better ui, uses fake data, wont be able to start games
This commit is contained in:
parent
2872bd6220
commit
789e0c1e13
22
app.go
22
app.go
@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
games []models.Game
|
games []models.Metadata
|
||||||
lastEmitTimestamp = time.Now().Add(-10 * time.Second)
|
lastEmitTimestamp = time.Now().Add(-10 * time.Second)
|
||||||
emitInterval = 150 * time.Millisecond
|
emitInterval = 150 * time.Millisecond
|
||||||
gameIsOpen = false
|
gameIsOpen = false
|
||||||
@ -30,9 +30,9 @@ func NewApp() *App {
|
|||||||
return &App{}
|
return &App{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// startup is called when the app starts. The context is saved
|
// Startup is called when the app starts. The context is saved
|
||||||
// so we can call the runtime methods
|
// so we can call the runtime methods
|
||||||
func (a *App) startup(ctx context.Context) {
|
func (a *App) Startup(ctx context.Context) {
|
||||||
a.ctx = ctx
|
a.ctx = ctx
|
||||||
inputs.Start(a.onControllerChange)
|
inputs.Start(a.onControllerChange)
|
||||||
provider.Update()
|
provider.Update()
|
||||||
@ -92,11 +92,21 @@ func (a *App) StartGame(id string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) LoadGames() []models.Game {
|
func (a *App) LoadGames() []models.Metadata {
|
||||||
games = provider.GetConjureGameInfo()
|
games = provider.GetConjureGameInfo()
|
||||||
return games
|
return games
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) LoadImage(gameId string, imageSrc string) provider.FileBlob {
|
func (a *App) LoadGamesNewModel() []models.Game {
|
||||||
return *provider.LoadImage(gameId, imageSrc)
|
return []models.Game{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) LoadImage(gameId string, imageSrc string) provider.FileBlob {
|
||||||
|
blob := provider.LoadImage(gameId, imageSrc)
|
||||||
|
|
||||||
|
if blob == nil {
|
||||||
|
return provider.FileBlob{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *blob
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
0d0407303e68412fce4ec212f727db35
|
0439ac4c00128949ad585ea9e2f68e8e
|
||||||
@ -1,38 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex h-screen w-screen bg-black text-white">
|
<div class="flex h-screen w-screen text-white">
|
||||||
<!-- Sidebar -->
|
<div class="flex flex-1 overflow-hidden" :style="backgroundStyle">
|
||||||
<Sidebar
|
<!-- Sidebar -->
|
||||||
:tags="tags"
|
<Sidebar
|
||||||
:selectedTag="selectedTag"
|
v-if="showSidebar"
|
||||||
@selectTag="store.selectTag"
|
:tags="tags"
|
||||||
@openOptions="optionsOpen = true"
|
:selectedTag="selectedTag"
|
||||||
/>
|
@selectTag="store.selectTag"
|
||||||
|
@openOptions="optionsOpen = true"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="flex flex-col flex-1 overflow-hidden">
|
<div class="flex flex-col flex-1 overflow-hidden">
|
||||||
<div class="flex-1 overflow-hidden">
|
|
||||||
<GamePreview
|
<GamePreview
|
||||||
:game="selectedGame"
|
:game="selectedGame"
|
||||||
@qr="store.showQr"
|
@qr="store.showQr"
|
||||||
/>
|
/>
|
||||||
|
<GameCarousel
|
||||||
|
:games="store.filteredGames"
|
||||||
|
:selectedGame="selectedGame"
|
||||||
|
:selectedTag="selectedTag"
|
||||||
|
:direction="transitionDirection"
|
||||||
|
@selectGame="store.selectGame"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<GameCarousel
|
|
||||||
:games="store.filteredGames"
|
|
||||||
:selectedGame="selectedGame"
|
|
||||||
:selectedTag="selectedTag"
|
|
||||||
:direction="transitionDirection"
|
|
||||||
@selectGame="store.selectGame"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<OptionsModal v-if="optionsOpen" @close="optionsOpen = false" />
|
<OptionsModal v-if="optionsOpen" @close="optionsOpen = false"/>
|
||||||
<QrModal v-if="qrLink" :link="qrLink" @close="qrLink = ''" />
|
<QrModal v-if="qrLink" :link="qrLink" @close="qrLink = ''"/>
|
||||||
<LoadingModal v-if="gameIsStarting" />
|
<LoadingModal v-if="gameIsStarting"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue';
|
import { onMounted, computed } from 'vue';
|
||||||
import Sidebar from './components/Sidebar.vue';
|
import Sidebar from './components/Sidebar.vue';
|
||||||
import GamePreview from './components/GamePreview.vue';
|
import GamePreview from './components/GamePreview.vue';
|
||||||
import GameCarousel from './components/GameCarousel.vue';
|
import GameCarousel from './components/GameCarousel.vue';
|
||||||
@ -46,15 +47,43 @@ import { storeToRefs } from "pinia";
|
|||||||
import { KeyboardManager } from "./utils/keyboard-manager";
|
import { KeyboardManager } from "./utils/keyboard-manager";
|
||||||
|
|
||||||
const store = useAppStore();
|
const store = useAppStore();
|
||||||
const { selectedTag, selectedGame, tags, games, transitionDirection, qrLink, gameIsStarting, optionsOpen } = storeToRefs(store);
|
const {
|
||||||
|
selectedTag,
|
||||||
|
selectedGame,
|
||||||
|
tags,
|
||||||
|
games,
|
||||||
|
transitionDirection,
|
||||||
|
qrLink,
|
||||||
|
gameIsStarting,
|
||||||
|
optionsOpen,
|
||||||
|
showSidebar
|
||||||
|
} = storeToRefs(store);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
games.value = await fetchGames();
|
games.value = await fetchGames();
|
||||||
tags.value = [...new Set(games.value.flatMap(game => game.Genres.split(",")))];
|
tags.value = [...new Set(games.value.flatMap(game => game.genres.split(",")))];
|
||||||
store.selectTag(tags.value[0])
|
store.selectTag(tags.value[0])
|
||||||
});
|
});
|
||||||
|
|
||||||
KeyboardManager.switchContext("sidebar")
|
// Create the gradient background style
|
||||||
|
const backgroundStyle = computed(() => {
|
||||||
|
const primary = selectedGame?.value?.color_scheme?.primary ?? "#4d97f8";
|
||||||
|
const secondary = selectedGame?.value?.color_scheme?.secondary ?? "#100a7d";
|
||||||
|
|
||||||
|
const backgroundImage = `linear-gradient(135deg, ${primary}, ${secondary})`;
|
||||||
|
// const backgroundImage = !!bgImage ? `url(${bgImage})` : `linear-gradient(135deg, ${primary}, ${secondary})`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
backgroundImage
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
if (showSidebar) {
|
||||||
|
KeyboardManager.switchContext("sidebar")
|
||||||
|
} else {
|
||||||
|
KeyboardManager.switchContext("carousel")
|
||||||
|
}
|
||||||
useKeyboardNavigation();
|
useKeyboardNavigation();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
BIN
frontend/src/assets/images/person.png
Normal file
BIN
frontend/src/assets/images/person.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
27
frontend/src/components/DevCard.vue
Normal file
27
frontend/src/components/DevCard.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<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-else src="../assets/images/person.png" class="w-14" />
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<span>{{ dev.name }}</span>
|
||||||
|
<span>{{ dev.role }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { models } from "../../wailsjs/go/models";
|
||||||
|
import Developer = models.Developer;
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
dev: Developer,
|
||||||
|
}>();
|
||||||
|
|
||||||
|
defineEmits(['qr']);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:key="game.Id"
|
:key="game.id"
|
||||||
:class="[
|
:class="[
|
||||||
'transition-transform transform cursor-pointer rounded-lg overflow-hidden border-4',
|
'transition-transform transform cursor-pointer rounded-lg overflow-hidden border-4',
|
||||||
selected ? 'scale-110 border-blue-500' : 'scale-100 border-transparent'
|
selected ? 'scale-110 border-blue-500' : 'scale-100 border-transparent'
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<LocalImage
|
<LocalImage
|
||||||
:src="game.ThumbnailPath"
|
:src="game.thumbnail_path"
|
||||||
class="h-32 w-48 object-cover"
|
class="h-32 w-48 object-cover"
|
||||||
:alt="game.Game"
|
:alt="game.title"
|
||||||
:gameId="game.Id"
|
:gameId="game.id"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<GameCard
|
<GameCard
|
||||||
v-for="game in games"
|
v-for="game in games"
|
||||||
:game="game"
|
:game="game"
|
||||||
:selected="game.Id === selectedGame?.Id"
|
:selected="game.id === selectedGame?.id"
|
||||||
@click="$emit('selectGame', game)"
|
@click="$emit('selectGame', game)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,37 +1,92 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-6 h-full overflow-auto">
|
<div class="p-6 h-full w-full overflow-auto flex flex-col items-center">
|
||||||
<div v-if="game" class="space-y-4">
|
<div v-if="game" class="space-y-4 flex flex-col items-center w-full max-w-400">
|
||||||
<h1 class="text-4xl font-bold">{{ game.Game }}</h1>
|
<img v-if="game.logo_path" :src="game.logo_path" :alt="game.title" class="h-72"/>
|
||||||
<div class="text-sm text-gray-400">{{ game.Genres }} - {{ game.Players }} players</div>
|
<h1 v-else class="text-4xl font-bold">{{ game.title }}</h1>
|
||||||
<p class="text-lg">{{ game.Description }}</p>
|
|
||||||
|
|
||||||
<div class="flex">
|
<!-- Actual carousel pls -->
|
||||||
<LocalImage
|
<!-- <div class="flex">-->
|
||||||
v-for="(img, index) in [game.ImagePath]"
|
<!-- <LocalImage-->
|
||||||
:key="index"
|
<!-- v-for="(img, index) in game.media_paths"-->
|
||||||
:src="img"
|
<!-- :key="index"-->
|
||||||
:alt="img"
|
<!-- :src="img"-->
|
||||||
:gameId="game.Id"
|
<!-- :alt="img"-->
|
||||||
class="rounded-lg border border-gray-600 max-h-48 object-cover"
|
<!-- :gameId="game.id"-->
|
||||||
|
<!-- class="rounded-lg border border-gray-600 max-h-48 object-cover"-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<div class="flex justify-between w-full space-y-1">
|
||||||
|
<ImageCarousel :gameId="game.id"
|
||||||
|
:links="game.media_paths"
|
||||||
|
class="basis-1/4"
|
||||||
/>
|
/>
|
||||||
|
<!-- <LocalImage-->
|
||||||
|
<!-- v-for="(img, index) in [game.media_paths[0]]"-->
|
||||||
|
<!-- :key="index"-->
|
||||||
|
<!-- :src="img"-->
|
||||||
|
<!-- :alt="img"-->
|
||||||
|
<!-- :gameId="game.id"-->
|
||||||
|
<!-- 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="text-md text-white">Players: {{ game.players }}</div>
|
||||||
|
<div class="text-md text-white">Genres: {{ game.genres }}</div>
|
||||||
|
<div class="text-md text-white">Collections: {{ game.collections }}</div>
|
||||||
|
<div class="text-md text-white">Updated: {{ game.modification }}</div>
|
||||||
|
<div class="text-md text-white">Released: {{ game.release }}</div>
|
||||||
|
<div class="text-md text-white">Version: {{ game.version }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<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">
|
<div class="flex gap-2 mt-2">
|
||||||
<button
|
<button
|
||||||
v-for="(link, name) in {
|
id="btn-play"
|
||||||
Repo: game.PublicRepositoryLink
|
key="play"
|
||||||
}"
|
:style="buttonStyle"
|
||||||
:key="name"
|
@click="store.startSelectedGame()"
|
||||||
class="bg-blue-600 px-3 py-1 rounded hover:bg-blue-500"
|
class="bg-blue-600 px-6 py-2 rounded hover:bg-blue-500 rounded-full text-2xl"
|
||||||
@click="$emit('qr', link)"
|
|
||||||
>
|
>
|
||||||
{{ name }} 🔗
|
Play
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="(link, name) in {
|
||||||
|
Repo: game?.public_repository_link ?? undefined,
|
||||||
|
Itch: game.itch_link
|
||||||
|
}">
|
||||||
|
<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 gap-3">
|
||||||
|
<DevCard
|
||||||
|
class="min-w-60"
|
||||||
|
v-for="dev in game.developers"
|
||||||
|
:dev="dev"
|
||||||
|
@qr="(value) => $emit('qr', value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-center text-gray-500">No game selected</div>
|
|
||||||
|
<div v-else class="h-full flex flex-col items-center justify-center text-gray-500">
|
||||||
|
<h1 class="text-3xl font-bold">No game selected</h1>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -39,11 +94,27 @@
|
|||||||
import { models } from "../../wailsjs/go/models";
|
import { models } from "../../wailsjs/go/models";
|
||||||
import Game = models.Game;
|
import Game = models.Game;
|
||||||
import LocalImage from "./LocalImage.vue";
|
import LocalImage from "./LocalImage.vue";
|
||||||
|
import { computed } from "vue";
|
||||||
|
import DevCard from "./DevCard.vue";
|
||||||
|
import { useAppStore } from "../stores/app-store";
|
||||||
|
import ImageCarousel from "./ImageCarousel.vue";
|
||||||
|
|
||||||
defineProps<{
|
const store = useAppStore();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
game: Game,
|
game: Game,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const buttonStyle = computed(() => {
|
||||||
|
const ternary = props.game?.color_scheme?.ternary ?? "#ffffff";
|
||||||
|
const secondary = props.game?.color_scheme?.primary ?? "#100a99";
|
||||||
|
|
||||||
|
return {
|
||||||
|
backgroundColor: secondary,
|
||||||
|
textColor: ternary
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
defineEmits(['qr']);
|
defineEmits(['qr']);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -51,6 +122,7 @@ defineEmits(['qr']);
|
|||||||
img {
|
img {
|
||||||
transition: transform 0.2s;
|
transition: transform 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
img:hover {
|
img:hover {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|||||||
57
frontend/src/components/ImageCarousel.vue
Normal file
57
frontend/src/components/ImageCarousel.vue
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<LocalImage :src="imag" :alt="imag" :gameId="gameId" class="rounded-lg border border-gray-600 max-h-52 w-full object-cover" />
|
||||||
|
<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
|
||||||
|
v-for="media in links"
|
||||||
|
:class="media === imag ? 'bg-white w-2 h-2 rounded-full opacity-100' : 'bg-white w-1 h-1 rounded-full opacity-100'"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
import LocalImage from "./LocalImage.vue";
|
||||||
|
import { models } from "../../wailsjs/go/models";
|
||||||
|
import Game = models.Game;
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
links: string[],
|
||||||
|
gameId: string
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const index = ref(0)
|
||||||
|
const imag = computed(() => {
|
||||||
|
return props.links[index.value]
|
||||||
|
});
|
||||||
|
|
||||||
|
function delay(time: number) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, time));
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeImage() {
|
||||||
|
delay(3000).then(() => {
|
||||||
|
index.value = (index.value + 1) % props.links?.length;
|
||||||
|
changeImage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
changeImage();
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.points {
|
||||||
|
top: -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@ -1,25 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-20 flex flex-col items-center bg-gray-900 py-4">
|
<div class="w-20 flex flex-col items-center py-4">
|
||||||
<div class="flex-1 space-y-4">
|
<div class="flex-1 space-y-4">
|
||||||
<button
|
<div
|
||||||
v-for="tag in tags"
|
v-for="tag in tags"
|
||||||
:key="tag"
|
:key="tag"
|
||||||
:class="[
|
:class="[
|
||||||
'w-12 h-12 rounded-full flex items-center justify-center',
|
'w-12 h-12 rounded-full flex items-center justify-center',
|
||||||
tag === selectedTag ? 'bg-blue-500' : 'bg-gray-700 hover:bg-gray-600'
|
tag === selectedTag ? 'bg-blue-500' : 'bg-gray-700 hover:bg-gray-600'
|
||||||
]"
|
]"
|
||||||
|
tabindex="-1"
|
||||||
@click="$emit('selectTag', tag)"
|
@click="$emit('selectTag', tag)"
|
||||||
>
|
>
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<div
|
||||||
class="mt-auto w-12 h-12 bg-gray-600 hover:bg-gray-500 rounded-full flex items-center justify-center"
|
class="mt-auto w-12 h-12 bg-gray-600 hover:bg-gray-500 rounded-full flex items-center justify-center"
|
||||||
|
tabindex="-1"
|
||||||
@click="$emit('openOptions')"
|
@click="$emit('openOptions')"
|
||||||
>
|
>
|
||||||
⚙️
|
⚙️
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -1,52 +1,130 @@
|
|||||||
import {LoadGames} from "../../wailsjs/go/main/App";
|
import {LoadGames} from "../../wailsjs/go/main/App";
|
||||||
import {models} from "../../wailsjs/go/models";
|
import {models} from "../../wailsjs/go/models";
|
||||||
|
import Metadata = models.Metadata;
|
||||||
import Game = models.Game;
|
import Game = models.Game;
|
||||||
|
import Developer = models.Developer;
|
||||||
|
|
||||||
// const localGames: Game[] = [
|
const localGames: Game[] = [
|
||||||
// {
|
new Game({
|
||||||
// id: 'g1',
|
id: "ddf1ab0c-d86e-442f-8fd8-cfe8a0dc0a52",
|
||||||
// title: 'Neon Rush',
|
title: "Soul Shaper",
|
||||||
// description: 'A fast-paced 2D platformer through glowing neon levels.',
|
version: "2.0.0",
|
||||||
// playerCount: '1',
|
description: "Laissez votre imagination prendre forme en maniant le pouvoir de la création contre la seule chose qui vous sépare du paradis. Manipulez l'espace et le temps contre votre adversaire dans Soul Shaper!",
|
||||||
// languages: ['English', 'French'],
|
players: "1-1",
|
||||||
// tags: ['platformer', 'neon', 'funny'],
|
release: "2023-12-15",
|
||||||
// media: [
|
modification: "2023-12-15",
|
||||||
// '/assets/neon-rush-1.png',
|
executable: "game\\Soul Shaper.exe",
|
||||||
// '/assets/neon-rush-2.png'
|
public_repository_link: "https://github.com/PFE033-ConjureOS/ConjureOS-SoulShaper",
|
||||||
// ],
|
genres: "action",
|
||||||
// thumbnail: '/assets/neon-rush-thumb.png',
|
developers: [
|
||||||
// links: {
|
new Developer({
|
||||||
// Website: 'https://neonrush.dev',
|
name: "William Gingras"
|
||||||
// Itch: 'https://hyperbyte.itch.io/neonrush'
|
}),
|
||||||
// },
|
new Developer({
|
||||||
// executablePath: "."
|
name: "Yussef Shehadeh"
|
||||||
// },
|
}),
|
||||||
// {
|
new Developer({
|
||||||
// id: 'g2',
|
name: "Leah Fortin"
|
||||||
// title: 'Ghost Bakery',
|
}),
|
||||||
// description: 'Bake spooky pastries in a haunted kitchen.',
|
new Developer({
|
||||||
// playerCount: '1-2',
|
name: "Cameron Lamoureux"
|
||||||
// languages: ['English'],
|
}),
|
||||||
// tags: ['cooking', 'funny', 'co-op'],
|
],
|
||||||
// media: [
|
thumbnail_path: "https://img.itch.zone/aW1hZ2UvMTkwMzc5MS8xMTgzNzY0Ny5wbmc=/original/r7iVIj.png",
|
||||||
// '/assets/ghost-bakery-1.jpg'
|
itch_link: "https://craftelia.itch.io/gamelab2023-blood-god",
|
||||||
// ],
|
media_paths: [
|
||||||
// thumbnail: '/assets/ghost-bakery-thumb.jpg',
|
"https://img.itch.zone/aW1hZ2UvMTkwMzc5MS8xMTgyMjc0Ni5wbmc=/original/65c%2FQT.png",
|
||||||
// links: {
|
"https://img.itch.zone/aW1hZ2UvMTkwMzc5MS8xMTgyMjc0Ny5wbmc=/original/4FiVFR.png",
|
||||||
// Itch: 'https://phantomforks.itch.io/ghostbakery'
|
"https://img.itch.zone/aW1hZ2UvMTkwMzc5MS8xMTgyMjc0OC5wbmc=/original/IkUG5I.png",
|
||||||
// },
|
"https://img.itch.zone/aW1hZ2UvMTkwMzc5MS8xMTgzNzY1My5wbmc=/original/jtOMly.png"
|
||||||
// executablePath: "."
|
],
|
||||||
// }
|
logo_path: "https://img.itch.zone/aW1nLzExODI4OTQyLnBuZw==/original/0MBnt3.png",
|
||||||
// ];
|
backgroundImagePath: "https://img.itch.zone/aW1nLzExODIyNzg4LnBuZw==/original/QVGL4L.png",
|
||||||
|
collections: "Ubi Gamelab",
|
||||||
|
color_scheme: {
|
||||||
|
primary: "#66258a",
|
||||||
|
secondary: "#80f071",
|
||||||
|
ternary: "#ffffff"
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
new Game({
|
||||||
|
id: "random-id",
|
||||||
|
title: "Froggin' Around",
|
||||||
|
version: "2.0.0",
|
||||||
|
description: "Incarnez deux grenouilles s'échappant d'un atelier magique. Travaillez ensemble et utilisez vos langues élastiques et rebondissantes pour progresser et résoudre des énigmes.",
|
||||||
|
players: "2-2",
|
||||||
|
release: "2025-04-13",
|
||||||
|
modification: "2023-05-05",
|
||||||
|
executable: "game\\FrogginAround.exe",
|
||||||
|
genres: "action, adventure, puzzle",
|
||||||
|
developers: [
|
||||||
|
new Developer({
|
||||||
|
name: "Jeanne Castonguay",
|
||||||
|
role: "Designer"
|
||||||
|
}),
|
||||||
|
new Developer({
|
||||||
|
name: "Mathieu Vézina",
|
||||||
|
role: "Developer",
|
||||||
|
link: "https://www.linkedin.com/in/mathieu-vezina/",
|
||||||
|
picture: "https://media.licdn.com/dms/image/v2/C4E03AQElTJL0iSscHg/profile-displayphoto-shrink_400_400/profile-displayphoto-shrink_400_400/0/1637863156486?e=1757548800&v=beta&t=oJQ1GfgqFx6dBgtIxS6GpsPp_eOYRGhVpAzmO1_ygr0"
|
||||||
|
}),
|
||||||
|
new Developer({
|
||||||
|
name: "Loïc Cyr",
|
||||||
|
role: "Developer"
|
||||||
|
}),
|
||||||
|
new Developer({
|
||||||
|
name: "Céline",
|
||||||
|
role: "Art"
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
thumbnail_path: "https://img.itch.zone/aW1nLzIwNzAwNDg1LnBuZw==/original/qGDxOj.png",
|
||||||
|
itch_link: "https://jeanne444.itch.io/froggin-around",
|
||||||
|
media_paths: [
|
||||||
|
"https://img.itch.zone/aW1hZ2UvMzQ2ODYzOS8yMDcxMDI1Mi5wbmc=/original/owXY2q.png",
|
||||||
|
"https://img.itch.zone/aW1hZ2UvMzQ2ODYzOS8yMDcwMDgwOC5wbmc=/original/PTa96E.png",
|
||||||
|
"https://img.itch.zone/aW1hZ2UvMzQ2ODYzOS8yMDcwMDgwNy5wbmc=/original/PXw75v.png",
|
||||||
|
"https://img.itch.zone/aW1hZ2UvMzQ2ODYzOS8yMDcwMDY2OC5wbmc=/original/MwyNjV.png",
|
||||||
|
"https://img.itch.zone/aW1hZ2UvMzQ2ODYzOS8yMDcwMDgwOS5wbmc=/original/FYGa9e.png",
|
||||||
|
"https://img.itch.zone/aW1hZ2UvMzQ2ODYzOS8yMDcyNDg1Mi5naWY=/original/%2BLoLem.gif"
|
||||||
|
],
|
||||||
|
logo_path: "https://img.itch.zone/aW1hZ2UvMzQ2ODYzOS8yMDg2MDc1OC5wbmc=/original/tgccKa.png",
|
||||||
|
collections: "Ubi Gamelab",
|
||||||
|
color_scheme: {
|
||||||
|
primary: "#cf2d30",
|
||||||
|
secondary: "#b3cf43",
|
||||||
|
ternary: "#ffffff"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
export async function fetchGames(): Promise<Game[]> {
|
export async function fetchGames(): Promise<Game[]> {
|
||||||
// const source = localStorage.getItem('dataSource') || 'local';
|
const source = localStorage.getItem('dataSource') || 'local';
|
||||||
// if (source === 'local') return localGames;
|
if (source === 'local') return localGames;
|
||||||
|
|
||||||
// TODO games should be loaded from and started from the wails/go code
|
|
||||||
const games = await LoadGames();
|
const games = await LoadGames();
|
||||||
for (const game of games) {
|
for (const game of games) {
|
||||||
console.log(game)
|
console.log(game)
|
||||||
}
|
}
|
||||||
return games;
|
return games.map(convertToNewFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToNewFormat(metadata: Metadata): Game {
|
||||||
|
return new Game({
|
||||||
|
id: metadata.Id,
|
||||||
|
title: metadata.Game,
|
||||||
|
description: metadata.Description,
|
||||||
|
collections: metadata.Collection,
|
||||||
|
thumbnail_path: metadata.ThumbnailPath,
|
||||||
|
media_paths: [metadata.ImagePath],
|
||||||
|
genres: metadata.Genres,
|
||||||
|
executable: metadata.Files,
|
||||||
|
release: metadata.Release,
|
||||||
|
modification: metadata.Modification,
|
||||||
|
version: metadata.Version,
|
||||||
|
players: metadata.Players,
|
||||||
|
public_repository_link: metadata.PublicRepositoryLink,
|
||||||
|
developers: metadata.Developers.split(",").map((dev) => new Developer({
|
||||||
|
name: dev
|
||||||
|
})),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,14 +10,15 @@ export const useAppStore = defineStore('app', {
|
|||||||
selectedTag: null as string | null,
|
selectedTag: null as string | null,
|
||||||
transitionDirection: 'down' as 'up' | 'down',
|
transitionDirection: 'down' as 'up' | 'down',
|
||||||
selectedGame: null as Game | null,
|
selectedGame: null as Game | null,
|
||||||
selectedGameIndex: -1,
|
selectedGameIndex: 0,
|
||||||
qrLink: '' as string,
|
qrLink: '' as string,
|
||||||
optionsOpen: false as boolean,
|
optionsOpen: false as boolean,
|
||||||
gameIsStarting: false as boolean
|
gameIsStarting: false as boolean,
|
||||||
|
showSidebar: false as boolean,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
filteredGames(state): Game[] {
|
filteredGames(state): Game[] {
|
||||||
return state.games.filter(game => game.Genres.split(",").includes(state.selectedTag ?? ''));
|
return state.games.filter(game => game.genres.split(",").includes(state.selectedTag ?? ''));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@ -39,14 +40,11 @@ export const useAppStore = defineStore('app', {
|
|||||||
},
|
},
|
||||||
selectGame(index: number) {
|
selectGame(index: number) {
|
||||||
const games = this.filteredGames;
|
const games = this.filteredGames;
|
||||||
if (index >= 0 && index < games.length) {
|
console.log(index)
|
||||||
this.selectedGameIndex = index;
|
index = mod(index, games.length);
|
||||||
this.selectedGame = games[index];
|
console.log(index)
|
||||||
}
|
this.selectedGameIndex = index;
|
||||||
else {
|
this.selectedGame = games[index]
|
||||||
this.selectedGameIndex = -1;
|
|
||||||
this.selectedGame = null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
selectTag(tag: string) {
|
selectTag(tag: string) {
|
||||||
this.selectedTag = tag;
|
this.selectedTag = tag;
|
||||||
@ -66,4 +64,8 @@ export const useAppStore = defineStore('app', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function mod(n, m) {
|
||||||
|
return ((n % m) + m) % m;
|
||||||
|
}
|
||||||
@ -1,14 +1,6 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
html {
|
|
||||||
background-color: rgba(27, 38, 54, 1);
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
|
||||||
color: white;
|
|
||||||
font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
||||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||||
sans-serif;
|
sans-serif;
|
||||||
@ -24,5 +16,6 @@ body {
|
|||||||
|
|
||||||
#app {
|
#app {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
text-align: center;
|
background-color: black;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,11 @@ export class ImageManager {
|
|||||||
static Dictionary: {[key: string]: string} = {}
|
static Dictionary: {[key: string]: string} = {}
|
||||||
|
|
||||||
public static async getImage(gameId: string, src: string): Promise<string> {
|
public static async getImage(gameId: string, src: string): Promise<string> {
|
||||||
|
|
||||||
|
if (src.startsWith("http")) {
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|
||||||
const id = gameId + "\\" + src;
|
const id = gameId + "\\" + src;
|
||||||
console.log(gameId, src, id)
|
console.log(gameId, src, id)
|
||||||
if (this.Dictionary[id])
|
if (this.Dictionary[id])
|
||||||
|
|||||||
@ -6,12 +6,16 @@ export class CarouselKeyContext extends KeyContext {
|
|||||||
|
|
||||||
protected onKeyUp() {
|
protected onKeyUp() {
|
||||||
super.onKeyUp();
|
super.onKeyUp();
|
||||||
this.store.moveTagUp();
|
if (this.store.showSidebar) {
|
||||||
|
this.store.moveTagUp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onKeyDown() {
|
protected onKeyDown() {
|
||||||
super.onKeyDown();
|
super.onKeyDown();
|
||||||
this.store.moveTagDown();
|
if (this.store.showSidebar) {
|
||||||
|
this.store.moveTagDown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onKeyRight() {
|
protected onKeyRight() {
|
||||||
@ -21,11 +25,13 @@ export class CarouselKeyContext extends KeyContext {
|
|||||||
|
|
||||||
protected onKeyLeft() {
|
protected onKeyLeft() {
|
||||||
super.onKeyLeft();
|
super.onKeyLeft();
|
||||||
if (this.store.selectedGameIndex === 0) {
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,11 +39,23 @@ export class CarouselKeyContext extends KeyContext {
|
|||||||
protected onEscape() {
|
protected onEscape() {
|
||||||
super.onEscape();
|
super.onEscape();
|
||||||
this.store.optionsOpen = true;
|
this.store.optionsOpen = true;
|
||||||
KeyboardManager.switchContext('options');
|
KeyboardManager.switchContext("options");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onEnter() {
|
protected onEnter() {
|
||||||
super.onEnter();
|
super.onEnter();
|
||||||
this.store.startSelectedGame();
|
this.store.startSelectedGame().then();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onSpace() {
|
||||||
|
super.onSpace();
|
||||||
|
if (this.store.selectedGameIndex !== -1) {
|
||||||
|
KeyboardManager.switchContext("preview");
|
||||||
|
const playBtn = document.getElementById("btn-play");
|
||||||
|
|
||||||
|
if (playBtn) {
|
||||||
|
playBtn.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,25 +1,86 @@
|
|||||||
import { KeyContext } from "./key-context";
|
import { KeyContext } from "./key-context";
|
||||||
|
import { KeyboardManager } from "../keyboard-manager";
|
||||||
|
|
||||||
export class GamePreviewKeyContext extends KeyContext {
|
export class GamePreviewKeyContext extends KeyContext {
|
||||||
readonly name: string = "GamePreviewKeyContext";
|
readonly name: string = "GamePreviewKeyContext";
|
||||||
|
|
||||||
protected onKeyUp() {
|
protected onKeyUp() {
|
||||||
super.onKeyUp();
|
super.onKeyUp();
|
||||||
|
this.focusLastElement()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onKeyDown() {
|
protected onKeyDown() {
|
||||||
super.onKeyDown();
|
super.onKeyDown();
|
||||||
|
this.focusNextElement();
|
||||||
|
// const activeElement = document.activeElement;
|
||||||
|
// activeElement.nextSibling.parentElement.focus();
|
||||||
|
// const tabEvent = new KeyboardEvent('keydown', {
|
||||||
|
// key: 'Tab',
|
||||||
|
// code: 'Tab',
|
||||||
|
// bubbles: true, // Allow the event to bubble up the DOM tree
|
||||||
|
// cancelable: true // Allow the event to be canceled
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// // Dispatch the event on the desired element
|
||||||
|
// window.dispatchEvent(tabEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private focusNextElement() {
|
||||||
|
const focusableSelectors = [
|
||||||
|
'a[href]',
|
||||||
|
'button:not([disabled])',
|
||||||
|
'input:not([disabled]):not([type="hidden"])',
|
||||||
|
'select:not([disabled])',
|
||||||
|
'textarea:not([disabled])',
|
||||||
|
'[tabindex]:not([tabindex="-1"])'
|
||||||
|
];
|
||||||
|
|
||||||
|
const focusableElements = Array.from(document.querySelectorAll(focusableSelectors.join(',')))
|
||||||
|
.filter(el => (el as any).offsetParent !== null); // filter out hidden elements
|
||||||
|
|
||||||
|
const currentIndex = focusableElements.indexOf(document.activeElement);
|
||||||
|
const nextIndex = (currentIndex + 1) % focusableElements.length;
|
||||||
|
|
||||||
|
(focusableElements[nextIndex] as any).focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private focusLastElement() {
|
||||||
|
const focusableSelectors = [
|
||||||
|
'a[href]',
|
||||||
|
'button:not([disabled])',
|
||||||
|
'input:not([disabled]):not([type="hidden"])',
|
||||||
|
'select:not([disabled])',
|
||||||
|
'textarea:not([disabled])',
|
||||||
|
'[tabindex]:not([tabindex="-1"])'
|
||||||
|
];
|
||||||
|
|
||||||
|
const focusableElements = Array.from(document.querySelectorAll(focusableSelectors.join(',')))
|
||||||
|
.filter(el => (el as any).offsetParent !== null); // filter out hidden elements
|
||||||
|
|
||||||
|
const currentIndex = focusableElements.indexOf(document.activeElement);
|
||||||
|
const nextIndex = (currentIndex - 1) % focusableElements.length;
|
||||||
|
|
||||||
|
(focusableElements[nextIndex] as any).focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onKeyRight() {
|
protected onKeyRight() {
|
||||||
super.onKeyRight();
|
super.onKeyRight();
|
||||||
|
this.focusNextElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onKeyLeft() {
|
||||||
|
super.onKeyLeft();
|
||||||
|
this.focusLastElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onEnter() {
|
protected onEnter() {
|
||||||
super.onEnter();
|
super.onEnter();
|
||||||
|
(document.activeElement as any).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onEscape() {
|
protected onEscape() {
|
||||||
super.onEscape();
|
super.onEscape();
|
||||||
|
(document.activeElement as any).blur();
|
||||||
|
KeyboardManager.switchContext("carousel");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -26,6 +26,9 @@ export abstract class KeyContext {
|
|||||||
case 'Enter':
|
case 'Enter':
|
||||||
this.onEnter();
|
this.onEnter();
|
||||||
break;
|
break;
|
||||||
|
case ' ':
|
||||||
|
this.onSpace();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -50,6 +53,10 @@ export abstract class KeyContext {
|
|||||||
this.onEnter()
|
this.onEnter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((state.buttons & 0x08) !== 0) {
|
||||||
|
this.onSpace()
|
||||||
|
}
|
||||||
|
|
||||||
// TODO should be 0x01 when the power button will work
|
// TODO should be 0x01 when the power button will work
|
||||||
if ((state.buttons & 0x02) !== 0) {
|
if ((state.buttons & 0x02) !== 0) {
|
||||||
this.onEscape()
|
this.onEscape()
|
||||||
@ -79,4 +86,8 @@ export abstract class KeyContext {
|
|||||||
protected onEnter(): void {
|
protected onEnter(): void {
|
||||||
console.log('onEnter');
|
console.log('onEnter');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected onSpace(): void {
|
||||||
|
console.log('onSpace');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -3,15 +3,31 @@ import { CarouselKeyContext } from "./key-contexts/carousel-key-context";
|
|||||||
import { KeyContext } from "./key-contexts/key-context";
|
import { KeyContext } from "./key-contexts/key-context";
|
||||||
import { OptionsKeyContext } from "./key-contexts/options-key-context";
|
import { OptionsKeyContext } from "./key-contexts/options-key-context";
|
||||||
import { SidebarKeyContext } from "./key-contexts/sidebar-key-context";
|
import { SidebarKeyContext } from "./key-contexts/sidebar-key-context";
|
||||||
|
import { GamePreviewKeyContext } from "./key-contexts/game-preview-key-context";
|
||||||
|
|
||||||
export class KeyboardManager {
|
export class KeyboardManager {
|
||||||
private static current?: KeyContext;
|
private static current?: KeyContext;
|
||||||
|
public static loaded = false;
|
||||||
|
|
||||||
static switchContext(name: 'sidebar' | 'carousel' | 'options') {
|
static switchContext(name: 'sidebar' | 'carousel' | 'options' | 'preview') {
|
||||||
console.log("Switching context to " + name);
|
console.log("Switching context to " + name);
|
||||||
if (name === 'sidebar') this.current = new SidebarKeyContext();
|
switch (name) {
|
||||||
else if (name === 'carousel') this.current = new CarouselKeyContext();
|
case 'carousel':
|
||||||
else if (name === 'options') this.current = new OptionsKeyContext();
|
this.current = new CarouselKeyContext();
|
||||||
|
break;
|
||||||
|
case 'sidebar':
|
||||||
|
this.current = new SidebarKeyContext();
|
||||||
|
break;
|
||||||
|
case 'preview':
|
||||||
|
this.current = new GamePreviewKeyContext();
|
||||||
|
break;
|
||||||
|
case 'options':
|
||||||
|
this.current = new OptionsKeyContext();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("Unknown context key " + name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static handle(event: KeyboardEvent) {
|
static handle(event: KeyboardEvent) {
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { onMounted, onBeforeUnmount } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
import { KeyboardManager } from "./keyboard-manager";
|
import { KeyboardManager } from "./keyboard-manager";
|
||||||
import { EventsOn } from "../../wailsjs/runtime";
|
import { EventsOn } from "../../wailsjs/runtime";
|
||||||
|
|
||||||
export function useKeyboardNavigation(): void {
|
export function useKeyboardNavigation(): void {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
EventsOn("controller_change", KeyboardManager.handleState.bind(KeyboardManager));
|
if (!KeyboardManager.loaded) {
|
||||||
window.addEventListener('keydown', KeyboardManager.handle.bind(KeyboardManager));
|
console.log("Loading inputs")
|
||||||
});
|
EventsOn("controller_change", KeyboardManager.handleState.bind(KeyboardManager));
|
||||||
|
window.addEventListener('keydown', KeyboardManager.handle.bind(KeyboardManager));
|
||||||
onBeforeUnmount(() => {
|
KeyboardManager.loaded = true;
|
||||||
window.removeEventListener('keydown', KeyboardManager.handle);
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
4
frontend/wailsjs/go/main/App.d.ts
vendored
4
frontend/wailsjs/go/main/App.d.ts
vendored
@ -3,7 +3,9 @@
|
|||||||
import {models} from '../models';
|
import {models} from '../models';
|
||||||
import {provider} from '../models';
|
import {provider} from '../models';
|
||||||
|
|
||||||
export function LoadGames():Promise<Array<models.Game>>;
|
export function LoadGames():Promise<Array<models.Metadata>>;
|
||||||
|
|
||||||
|
export function LoadGamesNewModel():Promise<Array<models.Game>>;
|
||||||
|
|
||||||
export function LoadImage(arg1:string,arg2:string):Promise<provider.FileBlob>;
|
export function LoadImage(arg1:string,arg2:string):Promise<provider.FileBlob>;
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,10 @@ export function LoadGames() {
|
|||||||
return window['go']['main']['App']['LoadGames']();
|
return window['go']['main']['App']['LoadGames']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function LoadGamesNewModel() {
|
||||||
|
return window['go']['main']['App']['LoadGamesNewModel']();
|
||||||
|
}
|
||||||
|
|
||||||
export function LoadImage(arg1, arg2) {
|
export function LoadImage(arg1, arg2) {
|
||||||
return window['go']['main']['App']['LoadImage'](arg1, arg2);
|
return window['go']['main']['App']['LoadImage'](arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,108 @@
|
|||||||
export namespace models {
|
export namespace models {
|
||||||
|
|
||||||
|
export class ColorScheme {
|
||||||
|
primary: string;
|
||||||
|
secondary: string;
|
||||||
|
ternary: string;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new ColorScheme(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.primary = source["primary"];
|
||||||
|
this.secondary = source["secondary"];
|
||||||
|
this.ternary = source["ternary"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Developer {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
link: string;
|
||||||
|
picture: string;
|
||||||
|
role: string;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new Developer(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.id = source["id"];
|
||||||
|
this.name = source["name"];
|
||||||
|
this.link = source["link"];
|
||||||
|
this.picture = source["picture"];
|
||||||
|
this.role = source["role"];
|
||||||
|
}
|
||||||
|
}
|
||||||
export class Game {
|
export class Game {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
version: string;
|
||||||
|
players: string;
|
||||||
|
release: string;
|
||||||
|
modification: string;
|
||||||
|
public_repository_link: string;
|
||||||
|
itch_link: string;
|
||||||
|
genres: string;
|
||||||
|
collections: string;
|
||||||
|
executable: string;
|
||||||
|
thumbnail_path: string;
|
||||||
|
logo_path: string;
|
||||||
|
backgroundImagePath: string;
|
||||||
|
media_paths: string[];
|
||||||
|
audio_path: string;
|
||||||
|
developers: Developer[];
|
||||||
|
color_scheme: ColorScheme;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new Game(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.id = source["id"];
|
||||||
|
this.title = source["title"];
|
||||||
|
this.description = source["description"];
|
||||||
|
this.version = source["version"];
|
||||||
|
this.players = source["players"];
|
||||||
|
this.release = source["release"];
|
||||||
|
this.modification = source["modification"];
|
||||||
|
this.public_repository_link = source["public_repository_link"];
|
||||||
|
this.itch_link = source["itch_link"];
|
||||||
|
this.genres = source["genres"];
|
||||||
|
this.collections = source["collections"];
|
||||||
|
this.executable = source["executable"];
|
||||||
|
this.thumbnail_path = source["thumbnail_path"];
|
||||||
|
this.logo_path = source["logo_path"];
|
||||||
|
this.backgroundImagePath = source["backgroundImagePath"];
|
||||||
|
this.media_paths = source["media_paths"];
|
||||||
|
this.audio_path = source["audio_path"];
|
||||||
|
this.developers = this.convertValues(source["developers"], Developer);
|
||||||
|
this.color_scheme = this.convertValues(source["color_scheme"], ColorScheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||||
|
if (!a) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (a.slice && a.map) {
|
||||||
|
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||||
|
} else if ("object" === typeof a) {
|
||||||
|
if (asMap) {
|
||||||
|
for (const key of Object.keys(a)) {
|
||||||
|
a[key] = new classs(a[key]);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
return new classs(a);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Metadata {
|
||||||
Collection: string;
|
Collection: string;
|
||||||
Launch: string;
|
Launch: string;
|
||||||
Id: string;
|
Id: string;
|
||||||
@ -16,10 +118,9 @@ export namespace models {
|
|||||||
PublicRepositoryLink: string;
|
PublicRepositoryLink: string;
|
||||||
Genres: string;
|
Genres: string;
|
||||||
Developers: string;
|
Developers: string;
|
||||||
Title: string;
|
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new Game(source);
|
return new Metadata(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
@ -39,7 +140,6 @@ export namespace models {
|
|||||||
this.PublicRepositoryLink = source["PublicRepositoryLink"];
|
this.PublicRepositoryLink = source["PublicRepositoryLink"];
|
||||||
this.Genres = source["Genres"];
|
this.Genres = source["Genres"];
|
||||||
this.Developers = source["Developers"];
|
this.Developers = source["Developers"];
|
||||||
this.Title = source["Title"];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
frontend/wailsjs/runtime/runtime.d.ts
vendored
2
frontend/wailsjs/runtime/runtime.d.ts
vendored
@ -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
2
go.mod
@ -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
4
go.sum
@ -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=
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -21,13 +20,11 @@ func GetDefaultConjureOsDirectory() string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
fmt.Println("User config dir:", configDir)
|
|
||||||
|
|
||||||
cacheDir, err := os.UserCacheDir()
|
cacheDir, err := os.UserCacheDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
fmt.Println("User cache dir:", cacheDir)
|
|
||||||
|
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
// I want the data to be store in LocalAppData not RoamingAppData
|
// I want the data to be store in LocalAppData not RoamingAppData
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
type Game struct {
|
type Metadata struct {
|
||||||
Collection string
|
Collection string
|
||||||
Launch string
|
Launch string
|
||||||
Id string
|
Id string
|
||||||
@ -16,5 +16,40 @@ type Game struct {
|
|||||||
PublicRepositoryLink string
|
PublicRepositoryLink string
|
||||||
Genres string
|
Genres string
|
||||||
Developers string
|
Developers string
|
||||||
Title string
|
}
|
||||||
|
|
||||||
|
type Game struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Players string `json:"players"`
|
||||||
|
Release string `json:"release"`
|
||||||
|
Modification string `json:"modification"`
|
||||||
|
PublicRepositoryLink string `json:"public_repository_link"`
|
||||||
|
ItchLink string `json:"itch_link"`
|
||||||
|
Genres string `json:"genres"`
|
||||||
|
Collections string `json:"collections"`
|
||||||
|
Executable string `json:"executable"`
|
||||||
|
ThumbnailPath string `json:"thumbnail_path"`
|
||||||
|
LogoPath string `json:"logo_path"`
|
||||||
|
BackgroundImagePath string `json:"backgroundImagePath"`
|
||||||
|
MediaPaths []string `json:"media_paths"`
|
||||||
|
AudioPath string `json:"audio_path"`
|
||||||
|
Developers []Developer `json:"developers"`
|
||||||
|
ColorScheme ColorScheme `json:"color_scheme"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Developer struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Link string `json:"link"`
|
||||||
|
Picture string `json:"picture"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ColorScheme struct {
|
||||||
|
Primary string `json:"primary"`
|
||||||
|
Secondary string `json:"secondary"`
|
||||||
|
Ternary string `json:"ternary"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ func LoadImage(gameId string, imageSrc string) *FileBlob {
|
|||||||
imagePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), gameId, imageSrc)
|
imagePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), gameId, imageSrc)
|
||||||
blob, err := GetFileBlob(imagePath)
|
blob, err := GetFileBlob(imagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return blob
|
return blob
|
||||||
|
|||||||
@ -160,7 +160,7 @@ func extractZipsInFolder(folder string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtractGame(game models.Game) string {
|
func ExtractGame(game models.Metadata) string {
|
||||||
info, err := os.Stat(filepath.Join(config.GetDefaultConjureGamesDirectory(), game.Id))
|
info, err := os.Stat(filepath.Join(config.GetDefaultConjureGamesDirectory(), game.Id))
|
||||||
if err != nil || !info.IsDir() {
|
if err != nil || !info.IsDir() {
|
||||||
gamePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), fmt.Sprintf("%s.conj", game.Id))
|
gamePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), fmt.Sprintf("%s.conj", game.Id))
|
||||||
@ -180,7 +180,7 @@ func ExtractGame(game models.Game) string {
|
|||||||
return gamePath
|
return gamePath
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConjureGameInfo() []models.Game {
|
func GetConjureGameInfo() []models.Metadata {
|
||||||
|
|
||||||
gamePath := config.GetDefaultConjureGamesDirectory()
|
gamePath := config.GetDefaultConjureGamesDirectory()
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ func GetConjureGameInfo() []models.Game {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var games []models.Game
|
var games []models.Metadata
|
||||||
|
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
if e.IsDir() {
|
if e.IsDir() {
|
||||||
@ -246,12 +246,12 @@ func printIndentedPath(path string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func readFolder(entry fs.DirEntry, path string) []models.Game {
|
func readFolder(entry fs.DirEntry, path string) []models.Metadata {
|
||||||
newPath := path + "/" + entry.Name()
|
newPath := path + "/" + entry.Name()
|
||||||
entries, err := os.ReadDir(newPath)
|
entries, err := os.ReadDir(newPath)
|
||||||
check(err)
|
check(err)
|
||||||
|
|
||||||
var games []models.Game
|
var games []models.Metadata
|
||||||
|
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
if e.IsDir() {
|
if e.IsDir() {
|
||||||
@ -268,14 +268,14 @@ func readFolder(entry fs.DirEntry, path string) []models.Game {
|
|||||||
return games
|
return games
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseGameInfoFromFile(path string) models.Game {
|
func parseGameInfoFromFile(path string) models.Metadata {
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
check(err)
|
check(err)
|
||||||
return parseGameInfo(data)
|
return parseGameInfo(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseGameInfo(data []byte) models.Game {
|
func parseGameInfo(data []byte) models.Metadata {
|
||||||
game := models.Game{}
|
game := models.Metadata{}
|
||||||
err := yaml.Unmarshal(data, &game)
|
err := yaml.Unmarshal(data, &game)
|
||||||
check(err)
|
check(err)
|
||||||
return game
|
return game
|
||||||
|
|||||||
31
main.go
31
main.go
@ -2,6 +2,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2"
|
"github.com/wailsapp/wails/v2"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
@ -11,6 +15,27 @@ import (
|
|||||||
//go:embed all:frontend/dist
|
//go:embed all:frontend/dist
|
||||||
var assets embed.FS
|
var assets embed.FS
|
||||||
|
|
||||||
|
type FileLoader struct {
|
||||||
|
http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileLoader() *FileLoader {
|
||||||
|
return &FileLoader{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *FileLoader) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
|
var err error
|
||||||
|
requestedFilename := strings.TrimPrefix(req.URL.Path, "/")
|
||||||
|
println("Requesting file:", requestedFilename)
|
||||||
|
fileData, err := os.ReadFile(requestedFilename)
|
||||||
|
if err != nil {
|
||||||
|
res.WriteHeader(http.StatusBadRequest)
|
||||||
|
res.Write([]byte(fmt.Sprintf("Could not load file %s", requestedFilename)))
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Write(fileData)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Create an instance of the app structure
|
// Create an instance of the app structure
|
||||||
app := NewApp()
|
app := NewApp()
|
||||||
@ -22,10 +47,10 @@ func main() {
|
|||||||
Height: 768,
|
Height: 768,
|
||||||
Fullscreen: true,
|
Fullscreen: true,
|
||||||
AssetServer: &assetserver.Options{
|
AssetServer: &assetserver.Options{
|
||||||
Assets: assets,
|
Assets: assets,
|
||||||
|
Handler: NewFileLoader(),
|
||||||
},
|
},
|
||||||
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
|
OnStartup: app.Startup,
|
||||||
OnStartup: app.startup,
|
|
||||||
Bind: []interface{}{
|
Bind: []interface{}{
|
||||||
app,
|
app,
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user