Updates games, and add ssr games page.
This commit is contained in:
parent
4579865ee4
commit
c3e17ea81a
20 changed files with 2011 additions and 1946 deletions
|
|
@ -11,6 +11,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"no-unused-vars": "error",
|
"no-unused-vars": "error",
|
||||||
|
"no-undef": "off",
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,11 @@ export default defineConfig({
|
||||||
adapter: node({
|
adapter: node({
|
||||||
mode: "middleware",
|
mode: "middleware",
|
||||||
}),
|
}),
|
||||||
|
vite: {
|
||||||
|
server: {
|
||||||
|
watch: {
|
||||||
|
usePolling: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
3
index.js
3
index.js
|
|
@ -16,6 +16,8 @@ import dotenv from "dotenv";
|
||||||
import cookieParser from "cookie-parser";
|
import cookieParser from "cookie-parser";
|
||||||
import wisp from "wisp-server-node";
|
import wisp from "wisp-server-node";
|
||||||
import { masqrCheck } from "./masqr.js";
|
import { masqrCheck } from "./masqr.js";
|
||||||
|
import { handler as ssrHandler } from './dist/server/entry.mjs';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const whiteListedDomains = ["aluu.xyz", "localhost:3000"];
|
const whiteListedDomains = ["aluu.xyz", "localhost:3000"];
|
||||||
|
|
@ -52,6 +54,7 @@ const rammerheadScopes = [
|
||||||
];
|
];
|
||||||
const rammerheadSession = /^\/[a-z0-9]{32}/;
|
const rammerheadSession = /^\/[a-z0-9]{32}/;
|
||||||
const app = express();
|
const app = express();
|
||||||
|
app.use(ssrHandler);
|
||||||
app.use(compression({ threshold: 0, filter: () => true }));
|
app.use(compression({ threshold: 0, filter: () => true }));
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
|
|
||||||
|
|
|
||||||
3715
pnpm-lock.yaml
generated
3715
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1 +1 @@
|
||||||
Subproject commit 8793db1e2fe64cae8936c00bcd4f333f158f6ebd
|
Subproject commit 3feceedb2e0ff4266df7a593c2e24945c1c46d65
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"nav": {
|
|
||||||
"brand": "Alu",
|
|
||||||
"games": "Games",
|
|
||||||
"settings": "Settings"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"brand": "アルー",
|
|
||||||
"games": "ゲーム",
|
|
||||||
"settings": "設定"
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
importScripts("/uv/uv.bundle.js")
|
importScripts("/uv/uv.bundle.js", "/uv.config.js", "/workerware/workerware.js");
|
||||||
importScripts("/uv.config.js")
|
importScripts( __uv$config.sw);
|
||||||
importScripts( __uv$config.sw, "/workerware/workerware.js");
|
|
||||||
|
|
||||||
const ww = new WorkerWare({
|
const ww = new WorkerWare({
|
||||||
debug: true,
|
debug: true,
|
||||||
|
|
@ -17,6 +16,8 @@ function loadExtensionScripts() {
|
||||||
let extensions = request.result;
|
let extensions = request.result;
|
||||||
extensions.forEach((extension) => {
|
extensions.forEach((extension) => {
|
||||||
if (extension.type != "serviceWorker") return;
|
if (extension.type != "serviceWorker") return;
|
||||||
|
// Loads the function to be added as a middleware into global scope.
|
||||||
|
// The function defined should NOT immediately execute any function.
|
||||||
eval(atob(extension.scriptCopy));
|
eval(atob(extension.scriptCopy));
|
||||||
ww.use({
|
ww.use({
|
||||||
function: self[extension.entryNamespace][extension.entryFunc],
|
function: self[extension.entryNamespace][extension.entryFunc],
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
---
|
---
|
||||||
const { name, image, slugName } = Astro.props;
|
const { name, image, slugName, lang } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="game" data-name={name}>
|
<div class="game" data-name={name}>
|
||||||
<a href=`/games/${slugName || name}`>
|
<a href=`/${lang}/game/${slugName || name}`>
|
||||||
<img class="game-img" src={image} alt="" />
|
<img class="game-img" src={image} alt="" />
|
||||||
</a>
|
</a>
|
||||||
<p class="game-title">{name}</p>
|
<p class="game-title">{name}</p>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
document.addEventListener("astro:after-swap", switchTheme);
|
document.addEventListener("astro:after-swap", switchTheme);
|
||||||
|
|
||||||
import IDBManager, { type ExtensionMetadata } from "@components/ts/IDBManager";
|
import IDBManager from "@components/ts/IDBManager";
|
||||||
const idb = IDBManager.loadIDB("AluDB", 1);
|
const idb = IDBManager.loadIDB("AluDB", 1);
|
||||||
|
|
||||||
idb.onsuccess = () => {
|
idb.onsuccess = () => {
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
store.getAll().onsuccess = (event) => {
|
store.getAll().onsuccess = (event) => {
|
||||||
const result = (event.target as IDBRequest).result;
|
const result = (event.target as IDBRequest).result;
|
||||||
if (result) {
|
if (result) {
|
||||||
result.forEach((extension: ExtensionMetadata) => {
|
result.forEach((extension: IExtensionMetadata) => {
|
||||||
if (extension.type === "theme" && extension.themeName) {
|
if (extension.type === "theme" && extension.themeName) {
|
||||||
// Load theme CSS
|
// Load theme CSS
|
||||||
window.loadedThemeAtob = atob(extension.scriptCopy!);
|
window.loadedThemeAtob = atob(extension.scriptCopy!);
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,3 @@
|
||||||
type VALID_EXT_TYPES = "serviceWorker" | "theme" | "page";
|
|
||||||
export interface ExtensionMetadata {
|
|
||||||
title: string;
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
// Versions should follow semantic versioning
|
|
||||||
version: string;
|
|
||||||
script: string;
|
|
||||||
entryNamespace?: string;
|
|
||||||
entryFunc?: string;
|
|
||||||
scriptCopy: string | null;
|
|
||||||
type: VALID_EXT_TYPES;
|
|
||||||
themeName?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export let CurrentIDB: IDBDatabase;
|
export let CurrentIDB: IDBDatabase;
|
||||||
|
|
||||||
export function loadIDB(name: string, version: number) {
|
export function loadIDB(name: string, version: number) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,4 @@
|
||||||
type Extension = {
|
export async function retrieveExtensions(type: ExtType) {
|
||||||
name: string;
|
|
||||||
script: string;
|
|
||||||
serviceWorkerExtension: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function retrieveExtensions() {
|
|
||||||
const extensionsArr: Array<Extension> = [];
|
const extensionsArr: Array<Extension> = [];
|
||||||
const db = await new Promise<IDBDatabase>((resolve, reject) => {
|
const db = await new Promise<IDBDatabase>((resolve, reject) => {
|
||||||
const request = indexedDB.open("AluDB", 1);
|
const request = indexedDB.open("AluDB", 1);
|
||||||
|
|
@ -12,7 +6,7 @@ export async function retrieveExtensions() {
|
||||||
request.onerror = reject;
|
request.onerror = reject;
|
||||||
});
|
});
|
||||||
|
|
||||||
const transaction = (await db).transaction("InstalledExtensions", "readwrite");
|
const transaction = db.transaction("InstalledExtensions", "readwrite");
|
||||||
const objectStore = transaction.objectStore("InstalledExtensions");
|
const objectStore = transaction.objectStore("InstalledExtensions");
|
||||||
const extensions: Array<Extension> = await new Promise((resolve, reject) => {
|
const extensions: Array<Extension> = await new Promise((resolve, reject) => {
|
||||||
const request = objectStore.getAll();
|
const request = objectStore.getAll();
|
||||||
|
|
@ -20,10 +14,6 @@ export async function retrieveExtensions() {
|
||||||
request.onerror = reject;
|
request.onerror = reject;
|
||||||
});
|
});
|
||||||
|
|
||||||
extensions.forEach(async (extension: Extension) => {
|
extensions.forEach(async (extension: Extension) => {});
|
||||||
if (extension.serviceWorkerExtension) {
|
|
||||||
extensionsArr.push(extension);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return extensionsArr;
|
return extensionsArr;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ import "notyf/notyf.min.css";
|
||||||
import { Notyf } from "notyf";
|
import { Notyf } from "notyf";
|
||||||
import marketplaceManifest from "../../json/marketplace.json";
|
import marketplaceManifest from "../../json/marketplace.json";
|
||||||
const installButtons = document.getElementsByClassName("btn-install");
|
const installButtons = document.getElementsByClassName("btn-install");
|
||||||
import IDBManager, { type ExtensionMetadata } from "./IDBManager";
|
import IDBManager from "./IDBManager";
|
||||||
|
|
||||||
|
const extManifest = marketplaceManifest as ExtensionMetadataJSON;
|
||||||
|
|
||||||
// This just makes it shorter to type
|
// This just makes it shorter to type
|
||||||
interface HTMLButton extends HTMLButtonElement {}
|
interface HTMLButton extends HTMLButtonElement {}
|
||||||
|
|
@ -13,12 +15,6 @@ enum EXT_RETURN {
|
||||||
ALREADY_INSTALLED = 1,
|
ALREADY_INSTALLED = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstallReturn = {
|
|
||||||
code: EXT_RETURN;
|
|
||||||
slug: string;
|
|
||||||
title?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
Array.from(installButtons).forEach((btn) => {
|
Array.from(installButtons).forEach((btn) => {
|
||||||
btn.addEventListener("click", async (event) => {
|
btn.addEventListener("click", async (event) => {
|
||||||
const ele = event.target as HTMLButton;
|
const ele = event.target as HTMLButton;
|
||||||
|
|
@ -81,13 +77,13 @@ Array.from(installButtons).forEach((btn) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getMarketplaceObj(slug: string): Promise<ExtensionMetadata> {
|
async function getMarketplaceObj(slug: string): Promise<IExtensionMetadata> {
|
||||||
const manifest = (marketplaceManifest as unknown as { [key: string]: ExtensionMetadata })[slug];
|
const manifest = extManifest[slug];
|
||||||
manifest.scriptCopy = btoa(await fetch(manifest.script).then((res) => res.text()));
|
manifest.scriptCopy = btoa(await fetch(manifest.script).then((res) => res.text()));
|
||||||
return manifest;
|
return manifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function installExtension(ext: ExtensionMetadata, slug: string): Promise<InstallReturn> {
|
async function installExtension(ext: IExtensionMetadata, slug: string): Promise<InstallReturn> {
|
||||||
return new Promise<InstallReturn>((resolve, reject) => {
|
return new Promise<InstallReturn>((resolve, reject) => {
|
||||||
const request = IDBManager.GetIDB();
|
const request = IDBManager.GetIDB();
|
||||||
const transaction = request.transaction("InstalledExtensions", "readwrite");
|
const transaction = request.transaction("InstalledExtensions", "readwrite");
|
||||||
|
|
@ -152,8 +148,6 @@ document.addEventListener("astro:after-swap", () => {
|
||||||
addUninstallEventListeners();
|
addUninstallEventListeners();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function uninstallExtension(slug: string): Promise<InstallReturn> {
|
async function uninstallExtension(slug: string): Promise<InstallReturn> {
|
||||||
return new Promise<InstallReturn>((resolve, reject) => {
|
return new Promise<InstallReturn>((resolve, reject) => {
|
||||||
if (!slug || slug == null) {
|
if (!slug || slug == null) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { ui, defaultLang } from "./ui";
|
||||||
|
|
||||||
export const STATIC_PATHS = [{ params: { lang: "en" } }, { params: { lang: "jp" } }];
|
export const STATIC_PATHS = [{ params: { lang: "en" } }, { params: { lang: "jp" } }];
|
||||||
|
|
||||||
function getLangFromUrl(url: URL) {
|
export function getLangFromUrl(url: URL) {
|
||||||
// comma lol
|
// comma lol
|
||||||
const [, lang] = url.pathname.split("/");
|
const [, lang] = url.pathname.split("/");
|
||||||
if (lang in ui) return lang as keyof typeof ui;
|
if (lang in ui) return lang as keyof typeof ui;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"2048": {
|
"2048": {
|
||||||
"name": "2048",
|
"name": "2048",
|
||||||
|
"description": "Join the numbers and get to the 2048 tile!",
|
||||||
"image": "/games/2048/logo.png",
|
"image": "/games/2048/logo.png",
|
||||||
"slug": "2048"
|
"slug": "2048"
|
||||||
},
|
},
|
||||||
|
|
@ -124,11 +125,6 @@
|
||||||
"image": "/games/game-inside/logo.png",
|
"image": "/games/game-inside/logo.png",
|
||||||
"slug": "game-inside"
|
"slug": "game-inside"
|
||||||
},
|
},
|
||||||
"google-snake": {
|
|
||||||
"name": "Google Snake",
|
|
||||||
"image": "/games/google-snake/logo.png",
|
|
||||||
"slug": "google-snake"
|
|
||||||
},
|
|
||||||
"grindcraft": {
|
"grindcraft": {
|
||||||
"name": "Grindcraft",
|
"name": "Grindcraft",
|
||||||
"image": "/games/grindcraft/logo.png",
|
"image": "/games/grindcraft/logo.png",
|
||||||
|
|
@ -184,11 +180,6 @@
|
||||||
"image": "/games/moto-x3m-winter/logo.png",
|
"image": "/games/moto-x3m-winter/logo.png",
|
||||||
"slug": "moto-x3m-winter"
|
"slug": "moto-x3m-winter"
|
||||||
},
|
},
|
||||||
"osu": {
|
|
||||||
"name": "osu!",
|
|
||||||
"image": "/games/osu/logo.png",
|
|
||||||
"slug": "osu"
|
|
||||||
},
|
|
||||||
"retro-bowl": {
|
"retro-bowl": {
|
||||||
"name": "Retro Bowl",
|
"name": "Retro Bowl",
|
||||||
"image": "/games/retro-bowl/logo.png",
|
"image": "/games/retro-bowl/logo.png",
|
||||||
|
|
|
||||||
69
src/pages/[lang]/game/[game].astro
Normal file
69
src/pages/[lang]/game/[game].astro
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
---
|
||||||
|
export const prerender = false;
|
||||||
|
import Layout from "src/layouts/Layout.astro";
|
||||||
|
import games from "../../../json/games.json";
|
||||||
|
|
||||||
|
let gamesList = games as GameList;
|
||||||
|
|
||||||
|
// get the current game based on the information in the url
|
||||||
|
let game = Astro.params.game;
|
||||||
|
if (!game) {
|
||||||
|
Astro.redirect("/en/games/");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidGameKey(key: string) {
|
||||||
|
return key in gamesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
let gameData = isValidGameKey(game) ? gamesList[game] : null;
|
||||||
|
|
||||||
|
if (!gameData) {
|
||||||
|
return Astro.redirect("/en/games/");
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title="Game">
|
||||||
|
<div id="main-content">
|
||||||
|
<h1 class="title-text">{gameData.name}</h1>
|
||||||
|
<p class="title-desc">{gameData?.description || "Play the game!"}</p>
|
||||||
|
<div class="game-container">
|
||||||
|
<iframe scrolling="no" src=`/games/${gameData.slug}` title={gameData.name} id="game-frame"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.game-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 4rem;
|
||||||
|
margin-bottom: 4rem;
|
||||||
|
}
|
||||||
|
#game-frame {
|
||||||
|
width: 80%;
|
||||||
|
height: 80vh;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let iframe = document.getElementById("game-frame") as HTMLIFrameElement;
|
||||||
|
iframe.contentWindow?.focus();
|
||||||
|
|
||||||
|
iframe?.addEventListener("load", () => {
|
||||||
|
iframe.contentWindow?.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
iframe.addEventListener("click", () => {
|
||||||
|
iframe.contentWindow?.focus();
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener("astro:after-swap", () => {
|
||||||
|
let iframe = document.getElementById("game-frame") as HTMLIFrameElement;
|
||||||
|
iframe?.contentWindow?.focus();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -3,9 +3,10 @@ import Layout from "../../layouts/Layout.astro";
|
||||||
import games from "../../json/games.json";
|
import games from "../../json/games.json";
|
||||||
import GameItem from "@components/GameItem.astro";
|
import GameItem from "@components/GameItem.astro";
|
||||||
|
|
||||||
import { STATIC_PATHS, i18n } from "@i18n/utils";
|
import { STATIC_PATHS, i18n, getLangFromUrl } from "@i18n/utils";
|
||||||
import Input from "@components/UI/Input.astro";
|
import Input from "@components/UI/Input.astro";
|
||||||
const t = i18n.inferLangUseTranslations(Astro.url);
|
const t = i18n.inferLangUseTranslations(Astro.url);
|
||||||
|
const lang = getLangFromUrl(Astro.url);
|
||||||
|
|
||||||
export function getStaticPaths() {
|
export function getStaticPaths() {
|
||||||
return STATIC_PATHS;
|
return STATIC_PATHS;
|
||||||
|
|
@ -22,7 +23,7 @@ export function getStaticPaths() {
|
||||||
{
|
{
|
||||||
Object.keys(games).map((key) => {
|
Object.keys(games).map((key) => {
|
||||||
const game = (games as any)[key];
|
const game = (games as any)[key];
|
||||||
return <GameItem name={game.name} image={game.image} slugName={game.slug} />;
|
return <GameItem lang={lang} name={game.name} image={game.image} slugName={game.slug} />;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,6 @@ import marketplace from "../../json/marketplace.json";
|
||||||
export const getStaticPaths = () => {
|
export const getStaticPaths = () => {
|
||||||
return STATIC_PATHS;
|
return STATIC_PATHS;
|
||||||
};
|
};
|
||||||
|
|
||||||
type MarketplaceItem = {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
version: string;
|
|
||||||
image: string;
|
|
||||||
script: string;
|
|
||||||
type: string;
|
|
||||||
entryNamespace?: string;
|
|
||||||
entryFunc?: string;
|
|
||||||
themeName?: string;
|
|
||||||
};
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Marketplace | Alu">
|
<Layout title="Marketplace | Alu">
|
||||||
|
|
@ -28,7 +16,7 @@ type MarketplaceItem = {
|
||||||
<div class="marketplace-ext-grid">
|
<div class="marketplace-ext-grid">
|
||||||
{
|
{
|
||||||
Object.keys(marketplace).map((mp_item) => {
|
Object.keys(marketplace).map((mp_item) => {
|
||||||
const item = (marketplace as { [key: string]: MarketplaceItem })[mp_item];
|
const item = (marketplace as { [key: string]: IExtensionMetadata })[mp_item];
|
||||||
const slug = mp_item;
|
const slug = mp_item;
|
||||||
return (
|
return (
|
||||||
<div class="marketplace-item" data-slug={slug}>
|
<div class="marketplace-item" data-slug={slug}>
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export function getStaticPaths() {
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<script>
|
<script>
|
||||||
import IDBManager, { type ExtensionMetadata } from "@components/ts/IDBManager";
|
import IDBManager from "@components/ts/IDBManager";
|
||||||
let load = IDBManager.loadIDB("AluDB", 1);
|
let load = IDBManager.loadIDB("AluDB", 1);
|
||||||
load.onsuccess = () => {
|
load.onsuccess = () => {
|
||||||
window.idb = IDBManager.GetIDB();
|
window.idb = IDBManager.GetIDB();
|
||||||
|
|
|
||||||
50
src/types.d.ts
vendored
Normal file
50
src/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
type ExtType = "serviceWorker" | "theme" | "page";
|
||||||
|
|
||||||
|
type Extension = {
|
||||||
|
name: string;
|
||||||
|
script: string;
|
||||||
|
type: ExtType;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
- title: The title of the extension
|
||||||
|
- description: A description of the extension
|
||||||
|
- version: The version of the extension (semver)
|
||||||
|
- script: The script URL to be downloaded and saved into scriptCopy
|
||||||
|
- entryNamespace: The namespace of the entry function for serviceWorker extensions
|
||||||
|
- entryFunc: The name of the entry function for serviceWorker extensions
|
||||||
|
- scriptCopy: The script contents of the extension btoa'd (null if not downloaded)
|
||||||
|
- type: The type of extension, see ExtType.
|
||||||
|
- themeName: The name of the theme that goes in the data attribute
|
||||||
|
*/
|
||||||
|
interface IExtensionMetadata {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
version: string;
|
||||||
|
image: string;
|
||||||
|
script: string;
|
||||||
|
entryNamespace?: string;
|
||||||
|
entryFunc?: string;
|
||||||
|
scriptCopy?: string;
|
||||||
|
type: ExtType;
|
||||||
|
themeName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtensionMetadataJSON = Record<string, IExtensionMetadata>;
|
||||||
|
|
||||||
|
type InstallReturn = {
|
||||||
|
code: EXT_RETURN;
|
||||||
|
slug: string;
|
||||||
|
title?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GameMetadata = {
|
||||||
|
name: string,
|
||||||
|
description?: string,
|
||||||
|
image: string,
|
||||||
|
slug: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
type GameList = {
|
||||||
|
[key: string]: GameMetadata;
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue