@tscircuit/fake-snippets 0.0.101 → 0.0.103
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/api/generated-index.js +23 -1
- package/bun.lock +2 -2
- package/dist/bundle.js +530 -367
- package/dist/index.d.ts +29 -2
- package/dist/index.js +18 -1
- package/dist/schema.d.ts +94 -1
- package/dist/schema.js +17 -1
- package/fake-snippets-api/lib/db/db-client.ts +6 -1
- package/fake-snippets-api/lib/db/schema.ts +15 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package.ts +2 -0
- package/fake-snippets-api/routes/api/github/installations/create_new_installation_redirect.ts +75 -0
- package/fake-snippets-api/routes/api/github/repos/list_available.ts +91 -0
- package/fake-snippets-api/routes/api/packages/update.ts +6 -0
- package/package.json +2 -2
- package/src/App.tsx +10 -1
- package/src/components/CreateReleaseDialog.tsx +124 -0
- package/src/components/FileSidebar.tsx +128 -23
- package/src/components/PackageBuildsPage/package-build-header.tsx +9 -1
- package/src/components/PageSearchComponent.tsx +2 -2
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/SuspenseRunFrame.tsx +2 -2
- package/src/components/TrendingPackagesCarousel.tsx +2 -2
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -0
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +13 -1
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +17 -0
- package/src/components/dialogs/GitHubRepositorySelector.tsx +183 -0
- package/src/components/dialogs/create-use-dialog.tsx +8 -2
- package/src/components/dialogs/edit-package-details-dialog.tsx +32 -3
- package/src/components/dialogs/view-ts-files-dialog.tsx +178 -33
- package/src/components/package-port/CodeAndPreview.tsx +4 -1
- package/src/components/package-port/CodeEditor.tsx +42 -35
- package/src/components/package-port/CodeEditorHeader.tsx +6 -4
- package/src/components/package-port/EditorNav.tsx +94 -37
- package/src/components/preview/BuildsList.tsx +241 -0
- package/src/components/preview/ConnectedPackagesList.tsx +187 -0
- package/src/components/preview/ConnectedRepoDashboard.tsx +243 -0
- package/src/components/preview/ConnectedRepoOverview.tsx +454 -0
- package/src/components/preview/index.tsx +248 -0
- package/src/components/ui/tree-view.tsx +23 -6
- package/src/hooks/use-axios.ts +2 -2
- package/src/hooks/use-create-release-dialog.ts +160 -0
- package/src/hooks/use-package-details-form.ts +7 -0
- package/src/hooks/use-packages-base-api-url.ts +1 -1
- package/src/hooks/use-sign-in.ts +2 -2
- package/src/hooks/useFileManagement.ts +22 -2
- package/src/index.css +4 -0
- package/src/lib/utils/formatTimeAgo.ts +10 -0
- package/src/lib/utils/isValidFileName.ts +15 -3
- package/src/pages/dashboard.tsx +2 -2
- package/src/pages/dev-login.tsx +2 -2
- package/src/pages/latest.tsx +2 -2
- package/src/pages/preview-build.tsx +380 -0
- package/src/pages/search.tsx +2 -2
- package/src/pages/trending.tsx +2 -2
- package/src/pages/user-profile.tsx +40 -24
- package/src/pages/view-connected-repo.tsx +18 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { useRef } from "react"
|
|
2
|
+
import {
|
|
3
|
+
Select,
|
|
4
|
+
SelectContent,
|
|
5
|
+
SelectItem,
|
|
6
|
+
SelectTrigger,
|
|
7
|
+
SelectValue,
|
|
8
|
+
} from "@/components/ui/select"
|
|
9
|
+
import { useAxios } from "@/hooks/use-axios"
|
|
10
|
+
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
11
|
+
import { useQuery } from "react-query"
|
|
12
|
+
import { Button } from "../ui/button"
|
|
13
|
+
import { Label } from "../ui/label"
|
|
14
|
+
import { Minus, Plus } from "lucide-react"
|
|
15
|
+
import { Switch } from "../ui/switch"
|
|
16
|
+
|
|
17
|
+
interface GitHubRepositorySelectorProps {
|
|
18
|
+
selectedRepository?: string
|
|
19
|
+
setSelectedRepository?: (value: string | null) => void
|
|
20
|
+
disabled?: boolean
|
|
21
|
+
open?: boolean
|
|
22
|
+
addFormContent?: (data: {
|
|
23
|
+
enablePrPreview?: boolean
|
|
24
|
+
privateBuild?: boolean
|
|
25
|
+
}) => void
|
|
26
|
+
formData?: any
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const GitHubRepositorySelector = ({
|
|
30
|
+
selectedRepository,
|
|
31
|
+
setSelectedRepository,
|
|
32
|
+
disabled = false,
|
|
33
|
+
open = false,
|
|
34
|
+
addFormContent,
|
|
35
|
+
formData,
|
|
36
|
+
}: GitHubRepositorySelectorProps) => {
|
|
37
|
+
const axios = useAxios()
|
|
38
|
+
const apiBaseUrl = useApiBaseUrl()
|
|
39
|
+
const initialValue = useRef(selectedRepository).current
|
|
40
|
+
// Fetch available repositories
|
|
41
|
+
const { data: repositoriesData, error: repositoriesError } = useQuery(
|
|
42
|
+
["github-repositories"],
|
|
43
|
+
async () => {
|
|
44
|
+
const response = await axios.get("/github/repos/list_available")
|
|
45
|
+
return response.data
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
enabled: open, // Only fetch when needed
|
|
49
|
+
retry: false,
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const handleConnectMoreRepos = async () => {
|
|
54
|
+
window.location.href = `${apiBaseUrl}/github/installations/create_new_installation_redirect?return_to_page=${window.location.pathname}`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const handleValueChange = (newValue: string) => {
|
|
58
|
+
if (newValue === "connect-more") {
|
|
59
|
+
handleConnectMoreRepos()
|
|
60
|
+
} else if (newValue === "unlink//repo") {
|
|
61
|
+
setSelectedRepository?.("unlink//repo")
|
|
62
|
+
} else {
|
|
63
|
+
setSelectedRepository?.(newValue)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<>
|
|
69
|
+
<div className="space-y-1 mb-3">
|
|
70
|
+
<Label htmlFor="repository">GitHub Repository</Label>
|
|
71
|
+
{(repositoriesError as any)?.response?.status === 400 &&
|
|
72
|
+
(repositoriesError as any)?.response?.data?.error_code ===
|
|
73
|
+
"github_not_connected" ? (
|
|
74
|
+
<div className="space-y-2">
|
|
75
|
+
<div className="text-sm text-muted-foreground">
|
|
76
|
+
Connect your GitHub account to link this package to a repository.
|
|
77
|
+
</div>
|
|
78
|
+
<Button
|
|
79
|
+
type="button"
|
|
80
|
+
variant="outline"
|
|
81
|
+
onClick={handleConnectMoreRepos}
|
|
82
|
+
className="w-full"
|
|
83
|
+
disabled={disabled}
|
|
84
|
+
>
|
|
85
|
+
<svg
|
|
86
|
+
className="w-4 h-4 mr-2"
|
|
87
|
+
viewBox="0 0 24 24"
|
|
88
|
+
fill="currentColor"
|
|
89
|
+
>
|
|
90
|
+
<path d="M12 0C5.374 0 0 5.373 0 12 0 17.302 3.438 21.8 8.207 23.387c.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z" />
|
|
91
|
+
</svg>
|
|
92
|
+
Connect GitHub Account
|
|
93
|
+
</Button>
|
|
94
|
+
</div>
|
|
95
|
+
) : (
|
|
96
|
+
<div className="space-y-2">
|
|
97
|
+
<Select
|
|
98
|
+
value={selectedRepository}
|
|
99
|
+
onValueChange={handleValueChange}
|
|
100
|
+
disabled={disabled}
|
|
101
|
+
>
|
|
102
|
+
<SelectTrigger className="w-full">
|
|
103
|
+
<SelectValue placeholder="Select a repository" />
|
|
104
|
+
</SelectTrigger>
|
|
105
|
+
<SelectContent className="!z-[999]">
|
|
106
|
+
{repositoriesData?.repos?.map((repo: any) => (
|
|
107
|
+
<SelectItem key={repo.full_name} value={repo.full_name}>
|
|
108
|
+
<div className="flex items-center space-x-2">
|
|
109
|
+
<span>{repo.unscoped_name}</span>
|
|
110
|
+
{repo.private && (
|
|
111
|
+
<span className="text-xs text-muted-foreground">
|
|
112
|
+
(private)
|
|
113
|
+
</span>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
</SelectItem>
|
|
117
|
+
))}
|
|
118
|
+
<SelectItem value="connect-more">
|
|
119
|
+
<div className="flex items-center space-x-2 text-blue-600">
|
|
120
|
+
<Plus className="w-3 h-3" />
|
|
121
|
+
<span>Connect More Repos</span>
|
|
122
|
+
</div>
|
|
123
|
+
</SelectItem>
|
|
124
|
+
{Boolean(initialValue) && (
|
|
125
|
+
<SelectItem value="unlink//repo">
|
|
126
|
+
<div className="flex items-center space-x-2 text-red-600">
|
|
127
|
+
<Minus className="w-3 h-3" />
|
|
128
|
+
<span>Unlink Repo</span>
|
|
129
|
+
</div>
|
|
130
|
+
</SelectItem>
|
|
131
|
+
)}
|
|
132
|
+
</SelectContent>
|
|
133
|
+
</Select>
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
{initialValue && selectedRepository !== "unlink//repo" && (
|
|
139
|
+
<div className="space-y-4 mt-4 p-4 border rounded-lg bg-gray-50">
|
|
140
|
+
<h4 className="text-sm font-medium text-gray-900">
|
|
141
|
+
Repository Settings
|
|
142
|
+
</h4>
|
|
143
|
+
|
|
144
|
+
<div className="flex items-center justify-between">
|
|
145
|
+
<div className="space-y-0.5">
|
|
146
|
+
<Label className="text-sm font-medium">Private Build</Label>
|
|
147
|
+
<p className="text-xs text-gray-500">
|
|
148
|
+
Keep build previews private
|
|
149
|
+
</p>
|
|
150
|
+
</div>
|
|
151
|
+
<Switch
|
|
152
|
+
checked={formData?.privateBuild}
|
|
153
|
+
onCheckedChange={(checked) =>
|
|
154
|
+
addFormContent?.({
|
|
155
|
+
privateBuild: checked,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
disabled={disabled}
|
|
159
|
+
/>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div className="flex items-center justify-between">
|
|
163
|
+
<div className="space-y-0.5">
|
|
164
|
+
<Label className="text-sm font-medium">Enable PR Preview</Label>
|
|
165
|
+
<p className="text-xs text-gray-500">
|
|
166
|
+
Generate preview builds for pull requests
|
|
167
|
+
</p>
|
|
168
|
+
</div>
|
|
169
|
+
<Switch
|
|
170
|
+
checked={formData?.enablePrPreview}
|
|
171
|
+
onCheckedChange={(checked) =>
|
|
172
|
+
addFormContent?.({
|
|
173
|
+
enablePrPreview: checked,
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
disabled={disabled}
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
</>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
@@ -5,14 +5,19 @@ export const createUseDialog = <DialogType extends React.ComponentType<any>>(
|
|
|
5
5
|
) => {
|
|
6
6
|
return () => {
|
|
7
7
|
const [open, setOpen] = useState(false)
|
|
8
|
+
const [dialogProps, setDialogProps] = useState<any>({})
|
|
8
9
|
|
|
9
10
|
return useMemo(
|
|
10
11
|
() => ({
|
|
11
|
-
openDialog: () => {
|
|
12
|
+
openDialog: (props?: any) => {
|
|
13
|
+
if (props) {
|
|
14
|
+
setDialogProps(props)
|
|
15
|
+
}
|
|
12
16
|
setOpen(true)
|
|
13
17
|
},
|
|
14
18
|
closeDialog: () => {
|
|
15
19
|
setOpen(false)
|
|
20
|
+
setDialogProps({})
|
|
16
21
|
},
|
|
17
22
|
Dialog: (
|
|
18
23
|
props: Omit<
|
|
@@ -22,13 +27,14 @@ export const createUseDialog = <DialogType extends React.ComponentType<any>>(
|
|
|
22
27
|
) => (
|
|
23
28
|
<DialogComponent
|
|
24
29
|
{...(props as any)}
|
|
30
|
+
{...dialogProps}
|
|
25
31
|
open={open}
|
|
26
32
|
onOpenChange={setOpen}
|
|
27
33
|
/>
|
|
28
34
|
),
|
|
29
35
|
open,
|
|
30
36
|
}),
|
|
31
|
-
[open],
|
|
37
|
+
[open, dialogProps],
|
|
32
38
|
)
|
|
33
39
|
}
|
|
34
40
|
}
|
|
@@ -27,6 +27,7 @@ import { createUseDialog } from "./create-use-dialog"
|
|
|
27
27
|
import { ChevronDown } from "lucide-react"
|
|
28
28
|
import { useLocation } from "wouter"
|
|
29
29
|
import { useDeletePackage } from "@/hooks/use-delete-package"
|
|
30
|
+
import { GitHubRepositorySelector } from "./GitHubRepositorySelector"
|
|
30
31
|
|
|
31
32
|
interface EditPackageDetailsDialogProps {
|
|
32
33
|
open: boolean
|
|
@@ -36,6 +37,7 @@ interface EditPackageDetailsDialogProps {
|
|
|
36
37
|
currentWebsite: string
|
|
37
38
|
currentLicense?: string | null
|
|
38
39
|
currentDefaultView?: string
|
|
40
|
+
currentGithubRepoFullName?: string | null
|
|
39
41
|
isPrivate?: boolean
|
|
40
42
|
packageName: string
|
|
41
43
|
unscopedPackageName: string
|
|
@@ -57,6 +59,7 @@ export const EditPackageDetailsDialog = ({
|
|
|
57
59
|
currentWebsite,
|
|
58
60
|
currentLicense,
|
|
59
61
|
currentDefaultView = "files",
|
|
62
|
+
currentGithubRepoFullName,
|
|
60
63
|
isPrivate = false,
|
|
61
64
|
unscopedPackageName,
|
|
62
65
|
packageReleaseId,
|
|
@@ -75,6 +78,7 @@ export const EditPackageDetailsDialog = ({
|
|
|
75
78
|
isFormValid,
|
|
76
79
|
} = usePackageDetailsForm({
|
|
77
80
|
initialDescription: currentDescription,
|
|
81
|
+
initialGithubRepoFullName: currentGithubRepoFullName ?? null,
|
|
78
82
|
initialWebsite: currentWebsite,
|
|
79
83
|
initialLicense: currentLicense || null,
|
|
80
84
|
initialDefaultView: currentDefaultView,
|
|
@@ -83,7 +87,6 @@ export const EditPackageDetailsDialog = ({
|
|
|
83
87
|
initialVisibility: isPrivate ? "private" : "public",
|
|
84
88
|
})
|
|
85
89
|
|
|
86
|
-
const [deleting, setDeleting] = useState(false)
|
|
87
90
|
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
|
88
91
|
const [dangerOpen, setDangerOpen] = useState(false)
|
|
89
92
|
const [, setLocation] = useLocation()
|
|
@@ -114,6 +117,10 @@ export const EditPackageDetailsDialog = ({
|
|
|
114
117
|
website: formData.website.trim(),
|
|
115
118
|
is_private: formData.visibility == "private",
|
|
116
119
|
default_view: formData.defaultView,
|
|
120
|
+
github_repo_full_name:
|
|
121
|
+
formData.githubRepoFullName === "unlink//repo"
|
|
122
|
+
? null
|
|
123
|
+
: formData.githubRepoFullName,
|
|
117
124
|
...(formData.unscopedPackageName !== unscopedPackageName && {
|
|
118
125
|
name: formData.unscopedPackageName.trim(),
|
|
119
126
|
}),
|
|
@@ -371,6 +378,26 @@ export const EditPackageDetailsDialog = ({
|
|
|
371
378
|
</SelectContent>
|
|
372
379
|
</Select>
|
|
373
380
|
</div>
|
|
381
|
+
<div className="space-y-1">
|
|
382
|
+
<GitHubRepositorySelector
|
|
383
|
+
selectedRepository={formData.githubRepoFullName || ""}
|
|
384
|
+
setSelectedRepository={(value) =>
|
|
385
|
+
setFormData((prev) => ({
|
|
386
|
+
...prev,
|
|
387
|
+
githubRepoFullName: value,
|
|
388
|
+
}))
|
|
389
|
+
}
|
|
390
|
+
disabled={updatePackageDetailsMutation.isLoading}
|
|
391
|
+
open={open}
|
|
392
|
+
formData={formData}
|
|
393
|
+
addFormContent={(content) => {
|
|
394
|
+
setFormData((prev) => ({
|
|
395
|
+
...prev,
|
|
396
|
+
...content,
|
|
397
|
+
}))
|
|
398
|
+
}}
|
|
399
|
+
/>
|
|
400
|
+
</div>
|
|
374
401
|
</div>
|
|
375
402
|
|
|
376
403
|
<details
|
|
@@ -394,10 +421,12 @@ export const EditPackageDetailsDialog = ({
|
|
|
394
421
|
variant="destructive"
|
|
395
422
|
size="default"
|
|
396
423
|
onClick={() => setShowConfirmDelete(true)}
|
|
397
|
-
disabled={
|
|
424
|
+
disabled={deletePackageMutation.isLoading}
|
|
398
425
|
className="shrink-0 lg:w-[115px] w-[70px]"
|
|
399
426
|
>
|
|
400
|
-
{
|
|
427
|
+
{deletePackageMutation.isLoading
|
|
428
|
+
? "Deleting..."
|
|
429
|
+
: "Delete"}
|
|
401
430
|
</Button>
|
|
402
431
|
</div>
|
|
403
432
|
</div>
|
|
@@ -21,13 +21,23 @@ import JSZip from "jszip"
|
|
|
21
21
|
import { saveAs } from "file-saver"
|
|
22
22
|
import { EditorView } from "codemirror"
|
|
23
23
|
import { EditorState } from "@codemirror/state"
|
|
24
|
+
import { autocompletion } from "@codemirror/autocomplete"
|
|
24
25
|
import { basicSetup } from "@/lib/codemirror/basic-setup"
|
|
25
26
|
import { javascript } from "@codemirror/lang-javascript"
|
|
26
27
|
import { json } from "@codemirror/lang-json"
|
|
28
|
+
import { tsAutocomplete, tsFacet, tsSync } from "@valtown/codemirror-ts"
|
|
29
|
+
import {
|
|
30
|
+
createSystem,
|
|
31
|
+
createVirtualTypeScriptEnvironment,
|
|
32
|
+
} from "@typescript/vfs"
|
|
33
|
+
import { loadDefaultLibMap } from "@/lib/ts-lib-cache"
|
|
34
|
+
import tsModule from "typescript"
|
|
27
35
|
|
|
28
36
|
interface ViewTsFilesDialogProps {
|
|
29
37
|
open: boolean
|
|
30
38
|
onOpenChange: (open: boolean) => void
|
|
39
|
+
initialFile?: string
|
|
40
|
+
initialLine?: number
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
interface FileNode {
|
|
@@ -41,6 +51,8 @@ interface FileNode {
|
|
|
41
51
|
export const ViewTsFilesDialog: React.FC<ViewTsFilesDialogProps> = ({
|
|
42
52
|
open,
|
|
43
53
|
onOpenChange,
|
|
54
|
+
initialFile,
|
|
55
|
+
initialLine,
|
|
44
56
|
}) => {
|
|
45
57
|
const [files, setFiles] = useState<Map<string, string>>(new Map())
|
|
46
58
|
const [selectedFile, setSelectedFile] = useState<string | null>(null)
|
|
@@ -48,8 +60,12 @@ export const ViewTsFilesDialog: React.FC<ViewTsFilesDialogProps> = ({
|
|
|
48
60
|
const [copiedFile, setCopiedFile] = useState<string | null>(null)
|
|
49
61
|
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set())
|
|
50
62
|
const [sidebarOpen, setSidebarOpen] = useState(true)
|
|
63
|
+
const [targetLine, setTargetLine] = useState<number | null>(null)
|
|
51
64
|
const editorRef = useRef<HTMLDivElement>(null)
|
|
52
65
|
const viewRef = useRef<EditorView | null>(null)
|
|
66
|
+
const [tsEnv, setTsEnv] = useState<ReturnType<
|
|
67
|
+
typeof createVirtualTypeScriptEnvironment
|
|
68
|
+
> | null>(null)
|
|
53
69
|
|
|
54
70
|
const fileTree = useMemo(() => {
|
|
55
71
|
const tree: FileNode[] = []
|
|
@@ -159,33 +175,86 @@ export const ViewTsFilesDialog: React.FC<ViewTsFilesDialogProps> = ({
|
|
|
159
175
|
viewRef.current.destroy()
|
|
160
176
|
}
|
|
161
177
|
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
"
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
178
|
+
const extensions = [
|
|
179
|
+
basicSetup,
|
|
180
|
+
isJson ? json() : javascript({ typescript: true, jsx: true }),
|
|
181
|
+
EditorState.readOnly.of(true),
|
|
182
|
+
EditorView.theme({
|
|
183
|
+
"&": {
|
|
184
|
+
height: "100%",
|
|
185
|
+
fontSize: "14px",
|
|
186
|
+
},
|
|
187
|
+
".cm-content": {
|
|
188
|
+
padding: "16px",
|
|
189
|
+
minHeight: "100%",
|
|
190
|
+
},
|
|
191
|
+
".cm-focused": {
|
|
192
|
+
outline: "none",
|
|
193
|
+
},
|
|
194
|
+
".cm-editor": {
|
|
195
|
+
height: "100%",
|
|
196
|
+
},
|
|
197
|
+
".cm-scroller": {
|
|
198
|
+
fontFamily:
|
|
199
|
+
"ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace",
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
if (
|
|
205
|
+
tsEnv &&
|
|
206
|
+
!isJson &&
|
|
207
|
+
(selectedFile.endsWith(".ts") || selectedFile.endsWith(".tsx"))
|
|
208
|
+
) {
|
|
209
|
+
extensions.push(
|
|
210
|
+
tsFacet.of({
|
|
211
|
+
env: tsEnv,
|
|
212
|
+
path: selectedFile.startsWith("/")
|
|
213
|
+
? selectedFile.slice(1)
|
|
214
|
+
: selectedFile,
|
|
215
|
+
}),
|
|
216
|
+
tsSync(),
|
|
217
|
+
autocompletion({ override: [tsAutocomplete()] }),
|
|
218
|
+
EditorView.domEventHandlers({
|
|
219
|
+
click: (event, view) => {
|
|
220
|
+
if (event.ctrlKey || event.metaKey) {
|
|
221
|
+
const pos = view.posAtCoords({
|
|
222
|
+
x: event.clientX,
|
|
223
|
+
y: event.clientY,
|
|
224
|
+
})
|
|
225
|
+
if (pos !== null) {
|
|
226
|
+
const path = selectedFile.startsWith("/")
|
|
227
|
+
? selectedFile.slice(1)
|
|
228
|
+
: selectedFile
|
|
229
|
+
const definitions =
|
|
230
|
+
tsEnv.languageService.getDefinitionAtPosition(path, pos)
|
|
231
|
+
if (definitions && definitions.length > 0) {
|
|
232
|
+
const definition = definitions[0]
|
|
233
|
+
const definitionFileName = definition.fileName
|
|
234
|
+
if (definitionFileName && files.has(definitionFileName)) {
|
|
235
|
+
const definitionContent =
|
|
236
|
+
files.get(definitionFileName) || ""
|
|
237
|
+
const lines = definitionContent
|
|
238
|
+
.substring(0, definition.textSpan.start)
|
|
239
|
+
.split("\n")
|
|
240
|
+
const lineNumber = lines.length
|
|
241
|
+
|
|
242
|
+
setSelectedFile(definitionFileName)
|
|
243
|
+
setTargetLine(lineNumber)
|
|
244
|
+
return true
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return false
|
|
186
250
|
},
|
|
187
251
|
}),
|
|
188
|
-
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const state = EditorState.create({
|
|
256
|
+
doc: content,
|
|
257
|
+
extensions,
|
|
189
258
|
})
|
|
190
259
|
|
|
191
260
|
viewRef.current = new EditorView({
|
|
@@ -193,25 +262,85 @@ export const ViewTsFilesDialog: React.FC<ViewTsFilesDialogProps> = ({
|
|
|
193
262
|
parent: editorRef.current,
|
|
194
263
|
})
|
|
195
264
|
|
|
265
|
+
if (targetLine && targetLine > 0) {
|
|
266
|
+
const scrollToLine = () => {
|
|
267
|
+
if (viewRef.current) {
|
|
268
|
+
const doc = viewRef.current.state.doc
|
|
269
|
+
if (targetLine <= doc.lines) {
|
|
270
|
+
const line = doc.line(targetLine)
|
|
271
|
+
|
|
272
|
+
const performScroll = () => {
|
|
273
|
+
if (viewRef.current) {
|
|
274
|
+
viewRef.current.dispatch({
|
|
275
|
+
selection: { anchor: line.from, head: line.to },
|
|
276
|
+
effects: EditorView.scrollIntoView(line.from, {
|
|
277
|
+
y: "center",
|
|
278
|
+
}),
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
setTimeout(() => {
|
|
282
|
+
if (viewRef.current) {
|
|
283
|
+
viewRef.current.dispatch({
|
|
284
|
+
effects: EditorView.scrollIntoView(line.from, {
|
|
285
|
+
y: "center",
|
|
286
|
+
}),
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
setTimeout(() => {
|
|
290
|
+
if (viewRef.current) {
|
|
291
|
+
viewRef.current.dispatch({
|
|
292
|
+
effects: EditorView.scrollIntoView(line.from, {
|
|
293
|
+
y: "center",
|
|
294
|
+
}),
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
}, 200)
|
|
298
|
+
}
|
|
299
|
+
}, 150)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
requestAnimationFrame(performScroll)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Extra delay when TypeScript environment is not ready or for large line numbers
|
|
309
|
+
// This handles cases when triggered from CodeEditor.tsx with TypeScript definitions
|
|
310
|
+
const isLargeLine = targetLine > 100
|
|
311
|
+
const needsExtraDelay = !tsEnv || isLargeLine
|
|
312
|
+
const initialDelay = needsExtraDelay ? 500 : 200
|
|
313
|
+
|
|
314
|
+
setTimeout(scrollToLine, initialDelay)
|
|
315
|
+
setTargetLine(null)
|
|
316
|
+
}
|
|
317
|
+
|
|
196
318
|
return () => {
|
|
197
319
|
if (viewRef.current) {
|
|
198
320
|
viewRef.current.destroy()
|
|
199
321
|
viewRef.current = null
|
|
200
322
|
}
|
|
201
323
|
}
|
|
202
|
-
}, [selectedFile, files])
|
|
324
|
+
}, [selectedFile, files, targetLine, tsEnv])
|
|
203
325
|
|
|
204
326
|
useEffect(() => {
|
|
205
327
|
if (open && window.__DEBUG_CODE_EDITOR_FS_MAP) {
|
|
206
328
|
setFiles(window.__DEBUG_CODE_EDITOR_FS_MAP)
|
|
207
329
|
|
|
208
330
|
if (window.__DEBUG_CODE_EDITOR_FS_MAP.size > 0) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
331
|
+
let fileToSelect: string
|
|
332
|
+
if (initialFile && window.__DEBUG_CODE_EDITOR_FS_MAP.has(initialFile)) {
|
|
333
|
+
fileToSelect = initialFile
|
|
334
|
+
if (initialLine) {
|
|
335
|
+
setTargetLine(initialLine)
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
fileToSelect = Array.from(window.__DEBUG_CODE_EDITOR_FS_MAP.keys())[0]
|
|
339
|
+
}
|
|
213
340
|
|
|
214
|
-
|
|
341
|
+
setSelectedFile(fileToSelect)
|
|
342
|
+
|
|
343
|
+
let normalizedPath = fileToSelect
|
|
215
344
|
if (normalizedPath.startsWith("/")) {
|
|
216
345
|
normalizedPath = normalizedPath.slice(1)
|
|
217
346
|
}
|
|
@@ -225,7 +354,23 @@ export const ViewTsFilesDialog: React.FC<ViewTsFilesDialogProps> = ({
|
|
|
225
354
|
setExpandedFolders(foldersToExpand)
|
|
226
355
|
}
|
|
227
356
|
}
|
|
228
|
-
}, [open])
|
|
357
|
+
}, [open, initialFile, initialLine])
|
|
358
|
+
|
|
359
|
+
useEffect(() => {
|
|
360
|
+
if (files.size > 0) {
|
|
361
|
+
const setupTsEnv = async () => {
|
|
362
|
+
try {
|
|
363
|
+
const libMap = await loadDefaultLibMap()
|
|
364
|
+
const system = createSystem(new Map([...libMap, ...files]))
|
|
365
|
+
const env = createVirtualTypeScriptEnvironment(system, [], tsModule)
|
|
366
|
+
setTsEnv(env)
|
|
367
|
+
} catch (error) {
|
|
368
|
+
console.error("Failed to setup TypeScript environment:", error)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
setupTsEnv()
|
|
372
|
+
}
|
|
373
|
+
}, [files])
|
|
229
374
|
|
|
230
375
|
useEffect(() => {
|
|
231
376
|
const handleResize = () => {
|
|
@@ -367,7 +512,7 @@ export const ViewTsFilesDialog: React.FC<ViewTsFilesDialogProps> = ({
|
|
|
367
512
|
</Badge>
|
|
368
513
|
</div>
|
|
369
514
|
</div>
|
|
370
|
-
<div className="flex items-center gap-2">
|
|
515
|
+
<div className="flex items-center gap-2 mr-4">
|
|
371
516
|
<Button
|
|
372
517
|
variant="outline"
|
|
373
518
|
size="sm"
|
|
@@ -78,6 +78,7 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
|
78
78
|
localFiles,
|
|
79
79
|
initialFiles,
|
|
80
80
|
renameFile,
|
|
81
|
+
packageFilesMeta,
|
|
81
82
|
} = useFileManagement({
|
|
82
83
|
templateCode: templateFromUrl?.code,
|
|
83
84
|
currentPackage: pkg,
|
|
@@ -197,6 +198,8 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
|
197
198
|
setState((prev) => ({ ...prev, showPreview: !prev.showPreview }))
|
|
198
199
|
}
|
|
199
200
|
previewOpen={state.showPreview}
|
|
201
|
+
files={localFiles}
|
|
202
|
+
packageFilesMeta={packageFilesMeta}
|
|
200
203
|
/>
|
|
201
204
|
<div
|
|
202
205
|
className={`flex ${state.showPreview ? "flex-col md:flex-row" : ""}`}
|
|
@@ -231,7 +234,7 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
|
231
234
|
</div>
|
|
232
235
|
<div
|
|
233
236
|
className={cn(
|
|
234
|
-
"flex p-0 flex-col min-h-[640px]",
|
|
237
|
+
"flex p-0 flex-col min-h-[640px] overflow-y-hidden",
|
|
235
238
|
state.fullScreen
|
|
236
239
|
? "fixed inset-0 z-50 bg-white p-4 overflow-hidden"
|
|
237
240
|
: "w-full md:w-1/2",
|