mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-02 16:26:14 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2264c93b6b |
@@ -319,7 +319,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (store.authorization?.method === "code" && store.authorization?.url) {
|
if (store.authorization?.method === "code" && store.authorization?.url) {
|
||||||
platform.openLink(store.authorization.url)
|
void platform.openLink(store.authorization.url).catch(() => undefined)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -396,7 +396,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
if (store.authorization?.url) {
|
if (store.authorization?.url) {
|
||||||
platform.openLink(store.authorization.url)
|
void platform.openLink(store.authorization.url).catch(() => undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await globalSDK.client.provider.oauth
|
const result = await globalSDK.client.provider.oauth
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ export function Link(props: LinkProps) {
|
|||||||
const [local, rest] = splitProps(props, ["href", "children"])
|
const [local, rest] = splitProps(props, ["href", "children"])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button class="text-text-strong underline" onClick={() => platform.openLink(local.href)} {...rest}>
|
<button
|
||||||
|
class="text-text-strong underline"
|
||||||
|
onClick={() => void platform.openLink(local.href).catch(() => undefined)}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
{local.children}
|
{local.children}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { Popover } from "@opencode-ai/ui/popover"
|
|||||||
import { TextField } from "@opencode-ai/ui/text-field"
|
import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
import { Keybind } from "@opencode-ai/ui/keybind"
|
import { Keybind } from "@opencode-ai/ui/keybind"
|
||||||
import { StatusPopover } from "../status-popover"
|
import { StatusPopover } from "../status-popover"
|
||||||
|
import { SessionOpenMenu } from "./session-open-menu"
|
||||||
|
|
||||||
export function SessionHeader() {
|
export function SessionHeader() {
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
@@ -117,7 +118,7 @@ export function SessionHeader() {
|
|||||||
function viewShare() {
|
function viewShare() {
|
||||||
const url = shareUrl()
|
const url = shareUrl()
|
||||||
if (!url) return
|
if (!url) return
|
||||||
platform.openLink(url)
|
void platform.openLink(url).catch(() => undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
const centerMount = createMemo(() => document.getElementById("opencode-titlebar-center"))
|
const centerMount = createMemo(() => document.getElementById("opencode-titlebar-center"))
|
||||||
@@ -150,6 +151,7 @@ export function SessionHeader() {
|
|||||||
{(mount) => (
|
{(mount) => (
|
||||||
<Portal mount={mount()}>
|
<Portal mount={mount()}>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
<SessionOpenMenu dir={projectDirectory()} />
|
||||||
<StatusPopover />
|
<StatusPopover />
|
||||||
<Show when={showShare()}>
|
<Show when={showShare()}>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import { createMemo, Show } from "solid-js"
|
||||||
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import { useServer } from "@/context/server"
|
||||||
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
|
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||||
|
import { FileTypeIcon } from "@opencode-ai/ui/file-type-icon"
|
||||||
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
|
|
||||||
|
export function SessionOpenMenu(props: { dir: string }) {
|
||||||
|
const platform = usePlatform()
|
||||||
|
const server = useServer()
|
||||||
|
const language = useLanguage()
|
||||||
|
|
||||||
|
const enabled = createMemo(
|
||||||
|
() => platform.platform === "desktop" && platform.os === "macos" && server.isLocal() && !!props.dir,
|
||||||
|
)
|
||||||
|
|
||||||
|
const open = (app?: string) => {
|
||||||
|
if (!props.dir) return
|
||||||
|
void platform.openLink(props.dir, app).catch((error) => {
|
||||||
|
showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: language.t("common.requestFailed"),
|
||||||
|
description: error instanceof Error ? error.message : String(error),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const copy = () => {
|
||||||
|
if (!props.dir) return
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(props.dir)
|
||||||
|
.then(() => {
|
||||||
|
showToast({
|
||||||
|
variant: "success",
|
||||||
|
icon: "check",
|
||||||
|
title: language.t("session.header.copyPath.copied"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: language.t("session.header.copyPath.copyFailed"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu modal={false}>
|
||||||
|
<DropdownMenu.Trigger
|
||||||
|
as={Button}
|
||||||
|
variant="ghost"
|
||||||
|
icon="folder"
|
||||||
|
class="rounded-sm h-[24px] py-1.5 pr-2 pl-2 gap-1.5 border-none shadow-none data-[expanded]:bg-surface-raised-base-active"
|
||||||
|
aria-label={language.t("session.header.open")}
|
||||||
|
>
|
||||||
|
<span class="text-12-regular text-text-strong">{language.t("session.header.open")}</span>
|
||||||
|
<Icon name="chevron-down" size="small" class="icon-base" />
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
<DropdownMenu.Portal>
|
||||||
|
<DropdownMenu.Content class="mt-1 w-60">
|
||||||
|
<Show when={enabled()}>
|
||||||
|
<DropdownMenu.Group>
|
||||||
|
<DropdownMenu.GroupLabel>{language.t("session.header.openIn")}</DropdownMenu.GroupLabel>
|
||||||
|
<DropdownMenu.Item onSelect={() => open("Visual Studio Code")}>
|
||||||
|
<FileTypeIcon id="Vscode" class="size-5" />
|
||||||
|
<DropdownMenu.ItemLabel>VS Code</DropdownMenu.ItemLabel>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item onSelect={() => open("Cursor")}>
|
||||||
|
<FileTypeIcon id="Cursor" class="size-5" />
|
||||||
|
<DropdownMenu.ItemLabel>Cursor</DropdownMenu.ItemLabel>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item onSelect={() => open("Finder")}>
|
||||||
|
<Icon name="folder" size="small" class="icon-base shrink-0" />
|
||||||
|
<DropdownMenu.ItemLabel>Finder</DropdownMenu.ItemLabel>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item onSelect={() => open("Terminal")}>
|
||||||
|
<FileTypeIcon id="Console" class="size-5" />
|
||||||
|
<DropdownMenu.ItemLabel>Terminal</DropdownMenu.ItemLabel>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item onSelect={() => open("iTerm")}>
|
||||||
|
<FileTypeIcon id="Console" class="size-5" />
|
||||||
|
<DropdownMenu.ItemLabel>iTerm2</DropdownMenu.ItemLabel>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item onSelect={() => open("Ghostty")}>
|
||||||
|
<FileTypeIcon id="Console" class="size-5" />
|
||||||
|
<DropdownMenu.ItemLabel>Ghostty</DropdownMenu.ItemLabel>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item onSelect={() => open("Xcode")}>
|
||||||
|
<FileTypeIcon id="Swift" class="size-5" />
|
||||||
|
<DropdownMenu.ItemLabel>Xcode</DropdownMenu.ItemLabel>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item onSelect={() => open("Android Studio")}>
|
||||||
|
<FileTypeIcon id="Android" class="size-5" />
|
||||||
|
<DropdownMenu.ItemLabel>Android Studio</DropdownMenu.ItemLabel>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Group>
|
||||||
|
<DropdownMenu.Separator />
|
||||||
|
</Show>
|
||||||
|
<DropdownMenu.Item onSelect={copy}>
|
||||||
|
<Icon name="copy" size="small" class="icon-base shrink-0" />
|
||||||
|
<DropdownMenu.ItemLabel>{language.t("session.header.copyPath")}</DropdownMenu.ItemLabel>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Portal>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -12,8 +12,8 @@ export type Platform = {
|
|||||||
/** App version */
|
/** App version */
|
||||||
version?: string
|
version?: string
|
||||||
|
|
||||||
/** Open a URL in the default browser */
|
/** Open a URL/path using the OS (optionally with a specific app) */
|
||||||
openLink(url: string): void
|
openLink(url: string, openWith?: string): Promise<void>
|
||||||
|
|
||||||
/** Restart the app */
|
/** Restart the app */
|
||||||
restart(): Promise<void>
|
restart(): Promise<void>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
|||||||
const platform: Platform = {
|
const platform: Platform = {
|
||||||
platform: "web",
|
platform: "web",
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
openLink(url: string) {
|
async openLink(url: string, _openWith?: string) {
|
||||||
window.open(url, "_blank")
|
window.open(url, "_blank")
|
||||||
},
|
},
|
||||||
back() {
|
back() {
|
||||||
|
|||||||
@@ -469,6 +469,11 @@ export const dict = {
|
|||||||
|
|
||||||
"session.header.search.placeholder": "Search {{project}}",
|
"session.header.search.placeholder": "Search {{project}}",
|
||||||
"session.header.searchFiles": "Search files",
|
"session.header.searchFiles": "Search files",
|
||||||
|
"session.header.open": "Open",
|
||||||
|
"session.header.openIn": "Open in",
|
||||||
|
"session.header.copyPath": "Copy Path",
|
||||||
|
"session.header.copyPath.copied": "Copied path",
|
||||||
|
"session.header.copyPath.copyFailed": "Failed to copy path to clipboard",
|
||||||
|
|
||||||
"status.popover.trigger": "Status",
|
"status.popover.trigger": "Status",
|
||||||
"status.popover.ariaLabel": "Server configurations",
|
"status.popover.ariaLabel": "Server configurations",
|
||||||
|
|||||||
@@ -269,14 +269,14 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
|||||||
<div class="flex flex-col items-center gap-2">
|
<div class="flex flex-col items-center gap-2">
|
||||||
<div class="flex items-center justify-center gap-1">
|
<div class="flex items-center justify-center gap-1">
|
||||||
{language.t("error.page.report.prefix")}
|
{language.t("error.page.report.prefix")}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex items-center text-text-interactive-base gap-1"
|
class="flex items-center text-text-interactive-base gap-1"
|
||||||
onClick={() => platform.openLink("https://opencode.ai/desktop-feedback")}
|
onClick={() => void platform.openLink("https://opencode.ai/desktop-feedback").catch(() => undefined)}
|
||||||
>
|
>
|
||||||
<div>{language.t("error.page.report.discord")}</div>
|
<div>{language.t("error.page.report.discord")}</div>
|
||||||
<Icon name="discord" class="text-text-interactive-base" />
|
<Icon name="discord" class="text-text-interactive-base" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Show when={platform.version}>
|
<Show when={platform.version}>
|
||||||
{(version) => (
|
{(version) => (
|
||||||
|
|||||||
@@ -2995,7 +2995,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
icon="help"
|
icon="help"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={() => platform.openLink("https://opencode.ai/desktop-feedback")}
|
onClick={() => void platform.openLink("https://opencode.ai/desktop-feedback").catch(() => undefined)}
|
||||||
aria-label={language.t("sidebar.help")}
|
aria-label={language.t("sidebar.help")}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
"opener:default",
|
"opener:default",
|
||||||
|
{
|
||||||
|
"identifier": "opener:allow-open-path",
|
||||||
|
"allow": [{ "path": "/**", "app": true }]
|
||||||
|
},
|
||||||
"deep-link:default",
|
"deep-link:default",
|
||||||
"core:window:allow-start-dragging",
|
"core:window:allow-start-dragging",
|
||||||
"core:window:allow-set-theme",
|
"core:window:allow-set-theme",
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
// This file has been generated by Tauri Specta. Do not edit this file manually.
|
// This file has been generated by Tauri Specta. Do not edit this file manually.
|
||||||
|
|
||||||
import { invoke as __TAURI_INVOKE, Channel } from "@tauri-apps/api/core"
|
import { invoke as __TAURI_INVOKE, Channel } from '@tauri-apps/api/core';
|
||||||
|
|
||||||
/** Commands */
|
/** Commands */
|
||||||
export const commands = {
|
export const commands = {
|
||||||
killSidecar: () => __TAURI_INVOKE<void>("kill_sidecar"),
|
killSidecar: () => __TAURI_INVOKE<void>("kill_sidecar"),
|
||||||
installCli: () => __TAURI_INVOKE<string>("install_cli"),
|
installCli: () => __TAURI_INVOKE<string>("install_cli"),
|
||||||
ensureServerReady: () => __TAURI_INVOKE<ServerReadyData>("ensure_server_ready"),
|
ensureServerReady: () => __TAURI_INVOKE<ServerReadyData>("ensure_server_ready"),
|
||||||
getDefaultServerUrl: () => __TAURI_INVOKE<string | null>("get_default_server_url"),
|
getDefaultServerUrl: () => __TAURI_INVOKE<string | null>("get_default_server_url"),
|
||||||
setDefaultServerUrl: (url: string | null) => __TAURI_INVOKE<null>("set_default_server_url", { url }),
|
setDefaultServerUrl: (url: string | null) => __TAURI_INVOKE<null>("set_default_server_url", { url }),
|
||||||
parseMarkdownCommand: (markdown: string) => __TAURI_INVOKE<string>("parse_markdown_command", { markdown }),
|
parseMarkdownCommand: (markdown: string) => __TAURI_INVOKE<string>("parse_markdown_command", { markdown }),
|
||||||
}
|
};
|
||||||
|
|
||||||
/* Types */
|
/* Types */
|
||||||
export type ServerReadyData = {
|
export type ServerReadyData = {
|
||||||
url: string
|
url: string,
|
||||||
password: string | null
|
password: string | null,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { render } from "solid-js/web"
|
|||||||
import { AppBaseProviders, AppInterface, PlatformProvider, Platform } from "@opencode-ai/app"
|
import { AppBaseProviders, AppInterface, PlatformProvider, Platform } from "@opencode-ai/app"
|
||||||
import { open, save } from "@tauri-apps/plugin-dialog"
|
import { open, save } from "@tauri-apps/plugin-dialog"
|
||||||
import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link"
|
import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link"
|
||||||
import { open as shellOpen } from "@tauri-apps/plugin-shell"
|
import { openPath, openUrl } from "@tauri-apps/plugin-opener"
|
||||||
import { type as ostype } from "@tauri-apps/plugin-os"
|
import { type as ostype } from "@tauri-apps/plugin-os"
|
||||||
import { check, Update } from "@tauri-apps/plugin-updater"
|
import { check, Update } from "@tauri-apps/plugin-updater"
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window"
|
import { getCurrentWindow } from "@tauri-apps/api/window"
|
||||||
@@ -94,8 +94,10 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({
|
|||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
|
|
||||||
openLink(url: string) {
|
openLink(url: string, openWith?: string) {
|
||||||
void shellOpen(url).catch(() => undefined)
|
const isUrl = /^(https?:|mailto:|tel:|opencode:)/.test(url)
|
||||||
|
if (isUrl) return openUrl(url, openWith)
|
||||||
|
return openPath(url, openWith)
|
||||||
},
|
},
|
||||||
|
|
||||||
back() {
|
back() {
|
||||||
@@ -359,7 +361,7 @@ render(() => {
|
|||||||
const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null
|
const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null
|
||||||
if (link?.href) {
|
if (link?.href) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
platform.openLink(link.href)
|
void platform.openLink(link.href).catch(() => undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { Component, JSX } from "solid-js"
|
||||||
|
import { splitProps } from "solid-js"
|
||||||
|
import sprite from "./file-icons/sprite.svg"
|
||||||
|
import type { IconName } from "./file-icons/types"
|
||||||
|
|
||||||
|
export type FileTypeIconProps = JSX.SVGElementTags["svg"] & {
|
||||||
|
id: IconName
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FileTypeIcon: Component<FileTypeIconProps> = (props) => {
|
||||||
|
const [local, rest] = splitProps(props, ["id", "class", "classList"])
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
data-component="file-type-icon"
|
||||||
|
{...rest}
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<use href={`${sprite}#${local.id}`} />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user