@tscircuit/fake-snippets 0.0.27 → 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 (55) 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 +14 -9
  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/packages/create.ts +2 -1
  17. package/fake-snippets-api/routes/api/snippets/create.ts +1 -0
  18. package/package.json +2 -1
  19. package/public/placeholder-logo.png +0 -0
  20. package/public/placeholder-logo.svg +1 -0
  21. package/public/placeholder-user.jpg +0 -0
  22. package/public/placeholder.jpg +0 -0
  23. package/public/placeholder.svg +1 -0
  24. package/src/App.tsx +5 -0
  25. package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +50 -0
  26. package/src/components/ViewPackagePage/components/file-explorer.tsx +118 -0
  27. package/src/components/ViewPackagePage/components/important-files-view.tsx +231 -0
  28. package/src/components/ViewPackagePage/components/main-content-header.tsx +172 -0
  29. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +106 -0
  30. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +130 -0
  31. package/src/components/ViewPackagePage/components/package-header.tsx +107 -0
  32. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +63 -0
  33. package/src/components/ViewPackagePage/components/readme-view.tsx +58 -0
  34. package/src/components/ViewPackagePage/components/repo-header-button.tsx +36 -0
  35. package/src/components/ViewPackagePage/components/repo-header.tsx +4 -0
  36. package/src/components/ViewPackagePage/components/repo-page-content.tsx +213 -0
  37. package/src/components/ViewPackagePage/components/repo-tab-header.tsx +12 -0
  38. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +103 -0
  39. package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +31 -0
  40. package/src/components/ViewPackagePage/components/sidebar-packages-section.tsx +16 -0
  41. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +44 -0
  42. package/src/components/ViewPackagePage/components/sidebar.tsx +40 -0
  43. package/src/components/ViewPackagePage/components/tab-views/3d-view.tsx +9 -0
  44. package/src/components/ViewPackagePage/components/tab-views/bom-view.tsx +9 -0
  45. package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +166 -0
  46. package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +9 -0
  47. package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +9 -0
  48. package/src/components/ViewPackagePage/components/theme-toggle.tsx +42 -0
  49. package/src/components/ViewPackagePage/hooks/use-mobile.tsx +19 -0
  50. package/src/components/ViewPackagePage/hooks/use-toast.ts +191 -0
  51. package/src/components/ViewPackagePage/simulate-page.tsx +120 -0
  52. package/src/components/ViewPackagePage/utils/is-package-file-important.ts +21 -0
  53. package/src/hooks/use-package-files.ts +1 -1
  54. package/src/index.css +15 -0
  55. package/src/pages/view-package.tsx +38 -0
@@ -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
+ }
@@ -0,0 +1,21 @@
1
+ const importanceMap = {
2
+ "readme.md": 200,
3
+ license: 100,
4
+ "license.md": 100,
5
+ "index.ts": 90,
6
+ "index.tsx": 90,
7
+ "circuit.tsx": 90,
8
+ }
9
+
10
+ export const scorePackageFileImportance = (filePath: string) => {
11
+ for (const [key, value] of Object.entries(importanceMap)) {
12
+ if (filePath.endsWith(key)) {
13
+ return value
14
+ }
15
+ }
16
+ return 0
17
+ }
18
+
19
+ export const isPackageFileImportant = (filePath: string) => {
20
+ return scorePackageFileImportance(filePath) > 0
21
+ }
@@ -84,7 +84,7 @@ export const usePackageFileByRelease = (
84
84
  }
85
85
 
86
86
  // Hook to list all files for a package release
87
- export const usePackageFiles = (packageReleaseId: string | null) => {
87
+ export const usePackageFiles = (packageReleaseId?: string | null) => {
88
88
  const axios = useAxios()
89
89
 
90
90
  return useQuery<PackageFile[], Error & { status: number }>(
package/src/index.css CHANGED
@@ -19,3 +19,18 @@
19
19
  .animate-fadeIn {
20
20
  animation: fadeIn 0.3s ease-in-out;
21
21
  }
22
+
23
+ .shiki code {
24
+ counter-reset: step;
25
+ counter-increment: step 0;
26
+ }
27
+
28
+ .shiki code .line::before {
29
+ content: counter(step);
30
+ counter-increment: step;
31
+ width: 1rem;
32
+ margin-right: 1.5rem;
33
+ display: inline-block;
34
+ text-align: right;
35
+ color: rgba(115, 138, 148, 0.4);
36
+ }
@@ -0,0 +1,38 @@
1
+ import RepoPageContent from "@/components/ViewPackagePage/components/repo-page-content"
2
+ import SimulatePage from "@/components/ViewPackagePage/simulate-page"
3
+ import { usePackageByName } from "@/hooks/use-package-by-package-name"
4
+ import { usePackageFiles } from "@/hooks/use-package-files"
5
+ import { usePackageRelease } from "@/hooks/use-package-release"
6
+ import { useLocation } from "wouter"
7
+
8
+ export const ViewPackagePage = () => {
9
+ // Get the current path and extract author/packageName
10
+ const [location] = useLocation()
11
+ const pathParts = location.split("/")
12
+ const author = pathParts[2]
13
+ const urlPackageName = pathParts[3]
14
+ const fullPackageName = `${author}/${urlPackageName}`
15
+
16
+ const { data: packageInfo } = usePackageByName(fullPackageName)
17
+
18
+ const { data: packageRelease } = usePackageRelease({
19
+ is_latest: true,
20
+ package_name: fullPackageName,
21
+ })
22
+
23
+ const { data: packageFiles } = usePackageFiles(
24
+ packageRelease?.package_release_id,
25
+ )
26
+
27
+ return (
28
+ <RepoPageContent
29
+ packageFiles={packageFiles as any}
30
+ packageInfo={packageInfo as any}
31
+ importantFilePaths={["README.md", "LICENSE", "package.json"]}
32
+ onFileClicked={() => {}}
33
+ onDirectoryClicked={() => {}}
34
+ onExportClicked={() => {}}
35
+ onEditClicked={() => {}}
36
+ />
37
+ )
38
+ }