diff options
| author | adamelmore <[email protected]> | 2026-01-26 10:04:59 -0600 |
|---|---|---|
| committer | adamelmore <[email protected]> | 2026-01-26 11:26:17 -0600 |
| commit | d05ed5ca83e03904c7c3dda23a6dcccfd607bdce (patch) | |
| tree | 1afcb055bcedf8b477472f0b25432b318a95f53b /packages/app/src/components | |
| parent | 37f1a1a4ef36eacb60ad5493db8aeb1130c5fa91 (diff) | |
| download | opencode-d05ed5ca83e03904c7c3dda23a6dcccfd607bdce.tar.gz opencode-d05ed5ca83e03904c7c3dda23a6dcccfd607bdce.zip | |
chore(app): createStore over signals
Diffstat (limited to 'packages/app/src/components')
4 files changed, 79 insertions, 71 deletions
diff --git a/packages/app/src/components/dialog-edit-project.tsx b/packages/app/src/components/dialog-edit-project.tsx index 9e2bddc6b..2ab25ceeb 100644 --- a/packages/app/src/components/dialog-edit-project.tsx +++ b/packages/app/src/components/dialog-edit-project.tsx @@ -3,7 +3,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog" import { Dialog } from "@opencode-ai/ui/dialog" import { TextField } from "@opencode-ai/ui/text-field" import { Icon } from "@opencode-ai/ui/icon" -import { createMemo, createSignal, For, Show } from "solid-js" +import { createMemo, For, Show } from "solid-js" import { createStore } from "solid-js/store" import { useGlobalSDK } from "@/context/global-sdk" import { useGlobalSync } from "@/context/global-sync" @@ -29,35 +29,34 @@ export function DialogEditProject(props: { project: LocalProject }) { iconUrl: props.project.icon?.override || "", startup: props.project.commands?.start ?? "", saving: false, + dragOver: false, + iconHover: false, }) - const [dragOver, setDragOver] = createSignal(false) - const [iconHover, setIconHover] = createSignal(false) - function handleFileSelect(file: File) { if (!file.type.startsWith("image/")) return const reader = new FileReader() reader.onload = (e) => { setStore("iconUrl", e.target?.result as string) - setIconHover(false) + setStore("iconHover", false) } reader.readAsDataURL(file) } function handleDrop(e: DragEvent) { e.preventDefault() - setDragOver(false) + setStore("dragOver", false) const file = e.dataTransfer?.files[0] if (file) handleFileSelect(file) } function handleDragOver(e: DragEvent) { e.preventDefault() - setDragOver(true) + setStore("dragOver", true) } function handleDragLeave() { - setDragOver(false) + setStore("dragOver", false) } function handleInputChange(e: Event) { @@ -116,19 +115,23 @@ export function DialogEditProject(props: { project: LocalProject }) { <div class="flex flex-col gap-2"> <label class="text-12-medium text-text-weak">{language.t("dialog.project.edit.icon")}</label> <div class="flex gap-3 items-start"> - <div class="relative" onMouseEnter={() => setIconHover(true)} onMouseLeave={() => setIconHover(false)}> + <div + class="relative" + onMouseEnter={() => setStore("iconHover", true)} + onMouseLeave={() => setStore("iconHover", false)} + > <div class="relative size-16 rounded-md transition-colors cursor-pointer" classList={{ - "border-text-interactive-base bg-surface-info-base/20": dragOver(), - "border-border-base hover:border-border-strong": !dragOver(), + "border-text-interactive-base bg-surface-info-base/20": store.dragOver, + "border-border-base hover:border-border-strong": !store.dragOver, "overflow-hidden": !!store.iconUrl, }} onDrop={handleDrop} onDragOver={handleDragOver} onDragLeave={handleDragLeave} onClick={() => { - if (store.iconUrl && iconHover()) { + if (store.iconUrl && store.iconHover) { clearIcon() } else { document.getElementById("icon-upload")?.click() @@ -166,7 +169,7 @@ export function DialogEditProject(props: { project: LocalProject }) { "border-radius": "6px", "z-index": 10, "pointer-events": "none", - opacity: iconHover() && !store.iconUrl ? 1 : 0, + opacity: store.iconHover && !store.iconUrl ? 1 : 0, display: "flex", "align-items": "center", "justify-content": "center", @@ -185,7 +188,7 @@ export function DialogEditProject(props: { project: LocalProject }) { "border-radius": "6px", "z-index": 10, "pointer-events": "none", - opacity: iconHover() && store.iconUrl ? 1 : 0, + opacity: store.iconHover && store.iconUrl ? 1 : 0, display: "flex", "align-items": "center", "justify-content": "center", diff --git a/packages/app/src/components/session/session-sortable-terminal-tab.tsx b/packages/app/src/components/session/session-sortable-terminal-tab.tsx index d16379e80..75e9b22f9 100644 --- a/packages/app/src/components/session/session-sortable-terminal-tab.tsx +++ b/packages/app/src/components/session/session-sortable-terminal-tab.tsx @@ -1,5 +1,6 @@ import type { JSX } from "solid-js" -import { createSignal, Show } from "solid-js" +import { Show } from "solid-js" +import { createStore } from "solid-js/store" import { createSortable } from "@thisbeyond/solid-dnd" import { IconButton } from "@opencode-ai/ui/icon-button" import { Tabs } from "@opencode-ai/ui/tabs" @@ -12,11 +13,13 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => const terminal = useTerminal() const language = useLanguage() const sortable = createSortable(props.terminal.id) - const [editing, setEditing] = createSignal(false) - const [title, setTitle] = createSignal(props.terminal.title) - const [menuOpen, setMenuOpen] = createSignal(false) - const [menuPosition, setMenuPosition] = createSignal({ x: 0, y: 0 }) - const [blurEnabled, setBlurEnabled] = createSignal(false) + const [store, setStore] = createStore({ + editing: false, + title: props.terminal.title, + menuOpen: false, + menuPosition: { x: 0, y: 0 }, + blurEnabled: false, + }) const isDefaultTitle = () => { const number = props.terminal.titleNumber @@ -47,7 +50,7 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => } const focus = () => { - if (editing()) return + if (store.editing) return if (document.activeElement instanceof HTMLElement) { document.activeElement.blur() @@ -71,26 +74,26 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => e.preventDefault() } - setBlurEnabled(false) - setTitle(props.terminal.title) - setEditing(true) + setStore("blurEnabled", false) + setStore("title", props.terminal.title) + setStore("editing", true) setTimeout(() => { const input = document.getElementById(`terminal-title-input-${props.terminal.id}`) as HTMLInputElement if (!input) return input.focus() input.select() - setTimeout(() => setBlurEnabled(true), 100) + setTimeout(() => setStore("blurEnabled", true), 100) }, 10) } const save = () => { - if (!blurEnabled()) return + if (!store.blurEnabled) return - const value = title().trim() + const value = store.title.trim() if (value && value !== props.terminal.title) { terminal.update({ id: props.terminal.id, title: value }) } - setEditing(false) + setStore("editing", false) } const keydown = (e: KeyboardEvent) => { @@ -101,14 +104,14 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => } if (e.key === "Escape") { e.preventDefault() - setEditing(false) + setStore("editing", false) } } const menu = (e: MouseEvent) => { e.preventDefault() - setMenuPosition({ x: e.clientX, y: e.clientY }) - setMenuOpen(true) + setStore("menuPosition", { x: e.clientX, y: e.clientY }) + setStore("menuOpen", true) } return ( @@ -143,17 +146,17 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => /> } > - <span onDblClick={edit} style={{ visibility: editing() ? "hidden" : "visible" }}> + <span onDblClick={edit} style={{ visibility: store.editing ? "hidden" : "visible" }}> {label()} </span> </Tabs.Trigger> - <Show when={editing()}> + <Show when={store.editing}> <div class="absolute inset-0 flex items-center px-3 bg-muted z-10 pointer-events-auto"> <input id={`terminal-title-input-${props.terminal.id}`} type="text" - value={title()} - onInput={(e) => setTitle(e.currentTarget.value)} + value={store.title} + onInput={(e) => setStore("title", e.currentTarget.value)} onBlur={save} onKeyDown={keydown} onMouseDown={(e) => e.stopPropagation()} @@ -161,13 +164,13 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => /> </div> </Show> - <DropdownMenu open={menuOpen()} onOpenChange={setMenuOpen}> + <DropdownMenu open={store.menuOpen} onOpenChange={(open) => setStore("menuOpen", open)}> <DropdownMenu.Portal> <DropdownMenu.Content style={{ position: "fixed", - left: `${menuPosition().x}px`, - top: `${menuPosition().y}px`, + left: `${store.menuPosition.x}px`, + top: `${store.menuPosition.y}px`, }} > <DropdownMenu.Item onSelect={edit}> diff --git a/packages/app/src/components/settings-keybinds.tsx b/packages/app/src/components/settings-keybinds.tsx index 4dc397149..7ff3425ab 100644 --- a/packages/app/src/components/settings-keybinds.tsx +++ b/packages/app/src/components/settings-keybinds.tsx @@ -1,4 +1,5 @@ -import { Component, For, Show, createMemo, createSignal, onCleanup, onMount } from "solid-js" +import { Component, For, Show, createMemo, onCleanup, onMount } from "solid-js" +import { createStore } from "solid-js/store" import { Button } from "@opencode-ai/ui/button" import { Icon } from "@opencode-ai/ui/icon" import { IconButton } from "@opencode-ai/ui/icon-button" @@ -111,24 +112,26 @@ export const SettingsKeybinds: Component = () => { const language = useLanguage() const settings = useSettings() - const [active, setActive] = createSignal<string | null>(null) - const [filter, setFilter] = createSignal("") + const [store, setStore] = createStore({ + active: null as string | null, + filter: "", + }) const stop = () => { - if (!active()) return - setActive(null) + if (!store.active) return + setStore("active", null) command.keybinds(true) } const start = (id: string) => { - if (active() === id) { + if (store.active === id) { stop() return } - if (active()) stop() + if (store.active) stop() - setActive(id) + setStore("active", id) command.keybinds(false) } @@ -203,7 +206,7 @@ export const SettingsKeybinds: Component = () => { }) const filtered = createMemo(() => { - const query = filter().toLowerCase().trim() + const query = store.filter.toLowerCase().trim() if (!query) return grouped() const map = list() @@ -285,7 +288,7 @@ export const SettingsKeybinds: Component = () => { onMount(() => { const handle = (event: KeyboardEvent) => { - const id = active() + const id = store.active if (!id) return event.preventDefault() @@ -345,7 +348,7 @@ export const SettingsKeybinds: Component = () => { }) onCleanup(() => { - if (active()) command.keybinds(true) + if (store.active) command.keybinds(true) }) return ( @@ -370,8 +373,8 @@ export const SettingsKeybinds: Component = () => { <TextField variant="ghost" type="text" - value={filter()} - onChange={setFilter} + value={store.filter} + onChange={(v) => setStore("filter", v)} placeholder={language.t("settings.shortcuts.search.placeholder")} spellcheck={false} autocorrect="off" @@ -379,8 +382,8 @@ export const SettingsKeybinds: Component = () => { autocapitalize="off" class="flex-1" /> - <Show when={filter()}> - <IconButton icon="circle-x" variant="ghost" onClick={() => setFilter("")} /> + <Show when={store.filter}> + <IconButton icon="circle-x" variant="ghost" onClick={() => setStore("filter", "")} /> </Show> </div> </div> @@ -402,13 +405,13 @@ export const SettingsKeybinds: Component = () => { classList={{ "h-8 px-3 rounded-md text-12-regular": true, "bg-surface-base text-text-subtle hover:bg-surface-raised-base-hover active:bg-surface-raised-base-active": - active() !== id, - "border border-border-weak-base bg-surface-inset-base text-text-weak": active() === id, + store.active !== id, + "border border-border-weak-base bg-surface-inset-base text-text-weak": store.active === id, }} onClick={() => start(id)} > <Show - when={active() === id} + when={store.active === id} fallback={command.keybind(id) || language.t("settings.shortcuts.unassigned")} > {language.t("settings.shortcuts.pressKeys")} @@ -423,11 +426,11 @@ export const SettingsKeybinds: Component = () => { )} </For> - <Show when={filter() && !hasResults()}> + <Show when={store.filter && !hasResults()}> <div class="flex flex-col items-center justify-center py-12 text-center"> <span class="text-14-regular text-text-weak">{language.t("settings.shortcuts.search.empty")}</span> - <Show when={filter()}> - <span class="text-14-regular text-text-strong mt-1">"{filter()}"</span> + <Show when={store.filter}> + <span class="text-14-regular text-text-strong mt-1">"{store.filter}"</span> </Show> </div> </Show> diff --git a/packages/app/src/components/status-popover.tsx b/packages/app/src/components/status-popover.tsx index cb45c3689..c2c4d268a 100644 --- a/packages/app/src/components/status-popover.tsx +++ b/packages/app/src/components/status-popover.tsx @@ -39,9 +39,10 @@ export function StatusPopover() { const language = useLanguage() const navigate = useNavigate() - const [loading, setLoading] = createSignal<string | null>(null) const [store, setStore] = createStore({ status: {} as Record<string, ServerStatus | undefined>, + loading: null as string | null, + defaultServerUrl: undefined as string | undefined, }) const servers = createMemo(() => { @@ -97,8 +98,8 @@ export function StatusPopover() { const mcpConnected = createMemo(() => mcpItems().filter((i) => i.status === "connected").length) const toggleMcp = async (name: string) => { - if (loading()) return - setLoading(name) + if (store.loading) return + setStore("loading", name) const status = sync.data.mcp[name] if (status?.status === "connected") { await sdk.client.mcp.disconnect({ name }) @@ -107,7 +108,7 @@ export function StatusPopover() { } const result = await sdk.client.mcp.status() if (result.data) sync.set("mcp", result.data) - setLoading(null) + setStore("loading", null) } const lspItems = createMemo(() => sync.data.lsp ?? []) @@ -123,19 +124,17 @@ export function StatusPopover() { const serverCount = createMemo(() => sortedServers().length) - const [defaultServerUrl, setDefaultServerUrl] = createSignal<string | undefined>() - const refreshDefaultServerUrl = () => { const result = platform.getDefaultServerUrl?.() if (!result) { - setDefaultServerUrl(undefined) + setStore("defaultServerUrl", undefined) return } if (result instanceof Promise) { - result.then((url) => setDefaultServerUrl(url ? normalizeServerUrl(url) : undefined)) + result.then((url) => setStore("defaultServerUrl", url ? normalizeServerUrl(url) : undefined)) return } - setDefaultServerUrl(normalizeServerUrl(result)) + setStore("defaultServerUrl", normalizeServerUrl(result)) } createEffect(() => { @@ -220,7 +219,7 @@ export function StatusPopover() { <For each={sortedServers()}> {(url) => { const isActive = () => url === server.url - const isDefault = () => url === defaultServerUrl() + const isDefault = () => url === store.defaultServerUrl const status = () => store.status[url] const isBlocked = () => status()?.healthy === false const [truncated, setTruncated] = createSignal(false) @@ -329,7 +328,7 @@ export function StatusPopover() { type="button" class="flex items-center gap-2 w-full h-8 pl-3 pr-2 py-1 rounded-md hover:bg-surface-raised-base-hover transition-colors text-left" onClick={() => toggleMcp(item.name)} - disabled={loading() === item.name} + disabled={store.loading === item.name} > <div classList={{ @@ -345,7 +344,7 @@ export function StatusPopover() { <div onClick={(event) => event.stopPropagation()}> <Switch checked={enabled()} - disabled={loading() === item.name} + disabled={store.loading === item.name} onChange={() => toggleMcp(item.name)} /> </div> |
