diff options
| author | adamelmore <[email protected]> | 2026-01-26 22:58:29 -0600 |
|---|---|---|
| committer | adamelmore <[email protected]> | 2026-01-27 06:29:18 -0600 |
| commit | 58b9b54600f12ba4ec1d80a2d1b7dee3879a0479 (patch) | |
| tree | 797755369dc2d81ff04c32f0d0b4780e2db84d03 | |
| parent | c0a5f853497de6778f1430b691e393caa54c59a7 (diff) | |
| download | opencode-58b9b54600f12ba4ec1d80a2d1b7dee3879a0479.tar.gz opencode-58b9b54600f12ba4ec1d80a2d1b7dee3879a0479.zip | |
feat(app): forward and back buttons
| -rw-r--r-- | packages/app/src/components/titlebar.tsx | 135 | ||||
| -rw-r--r-- | packages/app/src/context/platform.tsx | 6 | ||||
| -rw-r--r-- | packages/app/src/entry.tsx | 6 | ||||
| -rw-r--r-- | packages/app/src/i18n/en.ts | 1 | ||||
| -rw-r--r-- | packages/desktop/src/index.tsx | 8 | ||||
| -rw-r--r-- | packages/ui/src/components/icon.tsx | 1 |
6 files changed, 130 insertions, 27 deletions
diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index 159216a4f..1c8256069 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -1,8 +1,10 @@ -import { createEffect, createMemo, Show } from "solid-js" +import { createEffect, createMemo, Show, untrack } from "solid-js" +import { createStore } from "solid-js/store" +import { useLocation, useNavigate } from "@solidjs/router" import { IconButton } from "@opencode-ai/ui/icon-button" import { Icon } from "@opencode-ai/ui/icon" import { Button } from "@opencode-ai/ui/button" -import { TooltipKeybind } from "@opencode-ai/ui/tooltip" +import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" import { useTheme } from "@opencode-ai/ui/theme" import { useLayout } from "@/context/layout" @@ -16,11 +18,68 @@ export function Titlebar() { const command = useCommand() const language = useLanguage() const theme = useTheme() + const navigate = useNavigate() + const location = useLocation() const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos") const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows") const web = createMemo(() => platform.platform === "web") + const [history, setHistory] = createStore({ + stack: [] as string[], + index: 0, + action: undefined as "back" | "forward" | undefined, + }) + + const path = () => `${location.pathname}${location.search}${location.hash}` + + createEffect(() => { + const current = path() + + untrack(() => { + if (!history.stack.length) { + const stack = current === "/" ? ["/"] : ["/", current] + setHistory({ stack, index: stack.length - 1 }) + return + } + + const active = history.stack[history.index] + if (current === active) { + if (history.action) setHistory("action", undefined) + return + } + + if (history.action) { + setHistory("action", undefined) + return + } + + const next = history.stack.slice(0, history.index + 1).concat(current) + setHistory({ stack: next, index: next.length - 1 }) + }) + }) + + const canBack = createMemo(() => history.index > 0) + const canForward = createMemo(() => history.index < history.stack.length - 1) + + const back = () => { + if (!canBack()) return + const index = history.index - 1 + const to = history.stack[index] + if (!to) return + setHistory({ index, action: "back" }) + navigate(to) + } + + const forward = () => { + if (!canForward()) return + const index = history.index + 1 + const to = history.stack[index] + if (!to) return + setHistory({ index, action: "forward" }) + navigate(to) + } + const getWin = () => { if (platform.platform !== "desktop") return @@ -106,34 +165,56 @@ export function Titlebar() { /> </div> </Show> - <TooltipKeybind - class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"} - placement="bottom" - title={language.t("command.sidebar.toggle")} - keybind={command.keybind("sidebar.toggle")} - > - <Button - variant="ghost" - class="group/sidebar-toggle size-6 p-0" - onClick={layout.sidebar.toggle} - aria-label={language.t("command.sidebar.toggle")} - aria-expanded={layout.sidebar.opened()} + <div class="flex items-center gap-1 shrink-0"> + <TooltipKeybind + class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"} + placement="bottom" + title={language.t("command.sidebar.toggle")} + keybind={command.keybind("sidebar.toggle")} > - <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0"> - <Icon - size="small" - name={layout.sidebar.opened() ? "layout-left-full" : "layout-left"} - class="group-hover/sidebar-toggle:hidden" + <Button + variant="ghost" + class="group/sidebar-toggle size-6 p-0" + onClick={layout.sidebar.toggle} + aria-label={language.t("command.sidebar.toggle")} + aria-expanded={layout.sidebar.opened()} + > + <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0"> + <Icon + size="small" + name={layout.sidebar.opened() ? "layout-left-full" : "layout-left"} + class="group-hover/sidebar-toggle:hidden" + /> + <Icon size="small" name="layout-left-partial" class="hidden group-hover/sidebar-toggle:inline-block" /> + <Icon + size="small" + name={layout.sidebar.opened() ? "layout-left" : "layout-left-full"} + class="hidden group-active/sidebar-toggle:inline-block" + /> + </div> + </Button> + </TooltipKeybind> + <div class="hidden xl:flex items-center gap-1 shrink-0"> + <Tooltip placement="bottom" value={language.t("common.goBack")}> + <IconButton + icon="arrow-left" + variant="ghost" + disabled={!canBack()} + onClick={back} + aria-label={language.t("common.goBack")} /> - <Icon size="small" name="layout-left-partial" class="hidden group-hover/sidebar-toggle:inline-block" /> - <Icon - size="small" - name={layout.sidebar.opened() ? "layout-left" : "layout-left-full"} - class="hidden group-active/sidebar-toggle:inline-block" + </Tooltip> + <Tooltip placement="bottom" value={language.t("common.goForward")}> + <IconButton + icon="arrow-right" + variant="ghost" + disabled={!canForward()} + onClick={forward} + aria-label={language.t("common.goForward")} /> - </div> - </Button> - </TooltipKeybind> + </Tooltip> + </div> + </div> <div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" data-tauri-drag-region /> <div class="flex-1 h-full" data-tauri-drag-region /> <div diff --git a/packages/app/src/context/platform.tsx b/packages/app/src/context/platform.tsx index b97f70fea..f6fb157f0 100644 --- a/packages/app/src/context/platform.tsx +++ b/packages/app/src/context/platform.tsx @@ -17,6 +17,12 @@ export type Platform = { /** Restart the app */ restart(): Promise<void> + /** Navigate back in history */ + back(): void + + /** Navigate forward in history */ + forward(): void + /** Send a system notification (optional deep link) */ notify(title: string, description?: string, href?: string): Promise<void> diff --git a/packages/app/src/entry.tsx b/packages/app/src/entry.tsx index 7fe03bb6a..aa52fa1e7 100644 --- a/packages/app/src/entry.tsx +++ b/packages/app/src/entry.tsx @@ -31,6 +31,12 @@ const platform: Platform = { openLink(url: string) { window.open(url, "_blank") }, + back() { + window.history.back() + }, + forward() { + window.history.forward() + }, restart: async () => { window.location.reload() }, diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index bca08275f..abbe497dc 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -167,6 +167,7 @@ export const dict = { "common.search.placeholder": "Search", "common.goBack": "Go back", + "common.goForward": "Go forward", "common.loading": "Loading", "common.loading.ellipsis": "...", "common.cancel": "Cancel", diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx index fe9e3f92e..b19adfeda 100644 --- a/packages/desktop/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -80,6 +80,14 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({ void shellOpen(url).catch(() => undefined) }, + back() { + window.history.back() + }, + + forward() { + window.history.forward() + }, + storage: (() => { type StoreLike = { get(key: string): Promise<string | null | undefined> diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index ddedddb36..544c6abdd 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -4,6 +4,7 @@ const icons = { "align-right": `<path d="M12.292 6.04167L16.2503 9.99998L12.292 13.9583M2.91699 9.99998H15.6253M17.0837 3.75V16.25" stroke="currentColor" stroke-linecap="square"/>`, "arrow-up": `<path fill-rule="evenodd" clip-rule="evenodd" d="M9.99991 2.24121L16.0921 8.33343L15.2083 9.21731L10.6249 4.63397V17.5001H9.37492V4.63398L4.7916 9.21731L3.90771 8.33343L9.99991 2.24121Z" fill="currentColor"/>`, "arrow-left": `<path d="M8.33464 4.58398L2.91797 10.0007L8.33464 15.4173M3.33464 10.0007H17.0846" stroke="currentColor" stroke-linecap="square"/>`, + "arrow-right": `<path d="M11.6654 4.58398L17.082 10.0007L11.6654 15.4173M16.6654 10.0007H2.91536" stroke="currentColor" stroke-linecap="square"/>`, archive: `<path d="M16.8747 6.24935H17.3747V5.74935H16.8747V6.24935ZM16.8747 16.8743V17.3743H17.3747V16.8743H16.8747ZM3.12467 16.8743H2.62467V17.3743H3.12467V16.8743ZM3.12467 6.24935V5.74935H2.62467V6.24935H3.12467ZM2.08301 2.91602V2.41602H1.58301V2.91602H2.08301ZM17.9163 2.91602H18.4163V2.41602H17.9163V2.91602ZM17.9163 6.24935V6.74935H18.4163V6.24935H17.9163ZM2.08301 6.24935H1.58301V6.74935H2.08301V6.24935ZM8.33301 9.08268H7.83301V10.0827H8.33301V9.58268V9.08268ZM11.6663 10.0827H12.1663V9.08268H11.6663V9.58268V10.0827ZM16.8747 6.24935H16.3747V16.8743H16.8747H17.3747V6.24935H16.8747ZM16.8747 16.8743V16.3743H3.12467V16.8743V17.3743H16.8747V16.8743ZM3.12467 16.8743H3.62467V6.24935H3.12467H2.62467V16.8743H3.12467ZM3.12467 6.24935V6.74935H16.8747V6.24935V5.74935H3.12467V6.24935ZM2.08301 2.91602V3.41602H17.9163V2.91602V2.41602H2.08301V2.91602ZM17.9163 2.91602H17.4163V6.24935H17.9163H18.4163V2.91602H17.9163ZM17.9163 6.24935V5.74935H2.08301V6.24935V6.74935H17.9163V6.24935ZM2.08301 6.24935H2.58301V2.91602H2.08301H1.58301V6.24935H2.08301ZM8.33301 9.58268V10.0827H11.6663V9.58268V9.08268H8.33301V9.58268Z" fill="currentColor"/>`, "bubble-5": `<path d="M18.3327 9.99935C18.3327 5.57227 15.0919 2.91602 9.99935 2.91602C4.90676 2.91602 1.66602 5.57227 1.66602 9.99935C1.66602 11.1487 2.45505 13.1006 2.57637 13.3939C2.58707 13.4197 2.59766 13.4434 2.60729 13.4697C2.69121 13.6987 3.04209 14.9354 1.66602 16.7674C3.51787 17.6528 5.48453 16.1973 5.48453 16.1973C6.84518 16.9193 8.46417 17.0827 9.99935 17.0827C15.0919 17.0827 18.3327 14.4264 18.3327 9.99935Z" stroke="currentColor" stroke-linecap="square"/>`, brain: `<path d="M13.332 8.7487C11.4911 8.7487 9.9987 7.25631 9.9987 5.41536M6.66536 11.2487C8.50631 11.2487 9.9987 12.7411 9.9987 14.582M9.9987 2.78209L9.9987 17.0658M16.004 15.0475C17.1255 14.5876 17.9154 13.4849 17.9154 12.1978C17.9154 11.3363 17.5615 10.5575 16.9913 9.9987C17.5615 9.43991 17.9154 8.66108 17.9154 7.79962C17.9154 6.21199 16.7136 4.90504 15.1702 4.73878C14.7858 3.21216 13.4039 2.08203 11.758 2.08203C11.1171 2.08203 10.5162 2.25337 9.9987 2.55275C9.48117 2.25337 8.88032 2.08203 8.23944 2.08203C6.59353 2.08203 5.21157 3.21216 4.82722 4.73878C3.28377 4.90504 2.08203 6.21199 2.08203 7.79962C2.08203 8.66108 2.43585 9.43991 3.00609 9.9987C2.43585 10.5575 2.08203 11.3363 2.08203 12.1978C2.08203 13.4849 2.87191 14.5876 3.99339 15.0475C4.46688 16.7033 5.9917 17.9154 7.79962 17.9154C8.61335 17.9154 9.36972 17.6698 9.9987 17.2488C10.6277 17.6698 11.384 17.9154 12.1978 17.9154C14.0057 17.9154 15.5305 16.7033 16.004 15.0475Z" stroke="currentColor"/>`, |
