1- import { createMemo , createResource , Show } from "solid-js"
1+ import { createEffect , createMemo , onCleanup , Show } from "solid-js"
2+ import { createStore } from "solid-js/store"
23import { Portal } from "solid-js/web"
34import { useParams } from "@solidjs/router"
45import { useLayout } from "@/context/layout"
56import { useCommand } from "@/context/command"
67// import { useServer } from "@/context/server"
78// import { useDialog } from "@opencode-ai/ui/context/dialog"
9+ import { usePlatform } from "@/context/platform"
810import { useSync } from "@/context/sync"
911import { useGlobalSDK } from "@/context/global-sdk"
1012import { getFilename } from "@opencode-ai/util/path"
1113import { base64Decode } from "@opencode-ai/util/encode"
12- import { iife } from "@opencode-ai/util/iife"
14+
1315import { Icon } from "@opencode-ai/ui/icon"
1416import { IconButton } from "@opencode-ai/ui/icon-button"
1517import { Button } from "@opencode-ai/ui/button"
@@ -26,6 +28,7 @@ export function SessionHeader() {
2628 // const server = useServer()
2729 // const dialog = useDialog()
2830 const sync = useSync ( )
31+ const platform = usePlatform ( )
2932
3033 const projectDirectory = createMemo ( ( ) => base64Decode ( params . dir ?? "" ) )
3134 const project = createMemo ( ( ) => {
@@ -45,6 +48,78 @@ export function SessionHeader() {
4548 const sessionKey = createMemo ( ( ) => `${ params . dir } ${ params . id ? "/" + params . id : "" } ` )
4649 const view = createMemo ( ( ) => layout . view ( sessionKey ( ) ) )
4750
51+ const [ state , setState ] = createStore ( {
52+ share : false ,
53+ unshare : false ,
54+ copied : false ,
55+ timer : undefined as number | undefined ,
56+ } )
57+ const shareUrl = createMemo ( ( ) => currentSession ( ) ?. share ?. url )
58+
59+ createEffect ( ( ) => {
60+ const url = shareUrl ( )
61+ if ( url ) return
62+ if ( state . timer ) window . clearTimeout ( state . timer )
63+ setState ( { copied : false , timer : undefined } )
64+ } )
65+
66+ onCleanup ( ( ) => {
67+ if ( state . timer ) window . clearTimeout ( state . timer )
68+ } )
69+
70+ function shareSession ( ) {
71+ const session = currentSession ( )
72+ if ( ! session || state . share ) return
73+ setState ( "share" , true )
74+ globalSDK . client . session
75+ . share ( { sessionID : session . id , directory : projectDirectory ( ) } )
76+ . catch ( ( error ) => {
77+ console . error ( "Failed to share session" , error )
78+ } )
79+ . finally ( ( ) => {
80+ setState ( "share" , false )
81+ } )
82+ }
83+
84+ function unshareSession ( ) {
85+ const session = currentSession ( )
86+ if ( ! session || state . unshare ) return
87+ setState ( "unshare" , true )
88+ globalSDK . client . session
89+ . unshare ( { sessionID : session . id , directory : projectDirectory ( ) } )
90+ . catch ( ( error ) => {
91+ console . error ( "Failed to unshare session" , error )
92+ } )
93+ . finally ( ( ) => {
94+ setState ( "unshare" , false )
95+ } )
96+ }
97+
98+ function copyLink ( ) {
99+ const url = shareUrl ( )
100+ if ( ! url ) return
101+ navigator . clipboard
102+ . writeText ( url )
103+ . then ( ( ) => {
104+ if ( state . timer ) window . clearTimeout ( state . timer )
105+ setState ( "copied" , true )
106+ const timer = window . setTimeout ( ( ) => {
107+ setState ( "copied" , false )
108+ setState ( "timer" , undefined )
109+ } , 3000 )
110+ setState ( "timer" , timer )
111+ } )
112+ . catch ( ( error ) => {
113+ console . error ( "Failed to copy share link" , error )
114+ } )
115+ }
116+
117+ function viewShare ( ) {
118+ const url = shareUrl ( )
119+ if ( ! url ) return
120+ platform . openLink ( url )
121+ }
122+
48123 const centerMount = createMemo ( ( ) => document . getElementById ( "opencode-titlebar-center" ) )
49124 const rightMount = createMemo ( ( ) => document . getElementById ( "opencode-titlebar-right" ) )
50125
@@ -159,40 +234,77 @@ export function SessionHeader() {
159234 </ TooltipKeybind >
160235 </ div >
161236 < Show when = { shareEnabled ( ) && currentSession ( ) } >
162- < Popover
163- title = "Share session"
164- trigger = {
165- < Tooltip class = "shrink-0" value = "Share session" >
166- < IconButton icon = "share" variant = "ghost" class = "" />
167- </ Tooltip >
168- }
169- >
170- { iife ( ( ) => {
171- const [ url ] = createResource (
172- ( ) => currentSession ( ) ,
173- async ( session ) => {
174- if ( ! session ) return
175- let shareURL = session . share ?. url
176- if ( ! shareURL ) {
177- shareURL = await globalSDK . client . session
178- . share ( { sessionID : session . id , directory : projectDirectory ( ) } )
179- . then ( ( r ) => r . data ?. share ?. url )
180- . catch ( ( e ) => {
181- console . error ( "Failed to share session" , e )
182- return undefined
183- } )
237+ < div class = "flex items-center" >
238+ < Popover
239+ title = "Publish on web"
240+ description = {
241+ shareUrl ( )
242+ ? "This session is public on the web. It is accessible to anyone with the link."
243+ : "Share session publicly on the web. It will be accessible to anyone with the link."
244+ }
245+ trigger = {
246+ < Tooltip class = "shrink-0" value = "Share session" >
247+ < Button variant = "secondary" classList = { { "rounded-r-none" : shareUrl ( ) !== undefined } } >
248+ Share
249+ </ Button >
250+ </ Tooltip >
251+ }
252+ >
253+ < div class = "flex flex-col gap-2" >
254+ < Show
255+ when = { shareUrl ( ) }
256+ fallback = {
257+ < div class = "flex" >
258+ < Button
259+ size = "large"
260+ variant = "primary"
261+ class = "w-1/2"
262+ onClick = { shareSession }
263+ disabled = { state . share }
264+ >
265+ { state . share ? "Publishing..." : "Publish" }
266+ </ Button >
267+ </ div >
184268 }
185- return shareURL
186- } ,
187- { initialValue : "" } ,
188- )
189- return (
190- < Show when = { url . latest } >
191- { ( shareUrl ) => < TextField value = { shareUrl ( ) } readOnly copyable class = "w-72" /> }
269+ >
270+ < div class = "flex flex-col gap-2 w-72" >
271+ < TextField value = { shareUrl ( ) ?? "" } readOnly copyable class = "w-full" />
272+ < div class = "grid grid-cols-2 gap-2" >
273+ < Button
274+ size = "large"
275+ variant = "secondary"
276+ class = "w-full shadow-none border border-border-weak-base"
277+ onClick = { unshareSession }
278+ disabled = { state . unshare }
279+ >
280+ { state . unshare ? "Unpublishing..." : "Unpublish" }
281+ </ Button >
282+ < Button
283+ size = "large"
284+ variant = "primary"
285+ class = "w-full"
286+ onClick = { viewShare }
287+ disabled = { state . unshare }
288+ >
289+ View
290+ </ Button >
291+ </ div >
292+ </ div >
192293 </ Show >
193- )
194- } ) }
195- </ Popover >
294+ </ div >
295+ </ Popover >
296+ < Show when = { shareUrl ( ) } >
297+ < Tooltip value = { state . copied ? "Copied" : "Copy link" } placement = "top" gutter = { 8 } >
298+ < IconButton
299+ icon = { state . copied ? "check" : "copy" }
300+ variant = "secondary"
301+ class = "rounded-l-none border-l border-border-weak-base"
302+ onClick = { copyLink }
303+ disabled = { state . unshare }
304+ />
305+ </ Tooltip >
306+ </ Show >
307+ </ div >
196308 </ Show >
197309 </ div >
198310 </ Portal >
0 commit comments