@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.
Files changed (58) hide show
  1. package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +5 -6
  2. package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +3 -0
  3. package/bun-tests/fake-snippets-api/routes/packages/list-2.test.ts +2 -0
  4. package/bun-tests/fake-snippets-api/routes/snippets/list_newest.test.ts +5 -3
  5. package/bun-tests/fake-snippets-api/routes/snippets/list_trending.test.ts +8 -5
  6. package/bun-tests/fake-snippets-api/routes/snippets/update.test.ts +1 -1
  7. package/bun.lock +110 -5
  8. package/dist/bundle.js +62 -11
  9. package/dist/index.d.ts +5 -0
  10. package/dist/index.js +4 -2
  11. package/fake-snippets-api/lib/db/autoload-snippets.json +4 -0
  12. package/fake-snippets-api/lib/db/db-client.ts +2 -1
  13. package/fake-snippets-api/lib/db/schema.ts +1 -0
  14. package/fake-snippets-api/lib/public-mapping/public-map-package.ts +1 -0
  15. package/fake-snippets-api/routes/api/package_files/list.ts +6 -3
  16. package/fake-snippets-api/routes/api/package_releases/get.ts +67 -1
  17. package/fake-snippets-api/routes/api/packages/create.ts +2 -1
  18. package/fake-snippets-api/routes/api/snippets/create.ts +1 -0
  19. package/package.json +2 -1
  20. package/public/placeholder-logo.png +0 -0
  21. package/public/placeholder-logo.svg +1 -0
  22. package/public/placeholder-user.jpg +0 -0
  23. package/public/placeholder.jpg +0 -0
  24. package/public/placeholder.svg +1 -0
  25. package/src/App.tsx +6 -0
  26. package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +50 -0
  27. package/src/components/ViewPackagePage/components/file-explorer.tsx +118 -0
  28. package/src/components/ViewPackagePage/components/important-files-view.tsx +231 -0
  29. package/src/components/ViewPackagePage/components/main-content-header.tsx +172 -0
  30. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +106 -0
  31. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +130 -0
  32. package/src/components/ViewPackagePage/components/package-header.tsx +107 -0
  33. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +63 -0
  34. package/src/components/ViewPackagePage/components/readme-view.tsx +58 -0
  35. package/src/components/ViewPackagePage/components/repo-header-button.tsx +36 -0
  36. package/src/components/ViewPackagePage/components/repo-header.tsx +4 -0
  37. package/src/components/ViewPackagePage/components/repo-page-content.tsx +213 -0
  38. package/src/components/ViewPackagePage/components/repo-tab-header.tsx +12 -0
  39. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +103 -0
  40. package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +31 -0
  41. package/src/components/ViewPackagePage/components/sidebar-packages-section.tsx +16 -0
  42. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +44 -0
  43. package/src/components/ViewPackagePage/components/sidebar.tsx +40 -0
  44. package/src/components/ViewPackagePage/components/tab-views/3d-view.tsx +9 -0
  45. package/src/components/ViewPackagePage/components/tab-views/bom-view.tsx +9 -0
  46. package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +166 -0
  47. package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +9 -0
  48. package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +9 -0
  49. package/src/components/ViewPackagePage/components/theme-toggle.tsx +42 -0
  50. package/src/components/ViewPackagePage/hooks/use-mobile.tsx +19 -0
  51. package/src/components/ViewPackagePage/hooks/use-toast.ts +191 -0
  52. package/src/components/ViewPackagePage/simulate-page.tsx +120 -0
  53. package/src/components/ViewPackagePage/utils/is-package-file-important.ts +21 -0
  54. package/src/hooks/use-package-files.ts +29 -0
  55. package/src/hooks/use-package-release.ts +22 -0
  56. package/src/index.css +15 -0
  57. package/src/pages/beta.tsx +282 -99
  58. 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
+ }