Compare commits

..

32 Commits

Author SHA1 Message Date
Trit0
29bcd3cd6b test 2025-09-15 21:29:56 -04:00
Trit0
b85ca7c3cf show case data 2025-09-15 20:43:45 -04:00
Trit0
08efedc31b fix a couple of stuff 2025-07-18 19:43:49 -04:00
Trit0
5cec1493f6 control helper and cute tags 2025-07-13 20:41:03 -04:00
club
87f34a05db hahahahahahah 2025-07-12 23:44:45 -04:00
Trit0
c704b72a03 not always decompress 2025-07-12 21:01:15 -04:00
Trit0
3161bbbba1 better ui, uses fake data, wont be able to start games 2025-07-11 18:55:44 -04:00
club
11681873b6 all two controllers and no extract every time 2025-07-03 22:12:39 -04:00
Trit0
2368a00157 try to support both controllers 2025-07-03 19:55:34 -04:00
club
4a88270535 it only support one controller but thats already good 2025-07-01 22:18:52 -04:00
club
2763543104 Merge branch 'tristan-wip' of https://gitea.clubconjure.com/Conjure/conjure-os into tristan-wip 2025-07-01 16:30:35 -04:00
Trit0
8a5de26ca3 test 2025-07-01 16:30:23 -04:00
club
87eb3de238 Merge branch 'tristan-wip' of https://gitea.clubconjure.com/Conjure/conjure-os into tristan-wip 2025-06-30 21:32:35 -04:00
Trit0
8282473657 image! 2025-06-30 21:32:08 -04:00
club
d246b5cd3d Merge branch 'tristan-wip' of https://gitea.clubconjure.com/Conjure/conjure-os into tristan-wip 2025-06-30 20:51:30 -04:00
Trit0
f4e6226d5a test 2 2025-06-30 20:51:10 -04:00
club
552d30098f Merge branch 'tristan-wip' of https://gitea.clubconjure.com/Conjure/conjure-os into tristan-wip 2025-06-30 20:43:19 -04:00
Trit0
0b0e6af52a test 2025-06-30 20:43:07 -04:00
club
7f483c8d74 Merge branch 'tristan-wip' of https://gitea.clubconjure.com/Conjure/conjure-os into tristan-wip 2025-06-29 20:04:39 -04:00
Trit0
49ddcfe130 chui tanner 2025-06-29 20:04:14 -04:00
club
ac5eff0c09 Merge branch 'tristan-wip' of https://gitea.clubconjure.com/Conjure/conjure-os into tristan-wip 2025-06-29 20:02:08 -04:00
Trit0
c5086f0ecc chui tanner 2025-06-29 20:01:50 -04:00
club
563070aeeb Merge branch 'tristan-wip' of https://gitea.clubconjure.com/Conjure/conjure-os into tristan-wip 2025-06-29 18:59:19 -04:00
Trit0
5a2a070f64 images perchance? 3 2025-06-29 18:58:11 -04:00
Trit0
b46eefa34a images perchance? 2025-06-29 18:35:33 -04:00
Trit0
22e2cc088c stupid mistake 2025-06-29 18:23:25 -04:00
club
4ce60e0844 Merge branch 'tristan-wip' of https://gitea.clubconjure.com/Conjure/conjure-os into tristan-wip 2025-06-29 18:21:17 -04:00
Trit0
9180a94c56 start game perchance? 2025-06-29 18:20:53 -04:00
club
e3910c064f Merge branch 'tristan-wip' of https://gitea.clubconjure.com/Conjure/conjure-os into tristan-wip 2025-06-29 18:00:13 -04:00
Trit0
41460c0ca2 wip 4 2025-06-29 17:59:54 -04:00
club
77e1ee0c73 Merge branch 'tristan-wip' of https://gitea.clubconjure.com/Conjure/conjure-os into tristan-wip 2025-06-29 17:50:39 -04:00
Trit0
c350aedcb5 wip 3 2025-06-29 17:50:09 -04:00
11 changed files with 256 additions and 29 deletions

21
app.go
View File

@ -98,13 +98,28 @@ func (a *App) StartGame(id string) {
fmt.Println()
}
func (a *App) LoadGames() []models.Metadata {
func (a *App) LoadGames(mode string) []models.Metadata {
games = provider.GetConjureGameInfo()
if mode == "remote-showcase" {
games = provider.Filter(games, func(game models.Metadata) bool {
return game.Game == "Soul Shaper" || game.Game == "One Pixel Remaning" || game.Game == "Pong"
})
}
return games
}
func (a *App) LoadGamesNewModel() []models.Game {
return []models.Game{}
func (a *App) LoadGamesNewModel(mode string) []models.Game {
mgames := provider.GetConjureGameInfoNew()
if mode == "remote-showcase" {
mgames = provider.Filter(mgames, func(game models.Game) bool {
return game.Title == "Soul Shaper" || game.Title == "One Pixel Remaning" || game.Title == "Pong"
})
}
return mgames
}
func (a *App) Log(message string) {

View File

@ -30,8 +30,8 @@
</div>
</div>
<OptionsModal v-if="optionsOpen" @close="optionsOpen = false"/>
<QrModal v-if="qrLink" :link="qrLink" @close="qrLink = ''"/>
<OptionsModal v-if="optionsOpen" @close="store.closeModal()"/>
<QrModal v-if="qrLink" :link="qrLink" @close="store.closeModal()"/>
<LoadingModal v-if="gameIsStarting"/>
</div>
</template>

View File

@ -9,6 +9,8 @@
<select v-model="dataSource" class="mt-1 block w-full">
<option value="local">Local (Mock)</option>
<option value="remote">Remote (API)</option>
<option value="remote-showcase">Showcase</option>
<option value="new-model">New Model</option>
</select>
</label>
@ -28,15 +30,34 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useAppStore } from "../stores/app-store";
import { log } from "../services/logger-service";
const emit = defineEmits(['close']);
const store = useAppStore();
const dataSource = ref(localStorage.getItem('dataSource') || 'local');
const volume = ref(+localStorage.getItem('volume') || 50);
function save() {
async function save() {
const oldSource = localStorage.getItem('dataSource');
log(dataSource.value);
localStorage.setItem('dataSource', dataSource.value);
localStorage.setItem('volume', volume.value.toString());
try {
await store.loadGames();
}
catch (error) {
console.error(error);
}
//
// if (oldSource !== dataSource.value) {
// log("Reloading games")
// await store.loadGames();
// }
emit('close');
}
</script>

View File

@ -1,5 +1,6 @@
import { KeyContext } from "./key-context";
import { InputManager } from "../input-manager";
import { mod } from "../../stores/app-store";
export class GamePreviewKeyContext extends KeyContext {
readonly name: string = "GamePreviewKeyContext";
@ -58,7 +59,7 @@ export class GamePreviewKeyContext extends KeyContext {
.filter(el => (el as any).offsetParent !== null); // filter out hidden elements
const currentIndex = focusableElements.indexOf(document.activeElement);
const nextIndex = (currentIndex - 1) % focusableElements.length;
const nextIndex = mod((currentIndex - 1), focusableElements.length);
(focusableElements[nextIndex] as any).focus();
}

View File

@ -1,5 +1,7 @@
import { InputManager } from "../input-manager";
import { ModalKeyContext } from "./modal-key-context";
import { log } from "../../services/logger-service";
import { mod } from "../../stores/app-store";
export class OptionsKeyContext extends ModalKeyContext {
public name: string = "OptionsKeyContext";
@ -8,7 +10,110 @@ export class OptionsKeyContext extends ModalKeyContext {
super.onEscape();
this.store.optionsOpen = false;
this.store.qrLink = "";
(document.activeElement as any).blur();
InputManager.switchContext('carousel');
}
protected onKeyUp() {
super.onKeyUp();
this.focusLastElement()
}
protected onKeyDown() {
super.onKeyDown();
this.focusNextElement();
}
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();
}
protected onEnter() {
super.onEnter();
const activeElement = document.activeElement;
console.log(activeElement);
(document.activeElement as any).click();
}
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 = mod((currentIndex - 1), focusableElements.length);
(focusableElements[nextIndex] as any).focus();
}
protected onKeyRight() {
super.onKeyRight();
const activeElement = document.activeElement;
if (activeElement.tagName.toLowerCase() === 'select') {
const mySelect = document.activeElement as HTMLSelectElement;
const currentIndex = mySelect.selectedIndex;
mySelect.selectedIndex = (currentIndex + 1) % mySelect.options.length;
}
else if (activeElement && activeElement.tagName.toLowerCase() === 'input' && (activeElement as HTMLInputElement).type === 'range') {
// Get the current value, convert to number, add 10, and set the new value
const range = (activeElement as HTMLInputElement);
let currentValue = parseInt(range.value);
range.value = (currentValue + 10).toString();
}
else {
this.focusNextElement();
}
}
protected onKeyLeft() {
super.onKeyLeft();
const activeElement = document.activeElement;
if (activeElement.tagName.toLowerCase() === 'select') {
const mySelect = document.activeElement as HTMLSelectElement;
const currentIndex = mySelect.selectedIndex;
mySelect.selectedIndex = mod((currentIndex - 1), mySelect.options.length);
}
else if (activeElement && activeElement.tagName.toLowerCase() === 'input' && (activeElement as HTMLInputElement).type === 'range') {
// Get the current value, convert to number, add 10, and set the new value
const range = (activeElement as HTMLInputElement);
let currentValue = parseInt(range.value);
range.value = (currentValue - 10).toString();
}
else {
this.focusLastElement();
}
}
setAvailableActions() {
this.store.currentAvailableActions = {
"order": "controller,keyboard",
"1,Enter": "Click",
"Power,Escape": "Back"
};
}
}

View File

@ -1,10 +1,10 @@
import {LoadGames} from "../../wailsjs/go/main/App";
import { LoadGames, LoadGamesNewModel } 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[] = [
const mockGames: Game[] = [
new Game({
id: "ddf1ab0c-d86e-442f-8fd8-cfe8a0dc0a52",
title: "Soul Shaper",
@ -98,13 +98,17 @@ const localGames: Game[] = [
export async function fetchGames(): Promise<Game[]> {
const source = localStorage.getItem('dataSource') || 'local';
if (source === 'local') return localGames;
if (source === 'local') return mockGames;
const games = await LoadGames();
if (source === "new-model") {
return (await LoadGamesNewModel(source)) ?? [];
}
const games = (await LoadGames(source)) ?? [];
for (const game of games) {
console.log(game)
}
return [...games.map(convertToNewFormat), ...localGames];
return [...games.map(convertToNewFormat)];
}
function convertToNewFormat(metadata: Metadata): Game {

View File

@ -34,8 +34,10 @@ export const useAppStore = defineStore('app', {
console.log(this.games);
this.tags = [...new Set(this.games.flatMap(game => game.genres.split(",")))];
if (this.tags.length > 0) {
this.selectTag(this.tags[0])
this.selectTag(this.tags[0]);
}
this.selectGame(0);
},
moveGameRight() {
this.selectGame(this.selectedGameIndex + 1);
@ -69,6 +71,11 @@ export const useAppStore = defineStore('app', {
this.qrLink = link;
InputManager.switchContext("options");
},
closeModal() {
this.qrLink = "";
this.optionsOpen = false;
InputManager.switchContext("carousel");
},
async startSelectedGame() {
if (this.selectedGame && !this.gameIsStarting) {
this.gameIsStarting = true;
@ -82,6 +89,6 @@ export const useAppStore = defineStore('app', {
}
});
function mod(n, m) {
export function mod(n, m) {
return ((n % m) + m) % m;
}

View File

@ -3,9 +3,9 @@
import {models} from '../models';
import {provider} from '../models';
export function LoadGames():Promise<Array<models.Metadata>>;
export function LoadGames(arg1:string):Promise<Array<models.Metadata>>;
export function LoadGamesNewModel():Promise<Array<models.Game>>;
export function LoadGamesNewModel(arg1:string):Promise<Array<models.Game>>;
export function LoadImage(arg1:string,arg2:string):Promise<provider.FileBlob>;

View File

@ -2,12 +2,12 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function LoadGames() {
return window['go']['main']['App']['LoadGames']();
export function LoadGames(arg1) {
return window['go']['main']['App']['LoadGames'](arg1);
}
export function LoadGamesNewModel() {
return window['go']['main']['App']['LoadGamesNewModel']();
export function LoadGamesNewModel(arg1) {
return window['go']['main']['App']['LoadGamesNewModel'](arg1);
}
export function LoadImage(arg1, arg2) {

View File

@ -31,6 +31,7 @@ func GetDefaultConjureOsDirectory() string {
case "windows":
return filepath.Join(cacheDir, conjureDirectoryName, conjureOsDirectoryName)
case "darwin":
return filepath.Join(configDir, conjureDirectoryName, conjureOsDirectoryName)
case "linux":
return filepath.Join(configDir, conjureDirectoryName, conjureOsDirectoryName)
default:

View File

@ -182,16 +182,14 @@ func ExtractGame(game models.Metadata) string {
func GetConjureGameInfo() []models.Metadata {
gamePath := config.GetDefaultConjureGamesDirectory()
fmt.Println("Loading games....")
fmt.Println(gamePath)
fmt.Println("Loading games from path " + gamePath + "....")
var games []models.Metadata
entries, err := os.ReadDir(gamePath)
if err != nil {
log.Fatal(err)
return games
}
var games []models.Metadata
for _, e := range entries {
if e.IsDir() {
continue
@ -219,7 +217,7 @@ func GetConjureGameInfo() []models.Metadata {
fmt.Println("Contents of metadata.txt:")
metadata, err := io.ReadAll(rc)
check(err)
game := parseGameInfo([]byte(escapeBackslashes(string(metadata))))
game := parseGameInfoOld([]byte(escapeBackslashes(string(metadata))))
fmt.Println(game.ThumbnailPath)
games = append(games, game)
}
@ -228,13 +226,82 @@ func GetConjureGameInfo() []models.Metadata {
}
if len(games) > 0 {
fmt.Println("Found Conjure Games:", len(games))
fmt.Println("Found Conjure Games: ", len(games))
} else {
fmt.Println("No Conjure games Found")
fmt.Println("No Conjure games found")
}
return games
}
func GetConjureGameInfoNew() []models.Game {
gamePath := config.GetDefaultConjureGamesDirectory()
fmt.Println("Loading games from path " + gamePath + "....")
var games []models.Game
entries, err := os.ReadDir(gamePath)
if err != nil {
return games
}
for _, e := range entries {
if e.IsDir() {
continue
} else if filepath.Ext(e.Name()) == ".conj" {
conjPath := filepath.Join(gamePath, e.Name())
conjBase := strings.TrimSuffix(conjPath, ".conj")
// Check if the destination folder already exists
if _, err := os.Stat(conjBase); os.IsNotExist(err) {
err = extractZipToSiblingFolder(conjPath)
check(err)
}
// Now read metadata from the extracted directory
entries, err := os.ReadDir(conjBase)
check(err)
fmt.Println("Contents of", conjPath)
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)
check(err)
game, err := parseGameInfo([]byte(escapeBackslashes(string(metadata))))
if err != nil {
continue
}
fmt.Println(game.ThumbnailPath)
games = append(games, game)
}
}
}
}
if len(games) > 0 {
fmt.Println("Found Conjure Games: ", len(games))
} else {
fmt.Println("No Conjure games found")
}
return games
}
func Filter[T any](s []T, predicate func(T) bool) []T {
result := make([]T, 0, len(s)) // Pre-allocate for efficiency
for _, v := range s {
if predicate(v) {
result = append(result, v)
}
}
return result
}
// Function to escape backslashes in the YAML string
func escapeBackslashes(input string) string {
// Replace every single backslash with double backslashes
@ -253,13 +320,19 @@ func printIndentedPath(path string) {
}
}
func parseGameInfo(data []byte) models.Metadata {
func parseGameInfoOld(data []byte) models.Metadata {
game := models.Metadata{}
err := yaml.Unmarshal(data, &game)
check(err)
return game
}
func parseGameInfo(data []byte) (models.Game, error) {
game := models.Game{}
err := yaml.Unmarshal(data, &game)
return game, err
}
func check(e error) {
if e != nil {
panic(e)