control helper and cute tags

This commit is contained in:
Trit0 2025-07-13 20:41:03 -04:00 committed by TristanBrault
parent af42d6c61c
commit 0ab4b1f832
19 changed files with 202 additions and 57 deletions

View File

@ -15,6 +15,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/types": "^7.18.10", "@babel/types": "^7.18.10",
"@iconify/vue": "^5.0.0",
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@vitejs/plugin-vue": "^6.0.0", "@vitejs/plugin-vue": "^6.0.0",
@ -512,6 +513,27 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@iconify/types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
"dev": true
},
"node_modules/@iconify/vue": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-5.0.0.tgz",
"integrity": "sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg==",
"dev": true,
"dependencies": {
"@iconify/types": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/cyberalien"
},
"peerDependencies": {
"vue": ">=3"
}
},
"node_modules/@isaacs/fs-minipass": { "node_modules/@isaacs/fs-minipass": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",

View File

@ -20,6 +20,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/types": "^7.18.10", "@babel/types": "^7.18.10",
"@iconify/vue": "^5.0.0",
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@vitejs/plugin-vue": "^6.0.0", "@vitejs/plugin-vue": "^6.0.0",

View File

@ -1 +1 @@
0d0407303e68412fce4ec212f727db35 063583417fe9e58bc0e2d6b154be7c49

View File

@ -25,9 +25,7 @@
@selectGame="store.selectGame" @selectGame="store.selectGame"
class="mb-2" class="mb-2"
/> />
<div class="bg-black min-h-10 border-1 border-gray-500"> <CurrentActionsHelp />
</div>
</div> </div>
</div> </div>
</div> </div>
@ -47,17 +45,16 @@ import OptionsModal from './components/OptionsModal.vue';
import QrModal from './components/QrModal.vue'; import QrModal from './components/QrModal.vue';
import LoadingModal from './components/LoadingModal.vue'; import LoadingModal from './components/LoadingModal.vue';
import { useKeyboardNavigation } from './utils/use-keyboard-navigation'; import { useKeyboardNavigation } from './utils/use-keyboard-navigation';
import { fetchGames } from './services/game-service';
import { useAppStore } from "./stores/app-store"; import { useAppStore } from "./stores/app-store";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { KeyboardManager } from "./utils/keyboard-manager"; import { KeyboardManager } from "./utils/keyboard-manager";
import CurrentActionsHelp from "./components/CurrentActionsHelp.vue";
const store = useAppStore(); const store = useAppStore();
const { const {
selectedTag, selectedTag,
selectedGame, selectedGame,
tags, tags,
games,
transitionDirection, transitionDirection,
qrLink, qrLink,
gameIsStarting, gameIsStarting,

View File

@ -0,0 +1,51 @@
<template>
<div class="bg-black min-h-10 border-1 border-gray-500">
<div v-if="currentActions" class="flex justify-around w-full h-full items-center px-4">
<span v-for="tip in currentActions"> {{ tip }} </span>
</div>
<div v-else>
<span>Chungus</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useAppStore } from "../stores/app-store";
import { storeToRefs } from "pinia";
import { computed } from "vue";
const store = useAppStore();
const {
currentInputDevice,
currentAvailableActions,
} = storeToRefs(store);
const currentActions = computed(() => {
const { order, ...rest } = currentAvailableActions.value as any;
console.log(order, rest);
if (!order) {
return "";
}
const possibleControllers = order.split(",");
const index = possibleControllers.findIndex(controller => controller === currentInputDevice.value);
const list = [];
console.log(index);
console.log(rest);
for (const [key, value] of Object.entries(rest)) {
console.log(key, value);
const split = key.split(",");
const myKey = split[index];
list.push(`${myKey}: ${rest[key]}`);
}
return list;
})
</script>
<style scoped>
</style>

View File

@ -0,0 +1,34 @@
<template>
<div class="dev-card flex border-1 rounded p-2 border-gray-300 gap-3 items-center" :tabindex="!!tabbable ? 1 : -1" @click="$emit('click')">
<div>
<img v-if="!!picture && !useIcon" :src="picture" alt="picture" class="w-14 rounded-full" />
<Icon v-else-if="!!picture" :icon="picture" class="w-10 h-10"/>
</div>
<div class="flex flex-col gap-1">
<span class="text-sm">{{ title }}</span>
<span>{{ content }}</span>
</div>
</div>
</template>
<script setup lang="ts">
import { Icon } from "@iconify/vue";
import { models } from "../../wailsjs/go/models";
import Developer = models.Developer;
defineProps<{
title: string,
content: string,
picture: string,
useIcon: boolean,
tabbable: boolean,
}>();
defineEmits(['click']);
</script>
<style scoped>
</style>

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="dev-card flex border-1 rounded p-2 border-gray-300 gap-3" tabindex="0" @click="$emit('qr', dev.link)"> <div class="dev-card flex border-1 rounded p-2 border-gray-300 gap-3" tabindex="0" @click="$emit('qr', dev.link)">
<img v-if="dev.picture" :src="dev.picture" class="w-14 rounded-full" /> <img v-if="dev.picture" :src="dev.picture" alt="picture" class="w-14 rounded-full" />
<img v-else src="../assets/images/person.png" class="w-14" /> <Icon v-else icon="fluent:person-circle-24-filled" class="w-14 h-14"/>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span>{{ dev.name }}</span> <span>{{ dev.name }}</span>
<span>{{ dev.role }}</span> <span>{{ dev.role }}</span>
@ -12,6 +12,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Icon } from "@iconify/vue";
import { models } from "../../wailsjs/go/models"; import { models } from "../../wailsjs/go/models";
import Developer = models.Developer; import Developer = models.Developer;

View File

@ -16,12 +16,12 @@
<!-- />--> <!-- />-->
<!-- </div>--> <!-- </div>-->
<div class="flex justify-between w-full space-y-1"> <div class="flex justify-between w-full space-y-1 gap-6">
<ImageCarousel <ImageCarousel
v-if="game.mediaPaths" v-if="game.mediaPaths"
:gameId="game.id" :gameId="game.id"
:links="game.mediaPaths" :links="game.mediaPaths"
class="basis-1/4" class="basis-3/4 h-full"
/> />
<!-- <LocalImage--> <!-- <LocalImage-->
<!-- v-for="(img, index) in [game.media_paths[0]]"--> <!-- v-for="(img, index) in [game.media_paths[0]]"-->
@ -31,50 +31,53 @@
<!-- :gameId="game.id"--> <!-- :gameId="game.id"-->
<!-- class="rounded-lg border border-gray-600 max-h-48 object-cover basis-1/4"/>--> <!-- class="rounded-lg border border-gray-600 max-h-48 object-cover basis-1/4"/>-->
<div class="flex flex-col basis-1/4"> <div class="flex flex-col basis-1/4 gap-2">
<div class="text-md text-white">Players: {{ game.players }}</div> <CustomCard title="Number of players" :content="game.players + ' players'" :tabbable=true :use-icon=true picture="fluent:person-24-filled" />
<div class="text-md text-white">Genres: {{ game.genres }}</div> <CustomCard title="Genres" :content="game.genres.split(',').map(s => toUpperCamelCase(s.trim())).join(', ')" :tabbable=true :use-icon=true picture="fluent:apps-list-detail-24-filled" />
<div class="text-md text-white">Collections: {{ game.collections }}</div> <CustomCard title="Collections" :content="game.collections" :tabbable=true :use-icon=true picture="fluent:collections-empty-24-filled" />
<div class="text-md text-white">Updated: {{ game.modification }}</div> <CustomCard title="Updated" :content="game.modification" :tabbable=true :use-icon=true picture="fluent:arrow-upload-24-filled" />
<div class="text-md text-white">Released: {{ game.release }}</div> <CustomCard title="Release" :content="game.release" :tabbable=true :use-icon=true picture="fluent:clock-24-filled" />
<div class="text-md text-white">Version: {{ game.version }}</div> <CustomCard title="Version" :content="game.version" :tabbable=true :use-icon=true picture="fluent:tag-24-filled" />
</div>
</div>
<div class="space-y-2 w-full flex items-start">
<div class="flex gap-2">
<button
v-if="game.executable"
id="btn-play"
key="play"
:style="buttonStyle"
@click="store.startSelectedGame()"
class="bg-blue-600 px-6 py-2 rounded hover:bg-blue-500 rounded-full text-2xl"
>
Play
</button>
<button
v-if="game.publicRepositoryLink"
key="repo"
:style="buttonStyle"
@click="$emit('qr', game.publicRepositoryLink)"
class="bg-blue-600 px-6 py-2 rounded hover:bg-blue-500 rounded-full text-2xl"
>
Repo
</button>
<button
v-if="game.itchLink"
key="itch"
:style="buttonStyle"
@click="$emit('qr', game.itchLink)"
class="bg-blue-600 px-6 py-2 rounded hover:bg-blue-500 rounded-full text-2xl"
>
Itch
</button>
</div> </div>
</div> </div>
<div class="text-lg w-full wrap min-h-15">{{ game.description }}</div> <div class="text-lg w-full wrap min-h-15">{{ game.description }}</div>
<div class="space-y-2 w-full flex items-start">
<div class="flex gap-2 mt-2">
<button
v-if="game.executable"
id="btn-play"
key="play"
:style="buttonStyle"
@click="store.startSelectedGame()"
class="bg-blue-600 px-6 py-2 rounded hover:bg-blue-500 rounded-full text-2xl"
>
Play
</button>
<div
v-for="(link, name) in {
Repo: game?.publicRepositoryLink,
Itch: game?.itchLink
}">
<button
v-if="!!link"
:key="name"
:style="buttonStyle"
class="bg-blue-600 px-6 py-2 rounded hover:bg-blue-500 rounded-full text-2xl"
@click="$emit('qr', link)"
>
{{ name }}
</button>
</div>
</div>
</div>
<div class="flex w-full items-start"> <div class="flex w-full items-start">
<div class="flex gap-3 flex-wrap max-w-full"> <div class="flex gap-3 flex-wrap max-w-full">
<DevCard <DevCard
@ -100,6 +103,8 @@ import { computed } from "vue";
import DevCard from "./DevCard.vue"; import DevCard from "./DevCard.vue";
import { useAppStore } from "../stores/app-store"; import { useAppStore } from "../stores/app-store";
import ImageCarousel from "./ImageCarousel.vue"; import ImageCarousel from "./ImageCarousel.vue";
import CustomCard from "./CustomCard.vue";
import { toUpperCamelCase } from "../utils/string-utils";
const store = useAppStore(); const store = useAppStore();

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<LocalImage v-if="imag" :src="imag" :alt="imag" :gameId="gameId" class="rounded-lg border border-gray-600 max-h-52 w-full object-cover" /> <LocalImage v-if="imag" :src="imag" :alt="imag" :gameId="gameId" class="rounded-lg border border-gray-600 max-h-124 w-full h-full object-cover" />
<div class="flex justify-center"> <div class="flex justify-center">
<div class="flex gap-2 relative top-1 points bg-black items-center justify-center px-2 py-1 rounded-full opacity-75"> <div class="flex gap-2 relative top-1 points bg-black items-center justify-center px-2 py-1 rounded-full opacity-75">
<div <div

