diff --git a/kiosk/src/App.tsx b/kiosk/src/App.tsx index b25e4dc91617..d23f7086668b 100644 --- a/kiosk/src/App.tsx +++ b/kiosk/src/App.tsx @@ -55,7 +55,7 @@ function App() { // TODO: Handle this better dispatch(Actions.setTargetConfig({})); } - }); + }); // Init subsystems. SimHost.initialize(); NotificationService.initialize(); diff --git a/kiosk/src/Components/PlayingGame.tsx b/kiosk/src/Components/PlayingGame.tsx index d34a2c00910b..9472d976bb37 100644 --- a/kiosk/src/Components/PlayingGame.tsx +++ b/kiosk/src/Components/PlayingGame.tsx @@ -3,7 +3,9 @@ import { AppStateContext } from "../State/AppStateContext"; import { escapeGame } from "../Transforms/escapeGame"; import { playSoundEffect } from "../Services/SoundEffectService"; import { useOnControlPress } from "../Hooks"; +import { stringifyQueryString } from "../Utils"; import * as GamepadManager from "../Services/GamepadManager"; +import * as Storage from "../Services/LocalStorage"; import configData from "../config.json"; export default function PlayingGame() { @@ -23,11 +25,23 @@ export default function PlayingGame() { const playUrl = useMemo(() => { if (gameId) { - const playUrlBase = `${configData.PlayUrlRoot}?id=${gameId}&hideSimButtons=1&noFooter=1&single=1&fullscreen=1&autofocus=1`; - const playQueryParam = kiosk.builtGamesCache[gameId] - ? "&server=1" - : "&sendBuilt=1"; - return playUrlBase + playQueryParam; + const builtGame = Storage.getBuiltJsInfo(gameId); + return stringifyQueryString(configData.PlayUrlRoot, { + id: gameId, + // TODO: Show sim buttons on mobile & touch devices. + hideSimButtons: 1, + noFooter: 1, + single: 1, + fullscreen: 1, + autofocus: 1, + // If we have the built game cached, we will send it to the + // simulator once it loads. The `server` flag inhibits the + // simulator from trying to build it. + server: builtGame ? 1 : undefined, + // If we don't have the built game cached, tell the simulator to + // send it to us once it's built and we'll cache it. + sendBuilt: builtGame ? undefined : 1, + }); } }, [gameId]); diff --git a/kiosk/src/Services/LocalStorage.ts b/kiosk/src/Services/LocalStorage.ts index 1f263720fb91..02a5bd589675 100644 --- a/kiosk/src/Services/LocalStorage.ts +++ b/kiosk/src/Services/LocalStorage.ts @@ -14,6 +14,17 @@ function delValue(key: string) { localStorage.removeItem(key); } +function matchingKeys(pattern: RegExp): string[] { + const keys: string[] = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && pattern.test(key)) { + keys.push(key); + } + } + return keys; +} + function getJsonValue(key: string, defaultValue?: T): T | undefined { var value = getValue(key); if (value) { @@ -109,10 +120,29 @@ function clearKioskCode() { delValue(Constants.legacy_kioskCodeExpirationStorageKey); } +function getBuiltJsInfo(gameId: string): ts.pxtc.BuiltSimJsInfo | undefined { + const ver = pxt.appTarget?.versions?.target; + if (!ver) return undefined; + const key = `builtjs:${ver}:${gameId}`; + const rec = getJsonValue(key); + return rec; +} + +function setBuiltJsInfo(gameId: string, builtJs: ts.pxtc.BuiltSimJsInfo) { + const ver = pxt.appTarget?.versions?.target; + if (!ver) return; + const key = `builtjs:${ver}:${gameId}`; + setJsonValue(key, builtJs); +} + +function clearBuiltJsInfo() { + const keys = matchingKeys(/^builtjs:/); + for (const key of keys) { + delValue(key); + } +} + export { - getValue, - setValue, - delValue, getJsonValue, setJsonValue, getUserAddedGames, @@ -123,4 +153,7 @@ export { setKioskCode, getKioskCode, clearKioskCode, + getBuiltJsInfo, + setBuiltJsInfo, + clearBuiltJsInfo, }; diff --git a/kiosk/src/Services/SimHostService.ts b/kiosk/src/Services/SimHostService.ts index dbbdd447cb5a..29c3b1a1963a 100644 --- a/kiosk/src/Services/SimHostService.ts +++ b/kiosk/src/Services/SimHostService.ts @@ -1,5 +1,4 @@ -import { stateAndDispatch, getBuiltGame } from "../State"; -import { BuiltSimJSInfo } from "../Types"; +import { stateAndDispatch } from "../State"; import * as Actions from "../State/Actions"; import * as Constants from "../Constants"; import { gameOver } from "../Transforms/gameOver"; @@ -7,6 +6,7 @@ import { resetHighScores } from "../Transforms/resetHighScores"; import * as GamepadManager from "./GamepadManager"; import { postNotification } from "../Transforms/postNotification"; import { makeNotification } from "../Utils"; +import * as Storage from "./LocalStorage"; export function initialize() { let controlStates: GamepadManager.ControlStates = { @@ -64,8 +64,7 @@ export function initialize() { GamepadManager.addKeyupListener(keyuphandler); function sendBuiltGame(gameId: string) { - const { state } = stateAndDispatch(); - const builtGame = state.builtGamesCache[gameId]; + const builtGame = Storage.getBuiltJsInfo(gameId); if (builtGame) { const simIframe = document.getElementsByTagName( "iframe" @@ -80,15 +79,7 @@ export function initialize() { window.addEventListener("message", event => { const { state, dispatch } = stateAndDispatch(); if (event.data?.js && state.launchedGameId) { - const builtGame: BuiltSimJSInfo = - state.builtGamesCache?.[state.launchedGameId]; - if (!builtGame) { - dispatch( - Actions.addBuiltGame(state.launchedGameId, event.data) - ); - } else { - sendBuiltGame(state.launchedGameId); - } + Storage.setBuiltJsInfo(state.launchedGameId, event.data); } switch (event.data.type) { case "simulator": @@ -120,7 +111,7 @@ export function initialize() { break; case "ready": - const builtGame = getBuiltGame(state.launchedGameId); + const builtGame = Storage.getBuiltJsInfo(state.launchedGameId!); if (builtGame) { sendBuiltGame(state.launchedGameId!); } diff --git a/kiosk/src/State/Actions.ts b/kiosk/src/State/Actions.ts index 12506d9666de..97370d0f91ec 100644 --- a/kiosk/src/State/Actions.ts +++ b/kiosk/src/State/Actions.ts @@ -1,8 +1,6 @@ import { GameData, - BuiltSimJSInfo, KioskState, - HighScores, AllHighScores, NotificationWithId, HighScoreWithId, @@ -33,12 +31,6 @@ type SetKioskState = ActionBase & { state: KioskState; }; -type AddBuiltGame = ActionBase & { - type: "ADD_BUILT_GAME"; - gameId: string; - info: BuiltSimJSInfo; -}; - type SetLaunchedGame = ActionBase & { type: "SET_LAUNCHED_GAME"; gameId: string; @@ -139,7 +131,6 @@ export type Action = | SetGameList | SetSelectedGameId | SetKioskState - | AddBuiltGame | SetLaunchedGame | SetLockedGame | SetAllHighScores @@ -179,12 +170,6 @@ const setKioskState = (state: KioskState): SetKioskState => ({ state, }); -const addBuiltGame = (gameId: string, info: BuiltSimJSInfo): AddBuiltGame => ({ - type: "ADD_BUILT_GAME", - gameId, - info, -}); - const setLaunchedGame = (gameId: string): SetLaunchedGame => ({ type: "SET_LAUNCHED_GAME", gameId, @@ -288,7 +273,6 @@ export { setGameList, setSelectedGameId, setKioskState, - addBuiltGame, setLaunchedGame, setLockedGame, setAllHighScores, diff --git a/kiosk/src/State/Reducer.ts b/kiosk/src/State/Reducer.ts index d95e2f3e04e2..4129ecc47459 100644 --- a/kiosk/src/State/Reducer.ts +++ b/kiosk/src/State/Reducer.ts @@ -28,15 +28,6 @@ export default function reducer(state: AppState, action: Action): AppState { kioskState: action.state, }; } - case "ADD_BUILT_GAME": { - return { - ...state, - builtGamesCache: { - ...state.builtGamesCache, - [action.gameId]: action.info, - }, - }; - } case "SET_LAUNCHED_GAME": { return { ...state, diff --git a/kiosk/src/State/State.ts b/kiosk/src/State/State.ts index e6668ef17cf4..03443ad8fc5a 100644 --- a/kiosk/src/State/State.ts +++ b/kiosk/src/State/State.ts @@ -1,6 +1,5 @@ import { GameData, - BuiltSimJSInfo, KioskState, AllHighScores, Notifications, @@ -14,7 +13,6 @@ export type AppState = { selectedGameId?: string; launchedGameId?: string; lockedGameId?: string; - builtGamesCache: { [gameId: string]: BuiltSimJSInfo }; allHighScores: AllHighScores; kioskCode?: string; kioskCodeExpiration?: number; @@ -31,7 +29,6 @@ export const initialAppState: AppState = { kioskState: KioskState.MainMenu, allGames: [], mostRecentScores: [], - builtGamesCache: {}, allHighScores: {}, notifications: [], clean: false, diff --git a/kiosk/src/State/index.ts b/kiosk/src/State/index.ts index aba44d840931..2f25d4250f90 100644 --- a/kiosk/src/State/index.ts +++ b/kiosk/src/State/index.ts @@ -1,4 +1,4 @@ -import { HighScore, BuiltSimJSInfo } from "../Types"; +import { HighScore } from "../Types"; import { stateAndDispatch } from "./AppStateContext"; function getHighScores(gameId: string | undefined): HighScore[] { @@ -22,18 +22,9 @@ function getSelectedGameId(): string | undefined { return state.selectedGameId; } -function getBuiltGame(gameId: string | undefined): BuiltSimJSInfo | undefined { - const { state } = stateAndDispatch(); - if (!gameId) { - return undefined; - } - return state.builtGamesCache[gameId]; -} - export { stateAndDispatch, getHighScores, getSelectedGameIndex, getSelectedGameId, - getBuiltGame, }; diff --git a/kiosk/src/Transforms/addBuiltGame.ts b/kiosk/src/Transforms/addBuiltGame.ts deleted file mode 100644 index fbd46026d3c5..000000000000 --- a/kiosk/src/Transforms/addBuiltGame.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { stateAndDispatch } from "../State"; -import { BuiltSimJSInfo } from "../Types"; -import * as Actions from "../State/Actions"; - -export function addBuiltGame(gameId: string, builtSimJs: BuiltSimJSInfo) { - const { dispatch } = stateAndDispatch(); - dispatch(Actions.addBuiltGame(gameId, builtSimJs)); -} diff --git a/kiosk/src/Types/index.ts b/kiosk/src/Types/index.ts index 8825640287ba..ebd265ac936c 100644 --- a/kiosk/src/Types/index.ts +++ b/kiosk/src/Types/index.ts @@ -18,14 +18,6 @@ export type HighScoreWithId = HighScore & { id: string; }; -export type BuiltSimJSInfo = { - js: string; - targetVersion?: string; - funArgs?: string[]; - parts?: string[]; - usedBuiltinParts?: string[]; -}; - export enum KioskState { MainMenu = "MainMenu", PlayingGame = "PlayingGame", diff --git a/kiosk/src/Utils/index.ts b/kiosk/src/Utils/index.ts index d7fd43bed959..f554b1a06679 100644 --- a/kiosk/src/Utils/index.ts +++ b/kiosk/src/Utils/index.ts @@ -70,3 +70,18 @@ export function nodeListToArray(list: NodeListOf): U[] { } return out; } + +// Copied from pxt.Utils, modified to skip undefined values. +export function stringifyQueryString(url: string, qs: any) { + for (let k of Object.keys(qs)) { + if (qs[k] != null) { + if (url.indexOf("?") >= 0) { + url += "&"; + } else { + url += "?"; + } + url += encodeURIComponent(k) + "=" + encodeURIComponent(qs[k]); + } + } + return url; +}