@tscircuit/fake-snippets 0.0.98 → 0.0.100
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/bun.lock +304 -947
- package/dist/bundle.js +11 -5
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -1
- package/fake-snippets-api/routes/api/packages/create.ts +14 -3
- package/package.json +7 -4
- package/src/App.tsx +58 -2
- package/src/components/CircuitJsonImportDialog.tsx +10 -5
- package/src/components/DownloadButtonAndMenu.tsx +13 -0
- package/src/components/FileSidebar.tsx +83 -10
- package/src/components/PackageBuildsPage/LogContent.tsx +19 -7
- package/src/components/ViewPackagePage/components/important-files-view.tsx +294 -167
- package/src/components/ViewPackagePage/components/main-content-header.tsx +2 -2
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +9 -0
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +10 -3
- package/src/components/ViewPackagePage/components/sidebar.tsx +3 -1
- package/src/components/dialogs/edit-package-details-dialog.tsx +3 -2
- package/src/components/package-port/CodeAndPreview.tsx +6 -1
- package/src/components/package-port/CodeEditor.tsx +21 -3
- package/src/components/package-port/CodeEditorHeader.tsx +12 -7
- package/src/components/ui/tree-view.tsx +51 -2
- package/src/hooks/use-create-package-mutation.ts +1 -1
- package/src/hooks/useFileManagement.ts +71 -6
- package/src/lib/download-fns/download-spice-file.ts +13 -0
- package/src/lib/utils/package-utils.ts +0 -3
- package/src/pages/dashboard.tsx +1 -1
- package/src/pages/datasheet.tsx +157 -67
- package/src/pages/datasheets.tsx +2 -2
- package/src/pages/latest.tsx +2 -2
- package/src/pages/search.tsx +1 -1
- package/src/pages/trending.tsx +2 -2
- package/vite.config.ts +1 -0
|
@@ -186,6 +186,7 @@ export const EditPackageDetailsDialog = ({
|
|
|
186
186
|
"packageFile",
|
|
187
187
|
{ package_release_id: packageReleaseId },
|
|
188
188
|
])
|
|
189
|
+
qc.invalidateQueries(["packageFiles", packageReleaseId])
|
|
189
190
|
toast({
|
|
190
191
|
title: "Package details updated",
|
|
191
192
|
description: "Successfully updated package details",
|
|
@@ -376,7 +377,7 @@ export const EditPackageDetailsDialog = ({
|
|
|
376
377
|
className="mt-2 rounded-md"
|
|
377
378
|
onToggle={(e) => setDangerOpen(e.currentTarget.open)}
|
|
378
379
|
>
|
|
379
|
-
<summary className="cursor-pointer p-2 font-medium text-sm text-black list-none flex justify-between items-center">
|
|
380
|
+
<summary className="select-none cursor-pointer p-2 font-medium text-sm text-black list-none flex justify-between items-center">
|
|
380
381
|
Danger Zone
|
|
381
382
|
<ChevronDown
|
|
382
383
|
className={`w-4 h-4 mr-1 transition-transform ${dangerOpen ? "rotate-180" : ""}`}
|
|
@@ -405,7 +406,7 @@ export const EditPackageDetailsDialog = ({
|
|
|
405
406
|
</div>
|
|
406
407
|
|
|
407
408
|
<DialogFooter className="mt-auto">
|
|
408
|
-
<div className="lg:px-2 flex flex-col sm:flex-row justify-end gap-2">
|
|
409
|
+
<div className="lg:px-2 select-none flex flex-col sm:flex-row justify-end gap-2">
|
|
409
410
|
<Button
|
|
410
411
|
variant="outline"
|
|
411
412
|
onClick={() => onOpenChange(false)}
|
|
@@ -16,6 +16,7 @@ import { toastManualEditConflicts } from "@/lib/utils/toastManualEditConflicts"
|
|
|
16
16
|
import { ManualEditEvent } from "@tscircuit/props"
|
|
17
17
|
import { useFileManagement } from "@/hooks/useFileManagement"
|
|
18
18
|
import { DEFAULT_CODE } from "@/lib/utils/package-utils"
|
|
19
|
+
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
19
20
|
|
|
20
21
|
interface Props {
|
|
21
22
|
pkg?: Package
|
|
@@ -38,6 +39,7 @@ export interface CodeAndPreviewState {
|
|
|
38
39
|
|
|
39
40
|
export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
40
41
|
const { toast } = useToast()
|
|
42
|
+
const session = useGlobalStore((s) => s.session)
|
|
41
43
|
const urlParams = useUrlParams()
|
|
42
44
|
|
|
43
45
|
const templateFromUrl = useMemo(
|
|
@@ -75,6 +77,7 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
|
75
77
|
setLocalFiles,
|
|
76
78
|
localFiles,
|
|
77
79
|
initialFiles,
|
|
80
|
+
renameFile,
|
|
78
81
|
} = useFileManagement({
|
|
79
82
|
templateCode: templateFromUrl?.code,
|
|
80
83
|
currentPackage: pkg,
|
|
@@ -169,7 +172,7 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
|
169
172
|
|
|
170
173
|
if (urlParams.package_id && (!pkg || isLoading)) {
|
|
171
174
|
return (
|
|
172
|
-
<div className="flex items-center justify-center h-[
|
|
175
|
+
<div className="flex items-center justify-center h-[80vh]">
|
|
173
176
|
<div className="flex flex-col items-center justify-center">
|
|
174
177
|
<div className="text-lg text-gray-500 mb-4">Loading</div>
|
|
175
178
|
<Loader2 className="w-16 h-16 animate-spin text-gray-400" />
|
|
@@ -208,6 +211,8 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
|
208
211
|
isSaving={isSaving}
|
|
209
212
|
handleCreateFile={createFile}
|
|
210
213
|
handleDeleteFile={deleteFile}
|
|
214
|
+
handleRenameFile={renameFile}
|
|
215
|
+
pkg={pkg}
|
|
211
216
|
currentFile={currentFile}
|
|
212
217
|
onFileSelect={onFileSelect}
|
|
213
218
|
files={localFiles}
|
|
@@ -35,6 +35,7 @@ import CodeEditorHeader, {
|
|
|
35
35
|
import FileSidebar from "../FileSidebar"
|
|
36
36
|
import { findTargetFile } from "@/lib/utils/findTargetFile"
|
|
37
37
|
import type { PackageFile } from "@/types/package"
|
|
38
|
+
import type { Package } from "fake-snippets-api/lib/db/schema"
|
|
38
39
|
import { useShikiHighlighter } from "@/hooks/use-shiki-highlighter"
|
|
39
40
|
import QuickOpen from "./QuickOpen"
|
|
40
41
|
import GlobalFindReplace from "./GlobalFindReplace"
|
|
@@ -43,6 +44,8 @@ import {
|
|
|
43
44
|
ICreateFileResult,
|
|
44
45
|
IDeleteFileProps,
|
|
45
46
|
IDeleteFileResult,
|
|
47
|
+
IRenameFileProps,
|
|
48
|
+
IRenameFileResult,
|
|
46
49
|
} from "@/hooks/useFileManagement"
|
|
47
50
|
import { isHiddenFile } from "../ViewPackagePage/utils/is-hidden-file"
|
|
48
51
|
import { inlineCopilot } from "codemirror-copilot"
|
|
@@ -65,14 +68,18 @@ export const CodeEditor = ({
|
|
|
65
68
|
pkgFilesLoaded,
|
|
66
69
|
currentFile,
|
|
67
70
|
onFileSelect,
|
|
71
|
+
handleRenameFile,
|
|
68
72
|
handleCreateFile,
|
|
69
73
|
handleDeleteFile,
|
|
74
|
+
pkg,
|
|
70
75
|
}: {
|
|
71
76
|
onCodeChange: (code: string, filename?: string) => void
|
|
72
77
|
files: PackageFile[]
|
|
73
78
|
isSaving?: boolean
|
|
74
79
|
handleCreateFile: (props: ICreateFileProps) => ICreateFileResult
|
|
75
80
|
handleDeleteFile: (props: IDeleteFileProps) => IDeleteFileResult
|
|
81
|
+
handleRenameFile: (props: IRenameFileProps) => IRenameFileResult
|
|
82
|
+
pkg?: Package
|
|
76
83
|
readOnly?: boolean
|
|
77
84
|
isStreaming?: boolean
|
|
78
85
|
pkgFilesLoaded?: boolean
|
|
@@ -108,6 +115,9 @@ export const CodeEditor = ({
|
|
|
108
115
|
return files.find((x) => x.path === "index.tsx")?.path || "index.tsx"
|
|
109
116
|
}, [files])
|
|
110
117
|
|
|
118
|
+
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
119
|
+
const [isCreatingFile, setIsCreatingFile] = useState(false)
|
|
120
|
+
|
|
111
121
|
// Set current file on component mount
|
|
112
122
|
useEffect(() => {
|
|
113
123
|
if (files.length === 0 || !pkgFilesLoaded || currentFile) return
|
|
@@ -195,7 +205,7 @@ export const CodeEditor = ({
|
|
|
195
205
|
projectName: "my-project",
|
|
196
206
|
typescript: tsModule,
|
|
197
207
|
logger: console,
|
|
198
|
-
fetcher: async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
208
|
+
fetcher: (async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
199
209
|
const registryPrefixes = [
|
|
200
210
|
"https://data.jsdelivr.com/v1/package/resolve/npm/@tsci/",
|
|
201
211
|
"https://data.jsdelivr.com/v1/package/npm/@tsci/",
|
|
@@ -221,7 +231,7 @@ export const CodeEditor = ({
|
|
|
221
231
|
)
|
|
222
232
|
}
|
|
223
233
|
return fetch(input, init)
|
|
224
|
-
},
|
|
234
|
+
}) as typeof fetch,
|
|
225
235
|
delegate: {
|
|
226
236
|
started: () => {
|
|
227
237
|
const manualEditsTypeDeclaration = `
|
|
@@ -786,10 +796,14 @@ export const CodeEditor = ({
|
|
|
786
796
|
}
|
|
787
797
|
})
|
|
788
798
|
|
|
799
|
+
useHotkeyCombo("cmd+m", () => {
|
|
800
|
+
setSidebarOpen(true)
|
|
801
|
+
setIsCreatingFile(true)
|
|
802
|
+
})
|
|
803
|
+
|
|
789
804
|
if (isStreaming) {
|
|
790
805
|
return <div className="font-mono whitespace-pre-wrap text-xs">{code}</div>
|
|
791
806
|
}
|
|
792
|
-
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
793
807
|
return (
|
|
794
808
|
<div className="flex h-[98vh] w-full overflow-hidden">
|
|
795
809
|
<FileSidebar
|
|
@@ -800,7 +814,11 @@ export const CodeEditor = ({
|
|
|
800
814
|
}
|
|
801
815
|
onFileSelect={(path) => handleFileChange(path)}
|
|
802
816
|
handleCreateFile={handleCreateFile}
|
|
817
|
+
handleRenameFile={handleRenameFile}
|
|
803
818
|
handleDeleteFile={handleDeleteFile}
|
|
819
|
+
isCreatingFile={isCreatingFile}
|
|
820
|
+
setIsCreatingFile={setIsCreatingFile}
|
|
821
|
+
pkg={pkg}
|
|
804
822
|
/>
|
|
805
823
|
<div className="flex flex-col flex-1 w-full min-w-0 h-full">
|
|
806
824
|
{showImportAndFormatButtons && (
|
|
@@ -189,13 +189,10 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
189
189
|
onError: (error) => {
|
|
190
190
|
throw error
|
|
191
191
|
},
|
|
192
|
-
openFile: false,
|
|
193
192
|
})
|
|
194
193
|
if (!createFileResult.newFileCreated) {
|
|
195
|
-
throw new Error("Failed to create file")
|
|
194
|
+
throw new Error("Failed to create component file")
|
|
196
195
|
}
|
|
197
|
-
const newContent = `import ${componentName.replace(/-/g, "")} from "./${componentName}.tsx"\n${files[currentFile || ""]}`
|
|
198
|
-
updateFileContent(currentFile, newContent)
|
|
199
196
|
}
|
|
200
197
|
}
|
|
201
198
|
|
|
@@ -221,7 +218,15 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
221
218
|
sidebarOpen ? "-ml-2" : "-ml-1"
|
|
222
219
|
}`}
|
|
223
220
|
>
|
|
224
|
-
<SelectValue
|
|
221
|
+
<SelectValue
|
|
222
|
+
placeholder={
|
|
223
|
+
Object.keys(files).filter(
|
|
224
|
+
(filename) => !isHiddenFile(filename),
|
|
225
|
+
).length > 0
|
|
226
|
+
? "Select file"
|
|
227
|
+
: "No files"
|
|
228
|
+
}
|
|
229
|
+
/>
|
|
225
230
|
</SelectTrigger>
|
|
226
231
|
<SelectContent>
|
|
227
232
|
{Object.keys(files)
|
|
@@ -307,12 +312,12 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
307
312
|
onClick={() => {
|
|
308
313
|
setAiAutocompleteEnabled((prev) => !prev)
|
|
309
314
|
}}
|
|
310
|
-
className={`relative bg-transparent ${aiAutocompleteEnabled ? "text-gray-600 bg-gray-50" : "text-gray-400"}`}
|
|
315
|
+
className={`relative group bg-transparent ${aiAutocompleteEnabled ? "text-gray-600 bg-gray-50" : "text-gray-400"}`}
|
|
311
316
|
>
|
|
312
317
|
<Bot className="h-4 w-4" />
|
|
313
318
|
{!aiAutocompleteEnabled && (
|
|
314
319
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
315
|
-
<div className="w-5 h-0.5 bg-gray-400 rotate-45 rounded-full" />
|
|
320
|
+
<div className="w-5 h-0.5 group-hover:bg-slate-900 bg-gray-400 rotate-45 rounded-full" />
|
|
316
321
|
</div>
|
|
317
322
|
)}
|
|
318
323
|
</Button>
|
|
@@ -3,6 +3,7 @@ import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
|
|
3
3
|
import { ChevronRight } from "lucide-react"
|
|
4
4
|
import { cva } from "class-variance-authority"
|
|
5
5
|
import { cn } from "@/lib/utils"
|
|
6
|
+
import { Input } from "@/components/ui/input"
|
|
6
7
|
|
|
7
8
|
const treeVariants = cva(
|
|
8
9
|
"group hover:before:opacity-100 before:absolute before:rounded-lg before:left-0 before:w-full before:opacity-0 before:bg-slate-100/70 before:h-[2rem] before:-z-10' dark:before:bg-slate-800/70",
|
|
@@ -18,7 +19,7 @@ const dragOverVariants = cva(
|
|
|
18
19
|
|
|
19
20
|
interface TreeDataItem {
|
|
20
21
|
id: string
|
|
21
|
-
name:
|
|
22
|
+
name: React.ReactNode
|
|
22
23
|
icon?: any
|
|
23
24
|
selectedIcon?: any
|
|
24
25
|
openIcon?: any
|
|
@@ -27,6 +28,9 @@ interface TreeDataItem {
|
|
|
27
28
|
onClick?: () => void
|
|
28
29
|
draggable?: boolean
|
|
29
30
|
droppable?: boolean
|
|
31
|
+
isRenaming?: boolean
|
|
32
|
+
onRename?: (newName: string) => void
|
|
33
|
+
onCancelRename?: () => void
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
type TreeProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
@@ -403,7 +407,52 @@ const TreeLeaf = React.forwardRef<
|
|
|
403
407
|
isSelected={selectedItemId === item.id}
|
|
404
408
|
default={defaultLeafIcon}
|
|
405
409
|
/>
|
|
406
|
-
|
|
410
|
+
{item.isRenaming ? (
|
|
411
|
+
<Input
|
|
412
|
+
style={{
|
|
413
|
+
zIndex: 50,
|
|
414
|
+
}}
|
|
415
|
+
defaultValue={item.name as string}
|
|
416
|
+
onKeyDown={(e) => {
|
|
417
|
+
if (e.key === "Enter") {
|
|
418
|
+
e.preventDefault()
|
|
419
|
+
const value = e.currentTarget.value.trim()
|
|
420
|
+
if (value && value !== item.name) {
|
|
421
|
+
item.onRename?.(value)
|
|
422
|
+
} else {
|
|
423
|
+
item.onCancelRename?.()
|
|
424
|
+
}
|
|
425
|
+
} else if (e.key === "Escape") {
|
|
426
|
+
e.preventDefault()
|
|
427
|
+
item.onCancelRename?.()
|
|
428
|
+
}
|
|
429
|
+
}}
|
|
430
|
+
spellCheck={false}
|
|
431
|
+
autoComplete="off"
|
|
432
|
+
onBlur={(e) => {
|
|
433
|
+
const value = e.currentTarget.value.trim()
|
|
434
|
+
if (value && value !== item.name) {
|
|
435
|
+
item.onRename?.(value)
|
|
436
|
+
} else {
|
|
437
|
+
item.onCancelRename?.()
|
|
438
|
+
}
|
|
439
|
+
}}
|
|
440
|
+
autoFocus
|
|
441
|
+
onClick={(e) => e.stopPropagation()}
|
|
442
|
+
className="h-6 px-2 py-0 text-sm flex-1 mr-8 bg-white border border-blue-500 rounded-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 shadow-sm"
|
|
443
|
+
onFocus={(e) => {
|
|
444
|
+
e.currentTarget.select()
|
|
445
|
+
// Select filename without extension
|
|
446
|
+
const filename = e.currentTarget.value
|
|
447
|
+
const lastDotIndex = filename.lastIndexOf(".")
|
|
448
|
+
if (lastDotIndex > 0) {
|
|
449
|
+
e.currentTarget.setSelectionRange(0, lastDotIndex)
|
|
450
|
+
}
|
|
451
|
+
}}
|
|
452
|
+
/>
|
|
453
|
+
) : (
|
|
454
|
+
<span className="text-sm truncate">{item.name}</span>
|
|
455
|
+
)}
|
|
407
456
|
<div className="flex items-center" onClick={(e) => e.stopPropagation()}>
|
|
408
457
|
<TreeActions isSelected={true}>{item.actions}</TreeActions>
|
|
409
458
|
</div>
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState, useCallback, useRef } from "react"
|
|
2
2
|
import { isValidFileName } from "@/lib/utils/isValidFileName"
|
|
3
3
|
import { PackageFile } from "@/types/package"
|
|
4
|
-
import {
|
|
5
|
-
DEFAULT_CODE,
|
|
6
|
-
generateRandomPackageName,
|
|
7
|
-
} from "@/lib/utils/package-utils"
|
|
4
|
+
import { DEFAULT_CODE } from "@/lib/utils/package-utils"
|
|
8
5
|
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
9
6
|
import { usePackageFiles } from "./use-package-files"
|
|
10
7
|
import { decodeUrlHashToText } from "@/lib/decodeUrlHashToText"
|
|
@@ -17,6 +14,7 @@ import { useCreatePackageReleaseMutation } from "./use-create-package-release-mu
|
|
|
17
14
|
import { useCreatePackageMutation } from "./use-create-package-mutation"
|
|
18
15
|
import { findTargetFile } from "@/lib/utils/findTargetFile"
|
|
19
16
|
import { encodeFsMapToUrlHash } from "@/lib/encodeFsMapToUrlHash"
|
|
17
|
+
import { isHiddenFile } from "@/components/ViewPackagePage/utils/is-hidden-file"
|
|
20
18
|
|
|
21
19
|
export interface ICreateFileProps {
|
|
22
20
|
newFileName: string
|
|
@@ -36,6 +34,16 @@ export interface IDeleteFileProps {
|
|
|
36
34
|
onError: (error: Error) => void
|
|
37
35
|
}
|
|
38
36
|
|
|
37
|
+
export interface IRenameFileProps {
|
|
38
|
+
oldFilename: string
|
|
39
|
+
newFilename: string
|
|
40
|
+
onError: (error: Error) => void
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface IRenameFileResult {
|
|
44
|
+
fileRenamed: boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
export function useFileManagement({
|
|
40
48
|
templateCode,
|
|
41
49
|
currentPackage,
|
|
@@ -234,12 +242,69 @@ export function useFileManagement({
|
|
|
234
242
|
}
|
|
235
243
|
const updatedFiles = localFiles.filter((file) => file.path !== filename)
|
|
236
244
|
setLocalFiles(updatedFiles)
|
|
237
|
-
onFileSelect(
|
|
245
|
+
onFileSelect(
|
|
246
|
+
updatedFiles.filter((file) => !isHiddenFile(file.path))[0]?.path || "",
|
|
247
|
+
)
|
|
238
248
|
return {
|
|
239
249
|
fileDeleted: true,
|
|
240
250
|
}
|
|
241
251
|
}
|
|
242
252
|
|
|
253
|
+
const renameFile = ({
|
|
254
|
+
oldFilename,
|
|
255
|
+
newFilename,
|
|
256
|
+
onError,
|
|
257
|
+
}: IRenameFileProps): IRenameFileResult => {
|
|
258
|
+
newFilename = newFilename.trim()
|
|
259
|
+
if (!newFilename) {
|
|
260
|
+
onError(new Error("File name cannot be empty"))
|
|
261
|
+
return {
|
|
262
|
+
fileRenamed: false,
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Extract just the filename from the path for validation
|
|
267
|
+
const fileNameOnly = newFilename.split("/").pop() || ""
|
|
268
|
+
if (!isValidFileName(fileNameOnly)) {
|
|
269
|
+
onError(new Error("Invalid file name"))
|
|
270
|
+
return {
|
|
271
|
+
fileRenamed: false,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const oldFileExists = localFiles?.some((file) => file.path === oldFilename)
|
|
276
|
+
if (!oldFileExists) {
|
|
277
|
+
onError(new Error("File does not exist"))
|
|
278
|
+
return {
|
|
279
|
+
fileRenamed: false,
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const newFileExists = localFiles?.some((file) => file.path === newFilename)
|
|
284
|
+
if (newFileExists) {
|
|
285
|
+
onError(new Error("A file with this name already exists"))
|
|
286
|
+
return {
|
|
287
|
+
fileRenamed: false,
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const updatedFiles = localFiles.map((file) => {
|
|
292
|
+
if (file.path === oldFilename) {
|
|
293
|
+
return { ...file, path: newFilename }
|
|
294
|
+
}
|
|
295
|
+
return file
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
setLocalFiles(updatedFiles)
|
|
299
|
+
if (currentFile === oldFilename) {
|
|
300
|
+
setCurrentFile(newFilename)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
fileRenamed: true,
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
243
308
|
const savePackage = async (isPrivate: boolean) => {
|
|
244
309
|
if (!isLoggedIn) {
|
|
245
310
|
toast({
|
|
@@ -250,7 +315,6 @@ export function useFileManagement({
|
|
|
250
315
|
}
|
|
251
316
|
|
|
252
317
|
await createPackageMutation.mutateAsync({
|
|
253
|
-
name: `${loggedInUser?.github_username}/${generateRandomPackageName()}`,
|
|
254
318
|
is_private: isPrivate,
|
|
255
319
|
})
|
|
256
320
|
}
|
|
@@ -354,6 +418,7 @@ export function useFileManagement({
|
|
|
354
418
|
fsMap,
|
|
355
419
|
createFile,
|
|
356
420
|
deleteFile,
|
|
421
|
+
renameFile,
|
|
357
422
|
saveFiles,
|
|
358
423
|
localFiles,
|
|
359
424
|
initialFiles,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AnyCircuitElement } from "circuit-json"
|
|
2
|
+
import { saveAs } from "file-saver"
|
|
3
|
+
import { circuitJsonToSpice } from "circuit-json-to-spice"
|
|
4
|
+
|
|
5
|
+
export const downloadSpiceFile = (
|
|
6
|
+
circuitJson: AnyCircuitElement[],
|
|
7
|
+
fileName: string,
|
|
8
|
+
) => {
|
|
9
|
+
const spiceNetlist = circuitJsonToSpice(circuitJson)
|
|
10
|
+
const spiceString = spiceNetlist.toSpiceString()
|
|
11
|
+
const blob = new Blob([spiceString], { type: "text/plain" })
|
|
12
|
+
saveAs(blob, fileName + ".cir")
|
|
13
|
+
}
|
package/src/pages/dashboard.tsx
CHANGED
|
@@ -98,7 +98,7 @@ export const DashboardPage = () => {
|
|
|
98
98
|
<title>Dashboard - tscircuit</title>
|
|
99
99
|
</Helmet>
|
|
100
100
|
<Header />
|
|
101
|
-
<div className="container mx-auto px-4 py-8">
|
|
101
|
+
<div className="container mx-auto px-4 py-8 min-h-[80vh]">
|
|
102
102
|
<h1 className="text-3xl font-bold mb-6">Dashboard</h1>
|
|
103
103
|
<div className="flex md:flex-row flex-col">
|
|
104
104
|
<div className="md:w-3/4 p-0 md:pr-6">
|