@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.
- 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 +14 -9
- 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/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 +5 -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 +1 -1
- package/src/index.css +15 -0
- package/src/pages/view-package.tsx +38 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useMemo } from "react"
|
|
4
|
+
import MainContentHeader from "./main-content-header"
|
|
5
|
+
import Sidebar from "./sidebar"
|
|
6
|
+
import MobileSidebar from "./mobile-sidebar"
|
|
7
|
+
import ImportantFilesView from "./important-files-view"
|
|
8
|
+
import { ShikiCodeViewer } from "./ShikiCodeViewer"
|
|
9
|
+
|
|
10
|
+
// Tab Views
|
|
11
|
+
import FilesView from "./tab-views/files-view"
|
|
12
|
+
import ThreeDView from "./tab-views/3d-view"
|
|
13
|
+
import PCBView from "./tab-views/pcb-view"
|
|
14
|
+
import SchematicView from "./tab-views/schematic-view"
|
|
15
|
+
import BOMView from "./tab-views/bom-view"
|
|
16
|
+
import { isPackageFileImportant } from "../utils/is-package-file-important"
|
|
17
|
+
import Header from "@/components/Header"
|
|
18
|
+
import Footer from "@/components/Footer"
|
|
19
|
+
import ViewSnippetHeader from "@/components/ViewSnippetHeader"
|
|
20
|
+
import PackageHeader from "./package-header"
|
|
21
|
+
|
|
22
|
+
interface PackageFile {
|
|
23
|
+
package_file_id: string
|
|
24
|
+
package_release_id: string
|
|
25
|
+
file_path: string
|
|
26
|
+
content_text: string
|
|
27
|
+
created_at: string // iso-8601
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface PackageInfo {
|
|
31
|
+
name: string
|
|
32
|
+
unscoped_name: string
|
|
33
|
+
owner_github_username: string
|
|
34
|
+
star_count: string
|
|
35
|
+
description: string
|
|
36
|
+
ai_description: string
|
|
37
|
+
ai_usage_instructions: string
|
|
38
|
+
creator_account_id?: string
|
|
39
|
+
owner_org_id?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface RepoPageContentProps {
|
|
43
|
+
packageFiles?: PackageFile[]
|
|
44
|
+
importantFilePaths?: string[]
|
|
45
|
+
packageInfo?: PackageInfo
|
|
46
|
+
onFileClicked?: (file: any) => void
|
|
47
|
+
onDirectoryClicked?: (directory: any) => void
|
|
48
|
+
onExportClicked?: (exportType: string) => void
|
|
49
|
+
onEditClicked?: () => void
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default function RepoPageContent({
|
|
53
|
+
packageFiles,
|
|
54
|
+
packageInfo,
|
|
55
|
+
onFileClicked,
|
|
56
|
+
onDirectoryClicked,
|
|
57
|
+
onExportClicked,
|
|
58
|
+
onEditClicked,
|
|
59
|
+
}: RepoPageContentProps) {
|
|
60
|
+
const [activeView, setActiveView] = useState("files")
|
|
61
|
+
|
|
62
|
+
const importantFilePaths = packageFiles
|
|
63
|
+
?.filter((pf) => isPackageFileImportant(pf.file_path))
|
|
64
|
+
?.map((pf) => pf.file_path)
|
|
65
|
+
|
|
66
|
+
// Parse package files to determine directories and files structure
|
|
67
|
+
const { directories, files } = useMemo(() => {
|
|
68
|
+
if (!packageFiles) {
|
|
69
|
+
return { directories: [], files: [] }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const dirs = new Set<string>()
|
|
73
|
+
const filesList: Array<{
|
|
74
|
+
type: "file"
|
|
75
|
+
path: string
|
|
76
|
+
name: string
|
|
77
|
+
created_at: string
|
|
78
|
+
}> = []
|
|
79
|
+
|
|
80
|
+
packageFiles.forEach((file) => {
|
|
81
|
+
// Extract directory path
|
|
82
|
+
const pathParts = file.file_path.split("/")
|
|
83
|
+
const fileName = pathParts.pop() || ""
|
|
84
|
+
|
|
85
|
+
// Add all parent directories
|
|
86
|
+
let currentPath = ""
|
|
87
|
+
pathParts.forEach((part) => {
|
|
88
|
+
currentPath += (currentPath ? "/" : "") + part
|
|
89
|
+
dirs.add(currentPath)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
filesList.push({
|
|
93
|
+
type: "file",
|
|
94
|
+
path: file.file_path,
|
|
95
|
+
name: fileName,
|
|
96
|
+
created_at: file.created_at,
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// Convert directories set to array of directory objects
|
|
101
|
+
const dirsList = Array.from(dirs)
|
|
102
|
+
.map((path) => {
|
|
103
|
+
const pathParts = path.split("/")
|
|
104
|
+
if (!path) return null
|
|
105
|
+
return {
|
|
106
|
+
type: "directory",
|
|
107
|
+
path,
|
|
108
|
+
name: pathParts[pathParts.length - 1],
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
.filter((dir) => dir !== null)
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
directories: dirsList,
|
|
115
|
+
files: filesList,
|
|
116
|
+
}
|
|
117
|
+
}, [packageFiles])
|
|
118
|
+
|
|
119
|
+
// Find important files based on importantFilePaths
|
|
120
|
+
const importantFiles = useMemo(() => {
|
|
121
|
+
if (!packageFiles || !importantFilePaths) return []
|
|
122
|
+
|
|
123
|
+
return packageFiles.filter((file) =>
|
|
124
|
+
importantFilePaths.some((path) => file.file_path.endsWith(path)),
|
|
125
|
+
)
|
|
126
|
+
}, [packageFiles, importantFilePaths])
|
|
127
|
+
|
|
128
|
+
// Render the appropriate content based on the active view
|
|
129
|
+
const renderContent = () => {
|
|
130
|
+
switch (activeView) {
|
|
131
|
+
case "files":
|
|
132
|
+
return (
|
|
133
|
+
<FilesView
|
|
134
|
+
directories={directories as any}
|
|
135
|
+
files={files as any}
|
|
136
|
+
isLoading={!packageFiles}
|
|
137
|
+
onFileClicked={onFileClicked}
|
|
138
|
+
onDirectoryClicked={onDirectoryClicked}
|
|
139
|
+
/>
|
|
140
|
+
)
|
|
141
|
+
case "3d":
|
|
142
|
+
return <ThreeDView />
|
|
143
|
+
case "pcb":
|
|
144
|
+
return <PCBView />
|
|
145
|
+
case "schematic":
|
|
146
|
+
return <SchematicView />
|
|
147
|
+
case "bom":
|
|
148
|
+
return <BOMView />
|
|
149
|
+
default:
|
|
150
|
+
return (
|
|
151
|
+
<FilesView
|
|
152
|
+
directories={directories as any}
|
|
153
|
+
files={files as any}
|
|
154
|
+
isLoading={!packageFiles}
|
|
155
|
+
onFileClicked={onFileClicked}
|
|
156
|
+
onDirectoryClicked={onDirectoryClicked}
|
|
157
|
+
/>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div className="min-h-screen bg-white dark:bg-[#0d1117] text-gray-900 dark:text-[#c9d1d9] font-sans">
|
|
164
|
+
<Header />
|
|
165
|
+
<PackageHeader packageInfo={packageInfo} />
|
|
166
|
+
|
|
167
|
+
{/* Mobile Sidebar */}
|
|
168
|
+
<div className="max-w-[1200px] mx-auto">
|
|
169
|
+
<MobileSidebar packageInfo={packageInfo} isLoading={!packageInfo} />
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
{/* Main Content */}
|
|
173
|
+
<div className="max-w-[1200px] mx-auto">
|
|
174
|
+
<div className="flex flex-col md:flex-row">
|
|
175
|
+
{/* Main Content Area */}
|
|
176
|
+
<div className="w-full md:flex-1 border-r border-gray-200 dark:border-[#30363d] p-4 md:max-w-[calc(100%-296px)] max-w-full">
|
|
177
|
+
{/* Main Content Header with Tabs */}
|
|
178
|
+
<MainContentHeader
|
|
179
|
+
activeView={activeView}
|
|
180
|
+
onViewChange={setActiveView}
|
|
181
|
+
onExportClicked={onExportClicked}
|
|
182
|
+
packageInfo={packageInfo}
|
|
183
|
+
/>
|
|
184
|
+
|
|
185
|
+
{/* Dynamic Content based on active view */}
|
|
186
|
+
{renderContent()}
|
|
187
|
+
|
|
188
|
+
{/* Important Files View - Always shown */}
|
|
189
|
+
<ImportantFilesView
|
|
190
|
+
importantFiles={importantFiles}
|
|
191
|
+
isLoading={!packageFiles}
|
|
192
|
+
onEditClicked={onEditClicked}
|
|
193
|
+
aiDescription={packageInfo?.ai_description}
|
|
194
|
+
aiUsageInstructions={packageInfo?.ai_usage_instructions}
|
|
195
|
+
/>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
{/* Sidebar - Hidden on mobile, shown on md and up */}
|
|
199
|
+
<div className="hidden md:block md:w-[296px] flex-shrink-0">
|
|
200
|
+
<Sidebar
|
|
201
|
+
packageInfo={packageInfo}
|
|
202
|
+
isLoading={!packageInfo}
|
|
203
|
+
onViewChange={(view) => {
|
|
204
|
+
setActiveView(view)
|
|
205
|
+
}}
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
<Footer />
|
|
211
|
+
</div>
|
|
212
|
+
)
|
|
213
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface RepoTabHeaderProps {
|
|
2
|
+
activeTab: string
|
|
3
|
+
setActiveTab: (tab: string) => void
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export default function RepoTabHeader({
|
|
7
|
+
activeTab,
|
|
8
|
+
setActiveTab,
|
|
9
|
+
}: RepoTabHeaderProps) {
|
|
10
|
+
// This component is being handled by a different team
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Badge } from "@/components/ui/badge"
|
|
2
|
+
import { GitFork, Star } from "lucide-react"
|
|
3
|
+
import { Skeleton } from "@/components/ui/skeleton"
|
|
4
|
+
|
|
5
|
+
interface PackageInfo {
|
|
6
|
+
name: string
|
|
7
|
+
unscoped_name: string
|
|
8
|
+
owner_github_username: string
|
|
9
|
+
star_count: string
|
|
10
|
+
description: string
|
|
11
|
+
ai_description: string
|
|
12
|
+
creator_account_id?: string
|
|
13
|
+
owner_org_id?: string
|
|
14
|
+
is_package?: boolean
|
|
15
|
+
website?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface SidebarAboutSectionProps {
|
|
19
|
+
packageInfo?: PackageInfo
|
|
20
|
+
isLoading?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const LinkIcon = () => (
|
|
24
|
+
<svg className="h-4 w-4 mr-1" viewBox="0 0 16 16" fill="currentColor">
|
|
25
|
+
<path d="M7.775 3.275a.75.75 0 0 0 1.06 1.06l1.25-1.25a2 2 0 1 1 2.83 2.83l-2.5 2.5a2 2 0 0 1-2.83 0 .75.75 0 0 0-1.06 1.06 3.5 3.5 0 0 0 4.95 0l2.5-2.5a3.5 3.5 0 0 0-4.95-4.95l-1.25 1.25Zm-4.69 9.64a2 2 0 0 1 0-2.83l2.5-2.5a2 2 0 0 1 2.83 0 .75.75 0 0 0 1.06-1.06 3.5 3.5 0 0 0-4.95 0l-2.5 2.5a3.5 3.5 0 0 0 4.95 4.95l1.25-1.25a.75.75 0 0 0-1.06-1.06l-1.25 1.25a2 2 0 0 1-2.83 0Z"></path>
|
|
26
|
+
</svg>
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
export default function SidebarAboutSection({
|
|
30
|
+
packageInfo,
|
|
31
|
+
isLoading = false,
|
|
32
|
+
}: SidebarAboutSectionProps) {
|
|
33
|
+
const topics = packageInfo?.is_package ? ["Package"] : ["Board"]
|
|
34
|
+
|
|
35
|
+
if (isLoading) {
|
|
36
|
+
return (
|
|
37
|
+
<div className="mb-6">
|
|
38
|
+
<h2 className="text-lg font-semibold mb-2">About</h2>
|
|
39
|
+
<Skeleton className="h-4 w-full mb-3" />
|
|
40
|
+
<Skeleton className="h-4 w-3/4 mb-4" />
|
|
41
|
+
<div className="flex flex-wrap gap-2 mb-4">
|
|
42
|
+
{[1, 2, 3].map((i) => (
|
|
43
|
+
<Skeleton key={i} className="h-6 w-20 rounded-full" />
|
|
44
|
+
))}
|
|
45
|
+
</div>
|
|
46
|
+
<div className="space-y-2">
|
|
47
|
+
<Skeleton className="h-4 w-24" />
|
|
48
|
+
<Skeleton className="h-4 w-20" />
|
|
49
|
+
<Skeleton className="h-4 w-20" />
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="mb-6">
|
|
57
|
+
<h2 className="text-lg font-semibold mb-2">About</h2>
|
|
58
|
+
<p className="text-sm mb-3">
|
|
59
|
+
{packageInfo?.description || packageInfo?.ai_description}
|
|
60
|
+
</p>
|
|
61
|
+
{packageInfo?.website && (
|
|
62
|
+
<a
|
|
63
|
+
href="#"
|
|
64
|
+
className="text-blue-600 dark:text-[#58a6ff] hover:underline text-sm flex items-center mb-4"
|
|
65
|
+
>
|
|
66
|
+
<LinkIcon />
|
|
67
|
+
{packageInfo?.website}
|
|
68
|
+
</a>
|
|
69
|
+
)}
|
|
70
|
+
<div className="flex flex-wrap gap-2 mb-4">
|
|
71
|
+
{topics.map((topic, index) => (
|
|
72
|
+
<Badge
|
|
73
|
+
key={index}
|
|
74
|
+
variant="outline"
|
|
75
|
+
className="text-xs rounded-full px-2 py-0.5 bg-blue-100 dark:bg-[#1f6feb33] text-blue-600 dark:text-[#58a6ff] border-blue-300 dark:border-[#1f6feb66]"
|
|
76
|
+
>
|
|
77
|
+
{topic}
|
|
78
|
+
</Badge>
|
|
79
|
+
))}
|
|
80
|
+
</div>
|
|
81
|
+
<div className="space-y-2 text-sm">
|
|
82
|
+
<div className="flex items-center">
|
|
83
|
+
<svg
|
|
84
|
+
className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]"
|
|
85
|
+
viewBox="0 0 16 16"
|
|
86
|
+
fill="currentColor"
|
|
87
|
+
>
|
|
88
|
+
<path d="M8.75.75V2h.985c.304 0 .603.08.867.231l1.29.736c.038.022.08.033.124.033h2.234a.75.75 0 0 1 0 1.5h-.427l2.111 4.692a.75.75 0 0 1-.154.838l-.53-.53.53.53-.001.002-.002.002-.006.006-.006.005-.01.01-.045.04c-.21.176-.441.327-.686.45C14.556 10.78 13.88 11 13 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L12.178 4.5h-.162c-.305 0-.604-.079-.868-.231l-1.29-.736a.245.245 0 0 0-.124-.033H8.75V13h2.5a.75.75 0 0 1 0 1.5h-6.5a.75.75 0 0 1 0-1.5h2.5V3.5h-.984a.245.245 0 0 0-.124.033l-1.29.736c-.264.152-.563.231-.868.231h-.162l2.112 4.692a.75.75 0 0 1-.154.838l-.53-.53.53.53-.001.002-.002.002-.006.006-.016.015-.045.04c-.21.176-.441.327-.686.45C4.556 10.78 3.88 11 3 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L2.178 4.5H1.75a.75.75 0 0 1 0-1.5h2.234a.249.249 0 0 0 .125-.033l1.29-.736c.263-.15.561-.231.865-.231H7.25V.75a.75.75 0 0 1 1.5 0Zm2.945 8.477c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L13 6.327Zm-10 0c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L3 6.327Z"></path>
|
|
89
|
+
</svg>
|
|
90
|
+
<span>MIT license</span>
|
|
91
|
+
</div>
|
|
92
|
+
<div className="flex items-center">
|
|
93
|
+
<Star className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
|
|
94
|
+
<span>{packageInfo?.star_count || "16"} stars</span>
|
|
95
|
+
</div>
|
|
96
|
+
<div className="flex items-center">
|
|
97
|
+
<GitFork className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
|
|
98
|
+
<span>39 forks</span>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Badge } from "@/components/ui/badge"
|
|
2
|
+
|
|
3
|
+
export default function SidebarContributorsSection() {
|
|
4
|
+
return (
|
|
5
|
+
<div>
|
|
6
|
+
<div className="flex items-center justify-between mb-2">
|
|
7
|
+
<h2 className="text-lg font-semibold">Contributors</h2>
|
|
8
|
+
<Badge
|
|
9
|
+
variant="outline"
|
|
10
|
+
className="text-xs rounded-full px-2 py-0.5 bg-transparent border-gray-300 dark:border-[#30363d]"
|
|
11
|
+
>
|
|
12
|
+
18
|
|
13
|
+
</Badge>
|
|
14
|
+
</div>
|
|
15
|
+
<div className="flex flex-wrap gap-1 mb-2">
|
|
16
|
+
{[...Array(10)].map((_, i) => (
|
|
17
|
+
<div
|
|
18
|
+
key={i}
|
|
19
|
+
className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-500 to-purple-500"
|
|
20
|
+
></div>
|
|
21
|
+
))}
|
|
22
|
+
</div>
|
|
23
|
+
<a
|
|
24
|
+
href="#"
|
|
25
|
+
className="text-blue-600 dark:text-[#58a6ff] hover:underline text-sm"
|
|
26
|
+
>
|
|
27
|
+
+ 4 contributors
|
|
28
|
+
</a>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default function SidebarPackagesSection() {
|
|
2
|
+
return (
|
|
3
|
+
<div className="mb-6">
|
|
4
|
+
<h2 className="text-lg font-semibold mb-2">Packages</h2>
|
|
5
|
+
<p className="text-sm text-gray-500 dark:text-[#8b949e] mb-1">
|
|
6
|
+
No packages published
|
|
7
|
+
</p>
|
|
8
|
+
<a
|
|
9
|
+
href="#"
|
|
10
|
+
className="text-blue-600 dark:text-[#58a6ff] hover:underline text-sm"
|
|
11
|
+
>
|
|
12
|
+
Publish your first package
|
|
13
|
+
</a>
|
|
14
|
+
</div>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -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
|
+
}
|