Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion desktop/main/components/GameStatusButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,26 @@
</button>
</MenuItem>

<MenuItem
v-for="action in playActions"
Comment thread
DecDuck marked this conversation as resolved.
:key="action.id"
v-slot="{ active }"
>
<button
type="button"
@click="() => emit('play-action', action)"
:class="[
active
? 'bg-zinc-800 text-zinc-100 outline-none'
: 'text-zinc-300',
'w-full px-4 py-2 text-sm inline-flex justify-between items-center',
]"
>
{{ action.name }}
<PlayIcon class="size-4 text-purple-400" />
</button>
</MenuItem>

<MenuItem v-if="showOptions" v-slot="{ active }">
<button
@click="() => emit('options')"
Expand Down Expand Up @@ -113,14 +133,19 @@ import {
InstalledType,
type GameStatus,
} from "~/types.js";
import type { PlayAction } from "~/internal/plugins/types";
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/vue";
import { Cog6ToothIcon, TrashIcon } from "@heroicons/vue/24/outline";
import { ArrowsRightLeftIcon, ArrowUpTrayIcon } from "@heroicons/vue/24/solid";

const props = defineProps<{ status: GameStatus }>();
const props = defineProps<{
status: GameStatus;
playActions?: PlayAction[];
}>();
const emit = defineEmits<{
(e: "install"): void;
(e: "launch"): void;
(e: "play-action", action: PlayAction): void;
(e: "queue"): void;
(e: "uninstall"): void;
(e: "kill"): void;
Expand Down
13 changes: 9 additions & 4 deletions desktop/main/components/LibrarySearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@
as="div"
v-for="(nav, navIndex) in filteredNavigation"
:key="nav.id"
:class="['first:pt-0 last:pb-0', nav.tools && !filteredNavigation[navIndex - 1].tools ? 'mt-auto' : '']"
:class="[
'first:pt-0 last:pb-0',
nav.tools && !filteredNavigation[navIndex - 1]?.tools
? 'mt-auto'
: '',
]"
v-slot="{ open }"
:default-open="nav.deft"
>
Expand Down Expand Up @@ -90,10 +95,10 @@
<p
class="truncate text-[10px] font-bold uppercase font-display"
:class="[
getGameStatusStyleText(games[item.id].status.value)[0],
getGameStatusStyleText(games[item.id]!.status.value)[0],
]"
>
{{ getGameStatusStyleText(games[item.id].status.value)[1] }}
{{ getGameStatusStyleText(games[item.id]!.status.value)[1] }}
</p>
</div>
</div>
Expand Down Expand Up @@ -295,7 +300,7 @@ await new Promise<void>((r) => {
const navigation = computed(() =>
collections.value.map((collection) => {
const items = collection.entries.map(({ game }) => {
const status = games[game.id].status;
const status = games[game.id]!.status;

const isInstalled = computed(() => status.value.type != "Remote");

Expand Down
23 changes: 23 additions & 0 deletions desktop/main/components/PluginSlot.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<template>
<template v-for="item in slotComponents" :key="item.id">
<PluginErrorBoundary :plugin-id="item.pluginId">
<component :is="item.component" v-bind="context" />
</PluginErrorBoundary>
</template>
</template>

<script setup lang="ts">
import { computed } from "vue";
import type { UISlotName } from "~/internal/plugins/types";
import { clientPluginManager } from "~/internal/plugins/ClientPluginManager";
import PluginErrorBoundary from "~/internal/plugins/PluginErrorBoundary.vue";

const props = defineProps<{
name: UISlotName;
context?: Record<string, unknown>;
}>();

const slotComponents = computed(() => {
return clientPluginManager.slots[props.name] || [];
});
</script>
15 changes: 8 additions & 7 deletions desktop/main/composables/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const gameRegistry: { [key: string]: { game: Game; version: Ref<GameVersion | un
const gameStatusRegistry: { [key: string]: Ref<GameStatus> } = {};

export const parseStatus = (status: RawGameStatus): GameStatus => {
console.log(status[0]);
if (status[0]) {
return status[0];
}
Expand All @@ -33,16 +32,18 @@ export const useGame = async (gameId: string) => {
} = await invoke("fetch_game", {
gameId,
});
gameRegistry[gameId] = { game: data.game, version: ref(data.version) };
const entry = { game: data.game, version: ref(data.version) };
gameRegistry[gameId] = entry;
if (!gameStatusRegistry[gameId]) {
gameStatusRegistry[gameId] = ref(parseStatus(data.status));
const statusRef = ref(parseStatus(data.status));
gameStatusRegistry[gameId] = statusRef;

listen(`update_game/${gameId}`, (event) => {
const payload: {
status: RawGameStatus;
version?: GameVersion;
} = event.payload as any;
gameStatusRegistry[gameId].value = parseStatus(payload.status);
statusRef.value = parseStatus(payload.status);

/**
* I am not super happy about this.
Expand All @@ -52,14 +53,14 @@ export const useGame = async (gameId: string) => {
* on transient state updates.
*/
if (payload.version) {
gameRegistry[gameId].version.value = payload.version;
entry.version.value = payload.version;
}
});
}
}

const game = gameRegistry[gameId];
const status = gameStatusRegistry[gameId];
const game = gameRegistry[gameId]!;
const status = gameStatusRegistry[gameId]!;
return { ...game, status };
};

Expand Down
51 changes: 51 additions & 0 deletions desktop/main/composables/usePlugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ref, computed, watch, onMounted } from "vue";
import type { UISlotName, PlayAction } from "~/internal/plugins/types";
import { clientPluginManager } from "~/internal/plugins/ClientPluginManager";

export function usePluginManager() {
return clientPluginManager;
}

export function usePluginSlots(name: UISlotName) {
return computed(() => clientPluginManager.slots[name] || []);
}

export function usePlayActions(gameId: string | (() => string)) {
const actions = ref<PlayAction[]>([]);
const isLoading = ref(false);

const resolveId = () => (typeof gameId === "function" ? gameId() : gameId);

const refreshActions = async () => {
const id = resolveId();
if (!id) {
actions.value = [];
return;
}
isLoading.value = true;
try {
actions.value = await clientPluginManager.getPlayActions(id);
} catch (err) {
console.error(`Failed to load play actions for game ${id}:`, err);
actions.value = [];
} finally {
isLoading.value = false;
}
};

onMounted(() => {
refreshActions();
});

if (typeof gameId === "function") {
watch(gameId, () => {
refreshActions();
});
}

return {
actions,
isLoading,
refreshActions,
};
}
Loading
Loading