diff --git a/src/app.tsx b/src/app.tsx index c07db22ca..562e914fc 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -79,6 +79,7 @@ import TransformNoteView from "./views/tools/transform-note"; import SatelliteCDNView from "./views/tools/satellite-cdn"; import OtherStuffView from "./views/other-stuff"; import { RouteProviders } from "./providers/route"; +import LaunchpadView from "./views/launchpad"; const UserTracksTab = lazy(() => import("./views/user/tracks")); const ToolsHomeView = lazy(() => import("./views/tools")); @@ -200,6 +201,14 @@ const router = createHashRouter([ path: "map", element: , }, + { + path: "launchpad", + element: ( + + + + ), + }, { path: "/", element: , diff --git a/src/components/layout/index.tsx b/src/components/layout/index.tsx index b0ac07a6c..08b9dd484 100644 --- a/src/components/layout/index.tsx +++ b/src/components/layout/index.tsx @@ -1,6 +1,5 @@ -import React, { useEffect } from "react"; -import { Box, Container, Flex, Spacer, useDisclosure } from "@chakra-ui/react"; -import { useKeyPressEvent } from "react-use"; +import React from "react"; +import { Box, Container, Flex, Spacer } from "@chakra-ui/react"; import { ErrorBoundary } from "../error-boundary"; import { ReloadPrompt } from "../reload-prompt"; @@ -10,25 +9,10 @@ import useSubject from "../../hooks/use-subject"; import accountService from "../../services/account"; import GhostToolbar from "./ghost-toolbar"; import { useBreakpointValue } from "../../providers/global/breakpoint-provider"; -import SearchModal from "../search-modal"; -import { useLocation } from "react-router-dom"; export default function Layout({ children }: { children: React.ReactNode }) { const isMobile = useBreakpointValue({ base: true, md: false }); const isGhost = useSubject(accountService.isGhost); - const searchModal = useDisclosure(); - - useKeyPressEvent("k", (e) => { - if (e.ctrlKey) { - e.preventDefault(); - searchModal.onOpen(); - } - }); - - const location = useLocation(); - useEffect(() => { - searchModal.onClose(); - }, [location.pathname]); return ( <> @@ -64,7 +48,6 @@ export default function Layout({ children }: { children: React.ReactNode }) { {isGhost && } - {searchModal.isOpen && } ); } diff --git a/src/components/layout/nav-items.tsx b/src/components/layout/nav-items.tsx index 1ceb19082..1b2077e90 100644 --- a/src/components/layout/nav-items.tsx +++ b/src/components/layout/nav-items.tsx @@ -1,4 +1,5 @@ -import { Box, Button, ButtonProps, Link, Text, useDisclosure } from "@chakra-ui/react"; +import { useRef } from "react"; +import { Box, Button, ButtonProps, Code, Link, Text, useDisclosure } from "@chakra-ui/react"; import { Link as RouterLink, useLocation } from "react-router-dom"; import { nip19 } from "nostr-tools"; import dayjs from "dayjs"; @@ -19,10 +20,31 @@ import { } from "../icons"; import useCurrentAccount from "../../hooks/use-current-account"; import accountService from "../../services/account"; -import { useLocalStorage } from "react-use"; +import { useKeyPressEvent, useLocalStorage } from "react-use"; import ZapModal from "../event-zap-modal"; import PuzzlePiece01 from "../icons/puzzle-piece-01"; import Package from "../icons/package"; +import Rocket02 from "../icons/rocket-02"; +import { useBreakpointValue } from "../../providers/global/breakpoint-provider"; + +function KBD({ letter }: { letter: string }) { + const ref = useRef(null); + useKeyPressEvent( + (e) => e.ctrlKey && e.key === letter, + (e) => { + if (ref.current?.parentElement) { + e.preventDefault(); + ref.current.parentElement.click(); + } + }, + ); + + return ( + + ⌘{letter} + + ); +} export default function NavItems() { const location = useLocation(); @@ -31,6 +53,8 @@ export default function NavItems() { const donateModal = useDisclosure(); const [lastDonate, setLastDonate] = useLocalStorage("last-donate"); + const showShortcuts = useBreakpointValue({ base: false, md: true }); + const buttonProps: ButtonProps = { py: "2", justifyContent: "flex-start", @@ -39,6 +63,7 @@ export default function NavItems() { let active = "notes"; if (location.pathname.startsWith("/notifications")) active = "notifications"; + else if (location.pathname.startsWith("/launchpad")) active = "launchpad"; else if (location.pathname.startsWith("/dvm")) active = "dvm"; else if (location.pathname.startsWith("/dm")) active = "dm"; else if (location.pathname.startsWith("/streams")) active = "streams"; @@ -69,6 +94,16 @@ export default function NavItems() { return ( <> + )} @@ -117,6 +154,7 @@ export default function NavItems() { {...buttonProps} > Search + {showShortcuts && } {account?.pubkey && ( + + + + + ); +} + +export default function LaunchpadView() { + return ( + + + + + + + + ); +} diff --git a/src/views/other-stuff/index.tsx b/src/views/other-stuff/index.tsx index ce6a29682..f62212baa 100644 --- a/src/views/other-stuff/index.tsx +++ b/src/views/other-stuff/index.tsx @@ -6,6 +6,7 @@ import AppCard, { App } from "./component/app-card"; import useRouteSearchValue from "../../hooks/use-route-search-value"; import useRecentApps from "./use-recent-apps"; import { allApps, externalTools, internalTools } from "./apps"; +import { useBreakpointValue } from "../../providers/global/breakpoint-provider"; const tabs = ["all", "tools", "3rd-party-tools"]; @@ -13,6 +14,7 @@ export default function OtherStuffView() { const [search, setSearch] = useState(""); const tab = useRouteSearchValue("tab", "all"); const { recentApps, useApp } = useRecentApps(); + const autoFocusSearch = useBreakpointValue({ base: false, lg: true }); const sortByRecent = (a: App, b: App) => recentApps.indexOf(b.id) - recentApps.indexOf(a.id); const sortByName = (a: App, b: App) => { @@ -98,6 +100,7 @@ export default function OtherStuffView() { maxW="sm" value={search} onChange={(e) => setSearch(e.target.value)} + autoFocus={autoFocusSearch} /> {renderContent()} diff --git a/src/views/search/index.tsx b/src/views/search/index.tsx index a9ee77d21..08aee2d1d 100644 --- a/src/views/search/index.tsx +++ b/src/views/search/index.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useState } from "react"; import { Button, ButtonGroup, Flex, IconButton, Input, Link, useDisclosure } from "@chakra-ui/react"; -import { useSearchParams, useNavigate } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { SEARCH_RELAYS } from "../../const"; import { safeDecode } from "../../helpers/nip19"; @@ -19,11 +19,14 @@ import CommunitySearchResults from "./community-results"; import PeopleListProvider from "../../providers/local/people-list-provider"; import PeopleListSelection from "../../components/people-list-selection/people-list-selection"; import useRouteSearchValue from "../../hooks/use-route-search-value"; +import { useBreakpointValue } from "../../providers/global/breakpoint-provider"; export function SearchPage() { const navigate = useNavigate(); const qrScannerModal = useDisclosure(); + const autoFocusSearch = useBreakpointValue({ base: false, lg: true }); + const typeParam = useRouteSearchValue("type", "users"); const queryParam = useRouteSearchValue("q", ""); @@ -88,7 +91,12 @@ export function SearchPage() { {!!navigator.clipboard?.readText && ( } aria-label="Read clipboard" /> )} - setSearchInput(e.target.value)} /> + setSearchInput(e.target.value)} + autoFocus={autoFocusSearch} + />