mirror of
https://github.com/anomalyco/opencode.git
synced 2026-10-07 15:58:23 +00:00
Compare commits
11
Commits
external-method
...
v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2979039f1 | ||
|
|
119cb15d1d | ||
|
|
e1b7299bc9 | ||
|
|
68aa4f8017 | ||
|
|
69219470a2 | ||
|
|
10b11ba946 | ||
|
|
2e3d3a735a | ||
|
|
f655e7b2b5 | ||
|
|
6273bd7924 | ||
|
|
34cf183c4c | ||
|
|
fd99516e51 |
No files matched your search
@@ -285,7 +285,7 @@ const AnthropicThinkingEnabled = Schema.Struct({
|
||||
...AnthropicThinkingFields,
|
||||
})
|
||||
const AnthropicThinkingAdaptive = Schema.Struct({ type: Schema.tag("adaptive"), ...AnthropicThinkingFields })
|
||||
const AnthropicThinkingDisabled = Schema.Struct({ type: Schema.tag("disabled") })
|
||||
const AnthropicThinkingDisabled = Schema.Struct({ type: Schema.Literals(["disabled", "between_tools"]) })
|
||||
const AnthropicThinking = Schema.Union([AnthropicThinkingEnabled, AnthropicThinkingAdaptive, AnthropicThinkingDisabled])
|
||||
type AnthropicThinking = typeof AnthropicThinking.Type
|
||||
|
||||
@@ -1001,18 +1001,21 @@ const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest, breakpoi
|
||||
// TODO: Move per-model capability heuristics (`supportsEffortUpdates`, `supportsNativeSystemUpdates`,
|
||||
// `supportsThinkingBlockBinding`) into explicit model/provider `compatibility` metadata so the protocol
|
||||
// only reads `request.model.compatibility`.
|
||||
const isThinkingOff = Schema.is(AnthropicThinkingDisabled)
|
||||
|
||||
// Per-turn effort started with Claude Opus 5 and every Claude 5.1 model; later versions of any family inherit it.
|
||||
const supportsEffortUpdates = (model: LLMRequest["model"]) => {
|
||||
const override = model.compatibility?.supportsEffortUpdates
|
||||
const supportsEffortUpdates = (request: LLMRequest) => {
|
||||
if (isThinkingOff(request.providerOptions?.thinking)) return false
|
||||
const override = request.model.compatibility?.supportsEffortUpdates
|
||||
if (override !== undefined) return override
|
||||
const version = claudeVersion(model.id)
|
||||
const version = claudeVersion(request.model.id)
|
||||
if (version === undefined) return false
|
||||
if (version.family === "opus" && version.major >= 5) return true
|
||||
return version.major > 5 || (version.major === 5 && version.minor >= 1)
|
||||
}
|
||||
|
||||
const applyThinkingBindingDefault = (model: LLMRequest["model"], thinking: AnthropicThinking | undefined) => {
|
||||
if (thinking?.type === "disabled") return thinking
|
||||
if (isThinkingOff(thinking)) return thinking
|
||||
if (!supportsThinkingBlockBinding(model)) return thinking
|
||||
return {
|
||||
...(thinking ?? { type: "adaptive" as const }),
|
||||
@@ -1607,7 +1610,7 @@ export const protocol = Protocol.make({
|
||||
}),
|
||||
step,
|
||||
},
|
||||
supportsEffortUpdates: (request) => supportsEffortUpdates(request.model),
|
||||
supportsEffortUpdates,
|
||||
})
|
||||
|
||||
export const transport = <
|
||||
@@ -1653,7 +1656,7 @@ function requiredBetaHeaders(body: Pick<AnthropicMessagesBody, "messages" | "con
|
||||
betas.push("mid-conversation-output-config-2026-07-01")
|
||||
|
||||
const thinking = body.thinking
|
||||
if (thinking && thinking.type !== "disabled" && thinking.block_binding) betas.push(THINKING_BINDING_BETA)
|
||||
if (thinking && !isThinkingOff(thinking) && thinking.block_binding) betas.push(THINKING_BINDING_BETA)
|
||||
return betas
|
||||
}
|
||||
|
||||
|
||||
@@ -476,7 +476,9 @@ const MIN_THINKING_BUDGET = 1_024
|
||||
|
||||
const isThinkingDisabled = Schema.is(
|
||||
Schema.Struct({
|
||||
additionalModelRequestFields: Schema.Struct({ thinking: Schema.Struct({ type: Schema.Literal("disabled") }) }),
|
||||
additionalModelRequestFields: Schema.Struct({
|
||||
thinking: Schema.Struct({ type: Schema.Literals(["disabled", "between_tools"]) }),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -65,8 +65,8 @@ const messagesRoute = Route.make({
|
||||
...AnthropicMessages.protocol,
|
||||
// Mantle rejects mid-conversation `output_config` on Opus 5.0; support starts at 5.1+.
|
||||
supportsEffortUpdates: (request) => {
|
||||
const override = request.model.compatibility?.supportsEffortUpdates
|
||||
if (override !== undefined) return override
|
||||
if (!(AnthropicMessages.protocol.supportsEffortUpdates?.(request) ?? false)) return false
|
||||
if (request.model.compatibility?.supportsEffortUpdates !== undefined) return true
|
||||
const version = claudeVersion(request.model.id)
|
||||
return version !== undefined && (version.major > 5 || (version.major === 5 && version.minor >= 1))
|
||||
},
|
||||
|
||||
@@ -268,6 +268,22 @@ describe("Anthropic Messages effort updates", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("strips markers when thinking is disabled or between_tools", () =>
|
||||
Effect.gen(function* () {
|
||||
const sonnet = anthropic("claude-sonnet-5-5")
|
||||
const betweenTools = yield* compileRequest(
|
||||
LLM.request({
|
||||
model: sonnet,
|
||||
messages: conversation,
|
||||
providerOptions: { thinking: { type: "between_tools" }, effort: "low" },
|
||||
}),
|
||||
)
|
||||
|
||||
expect(systemMessages(betweenTools.body)).toHaveLength(0)
|
||||
expect(betweenTools.body.output_config).toEqual({ effort: "low" })
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("strips markers for Opus 5.0 on Bedrock Mantle Messages while lowering Opus 5.5", () =>
|
||||
Effect.gen(function* () {
|
||||
const mantle = AmazonBedrockMantle.configure({ apiKey: "test", region: "us-east-1" })
|
||||
|
||||
@@ -44,6 +44,7 @@ it.effect("preserves explicit thinking settings and combines required beta heade
|
||||
Effect.gen(function* () {
|
||||
for (const thinking of [
|
||||
{ type: "disabled" },
|
||||
{ type: "between_tools" },
|
||||
{ type: "adaptive", block_binding: { prefix_mismatch_behavior: "error" } },
|
||||
] as const) {
|
||||
const request = LLM.request({
|
||||
@@ -55,7 +56,7 @@ it.effect("preserves explicit thinking settings and combines required beta heade
|
||||
const prepared = yield* AnthropicMessages.route.prepareTransport(compiled.body, request)
|
||||
expect(compiled.body.thinking).toEqual(thinking)
|
||||
expect(prepared.request.headers["anthropic-beta"]).toBe(
|
||||
thinking.type === "disabled"
|
||||
thinking.type === "disabled" || thinking.type === "between_tools"
|
||||
? "interleaved-thinking-2025-05-14,compact-2026-01-12"
|
||||
: "interleaved-thinking-2025-05-14,compact-2026-01-12,thinking-binding-controls-2026-08-01",
|
||||
)
|
||||
|
||||
@@ -38,6 +38,16 @@ test("home commands do not wait for session search", async ({ page }) => {
|
||||
release.resolve()
|
||||
})
|
||||
|
||||
test("lists recent sessions before a query is entered", async ({ page }) => {
|
||||
const { dialog } = await openCommandPalette(page, true)
|
||||
const session = dialog.getByRole("option", { name: /Palette fixture session/ })
|
||||
await expect(dialog.getByText("Recent sessions", { exact: true })).toBeVisible()
|
||||
await expect(session).toHaveAttribute("aria-selected", "false")
|
||||
await session.click()
|
||||
await expect(dialog).toHaveCount(0)
|
||||
await expect(page.getByRole("heading", { name: paletteSession.title, exact: true })).toBeVisible()
|
||||
})
|
||||
|
||||
test("appends search results without resetting the selected command", async ({ page }) => {
|
||||
const { dialog, input } = await openCommandPalette(page)
|
||||
const files = Promise.withResolvers<void>()
|
||||
|
||||
@@ -106,6 +106,37 @@ for (const mount of ["cached", "explicit"] as const) {
|
||||
})
|
||||
}
|
||||
|
||||
test("reconnects the same terminal after a transient connection failure", async ({ page }) => {
|
||||
const { pty } = await openSession(page, workspace)
|
||||
let tickets = 0
|
||||
await page.route(/\/api\/pty\/[^/]+\/connect-token/, (route) => {
|
||||
if (route.request().method() === "OPTIONS") return route.fallback()
|
||||
tickets += 1
|
||||
|
||||
if (tickets !== 2) return route.fallback()
|
||||
|
||||
return route.fulfill({
|
||||
status: 503,
|
||||
headers: { "access-control-allow-origin": "*" },
|
||||
body: "Service Unavailable",
|
||||
})
|
||||
})
|
||||
await page.keyboard.press("Control+Backquote")
|
||||
const terminal = page.locator('[data-component="terminal"]')
|
||||
await expect(terminal.locator("textarea")).toHaveCount(1)
|
||||
await expect.poll(() => pty.sockets.length).toBe(1)
|
||||
const id = pty.sockets[0]!.id
|
||||
await terminal.evaluate((element) => element.setAttribute("data-connection-probe", "original"))
|
||||
|
||||
await pty.sockets[0]!.close(1011, "Temporary disconnection")
|
||||
await expect.poll(() => tickets).toBe(3)
|
||||
await expect.poll(() => pty.sockets.length).toBe(2)
|
||||
expect(pty.sockets[1]!.id).toBe(id)
|
||||
expect(pty.rejected).toEqual([])
|
||||
await expect(terminal).toHaveAttribute("data-connection-probe", "original")
|
||||
expect(pty.created.map((item) => item.id)).toEqual([id])
|
||||
})
|
||||
|
||||
test("focuses a terminal created from the new-terminal button", async ({ page }) => {
|
||||
const { editor } = await openSession(page, workspace)
|
||||
const terminal = page.locator('[data-component="terminal"]')
|
||||
|
||||
@@ -56,7 +56,6 @@ export function mount(input: { server: string; route: string; direction: Directi
|
||||
return (
|
||||
<AppInterface
|
||||
servers={[server]}
|
||||
defaultServer={ServerConnection.key(server)}
|
||||
canonicalLocalServer={ServerConnection.key(server)}
|
||||
router={(props) => <MemoryRouter {...props} history={history} />}
|
||||
>
|
||||
|
||||
@@ -63,6 +63,12 @@ export class MockShellNotFound extends Schema.TaggedError<MockShellNotFound>()("
|
||||
message: Schema.String,
|
||||
}) {}
|
||||
|
||||
// The server's error for an unknown PTY, or one owned by another workspace.
|
||||
export class MockPtyNotFound extends Schema.TaggedError<MockPtyNotFound>()("PtyNotFoundError", {
|
||||
ptyID: Schema.String,
|
||||
message: Schema.String,
|
||||
}) {}
|
||||
|
||||
// The server's error for a request without its password.
|
||||
export class MockUnauthorized extends Schema.TaggedError<MockUnauthorized>()("UnauthorizedError", {
|
||||
message: Schema.String,
|
||||
@@ -75,6 +81,8 @@ export class MockUnsupported extends Schema.TaggedError<MockUnsupported>()("Mock
|
||||
|
||||
const Unsupported = MockUnsupported.pipe(HttpApiSchema.status(501))
|
||||
|
||||
const PtyMissing = [MockNotFound.pipe(HttpApiSchema.status(404)), MockPtyNotFound.pipe(HttpApiSchema.status(404))]
|
||||
|
||||
const Group = HttpApiGroup.make("mock")
|
||||
.add(HttpApiEndpoint.get("info", "/api/info", { success: Json }))
|
||||
.add(
|
||||
@@ -242,7 +250,7 @@ const Group = HttpApiGroup.make("mock")
|
||||
HttpApiEndpoint.get("ptyGet", "/api/pty/:ptyID", {
|
||||
params: PtyParams,
|
||||
success: Json,
|
||||
error: MockNotFound.pipe(HttpApiSchema.status(404)),
|
||||
error: PtyMissing,
|
||||
}),
|
||||
)
|
||||
.add(
|
||||
@@ -250,21 +258,21 @@ const Group = HttpApiGroup.make("mock")
|
||||
params: PtyParams,
|
||||
payload: Pty.UpdateInput,
|
||||
success: Json,
|
||||
error: MockNotFound.pipe(HttpApiSchema.status(404)),
|
||||
error: PtyMissing,
|
||||
}),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.delete("ptyRemove", "/api/pty/:ptyID", {
|
||||
params: PtyParams,
|
||||
success: NoContent,
|
||||
error: MockNotFound.pipe(HttpApiSchema.status(404)),
|
||||
error: PtyMissing,
|
||||
}),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.post("ptyConnectToken", "/api/pty/:ptyID/connect-token", {
|
||||
params: PtyParams,
|
||||
success: Json,
|
||||
error: MockNotFound.pipe(HttpApiSchema.status(404)),
|
||||
error: PtyMissing,
|
||||
}),
|
||||
)
|
||||
.add(
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
MockBadRequest,
|
||||
MockInternal,
|
||||
MockNotFound,
|
||||
MockPtyNotFound,
|
||||
MockShellNotFound,
|
||||
MockUnauthorized,
|
||||
MockUnsupported,
|
||||
@@ -149,7 +150,14 @@ export type MockPtyInfo = {
|
||||
pid: number
|
||||
}
|
||||
|
||||
export type MockPtySocket = { id: string; url: URL; input: string[]; closed: boolean; send(data: string): void }
|
||||
export type MockPtySocket = {
|
||||
id: string
|
||||
url: URL
|
||||
input: string[]
|
||||
closed: boolean
|
||||
send(data: string): void
|
||||
close(code: number, reason: string): Promise<void>
|
||||
}
|
||||
|
||||
export type MockPty = {
|
||||
list: MockPtyInfo[]
|
||||
@@ -447,6 +455,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
|
||||
input: [],
|
||||
closed: false,
|
||||
send: (data) => ws.send(data),
|
||||
close: (code, reason) => ws.close({ code, reason }),
|
||||
}
|
||||
|
||||
ws.onMessage((message) => socket.input.push(message.toString()))
|
||||
@@ -697,7 +706,9 @@ function mockHandlers(
|
||||
const directory = requestDirectory(config, request)
|
||||
const found = state.pty.find(id, directory)
|
||||
|
||||
return found ? Effect.succeed(found) : Effect.fail(new MockNotFound({ message: "PTY not found" }))
|
||||
return found
|
||||
? Effect.succeed(found)
|
||||
: Effect.fail(new MockPtyNotFound({ ptyID: id, message: `PTY not found: ${id}` }))
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -241,11 +241,7 @@ export function mount(input: {
|
||||
|
||||
return (
|
||||
<Show when={extensions.ready()}>
|
||||
<AppInterface
|
||||
servers={servers()}
|
||||
defaultServer={ServerConnection.Key.make("sidecar")}
|
||||
router={(props) => <MemoryRouter {...props} history={history} />}
|
||||
/>
|
||||
<AppInterface servers={servers()} router={(props) => <MemoryRouter {...props} history={history} />} />
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { render } from "solid-js/web"
|
||||
import type { Bridge } from "@opencode/gui-extensions/sdk/bridge"
|
||||
import { AppBaseProviders, AppInterface } from "../../src/app"
|
||||
import { PlatformProvider, type Platform } from "../../src/runtime/platform/platform"
|
||||
import { ServerConnection } from "../../src/runtime/server/registry"
|
||||
|
||||
// The app on a Windows desktop platform, so the titlebar shows the app menu. Menu actions are listed in an output.
|
||||
export function mount(input: { server: string }) {
|
||||
@@ -59,7 +58,6 @@ export function mount(input: { server: string }) {
|
||||
<output aria-label="Desktop menu actions">{store.actions.join(",")}</output>
|
||||
<AppInterface
|
||||
servers={[{ type: "sidecar", variant: "base", displayName: "Local Server", http: { url: input.server } }]}
|
||||
defaultServer={ServerConnection.Key.make("sidecar")}
|
||||
router={(props) => <MemoryRouter {...props} history={history} />}
|
||||
/>
|
||||
</AppBaseProviders>
|
||||
|
||||
@@ -86,7 +86,6 @@ export function AppBaseProviders(
|
||||
|
||||
export function AppInterface(props: {
|
||||
children?: JSX.Element
|
||||
defaultServer?: ServerConnection.Key
|
||||
canonicalLocalServer?: ServerConnection.Key
|
||||
servers?: Array<ServerConnection.Any>
|
||||
router?: Component<BaseRouterProps>
|
||||
@@ -108,11 +107,7 @@ export function AppInterface(props: {
|
||||
)
|
||||
|
||||
return (
|
||||
<ServersProvider
|
||||
defaultServer={props.defaultServer}
|
||||
canonicalLocalServer={props.canonicalLocalServer}
|
||||
servers={props.servers}
|
||||
>
|
||||
<ServersProvider canonicalLocalServer={props.canonicalLocalServer} servers={props.servers}>
|
||||
<SettingsProvider>
|
||||
<Dynamic component={props.router ?? Router} root={Root}>
|
||||
<AppRoutes />
|
||||
|
||||
@@ -16,7 +16,7 @@ import pkg from "../package.json"
|
||||
import { ServerConnection } from "@/runtime/server/registry"
|
||||
|
||||
const getLocale = () => {
|
||||
if (typeof navigator !== "object") return "en" as const
|
||||
if (typeof navigator === "undefined") return "en" as const
|
||||
const languages = navigator.languages?.length ? navigator.languages : [navigator.language]
|
||||
|
||||
for (const language of languages) {
|
||||
@@ -101,7 +101,6 @@ if (root instanceof HTMLElement && root.dataset.opencodeMounted === undefined) {
|
||||
<PlatformProvider value={web.platform}>
|
||||
<AppBaseProviders locale={locale}>
|
||||
<AppInterface
|
||||
defaultServer={web.defaultServerUrl ? ServerConnection.Key.make(web.defaultServerUrl) : undefined}
|
||||
canonicalLocalServer={server ? ServerConnection.key(server) : undefined}
|
||||
servers={server ? [server] : []}
|
||||
>
|
||||
|
||||
@@ -86,10 +86,6 @@ export function createHomeProjectsController(home: HomeController) {
|
||||
const key = ServerConnection.key(conn)
|
||||
setState("collapsed", key, !state().collapsed[key])
|
||||
},
|
||||
canDefault: serverManagement.defaults.available,
|
||||
defaultKey: serverManagement.defaults.key,
|
||||
setDefault: (conn: ServerConnection.Any | undefined) =>
|
||||
serverManagement.defaults.set(conn ? ServerConnection.key(conn) : null),
|
||||
canRemove: (conn: ServerConnection.Any) => serverManagement.connection.canRemove(ServerConnection.key(conn)),
|
||||
remove: (conn: ServerConnection.Any) => serverManagement.connection.remove(ServerConnection.key(conn)),
|
||||
canHide: (conn: ServerConnection.Any) => serverManagement.connection.canHide(ServerConnection.key(conn)),
|
||||
|
||||
@@ -19,8 +19,6 @@ export function HomeProjects(props: {
|
||||
serverHealth={props.projects.server.health}
|
||||
projectsForServer={props.projects.server.projects}
|
||||
collapsed={props.projects.server.collapsed}
|
||||
canDefaultServer={props.projects.server.canDefault()}
|
||||
defaultServerKey={props.projects.server.defaultKey()}
|
||||
canRevealProject={props.projects.project.canReveal}
|
||||
unseenCount={props.projects.project.unseenCount}
|
||||
onWheel={props.scroll.viewport.containWheel}
|
||||
@@ -29,7 +27,6 @@ export function HomeProjects(props: {
|
||||
onAuthenticateServer={props.projects.server.authenticate}
|
||||
onToggleCollapsed={props.projects.server.toggleCollapsed}
|
||||
onEditServer={props.projects.server.edit}
|
||||
onSetDefaultServer={props.projects.server.setDefault}
|
||||
canRemoveServer={props.projects.server.canRemove}
|
||||
onRemoveServer={props.projects.server.remove}
|
||||
canHideServer={props.projects.server.canHide}
|
||||
|
||||
@@ -52,8 +52,6 @@ export type HomeProjectsViewProps = {
|
||||
serverHealth: (server: ServerConnection.Any) => ServerHealth | undefined
|
||||
projectsForServer: (server: ServerConnection.Any) => LocalProject[]
|
||||
collapsed: (server: ServerConnection.Any) => boolean
|
||||
canDefaultServer: boolean
|
||||
defaultServerKey: ServerConnection.Key | null | undefined
|
||||
canRevealProject: (server: ServerConnection.Any) => boolean
|
||||
unseenCount: (server: ServerConnection.Any, project: LocalProject) => number
|
||||
onWheel: (event: WheelEvent) => void
|
||||
@@ -62,7 +60,6 @@ export type HomeProjectsViewProps = {
|
||||
onAuthenticateServer?: (server: ServerConnection.Any) => void
|
||||
onToggleCollapsed: (server: ServerConnection.Any) => void
|
||||
onEditServer: (server: ServerConnection.Http) => void
|
||||
onSetDefaultServer: (server: ServerConnection.Any | undefined) => void
|
||||
canRemoveServer: (server: ServerConnection.Any) => boolean
|
||||
onRemoveServer: (server: ServerConnection.Any) => void
|
||||
canHideServer: (server: ServerConnection.Any) => boolean
|
||||
@@ -347,12 +344,9 @@ function HomeServerRow(props: {
|
||||
language: HomeProjectsViewProps["language"]
|
||||
projectsForServer: HomeProjectsViewProps["projectsForServer"]
|
||||
contextMenuOpen: HomeProjectsContextMenuProps["contextMenuOpen"]
|
||||
canDefaultServer: HomeProjectsViewProps["canDefaultServer"]
|
||||
defaultServerKey: HomeProjectsViewProps["defaultServerKey"]
|
||||
onFocusServer: HomeProjectsViewProps["onFocusServer"]
|
||||
onToggleCollapsed: HomeProjectsViewProps["onToggleCollapsed"]
|
||||
onEditServer: HomeProjectsViewProps["onEditServer"]
|
||||
onSetDefaultServer: HomeProjectsViewProps["onSetDefaultServer"]
|
||||
canRemoveServer: HomeProjectsViewProps["canRemoveServer"]
|
||||
onRemoveServer: HomeProjectsViewProps["onRemoveServer"]
|
||||
canHideServer: HomeProjectsViewProps["canHideServer"]
|
||||
@@ -470,13 +464,9 @@ function HomeServerRow(props: {
|
||||
<ServerRowMenuView
|
||||
server={props.server}
|
||||
labels={serverMenuLabels(props.language)}
|
||||
canDefault={props.canDefaultServer}
|
||||
isDefault={props.defaultServerKey === ServerConnection.key(props.server)}
|
||||
canRemove={props.canRemoveServer(props.server)}
|
||||
canHide={props.canHideServer(props.server)}
|
||||
onEdit={props.onEditServer}
|
||||
onSetDefault={() => props.onSetDefaultServer(props.server)}
|
||||
onRemoveDefault={() => props.onSetDefaultServer(undefined)}
|
||||
onRemove={() => props.onRemoveServer(props.server)}
|
||||
onHide={() => props.onHideServer(props.server)}
|
||||
open={props.contextMenuOpen(contextMenuID())}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
createCommandPaletteCommandEntry,
|
||||
createServerSessionEntries,
|
||||
type CommandPaletteEntry,
|
||||
type PaletteHighlight,
|
||||
} from "@/shell/commands/palette"
|
||||
import { CommandPaletteView, matchesCommandPaletteEntry } from "@/shell/commands/dialog"
|
||||
|
||||
@@ -20,7 +21,7 @@ export function HomeCommandPalette(props: {
|
||||
const global = useGlobal()
|
||||
const language = useLanguage()
|
||||
const server = global.ensureServerCtx(props.server)
|
||||
const state = { cleanup: undefined as (() => void) | void, committed: false }
|
||||
const state: PaletteHighlight = { cleanup: undefined, committed: false }
|
||||
|
||||
const commandEntries = createMemo(() => {
|
||||
const category = language.t("palette.group.commands")
|
||||
@@ -36,6 +37,7 @@ export function HomeCommandPalette(props: {
|
||||
get: (sessionID, signal) => server.sdk.api.session.get({ sessionID }, { signal }),
|
||||
untitled: () => language.t("command.session.new"),
|
||||
category: () => language.t("command.category.session"),
|
||||
recentCategory: () => language.t("palette.group.recentSessions"),
|
||||
})
|
||||
|
||||
const highlight = (item: CommandPaletteEntry | undefined) => {
|
||||
|
||||
@@ -70,7 +70,7 @@ export function NewSessionView(props: {
|
||||
<PromptProjectAddButton controller={props.project} />
|
||||
</Show>
|
||||
<Show when={props.project.selected()}>
|
||||
<div class="flex min-h-7 min-w-0 flex-col items-center justify-center gap-0 text-v2-text-text-faint sm:flex-row">
|
||||
<div class="flex min-h-7 min-w-0 flex-row flex-wrap items-center justify-center gap-0 text-v2-text-text-faint">
|
||||
<PromptProjectSelector controller={props.project} placement="bottom" />
|
||||
<Show
|
||||
when={props.workspace.bar.visible()}
|
||||
|
||||
@@ -327,7 +327,7 @@ export function PromptWorkspaceSelector(props: {
|
||||
setTimeout(() => requestAnimationFrame(() => branchSearchInput?.focus({ preventScroll: true })))
|
||||
}}
|
||||
>
|
||||
<div class="flex h-7 shrink-0 items-center gap-2 rounded-sm pl-3 pr-2.5 text-v2-icon-icon-muted">
|
||||
<div class="flex h-7 shrink-0 items-center gap-2 rounded-sm pl-3 pr-1 text-v2-icon-icon-muted">
|
||||
<Icon name="magnifying-glass" size="small" class="shrink-0" />
|
||||
<input
|
||||
ref={(element) => {
|
||||
@@ -355,7 +355,7 @@ export function PromptWorkspaceSelector(props: {
|
||||
<Show when={search.branches.trim()}>
|
||||
<button
|
||||
type="button"
|
||||
class="flex size-5 items-center justify-center rounded-sm text-v2-icon-icon-muted hover:bg-v2-overlay-simple-overlay-hover"
|
||||
class="flex size-5 items-center justify-center rounded-sm bg-transparent text-v2-icon-icon-faint transition-colors hover:text-v2-icon-icon-base focus-visible:text-v2-icon-icon-base active:text-v2-icon-icon-base"
|
||||
onPointerDown={(event) => event.preventDefault()}
|
||||
onClick={() => {
|
||||
setSearch("branches", "")
|
||||
@@ -363,7 +363,7 @@ export function PromptWorkspaceSelector(props: {
|
||||
}}
|
||||
aria-label={language.t("common.clear")}
|
||||
>
|
||||
<Icon name="close-small" size="small" />
|
||||
<Icon name="circle-xmark" />
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Button } from "@opencode/ui/button"
|
||||
import { Dialog, DialogBody, DialogHeader, DialogTitleGroup } from "@opencode/ui/dialog"
|
||||
import { Icon } from "@opencode/ui/icon"
|
||||
import { IconButton } from "@opencode/ui/icon-button"
|
||||
import { Switch } from "@opencode/ui/switch"
|
||||
import { TextInput } from "@opencode/ui/text-input"
|
||||
import { useFilteredList } from "@opencode/ui/hooks"
|
||||
@@ -24,7 +22,7 @@ export const DialogManageModels: Component = () => {
|
||||
const local = useLocal()
|
||||
const language = useLanguage()
|
||||
const dialog = useDialog()
|
||||
const [store, setStore] = createStore({ collapsed: {} as Record<string, boolean> })
|
||||
const [store, setStore] = createStore<{ collapsed: Record<string, boolean> }>({ collapsed: {} })
|
||||
const directory = () => decode64(local.slug())
|
||||
|
||||
const handleConnectProvider = () => {
|
||||
@@ -107,33 +105,24 @@ export const DialogManageModels: Component = () => {
|
||||
</DialogHeader>
|
||||
<DialogBody class="flex min-h-0 flex-1 flex-col">
|
||||
<div class="px-4 pt-px pb-3">
|
||||
<div class="relative">
|
||||
<TextInput
|
||||
type="search"
|
||||
appearance="base"
|
||||
class="!w-full self-stretch"
|
||||
value={list.filter()}
|
||||
onInput={(event) => list.onInput(event.currentTarget.value)}
|
||||
placeholder={language.t("dialog.model.search.placeholder")}
|
||||
spellcheck={false}
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
autofocus
|
||||
aria-label={language.t("dialog.model.search.placeholder")}
|
||||
/>
|
||||
<Show when={list.filter()}>
|
||||
<IconButton
|
||||
type="button"
|
||||
variant="ghost-muted"
|
||||
size="small"
|
||||
class="settings-tab-search-clear"
|
||||
icon={<Icon name="close" size="large" class="text-v2-icon-icon-muted" />}
|
||||
onClick={() => list.clear()}
|
||||
aria-label={language.t("common.clear")}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
<TextInput
|
||||
type="search"
|
||||
appearance="base"
|
||||
class="!w-full self-stretch"
|
||||
value={list.filter()}
|
||||
onInput={(event) => list.onInput(event.currentTarget.value)}
|
||||
placeholder={language.t("dialog.model.search.placeholder")}
|
||||
spellcheck={false}
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
autofocus
|
||||
aria-label={language.t("dialog.model.search.placeholder")}
|
||||
showClearButton={!!list.filter()}
|
||||
clearIcon="circle-xmark"
|
||||
clearLabel={language.t("common.clear")}
|
||||
onClearClick={() => list.clear()}
|
||||
/>
|
||||
</div>
|
||||
<div data-slot="manage-models-scroll" class="relative min-h-0 flex-1">
|
||||
<div class="settings-panel settings-models h-full px-4 pt-1 pb-4">
|
||||
|
||||
@@ -9,7 +9,6 @@ import { Button } from "@opencode/ui/button"
|
||||
import { Badge } from "@opencode/ui/badge"
|
||||
import { Dialog, DialogBody, DialogHeader, DialogTitleGroup } from "@opencode/ui/dialog"
|
||||
import { Icon } from "@opencode/ui/icon"
|
||||
import { IconButton } from "@opencode/ui/icon-button"
|
||||
import { ScrollView } from "@opencode/ui/scroll-view"
|
||||
import { Tooltip } from "@opencode/ui/tooltip"
|
||||
import { Menu } from "@opencode/ui/menu"
|
||||
@@ -186,55 +185,46 @@ const ModelList: Component<{
|
||||
return (
|
||||
<div class="flex min-h-0 flex-1 flex-col">
|
||||
<div data-slot="model-selector-search" class="shrink-0 pt-px pb-3" classList={{ "px-4": !props.mobile }}>
|
||||
<div class="relative">
|
||||
<TextInput
|
||||
type="search"
|
||||
appearance="base"
|
||||
class="!w-full self-stretch"
|
||||
placeholder={language.t("dialog.model.search.placeholder")}
|
||||
value={store.search}
|
||||
autofocus={!props.mobile}
|
||||
spellcheck={false}
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
onInput={(event) => setSearch(event.currentTarget.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.altKey || event.metaKey) return
|
||||
<TextInput
|
||||
type="search"
|
||||
appearance="base"
|
||||
class="!w-full self-stretch"
|
||||
placeholder={language.t("dialog.model.search.placeholder")}
|
||||
value={store.search}
|
||||
autofocus={!props.mobile}
|
||||
spellcheck={false}
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
onInput={(event) => setSearch(event.currentTarget.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.altKey || event.metaKey) return
|
||||
|
||||
if (event.key === "ArrowDown") {
|
||||
event.preventDefault()
|
||||
moveActive(1)
|
||||
if (event.key === "ArrowDown") {
|
||||
event.preventDefault()
|
||||
moveActive(1)
|
||||
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === "ArrowUp") {
|
||||
event.preventDefault()
|
||||
moveActive(-1)
|
||||
if (event.key === "ArrowUp") {
|
||||
event.preventDefault()
|
||||
moveActive(-1)
|
||||
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === "Enter" && !event.isComposing) {
|
||||
event.preventDefault()
|
||||
selectActive()
|
||||
}
|
||||
}}
|
||||
aria-label={language.t("dialog.model.search.placeholder")}
|
||||
/>
|
||||
<Show when={store.search}>
|
||||
<IconButton
|
||||
type="button"
|
||||
variant="ghost-muted"
|
||||
size="small"
|
||||
class="settings-tab-search-clear"
|
||||
icon={<Icon name="close" size="large" class="text-v2-icon-icon-muted" />}
|
||||
onClick={() => setSearch("")}
|
||||
aria-label={language.t("common.clear")}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
if (event.key === "Enter" && !event.isComposing) {
|
||||
event.preventDefault()
|
||||
selectActive()
|
||||
}
|
||||
}}
|
||||
aria-label={language.t("dialog.model.search.placeholder")}
|
||||
showClearButton={!!store.search}
|
||||
clearIcon="circle-xmark"
|
||||
clearLabel={language.t("common.clear")}
|
||||
onClearClick={() => setSearch("")}
|
||||
/>
|
||||
</div>
|
||||
<div class="relative min-h-0" classList={{ "flex-1": !props.mobile }}>
|
||||
<div
|
||||
@@ -613,7 +603,7 @@ export function ModelSelectorPopoverView(props: {
|
||||
onCloseAutoFocus={dismiss.onCloseAutoFocus}
|
||||
>
|
||||
<div class="flex flex-col p-0.5">
|
||||
<div class="flex h-7 items-center gap-2 rounded-sm pl-3 pr-2.5 text-v2-icon-icon-muted">
|
||||
<div class="flex h-7 items-center gap-2 rounded-sm pl-3 pr-1 text-v2-icon-icon-muted">
|
||||
<Icon name="magnifying-glass" size="small" class="shrink-0" />
|
||||
<input
|
||||
ref={(el) => (searchRef = el)}
|
||||
@@ -663,12 +653,12 @@ export function ModelSelectorPopoverView(props: {
|
||||
<Show when={store.search.trim()}>
|
||||
<button
|
||||
type="button"
|
||||
class="flex size-5 items-center justify-center rounded-sm text-v2-icon-icon-muted hover:bg-v2-overlay-simple-overlay-hover"
|
||||
class="flex size-5 items-center justify-center rounded-sm bg-transparent text-v2-icon-icon-faint transition-colors hover:text-v2-icon-icon-base focus-visible:text-v2-icon-icon-base active:text-v2-icon-icon-base"
|
||||
onPointerDown={(event) => event.preventDefault()}
|
||||
onClick={() => setSearch("")}
|
||||
aria-label={language.t("common.clear")}
|
||||
>
|
||||
<Icon name="close" size="small" />
|
||||
<Icon name="circle-xmark" />
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -285,9 +285,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "የይለፍ ቃል",
|
||||
"dialog.server.edit.title": "አገልጋይ አርትዕ",
|
||||
"dialog.server.menu.edit": "አርትዕ",
|
||||
"dialog.server.menu.default": "ነባሪ አዘጋጅ",
|
||||
"dialog.server.menu.defaultRemove": "ነባሪውን አስወግድ",
|
||||
"dialog.server.status.default": "ነባሪ",
|
||||
"dialog.project.edit.title": "ፕሮጀክት አርትዕ",
|
||||
"dialog.project.edit.icon": "አዶ",
|
||||
"dialog.project.edit.icon.alt": "የፕሮጀክት አዶ",
|
||||
|
||||
@@ -297,9 +297,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "كلمة المرور (اختياري)",
|
||||
"dialog.server.edit.title": "تحرير الخادم",
|
||||
"dialog.server.menu.edit": "تعديل",
|
||||
"dialog.server.menu.default": "تعيين كافتراضي",
|
||||
"dialog.server.menu.defaultRemove": "إزالة الافتراضي",
|
||||
"dialog.server.status.default": "افتراضي",
|
||||
"dialog.project.edit.title": "تحرير المشروع",
|
||||
"dialog.project.edit.icon": "أيقونة",
|
||||
"dialog.project.edit.icon.alt": "أيقونة المشروع",
|
||||
|
||||
@@ -288,9 +288,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "parol",
|
||||
"dialog.server.edit.title": "Serveri redaktə et",
|
||||
"dialog.server.menu.edit": "Redaktə et",
|
||||
"dialog.server.menu.default": "Standart olaraq təyin et",
|
||||
"dialog.server.menu.defaultRemove": "Standartı sil",
|
||||
"dialog.server.status.default": "Standart",
|
||||
"dialog.project.edit.title": "Layihəni redaktə et",
|
||||
"dialog.project.edit.icon": "İkon",
|
||||
"dialog.project.edit.icon.alt": "Layihə ikonu",
|
||||
|
||||
@@ -287,9 +287,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "парола",
|
||||
"dialog.server.edit.title": "Редактиране на сървъра",
|
||||
"dialog.server.menu.edit": "Редактиране",
|
||||
"dialog.server.menu.default": "Задайте по подразбиране",
|
||||
"dialog.server.menu.defaultRemove": "Премахване на подразбиране",
|
||||
"dialog.server.status.default": "По подразбиране",
|
||||
"dialog.project.edit.title": "Редактиране на проекта",
|
||||
"dialog.project.edit.icon": "Икона",
|
||||
"dialog.project.edit.icon.alt": "Икона на проекта",
|
||||
|
||||
@@ -290,9 +290,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "পাসওয়ার্ড",
|
||||
"dialog.server.edit.title": "সার্ভার সম্পাদনা করুন",
|
||||
"dialog.server.menu.edit": "সম্পাদনা করুন",
|
||||
"dialog.server.menu.default": "ডিফল্ট হিসেবে সেট করুন",
|
||||
"dialog.server.menu.defaultRemove": "ডিফল্ট সরান",
|
||||
"dialog.server.status.default": "ডিফল্ট",
|
||||
"dialog.project.edit.title": "প্রকল্প সম্পাদনা করুন",
|
||||
"dialog.project.edit.icon": "আইকন",
|
||||
"dialog.project.edit.icon.alt": "প্রকল্প আইকন",
|
||||
|
||||
@@ -508,9 +508,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "Senha (opcional)",
|
||||
"dialog.server.edit.title": "Editar servidor",
|
||||
"dialog.server.menu.edit": "Editar",
|
||||
"dialog.server.menu.default": "Definir como padrão",
|
||||
"dialog.server.menu.defaultRemove": "Remover padrão",
|
||||
"dialog.server.status.default": "Padrão",
|
||||
"dialog.project.edit.title": "Editar projeto",
|
||||
"dialog.project.edit.icon": "Ícone",
|
||||
"dialog.project.edit.icon.alt": "Ícone do projeto",
|
||||
|
||||
@@ -312,9 +312,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Uredi server",
|
||||
|
||||
"dialog.server.menu.edit": "Uredi",
|
||||
"dialog.server.menu.default": "Postavi kao podrazumijevano",
|
||||
"dialog.server.menu.defaultRemove": "Ukloni podrazumijevano",
|
||||
"dialog.server.status.default": "Podrazumijevano",
|
||||
|
||||
"dialog.project.edit.title": "Uredi projekat",
|
||||
"dialog.project.edit.icon": "Ikonica",
|
||||
|
||||
@@ -286,9 +286,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "contrasenya",
|
||||
"dialog.server.edit.title": "Edita el servidor",
|
||||
"dialog.server.menu.edit": "Edita",
|
||||
"dialog.server.menu.default": "Estableix com a predeterminat",
|
||||
"dialog.server.menu.defaultRemove": "Elimina el valor predeterminat",
|
||||
"dialog.server.status.default": "Per defecte",
|
||||
"dialog.project.edit.title": "Edita el projecte",
|
||||
"dialog.project.edit.icon": "Icona",
|
||||
"dialog.project.edit.icon.alt": "Icona del projecte",
|
||||
|
||||
@@ -286,9 +286,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "heslo",
|
||||
"dialog.server.edit.title": "Upravit server",
|
||||
"dialog.server.menu.edit": "Upravit",
|
||||
"dialog.server.menu.default": "Nastavit jako výchozí",
|
||||
"dialog.server.menu.defaultRemove": "Odebrat výchozí",
|
||||
"dialog.server.status.default": "Výchozí",
|
||||
"dialog.project.edit.title": "Upravit projekt",
|
||||
"dialog.project.edit.icon": "ikona",
|
||||
"dialog.project.edit.icon.alt": "Ikona projektu",
|
||||
|
||||
@@ -446,9 +446,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Rediger server",
|
||||
|
||||
"dialog.server.menu.edit": "Rediger",
|
||||
"dialog.server.menu.default": "Sæt som standard",
|
||||
"dialog.server.menu.defaultRemove": "Fjern som standard",
|
||||
"dialog.server.status.default": "Standard",
|
||||
|
||||
"dialog.project.edit.title": "Rediger projekt",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
|
||||
@@ -442,9 +442,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "Passwort (optional)",
|
||||
"dialog.server.edit.title": "Server bearbeiten",
|
||||
"dialog.server.menu.edit": "Bearbeiten",
|
||||
"dialog.server.menu.default": "Als Standard festlegen",
|
||||
"dialog.server.menu.defaultRemove": "Standard entfernen",
|
||||
"dialog.server.status.default": "Standard",
|
||||
"dialog.project.edit.title": "Projekt bearbeiten",
|
||||
"dialog.project.edit.icon": "Symbol",
|
||||
"dialog.project.edit.icon.alt": "Projektsymbol",
|
||||
|
||||
@@ -292,9 +292,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "ޕާސްވަރޑް",
|
||||
"dialog.server.edit.title": "އެޑިޓް ސަރވަރ",
|
||||
"dialog.server.menu.edit": "ބަދަލު ގެނައުން",
|
||||
"dialog.server.menu.default": "ޑިފޯލްޓް ގޮތުގައި ސެޓްކުރުން",
|
||||
"dialog.server.menu.defaultRemove": "ޑިފޯލްޓް ނައްތާލާށެވެ",
|
||||
"dialog.server.status.default": "ޑީފޯލްޓް",
|
||||
"dialog.project.edit.title": "އެޑިޓް ޕްރޮޖެކްޓް",
|
||||
"dialog.project.edit.icon": "އައިކޮން",
|
||||
"dialog.project.edit.icon.alt": "ޕްރޮޖެކްޓް އައިކޮން",
|
||||
|
||||
@@ -298,9 +298,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "གསང༌ཨང",
|
||||
"dialog.server.edit.title": "སར་བར་ཞུན་དག་འབད།",
|
||||
"dialog.server.menu.edit": "ཞུན༌དག",
|
||||
"dialog.server.menu.default": "སྔོན་སྒྲིག་སྦེ་གཞི་སྒྲིག་འབད།",
|
||||
"dialog.server.menu.defaultRemove": "སྔོན་སྒྲིག་རྩ་བསྐྲད་གཏང་།",
|
||||
"dialog.server.status.default": "འཐུས་ཤོར",
|
||||
"dialog.project.edit.title": "ཞུན་དག་ལས་གཞི།",
|
||||
"dialog.project.edit.icon": "ངོས་དཔར།",
|
||||
"dialog.project.edit.icon.alt": "ལས་འགུལ་ངོས་དཔར།",
|
||||
|
||||
@@ -288,9 +288,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "κωδικός πρόσβασης",
|
||||
"dialog.server.edit.title": "Επεξεργασία διακομιστή",
|
||||
"dialog.server.menu.edit": "Επεξεργασία",
|
||||
"dialog.server.menu.default": "Ορισμός ως προεπιλογή",
|
||||
"dialog.server.menu.defaultRemove": "Κατάργηση προεπιλογής",
|
||||
"dialog.server.status.default": "Προεπιλογή",
|
||||
"dialog.project.edit.title": "Επεξεργασία έργου",
|
||||
"dialog.project.edit.icon": "Εικονίδιο",
|
||||
"dialog.project.edit.icon.alt": "Εικονίδιο έργου",
|
||||
|
||||
@@ -86,6 +86,7 @@ export const dict = {
|
||||
"palette.empty": "No results found",
|
||||
"palette.group.commands": "Commands",
|
||||
"palette.group.files": "Files",
|
||||
"palette.group.recentSessions": "Recent sessions",
|
||||
|
||||
"dialog.provider.search.placeholder": "Search providers",
|
||||
"dialog.provider.empty": "No providers found",
|
||||
@@ -377,12 +378,9 @@ export const dict = {
|
||||
"Your sign-in for this server is no longer valid. Paste a new pairing link, or enter the server password.",
|
||||
|
||||
"dialog.server.menu.edit": "Edit",
|
||||
"dialog.server.menu.default": "Set as default",
|
||||
"dialog.server.menu.defaultRemove": "Remove default",
|
||||
"dialog.server.menu.remove": "Remove",
|
||||
"dialog.server.menu.hide": "Hide from project list",
|
||||
"dialog.server.menu.show": "Show in project list",
|
||||
"dialog.server.status.default": "Default",
|
||||
|
||||
"server.row.incompatible":
|
||||
"This server is running OpenCode {{version}}, which isn't compatible with this app. Upgrade it to OpenCode V2 to continue.",
|
||||
|
||||
@@ -535,9 +535,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Editar servidor",
|
||||
|
||||
"dialog.server.menu.edit": "Editar",
|
||||
"dialog.server.menu.default": "Establecer como predeterminado",
|
||||
"dialog.server.menu.defaultRemove": "Quitar como predeterminado",
|
||||
"dialog.server.status.default": "Predeterminado",
|
||||
|
||||
"dialog.project.edit.title": "Editar proyecto",
|
||||
"dialog.project.edit.icon": "Icono",
|
||||
|
||||
@@ -285,9 +285,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "parool",
|
||||
"dialog.server.edit.title": "Muuda serverit",
|
||||
"dialog.server.menu.edit": "Muuda",
|
||||
"dialog.server.menu.default": "Määra vaikeväärtuseks",
|
||||
"dialog.server.menu.defaultRemove": "Eemalda vaikeseade",
|
||||
"dialog.server.status.default": "Vaikimisi",
|
||||
"dialog.project.edit.title": "Redigeeri projekti",
|
||||
"dialog.project.edit.icon": "Ikoon",
|
||||
"dialog.project.edit.icon.alt": "Projekti ikoon",
|
||||
|
||||
@@ -295,9 +295,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "رمز عبور",
|
||||
"dialog.server.edit.title": "ویرایش سرور",
|
||||
"dialog.server.menu.edit": "ویرایش کنید",
|
||||
"dialog.server.menu.default": "به عنوان پیش فرض تنظیم کنید",
|
||||
"dialog.server.menu.defaultRemove": "حذف پیش فرض",
|
||||
"dialog.server.status.default": "پیش فرض",
|
||||
"dialog.project.edit.title": "ویرایش پروژه",
|
||||
"dialog.project.edit.icon": "نماد",
|
||||
"dialog.project.edit.icon.alt": "نماد پروژه",
|
||||
|
||||
@@ -424,9 +424,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "salasana",
|
||||
"dialog.server.edit.title": "Muokkaa palvelinta",
|
||||
"dialog.server.menu.edit": "Muokkaa",
|
||||
"dialog.server.menu.default": "Aseta oletukseksi",
|
||||
"dialog.server.menu.defaultRemove": "Poista oletuspalvelin",
|
||||
"dialog.server.status.default": "Oletus",
|
||||
"dialog.project.edit.title": "Muokkaa projektia",
|
||||
"dialog.project.edit.icon": "Kuvake",
|
||||
"dialog.project.edit.icon.alt": "Projektikuvake",
|
||||
|
||||
@@ -285,9 +285,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "loyniorð",
|
||||
"dialog.server.edit.title": "Rætta ambætara",
|
||||
"dialog.server.menu.edit": "Rætta",
|
||||
"dialog.server.menu.default": "Set sum forsett",
|
||||
"dialog.server.menu.defaultRemove": "Strika forsett",
|
||||
"dialog.server.status.default": "Forsett",
|
||||
"dialog.project.edit.title": "Rætta verkætlan",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
"dialog.project.edit.icon.alt": "Verkætlanarmerki",
|
||||
|
||||
@@ -516,9 +516,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "Mot de passe (optionnel)",
|
||||
"dialog.server.edit.title": "Modifier le serveur",
|
||||
"dialog.server.menu.edit": "Modifier",
|
||||
"dialog.server.menu.default": "Définir par défaut",
|
||||
"dialog.server.menu.defaultRemove": "Ne plus utiliser par défaut",
|
||||
"dialog.server.status.default": "Défaut",
|
||||
"dialog.project.edit.title": "Modifier le projet",
|
||||
"dialog.project.edit.icon": "Icône",
|
||||
"dialog.project.edit.icon.alt": "Icône du projet",
|
||||
|
||||
@@ -294,9 +294,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "סיסמה",
|
||||
"dialog.server.edit.title": "ערוך שרת",
|
||||
"dialog.server.menu.edit": "ערוך",
|
||||
"dialog.server.menu.default": "הגדר כברירת מחדל",
|
||||
"dialog.server.menu.defaultRemove": "הסר את ברירת המחדל",
|
||||
"dialog.server.status.default": "ברירת מחדל",
|
||||
"dialog.project.edit.title": "ערוך פרויקט",
|
||||
"dialog.project.edit.icon": "סמל",
|
||||
"dialog.project.edit.icon.alt": "סמל פרויקט",
|
||||
|
||||
@@ -292,9 +292,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "पासवर्ड",
|
||||
"dialog.server.edit.title": "सर्वर संपादित करें",
|
||||
"dialog.server.menu.edit": "संपादित करें",
|
||||
"dialog.server.menu.default": "डिफाल्ट के रूप में सेट",
|
||||
"dialog.server.menu.defaultRemove": "डिफ़ॉल्ट हटाएँ",
|
||||
"dialog.server.status.default": "डिफ़ॉल्ट",
|
||||
"dialog.project.edit.title": "प्रोजेक्ट संपादित करें",
|
||||
"dialog.project.edit.icon": "आइकन",
|
||||
"dialog.project.edit.icon.alt": "प्रोजेक्ट आइकन",
|
||||
|
||||
@@ -298,9 +298,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "lozinka",
|
||||
"dialog.server.edit.title": "Uredi poslužitelj",
|
||||
"dialog.server.menu.edit": "Uredi",
|
||||
"dialog.server.menu.default": "Postavi kao zadano",
|
||||
"dialog.server.menu.defaultRemove": "Ukloni zadano",
|
||||
"dialog.server.status.default": "Zadano",
|
||||
"dialog.project.edit.title": "Uredi projekt",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
"dialog.project.edit.icon.alt": "Ikona projekta",
|
||||
|
||||
@@ -298,9 +298,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "jelszó",
|
||||
"dialog.server.edit.title": "Szerver szerkesztése",
|
||||
"dialog.server.menu.edit": "Szerkesztés",
|
||||
"dialog.server.menu.default": "Beállítás alapértelmezettként",
|
||||
"dialog.server.menu.defaultRemove": "Alapértelmezés eltávolítása",
|
||||
"dialog.server.status.default": "Alapértelmezett",
|
||||
"dialog.project.edit.title": "Projekt szerkesztése",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
"dialog.project.edit.icon.alt": "Projekt ikonra",
|
||||
|
||||
@@ -287,9 +287,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "գաղտնաբառ",
|
||||
"dialog.server.edit.title": "Խմբագրել սերվերը",
|
||||
"dialog.server.menu.edit": "Խմբագրել",
|
||||
"dialog.server.menu.default": "Սահմանել որպես լռելյայն",
|
||||
"dialog.server.menu.defaultRemove": "Հեռացնել լռելյայն",
|
||||
"dialog.server.status.default": "Լռելյայն",
|
||||
"dialog.project.edit.title": "Խմբագրել նախագիծը",
|
||||
"dialog.project.edit.icon": "Պատկերակ",
|
||||
"dialog.project.edit.icon.alt": "Նախագծի պատկերակ",
|
||||
|
||||
@@ -323,9 +323,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Sunting server",
|
||||
|
||||
"dialog.server.menu.edit": "Sunting",
|
||||
"dialog.server.menu.default": "Tetapkan sebagai bawaan",
|
||||
"dialog.server.menu.defaultRemove": "Hapus bawaan",
|
||||
"dialog.server.status.default": "Bawaan",
|
||||
|
||||
"dialog.project.edit.title": "Sunting proyek",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
|
||||
@@ -298,9 +298,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "lykilorð",
|
||||
"dialog.server.edit.title": "Breyta miðlara",
|
||||
"dialog.server.menu.edit": "Breyta",
|
||||
"dialog.server.menu.default": "Stillt sem sjálfgefið",
|
||||
"dialog.server.menu.defaultRemove": "Fjarlægja sjálfgefið",
|
||||
"dialog.server.status.default": "Sjálfgefið",
|
||||
"dialog.project.edit.title": "Breyta verkefni",
|
||||
"dialog.project.edit.icon": "Táknmynd",
|
||||
"dialog.project.edit.icon.alt": "Verkefnistákn",
|
||||
|
||||
@@ -446,9 +446,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "password",
|
||||
"dialog.server.edit.title": "Modifica server",
|
||||
"dialog.server.menu.edit": "Modifica",
|
||||
"dialog.server.menu.default": "Imposta come predefinito",
|
||||
"dialog.server.menu.defaultRemove": "Rimuovi predefinito",
|
||||
"dialog.server.status.default": "Predefinito",
|
||||
"dialog.project.edit.title": "Modifica progetto",
|
||||
"dialog.project.edit.icon": "Icona",
|
||||
"dialog.project.edit.icon.alt": "Icona del progetto",
|
||||
|
||||
@@ -296,9 +296,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "パスワード (オプション)",
|
||||
"dialog.server.edit.title": "サーバーを編集",
|
||||
"dialog.server.menu.edit": "編集",
|
||||
"dialog.server.menu.default": "デフォルトに設定",
|
||||
"dialog.server.menu.defaultRemove": "デフォルト設定を解除",
|
||||
"dialog.server.status.default": "デフォルト",
|
||||
|
||||
"dialog.project.edit.title": "プロジェクトを編集",
|
||||
"dialog.project.edit.icon": "アイコン",
|
||||
|
||||
@@ -286,9 +286,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "პაროლი",
|
||||
"dialog.server.edit.title": "სერვერის რედაქტირება",
|
||||
"dialog.server.menu.edit": "რედაქტირება",
|
||||
"dialog.server.menu.default": "დაყენება ნაგულისხმევად",
|
||||
"dialog.server.menu.defaultRemove": "ნაგულისხმევის ამოღება",
|
||||
"dialog.server.status.default": "ნაგულისხმევი",
|
||||
"dialog.project.edit.title": "პროექტის რედაქტირება",
|
||||
"dialog.project.edit.icon": "ხატულა",
|
||||
"dialog.project.edit.icon.alt": "პროექტის ხატულა",
|
||||
|
||||
@@ -293,9 +293,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "ពាក្យសម្ងាត់",
|
||||
"dialog.server.edit.title": "កែសម្រួលម៉ាស៊ីនមេ",
|
||||
"dialog.server.menu.edit": "កែសម្រួល",
|
||||
"dialog.server.menu.default": "កំណត់ជាលំនាំដើម",
|
||||
"dialog.server.menu.defaultRemove": "លុបលំនាំដើមចេញ",
|
||||
"dialog.server.status.default": "លំនាំដើម",
|
||||
"dialog.project.edit.title": "កែសម្រួលគម្រោង",
|
||||
"dialog.project.edit.icon": "រូបតំណាង",
|
||||
"dialog.project.edit.icon.alt": "រូបតំណាងគម្រោង",
|
||||
|
||||
@@ -218,9 +218,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "비밀번호 (선택 사항)",
|
||||
"dialog.server.edit.title": "서버 편집",
|
||||
"dialog.server.menu.edit": "편집",
|
||||
"dialog.server.menu.default": "기본값으로 설정",
|
||||
"dialog.server.menu.defaultRemove": "기본값 제거",
|
||||
"dialog.server.status.default": "기본값",
|
||||
"dialog.project.edit.title": "프로젝트 편집",
|
||||
"dialog.project.edit.icon": "아이콘",
|
||||
"dialog.project.edit.icon.alt": "프로젝트 아이콘",
|
||||
|
||||
@@ -293,9 +293,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "ລະຫັດຜ່ານ",
|
||||
"dialog.server.edit.title": "ແກ້ໄຂເຊີບເວີ",
|
||||
"dialog.server.menu.edit": "ແກ້ໄຂ",
|
||||
"dialog.server.menu.default": "ຕັ້ງເປັນຄ່າເລີ່ມຕົ້ນ",
|
||||
"dialog.server.menu.defaultRemove": "ເອົາຄ່າເລີ່ມຕົ້ນ",
|
||||
"dialog.server.status.default": "ຄ່າເລີ່ມຕົ້ນ",
|
||||
"dialog.project.edit.title": "ແກ້ໄຂໂຄງການ",
|
||||
"dialog.project.edit.icon": "ໄອຄອນ",
|
||||
"dialog.project.edit.icon.alt": "ໄອຄອນໂຄງການ",
|
||||
|
||||
@@ -299,9 +299,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "slaptažodis",
|
||||
"dialog.server.edit.title": "Redaguoti serverį",
|
||||
"dialog.server.menu.edit": "Redaguoti",
|
||||
"dialog.server.menu.default": "Nustatyti kaip numatytąjį",
|
||||
"dialog.server.menu.defaultRemove": "Pašalinti numatytąjį",
|
||||
"dialog.server.status.default": "Numatytoji",
|
||||
"dialog.project.edit.title": "Redaguoti projektą",
|
||||
"dialog.project.edit.icon": "Piktograma",
|
||||
"dialog.project.edit.icon.alt": "Projekto piktograma",
|
||||
|
||||
@@ -286,9 +286,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "parole",
|
||||
"dialog.server.edit.title": "Rediģēt serveri",
|
||||
"dialog.server.menu.edit": "Rediģēt",
|
||||
"dialog.server.menu.default": "Iestatīt kā noklusējumu",
|
||||
"dialog.server.menu.defaultRemove": "Noņemt noklusējumu",
|
||||
"dialog.server.status.default": "Noklusējuma",
|
||||
"dialog.project.edit.title": "Rediģēt projektu",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
"dialog.project.edit.icon.alt": "Projekta ikona",
|
||||
|
||||
@@ -287,9 +287,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "лозинка",
|
||||
"dialog.server.edit.title": "Уреди сервер",
|
||||
"dialog.server.menu.edit": "Уреди",
|
||||
"dialog.server.menu.default": "Поставете како стандардно",
|
||||
"dialog.server.menu.defaultRemove": "Отстрани стандардно",
|
||||
"dialog.server.status.default": "Стандардно",
|
||||
"dialog.project.edit.title": "Уреди проект",
|
||||
"dialog.project.edit.icon": "Икона",
|
||||
"dialog.project.edit.icon.alt": "Икона на проектот",
|
||||
|
||||
@@ -287,9 +287,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "нууц үг",
|
||||
"dialog.server.edit.title": "Сервер засах",
|
||||
"dialog.server.menu.edit": "Засварлах",
|
||||
"dialog.server.menu.default": "Өгөгдмөл болгож тохируулах",
|
||||
"dialog.server.menu.defaultRemove": "Өгөгдмөлийг арилгах",
|
||||
"dialog.server.status.default": "Өгөгдмөл",
|
||||
"dialog.project.edit.title": "Төслийг засварлах",
|
||||
"dialog.project.edit.icon": "Дүрс",
|
||||
"dialog.project.edit.icon.alt": "Төслийн дүрс тэмдэг",
|
||||
|
||||
@@ -295,9 +295,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "kata laluan",
|
||||
"dialog.server.edit.title": "Edit pelayan",
|
||||
"dialog.server.menu.edit": "Sunting",
|
||||
"dialog.server.menu.default": "Tetapkan sebagai lalai",
|
||||
"dialog.server.menu.defaultRemove": "Buang lalai",
|
||||
"dialog.server.status.default": "Lalai",
|
||||
"dialog.project.edit.title": "Edit projek",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
"dialog.project.edit.icon.alt": "Ikon projek",
|
||||
|
||||
@@ -296,9 +296,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "စကားဝှက်",
|
||||
"dialog.server.edit.title": "ဆာဗာကို တည်းဖြတ်ပါ။",
|
||||
"dialog.server.menu.edit": "တည်းဖြတ်ရန်",
|
||||
"dialog.server.menu.default": "မူရင်းအဖြစ် သတ်မှတ်ပါ။",
|
||||
"dialog.server.menu.defaultRemove": "မူရင်းကို ဖယ်ရှားပါ။",
|
||||
"dialog.server.status.default": "မူရင်း",
|
||||
"dialog.project.edit.title": "ပရောဂျက်ကို တည်းဖြတ်ပါ။",
|
||||
"dialog.project.edit.icon": "သင်္ကေတ",
|
||||
"dialog.project.edit.icon.alt": "ပရောဂျက် သင်္ကေတ",
|
||||
|
||||
@@ -291,9 +291,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "पासवर्ड",
|
||||
"dialog.server.edit.title": "सर्भर सम्पादन गर्नुहोस्",
|
||||
"dialog.server.menu.edit": "सम्पादन गर्नुहोस्",
|
||||
"dialog.server.menu.default": "पूर्वनिर्धारित रूपमा सेट गर्नुहोस्",
|
||||
"dialog.server.menu.defaultRemove": "पूर्वनिर्धारित हटाउनुहोस्",
|
||||
"dialog.server.status.default": "पूर्वनिर्धारित",
|
||||
"dialog.project.edit.title": "परियोजना सम्पादन गर्नुहोस्",
|
||||
"dialog.project.edit.icon": "आइकन",
|
||||
"dialog.project.edit.icon.alt": "परियोजना आइकन",
|
||||
|
||||
@@ -498,9 +498,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "wachtwoord",
|
||||
"dialog.server.edit.title": "Server bewerken",
|
||||
"dialog.server.menu.edit": "Bewerken",
|
||||
"dialog.server.menu.default": "Als standaard instellen",
|
||||
"dialog.server.menu.defaultRemove": "Standaard verwijderen",
|
||||
"dialog.server.status.default": "Standaard",
|
||||
"dialog.project.edit.title": "Project bewerken",
|
||||
"dialog.project.edit.icon": "Pictogram",
|
||||
"dialog.project.edit.icon.alt": "Projectpictogram",
|
||||
|
||||
@@ -507,9 +507,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Rediger server",
|
||||
|
||||
"dialog.server.menu.edit": "Rediger",
|
||||
"dialog.server.menu.default": "Sett som standard",
|
||||
"dialog.server.menu.defaultRemove": "Fjern standard",
|
||||
"dialog.server.status.default": "Standard",
|
||||
|
||||
"dialog.project.edit.title": "Rediger prosjekt",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
|
||||
@@ -293,9 +293,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "پاس ورڈ",
|
||||
"dialog.server.edit.title": "سرور وچ ترمیم کرو",
|
||||
"dialog.server.menu.edit": "ترمیم کرو",
|
||||
"dialog.server.menu.default": "ڈیفالٹ دے طور تے سیٹ کرو",
|
||||
"dialog.server.menu.defaultRemove": "ڈیفالٹ ہٹاؤ",
|
||||
"dialog.server.status.default": "طے شدہ",
|
||||
"dialog.project.edit.title": "پروجیکٹ وچ ترمیم کرو",
|
||||
"dialog.project.edit.icon": "آئکن",
|
||||
"dialog.project.edit.icon.alt": "پروجیکٹ آئیکن",
|
||||
|
||||
@@ -289,9 +289,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "Hasło (opcjonalnie)",
|
||||
"dialog.server.edit.title": "Edytuj serwer",
|
||||
"dialog.server.menu.edit": "Edytuj",
|
||||
"dialog.server.menu.default": "Ustaw jako domyślny",
|
||||
"dialog.server.menu.defaultRemove": "Usuń domyślny",
|
||||
"dialog.server.status.default": "Domyślny",
|
||||
"dialog.project.edit.title": "Edytuj projekt",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
"dialog.project.edit.icon.alt": "Ikona projektu",
|
||||
|
||||
@@ -285,9 +285,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "parolă",
|
||||
"dialog.server.edit.title": "Editează server",
|
||||
"dialog.server.menu.edit": "Editează",
|
||||
"dialog.server.menu.default": "Setează ca implicit",
|
||||
"dialog.server.menu.defaultRemove": "Elimină implicitul",
|
||||
"dialog.server.status.default": "Implicit",
|
||||
"dialog.project.edit.title": "Editează proiectul",
|
||||
"dialog.project.edit.icon": "Pictogramă",
|
||||
"dialog.project.edit.icon.alt": "Pictograma proiectului",
|
||||
|
||||
@@ -311,9 +311,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Редактировать сервер",
|
||||
|
||||
"dialog.server.menu.edit": "Редактировать",
|
||||
"dialog.server.menu.default": "Сделать по умолчанию",
|
||||
"dialog.server.menu.defaultRemove": "Отменить выбор по умолчанию",
|
||||
"dialog.server.status.default": "По умолч.",
|
||||
|
||||
"dialog.project.edit.title": "Редактировать проект",
|
||||
"dialog.project.edit.icon": "Значок",
|
||||
|
||||
@@ -289,9 +289,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "මුරපදය",
|
||||
"dialog.server.edit.title": "සේවාදායකය සංස්කරණය කරන්න",
|
||||
"dialog.server.menu.edit": "සංස්කරණය කරන්න",
|
||||
"dialog.server.menu.default": "පෙරනිමිය ලෙස සකසන්න",
|
||||
"dialog.server.menu.defaultRemove": "පෙරනිමිය ඉවත් කරන්න",
|
||||
"dialog.server.status.default": "පෙරනිමිය",
|
||||
"dialog.project.edit.title": "ව්යාපෘතිය සංස්කරණය කරන්න",
|
||||
"dialog.project.edit.icon": "නිරූපකය",
|
||||
"dialog.project.edit.icon.alt": "ව්යාපෘති නිරූපකය",
|
||||
|
||||
@@ -311,9 +311,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "heslo",
|
||||
"dialog.server.edit.title": "Upraviť server",
|
||||
"dialog.server.menu.edit": "Upraviť",
|
||||
"dialog.server.menu.default": "Nastaviť ako predvolený",
|
||||
"dialog.server.menu.defaultRemove": "Odstrániť predvolený",
|
||||
"dialog.server.status.default": "Predvolený",
|
||||
"dialog.project.edit.title": "Upraviť projekt",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
"dialog.project.edit.icon.alt": "Ikona projektu",
|
||||
|
||||
@@ -311,9 +311,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "geslo",
|
||||
"dialog.server.edit.title": "Uredi strežnik",
|
||||
"dialog.server.menu.edit": "Uredi",
|
||||
"dialog.server.menu.default": "Nastavi kot privzeto",
|
||||
"dialog.server.menu.defaultRemove": "Odstrani privzeto",
|
||||
"dialog.server.status.default": "Privzeto",
|
||||
"dialog.project.edit.title": "Uredi projekt",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
"dialog.project.edit.icon.alt": "Ikona projekta",
|
||||
|
||||
@@ -286,9 +286,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "fjalëkalimin",
|
||||
"dialog.server.edit.title": "Redakto serverin",
|
||||
"dialog.server.menu.edit": "Redakto",
|
||||
"dialog.server.menu.default": "Cakto si parazgjedhje",
|
||||
"dialog.server.menu.defaultRemove": "Hiq parazgjedhjen",
|
||||
"dialog.server.status.default": "E paracaktuar",
|
||||
"dialog.project.edit.title": "Redakto projektin",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
"dialog.project.edit.icon.alt": "Ikona e projektit",
|
||||
|
||||
@@ -299,9 +299,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "лозинка",
|
||||
"dialog.server.edit.title": "Уреди сервер",
|
||||
"dialog.server.menu.edit": "Уреди",
|
||||
"dialog.server.menu.default": "Подесите као подразумевано",
|
||||
"dialog.server.menu.defaultRemove": "Уклони подразумевано",
|
||||
"dialog.server.status.default": "подразумевано",
|
||||
"dialog.project.edit.title": "Уреди пројекат",
|
||||
"dialog.project.edit.icon": "Икона",
|
||||
"dialog.project.edit.icon.alt": "икона пројекта",
|
||||
|
||||
@@ -491,9 +491,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "lösenord",
|
||||
"dialog.server.edit.title": "Redigera server",
|
||||
"dialog.server.menu.edit": "Redigera",
|
||||
"dialog.server.menu.default": "Ställ in som standard",
|
||||
"dialog.server.menu.defaultRemove": "Ta bort standard",
|
||||
"dialog.server.status.default": "Standard",
|
||||
"dialog.project.edit.title": "Redigera projekt",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
"dialog.project.edit.icon.alt": "Projektikonen",
|
||||
|
||||
@@ -287,9 +287,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "парол",
|
||||
"dialog.server.edit.title": "Серверро таҳрир кунед",
|
||||
"dialog.server.menu.edit": "Таҳрир",
|
||||
"dialog.server.menu.default": "Ҳамчун пешфарз таъин кунед",
|
||||
"dialog.server.menu.defaultRemove": "Пешфарзро хориҷ кунед",
|
||||
"dialog.server.status.default": "Пешфарз",
|
||||
"dialog.project.edit.title": "Таҳрири лоиҳа",
|
||||
"dialog.project.edit.icon": "Нишона",
|
||||
"dialog.project.edit.icon.alt": "Нишонаи лоиҳа",
|
||||
|
||||
@@ -319,9 +319,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "แก้ไขเซิร์ฟเวอร์",
|
||||
|
||||
"dialog.server.menu.edit": "แก้ไข",
|
||||
"dialog.server.menu.default": "ตั้งเป็นค่าเริ่มต้น",
|
||||
"dialog.server.menu.defaultRemove": "เอาค่าเริ่มต้นออก",
|
||||
"dialog.server.status.default": "ค่าเริ่มต้น",
|
||||
|
||||
"dialog.project.edit.title": "แก้ไขโปรเจกต์",
|
||||
"dialog.project.edit.icon": "ไอคอน",
|
||||
|
||||
@@ -286,9 +286,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "parol",
|
||||
"dialog.server.edit.title": "Serweri redaktirläň",
|
||||
"dialog.server.menu.edit": "Redaktirläň",
|
||||
"dialog.server.menu.default": "Dymmaklyk hökmünde düzüň",
|
||||
"dialog.server.menu.defaultRemove": "Dymmaklygy aýyryň",
|
||||
"dialog.server.status.default": "Bellenen",
|
||||
"dialog.project.edit.title": "Taslamany redaktirläň",
|
||||
"dialog.project.edit.icon": "Nyşan",
|
||||
"dialog.project.edit.icon.alt": "Taslamanyň nyşany",
|
||||
|
||||
@@ -324,9 +324,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Sunucuyu düzenle",
|
||||
|
||||
"dialog.server.menu.edit": "Düzenle",
|
||||
"dialog.server.menu.default": "Varsayılan olarak ayarla",
|
||||
"dialog.server.menu.defaultRemove": "Varsayılanı kaldır",
|
||||
"dialog.server.status.default": "Varsayılan",
|
||||
|
||||
"dialog.project.edit.title": "Projeyi düzenle",
|
||||
"dialog.project.edit.icon": "Simge",
|
||||
|
||||
@@ -341,9 +341,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Редагувати сервер",
|
||||
|
||||
"dialog.server.menu.edit": "Редагувати",
|
||||
"dialog.server.menu.default": "Встановити за замовчуванням",
|
||||
"dialog.server.menu.defaultRemove": "Скасувати використання за замовчуванням",
|
||||
"dialog.server.status.default": "За замовчуванням",
|
||||
|
||||
"dialog.project.edit.title": "Редагувати проєкт",
|
||||
"dialog.project.edit.icon": "Іконка",
|
||||
|
||||
@@ -295,9 +295,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "پاس ورڈ",
|
||||
"dialog.server.edit.title": "سرور میں ترمیم کریں۔",
|
||||
"dialog.server.menu.edit": "ترمیم کریں۔",
|
||||
"dialog.server.menu.default": "بطور ڈیفالٹ سیٹ کریں۔",
|
||||
"dialog.server.menu.defaultRemove": "ڈیفالٹ کو ہٹا دیں۔",
|
||||
"dialog.server.status.default": "طے شدہ",
|
||||
"dialog.project.edit.title": "پروجیکٹ میں ترمیم کریں۔",
|
||||
"dialog.project.edit.icon": "آئیکن",
|
||||
"dialog.project.edit.icon.alt": "پروجیکٹ کا آئیکن",
|
||||
|
||||
@@ -287,9 +287,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "parol",
|
||||
"dialog.server.edit.title": "Serverni tahrirlash",
|
||||
"dialog.server.menu.edit": "Tahrirlash",
|
||||
"dialog.server.menu.default": "Standart sifatida o'rnating",
|
||||
"dialog.server.menu.defaultRemove": "Standartni olib tashlang",
|
||||
"dialog.server.status.default": "Standart",
|
||||
"dialog.project.edit.title": "Loyihani tahrirlash",
|
||||
"dialog.project.edit.icon": "Belgi",
|
||||
"dialog.project.edit.icon.alt": "Loyiha belgisi",
|
||||
|
||||
@@ -299,9 +299,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "mật khẩu",
|
||||
"dialog.server.edit.title": "Chỉnh sửa máy chủ",
|
||||
"dialog.server.menu.edit": "Chỉnh sửa",
|
||||
"dialog.server.menu.default": "Đặt làm mặc định",
|
||||
"dialog.server.menu.defaultRemove": "Xóa mặc định",
|
||||
"dialog.server.status.default": "Mặc định",
|
||||
"dialog.project.edit.title": "Chỉnh sửa dự án",
|
||||
"dialog.project.edit.icon": "Biểu tượng",
|
||||
"dialog.project.edit.icon.alt": "Biểu tượng dự án",
|
||||
|
||||
@@ -336,9 +336,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "密码(可选)",
|
||||
"dialog.server.edit.title": "编辑服务器",
|
||||
"dialog.server.menu.edit": "编辑",
|
||||
"dialog.server.menu.default": "设为默认",
|
||||
"dialog.server.menu.defaultRemove": "取消默认",
|
||||
"dialog.server.status.default": "默认",
|
||||
|
||||
"dialog.project.edit.title": "编辑项目",
|
||||
"dialog.project.edit.icon": "图标",
|
||||
|
||||
@@ -321,9 +321,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "編輯伺服器",
|
||||
|
||||
"dialog.server.menu.edit": "編輯",
|
||||
"dialog.server.menu.default": "設為預設",
|
||||
"dialog.server.menu.defaultRemove": "取消預設",
|
||||
"dialog.server.status.default": "預設",
|
||||
|
||||
"dialog.project.edit.title": "編輯專案",
|
||||
"dialog.project.edit.icon": "圖示",
|
||||
|
||||
@@ -2,7 +2,6 @@ import { createSimpleContext } from "@opencode/ui/context"
|
||||
import type { AsyncStorage, SyncStorage } from "@solid-primitives/storage"
|
||||
import type { Accessor } from "solid-js"
|
||||
import type { DesktopMenuAction } from "@/shell/commands/desktop-menu"
|
||||
import { ServerConnection } from "@/runtime/server/registry"
|
||||
import type { DraftStore } from "@/runtime/persistence/drafts"
|
||||
import type { Bridge } from "@opencode/gui-extensions/sdk/bridge"
|
||||
|
||||
@@ -81,12 +80,6 @@ type PlatformBase = {
|
||||
/** Fetch override */
|
||||
fetch?: typeof fetch
|
||||
|
||||
/** Get the configured default server URL (platform-specific) */
|
||||
getDefaultServer?(): Promise<ServerConnection.Key | null>
|
||||
|
||||
/** Set the default server URL to use on app startup (platform-specific) */
|
||||
setDefaultServer?(url: ServerConnection.Key | null): Promise<void> | void
|
||||
|
||||
/** Webview zoom level (desktop only) */
|
||||
webviewZoom?: Accessor<number>
|
||||
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { createBrowserDraftStore } from "@/runtime/persistence/drafts"
|
||||
import { ServerConnection } from "@/runtime/server/registry"
|
||||
import type { Platform } from "./platform"
|
||||
|
||||
const DEFAULT_SERVER_URL_KEY = "opencode.settings.dat:defaultServerUrl"
|
||||
|
||||
export function createWebPlatform(version: string) {
|
||||
const currentServerUrl = getCurrentServerUrl()
|
||||
const storedServerUrl = readDefaultServerUrl()
|
||||
|
||||
const platform: Platform = {
|
||||
platform: "web",
|
||||
draftStore: createBrowserDraftStore(),
|
||||
@@ -43,18 +37,11 @@ export function createWebPlatform(version: string) {
|
||||
notification.close()
|
||||
}
|
||||
},
|
||||
getDefaultServer: async () => {
|
||||
const stored = readDefaultServerUrl()
|
||||
|
||||
return stored ? ServerConnection.Key.make(stored) : null
|
||||
},
|
||||
setDefaultServer: writeDefaultServerUrl,
|
||||
}
|
||||
|
||||
return {
|
||||
platform,
|
||||
currentServerUrl,
|
||||
defaultServerUrl: storedServerUrl ?? currentServerUrl,
|
||||
currentServerUrl: getCurrentServerUrl(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,29 +59,3 @@ function getCurrentServerUrl() {
|
||||
|
||||
return location.origin
|
||||
}
|
||||
|
||||
function readDefaultServerUrl() {
|
||||
if (typeof localStorage === "undefined") return null
|
||||
|
||||
try {
|
||||
return localStorage.getItem(DEFAULT_SERVER_URL_KEY)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function writeDefaultServerUrl(value: string | null) {
|
||||
if (typeof localStorage === "undefined") return
|
||||
|
||||
try {
|
||||
if (value !== null) {
|
||||
localStorage.setItem(DEFAULT_SERVER_URL_KEY, value)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
localStorage.removeItem(DEFAULT_SERVER_URL_KEY)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -214,11 +214,7 @@ export namespace ServerConnection {
|
||||
export const { use: useServers, provider: ServersProvider } = createSimpleContext({
|
||||
name: "Server",
|
||||
gate: true,
|
||||
init: (props: {
|
||||
defaultServer?: ServerConnection.Key
|
||||
canonicalLocalServer?: ServerConnection.Key
|
||||
servers?: Array<ServerConnection.Any>
|
||||
}) => {
|
||||
init: (props: { canonicalLocalServer?: ServerConnection.Key; servers?: Array<ServerConnection.Any> }) => {
|
||||
const [store, setStore, _, hydrated] = persisted(
|
||||
{
|
||||
...Persist.global("server"),
|
||||
|
||||
@@ -5,21 +5,19 @@ import { sortServerConnections } from "./controller"
|
||||
const server = (url: string): ServerConnection.Http => ({ type: "http", http: { url } })
|
||||
|
||||
describe("sortServerConnections", () => {
|
||||
test("places the default first and preserves health and insertion ordering", () => {
|
||||
test("orders by health and preserves insertion ordering", () => {
|
||||
const first = server("http://first")
|
||||
const offline = server("http://offline")
|
||||
const preferred = server("http://preferred")
|
||||
const unknown = server("http://unknown")
|
||||
|
||||
const result = sortServerConnections({
|
||||
servers: [first, offline, preferred, unknown],
|
||||
servers: [first, offline, unknown],
|
||||
health: {
|
||||
[ServerConnection.key(first)]: { healthy: true },
|
||||
[ServerConnection.key(offline)]: { healthy: false },
|
||||
},
|
||||
defaultKey: ServerConnection.key(preferred),
|
||||
})
|
||||
|
||||
expect(result).toEqual([preferred, first, unknown, offline])
|
||||
expect(result).toEqual([first, unknown, offline])
|
||||
})
|
||||
})
|
||||
@@ -1,58 +1,15 @@
|
||||
import { createMemo, createResource } from "solid-js"
|
||||
import { createMemo } from "solid-js"
|
||||
import { useGlobal } from "@/runtime/server/runtime"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { usePlatform } from "@/runtime/platform/platform"
|
||||
import { ServerConnection, useServers } from "@/runtime/server/registry"
|
||||
import { useTabs } from "@/shell/tabs/tabs"
|
||||
import { type ServerHealth } from "@/runtime/server/health"
|
||||
import { showToast } from "@/shell/notifications/toast"
|
||||
import { useExtensionServers } from "@/runtime/extension/servers"
|
||||
|
||||
function showRequestError(language: ReturnType<typeof useLanguage>, err: unknown) {
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: language.t("common.requestFailed"),
|
||||
description: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
|
||||
function useDefaultServer() {
|
||||
const language = useLanguage()
|
||||
const platform = usePlatform()
|
||||
|
||||
const [defaultKey, defaultKeyActions] = createResource(
|
||||
async () => {
|
||||
try {
|
||||
return (await platform.getDefaultServer?.()) ?? null
|
||||
} catch (err) {
|
||||
showRequestError(language, err)
|
||||
|
||||
return null
|
||||
}
|
||||
},
|
||||
{ initialValue: null },
|
||||
)
|
||||
|
||||
const set = async (key: ServerConnection.Key | null) => {
|
||||
try {
|
||||
await platform.setDefaultServer?.(key)
|
||||
defaultKeyActions.mutate(key)
|
||||
} catch (err) {
|
||||
showRequestError(language, err)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
key: () => defaultKey.latest,
|
||||
available: createMemo(() => !!platform.getDefaultServer && !!platform.setDefaultServer),
|
||||
set,
|
||||
}
|
||||
}
|
||||
|
||||
export function sortServerConnections(input: {
|
||||
servers: ServerConnection.Any[]
|
||||
health: Record<string, ServerHealth | undefined>
|
||||
defaultKey: ServerConnection.Key | null
|
||||
}) {
|
||||
const order = new Map(input.servers.map((item, index) => [item, index] as const))
|
||||
|
||||
@@ -65,10 +22,6 @@ export function sortServerConnections(input: {
|
||||
}
|
||||
|
||||
return input.servers.slice().sort((a, b) => {
|
||||
const preferred =
|
||||
Number(ServerConnection.key(b) === input.defaultKey) - Number(ServerConnection.key(a) === input.defaultKey)
|
||||
|
||||
if (preferred !== 0) return preferred
|
||||
const health = rank(input.health[ServerConnection.key(a)]) - rank(input.health[ServerConnection.key(b)])
|
||||
|
||||
if (health !== 0) return health
|
||||
@@ -81,24 +34,23 @@ export function useServerActionsController() {
|
||||
const server = useServers()
|
||||
const extensions = useExtensionServers()
|
||||
const tabs = useTabs()
|
||||
const platform = usePlatform()
|
||||
const language = useLanguage()
|
||||
const defaults = useDefaultServer()
|
||||
|
||||
const remove = async (key: ServerConnection.Key) => {
|
||||
try {
|
||||
await extensions.entry(key)?.entry.remove?.()
|
||||
tabs.removeServer(key)
|
||||
server.remove(key)
|
||||
|
||||
if ((await platform.getDefaultServer?.()) === key) await defaults.set(null)
|
||||
} catch (err) {
|
||||
showRequestError(language, err)
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: language.t("common.requestFailed"),
|
||||
description: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
defaults,
|
||||
connection: {
|
||||
canRemove: server.canRemove,
|
||||
remove,
|
||||
@@ -126,7 +78,6 @@ export function useServerCollectionController() {
|
||||
sortServerConnections({
|
||||
servers: items(),
|
||||
health: global.servers.health,
|
||||
defaultKey: actions.defaults.key(),
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -23,11 +23,6 @@ export function ExtensionServerRow(props: { server: ServerConnection.Key; contro
|
||||
authenticationRequired={indicator.auth}
|
||||
/>
|
||||
),
|
||||
default: {
|
||||
available: () => props.controller.defaults.available(),
|
||||
current: () => props.controller.defaults.key() === props.server,
|
||||
set: (value) => void props.controller.defaults.set(value ? props.server : null),
|
||||
},
|
||||
remove: () => props.controller.connection.remove(props.server),
|
||||
Items: () => <ServerRowItems server={props.server} />,
|
||||
}
|
||||
|
||||
@@ -20,14 +20,10 @@ export const ServerRowMenu: Component<{
|
||||
<ServerRowMenuView
|
||||
server={props.server}
|
||||
labels={serverMenuLabels(language)}
|
||||
canDefault={props.domain.defaults.available()}
|
||||
isDefault={props.domain.defaults.key() === key}
|
||||
canRemove={props.domain.connection.canRemove(key)}
|
||||
canHide={props.domain.connection.canHide(key)}
|
||||
hidden={props.domain.connection.isHidden(key)}
|
||||
onEdit={props.onEdit}
|
||||
onSetDefault={() => props.domain.defaults.set(key)}
|
||||
onRemoveDefault={() => props.domain.defaults.set(null)}
|
||||
onRemove={() => props.domain.connection.remove(key)}
|
||||
onHide={() => props.domain.connection.setHidden(key, true)}
|
||||
onShow={() => props.domain.connection.setHidden(key, false)}
|
||||
@@ -42,8 +38,6 @@ export function serverMenuLabels(language: ReturnType<typeof useLanguage>) {
|
||||
more: language.t("common.moreOptions"),
|
||||
server: language.t("settings.section.server"),
|
||||
edit: language.t("dialog.server.menu.edit"),
|
||||
default: language.t("dialog.server.menu.default"),
|
||||
defaultRemove: language.t("dialog.server.menu.defaultRemove"),
|
||||
remove: language.t("dialog.server.menu.remove"),
|
||||
hide: language.t("dialog.server.menu.hide"),
|
||||
show: language.t("dialog.server.menu.show"),
|
||||
@@ -53,14 +47,10 @@ export function serverMenuLabels(language: ReturnType<typeof useLanguage>) {
|
||||
export const ServerRowMenuView: Component<{
|
||||
server: ServerConnection.Any
|
||||
labels: ReturnType<typeof serverMenuLabels>
|
||||
canDefault: boolean
|
||||
isDefault: boolean
|
||||
canRemove: boolean
|
||||
canHide?: boolean
|
||||
hidden?: boolean
|
||||
onEdit: (server: ServerConnection.Http) => void
|
||||
onSetDefault: () => void
|
||||
onRemoveDefault: () => void
|
||||
onRemove: () => void
|
||||
onHide?: () => void
|
||||
onShow?: () => void
|
||||
@@ -93,12 +83,6 @@ export const ServerRowMenuView: Component<{
|
||||
>
|
||||
{props.labels.edit}
|
||||
</Menu.Item>
|
||||
<Show when={props.canDefault && !props.isDefault}>
|
||||
<Menu.Item onSelect={props.onSetDefault}>{props.labels.default}</Menu.Item>
|
||||
</Show>
|
||||
<Show when={props.canDefault && props.isDefault}>
|
||||
<Menu.Item onSelect={props.onRemoveDefault}>{props.labels.defaultRemove}</Menu.Item>
|
||||
</Show>
|
||||
<Show when={props.hidden}>
|
||||
<Menu.Item onSelect={props.onShow}>{props.labels.show}</Menu.Item>
|
||||
</Show>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Badge } from "@opencode/ui/badge"
|
||||
import { Button } from "@opencode/ui/button"
|
||||
import { useDialog } from "@opencode/ui/context/dialog"
|
||||
import { createMemo, Show, type Component } from "solid-js"
|
||||
@@ -82,9 +81,6 @@ export const SettingsServerGeneral: Component<{
|
||||
</Button>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={controller.defaults.available() && controller.defaults.key() === props.entry.key}>
|
||||
<Badge>{language.t("dialog.server.status.default")}</Badge>
|
||||
</Show>
|
||||
<ServerRowMenu server={server()} domain={controller} onEdit={edit} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1217,14 +1217,6 @@
|
||||
.settings-project-card[data-menu="true"] {
|
||||
background: var(--v2-background-bg-layer-02);
|
||||
}
|
||||
.settings-tab-search-clear {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
inset-inline-end: 6px;
|
||||
z-index: 1;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.settings-models {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@@ -248,13 +248,7 @@ function RootSettings() {
|
||||
const ordered = createMemo(() => {
|
||||
const order = new Map(list().map((server, index) => [ServerConnection.key(server), index]))
|
||||
|
||||
return inventory().toSorted((a, b) => {
|
||||
const preferred = Number(b.key === servers.defaults.key()) - Number(a.key === servers.defaults.key())
|
||||
|
||||
if (preferred) return preferred
|
||||
|
||||
return (order.get(a.key) ?? list().length) - (order.get(b.key) ?? list().length)
|
||||
})
|
||||
return inventory().toSorted((a, b) => (order.get(a.key) ?? list().length) - (order.get(b.key) ?? list().length))
|
||||
})
|
||||
|
||||
const sourceServer = createMemo(() => {
|
||||
|
||||
@@ -73,7 +73,7 @@ export function createCommandPaletteFileOpener(onOpenFile?: (path: string) => vo
|
||||
}
|
||||
|
||||
/** The highlighted option's preview cleanup, and whether the palette committed a choice. */
|
||||
type PaletteHighlight = { cleanup: (() => void) | void; committed: boolean }
|
||||
export type PaletteHighlight = { cleanup: (() => void) | void; committed: boolean }
|
||||
|
||||
export function createCommandPaletteModel(props: { filesOnly?: () => boolean; onOpenFile?: (path: string) => void }) {
|
||||
const command = useCommand()
|
||||
@@ -148,6 +148,7 @@ export function createCommandPaletteModel(props: { filesOnly?: () => boolean; on
|
||||
get: (sessionID, signal) => serverSDK.api.session.get({ sessionID }, { signal }),
|
||||
untitled: () => language.t("command.session.new"),
|
||||
category: () => language.t("command.category.session"),
|
||||
recentCategory: () => language.t("palette.group.recentSessions"),
|
||||
})
|
||||
|
||||
const highlight = (item: CommandPaletteEntry | undefined) => {
|
||||
@@ -232,6 +233,7 @@ export function createServerSessionEntries(props: {
|
||||
get: (sessionID: string, signal: AbortSignal) => Promise<SessionInfo>
|
||||
untitled: () => string
|
||||
category: () => string
|
||||
recentCategory: () => string
|
||||
}) {
|
||||
let abort: AbortController | undefined
|
||||
|
||||
@@ -240,17 +242,13 @@ export function createServerSessionEntries(props: {
|
||||
return async (text: string): Promise<CommandPaletteEntry[]> => {
|
||||
const search = text.trim()
|
||||
|
||||
if (!search) {
|
||||
abort?.abort()
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
abort?.abort()
|
||||
const current = new AbortController()
|
||||
abort = current
|
||||
|
||||
// Typed searches wait for a pause; an empty query lists recent sessions right away.
|
||||
await new Promise<void>((resolve) => {
|
||||
const timer = setTimeout(resolve, 100)
|
||||
const timer = setTimeout(resolve, search ? 100 : 0)
|
||||
current.signal.addEventListener(
|
||||
"abort",
|
||||
() => {
|
||||
@@ -264,8 +262,9 @@ export function createServerSessionEntries(props: {
|
||||
if (current.signal.aborted) return []
|
||||
const opened = props.opened()
|
||||
const stored = props.stored().map((project) => ({ ...project, expanded: false }))
|
||||
const category = search ? props.category() : props.recentCategory()
|
||||
|
||||
return Promise.all([
|
||||
const entries = await Promise.all([
|
||||
props.load(search, current.signal).then(
|
||||
(result) => result.data,
|
||||
() => [],
|
||||
@@ -289,7 +288,7 @@ export function createServerSessionEntries(props: {
|
||||
type: "session" as const,
|
||||
title: session.title || props.untitled(),
|
||||
description: project ? displayName(project) : getFilename(session.location.directory),
|
||||
category: props.category(),
|
||||
category,
|
||||
directory: session.location.directory,
|
||||
sessionID: session.id,
|
||||
server: props.server,
|
||||
@@ -300,5 +299,7 @@ export function createServerSessionEntries(props: {
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
return search ? entries : entries.slice(0, ENTRY_LIMIT)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SessionTab, Tab } from "./tabs"
|
||||
import type { SessionTab, Tab, TabInfo } from "./tabs"
|
||||
import type { TabStorage } from "./schema"
|
||||
|
||||
export type ClosedTab = typeof TabStorage.ClosedTab.Type
|
||||
@@ -7,15 +7,43 @@ const CLOSED_TAB_LIMIT = 25
|
||||
|
||||
// Only session tabs are recorded; closing a draft tab deletes its persisted
|
||||
// state, so a reopened draft would come back empty anyway.
|
||||
export function pushClosedTab(stack: ClosedTab[], tab: Tab, index: number): ClosedTab[] {
|
||||
export function pushClosedTab(stack: ClosedTab[], tab: Tab, index: number, info?: TabInfo): ClosedTab[] {
|
||||
if (tab.type !== "session") return stack
|
||||
|
||||
return [...stack, { tab: { ...tab }, index }].slice(-CLOSED_TAB_LIMIT)
|
||||
const entry: ClosedTab = { tab: { ...tab }, index }
|
||||
|
||||
if (info) entry.info = { ...info }
|
||||
|
||||
return [...stack.filter((item) => !sameTab(item.tab, tab)), entry].slice(-CLOSED_TAB_LIMIT)
|
||||
}
|
||||
|
||||
export function listClosedTabs(stack: ClosedTab[], tabs: Tab[]) {
|
||||
const seen = new Set<string>()
|
||||
|
||||
return stack.toReversed().filter((entry) => {
|
||||
const key = `${entry.tab.server}\n${entry.tab.sessionId}`
|
||||
|
||||
if (seen.has(key)) return false
|
||||
seen.add(key)
|
||||
|
||||
return !isOpen(tabs, entry.tab)
|
||||
})
|
||||
}
|
||||
|
||||
export type TakenClosedTab = { entry?: ClosedTab; stack: ClosedTab[] }
|
||||
|
||||
// Pops the most recently closed tab that is not open again,
|
||||
// discarding stale entries along the way.
|
||||
export function takeClosedTab(stack: ClosedTab[], tabs: Tab[]): { entry?: ClosedTab; stack: ClosedTab[] } {
|
||||
export function takeClosedTab(stack: ClosedTab[], tabs: Tab[], target?: SessionTab): TakenClosedTab {
|
||||
if (target) {
|
||||
const index = stack.findLastIndex((entry) => sameTab(entry.tab, target))
|
||||
const entry = stack[index]
|
||||
|
||||
if (!entry || isOpen(tabs, entry.tab)) return { stack }
|
||||
|
||||
return { entry, stack: [...stack.slice(0, index), ...stack.slice(index + 1)] }
|
||||
}
|
||||
|
||||
const remaining = [...stack]
|
||||
|
||||
while (remaining.length) {
|
||||
@@ -40,5 +68,9 @@ export function nextTabAfterClose(tabs: Tab[], index: number, active: boolean) {
|
||||
}
|
||||
|
||||
function isOpen(tabs: Tab[], tab: SessionTab) {
|
||||
return tabs.some((item) => item.type === "session" && item.server === tab.server && item.sessionId === tab.sessionId)
|
||||
return tabs.some((item) => item.type === "session" && sameTab(item, tab))
|
||||
}
|
||||
|
||||
function sameTab(first: SessionTab, second: SessionTab) {
|
||||
return first.server === second.server && first.sessionId === second.sessionId
|
||||
}
|
||||
@@ -42,6 +42,7 @@ export const Recent = Persistence.struct({
|
||||
export const Info = Persistence.struct({
|
||||
title: Schema.optional(Schema.String),
|
||||
directory: Schema.optional(Schema.String),
|
||||
prompted: Schema.optional(Schema.Boolean),
|
||||
})
|
||||
|
||||
export const Infos = Schema.Record(Schema.String, Schema.mutableKey(Info))
|
||||
@@ -59,7 +60,11 @@ export const Regions = Schema.Record(
|
||||
),
|
||||
)
|
||||
|
||||
export const ClosedTab = Schema.Struct({ tab: SessionCodec, index: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)) })
|
||||
export const ClosedTab = Persistence.struct({
|
||||
tab: SessionCodec,
|
||||
index: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
||||
info: Persistence.optional(Info),
|
||||
})
|
||||
|
||||
export const Closed = Persistence.array(ClosedTab)
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { createRoot, getOwner, onCleanup } from "solid-js"
|
||||
import { createTabMemory } from "./memory"
|
||||
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed"
|
||||
import {
|
||||
listClosedTabs,
|
||||
nextTabAfterClose,
|
||||
pushClosedTab,
|
||||
removeClosedTabs,
|
||||
takeClosedTab,
|
||||
type ClosedTab,
|
||||
} from "./closed"
|
||||
import { findSessionTab, sessionIDHasOpenTab, tabHref, tabKey, type SessionTab, type Tab } from "./tabs"
|
||||
import { Schema } from "effect"
|
||||
import { TabStorage } from "./schema"
|
||||
@@ -144,10 +151,21 @@ describe("tab memory", () => {
|
||||
})
|
||||
|
||||
describe("closed tab stack", () => {
|
||||
test("records session tabs with their index and ignores draft tabs", () => {
|
||||
test("records session tabs with their index", () => {
|
||||
const stack = pushClosedTab([], sessionTab("a"), 2, {
|
||||
title: "Alpha",
|
||||
directory: "/project",
|
||||
prompted: true,
|
||||
})
|
||||
|
||||
expect(stack).toEqual([
|
||||
{ tab: sessionTab("a"), index: 2, info: { title: "Alpha", directory: "/project", prompted: true } },
|
||||
])
|
||||
})
|
||||
|
||||
test("ignores draft tabs", () => {
|
||||
const draft: Tab = { type: "draft", draftID: "d1", server, directory: "/tmp" }
|
||||
|
||||
expect(pushClosedTab([], sessionTab("a"), 2)).toEqual([{ tab: sessionTab("a"), index: 2 }])
|
||||
expect(pushClosedTab([], draft, 0)).toEqual([])
|
||||
})
|
||||
|
||||
@@ -162,16 +180,85 @@ describe("closed tab stack", () => {
|
||||
expect(stack.at(-1)?.tab.sessionId).toBe("s29")
|
||||
})
|
||||
|
||||
test("pops the most recently closed tab that is not already open", () => {
|
||||
test("keeps only the newest close record for a session", () => {
|
||||
const stack = pushClosedTab(
|
||||
pushClosedTab([], sessionTab("a"), 1, { title: "Old", directory: "/old", prompted: true }),
|
||||
sessionTab("a"),
|
||||
3,
|
||||
{ title: "New", directory: "/new", prompted: true },
|
||||
)
|
||||
|
||||
expect(stack).toEqual([
|
||||
{ tab: sessionTab("a"), index: 3, info: { title: "New", directory: "/new", prompted: true } },
|
||||
])
|
||||
})
|
||||
|
||||
test("pops the most recently closed tab", () => {
|
||||
const stack = [
|
||||
{ tab: sessionTab("a"), index: 0 },
|
||||
{ tab: sessionTab("b"), index: 1 },
|
||||
]
|
||||
|
||||
expect(takeClosedTab(stack, [])).toEqual({ entry: stack[1], stack: [stack[0]] })
|
||||
expect(takeClosedTab(stack, [sessionTab("b")])).toEqual({ entry: stack[0], stack: [] })
|
||||
expect(takeClosedTab(stack, [sessionTab("a"), sessionTab("b")])).toEqual({ entry: undefined, stack: [] })
|
||||
const result = takeClosedTab(stack, [])
|
||||
|
||||
expect(result.entry?.tab.sessionId).toBe("b")
|
||||
expect(result.stack).toEqual([{ tab: sessionTab("a"), index: 0 }])
|
||||
})
|
||||
|
||||
test("skips entries whose tab is already open", () => {
|
||||
const stack = [
|
||||
{ tab: sessionTab("a"), index: 0 },
|
||||
{ tab: sessionTab("b"), index: 1 },
|
||||
]
|
||||
|
||||
const result = takeClosedTab(stack, [sessionTab("b")])
|
||||
|
||||
expect(result.entry?.tab.sessionId).toBe("a")
|
||||
expect(result.stack).toEqual([])
|
||||
})
|
||||
|
||||
test("lists closed tabs from newest to oldest and excludes open tabs", () => {
|
||||
const stack = [
|
||||
{ tab: sessionTab("a"), index: 0 },
|
||||
{ tab: sessionTab("b"), index: 1 },
|
||||
{ tab: sessionTab("c"), index: 2 },
|
||||
]
|
||||
|
||||
expect(listClosedTabs(stack, [sessionTab("b")]).map((entry) => entry.tab.sessionId)).toEqual(["c", "a"])
|
||||
})
|
||||
|
||||
test("deduplicates previously stored close records", () => {
|
||||
const stack = [
|
||||
{ tab: sessionTab("a"), index: 0 },
|
||||
{ tab: sessionTab("b"), index: 1 },
|
||||
{ tab: sessionTab("a"), index: 2 },
|
||||
]
|
||||
|
||||
expect(listClosedTabs(stack, []).map((entry) => [entry.tab.sessionId, entry.index])).toEqual([
|
||||
["a", 2],
|
||||
["b", 1],
|
||||
])
|
||||
})
|
||||
|
||||
test("takes a selected closed tab without removing newer entries", () => {
|
||||
const stack = [
|
||||
{ tab: sessionTab("a"), index: 0 },
|
||||
{ tab: sessionTab("b"), index: 1 },
|
||||
{ tab: sessionTab("c"), index: 2 },
|
||||
]
|
||||
|
||||
const result = takeClosedTab(stack, [], sessionTab("a"))
|
||||
|
||||
expect(result.entry?.tab.sessionId).toBe("a")
|
||||
expect(result.stack.map((entry) => entry.tab.sessionId)).toEqual(["b", "c"])
|
||||
})
|
||||
|
||||
test("returns no entry when everything is open or empty", () => {
|
||||
expect(takeClosedTab([], []).entry).toBeUndefined()
|
||||
|
||||
const result = takeClosedTab([{ tab: sessionTab("a"), index: 0 }], [sessionTab("a")])
|
||||
expect(result.entry).toBeUndefined()
|
||||
expect(result.stack).toEqual([])
|
||||
})
|
||||
|
||||
test("purges removed sessions", () => {
|
||||
|
||||
@@ -416,24 +416,26 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
||||
|
||||
if (!tab) return
|
||||
|
||||
if (tab.type === "session") updateClosed((stack) => pushClosedTab(stack, tab, index))
|
||||
if (tab.type === "session") updateClosed((stack) => pushClosedTab(stack, tab, index, info[tabKey(tab)]))
|
||||
removeTab(index)
|
||||
},
|
||||
reopenClosedTab() {
|
||||
reopenClosedTab(target?: SessionTab, options?: { append?: boolean }) {
|
||||
if (!closedReady()) {
|
||||
void closedReady.promise?.then(() => actions.reopenClosedTab())
|
||||
void closedReady.promise?.then(() => actions.reopenClosedTab(target, options))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const result = takeClosedTab(closed, store)
|
||||
const result = takeClosedTab(closed, store, target)
|
||||
|
||||
if (result.stack.length === closed.length) return
|
||||
setClosed(() => result.stack)
|
||||
const entry = result.entry
|
||||
|
||||
if (!entry) return
|
||||
const index = Math.min(entry.index, store.length)
|
||||
const index = options?.append ? store.length : Math.min(entry.index, store.length)
|
||||
|
||||
if (entry.info) setInfo(tabKey(entry.tab), entry.info)
|
||||
void startTransition(() => {
|
||||
setStore(
|
||||
produce((tabs) => {
|
||||
@@ -532,12 +534,23 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
||||
|
||||
for (const key of removed) removeInfo(key)
|
||||
},
|
||||
rememberSessionInfo(tab: SessionTab, session: SessionInfo) {
|
||||
rememberSessionInfo(tab: SessionTab, session: SessionInfo, prompted: boolean) {
|
||||
const key = tabKey(tab)
|
||||
const next = { title: session.title, directory: session.location.directory }
|
||||
const current = info[key]
|
||||
|
||||
if (current && current.title === next.title && current.directory === next.directory) return
|
||||
const next = {
|
||||
title: session.title,
|
||||
directory: session.location.directory,
|
||||
prompted: current?.prompted === true || prompted,
|
||||
}
|
||||
|
||||
if (
|
||||
current &&
|
||||
current.title === next.title &&
|
||||
current.directory === next.directory &&
|
||||
current.prompted === next.prompted
|
||||
)
|
||||
return
|
||||
console.debug("[tabs] update persisted session info", { key, sessionID: session.id, current, next })
|
||||
setInfo(key, next)
|
||||
},
|
||||
@@ -626,6 +639,6 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
||||
},
|
||||
}
|
||||
|
||||
return { ...actions, store, info, ready, infoReady, recentReady, regionsReady }
|
||||
return { ...actions, store, info, closed, ready, infoReady, recentReady, closedReady, regionsReady }
|
||||
},
|
||||
})
|
||||
Loaded 100 of 256 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user