@keith9681/dsh-sidebar 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/README_EN.md +0 -0
- package/cordis.patch.yml +49 -0
- package/lib/client-editor.js +34651 -0
- package/lib/client-mermaid.js +200629 -0
- package/lib/client-registry.js +13388 -0
- package/lib/client-terminal.js +8904 -0
- package/lib/client.js +13388 -0
- package/lib/index.js +3959 -0
- package/lib/invariant.js +22 -0
- package/lib/types/agent-pty.d.ts +226 -0
- package/lib/types/browser-probe.d.ts +15 -0
- package/lib/types/bundle-route.d.ts +12 -0
- package/lib/types/client/BrowserView.d.ts +22 -0
- package/lib/types/client/DiffTab.d.ts +6 -0
- package/lib/types/client/DiffView.d.ts +51 -0
- package/lib/types/client/EditorHost.d.ts +12 -0
- package/lib/types/client/FileTree.d.ts +36 -0
- package/lib/types/client/GitView.d.ts +8 -0
- package/lib/types/client/OrphanedTab.d.ts +11 -0
- package/lib/types/client/PdfView.d.ts +6 -0
- package/lib/types/client/RenderBoundary.d.ts +32 -0
- package/lib/types/client/SandboxStatusBar.d.ts +10 -0
- package/lib/types/client/SideCardSection.d.ts +74 -0
- package/lib/types/client/SideChatView.d.ts +16 -0
- package/lib/types/client/Sidebar.d.ts +6 -0
- package/lib/types/client/SubagentView.d.ts +16 -0
- package/lib/types/client/TabBar.d.ts +43 -0
- package/lib/types/client/TerminalView.d.ts +24 -0
- package/lib/types/client/TextEditor.d.ts +10 -0
- package/lib/types/client/TreePanel.d.ts +23 -0
- package/lib/types/client/UploadOverlay.d.ts +20 -0
- package/lib/types/client/add-plugin-modal.d.ts +15 -0
- package/lib/types/client/api.d.ts +268 -0
- package/lib/types/client/binary-download.d.ts +12 -0
- package/lib/types/client/breakpoints.d.ts +12 -0
- package/lib/types/client/browser.d.ts +50 -0
- package/lib/types/client/builtins/index.d.ts +18 -0
- package/lib/types/client/builtins/tabs.d.ts +11 -0
- package/lib/types/client/builtins/viewers.d.ts +3 -0
- package/lib/types/client/chunk-loader.d.ts +124 -0
- package/lib/types/client/chunks/editor.d.ts +9 -0
- package/lib/types/client/chunks/mermaid.d.ts +10 -0
- package/lib/types/client/chunks/terminal.d.ts +9 -0
- package/lib/types/client/cm-themes.d.ts +24 -0
- package/lib/types/client/conversation-draft.d.ts +14 -0
- package/lib/types/client/desktop-env.d.ts +35 -0
- package/lib/types/client/editable-state.d.ts +27 -0
- package/lib/types/client/editor-load.d.ts +66 -0
- package/lib/types/client/frame-batcher.d.ts +24 -0
- package/lib/types/client/icons.d.ts +57 -0
- package/lib/types/client/image-types.d.ts +3 -0
- package/lib/types/client/ime-guard.d.ts +36 -0
- package/lib/types/client/index.d.ts +19 -0
- package/lib/types/client/intercept.d.ts +33 -0
- package/lib/types/client/lang.d.ts +12 -0
- package/lib/types/client/lazy-chunk.d.ts +25 -0
- package/lib/types/client/link-intercept.d.ts +42 -0
- package/lib/types/client/locales.d.ts +366 -0
- package/lib/types/client/mermaid-blocks.d.ts +41 -0
- package/lib/types/client/mermaid-sanitize.d.ts +2 -0
- package/lib/types/client/mermaid.d.ts +12 -0
- package/lib/types/client/open-when-sized.d.ts +20 -0
- package/lib/types/client/open-with-settings.d.ts +4 -0
- package/lib/types/client/open-with.d.ts +87 -0
- package/lib/types/client/openpath-intercept.d.ts +41 -0
- package/lib/types/client/paths.d.ts +35 -0
- package/lib/types/client/pdf-types.d.ts +2 -0
- package/lib/types/client/plugin-settings.d.ts +8 -0
- package/lib/types/client/plugins-shared.d.ts +27 -0
- package/lib/types/client/plugins-tabs.d.ts +3 -0
- package/lib/types/client/plugins-viewers.d.ts +3 -0
- package/lib/types/client/prefs.d.ts +41 -0
- package/lib/types/client/produced-files.d.ts +23 -0
- package/lib/types/client/selection-payload.d.ts +27 -0
- package/lib/types/client/service.d.ts +440 -0
- package/lib/types/client/settings-nav-icon.d.ts +19 -0
- package/lib/types/client/shell-presets.d.ts +48 -0
- package/lib/types/client/sidechat-transcript.d.ts +103 -0
- package/lib/types/client/split-pane.d.ts +30 -0
- package/lib/types/client/state.d.ts +359 -0
- package/lib/types/client/subagent-detect.d.ts +54 -0
- package/lib/types/client/subagent-jobs.d.ts +63 -0
- package/lib/types/client/tab-content-memo.d.ts +33 -0
- package/lib/types/client/terminal-font.d.ts +20 -0
- package/lib/types/client/theme.d.ts +45 -0
- package/lib/types/client/titlebar-strip.d.ts +23 -0
- package/lib/types/client/upload.d.ts +69 -0
- package/lib/types/client/wco.d.ts +47 -0
- package/lib/types/config.d.ts +64 -0
- package/lib/types/context-types.d.ts +603 -0
- package/lib/types/fs-operations.d.ts +28 -0
- package/lib/types/fs-search.d.ts +24 -0
- package/lib/types/fs-tree.d.ts +51 -0
- package/lib/types/git.d.ts +82 -0
- package/lib/types/html-route.d.ts +59 -0
- package/lib/types/index.d.ts +47 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/jobs-routes.d.ts +45 -0
- package/lib/types/open-external.d.ts +24 -0
- package/lib/types/prefs-shared.d.ts +225 -0
- package/lib/types/pty-deps.d.ts +78 -0
- package/lib/types/pty-manager.d.ts +168 -0
- package/lib/types/sidechat-core.d.ts +172 -0
- package/lib/types/sidechat-routes.d.ts +30 -0
- package/lib/types/subagent-activity.d.ts +44 -0
- package/lib/types/subagent-live-route.d.ts +44 -0
- package/lib/types/tools.d.ts +31 -0
- package/lib/types/trust-fence.d.ts +24 -0
- package/lib/types/wire.d.ts +38 -0
- package/package.json +173 -0
- package/scripts/install.ps1 +289 -0
- package/scripts/install.sh +287 -0
- package/src/agent-pty.ts +527 -0
- package/src/browser-probe.ts +26 -0
- package/src/bundle-route.ts +129 -0
- package/src/client/BrowserView.tsx +253 -0
- package/src/client/DiffTab.tsx +110 -0
- package/src/client/DiffView.tsx +305 -0
- package/src/client/EditorHost.tsx +525 -0
- package/src/client/FileTree.tsx +653 -0
- package/src/client/GitView.tsx +558 -0
- package/src/client/OrphanedTab.tsx +27 -0
- package/src/client/PdfView.tsx +110 -0
- package/src/client/RenderBoundary.tsx +49 -0
- package/src/client/SandboxStatusBar.tsx +60 -0
- package/src/client/SideCardSection.module.css +960 -0
- package/src/client/SideCardSection.tsx +1152 -0
- package/src/client/SideChatView.module.css +437 -0
- package/src/client/SideChatView.tsx +644 -0
- package/src/client/Sidebar.tsx +1148 -0
- package/src/client/SubagentView.module.css +547 -0
- package/src/client/SubagentView.tsx +883 -0
- package/src/client/TabBar.tsx +305 -0
- package/src/client/TerminalView.tsx +383 -0
- package/src/client/TextEditor.tsx +500 -0
- package/src/client/TreePanel.tsx +273 -0
- package/src/client/UploadOverlay.tsx +62 -0
- package/src/client/add-plugin-modal.tsx +199 -0
- package/src/client/api.ts +335 -0
- package/src/client/binary-download.tsx +23 -0
- package/src/client/breakpoints.ts +51 -0
- package/src/client/browser.ts +119 -0
- package/src/client/builtins/index.ts +37 -0
- package/src/client/builtins/tabs.tsx +331 -0
- package/src/client/builtins/viewers.tsx +125 -0
- package/src/client/chunk-loader.ts +351 -0
- package/src/client/chunks/editor.tsx +9 -0
- package/src/client/chunks/mermaid.tsx +10 -0
- package/src/client/chunks/terminal.tsx +9 -0
- package/src/client/cm-themes.ts +129 -0
- package/src/client/conversation-draft.ts +29 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/desktop-env.ts +72 -0
- package/src/client/editable-state.ts +70 -0
- package/src/client/editor-load.ts +92 -0
- package/src/client/frame-batcher.ts +56 -0
- package/src/client/icons.tsx +161 -0
- package/src/client/image-types.ts +8 -0
- package/src/client/ime-guard.ts +47 -0
- package/src/client/index.tsx +369 -0
- package/src/client/intercept.tsx +110 -0
- package/src/client/lang.ts +103 -0
- package/src/client/layout.css +94 -0
- package/src/client/lazy-chunk.tsx +89 -0
- package/src/client/link-intercept.ts +73 -0
- package/src/client/locales.ts +734 -0
- package/src/client/mermaid-blocks.ts +110 -0
- package/src/client/mermaid-sanitize.ts +93 -0
- package/src/client/mermaid.tsx +401 -0
- package/src/client/open-when-sized.ts +43 -0
- package/src/client/open-with-settings.tsx +135 -0
- package/src/client/open-with.ts +221 -0
- package/src/client/openpath-intercept.ts +62 -0
- package/src/client/paths.ts +47 -0
- package/src/client/pdf-types.ts +4 -0
- package/src/client/plugin-settings.ts +43 -0
- package/src/client/plugins-shared.ts +29 -0
- package/src/client/plugins-tabs.ts +87 -0
- package/src/client/plugins-viewers.ts +30 -0
- package/src/client/prefs.ts +207 -0
- package/src/client/produced-files.ts +91 -0
- package/src/client/selection-payload.ts +86 -0
- package/src/client/service.ts +838 -0
- package/src/client/settings-nav-icon.ts +45 -0
- package/src/client/shell-presets.ts +83 -0
- package/src/client/sidebar.module.css +2577 -0
- package/src/client/sidechat-transcript.ts +321 -0
- package/src/client/split-pane.tsx +322 -0
- package/src/client/state.ts +1175 -0
- package/src/client/subagent-detect.ts +137 -0
- package/src/client/subagent-jobs.ts +161 -0
- package/src/client/tab-content-memo.ts +46 -0
- package/src/client/terminal-font.ts +28 -0
- package/src/client/theme.ts +110 -0
- package/src/client/titlebar-strip.ts +37 -0
- package/src/client/upload.ts +187 -0
- package/src/client/wco.ts +120 -0
- package/src/config.ts +155 -0
- package/src/context-types.ts +610 -0
- package/src/fs-operations.ts +99 -0
- package/src/fs-search.ts +86 -0
- package/src/fs-tree.ts +158 -0
- package/src/git.ts +244 -0
- package/src/html-route.ts +106 -0
- package/src/index.ts +1132 -0
- package/src/invariant.ts +32 -0
- package/src/jobs-routes.ts +257 -0
- package/src/open-external.ts +90 -0
- package/src/prefs-shared.ts +268 -0
- package/src/pty-deps.ts +240 -0
- package/src/pty-manager.ts +376 -0
- package/src/sidechat-core.ts +468 -0
- package/src/sidechat-routes.ts +344 -0
- package/src/subagent-activity.ts +91 -0
- package/src/subagent-live-route.ts +93 -0
- package/src/tools.ts +479 -0
- package/src/trust-fence.ts +77 -0
- package/src/wire.ts +100 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/** Browser-native PDF preview with an always-available download fallback. */
|
|
2
|
+
import { useEffect, useRef, useState } from 'react'
|
|
3
|
+
import clsx from 'clsx'
|
|
4
|
+
import { downloadUrl, mediaUrl, type SessionScope } from './api.ts'
|
|
5
|
+
import { t } from './locales.ts'
|
|
6
|
+
import css from './sidebar.module.css'
|
|
7
|
+
|
|
8
|
+
export function PdfView(props: { scope: SessionScope; path: string; title: string }) {
|
|
9
|
+
const { scope, path, title } = props
|
|
10
|
+
const [load, setLoad] = useState<
|
|
11
|
+
| { status: 'loading' }
|
|
12
|
+
| { status: 'ready'; url: string }
|
|
13
|
+
| { status: 'error'; message: string }
|
|
14
|
+
>({ status: 'loading' })
|
|
15
|
+
const [interactionBlocked, setInteractionBlocked] = useState(false)
|
|
16
|
+
const frameRef = useRef<HTMLIFrameElement>(null)
|
|
17
|
+
const shieldRef = useRef<HTMLDivElement>(null)
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const controller = new AbortController()
|
|
21
|
+
let objectUrl: string | undefined
|
|
22
|
+
setLoad({ status: 'loading' })
|
|
23
|
+
void (async () => {
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(mediaUrl(scope, path), { signal: controller.signal })
|
|
26
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
27
|
+
const bytes = await response.arrayBuffer()
|
|
28
|
+
if (controller.signal.aborted) return
|
|
29
|
+
// A direct iframe navigation may download when an old host process or
|
|
30
|
+
// proxy cached application/octet-stream. The Blob URL owns an explicit
|
|
31
|
+
// PDF MIME and therefore consistently opens the browser PDF viewer.
|
|
32
|
+
objectUrl = URL.createObjectURL(new Blob([bytes], { type: 'application/pdf' }))
|
|
33
|
+
setLoad({ status: 'ready', url: objectUrl })
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (controller.signal.aborted) return
|
|
36
|
+
setLoad({ status: 'error', message: error instanceof Error ? error.message : String(error) })
|
|
37
|
+
}
|
|
38
|
+
})()
|
|
39
|
+
return () => {
|
|
40
|
+
controller.abort()
|
|
41
|
+
if (objectUrl !== undefined) URL.revokeObjectURL(objectUrl)
|
|
42
|
+
}
|
|
43
|
+
}, [scope.sessionId, scope.cwd, path])
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const block = (): void => {
|
|
47
|
+
setInteractionBlocked(true)
|
|
48
|
+
if (frameRef.current !== null) frameRef.current.style.pointerEvents = 'none'
|
|
49
|
+
if (shieldRef.current !== null) shieldRef.current.style.pointerEvents = 'auto'
|
|
50
|
+
}
|
|
51
|
+
const unblock = (): void => {
|
|
52
|
+
setInteractionBlocked(false)
|
|
53
|
+
if (frameRef.current !== null) frameRef.current.style.pointerEvents = ''
|
|
54
|
+
if (shieldRef.current !== null) shieldRef.current.style.pointerEvents = 'none'
|
|
55
|
+
}
|
|
56
|
+
const blockForResize = (event: PointerEvent): void => {
|
|
57
|
+
const target = event.target
|
|
58
|
+
if (target instanceof Element
|
|
59
|
+
&& target.closest(`.${css.panelResize}, .${css.divider}`) !== null) {
|
|
60
|
+
block()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// An iframe is a separate browsing context and otherwise swallows the
|
|
64
|
+
// dragover/pointer stream as soon as the cursor crosses it. Temporarily
|
|
65
|
+
// remove it from hit-testing while a tab drag or pane resize is active.
|
|
66
|
+
document.addEventListener('dragstart', block, true)
|
|
67
|
+
document.addEventListener('dragend', unblock, true)
|
|
68
|
+
document.addEventListener('drop', unblock, true)
|
|
69
|
+
window.addEventListener('pointerdown', blockForResize, true)
|
|
70
|
+
window.addEventListener('pointerup', unblock, true)
|
|
71
|
+
window.addEventListener('pointercancel', unblock, true)
|
|
72
|
+
window.addEventListener('blur', unblock)
|
|
73
|
+
return () => {
|
|
74
|
+
document.removeEventListener('dragstart', block, true)
|
|
75
|
+
document.removeEventListener('dragend', unblock, true)
|
|
76
|
+
document.removeEventListener('drop', unblock, true)
|
|
77
|
+
window.removeEventListener('pointerdown', blockForResize, true)
|
|
78
|
+
window.removeEventListener('pointerup', unblock, true)
|
|
79
|
+
window.removeEventListener('pointercancel', unblock, true)
|
|
80
|
+
window.removeEventListener('blur', unblock)
|
|
81
|
+
}
|
|
82
|
+
}, [])
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className={css.editorPdf}>
|
|
86
|
+
<div className={css.editorPdfToolbar}>
|
|
87
|
+
<a className={css.editorDownloadLink} href={downloadUrl(scope, path)} download>
|
|
88
|
+
{t('downloadToView')}
|
|
89
|
+
</a>
|
|
90
|
+
</div>
|
|
91
|
+
<div className={css.editorPdfStage}>
|
|
92
|
+
{load.status === 'loading' && <div className={css.editorPlaceholder}>{t('loading')}</div>}
|
|
93
|
+
{load.status === 'error' && <div className={css.editorError}>{load.message}</div>}
|
|
94
|
+
{load.status === 'ready' && (
|
|
95
|
+
<iframe
|
|
96
|
+
ref={frameRef}
|
|
97
|
+
className={clsx(css.editorPdfFrame, interactionBlocked && css.editorPdfFrameBlocked)}
|
|
98
|
+
src={load.url}
|
|
99
|
+
title={title}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
<div
|
|
103
|
+
ref={shieldRef}
|
|
104
|
+
className={clsx(css.editorPdfDragShield, interactionBlocked && css.editorPdfDragShieldActive)}
|
|
105
|
+
aria-hidden="true"
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generic render error boundary for the sidebar tree: a render error in
|
|
3
|
+
* the wrapped subtree shows a dismissible error strip (retry re-renders the
|
|
4
|
+
* children) instead of blanking the shell. Used at two scopes:
|
|
5
|
+
*
|
|
6
|
+
* - ROOT (index.tsx, `css.boundaryError`): last-resort containment for
|
|
7
|
+
* errors in the sidebar shell itself (Workbench, drag layout, …) — a full
|
|
8
|
+
* swap keeps the page alive.
|
|
9
|
+
* - PER-TAB (Sidebar.tsx TabContent, `css.tabBoundaryError`): a crashing
|
|
10
|
+
* viewer/editor shows a strip inside ITS OWN pane; the toggle cluster, the
|
|
11
|
+
* other tabs, and the panel itself stay alive (issue #31 — a tab crash
|
|
12
|
+
* must never take down the whole sidebar).
|
|
13
|
+
*
|
|
14
|
+
* The className prop selects the strip's geometry: the root's full-height
|
|
15
|
+
* fixed rail vs. the tab's pane-filling block.
|
|
16
|
+
*/
|
|
17
|
+
import { Component, type ErrorInfo, type ReactNode } from 'react'
|
|
18
|
+
import { t } from './locales.ts'
|
|
19
|
+
import css from './sidebar.module.css'
|
|
20
|
+
|
|
21
|
+
export class RenderBoundary extends Component<{ children?: ReactNode; className?: string }, { error: string | null }> {
|
|
22
|
+
state = { error: null as string | null }
|
|
23
|
+
|
|
24
|
+
static getDerivedStateFromError(error: unknown): { error: string } {
|
|
25
|
+
return { error: error instanceof Error ? error.message : String(error) }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
componentDidCatch(error: Error, info: ErrorInfo): void {
|
|
29
|
+
console.error('[dsh-better-sidebar] render error:', error, info.componentStack)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
render(): ReactNode {
|
|
33
|
+
if (this.state.error !== null) {
|
|
34
|
+
return (
|
|
35
|
+
<div className={this.props.className}>
|
|
36
|
+
<span>dsh-better-sidebar: {this.state.error}</span>
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
className={css.terminalRetry}
|
|
40
|
+
onClick={() => { this.setState({ error: null }) }}
|
|
41
|
+
>
|
|
42
|
+
{t('terminalRetry')}
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
return this.props.children
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The live sandbox status row of the two built-in web surfaces (HTML
|
|
3
|
+
* preview and the browser tab): a green "sandbox on" state with a one-tap
|
|
4
|
+
* TEMPORARY unlock, or a RED "sandbox off" state (global setting or the
|
|
5
|
+
* temporary unlock) with a restore action.
|
|
6
|
+
*
|
|
7
|
+
* The temporary unlock is component state only — it never writes the
|
|
8
|
+
* global side card setting (`htmlViewerNoSandbox` / `browserNoSandbox`);
|
|
9
|
+
* it lasts until the surface unmounts (tab switch / file switch) or the
|
|
10
|
+
* user restores the sandbox from the row. When the global setting already
|
|
11
|
+
* drops the sandbox, no unlock/restore action is offered (changing the
|
|
12
|
+
* global setting is the settings page's job) — the red warning stands.
|
|
13
|
+
*/
|
|
14
|
+
import clsx from 'clsx'
|
|
15
|
+
import { t } from './locales.ts'
|
|
16
|
+
import css from './sidebar.module.css'
|
|
17
|
+
|
|
18
|
+
export function SandboxStatusBar(props: {
|
|
19
|
+
/** The effective sandbox state (global pref OR the local temporary unlock). */
|
|
20
|
+
sandboxed: boolean
|
|
21
|
+
/** Whether the sandbox is off due to the LOCAL temporary unlock (shows the restore action). */
|
|
22
|
+
local: boolean
|
|
23
|
+
/** The red-state explanation (e.g. "the page runs with full GUI privileges"). */
|
|
24
|
+
dangerCopy: string
|
|
25
|
+
onUnlock: () => void
|
|
26
|
+
onRestore: () => void
|
|
27
|
+
}) {
|
|
28
|
+
const { sandboxed, local, dangerCopy, onUnlock, onRestore } = props
|
|
29
|
+
if (sandboxed) {
|
|
30
|
+
const copy = t('sandboxStatusOn')
|
|
31
|
+
return (
|
|
32
|
+
<div className={clsx(css.sandboxStatus, css.sandboxStatusOn)}>
|
|
33
|
+
<span className={css.sandboxDot} />
|
|
34
|
+
<span className={css.sandboxStatusText} title={copy}>{copy}</span>
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
className={css.sandboxAction}
|
|
38
|
+
onClick={onUnlock}
|
|
39
|
+
>
|
|
40
|
+
{t('sandboxUnlock')}
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
return (
|
|
46
|
+
<div className={clsx(css.sandboxStatus, css.sandboxStatusOff)}>
|
|
47
|
+
<span className={css.sandboxDot} />
|
|
48
|
+
<span className={css.sandboxStatusText} title={dangerCopy}>{dangerCopy}</span>
|
|
49
|
+
{local && (
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
className={css.sandboxAction}
|
|
53
|
+
onClick={onRestore}
|
|
54
|
+
>
|
|
55
|
+
{t('sandboxRestore')}
|
|
56
|
+
</button>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
}
|