ugly glue for sharable kiosk code
This commit is contained in:
Родитель
adca5927dc
Коммит
0f85aeea5b
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
Загрузка…
Ссылка в новой задаче