@tscircuit/fake-snippets 0.0.88 → 0.0.90
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 +196 -215
- package/dist/bundle.js +596 -370
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +134 -0
- package/fake-snippets-api/routes/api/proxy.ts +128 -0
- package/package.json +59 -48
- package/renovate.json +2 -1
- package/src/App.tsx +67 -3
- package/src/ContextProviders.tsx +2 -0
- package/src/build-watcher.ts +52 -0
- package/src/components/CircuitJsonImportDialog.tsx +1 -1
- package/src/components/CmdKMenu.tsx +533 -197
- package/src/components/DownloadButtonAndMenu.tsx +104 -26
- package/src/components/FileSidebar.tsx +11 -1
- package/src/components/Header2.tsx +7 -2
- 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 +19 -28
- package/src/components/PackageCard.tsx +66 -16
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/SuspenseRunFrame.tsx +14 -2
- package/src/components/ViewPackagePage/components/important-files-view.tsx +90 -17
- package/src/components/ViewPackagePage/components/main-content-header.tsx +26 -2
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +2 -2
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +35 -30
- 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/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 +17 -16
- package/src/components/package-port/CodeEditor.tsx +138 -17
- package/src/components/package-port/CodeEditorHeader.tsx +44 -4
- package/src/components/package-port/EditorNav.tsx +42 -29
- 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-code-completion-ai-api.ts +3 -3
- package/src/hooks/use-current-package-release.ts +5 -1
- package/src/hooks/use-delete-package.ts +6 -2
- 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 +28 -10
- 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 +7 -0
- package/src/lib/ts-lib-cache.ts +47 -0
- package/src/lib/types.ts +2 -0
- package/src/lib/utils/findTargetFile.ts +1 -1
- package/src/lib/utils/package-utils.ts +10 -0
- package/src/main.tsx +7 -0
- package/src/pages/dashboard.tsx +18 -5
- package/src/pages/view-package.tsx +15 -7
- package/src/types/package.ts +4 -0
- 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"
|
|
@@ -27,14 +32,18 @@ import CodeEditorHeader, {
|
|
|
27
32
|
import { useCodeCompletionApi } from "@/hooks/use-code-completion-ai-api"
|
|
28
33
|
import FileSidebar from "../FileSidebar"
|
|
29
34
|
import { findTargetFile } from "@/lib/utils/findTargetFile"
|
|
30
|
-
import type { PackageFile } from "
|
|
35
|
+
import type { PackageFile } from "@/types/package"
|
|
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"
|
|
46
|
+
import { inlineCopilot } from "codemirror-copilot"
|
|
38
47
|
|
|
39
48
|
const defaultImports = `
|
|
40
49
|
import React from "@types/react/jsx-runtime"
|
|
@@ -77,12 +86,16 @@ export const CodeEditor = ({
|
|
|
77
86
|
const codeCompletionApi = useCodeCompletionApi()
|
|
78
87
|
const [cursorPosition, setCursorPosition] = useState<number | null>(null)
|
|
79
88
|
const [code, setCode] = useState(files[0]?.content || "")
|
|
89
|
+
const [fontSize, setFontSize] = useState(14)
|
|
90
|
+
const [showQuickOpen, setShowQuickOpen] = useState(false)
|
|
91
|
+
const [showGlobalFindReplace, setShowGlobalFindReplace] = useState(false)
|
|
80
92
|
|
|
81
93
|
const { highlighter } = useShikiHighlighter()
|
|
82
94
|
|
|
83
95
|
// Get URL search params for file_path
|
|
84
96
|
const urlParams = new URLSearchParams(window.location.search)
|
|
85
97
|
const filePathFromUrl = urlParams.get("file_path")
|
|
98
|
+
const [aiAutocompleteEnabled, setAiAutocompleteEnabled] = useState(false)
|
|
86
99
|
|
|
87
100
|
const entryPointFileName = useMemo(() => {
|
|
88
101
|
const entryPointFile = findTargetFile(files, null)
|
|
@@ -132,6 +145,14 @@ export const CodeEditor = ({
|
|
|
132
145
|
}
|
|
133
146
|
}, [isStreaming])
|
|
134
147
|
|
|
148
|
+
useHotkeyCombo(
|
|
149
|
+
"cmd+b",
|
|
150
|
+
() => {
|
|
151
|
+
setSidebarOpen((prev) => !prev)
|
|
152
|
+
},
|
|
153
|
+
{ target: window },
|
|
154
|
+
)
|
|
155
|
+
|
|
135
156
|
useEffect(() => {
|
|
136
157
|
if (!editorRef.current) return
|
|
137
158
|
|
|
@@ -141,12 +162,7 @@ export const CodeEditor = ({
|
|
|
141
162
|
})
|
|
142
163
|
;(window as any).__DEBUG_CODE_EDITOR_FS_MAP = fsMap
|
|
143
164
|
|
|
144
|
-
|
|
145
|
-
{ target: tsModule.ScriptTarget.ES2022 },
|
|
146
|
-
"5.6.3",
|
|
147
|
-
true,
|
|
148
|
-
tsModule,
|
|
149
|
-
).then((defaultFsMap) => {
|
|
165
|
+
loadDefaultLibMap().then((defaultFsMap) => {
|
|
150
166
|
defaultFsMap.forEach((content, filename) => {
|
|
151
167
|
fsMap.set(filename, content)
|
|
152
168
|
})
|
|
@@ -243,6 +259,29 @@ export const CodeEditor = ({
|
|
|
243
259
|
key: "Mod-Enter",
|
|
244
260
|
run: () => true,
|
|
245
261
|
},
|
|
262
|
+
{
|
|
263
|
+
key: "Tab",
|
|
264
|
+
run: (view) => {
|
|
265
|
+
if (completionStatus(view.state) === "active") {
|
|
266
|
+
return acceptCompletion(view)
|
|
267
|
+
}
|
|
268
|
+
return indentMore(view)
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
key: "Mod-p",
|
|
273
|
+
run: () => {
|
|
274
|
+
setShowQuickOpen(true)
|
|
275
|
+
return true
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
key: "Mod-Shift-f",
|
|
280
|
+
run: () => {
|
|
281
|
+
setShowGlobalFindReplace(true)
|
|
282
|
+
return true
|
|
283
|
+
},
|
|
284
|
+
},
|
|
246
285
|
]),
|
|
247
286
|
),
|
|
248
287
|
keymap.of([indentWithTab]),
|
|
@@ -263,13 +302,48 @@ export const CodeEditor = ({
|
|
|
263
302
|
setCursorPosition(pos)
|
|
264
303
|
}
|
|
265
304
|
}),
|
|
305
|
+
EditorView.theme({
|
|
306
|
+
".cm-editor": {
|
|
307
|
+
fontSize: `${fontSize}px`,
|
|
308
|
+
},
|
|
309
|
+
".cm-content": {
|
|
310
|
+
fontSize: `${fontSize}px`,
|
|
311
|
+
},
|
|
312
|
+
}),
|
|
313
|
+
EditorView.domEventHandlers({
|
|
314
|
+
wheel: (event) => {
|
|
315
|
+
if (event.ctrlKey || event.metaKey) {
|
|
316
|
+
event.preventDefault()
|
|
317
|
+
const delta = event.deltaY
|
|
318
|
+
setFontSize((prev) => {
|
|
319
|
+
const newSize =
|
|
320
|
+
delta > 0 ? Math.max(8, prev - 1) : Math.min(32, prev + 1)
|
|
321
|
+
return newSize
|
|
322
|
+
})
|
|
323
|
+
return true
|
|
324
|
+
}
|
|
325
|
+
return false
|
|
326
|
+
},
|
|
327
|
+
}),
|
|
266
328
|
]
|
|
267
|
-
if (
|
|
329
|
+
if (aiAutocompleteEnabled) {
|
|
268
330
|
baseExtensions.push(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
331
|
+
inlineCopilot(async (prefix, suffix) => {
|
|
332
|
+
const res = await fetch("/api/autocomplete/create_autocomplete", {
|
|
333
|
+
method: "POST",
|
|
334
|
+
headers: {
|
|
335
|
+
"Content-Type": "application/json",
|
|
336
|
+
},
|
|
337
|
+
body: JSON.stringify({
|
|
338
|
+
prefix,
|
|
339
|
+
suffix,
|
|
340
|
+
language: "typescript",
|
|
341
|
+
}),
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
const { prediction } = await res.json()
|
|
345
|
+
return prediction
|
|
346
|
+
}),
|
|
273
347
|
EditorView.theme({
|
|
274
348
|
".cm-ghostText, .cm-ghostText *": {
|
|
275
349
|
opacity: "0.6",
|
|
@@ -470,7 +544,15 @@ export const CodeEditor = ({
|
|
|
470
544
|
return () => {
|
|
471
545
|
view.destroy()
|
|
472
546
|
}
|
|
473
|
-
}, [
|
|
547
|
+
}, [
|
|
548
|
+
!isStreaming,
|
|
549
|
+
currentFile,
|
|
550
|
+
code !== "",
|
|
551
|
+
Boolean(highlighter),
|
|
552
|
+
isSaving,
|
|
553
|
+
fontSize,
|
|
554
|
+
aiAutocompleteEnabled,
|
|
555
|
+
])
|
|
474
556
|
|
|
475
557
|
const updateCurrentEditorContent = (newContent: string) => {
|
|
476
558
|
if (viewRef.current) {
|
|
@@ -541,12 +623,30 @@ export const CodeEditor = ({
|
|
|
541
623
|
updateEditorToMatchCurrentFile()
|
|
542
624
|
}, [currentFile])
|
|
543
625
|
|
|
626
|
+
// Global keyboard listeners
|
|
627
|
+
useHotkeyCombo("cmd+p", () => {
|
|
628
|
+
setShowQuickOpen(true)
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
useHotkeyCombo("cmd+shift+f", () => {
|
|
632
|
+
setShowGlobalFindReplace(true)
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
useHotkeyCombo("Escape", () => {
|
|
636
|
+
if (showQuickOpen) {
|
|
637
|
+
setShowQuickOpen(false)
|
|
638
|
+
}
|
|
639
|
+
if (showGlobalFindReplace) {
|
|
640
|
+
setShowGlobalFindReplace(false)
|
|
641
|
+
}
|
|
642
|
+
})
|
|
643
|
+
|
|
544
644
|
if (isStreaming) {
|
|
545
645
|
return <div className="font-mono whitespace-pre-wrap text-xs">{code}</div>
|
|
546
646
|
}
|
|
547
647
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
548
648
|
return (
|
|
549
|
-
<div className="flex h-
|
|
649
|
+
<div className="flex h-[98vh] w-full overflow-hidden">
|
|
550
650
|
<FileSidebar
|
|
551
651
|
files={Object.fromEntries(files.map((f) => [f.path, f.content]))}
|
|
552
652
|
currentFile={currentFile}
|
|
@@ -570,6 +670,10 @@ export const CodeEditor = ({
|
|
|
570
670
|
files={Object.fromEntries(files.map((f) => [f.path, f.content]))}
|
|
571
671
|
updateFileContent={updateFileContent}
|
|
572
672
|
handleFileChange={handleFileChange}
|
|
673
|
+
aiAutocompleteState={[
|
|
674
|
+
aiAutocompleteEnabled,
|
|
675
|
+
setAiAutocompleteEnabled,
|
|
676
|
+
]}
|
|
573
677
|
/>
|
|
574
678
|
)}
|
|
575
679
|
<div
|
|
@@ -579,6 +683,23 @@ export const CodeEditor = ({
|
|
|
579
683
|
}
|
|
580
684
|
/>
|
|
581
685
|
</div>
|
|
686
|
+
{showQuickOpen && (
|
|
687
|
+
<QuickOpen
|
|
688
|
+
files={files.filter((f) => !isHiddenFile(f.path))}
|
|
689
|
+
currentFile={currentFile}
|
|
690
|
+
onFileSelect={handleFileChange}
|
|
691
|
+
onClose={() => setShowQuickOpen(false)}
|
|
692
|
+
/>
|
|
693
|
+
)}
|
|
694
|
+
{showGlobalFindReplace && (
|
|
695
|
+
<GlobalFindReplace
|
|
696
|
+
files={files.filter((f) => !isHiddenFile(f.path))}
|
|
697
|
+
currentFile={currentFile}
|
|
698
|
+
onFileSelect={handleFileChange}
|
|
699
|
+
onFileContentChanged={onCodeChange}
|
|
700
|
+
onClose={() => setShowGlobalFindReplace(false)}
|
|
701
|
+
/>
|
|
702
|
+
)}
|
|
582
703
|
</div>
|
|
583
704
|
)
|
|
584
705
|
}
|
|
@@ -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,13 @@ 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"
|
|
29
|
+
import ai from "fake-snippets-api/routes/api/ai"
|
|
23
30
|
|
|
24
31
|
export type FileName = string
|
|
25
32
|
|
|
@@ -30,6 +37,7 @@ interface CodeEditorHeaderProps {
|
|
|
30
37
|
fileSidebarState: ReturnType<typeof useState<boolean>>
|
|
31
38
|
handleFileChange: (filename: FileName) => void
|
|
32
39
|
entrypointFileName?: string
|
|
40
|
+
aiAutocompleteState: [boolean, React.Dispatch<React.SetStateAction<boolean>>]
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
@@ -39,11 +47,13 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
39
47
|
fileSidebarState,
|
|
40
48
|
handleFileChange,
|
|
41
49
|
entrypointFileName = "index.tsx",
|
|
50
|
+
aiAutocompleteState,
|
|
42
51
|
}) => {
|
|
43
52
|
const { Dialog: ImportPackageDialog, openDialog: openImportDialog } =
|
|
44
53
|
useImportPackageDialog()
|
|
45
54
|
const { toast } = useToast()
|
|
46
55
|
const [sidebarOpen, setSidebarOpen] = fileSidebarState
|
|
56
|
+
const [aiAutocompleteEnabled, setAiAutocompleteEnabled] = aiAutocompleteState
|
|
47
57
|
|
|
48
58
|
const handleFormatFile = useCallback(() => {
|
|
49
59
|
if (!window.prettier || !window.prettierPlugins) return
|
|
@@ -152,7 +162,7 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
152
162
|
<div>
|
|
153
163
|
<Select value={currentFile || ""} onValueChange={handleFileChange}>
|
|
154
164
|
<SelectTrigger
|
|
155
|
-
className={`h-7 w-
|
|
165
|
+
className={`h-7 w-32 sm:w-48 px-3 bg-white select-none transition-[margin] duration-300 ease-in-out ${
|
|
156
166
|
sidebarOpen ? "-ml-2" : "-ml-1"
|
|
157
167
|
}`}
|
|
158
168
|
>
|
|
@@ -170,7 +180,9 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
170
180
|
<SelectItem className="py-1" key={filename} value={filename}>
|
|
171
181
|
<span
|
|
172
182
|
className={`text-xs pr-1 block truncate ${
|
|
173
|
-
sidebarOpen
|
|
183
|
+
sidebarOpen
|
|
184
|
+
? "max-w-[8rem] sm:max-w-[12rem]"
|
|
185
|
+
: "max-w-[12rem] sm:max-w-[16rem]"
|
|
174
186
|
}`}
|
|
175
187
|
>
|
|
176
188
|
{filename}
|
|
@@ -187,7 +199,9 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
187
199
|
<SelectItem className="select-none py-1" value={currentFile}>
|
|
188
200
|
<span
|
|
189
201
|
className={`text-xs pr-1 block truncate ${
|
|
190
|
-
sidebarOpen
|
|
202
|
+
sidebarOpen
|
|
203
|
+
? "max-w-[8rem] sm:max-w-[12rem]"
|
|
204
|
+
: "max-w-[12rem] sm:max-w-[16rem]"
|
|
191
205
|
}`}
|
|
192
206
|
>
|
|
193
207
|
{currentFile}
|
|
@@ -228,6 +242,32 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
228
242
|
</DropdownMenuContent>
|
|
229
243
|
</DropdownMenu>
|
|
230
244
|
)}
|
|
245
|
+
|
|
246
|
+
<TooltipProvider>
|
|
247
|
+
<Tooltip>
|
|
248
|
+
<TooltipTrigger asChild>
|
|
249
|
+
<Button
|
|
250
|
+
size="sm"
|
|
251
|
+
variant="ghost"
|
|
252
|
+
onClick={() => {
|
|
253
|
+
setAiAutocompleteEnabled((prev) => !prev)
|
|
254
|
+
}}
|
|
255
|
+
className={`relative bg-transparent ${aiAutocompleteEnabled ? "text-gray-600 bg-gray-50" : "text-gray-400"}`}
|
|
256
|
+
>
|
|
257
|
+
<Bot className="h-4 w-4" />
|
|
258
|
+
{!aiAutocompleteEnabled && (
|
|
259
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
260
|
+
<div className="w-5 h-0.5 bg-gray-400 rotate-45 rounded-full" />
|
|
261
|
+
</div>
|
|
262
|
+
)}
|
|
263
|
+
</Button>
|
|
264
|
+
</TooltipTrigger>
|
|
265
|
+
<TooltipContent>
|
|
266
|
+
<p>Toggle AI autocomplete for code suggestions</p>
|
|
267
|
+
</TooltipContent>
|
|
268
|
+
</Tooltip>
|
|
269
|
+
</TooltipProvider>
|
|
270
|
+
|
|
231
271
|
<Button size="sm" variant="ghost" onClick={() => openImportDialog()}>
|
|
232
272
|
Import
|
|
233
273
|
</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"
|
|
@@ -18,28 +18,26 @@ import { Package } from "fake-snippets-api/lib/db/schema"
|
|
|
18
18
|
import {
|
|
19
19
|
ChevronDown,
|
|
20
20
|
CodeIcon,
|
|
21
|
-
Download,
|
|
22
21
|
Edit2,
|
|
23
22
|
Eye,
|
|
24
23
|
EyeIcon,
|
|
25
24
|
File,
|
|
26
25
|
FilePenLine,
|
|
27
26
|
MoreVertical,
|
|
28
|
-
Package as PackageIcon,
|
|
29
27
|
Pencil,
|
|
30
28
|
Save,
|
|
31
29
|
Share,
|
|
32
30
|
Sidebar,
|
|
33
|
-
Sparkles,
|
|
34
31
|
Trash2,
|
|
32
|
+
Undo2,
|
|
35
33
|
} from "lucide-react"
|
|
36
34
|
import { useEffect, useMemo, useState } from "react"
|
|
37
35
|
import { useQueryClient } from "react-query"
|
|
38
36
|
import { Link, useLocation } from "wouter"
|
|
39
37
|
import { useAxios } from "@/hooks/use-axios"
|
|
38
|
+
import { useHotkeyCombo } from "@/hooks/use-hotkey"
|
|
40
39
|
import { useToast } from "@/hooks/use-toast"
|
|
41
40
|
import { useConfirmDeletePackageDialog } from "@/components/dialogs/confirm-delete-package-dialog"
|
|
42
|
-
import { useFilesDialog } from "@/components/dialogs/files-dialog"
|
|
43
41
|
import { useViewTsFilesDialog } from "@/components/dialogs/view-ts-files-dialog"
|
|
44
42
|
import { DownloadButtonAndMenu } from "@/components/DownloadButtonAndMenu"
|
|
45
43
|
import { TypeBadge } from "@/components/TypeBadge"
|
|
@@ -52,22 +50,26 @@ export default function EditorNav({
|
|
|
52
50
|
circuitJson,
|
|
53
51
|
pkg,
|
|
54
52
|
code,
|
|
53
|
+
fsMap,
|
|
55
54
|
hasUnsavedChanges,
|
|
56
55
|
onTogglePreview,
|
|
57
56
|
previewOpen,
|
|
58
57
|
onSave,
|
|
58
|
+
onDiscard,
|
|
59
59
|
packageType,
|
|
60
60
|
isSaving,
|
|
61
61
|
}: {
|
|
62
62
|
pkg?: Package | null
|
|
63
63
|
circuitJson?: AnyCircuitElement[] | null
|
|
64
64
|
code: string
|
|
65
|
+
fsMap: Record<string, string>
|
|
65
66
|
packageType?: string
|
|
66
67
|
hasUnsavedChanges: boolean
|
|
67
68
|
previewOpen: boolean
|
|
68
69
|
onTogglePreview: () => void
|
|
69
70
|
isSaving: boolean
|
|
70
71
|
onSave: () => void
|
|
72
|
+
onDiscard?: () => void
|
|
71
73
|
}) {
|
|
72
74
|
const [, navigate] = useLocation()
|
|
73
75
|
const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
|
|
@@ -80,7 +82,6 @@ export default function EditorNav({
|
|
|
80
82
|
} = useUpdatePackageDescriptionDialog()
|
|
81
83
|
const { Dialog: DeleteDialog, openDialog: openDeleteDialog } =
|
|
82
84
|
useConfirmDeletePackageDialog()
|
|
83
|
-
const { Dialog: FilesDialog, openDialog: openFilesDialog } = useFilesDialog()
|
|
84
85
|
const { Dialog: ViewTsFilesDialog, openDialog: openViewTsFilesDialog } =
|
|
85
86
|
useViewTsFilesDialog()
|
|
86
87
|
|
|
@@ -190,17 +191,14 @@ export default function EditorNav({
|
|
|
190
191
|
[isLoggedIn, pkg, session?.github_username],
|
|
191
192
|
)
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
window.addEventListener("keydown", handleKeyDown)
|
|
202
|
-
return () => window.removeEventListener("keydown", handleKeyDown)
|
|
203
|
-
}, [onSave, hasUnsavedChanges, canSavePackage])
|
|
194
|
+
useHotkeyCombo(
|
|
195
|
+
"cmd+s",
|
|
196
|
+
() => {
|
|
197
|
+
if (!hasUnsavedChanges || !canSavePackage) return
|
|
198
|
+
onSave()
|
|
199
|
+
},
|
|
200
|
+
{ target: window },
|
|
201
|
+
)
|
|
204
202
|
return (
|
|
205
203
|
<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
204
|
<div className="lg:flex items-center my-2 ">
|
|
@@ -311,6 +309,17 @@ export default function EditorNav({
|
|
|
311
309
|
{pkg ? "unsaved changes" : "unsaved"}
|
|
312
310
|
</div>
|
|
313
311
|
)}
|
|
312
|
+
{hasUnsavedChanges && onDiscard && Boolean(pkg?.package_id) && (
|
|
313
|
+
<Button
|
|
314
|
+
variant="ghost"
|
|
315
|
+
size="sm"
|
|
316
|
+
className="h-6 px-2 text-xs text-red-600 hover:text-red-700 hover:bg-red-50"
|
|
317
|
+
onClick={onDiscard}
|
|
318
|
+
title="Discard all unsaved changes (Cmd+Shift+Z)"
|
|
319
|
+
>
|
|
320
|
+
<Undo2 className="mr-1 h-3 w-3" />
|
|
321
|
+
</Button>
|
|
322
|
+
)}
|
|
314
323
|
</div>
|
|
315
324
|
</div>
|
|
316
325
|
<div className="flex items-center justify-end -space-x-1">
|
|
@@ -326,16 +335,19 @@ export default function EditorNav({
|
|
|
326
335
|
Edit with AI
|
|
327
336
|
</Button> */}
|
|
328
337
|
<DownloadButtonAndMenu
|
|
329
|
-
|
|
338
|
+
offerMultipleImageFormats
|
|
339
|
+
unscopedName={pkg?.unscoped_name}
|
|
330
340
|
circuitJson={circuitJson}
|
|
331
341
|
className="flex"
|
|
342
|
+
desiredImageType={pkg?.default_view ?? "pcb"}
|
|
343
|
+
author={pkg?.owner_github_username ?? undefined}
|
|
332
344
|
/>
|
|
333
345
|
<Button
|
|
334
346
|
variant="ghost"
|
|
335
347
|
size="sm"
|
|
336
348
|
className="hidden md:flex px-2 text-xs"
|
|
337
349
|
onClick={() => {
|
|
338
|
-
const url =
|
|
350
|
+
const url = encodeFsMapToUrlHash(fsMap, packageType)
|
|
339
351
|
navigator.clipboard.writeText(url)
|
|
340
352
|
alert("URL copied to clipboard!")
|
|
341
353
|
}}
|
|
@@ -359,13 +371,6 @@ export default function EditorNav({
|
|
|
359
371
|
</Button>
|
|
360
372
|
</DropdownMenuTrigger>
|
|
361
373
|
<DropdownMenuContent>
|
|
362
|
-
<DropdownMenuItem
|
|
363
|
-
className="text-xs"
|
|
364
|
-
onClick={() => openFilesDialog()}
|
|
365
|
-
>
|
|
366
|
-
<File className="mr-2 h-3 w-3" />
|
|
367
|
-
View Files
|
|
368
|
-
</DropdownMenuItem>
|
|
369
374
|
<DropdownMenuItem
|
|
370
375
|
className="text-xs"
|
|
371
376
|
onClick={() => openupdateDescriptionDialog()}
|
|
@@ -378,7 +383,7 @@ export default function EditorNav({
|
|
|
378
383
|
onClick={() => openViewTsFilesDialog()}
|
|
379
384
|
>
|
|
380
385
|
<File className="mr-2 h-3 w-3" />
|
|
381
|
-
|
|
386
|
+
View Files
|
|
382
387
|
</DropdownMenuItem>
|
|
383
388
|
<DropdownMenuSub>
|
|
384
389
|
<DropdownMenuSubTrigger
|
|
@@ -432,7 +437,7 @@ export default function EditorNav({
|
|
|
432
437
|
onClick={() => openDeleteDialog()}
|
|
433
438
|
>
|
|
434
439
|
<Trash2 className="mr-2 h-3 w-3" />
|
|
435
|
-
Delete
|
|
440
|
+
Delete Package
|
|
436
441
|
</DropdownMenuItem>
|
|
437
442
|
<DropdownMenuItem className="text-xs text-gray-500" disabled>
|
|
438
443
|
@tscircuit/core@{tscircuitCorePkg.version}
|
|
@@ -468,6 +473,15 @@ export default function EditorNav({
|
|
|
468
473
|
</div>
|
|
469
474
|
</DropdownMenuTrigger>
|
|
470
475
|
<DropdownMenuContent>
|
|
476
|
+
{hasUnsavedChanges && onDiscard && (
|
|
477
|
+
<DropdownMenuItem
|
|
478
|
+
className="text-xs text-red-600"
|
|
479
|
+
onClick={onDiscard}
|
|
480
|
+
>
|
|
481
|
+
<Undo2 className="mr-1 h-3 w-3" />
|
|
482
|
+
Discard Changes
|
|
483
|
+
</DropdownMenuItem>
|
|
484
|
+
)}
|
|
471
485
|
<DropdownMenuItem
|
|
472
486
|
className="text-xs"
|
|
473
487
|
onClick={() => {
|
|
@@ -540,7 +554,6 @@ export default function EditorNav({
|
|
|
540
554
|
packageName={pkg?.unscoped_name ?? ""}
|
|
541
555
|
packageOwner={pkg?.owner_github_username ?? ""}
|
|
542
556
|
/>
|
|
543
|
-
<FilesDialog snippetId={pkg?.package_id ?? ""} />
|
|
544
557
|
<ViewTsFilesDialog />
|
|
545
558
|
</nav>
|
|
546
559
|
)
|