View File

@ -17,6 +17,8 @@ export const useAppStore = defineStore('app', {
optionsOpen: false as boolean, optionsOpen: false as boolean,
gameIsStarting: false as boolean, gameIsStarting: false as boolean,
showSidebar: false as boolean, showSidebar: false as boolean,
currentInputDevice: 'controller' as 'controller' | 'keyboard',
currentAvailableActions: {} as object
}), }),
getters: { getters: {
filteredGames(state): Game[] { filteredGames(state): Game[] {

View File

@ -26,12 +26,10 @@ export class CarouselKeyContext extends KeyContext {
protected onKeyLeft() { protected onKeyLeft() {
super.onKeyLeft(); super.onKeyLeft();
if (this.store.selectedGameIndex === 0 && this.store.showSidebar) { if (this.store.selectedGameIndex === 0 && this.store.showSidebar) {
console.log("fjxhfjkdshjkfd")
KeyboardManager.switchContext("sidebar"); KeyboardManager.switchContext("sidebar");
this.store.selectGame(-1); this.store.selectGame(-1);
} }
else { else {
console.log("assdsds")
this.store.moveGameLeft(); this.store.moveGameLeft();
} }
} }
@ -58,4 +56,13 @@ export class CarouselKeyContext extends KeyContext {
} }
} }
} }
public setAvailableActions() {
this.store.currentAvailableActions = {
"order": "controller,keyboard",
"1,Enter": "Start",
"2,Space": "Details",
"Power,Escape": "See options"
};
}
} }

