ugly glue for sharable kiosk code

This commit is contained in:
Joey Wunderlich 2023-08-10 16:00:26 -07:00
Родитель adca5927dc
Коммит 0f85aeea5b
5 изменённых файлов: 149 добавлений и 16 удалений

Просмотреть файл

@ -14,8 +14,14 @@ const url = window.location.href;
const clean = !!/clean(?:[:=])1/.test(url);
const locked = !!/lock(?:[:=])1/i.test(url);
const time = (/time=((?:[0-9]{1,3}))/i.exec(url))?.[1];
const shareSrc = /shared[:=]([^#?]+)/i.exec(url)?.[1];
const kioskSingleton: Kiosk = new Kiosk(clean, locked, time);
const kioskSingleton: Kiosk = new Kiosk({
clean,
locked,
time,
shareSrc,
});
kioskSingleton.initialize().catch(error => alert(error));
function App() {

Просмотреть файл

@ -7,6 +7,7 @@ import HighScoresList from "./HighScoresList";
import { DeleteButton } from "./DeleteButton";
import { tickEvent } from "../browserUtils";
import DeletionModal from "./DeletionModal";
import { createKioskShareLink } from "../share";
interface IProps {
kiosk: Kiosk
@ -49,7 +50,7 @@ const MainMenu: React.FC<IProps> = ({ kiosk }) => {
updateLoop();
}
}, configData.GamepadPollLoopMilli);
return () => {
if (intervalId) {
clearInterval(intervalId);
@ -60,6 +61,17 @@ const MainMenu: React.FC<IProps> = ({ kiosk }) => {
}
});
const onUploadClick = async () => {
const sharePointer = await createKioskShareLink({
games: kiosk.games,
});
const outputLink = `https://arcade.makecode.com/kiosk?shared=${sharePointer}`;
alert(`send em to ${outputLink}`);
console.log(outputLink);
}
return(
<div className="mainMenu">
<nav className="mainMenuTopBar">
@ -70,6 +82,9 @@ const MainMenu: React.FC<IProps> = ({ kiosk }) => {
<AddGameButton selected={addButtonSelected} content="Add your game" />
</div>
}
{
!kiosk.locked && <button tabIndex={0} onClick={onUploadClick}>click me to upload</button>
}
</nav>
<GameList kiosk={kiosk} addButtonSelected={addButtonSelected}
deleteButtonSelected={deleteButtonSelected} />
@ -81,5 +96,5 @@ const MainMenu: React.FC<IProps> = ({ kiosk }) => {
</div>
)
}
export default MainMenu;

Просмотреть файл

@ -6,6 +6,15 @@ import { KioskState } from "./KioskState";
import configData from "../config.json";
import { getGameDetailsAsync } from "../BackendRequests"
import { tickEvent } from "../browserUtils";
import { getSharedKioskData } from "../share";
export interface KioskOpts {
clean: boolean;
locked: boolean;
time?: string;
shareSrc?: string;
}
export class Kiosk {
games: GameData[] = [];
gamepadManager: GamepadManager = new GamepadManager();
@ -19,6 +28,7 @@ export class Kiosk {
clean: boolean;
locked: boolean;
time?: string;
shareSrc?: string; // `{shareId}/{filename}`
private readonly highScoresLocalStorageKey: string = "HighScores";
private readonly addedGamesLocalStorageKey: string = "UserAddedGames";
@ -30,27 +40,36 @@ export class Kiosk {
private builtGamesCache: { [gameId: string]: BuiltSimJSInfo } = { };
private defaultGameDescription = "Made with love in MakeCode Arcade";
constructor(clean: boolean, locked: boolean, time?: string) {
constructor(opts: KioskOpts) {
const { clean, locked, time, shareSrc } = opts;
this.clean = clean;
this.locked = locked;
this.shareSrc = shareSrc;
// TODO figure out this interaction; right now treating loaded shareSrc as readonly to keep easy
this.locked = locked || !!shareSrc;
this.time = time;
}
async downloadGameListAsync(): Promise<void> {
if (this.shareSrc) {
const [id, ...filename] = this.shareSrc.split("/");
const kioskData = await getSharedKioskData(id, filename.join("/"));
this.games = kioskData.games;
return;
}
if (!this.clean) {
let url = configData.GameDataUrl;
if (configData.Debug) {
url = `/static/kiosk/${url}`;
}
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Unable to download game list from "${url}"`);
}
try {
this.games = (await response.json()).games;
this.games.push()
}
catch (error) {
throw new Error(`Unable to process game list downloaded from "${url}": ${error}`);
@ -68,7 +87,6 @@ export class Kiosk {
if (name.toLowerCase() === "untitled") {
return "Kiosk Game";
}
return name;
}
@ -76,7 +94,7 @@ export class Kiosk {
if (desc.length === 0) {
return this.defaultGameDescription
}
return desc;
}
@ -88,7 +106,7 @@ export class Kiosk {
if (!allAddedGames[gameId]) {
let gameName;
let gameDescription;
try {
const gameDetails = await getGameDetailsAsync(gameId);
gameName = this.getGameName(gameDetails.name);
@ -97,10 +115,10 @@ export class Kiosk {
gameName = "Kiosk Game";
gameDescription = this.defaultGameDescription;
}
const gameUploadDate = (new Date()).toLocaleString()
const newGame = new GameData(gameId, gameName, gameDescription, "None", games[gameId].id, gameUploadDate, true);
this.games.push(newGame);
gamesToAdd.push(gameName);
allAddedGames[gameId] = newGame;
@ -256,8 +274,8 @@ export class Kiosk {
const launchedGameHighs = this.getHighScores(this.launchedGame);
const currentHighScore = this.mostRecentScores[0];
const lastScore = launchedGameHighs[launchedGameHighs.length - 1]?.score;
if (launchedGameHighs.length === configData.HighScoresToKeep
&& lastScore
if (launchedGameHighs.length === configData.HighScoresToKeep
&& lastScore
&& currentHighScore < lastScore) {
this.exitGame(KioskState.GameOver);

Просмотреть файл

@ -21,4 +21,5 @@ export function devicePixelRatio(): number {
export function isLocal() {
return window.location.hostname === "localhost";
}
}

93
kiosk/src/share.ts Normal file
Просмотреть файл

@ -0,0 +1,93 @@
import { GameData } from "./Models/GameData";
const apiRoot = "https://www.makecode.com";
const description = "A kiosk for MakeCode Arcade";
export interface SharedKioskData {
games: GameData[];
}
export async function createKioskShareLink(kioskData: SharedKioskData) {
const payload = createShareRequest(
"kiosk",
createProjectFiles("kiosk", kioskData)
);
const url = apiRoot + "/api/scripts";
const result = await fetch(
url,
{
method: "POST",
body: new Blob([JSON.stringify(payload)], { type: "application/json" })
}
);
if (result.status === 200) {
const resJSON = await result.json();
// return "https://arcade.makecode.com/" + resJSON.shortid;
return `${resJSON.shortid}/kiosk.json`;
}
return "ERROR"
}
function createShareRequest(projectName: string, files: {[index: string]: string}) {
const header = {
"name": projectName,
"meta": {
},
"editor": "tsprj",
"pubId": undefined,
"pubCurrent": false,
"target": "arcade",
"id": crypto.randomUUID(),
"recentUse": Date.now(),
"modificationTime": Date.now(),
"path": projectName,
"saveId": {},
"githubCurrent": false,
"pubVersions": []
}
return {
id: header.id,
name: projectName,
target: header.target,
description: description,
editor: "tsprj",
header: JSON.stringify(header),
text: JSON.stringify(files),
meta: {
}
}
}
function createProjectFiles(projectName: string, kioskData: SharedKioskData) {
const files: {[index: string]: string} = {};
const config = {
"name": projectName,
"description": description,
"dependencies": {
"device": "*"
},
"files": [
"main.ts",
"kiosk.json"
],
"preferredEditor": "tsprj"
};
files["pxt.json"] = JSON.stringify(config, null, 4);
files["main.ts"] = " ";
files["kiosk.json"] = JSON.stringify(kioskData, undefined, 4);
return files;
}
export async function getSharedKioskData(shareId: string, filename: string): Promise<SharedKioskData> {
const resp = await fetch(`${apiRoot}/api/${shareId}/text`);
const proj: any = await resp.json();
const kioskData = JSON.parse(proj[filename]);
return kioskData;
}