From 2acc9b0133331f41eeadfadbdc6d0dea65efd07d Mon Sep 17 00:00:00 2001 From: Trit0 Date: Sun, 29 Jun 2025 16:51:47 -0400 Subject: [PATCH 01/24] Supposed to load games --- app.go | 5 +- frontend/package.json.md5 | 2 +- frontend/src/App.vue | 2 +- frontend/src/components/GameCarousel.vue | 12 +-- frontend/src/models/game.ts | 22 ----- frontend/src/services/game-service.ts | 91 ++++++++++--------- frontend/src/stores/app-store.ts | 5 +- frontend/wailsjs/go/main/App.d.ts | 4 +- frontend/wailsjs/go/models.ts | 40 +++++++-- go.mod | 40 +++++---- go.sum | 107 +++++++++++------------ lib/config/config.go | 51 +++++++++++ lib/models/game.go | 20 +++++ lib/provider/provider.go | 52 ++++++----- 14 files changed, 268 insertions(+), 185 deletions(-) delete mode 100644 frontend/src/models/game.ts create mode 100644 lib/config/config.go create mode 100644 lib/models/game.go diff --git a/app.go b/app.go index 0d0e6f3..79c976f 100644 --- a/app.go +++ b/app.go @@ -1,6 +1,7 @@ package main import ( + "conjure-os/lib/models" "context" "fmt" @@ -37,6 +38,6 @@ func (a *App) Greet(name string) string { func (a *App) SelectGame(id string) { } -func (a *App) LoadGames() []provider.Game { - return provider.ObtainConjureGameInfo() +func (a *App) LoadGames() []models.Game { + return provider.GetConjureGameInfo() } diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 3dd94f5..7f01a6d 100755 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -1f77c5bc2ac4189b53ca32c331845b25 \ No newline at end of file +0439ac4c00128949ad585ea9e2f68e8e \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue index bdced78..fdb39e6 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -50,7 +50,7 @@ const optionsOpen = ref(false); onMounted(async () => { games.value = await fetchGames(); - tags.value = [...new Set(games.value.flatMap(game => game.tags))]; + tags.value = [...new Set(games.value.flatMap(game => game.Genres))]; selectedTag.value = tags.value[0]; selectedGame.value = store.filteredGames[0]; }); diff --git a/frontend/src/components/GameCarousel.vue b/frontend/src/components/GameCarousel.vue index 2465388..93bf1c2 100644 --- a/frontend/src/components/GameCarousel.vue +++ b/frontend/src/components/GameCarousel.vue @@ -4,17 +4,17 @@
@@ -33,7 +33,9 @@ From 938e8e31953deff1f90f240567e25235cbb248fe Mon Sep 17 00:00:00 2001 From: Trit0 Date: Sun, 29 Jun 2025 18:20:53 -0400 Subject: [PATCH 07/24] start game perchance? --- app.go | 46 +++++--- frontend/src/stores/app-store.ts | 9 ++ .../key-contexts/carousel-key-context.ts | 1 + frontend/wailsjs/go/main/App.d.ts | 4 +- frontend/wailsjs/go/main/App.js | 8 +- lib/provider/provider.go | 100 ++++++++++++++++++ 6 files changed, 147 insertions(+), 21 deletions(-) diff --git a/app.go b/app.go index 79c976f..337f1c2 100644 --- a/app.go +++ b/app.go @@ -1,16 +1,17 @@ package main import ( + "conjure-os/lib/inputs" "conjure-os/lib/models" + "conjure-os/lib/provider" "context" "fmt" - - "conjure-os/lib/inputs" - "conjure-os/lib/provider" - - "github.com/wailsapp/wails/v2/pkg/runtime" + "os" + "os/exec" ) +var games []models.Game + // App struct type App struct { ctx context.Context @@ -29,15 +30,36 @@ func (a *App) startup(ctx context.Context) { provider.Update() } -// Greet returns a greeting for the given name -func (a *App) Greet(name string) string { - runtime.LogInfo(a.ctx, "Test : "+name) - return fmt.Sprintf("Hello %s, It's show time!", name) -} +func (a *App) StartGame(id string) { + for _, game := range games { + if game.Id == id { + gamePath := provider.ExtractGame(game) + cmd := exec.Command(gamePath) -func (a *App) SelectGame(id string) { + // Optional: attach current terminal's std streams + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + + // Start the process + if err := cmd.Start(); err != nil { + fmt.Println("Failed to start:", err) + return + } + + fmt.Printf("Process started with PID %d\n", cmd.Process.Pid) + + // Optional: wait for it to finish + if err := cmd.Wait(); err != nil { + fmt.Println("Process exited with error:", err) + } else { + fmt.Println("Process exited successfully.") + } + } + } } func (a *App) LoadGames() []models.Game { - return provider.GetConjureGameInfo() + games = provider.GetConjureGameInfo() + return games } diff --git a/frontend/src/stores/app-store.ts b/frontend/src/stores/app-store.ts index b3c456d..18715bc 100644 --- a/frontend/src/stores/app-store.ts +++ b/frontend/src/stores/app-store.ts @@ -1,6 +1,7 @@ import { defineStore } from 'pinia'; import {models} from "../../wailsjs/go/models"; import Game = models.Game; +import { StartGame } from "../../wailsjs/go/main/App"; export const useAppStore = defineStore('app', { state: () => ({ @@ -48,6 +49,14 @@ export const useAppStore = defineStore('app', { }, showQr(link: string) { this.qrLink = link; + }, + startSelectedGame() { + if (this.selectedGame) { + StartGame(this.selectedGame.Id).then() + } + else { + console.log("No game selected") + } } } }); \ No newline at end of file diff --git a/frontend/src/utils/key-contexts/carousel-key-context.ts b/frontend/src/utils/key-contexts/carousel-key-context.ts index c0300ca..ef60030 100644 --- a/frontend/src/utils/key-contexts/carousel-key-context.ts +++ b/frontend/src/utils/key-contexts/carousel-key-context.ts @@ -35,5 +35,6 @@ export class CarouselKeyContext extends KeyContext { protected onEnter() { super.onEnter(); + this.store.startSelectedGame(); } } \ No newline at end of file diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 8cf3cd6..6deff79 100755 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -2,8 +2,6 @@ // This file is automatically generated. DO NOT EDIT import {models} from '../models'; -export function Greet(arg1:string):Promise; - export function LoadGames():Promise>; -export function SelectGame(arg1:string):Promise; +export function StartGame(arg1:string):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 813f75e..76e728c 100755 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -2,14 +2,10 @@ // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT -export function Greet(arg1) { - return window['go']['main']['App']['Greet'](arg1); -} - export function LoadGames() { return window['go']['main']['App']['LoadGames'](); } -export function SelectGame(arg1) { - return window['go']['main']['App']['SelectGame'](arg1); +export function StartGame(arg1) { + return window['go']['main']['App']['StartGame'](arg1); } diff --git a/lib/provider/provider.go b/lib/provider/provider.go index 782ba4d..b7036da 100644 --- a/lib/provider/provider.go +++ b/lib/provider/provider.go @@ -70,6 +70,106 @@ func GetOrSetEnvKey(key string, defaultValue string) string { return defaultValue } +func extractZipToSiblingFolder(zipPath string) error { + // Determine destination folder name (same name as zip file, without .zip) + zipBase := strings.TrimSuffix(filepath.Base(zipPath), ".conj") + destDir := filepath.Join(filepath.Dir(zipPath), zipBase) + + // Delete destination folder if it exists + if _, err := os.Stat(destDir); err == nil { + err = os.RemoveAll(destDir) + if err != nil { + return fmt.Errorf("failed to remove existing folder: %v", err) + } + } + + // Open the zip archive + r, err := zip.OpenReader(zipPath) + if err != nil { + return err + } + defer r.Close() + + // Create the destination directory + err = os.MkdirAll(destDir, os.ModePerm) + if err != nil { + return err + } + + for _, f := range r.File { + destPath := filepath.Join(destDir, f.Name) + + // ZipSlip protection + if !strings.HasPrefix(destPath, filepath.Clean(destDir)+string(os.PathSeparator)) { + return fmt.Errorf("illegal file path: %s", destPath) + } + + if f.FileInfo().IsDir() { + if err := os.MkdirAll(destPath, os.ModePerm); err != nil { + return err + } + continue + } + + // Ensure parent directories exist + if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil { + return err + } + + // Create and copy the file + srcFile, err := f.Open() + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer dstFile.Close() + + if _, err := io.Copy(dstFile, srcFile); err != nil { + return err + } + } + + return nil +} + +func extractZipsInFolder(folder string) error { + entries, err := os.ReadDir(folder) + if err != nil { + return fmt.Errorf("reading directory failed: %w", err) + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + if strings.HasSuffix(strings.ToLower(entry.Name()), ".conj") { + zipPath := filepath.Join(folder, entry.Name()) + fmt.Println("Extracting:", zipPath) + if err := extractZipToSiblingFolder(zipPath); err != nil { + return fmt.Errorf("failed to extract %s: %w", zipPath, err) + } + } + } + + return nil +} + +func ExtractGame(game models.Game) string { + gamePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), fmt.Sprintf("%s.conj", game.Id)) + err := extractZipToSiblingFolder(gamePath) + check(err) + gamePath = filepath.Join(config.GetDefaultConjureGamesDirectory(), game.Id) + err = extractZipsInFolder(gamePath) + check(err) + gamePath = filepath.Join(gamePath, game.Files) + return gamePath +} + func GetConjureGameInfo() []models.Game { gamePath := config.GetDefaultConjureGamesDirectory() From 39a35e4eaa4a78c4871e456e20fc56f9ff2fc81c Mon Sep 17 00:00:00 2001 From: Trit0 Date: Sun, 29 Jun 2025 18:23:25 -0400 Subject: [PATCH 08/24] stupid mistake --- lib/provider/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/provider/provider.go b/lib/provider/provider.go index b7036da..bebac4d 100644 --- a/lib/provider/provider.go +++ b/lib/provider/provider.go @@ -147,7 +147,7 @@ func extractZipsInFolder(folder string) error { if entry.IsDir() { continue } - if strings.HasSuffix(strings.ToLower(entry.Name()), ".conj") { + if strings.HasSuffix(strings.ToLower(entry.Name()), ".zip") { zipPath := filepath.Join(folder, entry.Name()) fmt.Println("Extracting:", zipPath) if err := extractZipToSiblingFolder(zipPath); err != nil { From 10174b1232d895d918de64f41145a3567f69863f Mon Sep 17 00:00:00 2001 From: Trit0 Date: Sun, 29 Jun 2025 18:35:33 -0400 Subject: [PATCH 09/24] images perchance? --- lib/provider/provider.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/provider/provider.go b/lib/provider/provider.go index bebac4d..9e09110 100644 --- a/lib/provider/provider.go +++ b/lib/provider/provider.go @@ -73,6 +73,7 @@ func GetOrSetEnvKey(key string, defaultValue string) string { func extractZipToSiblingFolder(zipPath string) error { // Determine destination folder name (same name as zip file, without .zip) zipBase := strings.TrimSuffix(filepath.Base(zipPath), ".conj") + zipBase = strings.TrimSuffix(filepath.Base(zipBase), ".zip") destDir := filepath.Join(filepath.Dir(zipPath), zipBase) // Delete destination folder if it exists @@ -186,14 +187,14 @@ func GetConjureGameInfo() []models.Game { newGames := readFolder(e, gamePath) games = append(newGames, games...) } else if filepath.Ext(e.Name()) == ".conj" { - zipPath := filepath.Join(gamePath, e.Name()) - r, err := zip.OpenReader(zipPath) + conjPath := filepath.Join(gamePath, e.Name()) + r, err := zip.OpenReader(conjPath) if err != nil { log.Fatal(err) } defer r.Close() - fmt.Println("Contents of", zipPath) + fmt.Println("Contents of", conjPath) for _, f := range r.File { printIndentedPath(f.Name) if f.Name == "metadata.txt" { @@ -206,6 +207,9 @@ func GetConjureGameInfo() []models.Game { fmt.Println("Contents of metadata.txt:") metadata, err := io.ReadAll(rc) game := parseGameInfo(metadata) + conjBase := strings.TrimSuffix(filepath.Base(conjPath), ".conj") + game.ThumbnailPath = filepath.Join(conjBase, game.ThumbnailPath) + game.ImagePath = filepath.Join(conjBase, game.ImagePath) games = append(games, game) if err != nil { log.Fatal(err) From 6f9f80a1901e3d8fe9f670e1b12b0fdcb9b436b3 Mon Sep 17 00:00:00 2001 From: Trit0 Date: Sun, 29 Jun 2025 18:58:11 -0400 Subject: [PATCH 10/24] images perchance? 3 --- lib/provider/provider.go | 46 ++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/lib/provider/provider.go b/lib/provider/provider.go index 9e09110..c203830 100644 --- a/lib/provider/provider.go +++ b/lib/provider/provider.go @@ -161,10 +161,13 @@ func extractZipsInFolder(folder string) error { } func ExtractGame(game models.Game) string { - gamePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), fmt.Sprintf("%s.conj", game.Id)) - err := extractZipToSiblingFolder(gamePath) - check(err) - gamePath = filepath.Join(config.GetDefaultConjureGamesDirectory(), game.Id) + info, err := os.Stat(filepath.Join(config.GetDefaultConjureGamesDirectory(), game.Id)) + if err != nil || !info.IsDir() { + gamePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), fmt.Sprintf("%s.conj", game.Id)) + err = extractZipToSiblingFolder(gamePath) + check(err) + } + gamePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), game.Id) err = extractZipsInFolder(gamePath) check(err) gamePath = filepath.Join(gamePath, game.Files) @@ -175,47 +178,40 @@ func GetConjureGameInfo() []models.Game { gamePath := config.GetDefaultConjureGamesDirectory() - entrie, err := os.ReadDir(gamePath) + entries, err := os.ReadDir(gamePath) if err != nil { log.Fatal(err) } var games []models.Game - for _, e := range entrie { + for _, e := range entries { if e.IsDir() { - newGames := readFolder(e, gamePath) - games = append(newGames, games...) + continue } else if filepath.Ext(e.Name()) == ".conj" { conjPath := filepath.Join(gamePath, e.Name()) - r, err := zip.OpenReader(conjPath) - if err != nil { - log.Fatal(err) - } - defer r.Close() - + err = extractZipToSiblingFolder(conjPath) + check(err) + conjBase := strings.TrimSuffix(conjPath, ".conj") + entries, err := os.ReadDir(conjBase) + check(err) fmt.Println("Contents of", conjPath) - for _, f := range r.File { - printIndentedPath(f.Name) - if f.Name == "metadata.txt" { - rc, err := f.Open() - if err != nil { - log.Fatal(err) - } + for _, f := range entries { + if f.Name() == "metadata.txt" { + rc, err := os.Open(filepath.Join(conjBase, f.Name())) + check(err) defer rc.Close() fmt.Println("Contents of metadata.txt:") metadata, err := io.ReadAll(rc) game := parseGameInfo(metadata) - conjBase := strings.TrimSuffix(filepath.Base(conjPath), ".conj") game.ThumbnailPath = filepath.Join(conjBase, game.ThumbnailPath) game.ImagePath = filepath.Join(conjBase, game.ImagePath) games = append(games, game) - if err != nil { - log.Fatal(err) - } + check(err) } } + } } From 34864e96ac6b8c471209d41fa5ec0d63db2dc4ee Mon Sep 17 00:00:00 2001 From: Trit0 Date: Sun, 29 Jun 2025 20:01:50 -0400 Subject: [PATCH 11/24] chui tanner --- lib/models/game.go | 4 ++-- lib/provider/provider.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/models/game.go b/lib/models/game.go index 9948ac5..69b5466 100644 --- a/lib/models/game.go +++ b/lib/models/game.go @@ -8,8 +8,8 @@ type Game struct { Version string Description string Players string - ThumbnailPath string - ImagePath string + ThumbnailPath string `yaml:"thumbnailPath"` + ImagePath string `yaml:"imagePath"` Release string Modification string Files string diff --git a/lib/provider/provider.go b/lib/provider/provider.go index c203830..4f2ee53 100644 --- a/lib/provider/provider.go +++ b/lib/provider/provider.go @@ -205,6 +205,7 @@ func GetConjureGameInfo() []models.Game { fmt.Println("Contents of metadata.txt:") metadata, err := io.ReadAll(rc) game := parseGameInfo(metadata) + fmt.Println(game.ThumbnailPath) game.ThumbnailPath = filepath.Join(conjBase, game.ThumbnailPath) game.ImagePath = filepath.Join(conjBase, game.ImagePath) games = append(games, game) From 76997a60a1bb9ad34684122b5701bafb2a68e47f Mon Sep 17 00:00:00 2001 From: Trit0 Date: Sun, 29 Jun 2025 20:04:14 -0400 Subject: [PATCH 12/24] chui tanner --- lib/provider/provider.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/provider/provider.go b/lib/provider/provider.go index 4f2ee53..4dc97d6 100644 --- a/lib/provider/provider.go +++ b/lib/provider/provider.go @@ -204,7 +204,7 @@ func GetConjureGameInfo() []models.Game { fmt.Println("Contents of metadata.txt:") metadata, err := io.ReadAll(rc) - game := parseGameInfo(metadata) + game := parseGameInfo([]byte(escapeBackslashes(string(metadata)))) fmt.Println(game.ThumbnailPath) game.ThumbnailPath = filepath.Join(conjBase, game.ThumbnailPath) game.ImagePath = filepath.Join(conjBase, game.ImagePath) @@ -224,6 +224,12 @@ func GetConjureGameInfo() []models.Game { return games } +// Function to escape backslashes in the YAML string +func escapeBackslashes(input string) string { + // Replace every single backslash with double backslashes + return strings.ReplaceAll(input, `\`, `\\`) +} + // Helper to print tree-like structure func printIndentedPath(path string) { parts := strings.Split(path, "/") From 900210eeb747287a89a6f0e0584d0f5f60882535 Mon Sep 17 00:00:00 2001 From: Trit0 Date: Mon, 30 Jun 2025 20:43:07 -0400 Subject: [PATCH 13/24] test --- app.go | 4 ++ frontend/src/components/GameCard.vue | 31 +++++++++ frontend/src/components/GameCarousel.vue | 18 ++--- frontend/src/components/LocalImage.vue | 29 ++++++++ frontend/src/utils/image-manager.ts | 21 ++++++ frontend/wailsjs/go/main/App.d.ts | 3 + frontend/wailsjs/go/main/App.js | 4 ++ frontend/wailsjs/go/models.ts | 21 ++++++ frontend/wailsjs/runtime/package.json | 0 frontend/wailsjs/runtime/runtime.d.ts | 0 frontend/wailsjs/runtime/runtime.js | 0 go.mod | 1 + go.sum | 2 + lib/inputs/joystick.go | 86 +++++++++++++++++++----- lib/provider/file-provider.go | 53 +++++++++++++++ lib/provider/provider.go | 2 - 16 files changed, 244 insertions(+), 31 deletions(-) create mode 100644 frontend/src/components/GameCard.vue create mode 100644 frontend/src/components/LocalImage.vue create mode 100644 frontend/src/utils/image-manager.ts mode change 100644 => 100755 frontend/wailsjs/runtime/package.json mode change 100644 => 100755 frontend/wailsjs/runtime/runtime.d.ts mode change 100644 => 100755 frontend/wailsjs/runtime/runtime.js create mode 100644 lib/provider/file-provider.go diff --git a/app.go b/app.go index 337f1c2..5588f2d 100644 --- a/app.go +++ b/app.go @@ -63,3 +63,7 @@ func (a *App) LoadGames() []models.Game { games = provider.GetConjureGameInfo() return games } + +func (a *App) LoadImage(gameId string, imageSrc string) provider.FileBlob { + return *provider.LoadImage(gameId, imageSrc) +} diff --git a/frontend/src/components/GameCard.vue b/frontend/src/components/GameCard.vue new file mode 100644 index 0000000..1c8f515 --- /dev/null +++ b/frontend/src/components/GameCard.vue @@ -0,0 +1,31 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/GameCarousel.vue b/frontend/src/components/GameCarousel.vue index 93bf1c2..c8fd827 100644 --- a/frontend/src/components/GameCarousel.vue +++ b/frontend/src/components/GameCarousel.vue @@ -2,21 +2,12 @@
-
- -
+ />
@@ -36,6 +27,7 @@ import { models } from "../../wailsjs/go/models"; import Game = models.Game; +import GameCard from "./GameCard.vue"; defineProps<{ games: Game[], diff --git a/frontend/src/components/LocalImage.vue b/frontend/src/components/LocalImage.vue new file mode 100644 index 0000000..c81de79 --- /dev/null +++ b/frontend/src/components/LocalImage.vue @@ -0,0 +1,29 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/utils/image-manager.ts b/frontend/src/utils/image-manager.ts new file mode 100644 index 0000000..aea3939 --- /dev/null +++ b/frontend/src/utils/image-manager.ts @@ -0,0 +1,21 @@ +import {LoadImage} from "../../wailsjs/go/main/App"; +import * as path from "node:path"; + +export class ImageManager { + + static Dictionary: {[key: string]: string} = {} + + public static async getImage(gameId: string, src: string): Promise { + const id = path.join(gameId, src); + if (this.Dictionary[id]) + return this.Dictionary[id] + + const fileBlob = await LoadImage(gameId, src); + const bytes = new Uint8Array(fileBlob.Data); + const blob = new Blob([bytes], {type: fileBlob.MimeType }); + const url = URL.createObjectURL(blob); + this.Dictionary[id] = url + return url; + } + +} \ No newline at end of file diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 6deff79..5b27953 100755 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -1,7 +1,10 @@ // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT import {models} from '../models'; +import {provider} from '../models'; export function LoadGames():Promise>; +export function LoadImage(arg1:string,arg2:string):Promise; + export function StartGame(arg1:string):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 76e728c..2b66092 100755 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -6,6 +6,10 @@ export function LoadGames() { return window['go']['main']['App']['LoadGames'](); } +export function LoadImage(arg1, arg2) { + return window['go']['main']['App']['LoadImage'](arg1, arg2); +} + export function StartGame(arg1) { return window['go']['main']['App']['StartGame'](arg1); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index afc1457..5ec11da 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -45,3 +45,24 @@ export namespace models { } +export namespace provider { + + export class FileBlob { + Name: string; + MimeType: string; + Data: number[]; + + static createFrom(source: any = {}) { + return new FileBlob(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.Name = source["Name"]; + this.MimeType = source["MimeType"]; + this.Data = source["Data"]; + } + } + +} + diff --git a/frontend/wailsjs/runtime/package.json b/frontend/wailsjs/runtime/package.json old mode 100644 new mode 100755 diff --git a/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts old mode 100644 new mode 100755 diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js old mode 100644 new mode 100755 diff --git a/go.mod b/go.mod index 2b841fc..a42c190 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.0 toolchain go1.23.5 require ( + github.com/karalabe/hid v1.0.0 github.com/wailsapp/wails/v2 v2.9.2 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index ec6ad85..9690a5f 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/karalabe/hid v1.0.0 h1:+/CIMNXhSU/zIJgnIvBD2nKHxS/bnRHhhs9xBryLpPo= +github.com/karalabe/hid v1.0.0/go.mod h1:Vr51f8rUOLYrfrWDFlV12GGQgM5AT8sVh+2fY4MPeu8= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= diff --git a/lib/inputs/joystick.go b/lib/inputs/joystick.go index c06a11f..04f1271 100644 --- a/lib/inputs/joystick.go +++ b/lib/inputs/joystick.go @@ -1,10 +1,9 @@ package inputs import ( - "encoding/binary" "fmt" - "os" - "time" + + "github.com/karalabe/hid" ) type JoystickEvent struct { @@ -21,29 +20,84 @@ const ( ) func Start() { - // Open the joystick device file - file, err := os.Open("/dev/input/js0") - if err != nil { - fmt.Println("Error opening joystick:", err) + return + fmt.Println("Opening devices") + const vendorID = 0x0079 + const productID = 0x0006 + + // Enumerate all matching devices + devices := hid.Enumerate(vendorID, productID) + if len(devices) == 0 { + fmt.Printf("Device with VID:PID %04X:%04X not found", vendorID, productID) return } - defer file.Close() - // Continuously read joystick events + for _, d := range devices { + fmt.Printf("Found: %s - VID:%04X PID:%04X\n", d.Product, d.VendorID, d.ProductID) + } + + // Open the first matching device + device, err := devices[0].Open() + if err != nil { + fmt.Printf("Failed to open device: %v", err) + return + } + defer device.Close() + + fmt.Println("Reading data... Press Ctrl+C to exit") + + buf := make([]byte, 32) // Adjust size if needed for { - var e JoystickEvent - err := binary.Read(file, binary.LittleEndian, &e) + _, err := device.Read(buf) if err != nil { - fmt.Println("Error reading joystick event:", err) + fmt.Printf("Read error: %v", err) return } - // Handle the event - handleJoystickEvent(e) + // For debugging: print raw data + // fmt.Printf("Raw: % X\n", buf[:n]) - // Sleep to avoid flooding output - time.Sleep(10 * time.Millisecond) + // Example: decode joystick + button state + x := buf[0] // Horizontal axis (0–255) + y := buf[1] // Vertical axis (0–255) + buttons := buf[6] // Buttons as bitfield + + // fmt.Printf("Joystick X: %d, Y: %d, Buttons: %08b\n", x, y, buttons) + + if buttons != 0 { + fmt.Printf("Button was pressed! %d\n", buttons) + } + + if x != 127 || y != 127 { + fmt.Printf("Joystick moved! %d - %d\n", x, y) + } } + + fmt.Println("Out") + + // Open the joystick device file + // file, err := os.Open("/dev/input/js0") + // if err != nil { + // fmt.Println("Error opening joystick:", err) + // return + // } + // defer file.Close() + + // // Continuously read joystick events + // for { + // var e JoystickEvent + // err := binary.Read(file, binary.LittleEndian, &e) + // if err != nil { + // fmt.Println("Error reading joystick event:", err) + // return + // } + + // // Handle the event + // handleJoystickEvent(e) + + // // Sleep to avoid flooding output + // time.Sleep(10 * time.Millisecond) + // } } // handleJoystickEvent processes joystick events. diff --git a/lib/provider/file-provider.go b/lib/provider/file-provider.go new file mode 100644 index 0000000..3e08406 --- /dev/null +++ b/lib/provider/file-provider.go @@ -0,0 +1,53 @@ +package provider + +import ( + "conjure-os/lib/config" + "mime" + "net/http" + "os" + "path/filepath" +) + +func LoadImage(gameId string, imageSrc string) *FileBlob { + imagePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), gameId, imageSrc) + blob, err := GetFileBlob(imagePath) + if err != nil { + panic(err) + } + + return blob +} + +type FileBlob struct { + Name string + MimeType string + Data []byte +} + +func GetFileBlob(path string) (*FileBlob, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + // Try to guess MIME type from content + mimeType := http.DetectContentType(data) + + // Fallback to extension-based detection + if mimeType == "application/octet-stream" { + ext := filepath.Ext(path) + if ext != "" { + if t := mime.TypeByExtension(ext); t != "" { + mimeType = t + } + } + } + + name := filepath.Base(path) + + return &FileBlob{ + Name: name, + MimeType: mimeType, + Data: data, + }, nil +} diff --git a/lib/provider/provider.go b/lib/provider/provider.go index 4dc97d6..aeb356a 100644 --- a/lib/provider/provider.go +++ b/lib/provider/provider.go @@ -206,8 +206,6 @@ func GetConjureGameInfo() []models.Game { metadata, err := io.ReadAll(rc) game := parseGameInfo([]byte(escapeBackslashes(string(metadata)))) fmt.Println(game.ThumbnailPath) - game.ThumbnailPath = filepath.Join(conjBase, game.ThumbnailPath) - game.ImagePath = filepath.Join(conjBase, game.ImagePath) games = append(games, game) check(err) } From fd33b6df730de4fc336cd143c88b2886a6ec4e43 Mon Sep 17 00:00:00 2001 From: Trit0 Date: Mon, 30 Jun 2025 20:51:10 -0400 Subject: [PATCH 14/24] test 2 --- frontend/src/components/LocalImage.vue | 12 ++++++------ frontend/wailsjs/runtime/package.json | 0 frontend/wailsjs/runtime/runtime.d.ts | 0 frontend/wailsjs/runtime/runtime.js | 0 4 files changed, 6 insertions(+), 6 deletions(-) mode change 100755 => 100644 frontend/wailsjs/runtime/package.json mode change 100755 => 100644 frontend/wailsjs/runtime/runtime.d.ts mode change 100755 => 100644 frontend/wailsjs/runtime/runtime.js diff --git a/frontend/src/components/LocalImage.vue b/frontend/src/components/LocalImage.vue index c81de79..985804a 100644 --- a/frontend/src/components/LocalImage.vue +++ b/frontend/src/components/LocalImage.vue @@ -13,17 +13,17 @@ import { ref, watch } from "vue"; import { ImageManager } from "../utils/image-manager"; -defineProps<{ +const props = defineProps<{ key: string src: string alt: string -}>() +}>(); const blobUrl = ref(null); -watch(() => props.imageUrl, async (newUrl) => { - blobUrl.value = await ImageManager.getBlob(key, newUrl); +watch(() => props.src, async (newUrl) => { + console.log(newUrl); + blobUrl.value = await ImageManager.getImage(props.key, newUrl); + console.log(blobUrl.value); }, { immediate: true }); - -console.log(src) \ No newline at end of file diff --git a/frontend/wailsjs/runtime/package.json b/frontend/wailsjs/runtime/package.json old mode 100755 new mode 100644 diff --git a/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts old mode 100755 new mode 100644 diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js old mode 100755 new mode 100644 From 8bf08218c047710eea7cd5576ecd08d2b0efc81a Mon Sep 17 00:00:00 2001 From: Trit0 Date: Mon, 30 Jun 2025 21:32:08 -0400 Subject: [PATCH 15/24] image! --- frontend/src/components/GameCard.vue | 2 +- frontend/src/components/GamePreview.vue | 9 ++++++--- frontend/src/components/LocalImage.vue | 8 +++----- frontend/src/utils/image-manager.ts | 17 +++++++++++++---- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/GameCard.vue b/frontend/src/components/GameCard.vue index 1c8f515..e08eadd 100644 --- a/frontend/src/components/GameCard.vue +++ b/frontend/src/components/GameCard.vue @@ -10,7 +10,7 @@ :src="game.ThumbnailPath" class="h-32 w-48 object-cover" :alt="game.Game" - :key="game.Id" + :gameId="game.Id" />
diff --git a/frontend/src/components/GamePreview.vue b/frontend/src/components/GamePreview.vue index bb1205f..3ac3a0e 100644 --- a/frontend/src/components/GamePreview.vue +++ b/frontend/src/components/GamePreview.vue @@ -5,11 +5,13 @@
{{ game.Genres }} - {{ game.Players }} players

{{ game.Description }}

-
- +
@@ -36,6 +38,7 @@ \ No newline at end of file diff --git a/frontend/src/utils/image-manager.ts b/frontend/src/utils/image-manager.ts index aea3939..45c6f12 100644 --- a/frontend/src/utils/image-manager.ts +++ b/frontend/src/utils/image-manager.ts @@ -1,20 +1,29 @@ -import {LoadImage} from "../../wailsjs/go/main/App"; -import * as path from "node:path"; +import { LoadImage } from "../../wailsjs/go/main/App"; export class ImageManager { static Dictionary: {[key: string]: string} = {} public static async getImage(gameId: string, src: string): Promise { - const id = path.join(gameId, src); + const id = gameId + "\\" + src; + console.log(gameId, src, id) if (this.Dictionary[id]) return this.Dictionary[id] const fileBlob = await LoadImage(gameId, src); - const bytes = new Uint8Array(fileBlob.Data); + console.log(fileBlob) + const byteCharacters = atob(fileBlob.Data as any as string); + const byteNumbers = Array.from(byteCharacters).map(c => c.charCodeAt(0)); + console.log(byteCharacters, byteNumbers) + + const bytes = new Uint8Array(byteNumbers); + console.log(bytes) const blob = new Blob([bytes], {type: fileBlob.MimeType }); + console.log(blob) const url = URL.createObjectURL(blob); + console.log(url) this.Dictionary[id] = url + console.log(this.Dictionary) return url; } From c20554c4f6d2f529912ee4e1128841d45df6fefd Mon Sep 17 00:00:00 2001 From: Trit0 Date: Tue, 1 Jul 2025 16:30:23 -0400 Subject: [PATCH 16/24] test --- app.go | 7 +- frontend/src/utils/use-keyboard-navigation.ts | 2 + lib/inputs/joystick.go | 89 ++++++++++++++++++- 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 5588f2d..0eb4ea0 100644 --- a/app.go +++ b/app.go @@ -6,6 +6,7 @@ import ( "conjure-os/lib/provider" "context" "fmt" + "github.com/wailsapp/wails/v2/pkg/runtime" "os" "os/exec" ) @@ -26,10 +27,14 @@ func NewApp() *App { // so we can call the runtime methods func (a *App) startup(ctx context.Context) { a.ctx = ctx - inputs.Start() + inputs.Start(a.onControllerChange) provider.Update() } +func (a *App) onControllerChange(data any) { + runtime.EventsEmit(a.ctx, "controller_change", data) +} + func (a *App) StartGame(id string) { for _, game := range games { if game.Id == id { diff --git a/frontend/src/utils/use-keyboard-navigation.ts b/frontend/src/utils/use-keyboard-navigation.ts index 8de2fbf..fe7f3ae 100644 --- a/frontend/src/utils/use-keyboard-navigation.ts +++ b/frontend/src/utils/use-keyboard-navigation.ts @@ -1,8 +1,10 @@ import { onMounted, onBeforeUnmount } from 'vue'; import { KeyboardManager } from "./keyboard-manager"; +import { EventsOn } from "../../wailsjs/runtime"; export function useKeyboardNavigation(): void { onMounted(() => { + EventsOn("controller_change", (data) => console.log(data)); window.addEventListener('keydown', KeyboardManager.handle.bind(KeyboardManager)); }); diff --git a/lib/inputs/joystick.go b/lib/inputs/joystick.go index 04f1271..d73c8d4 100644 --- a/lib/inputs/joystick.go +++ b/lib/inputs/joystick.go @@ -19,8 +19,82 @@ const ( JS_EVENT_INIT = 0x80 // Initial state of device ) -func Start() { - return +type Controller struct { + Device *hid.Device +} + +func (c *Controller) ReadState() error { + buf := make([]byte, 64) + _, err := c.Device.Read(buf) + if err != nil { + return err + } + + fmt.Printf("Joystick: X=%d Y=%d Buttons=%08b\n", buf[0], buf[1], buf[6]) + return nil +} + +type Vec2B struct { + X, Y byte +} + +type ControllerState struct { + joystick Vec2B + buttons byte +} + +type ConjureControllerButton int + +const ( + ButtonA ConjureControllerButton = iota //0 + ButtonB + ButtonC + + Button1 + Button2 + Button3 + + ButtonStart + ButtonPower // 7 +) + +var ConjureControllerButtons = []ConjureControllerButton{ + ButtonA, + ButtonB, + ButtonC, + + Button1, + Button2, + Button3, + + ButtonStart, + ButtonPower, +} + +func (s ConjureControllerButton) String() string { + switch s { + case ButtonA: + return "ButtonA" + case ButtonB: + return "ButtonB" + case ButtonC: + return "ButtonC" + case Button1: + return "Button1" + case Button2: + return "Button2" + case Button3: + return "Button3" + case ButtonStart: + return "ButtonStart" + case ButtonPower: + return "ButtonPower" + default: + return "Unknown" + } +} + +func Start(onStateChange func(any)) { fmt.Println("Opening devices") const vendorID = 0x0079 const productID = 0x0006 @@ -61,14 +135,25 @@ func Start() { x := buf[0] // Horizontal axis (0–255) y := buf[1] // Vertical axis (0–255) buttons := buf[6] // Buttons as bitfield + state := ControllerState{ + joystick: Vec2B{x, y}, + buttons: buttons, + } // fmt.Printf("Joystick X: %d, Y: %d, Buttons: %08b\n", x, y, buttons) if buttons != 0 { + for _, button := range ConjureControllerButtons { + if buttons&(1< Date: Tue, 1 Jul 2025 22:18:52 -0400 Subject: [PATCH 17/24] it only support one controller but thats already good --- app.go | 36 ++++++- frontend/package.json.md5 | 2 +- frontend/src/App.vue | 6 +- frontend/src/components/LoadingModal.vue | 13 +++ frontend/src/models/controller-state.ts | 7 ++ frontend/src/stores/app-store.ts | 9 +- .../src/utils/key-contexts/key-context.ts | 26 +++++ frontend/src/utils/keyboard-manager.ts | 5 + frontend/src/utils/use-keyboard-navigation.ts | 2 +- frontend/wailsjs/runtime/runtime.d.ts | 2 +- go.mod | 2 +- go.sum | 4 +- lib/inputs/joystick.go | 96 +++++++++---------- main.go | 7 +- 14 files changed, 147 insertions(+), 70 deletions(-) create mode 100644 frontend/src/components/LoadingModal.vue create mode 100644 frontend/src/models/controller-state.ts diff --git a/app.go b/app.go index 0eb4ea0..cc04e72 100644 --- a/app.go +++ b/app.go @@ -6,12 +6,19 @@ import ( "conjure-os/lib/provider" "context" "fmt" - "github.com/wailsapp/wails/v2/pkg/runtime" "os" "os/exec" + "time" + + "github.com/wailsapp/wails/v2/pkg/runtime" ) -var games []models.Game +var ( + games []models.Game + lastEmitTimestamp = time.Now().Add(-10 * time.Second) + emitInterval = 300 * time.Millisecond + gameIsOpen = false +) // App struct type App struct { @@ -31,8 +38,27 @@ func (a *App) startup(ctx context.Context) { provider.Update() } -func (a *App) onControllerChange(data any) { - runtime.EventsEmit(a.ctx, "controller_change", data) +func (a *App) onControllerChange(state inputs.ControllerState) { + now := time.Now() + + if now.Sub(lastEmitTimestamp) >= emitInterval && !gameIsOpen { + if state.Buttons != 0 { + for _, button := range inputs.ConjureControllerButtons { + if state.Buttons&(1< + + + diff --git a/frontend/src/models/controller-state.ts b/frontend/src/models/controller-state.ts new file mode 100644 index 0000000..441d3f4 --- /dev/null +++ b/frontend/src/models/controller-state.ts @@ -0,0 +1,7 @@ +export interface ControllerState { + joystick: { + x: number, + y: number, + }, + buttons: number +} \ No newline at end of file diff --git a/frontend/src/stores/app-store.ts b/frontend/src/stores/app-store.ts index 18715bc..896d6f4 100644 --- a/frontend/src/stores/app-store.ts +++ b/frontend/src/stores/app-store.ts @@ -11,7 +11,8 @@ export const useAppStore = defineStore('app', { transitionDirection: 'down' as 'up' | 'down', selectedGame: null as Game | null, selectedGameIndex: 0, - qrLink: '' as string + qrLink: '' as string, + gameIsStarting: false as boolean }), getters: { filteredGames(state): Game[] { @@ -50,9 +51,11 @@ export const useAppStore = defineStore('app', { showQr(link: string) { this.qrLink = link; }, - startSelectedGame() { + async startSelectedGame() { if (this.selectedGame) { - StartGame(this.selectedGame.Id).then() + this.gameIsStarting = true; + await StartGame(this.selectedGame.Id); + this.gameIsStarting = false; } else { console.log("No game selected") diff --git a/frontend/src/utils/key-contexts/key-context.ts b/frontend/src/utils/key-contexts/key-context.ts index 7a4aa32..1eaffdc 100644 --- a/frontend/src/utils/key-contexts/key-context.ts +++ b/frontend/src/utils/key-contexts/key-context.ts @@ -1,3 +1,4 @@ +import { ControllerState } from "../../models/controller-state"; import { useAppStore } from "../../stores/app-store"; export abstract class KeyContext { @@ -30,6 +31,31 @@ export abstract class KeyContext { } } + public handleState(state: ControllerState) { + if (state.joystick.x === 0) { + this.onKeyLeft() + } + else if (state.joystick.x === 255) { + this.onKeyRight() + } + + if (state.joystick.y === 0) { + this.onKeyUp() + } + else if (state.joystick.y === 255) { + this.onKeyDown() + } + + if ((state.buttons & 0x02) !== 0) { + this.onEnter() + } + + // TODO should be 0x01 when the power button will work + if ((state.buttons & 0x04) !== 0) { + this.onEscape() + } + } + protected onKeyRight(): void { console.log('onKeyRight'); } diff --git a/frontend/src/utils/keyboard-manager.ts b/frontend/src/utils/keyboard-manager.ts index 810b137..faf9cb7 100644 --- a/frontend/src/utils/keyboard-manager.ts +++ b/frontend/src/utils/keyboard-manager.ts @@ -1,3 +1,4 @@ +import { ControllerState } from "../models/controller-state"; import { CarouselKeyContext } from "./key-contexts/carousel-key-context"; import { KeyContext } from "./key-contexts/key-context"; import { SidebarKeyContext } from "./key-contexts/sidebar-key-context"; @@ -14,4 +15,8 @@ export class KeyboardManager { static handle(event: KeyboardEvent) { this.current?.handleKey(event); } + + static handleState(controllerState: ControllerState) { + this.current?.handleState(controllerState); + } } \ No newline at end of file diff --git a/frontend/src/utils/use-keyboard-navigation.ts b/frontend/src/utils/use-keyboard-navigation.ts index fe7f3ae..666d59f 100644 --- a/frontend/src/utils/use-keyboard-navigation.ts +++ b/frontend/src/utils/use-keyboard-navigation.ts @@ -4,7 +4,7 @@ import { EventsOn } from "../../wailsjs/runtime"; export function useKeyboardNavigation(): void { onMounted(() => { - EventsOn("controller_change", (data) => console.log(data)); + EventsOn("controller_change", KeyboardManager.handleState.bind(KeyboardManager)); window.addEventListener('keydown', KeyboardManager.handle.bind(KeyboardManager)); }); diff --git a/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts index 94778df..4445dac 100644 --- a/frontend/wailsjs/runtime/runtime.d.ts +++ b/frontend/wailsjs/runtime/runtime.d.ts @@ -134,7 +134,7 @@ export function WindowIsFullscreen(): Promise; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): Promise; +export function WindowSetSize(width: number, height: number): void; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. diff --git a/go.mod b/go.mod index a42c190..aa26443 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.23.5 require ( github.com/karalabe/hid v1.0.0 - github.com/wailsapp/wails/v2 v2.9.2 + github.com/wailsapp/wails/v2 v2.10.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 9690a5f..58e26dc 100644 --- a/go.sum +++ b/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/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= -github.com/wailsapp/wails/v2 v2.9.2 h1:Xb5YRTos1w5N7DTMyYegWaGukCP2fIaX9WF21kPPF2k= -github.com/wailsapp/wails/v2 v2.9.2/go.mod h1:uehvlCwJSFcBq7rMCGfk4rxca67QQGsbg5Nm4m9UnBs= +github.com/wailsapp/wails/v2 v2.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns= +github.com/wailsapp/wails/v2 v2.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY= 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/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= diff --git a/lib/inputs/joystick.go b/lib/inputs/joystick.go index d73c8d4..375e3dc 100644 --- a/lib/inputs/joystick.go +++ b/lib/inputs/joystick.go @@ -23,52 +23,60 @@ type Controller struct { Device *hid.Device } -func (c *Controller) ReadState() error { - buf := make([]byte, 64) +func (c *Controller) ReadState(buf []byte) (*ControllerState, error) { _, err := c.Device.Read(buf) + if err != nil { - return err + return nil, err } - fmt.Printf("Joystick: X=%d Y=%d Buttons=%08b\n", buf[0], buf[1], buf[6]) - return nil + x := buf[0] // Horizontal axis (0–255) + y := buf[1] // Vertical axis (0–255) + buttons := buf[6] // Buttons as bitfield + state := ControllerState{ + Joystick: Vec2B{x, y}, + Buttons: buttons, + } + + return &state, nil } type Vec2B struct { - X, Y byte + X byte `json:"x"` + Y byte `json:"y"` } type ControllerState struct { - joystick Vec2B - buttons byte + Joystick Vec2B `json:"joystick"` + Buttons byte `json:"buttons"` } type ConjureControllerButton int const ( - ButtonA ConjureControllerButton = iota //0 - ButtonB - ButtonC + ButtonPower ConjureControllerButton = iota //0 + ButtonStart Button1 Button2 Button3 - ButtonStart - ButtonPower // 7 + ButtonA + ButtonB + ButtonC // 7 ) var ConjureControllerButtons = []ConjureControllerButton{ - ButtonA, - ButtonB, - ButtonC, + ButtonStart, + ButtonPower, Button1, Button2, Button3, - ButtonStart, - ButtonPower, + ButtonA, + ButtonB, + ButtonC, } func (s ConjureControllerButton) String() string { @@ -94,7 +102,7 @@ func (s ConjureControllerButton) String() string { } } -func Start(onStateChange func(any)) { +func Start(onStateChange func(ControllerState)) { fmt.Println("Opening devices") const vendorID = 0x0079 const productID = 0x0006 @@ -111,50 +119,34 @@ func Start(onStateChange func(any)) { } // Open the first matching device - device, err := devices[0].Open() - if err != nil { - fmt.Printf("Failed to open device: %v", err) - return - } - defer device.Close() - fmt.Println("Reading data... Press Ctrl+C to exit") + for i, deviceDetected := range devices { - buf := make([]byte, 32) // Adjust size if needed - for { - _, err := device.Read(buf) + fmt.Printf("device %d detected\n", i) + device, err := deviceDetected.Open() if err != nil { - fmt.Printf("Read error: %v", err) + fmt.Printf("Failed to open device: %v", err) return } + defer device.Close() - // For debugging: print raw data - // fmt.Printf("Raw: % X\n", buf[:n]) + controller := Controller{Device: device} - // Example: decode joystick + button state - x := buf[0] // Horizontal axis (0–255) - y := buf[1] // Vertical axis (0–255) - buttons := buf[6] // Buttons as bitfield - state := ControllerState{ - joystick: Vec2B{x, y}, - buttons: buttons, - } + fmt.Println("Reading data... Press Ctrl+C to exit") - // fmt.Printf("Joystick X: %d, Y: %d, Buttons: %08b\n", x, y, buttons) + buf := make([]byte, 32) // Adjust size if needed + for { + state, err := controller.ReadState(buf) - if buttons != 0 { - for _, button := range ConjureControllerButtons { - if buttons&(1< Date: Thu, 3 Jul 2025 19:55:34 -0400 Subject: [PATCH 18/24] try to support both controllers --- frontend/src/models/controller-state.ts | 1 + lib/inputs/joystick.go | 83 +++++++++---------------- 2 files changed, 29 insertions(+), 55 deletions(-) diff --git a/frontend/src/models/controller-state.ts b/frontend/src/models/controller-state.ts index 441d3f4..2154d7f 100644 --- a/frontend/src/models/controller-state.ts +++ b/frontend/src/models/controller-state.ts @@ -1,4 +1,5 @@ export interface ControllerState { + id?: number, joystick: { x: number, y: number, diff --git a/lib/inputs/joystick.go b/lib/inputs/joystick.go index 375e3dc..f3b123e 100644 --- a/lib/inputs/joystick.go +++ b/lib/inputs/joystick.go @@ -47,6 +47,7 @@ type Vec2B struct { } type ControllerState struct { + Id int `json:"id"` Joystick Vec2B `json:"joystick"` Buttons byte `json:"buttons"` } @@ -107,74 +108,46 @@ func Start(onStateChange func(ControllerState)) { const vendorID = 0x0079 const productID = 0x0006 - // Enumerate all matching devices devices := hid.Enumerate(vendorID, productID) if len(devices) == 0 { - fmt.Printf("Device with VID:PID %04X:%04X not found", vendorID, productID) + fmt.Printf("Device with VID:PID %04X:%04X not found\n", vendorID, productID) return } - for _, d := range devices { - fmt.Printf("Found: %s - VID:%04X PID:%04X\n", d.Product, d.VendorID, d.ProductID) - } - - // Open the first matching device - - for i, deviceDetected := range devices { - - fmt.Printf("device %d detected\n", i) - device, err := deviceDetected.Open() - if err != nil { - fmt.Printf("Failed to open device: %v", err) - return - } - defer device.Close() - - controller := Controller{Device: device} - - fmt.Println("Reading data... Press Ctrl+C to exit") - - buf := make([]byte, 32) // Adjust size if needed - for { - state, err := controller.ReadState(buf) + for i, d := range devices { + fmt.Printf("Found device %d: %s - VID:%04X PID:%04X\n", i, d.Product, d.VendorID, d.ProductID) + go func(i int, d hid.DeviceInfo) { + device, err := d.Open() if err != nil { - fmt.Printf("Read error: %v", err) + fmt.Printf("Failed to open device %d: %v\n", i, err) return } + defer device.Close() - if state.Buttons != 0 || state.Joystick.X != 127 || state.Joystick.Y != 127 { - fmt.Printf("State changed on device %d\n", i) - onStateChange(*state) + controller := Controller{Device: device} + buf := make([]byte, 32) + + fmt.Printf("Reading data from device %d... Press Ctrl+C to exit\n", i) + for { + state, err := controller.ReadState(buf) + if err != nil { + fmt.Printf("Read error on device %d: %v\n", i, err) + return + } + + state.Id = i + + if state.Buttons != 0 || state.Joystick.X != 127 || state.Joystick.Y != 127 { + fmt.Printf("State changed on device %d\n", i) + onStateChange(*state) + } } - } + }(i, d) } - fmt.Println("Out") - - // Open the joystick device file - // file, err := os.Open("/dev/input/js0") - // if err != nil { - // fmt.Println("Error opening joystick:", err) - // return - // } - // defer file.Close() - - // // Continuously read joystick events - // for { - // var e JoystickEvent - // err := binary.Read(file, binary.LittleEndian, &e) - // if err != nil { - // fmt.Println("Error reading joystick event:", err) - // return - // } - - // // Handle the event - // handleJoystickEvent(e) - - // // Sleep to avoid flooding output - // time.Sleep(10 * time.Millisecond) - // } + // Prevent the function from exiting + select {} } // handleJoystickEvent processes joystick events. From 2872bd6220a28ffcaee5b3e11a9cb774ac4d7445 Mon Sep 17 00:00:00 2001 From: club Date: Thu, 3 Jul 2025 22:12:39 -0400 Subject: [PATCH 19/24] all two controllers and no extract every time --- app.go | 2 +- frontend/src/App.vue | 4 +--- frontend/src/stores/app-store.ts | 12 ++++++++---- .../src/utils/key-contexts/carousel-key-context.ts | 3 +++ frontend/src/utils/key-contexts/key-context.ts | 4 ++-- .../src/utils/key-contexts/options-key-context.ts | 13 +++++++++++++ frontend/src/utils/keyboard-manager.ts | 6 ++++-- lib/inputs/joystick.go | 5 +++-- lib/provider/provider.go | 6 ++++++ 9 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 frontend/src/utils/key-contexts/options-key-context.ts diff --git a/app.go b/app.go index cc04e72..bacdfcc 100644 --- a/app.go +++ b/app.go @@ -16,7 +16,7 @@ import ( var ( games []models.Game lastEmitTimestamp = time.Now().Add(-10 * time.Second) - emitInterval = 300 * time.Millisecond + emitInterval = 150 * time.Millisecond gameIsOpen = false ) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 5f0a2cb..045c613 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -46,9 +46,7 @@ import { storeToRefs } from "pinia"; import { KeyboardManager } from "./utils/keyboard-manager"; const store = useAppStore(); -const { selectedTag, selectedGame, tags, games, transitionDirection, qrLink, gameIsStarting } = storeToRefs(store); - -const optionsOpen = ref(false); +const { selectedTag, selectedGame, tags, games, transitionDirection, qrLink, gameIsStarting, optionsOpen } = storeToRefs(store); onMounted(async () => { games.value = await fetchGames(); diff --git a/frontend/src/stores/app-store.ts b/frontend/src/stores/app-store.ts index 896d6f4..3c439fa 100644 --- a/frontend/src/stores/app-store.ts +++ b/frontend/src/stores/app-store.ts @@ -10,8 +10,9 @@ export const useAppStore = defineStore('app', { selectedTag: null as string | null, transitionDirection: 'down' as 'up' | 'down', selectedGame: null as Game | null, - selectedGameIndex: 0, + selectedGameIndex: -1, qrLink: '' as string, + optionsOpen: false as boolean, gameIsStarting: false as boolean }), getters: { @@ -42,17 +43,20 @@ export const useAppStore = defineStore('app', { this.selectedGameIndex = index; this.selectedGame = games[index]; } + else { + this.selectedGameIndex = -1; + this.selectedGame = null; + } }, selectTag(tag: string) { this.selectedTag = tag; - this.selectedGameIndex = 0; - this.selectedGame = this.filteredGames[0] ?? null; + this.selectGame(this.selectedGameIndex); }, showQr(link: string) { this.qrLink = link; }, async startSelectedGame() { - if (this.selectedGame) { + if (this.selectedGame && !this.gameIsStarting) { this.gameIsStarting = true; await StartGame(this.selectedGame.Id); this.gameIsStarting = false; diff --git a/frontend/src/utils/key-contexts/carousel-key-context.ts b/frontend/src/utils/key-contexts/carousel-key-context.ts index ef60030..e36648e 100644 --- a/frontend/src/utils/key-contexts/carousel-key-context.ts +++ b/frontend/src/utils/key-contexts/carousel-key-context.ts @@ -23,6 +23,7 @@ export class CarouselKeyContext extends KeyContext { super.onKeyLeft(); if (this.store.selectedGameIndex === 0) { KeyboardManager.switchContext("sidebar"); + this.store.selectGame(-1); } else { this.store.moveGameLeft(); @@ -31,6 +32,8 @@ export class CarouselKeyContext extends KeyContext { protected onEscape() { super.onEscape(); + this.store.optionsOpen = true; + KeyboardManager.switchContext('options'); } protected onEnter() { diff --git a/frontend/src/utils/key-contexts/key-context.ts b/frontend/src/utils/key-contexts/key-context.ts index 1eaffdc..dce2859 100644 --- a/frontend/src/utils/key-contexts/key-context.ts +++ b/frontend/src/utils/key-contexts/key-context.ts @@ -46,12 +46,12 @@ export abstract class KeyContext { this.onKeyDown() } - if ((state.buttons & 0x02) !== 0) { + if ((state.buttons & 0x04) !== 0) { this.onEnter() } // TODO should be 0x01 when the power button will work - if ((state.buttons & 0x04) !== 0) { + if ((state.buttons & 0x02) !== 0) { this.onEscape() } } diff --git a/frontend/src/utils/key-contexts/options-key-context.ts b/frontend/src/utils/key-contexts/options-key-context.ts new file mode 100644 index 0000000..713d151 --- /dev/null +++ b/frontend/src/utils/key-contexts/options-key-context.ts @@ -0,0 +1,13 @@ +import { KeyboardManager } from "../keyboard-manager"; +import { ModalKeyContext } from "./modal-key-context"; + +export class OptionsKeyContext extends ModalKeyContext { + public name: string = "OptionsKeyContext"; + + protected override onEscape(): void { + super.onEscape(); + this.store.optionsOpen = false; + KeyboardManager.switchContext('carousel'); + } + +} \ No newline at end of file diff --git a/frontend/src/utils/keyboard-manager.ts b/frontend/src/utils/keyboard-manager.ts index faf9cb7..6eadbae 100644 --- a/frontend/src/utils/keyboard-manager.ts +++ b/frontend/src/utils/keyboard-manager.ts @@ -1,15 +1,17 @@ import { ControllerState } from "../models/controller-state"; import { CarouselKeyContext } from "./key-contexts/carousel-key-context"; import { KeyContext } from "./key-contexts/key-context"; +import { OptionsKeyContext } from "./key-contexts/options-key-context"; import { SidebarKeyContext } from "./key-contexts/sidebar-key-context"; export class KeyboardManager { private static current?: KeyContext; - static switchContext(name: 'sidebar' | 'carousel') { + static switchContext(name: 'sidebar' | 'carousel' | 'options') { console.log("Switching context to " + name); if (name === 'sidebar') this.current = new SidebarKeyContext(); - else this.current = new CarouselKeyContext(); + else if (name === 'carousel') this.current = new CarouselKeyContext(); + else if (name === 'options') this.current = new OptionsKeyContext(); } static handle(event: KeyboardEvent) { diff --git a/lib/inputs/joystick.go b/lib/inputs/joystick.go index f3b123e..c948104 100644 --- a/lib/inputs/joystick.go +++ b/lib/inputs/joystick.go @@ -138,8 +138,9 @@ func Start(onStateChange func(ControllerState)) { state.Id = i - if state.Buttons != 0 || state.Joystick.X != 127 || state.Joystick.Y != 127 { - fmt.Printf("State changed on device %d\n", i) + // TODO Samuel please fix help me + if (state.Buttons != 0 || state.Joystick.X != 127 || state.Joystick.Y != 127) && !(state.Id == 0 && state.Buttons == 128 && state.Joystick.X != 127 && state.Joystick.Y != 127) { + // fmt.Printf("Id: %d - %d buttons ", state.Id, state.Buttons) onStateChange(*state) } } diff --git a/lib/provider/provider.go b/lib/provider/provider.go index aeb356a..38ae985 100644 --- a/lib/provider/provider.go +++ b/lib/provider/provider.go @@ -168,6 +168,12 @@ func ExtractGame(game models.Game) string { check(err) } gamePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), game.Id) + + _, err = os.Stat(filepath.Join(gamePath, game.Files)) + if err == nil { + return filepath.Join(gamePath, game.Files) + } + err = extractZipsInFolder(gamePath) check(err) gamePath = filepath.Join(gamePath, game.Files) From 789e0c1e1372d81339ece40a85d1dbda01604d0f Mon Sep 17 00:00:00 2001 From: Trit0 Date: Fri, 11 Jul 2025 18:55:44 -0400 Subject: [PATCH 20/24] better ui, uses fake data, wont be able to start games --- app.go | 22 ++- frontend/package.json.md5 | 2 +- frontend/src/App.vue | 79 ++++++--- frontend/src/assets/images/person.png | Bin 0 -> 12849 bytes frontend/src/components/DevCard.vue | 27 +++ frontend/src/components/GameCard.vue | 8 +- frontend/src/components/GameCarousel.vue | 2 +- frontend/src/components/GamePreview.vue | 118 ++++++++++--- frontend/src/components/ImageCarousel.vue | 57 +++++++ frontend/src/components/Sidebar.vue | 12 +- frontend/src/services/game-service.ts | 158 +++++++++++++----- frontend/src/stores/app-store.ts | 26 +-- frontend/src/style.css | 11 +- frontend/src/utils/image-manager.ts | 5 + .../key-contexts/carousel-key-context.ts | 28 +++- .../key-contexts/game-preview-key-context.ts | 61 +++++++ .../src/utils/key-contexts/key-context.ts | 11 ++ frontend/src/utils/keyboard-manager.ts | 24 ++- frontend/src/utils/use-keyboard-navigation.ts | 14 +- frontend/wailsjs/go/main/App.d.ts | 4 +- frontend/wailsjs/go/main/App.js | 4 + frontend/wailsjs/go/models.ts | 106 +++++++++++- frontend/wailsjs/runtime/runtime.d.ts | 2 +- go.mod | 2 +- go.sum | 4 +- lib/config/config.go | 3 - lib/models/game.go | 39 ++++- lib/provider/file-provider.go | 2 +- lib/provider/provider.go | 16 +- main.go | 31 +++- 30 files changed, 711 insertions(+), 167 deletions(-) create mode 100644 frontend/src/assets/images/person.png create mode 100644 frontend/src/components/DevCard.vue create mode 100644 frontend/src/components/ImageCarousel.vue diff --git a/app.go b/app.go index bacdfcc..3d8639e 100644 --- a/app.go +++ b/app.go @@ -14,7 +14,7 @@ import ( ) var ( - games []models.Game + games []models.Metadata lastEmitTimestamp = time.Now().Add(-10 * time.Second) emitInterval = 150 * time.Millisecond gameIsOpen = false @@ -30,9 +30,9 @@ func NewApp() *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 -func (a *App) startup(ctx context.Context) { +func (a *App) Startup(ctx context.Context) { a.ctx = ctx inputs.Start(a.onControllerChange) 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() return games } -func (a *App) LoadImage(gameId string, imageSrc string) provider.FileBlob { - return *provider.LoadImage(gameId, imageSrc) +func (a *App) LoadGamesNewModel() []models.Game { + 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 } diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 3dfc225..7f01a6d 100755 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -0d0407303e68412fce4ec212f727db35 \ No newline at end of file +0439ac4c00128949ad585ea9e2f68e8e \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 045c613..07b4e28 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,38 +1,39 @@ diff --git a/frontend/src/assets/images/person.png b/frontend/src/assets/images/person.png new file mode 100644 index 0000000000000000000000000000000000000000..b72cbcee33276860409fb7418e54a8af109a22d2 GIT binary patch literal 12849 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 + +
+ + +
+ {{ dev.name }} + {{ dev.role }} +
+
+ + + + + + \ No newline at end of file diff --git a/frontend/src/components/GameCard.vue b/frontend/src/components/GameCard.vue index e08eadd..ba81e77 100644 --- a/frontend/src/components/GameCard.vue +++ b/frontend/src/components/GameCard.vue @@ -1,16 +1,16 @@ diff --git a/frontend/src/components/GameCarousel.vue b/frontend/src/components/GameCarousel.vue index c8fd827..9903cc0 100644 --- a/frontend/src/components/GameCarousel.vue +++ b/frontend/src/components/GameCarousel.vue @@ -5,7 +5,7 @@ diff --git a/frontend/src/components/GamePreview.vue b/frontend/src/components/GamePreview.vue index 3ac3a0e..37dda83 100644 --- a/frontend/src/components/GamePreview.vue +++ b/frontend/src/components/GamePreview.vue @@ -1,37 +1,92 @@ @@ -39,11 +94,27 @@ import { models } from "../../wailsjs/go/models"; import Game = models.Game; 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, }>(); +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']); @@ -51,6 +122,7 @@ defineEmits(['qr']); img { transition: transform 0.2s; } + img:hover { transform: scale(1.05); } diff --git a/frontend/src/components/ImageCarousel.vue b/frontend/src/components/ImageCarousel.vue new file mode 100644 index 0000000..a1cfb7f --- /dev/null +++ b/frontend/src/components/ImageCarousel.vue @@ -0,0 +1,57 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/Sidebar.vue b/frontend/src/components/Sidebar.vue index 49a4828..e2a9231 100644 --- a/frontend/src/components/Sidebar.vue +++ b/frontend/src/components/Sidebar.vue @@ -1,25 +1,27 @@ diff --git a/frontend/src/services/game-service.ts b/frontend/src/services/game-service.ts index 284d55d..2efa052 100644 --- a/frontend/src/services/game-service.ts +++ b/frontend/src/services/game-service.ts @@ -1,52 +1,130 @@ import {LoadGames} from "../../wailsjs/go/main/App"; import {models} from "../../wailsjs/go/models"; +import Metadata = models.Metadata; import Game = models.Game; +import Developer = models.Developer; -// const localGames: Game[] = [ -// { -// id: 'g1', -// title: 'Neon Rush', -// description: 'A fast-paced 2D platformer through glowing neon levels.', -// playerCount: '1', -// languages: ['English', 'French'], -// tags: ['platformer', 'neon', 'funny'], -// media: [ -// '/assets/neon-rush-1.png', -// '/assets/neon-rush-2.png' -// ], -// thumbnail: '/assets/neon-rush-thumb.png', -// links: { -// Website: 'https://neonrush.dev', -// Itch: 'https://hyperbyte.itch.io/neonrush' -// }, -// executablePath: "." -// }, -// { -// id: 'g2', -// title: 'Ghost Bakery', -// description: 'Bake spooky pastries in a haunted kitchen.', -// playerCount: '1-2', -// languages: ['English'], -// tags: ['cooking', 'funny', 'co-op'], -// media: [ -// '/assets/ghost-bakery-1.jpg' -// ], -// thumbnail: '/assets/ghost-bakery-thumb.jpg', -// links: { -// Itch: 'https://phantomforks.itch.io/ghostbakery' -// }, -// executablePath: "." -// } -// ]; +const localGames: Game[] = [ + new Game({ + id: "ddf1ab0c-d86e-442f-8fd8-cfe8a0dc0a52", + title: "Soul Shaper", + version: "2.0.0", + 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!", + players: "1-1", + release: "2023-12-15", + modification: "2023-12-15", + executable: "game\\Soul Shaper.exe", + public_repository_link: "https://github.com/PFE033-ConjureOS/ConjureOS-SoulShaper", + genres: "action", + developers: [ + new Developer({ + name: "William Gingras" + }), + new Developer({ + name: "Yussef Shehadeh" + }), + new Developer({ + name: "Leah Fortin" + }), + new Developer({ + name: "Cameron Lamoureux" + }), + ], + thumbnail_path: "https://img.itch.zone/aW1hZ2UvMTkwMzc5MS8xMTgzNzY0Ny5wbmc=/original/r7iVIj.png", + itch_link: "https://craftelia.itch.io/gamelab2023-blood-god", + media_paths: [ + "https://img.itch.zone/aW1hZ2UvMTkwMzc5MS8xMTgyMjc0Ni5wbmc=/original/65c%2FQT.png", + "https://img.itch.zone/aW1hZ2UvMTkwMzc5MS8xMTgyMjc0Ny5wbmc=/original/4FiVFR.png", + "https://img.itch.zone/aW1hZ2UvMTkwMzc5MS8xMTgyMjc0OC5wbmc=/original/IkUG5I.png", + "https://img.itch.zone/aW1hZ2UvMTkwMzc5MS8xMTgzNzY1My5wbmc=/original/jtOMly.png" + ], + 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 { - // const source = localStorage.getItem('dataSource') || 'local'; - // if (source === 'local') return localGames; + const source = localStorage.getItem('dataSource') || 'local'; + if (source === 'local') return localGames; - // TODO games should be loaded from and started from the wails/go code const games = await LoadGames(); for (const game of games) { 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 + })), + }); } diff --git a/frontend/src/stores/app-store.ts b/frontend/src/stores/app-store.ts index 3c439fa..cbff8bf 100644 --- a/frontend/src/stores/app-store.ts +++ b/frontend/src/stores/app-store.ts @@ -10,14 +10,15 @@ export const useAppStore = defineStore('app', { selectedTag: null as string | null, transitionDirection: 'down' as 'up' | 'down', selectedGame: null as Game | null, - selectedGameIndex: -1, + selectedGameIndex: 0, qrLink: '' as string, optionsOpen: false as boolean, - gameIsStarting: false as boolean + gameIsStarting: false as boolean, + showSidebar: false as boolean, }), getters: { 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: { @@ -39,14 +40,11 @@ export const useAppStore = defineStore('app', { }, selectGame(index: number) { const games = this.filteredGames; - if (index >= 0 && index < games.length) { - this.selectedGameIndex = index; - this.selectedGame = games[index]; - } - else { - this.selectedGameIndex = -1; - this.selectedGame = null; - } + console.log(index) + index = mod(index, games.length); + console.log(index) + this.selectedGameIndex = index; + this.selectedGame = games[index] }, selectTag(tag: string) { this.selectedTag = tag; @@ -66,4 +64,8 @@ export const useAppStore = defineStore('app', { } } } -}); \ No newline at end of file +}); + +function mod(n, m) { + return ((n % m) + m) % m; +} \ No newline at end of file diff --git a/frontend/src/style.css b/frontend/src/style.css index f567423..535378c 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -1,14 +1,6 @@ @import "tailwindcss"; -html { - background-color: rgba(27, 38, 54, 1); - text-align: center; - color: white; -} - body { - margin: 0; - color: white; font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; @@ -24,5 +16,6 @@ body { #app { height: 100vh; - text-align: center; + background-color: black; + color: white; } diff --git a/frontend/src/utils/image-manager.ts b/frontend/src/utils/image-manager.ts index 45c6f12..e20cea8 100644 --- a/frontend/src/utils/image-manager.ts +++ b/frontend/src/utils/image-manager.ts @@ -5,6 +5,11 @@ export class ImageManager { static Dictionary: {[key: string]: string} = {} public static async getImage(gameId: string, src: string): Promise { + + if (src.startsWith("http")) { + return src; + } + const id = gameId + "\\" + src; console.log(gameId, src, id) if (this.Dictionary[id]) diff --git a/frontend/src/utils/key-contexts/carousel-key-context.ts b/frontend/src/utils/key-contexts/carousel-key-context.ts index e36648e..1326dee 100644 --- a/frontend/src/utils/key-contexts/carousel-key-context.ts +++ b/frontend/src/utils/key-contexts/carousel-key-context.ts @@ -6,12 +6,16 @@ export class CarouselKeyContext extends KeyContext { protected onKeyUp() { super.onKeyUp(); - this.store.moveTagUp(); + if (this.store.showSidebar) { + this.store.moveTagUp(); + } } protected onKeyDown() { super.onKeyDown(); - this.store.moveTagDown(); + if (this.store.showSidebar) { + this.store.moveTagDown(); + } } protected onKeyRight() { @@ -21,11 +25,13 @@ export class CarouselKeyContext extends KeyContext { protected onKeyLeft() { super.onKeyLeft(); - if (this.store.selectedGameIndex === 0) { + if (this.store.selectedGameIndex === 0 && this.store.showSidebar) { + console.log("fjxhfjkdshjkfd") KeyboardManager.switchContext("sidebar"); this.store.selectGame(-1); } else { + console.log("assdsds") this.store.moveGameLeft(); } } @@ -33,11 +39,23 @@ export class CarouselKeyContext extends KeyContext { protected onEscape() { super.onEscape(); this.store.optionsOpen = true; - KeyboardManager.switchContext('options'); + KeyboardManager.switchContext("options"); } protected 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(); + } + } } } \ No newline at end of file diff --git a/frontend/src/utils/key-contexts/game-preview-key-context.ts b/frontend/src/utils/key-contexts/game-preview-key-context.ts index 5e33230..d9af189 100644 --- a/frontend/src/utils/key-contexts/game-preview-key-context.ts +++ b/frontend/src/utils/key-contexts/game-preview-key-context.ts @@ -1,25 +1,86 @@ import { KeyContext } from "./key-context"; +import { KeyboardManager } from "../keyboard-manager"; export class GamePreviewKeyContext extends KeyContext { readonly name: string = "GamePreviewKeyContext"; protected onKeyUp() { super.onKeyUp(); + this.focusLastElement() } protected 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() { super.onKeyRight(); + this.focusNextElement(); + } + + protected onKeyLeft() { + super.onKeyLeft(); + this.focusLastElement(); } protected onEnter() { super.onEnter(); + (document.activeElement as any).click(); } protected onEscape() { super.onEscape(); + (document.activeElement as any).blur(); + KeyboardManager.switchContext("carousel"); } } \ No newline at end of file diff --git a/frontend/src/utils/key-contexts/key-context.ts b/frontend/src/utils/key-contexts/key-context.ts index dce2859..8f36342 100644 --- a/frontend/src/utils/key-contexts/key-context.ts +++ b/frontend/src/utils/key-contexts/key-context.ts @@ -26,6 +26,9 @@ export abstract class KeyContext { case 'Enter': this.onEnter(); break; + case ' ': + this.onSpace(); + break; default: break; } @@ -50,6 +53,10 @@ export abstract class KeyContext { this.onEnter() } + if ((state.buttons & 0x08) !== 0) { + this.onSpace() + } + // TODO should be 0x01 when the power button will work if ((state.buttons & 0x02) !== 0) { this.onEscape() @@ -79,4 +86,8 @@ export abstract class KeyContext { protected onEnter(): void { console.log('onEnter'); } + + protected onSpace(): void { + console.log('onSpace'); + } } \ No newline at end of file diff --git a/frontend/src/utils/keyboard-manager.ts b/frontend/src/utils/keyboard-manager.ts index 6eadbae..d9f42bb 100644 --- a/frontend/src/utils/keyboard-manager.ts +++ b/frontend/src/utils/keyboard-manager.ts @@ -3,15 +3,31 @@ import { CarouselKeyContext } from "./key-contexts/carousel-key-context"; import { KeyContext } from "./key-contexts/key-context"; import { OptionsKeyContext } from "./key-contexts/options-key-context"; import { SidebarKeyContext } from "./key-contexts/sidebar-key-context"; +import { GamePreviewKeyContext } from "./key-contexts/game-preview-key-context"; export class KeyboardManager { 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); - if (name === 'sidebar') this.current = new SidebarKeyContext(); - else if (name === 'carousel') this.current = new CarouselKeyContext(); - else if (name === 'options') this.current = new OptionsKeyContext(); + switch (name) { + case 'carousel': + 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) { diff --git a/frontend/src/utils/use-keyboard-navigation.ts b/frontend/src/utils/use-keyboard-navigation.ts index 666d59f..2141598 100644 --- a/frontend/src/utils/use-keyboard-navigation.ts +++ b/frontend/src/utils/use-keyboard-navigation.ts @@ -1,14 +1,14 @@ -import { onMounted, onBeforeUnmount } from 'vue'; +import { onMounted } from 'vue'; import { KeyboardManager } from "./keyboard-manager"; import { EventsOn } from "../../wailsjs/runtime"; export function useKeyboardNavigation(): void { onMounted(() => { - EventsOn("controller_change", KeyboardManager.handleState.bind(KeyboardManager)); - window.addEventListener('keydown', KeyboardManager.handle.bind(KeyboardManager)); - }); - - onBeforeUnmount(() => { - window.removeEventListener('keydown', KeyboardManager.handle); + if (!KeyboardManager.loaded) { + console.log("Loading inputs") + EventsOn("controller_change", KeyboardManager.handleState.bind(KeyboardManager)); + window.addEventListener('keydown', KeyboardManager.handle.bind(KeyboardManager)); + KeyboardManager.loaded = true; + } }); } diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 5b27953..952543f 100755 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -3,7 +3,9 @@ import {models} from '../models'; import {provider} from '../models'; -export function LoadGames():Promise>; +export function LoadGames():Promise>; + +export function LoadGamesNewModel():Promise>; export function LoadImage(arg1:string,arg2:string):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 2b66092..aa6e549 100755 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -6,6 +6,10 @@ export function LoadGames() { return window['go']['main']['App']['LoadGames'](); } +export function LoadGamesNewModel() { + return window['go']['main']['App']['LoadGamesNewModel'](); +} + export function LoadImage(arg1, arg2) { return window['go']['main']['App']['LoadImage'](arg1, arg2); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index 5ec11da..a650e30 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -1,6 +1,108 @@ 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 { + 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; Launch: string; Id: string; @@ -16,10 +118,9 @@ export namespace models { PublicRepositoryLink: string; Genres: string; Developers: string; - Title: string; static createFrom(source: any = {}) { - return new Game(source); + return new Metadata(source); } constructor(source: any = {}) { @@ -39,7 +140,6 @@ export namespace models { this.PublicRepositoryLink = source["PublicRepositoryLink"]; this.Genres = source["Genres"]; this.Developers = source["Developers"]; - this.Title = source["Title"]; } } diff --git a/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts index 4445dac..94778df 100644 --- a/frontend/wailsjs/runtime/runtime.d.ts +++ b/frontend/wailsjs/runtime/runtime.d.ts @@ -134,7 +134,7 @@ export function WindowIsFullscreen(): Promise; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; +export function WindowSetSize(width: number, height: number): Promise; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. diff --git a/go.mod b/go.mod index aa26443..a42c190 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.23.5 require ( 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 ) diff --git a/go.sum b/go.sum index 58e26dc..9690a5f 100644 --- a/go.sum +++ b/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/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= 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.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY= +github.com/wailsapp/wails/v2 v2.9.2 h1:Xb5YRTos1w5N7DTMyYegWaGukCP2fIaX9WF21kPPF2k= +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/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= diff --git a/lib/config/config.go b/lib/config/config.go index 6c6471c..22e5bed 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -1,7 +1,6 @@ package config import ( - "fmt" "os" "path/filepath" "runtime" @@ -21,13 +20,11 @@ func GetDefaultConjureOsDirectory() string { if err != nil { panic(err) } - fmt.Println("User config dir:", configDir) cacheDir, err := os.UserCacheDir() if err != nil { panic(err) } - fmt.Println("User cache dir:", cacheDir) switch runtime.GOOS { // I want the data to be store in LocalAppData not RoamingAppData diff --git a/lib/models/game.go b/lib/models/game.go index 69b5466..f72583b 100644 --- a/lib/models/game.go +++ b/lib/models/game.go @@ -1,6 +1,6 @@ package models -type Game struct { +type Metadata struct { Collection string Launch string Id string @@ -16,5 +16,40 @@ type Game struct { PublicRepositoryLink string Genres 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"` } diff --git a/lib/provider/file-provider.go b/lib/provider/file-provider.go index 3e08406..bcc1a6a 100644 --- a/lib/provider/file-provider.go +++ b/lib/provider/file-provider.go @@ -12,7 +12,7 @@ func LoadImage(gameId string, imageSrc string) *FileBlob { imagePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), gameId, imageSrc) blob, err := GetFileBlob(imagePath) if err != nil { - panic(err) + return nil } return blob diff --git a/lib/provider/provider.go b/lib/provider/provider.go index 38ae985..a3329ec 100644 --- a/lib/provider/provider.go +++ b/lib/provider/provider.go @@ -160,7 +160,7 @@ func extractZipsInFolder(folder string) error { return nil } -func ExtractGame(game models.Game) string { +func ExtractGame(game models.Metadata) string { info, err := os.Stat(filepath.Join(config.GetDefaultConjureGamesDirectory(), game.Id)) if err != nil || !info.IsDir() { gamePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), fmt.Sprintf("%s.conj", game.Id)) @@ -180,7 +180,7 @@ func ExtractGame(game models.Game) string { return gamePath } -func GetConjureGameInfo() []models.Game { +func GetConjureGameInfo() []models.Metadata { gamePath := config.GetDefaultConjureGamesDirectory() @@ -189,7 +189,7 @@ func GetConjureGameInfo() []models.Game { log.Fatal(err) } - var games []models.Game + var games []models.Metadata for _, e := range entries { 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() entries, err := os.ReadDir(newPath) check(err) - var games []models.Game + var games []models.Metadata for _, e := range entries { if e.IsDir() { @@ -268,14 +268,14 @@ func readFolder(entry fs.DirEntry, path string) []models.Game { return games } -func parseGameInfoFromFile(path string) models.Game { +func parseGameInfoFromFile(path string) models.Metadata { data, err := os.ReadFile(path) check(err) return parseGameInfo(data) } -func parseGameInfo(data []byte) models.Game { - game := models.Game{} +func parseGameInfo(data []byte) models.Metadata { + game := models.Metadata{} err := yaml.Unmarshal(data, &game) check(err) return game diff --git a/main.go b/main.go index 2c5465e..c476859 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,10 @@ package main import ( "embed" + "fmt" + "net/http" + "os" + "strings" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" @@ -11,6 +15,27 @@ import ( //go:embed all:frontend/dist 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() { // Create an instance of the app structure app := NewApp() @@ -22,10 +47,10 @@ func main() { Height: 768, Fullscreen: true, 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{}{ app, }, From 889a00fc49cefa496b9be991da93a7bc44fbbe9a Mon Sep 17 00:00:00 2001 From: Trit0 Date: Sat, 12 Jul 2025 21:01:15 -0400 Subject: [PATCH 21/24] not always decompress --- frontend/src/App.vue | 4 +-- frontend/src/components/GameCard.vue | 2 +- frontend/src/components/GamePreview.vue | 13 ++++--- frontend/wailsjs/go/models.ts | 28 +++++++-------- lib/models/game.go | 16 ++++----- lib/provider/provider.go | 46 ++++++------------------- 6 files changed, 42 insertions(+), 67 deletions(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 07b4e28..9cf16b9 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -67,8 +67,8 @@ onMounted(async () => { // 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 primary = selectedGame?.value?.colorScheme?.primary ?? "#4d97f8"; + const secondary = selectedGame?.value?.colorScheme?.secondary ?? "#100a7d"; const backgroundImage = `linear-gradient(135deg, ${primary}, ${secondary})`; // const backgroundImage = !!bgImage ? `url(${bgImage})` : `linear-gradient(135deg, ${primary}, ${secondary})`; diff --git a/frontend/src/components/GameCard.vue b/frontend/src/components/GameCard.vue index ba81e77..9cfe232 100644 --- a/frontend/src/components/GameCard.vue +++ b/frontend/src/components/GameCard.vue @@ -7,7 +7,7 @@ ]" >
- +

{{ game.title }}

@@ -18,7 +18,7 @@
@@ -56,8 +56,8 @@
@@ -60,9 +66,7 @@ const { } = storeToRefs(store); onMounted(async () => { - games.value = await fetchGames(); - tags.value = [...new Set(games.value.flatMap(game => game.genres.split(",")))]; - store.selectTag(tags.value[0]) + await store.loadGames(); }); // Create the gradient background style diff --git a/frontend/src/components/GameCarousel.vue b/frontend/src/components/GameCarousel.vue index 9903cc0..79139ad 100644 --- a/frontend/src/components/GameCarousel.vue +++ b/frontend/src/components/GameCarousel.vue @@ -1,7 +1,7 @@