@tscircuit/fake-snippets 0.0.87 → 0.0.89
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/api/generated-index.js +96 -14
- package/bun-tests/fake-snippets-api/routes/proxy.test.ts +42 -0
- package/bun.lock +187 -206
- package/dist/bundle.js +207 -101
- package/fake-snippets-api/routes/api/package_releases/create.ts +1 -1
- package/fake-snippets-api/routes/api/proxy.ts +128 -0
- package/package.json +57 -50
- package/renovate.json +2 -1
- package/src/App.tsx +22 -3
- package/src/ContextProviders.tsx +2 -0
- package/src/build-watcher.ts +52 -0
- package/src/components/CmdKMenu.tsx +533 -197
- package/src/components/DownloadButtonAndMenu.tsx +104 -26
- package/src/components/FileSidebar.tsx +11 -1
- package/src/components/Header.tsx +5 -1
- package/src/components/Header2.tsx +7 -2
- package/src/components/HeaderLogin.tsx +1 -1
- package/src/components/PackageBuildsPage/LogContent.tsx +25 -22
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +6 -6
- package/src/components/PackageBuildsPage/build-preview-content.tsx +5 -5
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +15 -13
- package/src/components/PackageBuildsPage/package-build-header.tsx +17 -16
- package/src/components/PackageCard.tsx +66 -16
- package/src/components/PrefetchPageLink.tsx +66 -15
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/SuspenseRunFrame.tsx +14 -2
- package/src/components/ViewPackagePage/components/important-files-view.tsx +97 -22
- package/src/components/ViewPackagePage/components/main-content-header.tsx +27 -3
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +2 -2
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +49 -34
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +2 -2
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +20 -12
- package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +0 -7
- package/src/components/ViewPackagePage/utils/fuzz-search.ts +121 -0
- package/src/components/ViewPackagePage/utils/is-hidden-file.ts +4 -0
- package/src/components/ViewPackagePage/utils/is-package-file-important.ts +18 -5
- package/src/components/dialogs/confirm-delete-package-dialog.tsx +1 -1
- package/src/components/dialogs/confirm-discard-changes-dialog.tsx +73 -0
- package/src/components/dialogs/edit-package-details-dialog.tsx +2 -2
- package/src/components/dialogs/view-ts-files-dialog.tsx +478 -42
- package/src/components/package-port/CodeAndPreview.tsx +16 -0
- package/src/components/package-port/CodeEditor.tsx +113 -11
- package/src/components/package-port/CodeEditorHeader.tsx +39 -4
- package/src/components/package-port/EditorNav.tsx +41 -15
- package/src/components/package-port/GlobalFindReplace.tsx +681 -0
- package/src/components/package-port/QuickOpen.tsx +241 -0
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/tree-view.tsx +1 -1
- package/src/global.d.ts +3 -0
- package/src/hooks/use-ai-review.ts +31 -0
- package/src/hooks/use-current-package-release.ts +5 -1
- package/src/hooks/use-download-zip.ts +50 -0
- package/src/hooks/use-hotkey.ts +116 -0
- package/src/hooks/use-package-by-package-id.ts +1 -0
- package/src/hooks/use-package-by-package-name.ts +1 -0
- package/src/hooks/use-package-files.ts +3 -0
- package/src/hooks/use-package-release.ts +5 -1
- package/src/hooks/use-package.ts +1 -0
- package/src/hooks/use-request-ai-review-mutation.ts +14 -6
- package/src/hooks/use-snippet.ts +1 -0
- package/src/hooks/useFileManagement.ts +26 -8
- package/src/hooks/usePackageFilesLoader.ts +3 -1
- package/src/index.css +11 -0
- package/src/lib/decodeUrlHashToFsMap.ts +17 -0
- package/src/lib/download-fns/download-circuit-png.ts +88 -0
- package/src/lib/download-fns/download-png-utils.ts +31 -0
- package/src/lib/encodeFsMapToUrlHash.ts +13 -0
- package/src/lib/populate-query-cache-with-ssr-data.ts +39 -38
- package/src/lib/ts-lib-cache.ts +47 -0
- package/src/lib/types.ts +2 -0
- package/src/main.tsx +7 -0
- package/src/pages/dashboard.tsx +8 -5
- package/src/pages/user-profile.tsx +1 -1
- package/src/pages/view-package.tsx +15 -7
- package/vite.config.ts +100 -1
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
|
|
2
|
+
import { useHotkeyCombo } from "@/hooks/use-hotkey"
|
|
2
3
|
import { basicSetup } from "@/lib/codemirror/basic-setup"
|
|
3
|
-
import {
|
|
4
|
-
|
|
4
|
+
import {
|
|
5
|
+
autocompletion,
|
|
6
|
+
acceptCompletion,
|
|
7
|
+
completionStatus,
|
|
8
|
+
} from "@codemirror/autocomplete"
|
|
9
|
+
import { indentWithTab, indentMore } from "@codemirror/commands"
|
|
5
10
|
import { javascript } from "@codemirror/lang-javascript"
|
|
6
11
|
import { json } from "@codemirror/lang-json"
|
|
7
12
|
import { EditorState, Prec } from "@codemirror/state"
|
|
@@ -12,10 +17,10 @@ import { setupTypeAcquisition } from "@typescript/ata"
|
|
|
12
17
|
import { linter } from "@codemirror/lint"
|
|
13
18
|
import { TSCI_PACKAGE_PATTERN } from "@/lib/constants"
|
|
14
19
|
import {
|
|
15
|
-
createDefaultMapFromCDN,
|
|
16
20
|
createSystem,
|
|
17
21
|
createVirtualTypeScriptEnvironment,
|
|
18
22
|
} from "@typescript/vfs"
|
|
23
|
+
import { loadDefaultLibMap } from "@/lib/ts-lib-cache"
|
|
19
24
|
import { tsAutocomplete, tsFacet, tsSync } from "@valtown/codemirror-ts"
|
|
20
25
|
import { getLints } from "@valtown/codemirror-ts"
|
|
21
26
|
import { EditorView } from "codemirror"
|
|
@@ -29,12 +34,15 @@ import FileSidebar from "../FileSidebar"
|
|
|
29
34
|
import { findTargetFile } from "@/lib/utils/findTargetFile"
|
|
30
35
|
import type { PackageFile } from "./CodeAndPreview"
|
|
31
36
|
import { useShikiHighlighter } from "@/hooks/use-shiki-highlighter"
|
|
37
|
+
import QuickOpen from "./QuickOpen"
|
|
38
|
+
import GlobalFindReplace from "./GlobalFindReplace"
|
|
32
39
|
import {
|
|
33
40
|
ICreateFileProps,
|
|
34
41
|
ICreateFileResult,
|
|
35
42
|
IDeleteFileProps,
|
|
36
43
|
IDeleteFileResult,
|
|
37
44
|
} from "@/hooks/useFileManagement"
|
|
45
|
+
import { isHiddenFile } from "../ViewPackagePage/utils/is-hidden-file"
|
|
38
46
|
|
|
39
47
|
const defaultImports = `
|
|
40
48
|
import React from "@types/react/jsx-runtime"
|
|
@@ -77,6 +85,9 @@ export const CodeEditor = ({
|
|
|
77
85
|
const codeCompletionApi = useCodeCompletionApi()
|
|
78
86
|
const [cursorPosition, setCursorPosition] = useState<number | null>(null)
|
|
79
87
|
const [code, setCode] = useState(files[0]?.content || "")
|
|
88
|
+
const [fontSize, setFontSize] = useState(14)
|
|
89
|
+
const [showQuickOpen, setShowQuickOpen] = useState(false)
|
|
90
|
+
const [showGlobalFindReplace, setShowGlobalFindReplace] = useState(false)
|
|
80
91
|
|
|
81
92
|
const { highlighter } = useShikiHighlighter()
|
|
82
93
|
|
|
@@ -132,6 +143,14 @@ export const CodeEditor = ({
|
|
|
132
143
|
}
|
|
133
144
|
}, [isStreaming])
|
|
134
145
|
|
|
146
|
+
useHotkeyCombo(
|
|
147
|
+
"cmd+b",
|
|
148
|
+
() => {
|
|
149
|
+
setSidebarOpen((prev) => !prev)
|
|
150
|
+
},
|
|
151
|
+
{ target: window },
|
|
152
|
+
)
|
|
153
|
+
|
|
135
154
|
useEffect(() => {
|
|
136
155
|
if (!editorRef.current) return
|
|
137
156
|
|
|
@@ -141,12 +160,7 @@ export const CodeEditor = ({
|
|
|
141
160
|
})
|
|
142
161
|
;(window as any).__DEBUG_CODE_EDITOR_FS_MAP = fsMap
|
|
143
162
|
|
|
144
|
-
|
|
145
|
-
{ target: tsModule.ScriptTarget.ES2022 },
|
|
146
|
-
"5.6.3",
|
|
147
|
-
true,
|
|
148
|
-
tsModule,
|
|
149
|
-
).then((defaultFsMap) => {
|
|
163
|
+
loadDefaultLibMap().then((defaultFsMap) => {
|
|
150
164
|
defaultFsMap.forEach((content, filename) => {
|
|
151
165
|
fsMap.set(filename, content)
|
|
152
166
|
})
|
|
@@ -243,6 +257,29 @@ export const CodeEditor = ({
|
|
|
243
257
|
key: "Mod-Enter",
|
|
244
258
|
run: () => true,
|
|
245
259
|
},
|
|
260
|
+
{
|
|
261
|
+
key: "Tab",
|
|
262
|
+
run: (view) => {
|
|
263
|
+
if (completionStatus(view.state) === "active") {
|
|
264
|
+
return acceptCompletion(view)
|
|
265
|
+
}
|
|
266
|
+
return indentMore(view)
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
key: "Mod-p",
|
|
271
|
+
run: () => {
|
|
272
|
+
setShowQuickOpen(true)
|
|
273
|
+
return true
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
key: "Mod-Shift-f",
|
|
278
|
+
run: () => {
|
|
279
|
+
setShowGlobalFindReplace(true)
|
|
280
|
+
return true
|
|
281
|
+
},
|
|
282
|
+
},
|
|
246
283
|
]),
|
|
247
284
|
),
|
|
248
285
|
keymap.of([indentWithTab]),
|
|
@@ -263,6 +300,29 @@ export const CodeEditor = ({
|
|
|
263
300
|
setCursorPosition(pos)
|
|
264
301
|
}
|
|
265
302
|
}),
|
|
303
|
+
EditorView.theme({
|
|
304
|
+
".cm-editor": {
|
|
305
|
+
fontSize: `${fontSize}px`,
|
|
306
|
+
},
|
|
307
|
+
".cm-content": {
|
|
308
|
+
fontSize: `${fontSize}px`,
|
|
309
|
+
},
|
|
310
|
+
}),
|
|
311
|
+
EditorView.domEventHandlers({
|
|
312
|
+
wheel: (event) => {
|
|
313
|
+
if (event.ctrlKey || event.metaKey) {
|
|
314
|
+
event.preventDefault()
|
|
315
|
+
const delta = event.deltaY
|
|
316
|
+
setFontSize((prev) => {
|
|
317
|
+
const newSize =
|
|
318
|
+
delta > 0 ? Math.max(8, prev - 1) : Math.min(32, prev + 1)
|
|
319
|
+
return newSize
|
|
320
|
+
})
|
|
321
|
+
return true
|
|
322
|
+
}
|
|
323
|
+
return false
|
|
324
|
+
},
|
|
325
|
+
}),
|
|
266
326
|
]
|
|
267
327
|
if (codeCompletionApi?.apiKey) {
|
|
268
328
|
baseExtensions.push(
|
|
@@ -470,7 +530,14 @@ export const CodeEditor = ({
|
|
|
470
530
|
return () => {
|
|
471
531
|
view.destroy()
|
|
472
532
|
}
|
|
473
|
-
}, [
|
|
533
|
+
}, [
|
|
534
|
+
!isStreaming,
|
|
535
|
+
currentFile,
|
|
536
|
+
code !== "",
|
|
537
|
+
Boolean(highlighter),
|
|
538
|
+
isSaving,
|
|
539
|
+
fontSize,
|
|
540
|
+
])
|
|
474
541
|
|
|
475
542
|
const updateCurrentEditorContent = (newContent: string) => {
|
|
476
543
|
if (viewRef.current) {
|
|
@@ -541,12 +608,30 @@ export const CodeEditor = ({
|
|
|
541
608
|
updateEditorToMatchCurrentFile()
|
|
542
609
|
}, [currentFile])
|
|
543
610
|
|
|
611
|
+
// Global keyboard listeners
|
|
612
|
+
useHotkeyCombo("cmd+p", () => {
|
|
613
|
+
setShowQuickOpen(true)
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
useHotkeyCombo("cmd+shift+f", () => {
|
|
617
|
+
setShowGlobalFindReplace(true)
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
useHotkeyCombo("Escape", () => {
|
|
621
|
+
if (showQuickOpen) {
|
|
622
|
+
setShowQuickOpen(false)
|
|
623
|
+
}
|
|
624
|
+
if (showGlobalFindReplace) {
|
|
625
|
+
setShowGlobalFindReplace(false)
|
|
626
|
+
}
|
|
627
|
+
})
|
|
628
|
+
|
|
544
629
|
if (isStreaming) {
|
|
545
630
|
return <div className="font-mono whitespace-pre-wrap text-xs">{code}</div>
|
|
546
631
|
}
|
|
547
632
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
548
633
|
return (
|
|
549
|
-
<div className="flex h-
|
|
634
|
+
<div className="flex h-[98vh] w-full overflow-hidden">
|
|
550
635
|
<FileSidebar
|
|
551
636
|
files={Object.fromEntries(files.map((f) => [f.path, f.content]))}
|
|
552
637
|
currentFile={currentFile}
|
|
@@ -579,6 +664,23 @@ export const CodeEditor = ({
|
|
|
579
664
|
}
|
|
580
665
|
/>
|
|
581
666
|
</div>
|
|
667
|
+
{showQuickOpen && (
|
|
668
|
+
<QuickOpen
|
|
669
|
+
files={files.filter((f) => !isHiddenFile(f.path))}
|
|
670
|
+
currentFile={currentFile}
|
|
671
|
+
onFileSelect={handleFileChange}
|
|
672
|
+
onClose={() => setShowQuickOpen(false)}
|
|
673
|
+
/>
|
|
674
|
+
)}
|
|
675
|
+
{showGlobalFindReplace && (
|
|
676
|
+
<GlobalFindReplace
|
|
677
|
+
files={files.filter((f) => !isHiddenFile(f.path))}
|
|
678
|
+
currentFile={currentFile}
|
|
679
|
+
onFileSelect={handleFileChange}
|
|
680
|
+
onFileContentChanged={onCodeChange}
|
|
681
|
+
onClose={() => setShowGlobalFindReplace(false)}
|
|
682
|
+
/>
|
|
683
|
+
)}
|
|
582
684
|
</div>
|
|
583
685
|
)
|
|
584
686
|
}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
DropdownMenuItem,
|
|
10
10
|
DropdownMenuTrigger,
|
|
11
11
|
} from "@/components/ui/dropdown-menu"
|
|
12
|
-
import { AlertTriangle, PanelRightClose } from "lucide-react"
|
|
12
|
+
import { AlertTriangle, PanelRightClose, Bot } from "lucide-react"
|
|
13
13
|
import { checkIfManualEditsImported } from "@/lib/utils/checkIfManualEditsImported"
|
|
14
14
|
import {
|
|
15
15
|
Select,
|
|
@@ -20,6 +20,12 @@ import {
|
|
|
20
20
|
} from "../ui/select"
|
|
21
21
|
import { isHiddenFile } from "../ViewPackagePage/utils/is-hidden-file"
|
|
22
22
|
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
23
|
+
import {
|
|
24
|
+
Tooltip,
|
|
25
|
+
TooltipContent,
|
|
26
|
+
TooltipProvider,
|
|
27
|
+
TooltipTrigger,
|
|
28
|
+
} from "@/components/ui/tooltip"
|
|
23
29
|
|
|
24
30
|
export type FileName = string
|
|
25
31
|
|
|
@@ -44,6 +50,7 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
44
50
|
useImportPackageDialog()
|
|
45
51
|
const { toast } = useToast()
|
|
46
52
|
const [sidebarOpen, setSidebarOpen] = fileSidebarState
|
|
53
|
+
const [aiAutocompleteEnabled, setAiAutocompleteEnabled] = useState(false)
|
|
47
54
|
|
|
48
55
|
const handleFormatFile = useCallback(() => {
|
|
49
56
|
if (!window.prettier || !window.prettierPlugins) return
|
|
@@ -152,7 +159,7 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
152
159
|
<div>
|
|
153
160
|
<Select value={currentFile || ""} onValueChange={handleFileChange}>
|
|
154
161
|
<SelectTrigger
|
|
155
|
-
className={`h-7 w-
|
|
162
|
+
className={`h-7 w-32 sm:w-48 px-3 bg-white select-none transition-[margin] duration-300 ease-in-out ${
|
|
156
163
|
sidebarOpen ? "-ml-2" : "-ml-1"
|
|
157
164
|
}`}
|
|
158
165
|
>
|
|
@@ -170,7 +177,9 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
170
177
|
<SelectItem className="py-1" key={filename} value={filename}>
|
|
171
178
|
<span
|
|
172
179
|
className={`text-xs pr-1 block truncate ${
|
|
173
|
-
sidebarOpen
|
|
180
|
+
sidebarOpen
|
|
181
|
+
? "max-w-[8rem] sm:max-w-[12rem]"
|
|
182
|
+
: "max-w-[12rem] sm:max-w-[16rem]"
|
|
174
183
|
}`}
|
|
175
184
|
>
|
|
176
185
|
{filename}
|
|
@@ -187,7 +196,9 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
187
196
|
<SelectItem className="select-none py-1" value={currentFile}>
|
|
188
197
|
<span
|
|
189
198
|
className={`text-xs pr-1 block truncate ${
|
|
190
|
-
sidebarOpen
|
|
199
|
+
sidebarOpen
|
|
200
|
+
? "max-w-[8rem] sm:max-w-[12rem]"
|
|
201
|
+
: "max-w-[12rem] sm:max-w-[16rem]"
|
|
191
202
|
}`}
|
|
192
203
|
>
|
|
193
204
|
{currentFile}
|
|
@@ -228,6 +239,30 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
228
239
|
</DropdownMenuContent>
|
|
229
240
|
</DropdownMenu>
|
|
230
241
|
)}
|
|
242
|
+
<TooltipProvider>
|
|
243
|
+
<Tooltip>
|
|
244
|
+
<TooltipTrigger asChild>
|
|
245
|
+
<Button
|
|
246
|
+
size="sm"
|
|
247
|
+
variant="ghost"
|
|
248
|
+
onClick={() =>
|
|
249
|
+
setAiAutocompleteEnabled(!aiAutocompleteEnabled)
|
|
250
|
+
}
|
|
251
|
+
className={`relative bg-transparent ${aiAutocompleteEnabled ? "text-gray-600 bg-gray-50" : "text-gray-400"}`}
|
|
252
|
+
>
|
|
253
|
+
<Bot className="h-4 w-4" />
|
|
254
|
+
{!aiAutocompleteEnabled && (
|
|
255
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
256
|
+
<div className="w-5 h-0.5 bg-gray-400 rotate-45 rounded-full" />
|
|
257
|
+
</div>
|
|
258
|
+
)}
|
|
259
|
+
</Button>
|
|
260
|
+
</TooltipTrigger>
|
|
261
|
+
<TooltipContent>
|
|
262
|
+
<p>Toggle AI autocomplete for code suggestions</p>
|
|
263
|
+
</TooltipContent>
|
|
264
|
+
</Tooltip>
|
|
265
|
+
</TooltipProvider>
|
|
231
266
|
<Button size="sm" variant="ghost" onClick={() => openImportDialog()}>
|
|
232
267
|
Import
|
|
233
268
|
</Button>
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
DropdownMenuTrigger,
|
|
11
11
|
} from "@/components/ui/dropdown-menu"
|
|
12
12
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
13
|
-
import {
|
|
13
|
+
import { encodeFsMapToUrlHash } from "@/lib/encodeFsMapToUrlHash"
|
|
14
14
|
import { cn } from "@/lib/utils"
|
|
15
15
|
import { OpenInNewWindowIcon, LockClosedIcon } from "@radix-ui/react-icons"
|
|
16
16
|
import { AnyCircuitElement } from "circuit-json"
|
|
@@ -32,11 +32,13 @@ import {
|
|
|
32
32
|
Sidebar,
|
|
33
33
|
Sparkles,
|
|
34
34
|
Trash2,
|
|
35
|
+
Undo2,
|
|
35
36
|
} from "lucide-react"
|
|
36
37
|
import { useEffect, useMemo, useState } from "react"
|
|
37
38
|
import { useQueryClient } from "react-query"
|
|
38
39
|
import { Link, useLocation } from "wouter"
|
|
39
40
|
import { useAxios } from "@/hooks/use-axios"
|
|
41
|
+
import { useHotkeyCombo } from "@/hooks/use-hotkey"
|
|
40
42
|
import { useToast } from "@/hooks/use-toast"
|
|
41
43
|
import { useConfirmDeletePackageDialog } from "@/components/dialogs/confirm-delete-package-dialog"
|
|
42
44
|
import { useFilesDialog } from "@/components/dialogs/files-dialog"
|
|
@@ -52,22 +54,26 @@ export default function EditorNav({
|
|
|
52
54
|
circuitJson,
|
|
53
55
|
pkg,
|
|
54
56
|
code,
|
|
57
|
+
fsMap,
|
|
55
58
|
hasUnsavedChanges,
|
|
56
59
|
onTogglePreview,
|
|
57
60
|
previewOpen,
|
|
58
61
|
onSave,
|
|
62
|
+
onDiscard,
|
|
59
63
|
packageType,
|
|
60
64
|
isSaving,
|
|
61
65
|
}: {
|
|
62
66
|
pkg?: Package | null
|
|
63
67
|
circuitJson?: AnyCircuitElement[] | null
|
|
64
68
|
code: string
|
|
69
|
+
fsMap: Record<string, string>
|
|
65
70
|
packageType?: string
|
|
66
71
|
hasUnsavedChanges: boolean
|
|
67
72
|
previewOpen: boolean
|
|
68
73
|
onTogglePreview: () => void
|
|
69
74
|
isSaving: boolean
|
|
70
75
|
onSave: () => void
|
|
76
|
+
onDiscard?: () => void
|
|
71
77
|
}) {
|
|
72
78
|
const [, navigate] = useLocation()
|
|
73
79
|
const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
|
|
@@ -190,17 +196,14 @@ export default function EditorNav({
|
|
|
190
196
|
[isLoggedIn, pkg, session?.github_username],
|
|
191
197
|
)
|
|
192
198
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
window.addEventListener("keydown", handleKeyDown)
|
|
202
|
-
return () => window.removeEventListener("keydown", handleKeyDown)
|
|
203
|
-
}, [onSave, hasUnsavedChanges, canSavePackage])
|
|
199
|
+
useHotkeyCombo(
|
|
200
|
+
"cmd+s",
|
|
201
|
+
() => {
|
|
202
|
+
if (!hasUnsavedChanges || !canSavePackage) return
|
|
203
|
+
onSave()
|
|
204
|
+
},
|
|
205
|
+
{ target: window },
|
|
206
|
+
)
|
|
204
207
|
return (
|
|
205
208
|
<nav className="lg:flex w-screen items-center justify-between px-2 py-3 border-b border-gray-200 bg-white text-sm border-t">
|
|
206
209
|
<div className="lg:flex items-center my-2 ">
|
|
@@ -311,6 +314,17 @@ export default function EditorNav({
|
|
|
311
314
|
{pkg ? "unsaved changes" : "unsaved"}
|
|
312
315
|
</div>
|
|
313
316
|
)}
|
|
317
|
+
{hasUnsavedChanges && onDiscard && Boolean(pkg?.package_id) && (
|
|
318
|
+
<Button
|
|
319
|
+
variant="ghost"
|
|
320
|
+
size="sm"
|
|
321
|
+
className="h-6 px-2 text-xs text-red-600 hover:text-red-700 hover:bg-red-50"
|
|
322
|
+
onClick={onDiscard}
|
|
323
|
+
title="Discard all unsaved changes (Cmd+Shift+Z)"
|
|
324
|
+
>
|
|
325
|
+
<Undo2 className="mr-1 h-3 w-3" />
|
|
326
|
+
</Button>
|
|
327
|
+
)}
|
|
314
328
|
</div>
|
|
315
329
|
</div>
|
|
316
330
|
<div className="flex items-center justify-end -space-x-1">
|
|
@@ -326,16 +340,19 @@ export default function EditorNav({
|
|
|
326
340
|
Edit with AI
|
|
327
341
|
</Button> */}
|
|
328
342
|
<DownloadButtonAndMenu
|
|
329
|
-
|
|
343
|
+
offerMultipleImageFormats
|
|
344
|
+
unscopedName={pkg?.unscoped_name}
|
|
330
345
|
circuitJson={circuitJson}
|
|
331
346
|
className="flex"
|
|
347
|
+
desiredImageType={pkg?.default_view ?? "pcb"}
|
|
348
|
+
author={pkg?.owner_github_username ?? undefined}
|
|
332
349
|
/>
|
|
333
350
|
<Button
|
|
334
351
|
variant="ghost"
|
|
335
352
|
size="sm"
|
|
336
353
|
className="hidden md:flex px-2 text-xs"
|
|
337
354
|
onClick={() => {
|
|
338
|
-
const url =
|
|
355
|
+
const url = encodeFsMapToUrlHash(fsMap, packageType)
|
|
339
356
|
navigator.clipboard.writeText(url)
|
|
340
357
|
alert("URL copied to clipboard!")
|
|
341
358
|
}}
|
|
@@ -432,7 +449,7 @@ export default function EditorNav({
|
|
|
432
449
|
onClick={() => openDeleteDialog()}
|
|
433
450
|
>
|
|
434
451
|
<Trash2 className="mr-2 h-3 w-3" />
|
|
435
|
-
Delete
|
|
452
|
+
Delete Package
|
|
436
453
|
</DropdownMenuItem>
|
|
437
454
|
<DropdownMenuItem className="text-xs text-gray-500" disabled>
|
|
438
455
|
@tscircuit/core@{tscircuitCorePkg.version}
|
|
@@ -468,6 +485,15 @@ export default function EditorNav({
|
|
|
468
485
|
</div>
|
|
469
486
|
</DropdownMenuTrigger>
|
|
470
487
|
<DropdownMenuContent>
|
|
488
|
+
{hasUnsavedChanges && onDiscard && (
|
|
489
|
+
<DropdownMenuItem
|
|
490
|
+
className="text-xs text-red-600"
|
|
491
|
+
onClick={onDiscard}
|
|
492
|
+
>
|
|
493
|
+
<Undo2 className="mr-1 h-3 w-3" />
|
|
494
|
+
Discard Changes
|
|
495
|
+
</DropdownMenuItem>
|
|
496
|
+
)}
|
|
471
497
|
<DropdownMenuItem
|
|
472
498
|
className="text-xs"
|
|
473
499
|
onClick={() => {
|