feat(desktop): implement session unshare button (#8660)
This commit is contained in:
committed by
GitHub
parent
0ccf9bd9ac
commit
06bc4dcb06
@@ -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 { Portal } from "solid-js/web"
|
||||||
import { useParams } from "@solidjs/router"
|
import { useParams } from "@solidjs/router"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { useCommand } from "@/context/command"
|
import { useCommand } from "@/context/command"
|
||||||
// import { useServer } from "@/context/server"
|
// import { useServer } from "@/context/server"
|
||||||
// import { useDialog } from "@opencode-ai/ui/context/dialog"
|
// import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
|
import { usePlatform } from "@/context/platform"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { useGlobalSDK } from "@/context/global-sdk"
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { base64Decode } from "@opencode-ai/util/encode"
|
import { base64Decode } from "@opencode-ai/util/encode"
|
||||||
import { iife } from "@opencode-ai/util/iife"
|
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
@@ -26,6 +28,7 @@ export function SessionHeader() {
|
|||||||
// const server = useServer()
|
// const server = useServer()
|
||||||
// const dialog = useDialog()
|
// const dialog = useDialog()
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
|
const platform = usePlatform()
|
||||||
|
|
||||||
const projectDirectory = createMemo(() => base64Decode(params.dir ?? ""))
|
const projectDirectory = createMemo(() => base64Decode(params.dir ?? ""))
|
||||||
const project = createMemo(() => {
|
const project = createMemo(() => {
|
||||||
@@ -45,6 +48,78 @@ export function SessionHeader() {
|
|||||||
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
||||||
const view = createMemo(() => layout.view(sessionKey()))
|
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 centerMount = createMemo(() => document.getElementById("opencode-titlebar-center"))
|
||||||
const rightMount = createMemo(() => document.getElementById("opencode-titlebar-right"))
|
const rightMount = createMemo(() => document.getElementById("opencode-titlebar-right"))
|
||||||
|
|
||||||
@@ -159,40 +234,77 @@ export function SessionHeader() {
|
|||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</div>
|
</div>
|
||||||
<Show when={shareEnabled() && currentSession()}>
|
<Show when={shareEnabled() && currentSession()}>
|
||||||
<Popover
|
<div class="flex items-center">
|
||||||
title="Share session"
|
<Popover
|
||||||
trigger={
|
title="Publish on web"
|
||||||
<Tooltip class="shrink-0" value="Share session">
|
description={
|
||||||
<IconButton icon="share" variant="ghost" class="" />
|
shareUrl()
|
||||||
</Tooltip>
|
? "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."
|
||||||
>
|
}
|
||||||
{iife(() => {
|
trigger={
|
||||||
const [url] = createResource(
|
<Tooltip class="shrink-0" value="Share session">
|
||||||
() => currentSession(),
|
<Button variant="secondary" classList={{ "rounded-r-none": shareUrl() !== undefined }}>
|
||||||
async (session) => {
|
Share
|
||||||
if (!session) return
|
</Button>
|
||||||
let shareURL = session.share?.url
|
</Tooltip>
|
||||||
if (!shareURL) {
|
}
|
||||||
shareURL = await globalSDK.client.session
|
>
|
||||||
.share({ sessionID: session.id, directory: projectDirectory() })
|
<div class="flex flex-col gap-2">
|
||||||
.then((r) => r.data?.share?.url)
|
<Show
|
||||||
.catch((e) => {
|
when={shareUrl()}
|
||||||
console.error("Failed to share session", e)
|
fallback={
|
||||||
return undefined
|
<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
|
>
|
||||||
},
|
<div class="flex flex-col gap-2 w-72">
|
||||||
{ initialValue: "" },
|
<TextField value={shareUrl() ?? ""} readOnly copyable class="w-full" />
|
||||||
)
|
<div class="grid grid-cols-2 gap-2">
|
||||||
return (
|
<Button
|
||||||
<Show when={url.latest}>
|
size="large"
|
||||||
{(shareUrl) => <TextField value={shareUrl()} readOnly copyable class="w-72" />}
|
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>
|
</Show>
|
||||||
)
|
</div>
|
||||||
})}
|
</Popover>
|
||||||
</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>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</Portal>
|
||||||
|
|||||||
@@ -654,6 +654,72 @@ export default function Page() {
|
|||||||
disabled: !params.id || visibleUserMessages().length === 0,
|
disabled: !params.id || visibleUserMessages().length === 0,
|
||||||
onSelect: () => dialog.show(() => <DialogFork />),
|
onSelect: () => dialog.show(() => <DialogFork />),
|
||||||
},
|
},
|
||||||
|
...(sync.data.config.share !== "disabled"
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: "session.share",
|
||||||
|
title: "Share session",
|
||||||
|
description: "Share this session and copy the URL to clipboard",
|
||||||
|
category: "Session",
|
||||||
|
slash: "share",
|
||||||
|
disabled: !params.id || !!info()?.share?.url,
|
||||||
|
onSelect: async () => {
|
||||||
|
if (!params.id) return
|
||||||
|
await sdk.client.session
|
||||||
|
.share({ sessionID: params.id })
|
||||||
|
.then((res) => {
|
||||||
|
navigator.clipboard.writeText(res.data!.share!.url).catch(() =>
|
||||||
|
showToast({
|
||||||
|
title: "Failed to copy URL to clipboard",
|
||||||
|
variant: "error",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.then(() =>
|
||||||
|
showToast({
|
||||||
|
title: "Session shared",
|
||||||
|
description: "Share URL copied to clipboard!",
|
||||||
|
variant: "success",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.catch(() =>
|
||||||
|
showToast({
|
||||||
|
title: "Failed to share session",
|
||||||
|
description: "An error occurred while sharing the session",
|
||||||
|
variant: "error",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "session.unshare",
|
||||||
|
title: "Unshare session",
|
||||||
|
description: "Stop sharing this session",
|
||||||
|
category: "Session",
|
||||||
|
slash: "unshare",
|
||||||
|
disabled: !params.id || !info()?.share?.url,
|
||||||
|
onSelect: async () => {
|
||||||
|
if (!params.id) return
|
||||||
|
await sdk.client.session
|
||||||
|
.unshare({ sessionID: params.id })
|
||||||
|
.then(() =>
|
||||||
|
showToast({
|
||||||
|
title: "Session unshared",
|
||||||
|
description: "Session unshared successfully!",
|
||||||
|
variant: "success",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.catch(() =>
|
||||||
|
showToast({
|
||||||
|
title: "Failed to unshare session",
|
||||||
|
description: "An error occurred while unsharing the session",
|
||||||
|
variant: "error",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
])
|
])
|
||||||
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user