diff --git a/kiosk/src/App.tsx b/kiosk/src/App.tsx index d79b9ef9cd0a..c38bdcff9127 100644 --- a/kiosk/src/App.tsx +++ b/kiosk/src/App.tsx @@ -46,11 +46,11 @@ function App() { if (cfg) { dispatch(Actions.setTargetConfig(cfg)); if (cfg.kiosk) { - dispatch(Actions.setGameList(cfg.kiosk.games)); + if (!state.clean) { + dispatch(Actions.setGameList(cfg.kiosk.games)); + } // Load user-added games from local storage. dispatch(Actions.loadUserAddedGames()); - // Select the first game in the list. - dispatch(Actions.setSelectedGameId(cfg.kiosk.games[0].id)); } } else { // TODO: Handle this better diff --git a/kiosk/src/Components/GameList.tsx b/kiosk/src/Components/GameList.tsx index 44f322b8676a..1a459ee79145 100644 --- a/kiosk/src/Components/GameList.tsx +++ b/kiosk/src/Components/GameList.tsx @@ -23,6 +23,7 @@ const GameList: React.FC = ({}) => { const [userInitiatedTransition, setUserInitiatedTransition] = React.useState(false); const [pageInited, setPageInited] = React.useState(false); + const [generation, setGeneration] = React.useState(0); const handleLocalSwiper = (swiper: SwiperClass) => { setLocalSwiper(swiper); @@ -116,11 +117,12 @@ const GameList: React.FC = ({}) => { }; const slideChangeTransitionEnd = () => { + setGeneration(generation + 1); // This will force GameSlides to re-render if (userInitiatedTransition) { syncSelectedGame(); setUserInitiatedTransition(false); } - } + }; if (!kiosk.allGames?.length) { return ( @@ -134,7 +136,6 @@ const GameList: React.FC = ({}) => {
= ({}) => { onSwiper={handleLocalSwiper} allowTouchMove={true} modules={[Pagination]} - onSlideChangeTransitionStart={() => slideChangeTransitionStart()} + onSlideChangeTransitionStart={() => + slideChangeTransitionStart() + } onSlideChangeTransitionEnd={() => slideChangeTransitionEnd()} onTouchStart={() => setUserInitiatedTransition(true)} > {kiosk.allGames.map((game, index) => { return ( - + ); })} diff --git a/kiosk/src/Components/GameSlide.tsx b/kiosk/src/Components/GameSlide.tsx index 93236c8f140c..cbb589f3a594 100644 --- a/kiosk/src/Components/GameSlide.tsx +++ b/kiosk/src/Components/GameSlide.tsx @@ -1,4 +1,4 @@ -import { useContext, useMemo } from "react"; +import { useContext, useMemo, useState } from "react"; import { playSoundEffect } from "../Services/SoundEffectService"; import { launchGame } from "../Transforms/launchGame"; import { GameData } from "../Types"; @@ -10,9 +10,35 @@ import { getEffectiveGameId } from "../Utils"; interface IProps { game: GameData; + generation: number; // Enables GameList to force a re-render of GameSlide when carousel transitions } -const GameSlide: React.FC = ({ game }) => { +const GameSlide: React.FC = ({ game, generation }) => { const { state: kiosk } = useContext(AppStateContext); + const [myParentRef, setMyParentRef] = useState< + HTMLElement | null | undefined + >(null); + + const handleRef = (el: HTMLElement | null) => { + setMyParentRef(el?.parentElement); + }; + + const isSelected = useMemo(() => { + let selected = kiosk.selectedGameId === game.id; + if (myParentRef) { + selected = + selected && + myParentRef.classList.contains("swiper-slide-active"); + } + return selected; + }, [ + kiosk.selectedGameId, + game.id, + myParentRef, + // `generation` is included here to force re-render when carousel + // transitions. This is a workaround for Swiper's direct DOM + // manipulation .. React doesn't always know about it. + generation, + ]); const handleSlideClick = () => { pxt.tickEvent("kiosk.gameLaunched", { game: game.id }); @@ -28,7 +54,7 @@ const GameSlide: React.FC = ({ game }) => { }, [game, game?.tempGameId]); return ( -
+
= ({ game }) => { )}
- {kiosk.selectedGameId && game.id === kiosk.selectedGameId && ( + {isSelected && (
{lf("Press A to Start")}
)} - {kiosk.selectedGameId && game.id === kiosk.selectedGameId && ( - - )} + {isSelected && }
); }; diff --git a/kiosk/src/Kiosk.css b/kiosk/src/Kiosk.css index d740bcfbe002..4ddb97a02ef7 100644 --- a/kiosk/src/Kiosk.css +++ b/kiosk/src/Kiosk.css @@ -552,19 +552,26 @@ h2 { .swiper-slide { position: relative; - width: var(--swiper-width); transform: scale(0.75); top: -10%; } -.swiper-slide-active { - cursor: pointer; - transform: scale(1) !important; - top: 0; +.swiper-slide.swiper-slide-next { + position: relative; + transform: scale(0.75) !important; + top: -10%; +} + +.swiper-slide.swiper-slide-prev { + position: relative; + transform: scale(0.75) !important; + top: -10%; } -.swiper-slide-active .deleted { - animation: 1s shrink; +.swiper-slide.swiper-slide-active { + cursor: pointer; + transform: scale(1); + top: 0; } .gameTile { diff --git a/kiosk/src/State/Reducer.ts b/kiosk/src/State/Reducer.ts index 65c0922a2caf..756f5aeb463f 100644 --- a/kiosk/src/State/Reducer.ts +++ b/kiosk/src/State/Reducer.ts @@ -93,7 +93,7 @@ export default function reducer(state: AppState, action: Action): AppState { if (state.selectedGameId === action.gameId) { // Get the index of the now-deleted game in the original list. const selectedGameIndex = state.allGames.findIndex(g => g.id === action.gameId); - if (selectedGameIndex >= 0) { + if (selectedGameIndex >= 0 && remainingGames.length) { if (selectedGameIndex > remainingGames.length - 1) { // The index of the deleted game is beyond the bounds of the updated list. Select the last game in the updated list. selectedGameId = remainingGames[remainingGames.length - 1].id; diff --git a/kiosk/src/State/State.ts b/kiosk/src/State/State.ts index 03443ad8fc5a..58c6bdada560 100644 --- a/kiosk/src/State/State.ts +++ b/kiosk/src/State/State.ts @@ -18,10 +18,10 @@ export type AppState = { kioskCodeExpiration?: number; notifications: Notifications; modal?: ModalConfig; - clean: boolean; - locked: boolean; - time?: string; - volume?: number; + clean: boolean; // if true, don't load built-in games + locked: boolean; // if true, hide the add games button + time?: string; // lifetime of kiosk code, in minutes + volume?: number; // volume level of kiosk UI sounds, in [0..1] targetConfig?: pxt.TargetConfig; };