View File

@ -83,4 +83,12 @@ export class GamePreviewKeyContext extends KeyContext {
(document.activeElement as any).blur(); (document.activeElement as any).blur();
KeyboardManager.switchContext("carousel"); KeyboardManager.switchContext("carousel");
} }
setAvailableActions() {
this.store.currentAvailableActions = {
"order": "controller,keyboard",
"1,Enter": "Click",
"Power,Escape": "Back"
};
}
} }

View File

@ -7,6 +7,8 @@ export abstract class KeyContext {
protected store = useAppStore(); protected store = useAppStore();
public handleKey(event: KeyboardEvent): void { public handleKey(event: KeyboardEvent): void {
this.store.currentInputDevice = "keyboard";
switch (event.key) { switch (event.key) {
case 'ArrowRight': case 'ArrowRight':
this.onKeyRight(); this.onKeyRight();
@ -31,7 +33,7 @@ export abstract class KeyContext {
break; break;
case 'r': case 'r':
console.log("Loading games"); console.log("Loading games");
this.store.loadGames(); this.store.loadGames().then();
break; break;
default: default:
break; break;
@ -39,6 +41,8 @@ export abstract class KeyContext {
} }
public handleState(state: ControllerState) { public handleState(state: ControllerState) {
this.store.currentInputDevice = "controller";
if (state.joystick.x === 0) { if (state.joystick.x === 0) {
this.onKeyLeft() this.onKeyLeft()
} }
@ -94,4 +98,6 @@ export abstract class KeyContext {
protected onSpace(): void { protected onSpace(): void {
console.log('onSpace'); console.log('onSpace');
} }
public setAvailableActions() {}
} }

View File

@ -28,6 +28,7 @@ export class KeyboardManager {
console.log("Unknown context key " + name); console.log("Unknown context key " + name);
break; break;
} }
this.current?.setAvailableActions();
} }
static handle(event: KeyboardEvent) { static handle(event: KeyboardEvent) {

View File

@ -0,0 +1,4 @@
export function toUpperCamelCase(str: string): string {
const [firstChar, ...otherChars] = str;
return firstChar.toUpperCase() + otherChars.join('').toLowerCase();
}

View File

@ -4,6 +4,12 @@ import { EventsOn } from "../../wailsjs/runtime";
export function useKeyboardNavigation(): void { export function useKeyboardNavigation(): void {
onMounted(() => { onMounted(() => {
if (KeyboardManager.loaded) {
console.log("Unloading inputs");
window.removeEventListener('keydown', KeyboardManager.handle.bind(KeyboardManager));
KeyboardManager.loaded = false;
}
if (!KeyboardManager.loaded) { if (!KeyboardManager.loaded) {
console.log("Loading inputs") console.log("Loading inputs")
EventsOn("controller_change", KeyboardManager.handleState.bind(KeyboardManager)); EventsOn("controller_change", KeyboardManager.handleState.bind(KeyboardManager));

View File

@ -134,7 +134,7 @@ export function WindowIsFullscreen(): Promise<boolean>;
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
// Sets the width and height of the window. // Sets the width and height of the window.
export function WindowSetSize(width: number, height: number): void; export function WindowSetSize(width: number, height: number): Promise<Size>;
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
// Gets the width and height of the window. // Gets the width and height of the window.

2
go.mod
View File

@ -6,7 +6,7 @@ toolchain go1.23.5
require ( require (
github.com/karalabe/hid v1.0.0 github.com/karalabe/hid v1.0.0
github.com/wailsapp/wails/v2 v2.10.1 github.com/wailsapp/wails/v2 v2.9.2
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )

4
go.sum
View File

@ -69,8 +69,8 @@ github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyT
github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns= github.com/wailsapp/wails/v2 v2.9.2 h1:Xb5YRTos1w5N7DTMyYegWaGukCP2fIaX9WF21kPPF2k=
github.com/wailsapp/wails/v2 v2.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY= github.com/wailsapp/wails/v2 v2.9.2/go.mod h1:uehvlCwJSFcBq7rMCGfk4rxca67QQGsbg5Nm4m9UnBs=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=