diff --git a/web/src/App.tsx b/web/src/App.tsx index cf99c0b..e2686f7 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -21,6 +21,8 @@ import { AmbienceDebugOverlay } from "./components/AmbienceDebugOverlay"; import { AppHeader } from "./components/AppHeader"; import { BrowseView } from "./components/BrowseView"; import { HelpOverlay } from "./components/HelpOverlay"; +import { MobileBrowse } from "./components/mobile/MobileBrowse"; +import { MobileDock } from "./components/mobile/MobileDock"; import { ParentPanel } from "./components/ParentPanel"; import { PlayerBar } from "./components/PlayerBar"; import { PlayView } from "./components/PlayView"; @@ -28,6 +30,7 @@ import { RemoteAssignPopup } from "./components/RemoteAssignPopup"; import { TabRail } from "./components/TabRail"; import { useGridColumns } from "./hooks/useGridColumns"; import { useLibrary } from "./hooks/useLibrary"; +import { usePhoneLayout } from "./hooks/usePhoneLayout"; import { usePlayerState } from "./hooks/usePlayerState"; import { DEFAULT_MANUAL_CONTROL, DEFAULT_TUNABLES } from "./lib/ambienceTunables"; import type { AmbienceTunables, ManualControl } from "./lib/ambienceTunables"; @@ -35,7 +38,7 @@ import type { Action, UiState } from "./lib/keyboard"; import { browseBackActions, GROUP_ORDER, handleKey, initialUiState, isRootShelf, toggleModeActions } from "./lib/keyboard"; import { playPop } from "./lib/pop"; import { targetForAlbum } from "./lib/remote"; -import type { Group, Results, SongHit } from "./lib/search"; +import type { Group, Mode, Results, SongHit } from "./lib/search"; import { buildSearchIndex, categoryMatches, groupOf, results as computeResults } from "./lib/search"; import { SHOW_AMBIENCE, SHOW_CARD_BLUR, SHOW_GLASS_BLUR } from "./lib/theme"; @@ -64,6 +67,9 @@ export function App() { const library = useLibrary(); const connection = usePlayerState(library.reload); const [gridRef, columns] = useGridColumns(); + // A phone gets its own touch layout (components/mobile/); everything else - the + // kiosk, a desktop browser, a tablet - keeps the keyboard-first one. + const phone = usePhoneLayout(); const [parentMode, setParentMode] = useState( () => new URLSearchParams(location.search).get("parentMode") === "1", ); @@ -413,6 +419,7 @@ export function App() { }; const onPlaySong = useEvent((hit: SongHit) => play(hit.album.id, hit.index)); + const onMode = useEvent((mode: Mode) => setUi((previous) => ({ ...previous, mode, selIndex: 0 }))); const onNext = useCallback(() => run([{ type: "next" }]), [run]); const onPrevious = useCallback(() => run([{ type: "previous" }]), [run]); @@ -420,7 +427,9 @@ export function App() { // search, categories, play view - so it always means "one step out" no matter what // is on screen. Nothing to back out of on the room page (it has no sub-levels), and // the toggle button is what moves between the two pages. - const canGoBack = ui.page === "music" && (ui.view === "play" || !isRootShelf(ui)); + // The phone layout has its own back button in the browse screen's nav bar. + const canGoBack = + ui.page === "music" && (ui.view === "play" || (!phone && !isRootShelf(ui))); const onBack = () => { if (ui.view === "play") { setUi((previous) => ({ ...previous, view: "browse" })); @@ -430,6 +439,9 @@ export function App() { }; const onSelectPage = (page: UiState["page"]) => setUi((previous) => ({ ...previous, page: previous.page === page ? "music" : page })); + // Tabs, unlike the rail's toggles: tapping the open one again leaves it open. + const onSelectPhonePage = (page: UiState["page"]) => + setUi((previous) => ({ ...previous, page, search: "", searchOpen: false })); return (
)} - {ui.page === "music" && ui.view === "browse" && state && ( + {phone && ui.page === "music" && ui.view === "browse" && state && ( +
+
+