wip: polish to shortcuts
This commit is contained in:
parent
0f398e612f
commit
3ff6ce5967
6 changed files with 64 additions and 22 deletions
|
|
@ -44,7 +44,7 @@ import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
||||||
import { ModelSelectorPopover } from "@/components/dialog-select-model"
|
import { ModelSelectorPopover } from "@/components/dialog-select-model"
|
||||||
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
|
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
|
||||||
import { useProviders } from "@/hooks/use-providers"
|
import { useProviders } from "@/hooks/use-providers"
|
||||||
import { useCommand } from "@/context/command"
|
import { formatKeybind, useCommand } from "@/context/command"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
import { Identifier } from "@/utils/id"
|
import { Identifier } from "@/utils/id"
|
||||||
import { SessionContextUsage } from "@/components/session-context-usage"
|
import { SessionContextUsage } from "@/components/session-context-usage"
|
||||||
|
|
@ -1400,8 +1400,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
custom
|
custom
|
||||||
</span>
|
</span>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={command.keybind(cmd.id)}>
|
<Show when={cmd.keybind}>
|
||||||
<span class="text-12-regular text-text-subtle">{command.keybind(cmd.id)}</span>
|
<span class="text-12-regular text-text-subtle">{formatKeybind(cmd.keybind!)}</span>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { parseKeybind } from "@/context/command"
|
import { parseKeybind, formatKeybind } from "@/context/command"
|
||||||
|
|
||||||
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
|
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
|
||||||
|
|
||||||
|
|
@ -163,10 +163,14 @@ function ShortcutItem(props: { shortcut: Shortcut }) {
|
||||||
<For each={getKeyChars(props.shortcut.keybind)}>
|
<For each={getKeyChars(props.shortcut.keybind)}>
|
||||||
{(char) => {
|
{(char) => {
|
||||||
const tooltip = SPECIAL_CHAR_NAMES[char]
|
const tooltip = SPECIAL_CHAR_NAMES[char]
|
||||||
|
const isSpecial = tooltip && !isLetter(char)
|
||||||
|
const isShift = char === "⇧"
|
||||||
return (
|
return (
|
||||||
<Show when={tooltip && !isLetter(char)} fallback={<kbd class="shortcut-key">{char}</kbd>}>
|
<Show when={isSpecial} fallback={<kbd class="shortcut-key">{char}</kbd>}>
|
||||||
<Tooltip value={tooltip} placement="top">
|
<Tooltip value={tooltip} placement="top">
|
||||||
<kbd class="shortcut-key">{char}</kbd>
|
<kbd class="shortcut-key shortcut-key-special">
|
||||||
|
<span classList={{ "shortcut-key-shift": isShift }}>{char}</span>
|
||||||
|
</kbd>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
|
|
@ -197,7 +201,16 @@ export function ShortcutsPanel(props: { onClose: () => void }) {
|
||||||
{(category) => <Tabs.Trigger value={category.name}>{category.name}</Tabs.Trigger>}
|
{(category) => <Tabs.Trigger value={category.name}>{category.name}</Tabs.Trigger>}
|
||||||
</For>
|
</For>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<IconButton icon="close" variant="ghost" onClick={props.onClose} />
|
<Tooltip
|
||||||
|
placement="top"
|
||||||
|
value={
|
||||||
|
<span>
|
||||||
|
Close shortcuts <span class="text-text-weak">{formatKeybind("ctrl+/")}</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconButton icon="close" variant="ghost" onClick={props.onClose} />
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<For each={SHORTCUT_CATEGORIES}>
|
<For each={SHORTCUT_CATEGORIES}>
|
||||||
{(category) => (
|
{(category) => (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
import { batch, createEffect, createMemo, onCleanup, onMount } from "solid-js"
|
import { batch, createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { useGlobalSync } from "./global-sync"
|
import { useGlobalSync } from "./global-sync"
|
||||||
import { useGlobalSDK } from "./global-sdk"
|
import { useGlobalSDK } from "./global-sdk"
|
||||||
|
|
@ -93,6 +93,8 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [shortcutsOpened, setShortcutsOpened] = createSignal(false)
|
||||||
|
|
||||||
const MAX_SESSION_KEYS = 50
|
const MAX_SESSION_KEYS = 50
|
||||||
const meta = { active: undefined as string | undefined, pruned: false }
|
const meta = { active: undefined as string | undefined, pruned: false }
|
||||||
const used = new Map<string, number>()
|
const used = new Map<string, number>()
|
||||||
|
|
@ -353,6 +355,18 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||||
setStore("review", "diffStyle", diffStyle)
|
setStore("review", "diffStyle", diffStyle)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
shortcuts: {
|
||||||
|
opened: shortcutsOpened,
|
||||||
|
open() {
|
||||||
|
setShortcutsOpened(true)
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
setShortcutsOpened(false)
|
||||||
|
},
|
||||||
|
toggle() {
|
||||||
|
setShortcutsOpened((x) => !x)
|
||||||
|
},
|
||||||
|
},
|
||||||
session: {
|
session: {
|
||||||
width: createMemo(() => store.session?.width ?? 600),
|
width: createMemo(() => store.session?.width ?? 600),
|
||||||
resize(width: number) {
|
resize(width: number) {
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
|
|
||||||
.shortcuts-panel [data-slot="tabs-trigger-wrapper"] {
|
.shortcuts-panel [data-slot="tabs-trigger-wrapper"] {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0.5px solid transparent;
|
border: none;
|
||||||
font-weight: var(--font-weight-regular);
|
font-weight: var(--font-weight-regular);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
color: var(--color-text-weak);
|
color: var(--color-text-weak);
|
||||||
|
|
@ -108,8 +108,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.shortcuts-panel [data-slot="tabs-trigger-wrapper"]:has([data-selected]) {
|
.shortcuts-panel [data-slot="tabs-trigger-wrapper"]:has([data-selected]) {
|
||||||
border: 1px solid var(--color-border-weak-base);
|
background: var(--color-surface-raised-base-active);
|
||||||
background: var(--color-surface-raised-base);
|
|
||||||
color: var(--color-text-strong);
|
color: var(--color-text-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +201,20 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Adjust main content when shortcuts panel is open */
|
.shortcut-key-special {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortcut-key-shift {
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust main content and sidebar when shortcuts panel is open */
|
||||||
main.shortcuts-open {
|
main.shortcuts-open {
|
||||||
padding-bottom: 280px;
|
padding-bottom: 280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-shortcuts-open {
|
||||||
|
padding-bottom: 280px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
||||||
import { DialogSelectProvider } from "@/components/dialog-select-provider"
|
import { DialogSelectProvider } from "@/components/dialog-select-provider"
|
||||||
import { DialogSelectServer } from "@/components/dialog-select-server"
|
import { DialogSelectServer } from "@/components/dialog-select-server"
|
||||||
import { useCommand, type CommandOption } from "@/context/command"
|
import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
|
||||||
import { ConstrainDragXAxis } from "@/utils/solid-dnd"
|
import { ConstrainDragXAxis } from "@/utils/solid-dnd"
|
||||||
import { navStart } from "@/utils/perf"
|
import { navStart } from "@/utils/perf"
|
||||||
import { DialogSelectDirectory } from "@/components/dialog-select-directory"
|
import { DialogSelectDirectory } from "@/components/dialog-select-directory"
|
||||||
|
|
@ -81,7 +81,6 @@ export default function Layout(props: ParentProps) {
|
||||||
workspaceExpanded: {} as Record<string, boolean>,
|
workspaceExpanded: {} as Record<string, boolean>,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const [shortcutsOpen, setShortcutsOpen] = createSignal(false)
|
|
||||||
|
|
||||||
const pageReady = createMemo(() => ready())
|
const pageReady = createMemo(() => ready())
|
||||||
|
|
||||||
|
|
@ -757,7 +756,7 @@ export default function Layout(props: ParentProps) {
|
||||||
title: "Toggle shortcuts panel",
|
title: "Toggle shortcuts panel",
|
||||||
category: "View",
|
category: "View",
|
||||||
keybind: "ctrl+/",
|
keybind: "ctrl+/",
|
||||||
onSelect: () => setShortcutsOpen(!shortcutsOpen()),
|
onSelect: () => layout.shortcuts.toggle(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "project.open",
|
id: "project.open",
|
||||||
|
|
@ -1897,7 +1896,7 @@ export default function Layout(props: ParentProps) {
|
||||||
const homedir = createMemo(() => sync.data.path.home)
|
const homedir = createMemo(() => sync.data.path.home)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex h-full w-full overflow-hidden">
|
<div class="flex h-full w-full overflow-hidden" classList={{ "sidebar-shortcuts-open": layout.shortcuts.opened() }}>
|
||||||
<div class="w-16 shrink-0 bg-background-base flex flex-col items-center overflow-hidden">
|
<div class="w-16 shrink-0 bg-background-base flex flex-col items-center overflow-hidden">
|
||||||
<div class="flex-1 min-h-0 w-full">
|
<div class="flex-1 min-h-0 w-full">
|
||||||
<DragDropProvider
|
<DragDropProvider
|
||||||
|
|
@ -1937,7 +1936,7 @@ export default function Layout(props: ParentProps) {
|
||||||
<Tooltip placement={sidebarProps.mobile ? "bottom" : "right"} value="Settings" class="hidden">
|
<Tooltip placement={sidebarProps.mobile ? "bottom" : "right"} value="Settings" class="hidden">
|
||||||
<IconButton disabled icon="settings-gear" variant="ghost" size="large" />
|
<IconButton disabled icon="settings-gear" variant="ghost" size="large" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<DropdownMenu>
|
<DropdownMenu placement={layout.shortcuts.opened() ? "top-start" : "bottom-start"}>
|
||||||
<Tooltip placement={sidebarProps.mobile ? "bottom" : "right"} value="Help">
|
<Tooltip placement={sidebarProps.mobile ? "bottom" : "right"} value="Help">
|
||||||
<DropdownMenu.Trigger as={IconButton} icon="question-mark" variant="ghost" size="large" />
|
<DropdownMenu.Trigger as={IconButton} icon="question-mark" variant="ghost" size="large" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
@ -1946,7 +1945,9 @@ export default function Layout(props: ParentProps) {
|
||||||
<DropdownMenu.Item onSelect={() => platform.openLink("https://opencode.ai/desktop-feedback")}>
|
<DropdownMenu.Item onSelect={() => platform.openLink("https://opencode.ai/desktop-feedback")}>
|
||||||
Submit feedback
|
Submit feedback
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item onSelect={() => setShortcutsOpen(true)}>Keyboard shortcuts</DropdownMenu.Item>
|
<DropdownMenu.Item class="flex justify-between gap-6" onSelect={() => layout.shortcuts.open()}>
|
||||||
|
Keyboard shortcuts <span class="text-text-weaker">{formatKeybind("ctrl+/")}</span>
|
||||||
|
</DropdownMenu.Item>
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu.Portal>
|
</DropdownMenu.Portal>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
@ -2160,14 +2161,14 @@ export default function Layout(props: ParentProps) {
|
||||||
classList={{
|
classList={{
|
||||||
"size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base": true,
|
"size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base": true,
|
||||||
"xl:border-l xl:rounded-tl-sm": !layout.sidebar.opened(),
|
"xl:border-l xl:rounded-tl-sm": !layout.sidebar.opened(),
|
||||||
"shortcuts-open": shortcutsOpen(),
|
"shortcuts-open": layout.shortcuts.opened(),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<Show when={shortcutsOpen()}>
|
<Show when={layout.shortcuts.opened()}>
|
||||||
<ShortcutsPanel onClose={() => setShortcutsOpen(false)} />
|
<ShortcutsPanel onClose={() => layout.shortcuts.close()} />
|
||||||
</Show>
|
</Show>
|
||||||
<Toast.Region />
|
<Toast.Region />
|
||||||
<ReleaseNotesHandler />
|
<ReleaseNotesHandler />
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export type SelectProps<T> = Omit<ComponentProps<typeof Kobalte<T>>, "value" | "
|
||||||
class?: ComponentProps<"div">["class"]
|
class?: ComponentProps<"div">["class"]
|
||||||
classList?: ComponentProps<"div">["classList"]
|
classList?: ComponentProps<"div">["classList"]
|
||||||
children?: (item: T | undefined) => JSX.Element
|
children?: (item: T | undefined) => JSX.Element
|
||||||
|
placement?: "bottom-start" | "bottom-end" | "top-start" | "top-end"
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Select<T>(props: SelectProps<T> & ButtonProps) {
|
export function Select<T>(props: SelectProps<T> & ButtonProps) {
|
||||||
|
|
@ -29,6 +30,7 @@ export function Select<T>(props: SelectProps<T> & ButtonProps) {
|
||||||
"groupBy",
|
"groupBy",
|
||||||
"onSelect",
|
"onSelect",
|
||||||
"children",
|
"children",
|
||||||
|
"placement",
|
||||||
])
|
])
|
||||||
const grouped = createMemo(() => {
|
const grouped = createMemo(() => {
|
||||||
const result = pipe(
|
const result = pipe(
|
||||||
|
|
@ -46,7 +48,7 @@ export function Select<T>(props: SelectProps<T> & ButtonProps) {
|
||||||
<Kobalte<T, { category: string; options: T[] }>
|
<Kobalte<T, { category: string; options: T[] }>
|
||||||
{...others}
|
{...others}
|
||||||
data-component="select"
|
data-component="select"
|
||||||
placement="bottom-start"
|
placement={local.placement ?? "bottom-start"}
|
||||||
value={local.current}
|
value={local.current}
|
||||||
options={grouped()}
|
options={grouped()}
|
||||||
optionValue={(x) => (local.value ? local.value(x) : (x as string))}
|
optionValue={(x) => (local.value ? local.value(x) : (x as string))}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue