@tscircuit/fake-snippets 0.0.26 → 0.0.28
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-tests/fake-snippets-api/routes/package_files/create.test.ts +5 -6
- package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +3 -0
- package/bun-tests/fake-snippets-api/routes/packages/list-2.test.ts +2 -0
- package/bun-tests/fake-snippets-api/routes/snippets/list_newest.test.ts +5 -3
- package/bun-tests/fake-snippets-api/routes/snippets/list_trending.test.ts +8 -5
- package/bun-tests/fake-snippets-api/routes/snippets/update.test.ts +1 -1
- package/bun.lock +110 -5
- package/dist/bundle.js +62 -11
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -2
- package/fake-snippets-api/lib/db/autoload-snippets.json +4 -0
- package/fake-snippets-api/lib/db/db-client.ts +2 -1
- package/fake-snippets-api/lib/db/schema.ts +1 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package.ts +1 -0
- package/fake-snippets-api/routes/api/package_files/list.ts +6 -3
- package/fake-snippets-api/routes/api/package_releases/get.ts +67 -1
- package/fake-snippets-api/routes/api/packages/create.ts +2 -1
- package/fake-snippets-api/routes/api/snippets/create.ts +1 -0
- package/package.json +2 -1
- package/public/placeholder-logo.png +0 -0
- package/public/placeholder-logo.svg +1 -0
- package/public/placeholder-user.jpg +0 -0
- package/public/placeholder.jpg +0 -0
- package/public/placeholder.svg +1 -0
- package/src/App.tsx +6 -0
- package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +50 -0
- package/src/components/ViewPackagePage/components/file-explorer.tsx +118 -0
- package/src/components/ViewPackagePage/components/important-files-view.tsx +231 -0
- package/src/components/ViewPackagePage/components/main-content-header.tsx +172 -0
- package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +106 -0
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +130 -0
- package/src/components/ViewPackagePage/components/package-header.tsx +107 -0
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +63 -0
- package/src/components/ViewPackagePage/components/readme-view.tsx +58 -0
- package/src/components/ViewPackagePage/components/repo-header-button.tsx +36 -0
- package/src/components/ViewPackagePage/components/repo-header.tsx +4 -0
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +213 -0
- package/src/components/ViewPackagePage/components/repo-tab-header.tsx +12 -0
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +103 -0
- package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +31 -0
- package/src/components/ViewPackagePage/components/sidebar-packages-section.tsx +16 -0
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +44 -0
- package/src/components/ViewPackagePage/components/sidebar.tsx +40 -0
- package/src/components/ViewPackagePage/components/tab-views/3d-view.tsx +9 -0
- package/src/components/ViewPackagePage/components/tab-views/bom-view.tsx +9 -0
- package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +166 -0
- package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +9 -0
- package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +9 -0
- package/src/components/ViewPackagePage/components/theme-toggle.tsx +42 -0
- package/src/components/ViewPackagePage/hooks/use-mobile.tsx +19 -0
- package/src/components/ViewPackagePage/hooks/use-toast.ts +191 -0
- package/src/components/ViewPackagePage/simulate-page.tsx +120 -0
- package/src/components/ViewPackagePage/utils/is-package-file-important.ts +21 -0
- package/src/hooks/use-package-files.ts +29 -0
- package/src/hooks/use-package-release.ts +22 -0
- package/src/index.css +15 -0
- package/src/pages/beta.tsx +282 -99
- package/src/pages/view-package.tsx +38 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Tag, Clock } from "lucide-react"
|
|
2
|
+
import { Skeleton } from "@/components/ui/skeleton"
|
|
3
|
+
|
|
4
|
+
interface SidebarReleasesSectionProps {
|
|
5
|
+
isLoading?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function SidebarReleasesSection({
|
|
9
|
+
isLoading = false,
|
|
10
|
+
}: SidebarReleasesSectionProps) {
|
|
11
|
+
if (isLoading) {
|
|
12
|
+
return (
|
|
13
|
+
<div className="mb-6">
|
|
14
|
+
<h2 className="text-lg font-semibold mb-2">Releases</h2>
|
|
15
|
+
<div className="mb-2">
|
|
16
|
+
<Skeleton className="h-4 w-20" />
|
|
17
|
+
</div>
|
|
18
|
+
<div className="mb-3">
|
|
19
|
+
<Skeleton className="h-4 w-24" />
|
|
20
|
+
</div>
|
|
21
|
+
<Skeleton className="h-4 w-32" />
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="mb-6">
|
|
28
|
+
<h2 className="text-lg font-semibold mb-2">Releases</h2>
|
|
29
|
+
<div className="flex items-center mb-2">
|
|
30
|
+
<Tag className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
|
|
31
|
+
<span className="text-sm font-medium">v0.0.361</span>
|
|
32
|
+
</div>
|
|
33
|
+
<div className="flex items-center mb-3">
|
|
34
|
+
<Clock className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
|
|
35
|
+
<span className="text-sm text-gray-500 dark:text-[#8b949e]">
|
|
36
|
+
2 days ago
|
|
37
|
+
</span>
|
|
38
|
+
</div>
|
|
39
|
+
{/* <a href="#" className="text-blue-600 dark:text-[#58a6ff] hover:underline text-sm">
|
|
40
|
+
Push a new release
|
|
41
|
+
</a> */}
|
|
42
|
+
</div>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useState } from "react"
|
|
4
|
+
import SidebarAboutSection from "./sidebar-about-section"
|
|
5
|
+
import SidebarReleasesSection from "./sidebar-releases-section"
|
|
6
|
+
import PreviewImageSquares from "./preview-image-squares"
|
|
7
|
+
|
|
8
|
+
interface PackageInfo {
|
|
9
|
+
name: string
|
|
10
|
+
unscoped_name: string
|
|
11
|
+
owner_github_username: string
|
|
12
|
+
star_count: string
|
|
13
|
+
description: string
|
|
14
|
+
ai_description: string
|
|
15
|
+
creator_account_id?: string
|
|
16
|
+
owner_org_id?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface SidebarProps {
|
|
20
|
+
packageInfo?: PackageInfo
|
|
21
|
+
isLoading?: boolean
|
|
22
|
+
onViewChange?: (view: "3d" | "pcb" | "schematic") => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function Sidebar({
|
|
26
|
+
packageInfo,
|
|
27
|
+
isLoading = false,
|
|
28
|
+
onViewChange,
|
|
29
|
+
}: SidebarProps) {
|
|
30
|
+
return (
|
|
31
|
+
<div className="h-full p-4 bg-white dark:bg-[#0d1117] overflow-y-auto">
|
|
32
|
+
<SidebarAboutSection packageInfo={packageInfo} isLoading={isLoading} />
|
|
33
|
+
<PreviewImageSquares
|
|
34
|
+
packageInfo={packageInfo}
|
|
35
|
+
onViewChange={onViewChange}
|
|
36
|
+
/>
|
|
37
|
+
<SidebarReleasesSection isLoading={isLoading} />
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default function ThreeDView() {
|
|
2
|
+
return (
|
|
3
|
+
<div className="border border-gray-200 dark:border-[#30363d] rounded-md p-8 mb-4 bg-white dark:bg-[#0d1117] flex items-center justify-center">
|
|
4
|
+
<h2 className="text-xl font-semibold text-gray-500 dark:text-[#8b949e]">
|
|
5
|
+
3D View Tab
|
|
6
|
+
</h2>
|
|
7
|
+
</div>
|
|
8
|
+
)
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default function BOMView() {
|
|
2
|
+
return (
|
|
3
|
+
<div className="border border-gray-200 dark:border-[#30363d] rounded-md p-8 mb-4 bg-white dark:bg-[#0d1117] flex items-center justify-center">
|
|
4
|
+
<h2 className="text-xl font-semibold text-gray-500 dark:text-[#8b949e]">
|
|
5
|
+
BOM View Tab
|
|
6
|
+
</h2>
|
|
7
|
+
</div>
|
|
8
|
+
)
|
|
9
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { FileText, Folder } from "lucide-react"
|
|
4
|
+
import { Skeleton } from "@/components/ui/skeleton"
|
|
5
|
+
|
|
6
|
+
interface Directory {
|
|
7
|
+
type: "directory"
|
|
8
|
+
path: string
|
|
9
|
+
name: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface File {
|
|
13
|
+
type: "file"
|
|
14
|
+
path: string
|
|
15
|
+
name: string
|
|
16
|
+
content: string
|
|
17
|
+
created_at: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface FilesViewProps {
|
|
21
|
+
directories?: Directory[]
|
|
22
|
+
files?: File[]
|
|
23
|
+
isLoading?: boolean
|
|
24
|
+
onFileClicked?: (file: File) => void
|
|
25
|
+
onDirectoryClicked?: (directory: Directory) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default function FilesView({
|
|
29
|
+
directories = [],
|
|
30
|
+
files = [],
|
|
31
|
+
isLoading = false,
|
|
32
|
+
onFileClicked,
|
|
33
|
+
onDirectoryClicked,
|
|
34
|
+
}: FilesViewProps) {
|
|
35
|
+
// Format date for display
|
|
36
|
+
const formatDate = (dateString: string) => {
|
|
37
|
+
const date = new Date(dateString)
|
|
38
|
+
const now = new Date()
|
|
39
|
+
const diffTime = Math.abs(now.getTime() - date.getTime())
|
|
40
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
|
41
|
+
|
|
42
|
+
if (diffDays < 1) return "today"
|
|
43
|
+
if (diffDays === 1) return "yesterday"
|
|
44
|
+
if (diffDays < 7) return `${diffDays} days ago`
|
|
45
|
+
if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`
|
|
46
|
+
if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago`
|
|
47
|
+
return `${Math.floor(diffDays / 365)} years ago`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Combine directories and files for display
|
|
51
|
+
const items = [
|
|
52
|
+
...directories.map((dir) => ({
|
|
53
|
+
...dir,
|
|
54
|
+
message: "", // TODO insert ai description of directory here!
|
|
55
|
+
time: "",
|
|
56
|
+
})),
|
|
57
|
+
...files.map((file) => ({
|
|
58
|
+
...file,
|
|
59
|
+
message: "", // TODO insert ai description of file here!
|
|
60
|
+
time: formatDate(file.created_at),
|
|
61
|
+
})),
|
|
62
|
+
].sort((a, b) => {
|
|
63
|
+
// Sort directories first, then files
|
|
64
|
+
if (a.type === "directory" && b.type === "file") return -1
|
|
65
|
+
if (a.type === "file" && b.type === "directory") return 1
|
|
66
|
+
// Then sort alphabetically by name
|
|
67
|
+
return a.name.localeCompare(b.name)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const handleItemClick = (item: any) => {
|
|
71
|
+
if (item.type === "directory" && onDirectoryClicked) {
|
|
72
|
+
onDirectoryClicked(item)
|
|
73
|
+
} else if (item.type === "file" && onFileClicked) {
|
|
74
|
+
onFileClicked(item)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (isLoading) {
|
|
79
|
+
return (
|
|
80
|
+
<div className="mb-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
|
|
81
|
+
<div className="flex items-center px-4 py-2 md:py-3 bg-gray-100 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d]">
|
|
82
|
+
<Skeleton className="h-4 w-24" />
|
|
83
|
+
<div className="ml-auto flex items-center space-x-4">
|
|
84
|
+
<Skeleton className="h-4 w-20" />
|
|
85
|
+
<Skeleton className="h-4 w-24" />
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div className="bg-white dark:bg-[#0d1117]">
|
|
89
|
+
{[...Array(5)].map((_, index) => (
|
|
90
|
+
<div
|
|
91
|
+
key={index}
|
|
92
|
+
className="flex items-center px-4 py-2 border-b border-gray-200 dark:border-[#30363d]"
|
|
93
|
+
>
|
|
94
|
+
<Skeleton className="h-4 w-4 mr-2" />
|
|
95
|
+
<Skeleton className="h-4 w-32" />
|
|
96
|
+
<div className="ml-auto flex items-center space-x-4">
|
|
97
|
+
<Skeleton className="h-4 w-40" />
|
|
98
|
+
<Skeleton className="h-4 w-16" />
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
))}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div className="mb-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
|
|
109
|
+
<div className="flex items-center px-4 py-2 md:py-3 bg-gray-100 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d]">
|
|
110
|
+
{/* Desktop view */}
|
|
111
|
+
<div className="hidden md:flex items-center text-xs">
|
|
112
|
+
<span className="text-gray-500 dark:text-[#8b949e]">Files</span>
|
|
113
|
+
</div>
|
|
114
|
+
<div className="hidden md:flex ml-auto items-center text-xs text-gray-500 dark:text-[#8b949e]">
|
|
115
|
+
<span>
|
|
116
|
+
{files.length} files, {directories.length} directories
|
|
117
|
+
</span>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{/* Mobile view */}
|
|
121
|
+
<div className="md:hidden flex items-center justify-between w-full">
|
|
122
|
+
<div className="flex items-center">
|
|
123
|
+
<span className="text-xs text-gray-500 dark:text-[#8b949e]">
|
|
124
|
+
Files
|
|
125
|
+
</span>
|
|
126
|
+
</div>
|
|
127
|
+
<div className="flex items-center text-xs text-gray-500 dark:text-[#8b949e]">
|
|
128
|
+
<span>{files.length + directories.length} items</span>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{/* Files and Directories */}
|
|
134
|
+
<div className="bg-white dark:bg-[#0d1117]">
|
|
135
|
+
{items.length === 0 ? (
|
|
136
|
+
<div className="px-4 py-8 text-center text-gray-500 dark:text-[#8b949e]">
|
|
137
|
+
No files found
|
|
138
|
+
</div>
|
|
139
|
+
) : (
|
|
140
|
+
items.map((item, index) => (
|
|
141
|
+
<div
|
|
142
|
+
key={index}
|
|
143
|
+
className="flex items-center px-4 py-2 hover:bg-gray-50 dark:hover:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d] cursor-pointer group"
|
|
144
|
+
onClick={() => handleItemClick(item)}
|
|
145
|
+
>
|
|
146
|
+
{item.type === "directory" ? (
|
|
147
|
+
<Folder className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
|
|
148
|
+
) : (
|
|
149
|
+
<FileText className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
|
|
150
|
+
)}
|
|
151
|
+
<span className="text-sm group-hover:underline">{item.name}</span>
|
|
152
|
+
<span className="ml-auto text-xs text-gray-500 dark:text-[#8b949e]">
|
|
153
|
+
{item.message}
|
|
154
|
+
</span>
|
|
155
|
+
{item.time && (
|
|
156
|
+
<span className="ml-4 text-xs text-gray-500 dark:text-[#8b949e]">
|
|
157
|
+
{item.time}
|
|
158
|
+
</span>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
))
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default function PCBView() {
|
|
2
|
+
return (
|
|
3
|
+
<div className="border border-gray-200 dark:border-[#30363d] rounded-md p-8 mb-4 bg-white dark:bg-[#0d1117] flex items-center justify-center">
|
|
4
|
+
<h2 className="text-xl font-semibold text-gray-500 dark:text-[#8b949e]">
|
|
5
|
+
PCB View Tab
|
|
6
|
+
</h2>
|
|
7
|
+
</div>
|
|
8
|
+
)
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default function SchematicView() {
|
|
2
|
+
return (
|
|
3
|
+
<div className="border border-gray-200 dark:border-[#30363d] rounded-md p-8 mb-4 bg-white dark:bg-[#0d1117] flex items-center justify-center">
|
|
4
|
+
<h2 className="text-xl font-semibold text-gray-500 dark:text-[#8b949e]">
|
|
5
|
+
Schematic View Tab
|
|
6
|
+
</h2>
|
|
7
|
+
</div>
|
|
8
|
+
)
|
|
9
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useTheme } from "next-themes"
|
|
4
|
+
import { Moon, Sun } from "lucide-react"
|
|
5
|
+
import { Button } from "@/components/ui/button"
|
|
6
|
+
import { useEffect, useState } from "react"
|
|
7
|
+
|
|
8
|
+
export default function ThemeToggle() {
|
|
9
|
+
const { theme, setTheme } = useTheme()
|
|
10
|
+
const [mounted, setMounted] = useState(false)
|
|
11
|
+
|
|
12
|
+
// Ensure component is mounted before accessing theme
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
setMounted(true)
|
|
15
|
+
}, [])
|
|
16
|
+
|
|
17
|
+
if (!mounted) {
|
|
18
|
+
return (
|
|
19
|
+
<Button
|
|
20
|
+
variant="outline"
|
|
21
|
+
size="icon"
|
|
22
|
+
className="border-gray-300 dark:border-[#30363d] bg-gray-100 hover:bg-gray-200 dark:bg-[#21262d] dark:hover:bg-[#30363d] text-gray-700 dark:text-[#c9d1d9]"
|
|
23
|
+
>
|
|
24
|
+
<Sun className="h-4 w-4" />
|
|
25
|
+
<span className="sr-only">Toggle theme</span>
|
|
26
|
+
</Button>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Button
|
|
32
|
+
variant="outline"
|
|
33
|
+
size="icon"
|
|
34
|
+
className="border-gray-300 dark:border-[#30363d] bg-gray-100 hover:bg-gray-200 dark:bg-[#21262d] dark:hover:bg-[#30363d] text-gray-700 dark:text-[#c9d1d9]"
|
|
35
|
+
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
|
36
|
+
>
|
|
37
|
+
<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
|
38
|
+
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
|
39
|
+
<span className="sr-only">Toggle theme</span>
|
|
40
|
+
</Button>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768
|
|
4
|
+
|
|
5
|
+
export function useIsMobile() {
|
|
6
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
|
7
|
+
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
10
|
+
const onChange = () => {
|
|
11
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
12
|
+
}
|
|
13
|
+
mql.addEventListener("change", onChange)
|
|
14
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
15
|
+
return () => mql.removeEventListener("change", onChange)
|
|
16
|
+
}, [])
|
|
17
|
+
|
|
18
|
+
return !!isMobile
|
|
19
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
// Inspired by react-hot-toast library
|
|
4
|
+
import * as React from "react"
|
|
5
|
+
|
|
6
|
+
import type { ToastActionElement, ToastProps } from "@/components/ui/toast"
|
|
7
|
+
|
|
8
|
+
const TOAST_LIMIT = 1
|
|
9
|
+
const TOAST_REMOVE_DELAY = 1000000
|
|
10
|
+
|
|
11
|
+
type ToasterToast = ToastProps & {
|
|
12
|
+
id: string
|
|
13
|
+
title?: React.ReactNode
|
|
14
|
+
description?: React.ReactNode
|
|
15
|
+
action?: ToastActionElement
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const actionTypes = {
|
|
19
|
+
ADD_TOAST: "ADD_TOAST",
|
|
20
|
+
UPDATE_TOAST: "UPDATE_TOAST",
|
|
21
|
+
DISMISS_TOAST: "DISMISS_TOAST",
|
|
22
|
+
REMOVE_TOAST: "REMOVE_TOAST",
|
|
23
|
+
} as const
|
|
24
|
+
|
|
25
|
+
let count = 0
|
|
26
|
+
|
|
27
|
+
function genId() {
|
|
28
|
+
count = (count + 1) % Number.MAX_SAFE_INTEGER
|
|
29
|
+
return count.toString()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type ActionType = typeof actionTypes
|
|
33
|
+
|
|
34
|
+
type Action =
|
|
35
|
+
| {
|
|
36
|
+
type: ActionType["ADD_TOAST"]
|
|
37
|
+
toast: ToasterToast
|
|
38
|
+
}
|
|
39
|
+
| {
|
|
40
|
+
type: ActionType["UPDATE_TOAST"]
|
|
41
|
+
toast: Partial<ToasterToast>
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
type: ActionType["DISMISS_TOAST"]
|
|
45
|
+
toastId?: ToasterToast["id"]
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
type: ActionType["REMOVE_TOAST"]
|
|
49
|
+
toastId?: ToasterToast["id"]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface State {
|
|
53
|
+
toasts: ToasterToast[]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
|
57
|
+
|
|
58
|
+
const addToRemoveQueue = (toastId: string) => {
|
|
59
|
+
if (toastTimeouts.has(toastId)) {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const timeout = setTimeout(() => {
|
|
64
|
+
toastTimeouts.delete(toastId)
|
|
65
|
+
dispatch({
|
|
66
|
+
type: "REMOVE_TOAST",
|
|
67
|
+
toastId: toastId,
|
|
68
|
+
})
|
|
69
|
+
}, TOAST_REMOVE_DELAY)
|
|
70
|
+
|
|
71
|
+
toastTimeouts.set(toastId, timeout)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const reducer = (state: State, action: Action): State => {
|
|
75
|
+
switch (action.type) {
|
|
76
|
+
case "ADD_TOAST":
|
|
77
|
+
return {
|
|
78
|
+
...state,
|
|
79
|
+
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case "UPDATE_TOAST":
|
|
83
|
+
return {
|
|
84
|
+
...state,
|
|
85
|
+
toasts: state.toasts.map((t) =>
|
|
86
|
+
t.id === action.toast.id ? { ...t, ...action.toast } : t,
|
|
87
|
+
),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
case "DISMISS_TOAST": {
|
|
91
|
+
const { toastId } = action
|
|
92
|
+
|
|
93
|
+
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
|
94
|
+
// but I'll keep it here for simplicity
|
|
95
|
+
if (toastId) {
|
|
96
|
+
addToRemoveQueue(toastId)
|
|
97
|
+
} else {
|
|
98
|
+
state.toasts.forEach((toast) => {
|
|
99
|
+
addToRemoveQueue(toast.id)
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
...state,
|
|
105
|
+
toasts: state.toasts.map((t) =>
|
|
106
|
+
t.id === toastId || toastId === undefined
|
|
107
|
+
? {
|
|
108
|
+
...t,
|
|
109
|
+
open: false,
|
|
110
|
+
}
|
|
111
|
+
: t,
|
|
112
|
+
),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
case "REMOVE_TOAST":
|
|
116
|
+
if (action.toastId === undefined) {
|
|
117
|
+
return {
|
|
118
|
+
...state,
|
|
119
|
+
toasts: [],
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
...state,
|
|
124
|
+
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const listeners: Array<(state: State) => void> = []
|
|
130
|
+
|
|
131
|
+
let memoryState: State = { toasts: [] }
|
|
132
|
+
|
|
133
|
+
function dispatch(action: Action) {
|
|
134
|
+
memoryState = reducer(memoryState, action)
|
|
135
|
+
listeners.forEach((listener) => {
|
|
136
|
+
listener(memoryState)
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
type Toast = Omit<ToasterToast, "id">
|
|
141
|
+
|
|
142
|
+
function toast({ ...props }: Toast) {
|
|
143
|
+
const id = genId()
|
|
144
|
+
|
|
145
|
+
const update = (props: ToasterToast) =>
|
|
146
|
+
dispatch({
|
|
147
|
+
type: "UPDATE_TOAST",
|
|
148
|
+
toast: { ...props, id },
|
|
149
|
+
})
|
|
150
|
+
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
|
|
151
|
+
|
|
152
|
+
dispatch({
|
|
153
|
+
type: "ADD_TOAST",
|
|
154
|
+
toast: {
|
|
155
|
+
...props,
|
|
156
|
+
id,
|
|
157
|
+
open: true,
|
|
158
|
+
onOpenChange: (open) => {
|
|
159
|
+
if (!open) dismiss()
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
id: id,
|
|
166
|
+
dismiss,
|
|
167
|
+
update,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function useToast() {
|
|
172
|
+
const [state, setState] = React.useState<State>(memoryState)
|
|
173
|
+
|
|
174
|
+
React.useEffect(() => {
|
|
175
|
+
listeners.push(setState)
|
|
176
|
+
return () => {
|
|
177
|
+
const index = listeners.indexOf(setState)
|
|
178
|
+
if (index > -1) {
|
|
179
|
+
listeners.splice(index, 1)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}, [state])
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
...state,
|
|
186
|
+
toast,
|
|
187
|
+
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export { useToast, toast }
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react"
|
|
4
|
+
import RepoPageContent from "./components/repo-page-content"
|
|
5
|
+
|
|
6
|
+
// Sample data for simulation
|
|
7
|
+
const samplePackageFiles = [
|
|
8
|
+
{
|
|
9
|
+
package_file_id: "1",
|
|
10
|
+
package_release_id: "1",
|
|
11
|
+
file_path: "README.md",
|
|
12
|
+
content_text:
|
|
13
|
+
"# @tscircuit/keyboard-default60\n\nA Default 60 keyboard created with tscircuit\n\n## Features\n\n- Full mechanical keyboard PCB design\n- Compatible with standard 60% cases\n- USB-C connector\n- Supports QMK firmware",
|
|
14
|
+
created_at: "2023-04-15T12:00:00Z",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
package_file_id: "2",
|
|
18
|
+
package_release_id: "1",
|
|
19
|
+
file_path: "LICENSE",
|
|
20
|
+
content_text:
|
|
21
|
+
'MIT License\n\nCopyright (c) 2023 tscircuit\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions...',
|
|
22
|
+
created_at: "2023-04-15T12:00:00Z",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
package_file_id: "3",
|
|
26
|
+
package_release_id: "1",
|
|
27
|
+
file_path: "lib/index.tsx",
|
|
28
|
+
content_text:
|
|
29
|
+
"import React from 'react'\nimport { PCB, Resistor, Capacitor, LED } from '@tscircuit/react'\n\nexport const KeyboardDefault60 = () => {\n return (\n <PCB name=\"keyboard-default60\">\n {/* Main controller */}\n <Microcontroller x={50} y={50} name=\"MCU1\" />\n \n {/* Key matrix */}\n <KeyMatrix x={100} y={100} rows={5} cols={14} />\n \n {/* Power circuit */}\n <PowerCircuit x={20} y={20} />\n </PCB>\n )\n}",
|
|
30
|
+
created_at: "2023-04-15T12:00:00Z",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
package_file_id: "4",
|
|
34
|
+
package_release_id: "1",
|
|
35
|
+
file_path: "lib/components/KeyMatrix.tsx",
|
|
36
|
+
content_text:
|
|
37
|
+
"import React from 'react'\nimport { Switch, Diode } from '@tscircuit/react'\n\ninterface KeyMatrixProps {\n x: number\n y: number\n rows: number\n cols: number\n}\n\nexport const KeyMatrix = ({ x, y, rows, cols }: KeyMatrixProps) => {\n const keys = []\n \n for (let row = 0; row < rows; row++) {\n for (let col = 0; col < cols; col++) {\n keys.push(\n <Switch\n key={`key-${row}-${col}`}\n x={x + col * 19.05}\n y={y + row * 19.05}\n name={`SW${row * cols + col + 1}`}\n />\n )\n \n keys.push(\n <Diode\n key={`diode-${row}-${col}`}\n x={x + col * 19.05 + 5}\n y={y + row * 19.05 + 5}\n name={`D${row * cols + col + 1}`}\n />\n )\n }\n }\n \n return <>{keys}</>\n}",
|
|
38
|
+
created_at: "2023-04-15T12:00:00Z",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
package_file_id: "5",
|
|
42
|
+
package_release_id: "1",
|
|
43
|
+
file_path: "lib/components/PowerCircuit.tsx",
|
|
44
|
+
content_text:
|
|
45
|
+
'import React from \'react\'\nimport { Capacitor, Resistor, Regulator } from \'@tscircuit/react\'\n\ninterface PowerCircuitProps {\n x: number\n y: number\n}\n\nexport const PowerCircuit = ({ x, y }: PowerCircuitProps) => {\n return (\n <>\n <Regulator\n x={x}\n y={y}\n name="U1"\n value="AP2112K-3.3"\n />\n <Capacitor\n x={x - 10}\n y={y}\n name="C1"\n value="10uF"\n />\n <Capacitor\n x={x + 10}\n y={y}\n name="C2"\n value="10uF"\n />\n <Resistor\n x={x}\n y={y + 10}\n name="R1"\n value="10k"\n />\n </>\n )\n}',
|
|
46
|
+
created_at: "2023-04-15T12:00:00Z",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
package_file_id: "6",
|
|
50
|
+
package_release_id: "1",
|
|
51
|
+
file_path: "package.json",
|
|
52
|
+
content_text:
|
|
53
|
+
'{\n "name": "@tscircuit/keyboard-default60",\n "version": "0.0.361",\n "description": "A Default 60 keyboard created with tscircuit",\n "main": "dist/index.js",\n "types": "dist/index.d.ts",\n "scripts": {\n "build": "tsc",\n "test": "jest"\n },\n "dependencies": {\n "@tscircuit/react": "^0.1.0",\n "react": "^18.2.0"\n },\n "devDependencies": {\n "typescript": "^5.0.0",\n "jest": "^29.0.0"\n },\n "license": "MIT"\n}',
|
|
54
|
+
created_at: "2023-04-15T12:00:00Z",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
package_file_id: "7",
|
|
58
|
+
package_release_id: "1",
|
|
59
|
+
file_path: "tsconfig.json",
|
|
60
|
+
content_text:
|
|
61
|
+
'{\n "compilerOptions": {\n "target": "es2020",\n "module": "esnext",\n "moduleResolution": "node",\n "declaration": true,\n "outDir": "./dist",\n "strict": true,\n "esModuleInterop": true,\n "skipLibCheck": true,\n "forceConsistentCasingInFileNames": true,\n "jsx": "react"\n },\n "include": ["lib/**/*"],\n "exclude": ["node_modules", "**/*.test.ts"]\n}',
|
|
62
|
+
created_at: "2023-04-15T12:00:00Z",
|
|
63
|
+
},
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
const samplePackageInfo = {
|
|
67
|
+
name: "@tscircuit/keyboard-default60",
|
|
68
|
+
unscoped_name: "keyboard-default60",
|
|
69
|
+
owner_github_username: "tscircuit",
|
|
70
|
+
star_count: "16",
|
|
71
|
+
description: "A Default 60 keyboard created with tscircuit",
|
|
72
|
+
ai_description:
|
|
73
|
+
"This package contains the PCB design for a standard 60% mechanical keyboard layout, created using the tscircuit library. It includes the schematic, PCB layout, and 3D model.",
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default function SimulatePage() {
|
|
77
|
+
const [packageFiles, setPackageFiles] = useState<any>(null)
|
|
78
|
+
const [packageInfo, setPackageInfo] = useState<any>(null)
|
|
79
|
+
const [loading, setLoading] = useState(true)
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
// Simulate loading data after 1 second
|
|
83
|
+
const timer = setTimeout(() => {
|
|
84
|
+
setPackageFiles(samplePackageFiles)
|
|
85
|
+
setPackageInfo(samplePackageInfo)
|
|
86
|
+
setLoading(false)
|
|
87
|
+
}, 1000)
|
|
88
|
+
|
|
89
|
+
return () => clearTimeout(timer)
|
|
90
|
+
}, [])
|
|
91
|
+
|
|
92
|
+
// Event handlers with window.alert for testing
|
|
93
|
+
const handleFileClicked = (file: any) => {
|
|
94
|
+
window.alert(`File clicked: ${file.name}`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const handleDirectoryClicked = (directory: any) => {
|
|
98
|
+
window.alert(`Directory clicked: ${directory.name}`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const handleExportClicked = (exportType: string) => {
|
|
102
|
+
window.alert(`Export clicked: ${exportType}`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const handleEditClicked = () => {
|
|
106
|
+
window.alert("Edit button clicked")
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<RepoPageContent
|
|
111
|
+
packageFiles={packageFiles}
|
|
112
|
+
packageInfo={packageInfo}
|
|
113
|
+
importantFilePaths={["README.md", "LICENSE", "package.json"]}
|
|
114
|
+
onFileClicked={handleFileClicked}
|
|
115
|
+
onDirectoryClicked={handleDirectoryClicked}
|
|
116
|
+
onExportClicked={handleExportClicked}
|
|
117
|
+
onEditClicked={handleEditClicked}
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
}
|