|
|
@@ -1,15 +1,17 @@
|
|
|
-import { createMemo, createResource, Show } from "solid-js"
|
|
|
+import { createEffect, createMemo, onCleanup, Show } from "solid-js"
|
|
|
+import { createStore } from "solid-js/store"
|
|
|
import { Portal } from "solid-js/web"
|
|
|
import { useParams } from "@solidjs/router"
|
|
|
import { useLayout } from "@/context/layout"
|
|
|
import { useCommand } from "@/context/command"
|
|
|
// import { useServer } from "@/context/server"
|
|
|
// import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|
|
+import { usePlatform } from "@/context/platform"
|
|
|
import { useSync } from "@/context/sync"
|
|
|
import { useGlobalSDK } from "@/context/global-sdk"
|
|
|
import { getFilename } from "@opencode-ai/util/path"
|
|
|
import { base64Decode } from "@opencode-ai/util/encode"
|
|
|
-import { iife } from "@opencode-ai/util/iife"
|
|
|
+
|
|
|
import { Icon } from "@opencode-ai/ui/icon"
|
|
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
|
|
import { Button } from "@opencode-ai/ui/button"
|
|
|
@@ -26,6 +28,7 @@ export function SessionHeader() {
|
|
|
// const server = useServer()
|
|
|
// const dialog = useDialog()
|
|
|
const sync = useSync()
|
|
|
+ const platform = usePlatform()
|
|
|
|
|
|
const projectDirectory = createMemo(() => base64Decode(params.dir ?? ""))
|
|
|
const project = createMemo(() => {
|
|
|
@@ -45,6 +48,78 @@ export function SessionHeader() {
|
|
|
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
|
|
const view = createMemo(() => layout.view(sessionKey()))
|
|
|
|
|
|
+ const [state, setState] = createStore({
|
|
|
+ share: false,
|
|
|
+ unshare: false,
|
|
|
+ copied: false,
|
|
|
+ timer: undefined as number | undefined,
|
|
|
+ })
|
|
|
+ const shareUrl = createMemo(() => currentSession()?.share?.url)
|
|
|
+
|
|
|
+ createEffect(() => {
|
|
|
+ const url = shareUrl()
|
|
|
+ if (url) return
|
|
|
+ if (state.timer) window.clearTimeout(state.timer)
|
|
|
+ setState({ copied: false, timer: undefined })
|
|
|
+ })
|
|
|
+
|
|
|
+ onCleanup(() => {
|
|
|
+ if (state.timer) window.clearTimeout(state.timer)
|
|
|
+ })
|
|
|
+
|
|
|
+ function shareSession() {
|
|
|
+ const session = currentSession()
|
|
|
+ if (!session || state.share) return
|
|
|
+ setState("share", true)
|
|
|
+ globalSDK.client.session
|
|
|
+ .share({ sessionID: session.id, directory: projectDirectory() })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error("Failed to share session", error)
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ setState("share", false)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ function unshareSession() {
|
|
|
+ const session = currentSession()
|
|
|
+ if (!session || state.unshare) return
|
|
|
+ setState("unshare", true)
|
|
|
+ globalSDK.client.session
|
|
|
+ .unshare({ sessionID: session.id, directory: projectDirectory() })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error("Failed to unshare session", error)
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ setState("unshare", false)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ function copyLink() {
|
|
|
+ const url = shareUrl()
|
|
|
+ if (!url) return
|
|
|
+ navigator.clipboard
|
|
|
+ .writeText(url)
|
|
|
+ .then(() => {
|
|
|
+ if (state.timer) window.clearTimeout(state.timer)
|
|
|
+ setState("copied", true)
|
|
|
+ const timer = window.setTimeout(() => {
|
|
|
+ setState("copied", false)
|
|
|
+ setState("timer", undefined)
|
|
|
+ }, 3000)
|
|
|
+ setState("timer", timer)
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error("Failed to copy share link", error)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ function viewShare() {
|
|
|
+ const url = shareUrl()
|
|
|
+ if (!url) return
|
|
|
+ platform.openLink(url)
|
|
|
+ }
|
|
|
+
|
|
|
const centerMount = createMemo(() => document.getElementById("opencode-titlebar-center"))
|
|
|
const rightMount = createMemo(() => document.getElementById("opencode-titlebar-right"))
|
|
|
|
|
|
@@ -159,40 +234,77 @@ export function SessionHeader() {
|
|
|
</TooltipKeybind>
|
|
|
</div>
|
|
|
<Show when={shareEnabled() && currentSession()}>
|
|
|
- <Popover
|
|
|
- title="Share session"
|
|
|
- trigger={
|
|
|
- <Tooltip class="shrink-0" value="Share session">
|
|
|
- <IconButton icon="share" variant="ghost" class="" />
|
|
|
- </Tooltip>
|
|
|
- }
|
|
|
- >
|
|
|
- {iife(() => {
|
|
|
- const [url] = createResource(
|
|
|
- () => currentSession(),
|
|
|
- async (session) => {
|
|
|
- if (!session) return
|
|
|
- let shareURL = session.share?.url
|
|
|
- if (!shareURL) {
|
|
|
- shareURL = await globalSDK.client.session
|
|
|
- .share({ sessionID: session.id, directory: projectDirectory() })
|
|
|
- .then((r) => r.data?.share?.url)
|
|
|
- .catch((e) => {
|
|
|
- console.error("Failed to share session", e)
|
|
|
- return undefined
|
|
|
- })
|
|
|
+ <div class="flex items-center">
|
|
|
+ <Popover
|
|
|
+ title="Publish on web"
|
|
|
+ description={
|
|
|
+ shareUrl()
|
|
|
+ ? "This session is public on the web. It is accessible to anyone with the link."
|
|
|
+ : "Share session publicly on the web. It will be accessible to anyone with the link."
|
|
|
+ }
|
|
|
+ trigger={
|
|
|
+ <Tooltip class="shrink-0" value="Share session">
|
|
|
+ <Button variant="secondary" classList={{ "rounded-r-none": shareUrl() !== undefined }}>
|
|
|
+ Share
|
|
|
+ </Button>
|
|
|
+ </Tooltip>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <div class="flex flex-col gap-2">
|
|
|
+ <Show
|
|
|
+ when={shareUrl()}
|
|
|
+ fallback={
|
|
|
+ <div class="flex">
|
|
|
+ <Button
|
|
|
+ size="large"
|
|
|
+ variant="primary"
|
|
|
+ class="w-1/2"
|
|
|
+ onClick={shareSession}
|
|
|
+ disabled={state.share}
|
|
|
+ >
|
|
|
+ {state.share ? "Publishing..." : "Publish"}
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
}
|
|
|
- return shareURL
|
|
|
- },
|
|
|
- { initialValue: "" },
|
|
|
- )
|
|
|
- return (
|
|
|
- <Show when={url.latest}>
|
|
|
- {(shareUrl) => <TextField value={shareUrl()} readOnly copyable class="w-72" />}
|
|
|
+ >
|
|
|
+ <div class="flex flex-col gap-2 w-72">
|
|
|
+ <TextField value={shareUrl() ?? ""} readOnly copyable class="w-full" />
|
|
|
+ <div class="grid grid-cols-2 gap-2">
|
|
|
+ <Button
|
|
|
+ size="large"
|
|
|
+ variant="secondary"
|
|
|
+ class="w-full shadow-none border border-border-weak-base"
|
|
|
+ onClick={unshareSession}
|
|
|
+ disabled={state.unshare}
|
|
|
+ >
|
|
|
+ {state.unshare ? "Unpublishing..." : "Unpublish"}
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ size="large"
|
|
|
+ variant="primary"
|
|
|
+ class="w-full"
|
|
|
+ onClick={viewShare}
|
|
|
+ disabled={state.unshare}
|
|
|
+ >
|
|
|
+ View
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</Show>
|
|
|
- )
|
|
|
- })}
|
|
|
- </Popover>
|
|
|
+ </div>
|
|
|
+ </Popover>
|
|
|
+ <Show when={shareUrl()}>
|
|
|
+ <Tooltip value={state.copied ? "Copied" : "Copy link"} placement="top" gutter={8}>
|
|
|
+ <IconButton
|
|
|
+ icon={state.copied ? "check" : "copy"}
|
|
|
+ variant="secondary"
|
|
|
+ class="rounded-l-none border-l border-border-weak-base"
|
|
|
+ onClick={copyLink}
|
|
|
+ disabled={state.unshare}
|
|
|
+ />
|
|
|
+ </Tooltip>
|
|
|
+ </Show>
|
|
|
+ </div>
|
|
|
</Show>
|
|
|
</div>
|
|
|
</Portal>
|