@tanstack/cta-ui 0.10.0-alpha.19 → 0.10.0-alpha.20
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/lib/index.ts +16 -7
- package/lib-dist/index.d.ts +6 -1
- package/lib-dist/index.js +6 -3
- package/package.json +20 -7
- package/public/tailwind.svg +1 -0
- package/public/tanstack.png +0 -0
- package/public/typescript.svg +1 -0
- package/src/components/StatusList.tsx +22 -0
- package/src/components/add-on-info-dialog.tsx +39 -0
- package/src/components/cta-sidebar.tsx +55 -0
- package/src/components/custom-add-on-dialog.tsx +79 -0
- package/src/components/file-navigator.tsx +205 -0
- package/src/components/file-tree.tsx +18 -60
- package/src/components/file-viewer.tsx +11 -3
- package/src/components/sidebar-items/add-ons.tsx +91 -0
- package/src/components/sidebar-items/mode-selector.tsx +55 -0
- package/src/components/sidebar-items/project-name.tsx +29 -0
- package/src/components/sidebar-items/run-add-ons.tsx +71 -0
- package/src/components/sidebar-items/run-create-app.tsx +82 -0
- package/src/components/sidebar-items/starter.tsx +115 -0
- package/src/components/sidebar-items/typescript-switch.tsx +52 -0
- package/src/components/toaster.tsx +29 -0
- package/src/components/ui/button.tsx +21 -19
- package/src/components/ui/dialog.tsx +25 -20
- package/src/components/ui/dropdown-menu.tsx +255 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +22 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +137 -0
- package/src/components/ui/sidebar.tsx +726 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +23 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/toggle-group.tsx +11 -11
- package/src/components/ui/toggle.tsx +15 -13
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/components/ui/tree-view.tsx +17 -12
- package/src/engine-handling/add-to-app-wrapper.ts +114 -0
- package/src/engine-handling/create-app-wrapper.ts +107 -0
- package/src/engine-handling/file-helpers.ts +25 -0
- package/src/engine-handling/framework-registration.ts +11 -0
- package/src/engine-handling/generate-initial-payload.ts +93 -0
- package/src/engine-handling/server-environment.ts +13 -0
- package/src/file-classes.ts +54 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/hooks/use-streaming-status.ts +70 -0
- package/src/lib/api.ts +90 -0
- package/src/routeTree.gen.ts +4 -27
- package/src/routes/__root.tsx +36 -7
- package/src/routes/api/add-to-app.ts +21 -0
- package/src/routes/api/create-app.ts +21 -0
- package/src/routes/api/dry-run-add-to-app.ts +16 -0
- package/src/routes/api/dry-run-create-app.ts +16 -0
- package/src/routes/api/initial-payload.ts +10 -0
- package/src/routes/api/load-remote-add-on.ts +42 -0
- package/src/routes/api/load-starter.ts +47 -0
- package/src/routes/api/shutdown.ts +11 -0
- package/src/routes/index.tsx +3 -210
- package/src/store/add-ons.ts +81 -0
- package/src/store/project.ts +268 -0
- package/src/styles.css +47 -0
- package/src/types.d.ts +87 -0
- package/tests/store/add-ons.test.ts +222 -0
- package/vitest.config.ts +6 -0
- package/.cursorrules +0 -7
- package/src/components/Header.tsx +0 -13
- package/src/components/applied-add-on.tsx +0 -149
- package/src/lib/server-fns.ts +0 -78
- package/src/routes/api.demo-names.ts +0 -11
- package/src/routes/demo.tanstack-query.tsx +0 -28
package/lib/index.ts
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { dirname, resolve } from 'node:path'
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const projectPath = process.cwd()
|
|
4
|
+
import type { SerializedOptions } from '@tanstack/cta-engine'
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
export function launchUI({
|
|
7
|
+
mode,
|
|
8
|
+
addOns,
|
|
9
|
+
options,
|
|
10
|
+
}: {
|
|
11
|
+
mode: 'add' | 'setup'
|
|
12
|
+
addOns?: Array<string>
|
|
13
|
+
options?: SerializedOptions
|
|
14
|
+
}) {
|
|
15
|
+
const projectPath = process.cwd()
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
process.env.CTA_PROJECT_PATH = projectPath
|
|
18
|
+
process.env.CTA_ADD_ONS = addOns?.join(',') || ''
|
|
19
|
+
process.env.CTA_OPTIONS = options ? JSON.stringify(options) : ''
|
|
20
|
+
process.env.CTA_MODE = mode
|
|
13
21
|
|
|
14
22
|
const developerPath = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
23
|
+
const configPath = resolve(developerPath, './app.config.js')
|
|
15
24
|
|
|
16
25
|
process.chdir(developerPath)
|
|
17
26
|
|
package/lib-dist/index.d.ts
CHANGED
package/lib-dist/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { dirname, resolve } from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
export function launchUI() {
|
|
3
|
+
export function launchUI({ mode, addOns, options, }) {
|
|
4
4
|
const projectPath = process.cwd();
|
|
5
|
-
process.env.
|
|
6
|
-
|
|
5
|
+
process.env.CTA_PROJECT_PATH = projectPath;
|
|
6
|
+
process.env.CTA_ADD_ONS = addOns?.join(',') || '';
|
|
7
|
+
process.env.CTA_OPTIONS = options ? JSON.stringify(options) : '';
|
|
8
|
+
process.env.CTA_MODE = mode;
|
|
7
9
|
const developerPath = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
|
+
const configPath = resolve(developerPath, './app.config.js');
|
|
8
11
|
process.chdir(developerPath);
|
|
9
12
|
import(configPath).then(async (config) => {
|
|
10
13
|
const out = await config.default;
|
package/package.json
CHANGED
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
"@codemirror/lang-json": "^6.0.1",
|
|
11
11
|
"@radix-ui/react-accordion": "^1.2.3",
|
|
12
12
|
"@radix-ui/react-checkbox": "^1.1.4",
|
|
13
|
-
"@radix-ui/react-dialog": "^1.1.
|
|
14
|
-
"@radix-ui/react-
|
|
13
|
+
"@radix-ui/react-dialog": "^1.1.10",
|
|
14
|
+
"@radix-ui/react-dropdown-menu": "^2.1.11",
|
|
15
|
+
"@radix-ui/react-label": "^2.1.4",
|
|
16
|
+
"@radix-ui/react-popover": "^1.1.10",
|
|
17
|
+
"@radix-ui/react-separator": "^1.1.4",
|
|
18
|
+
"@radix-ui/react-slot": "^1.2.0",
|
|
19
|
+
"@radix-ui/react-switch": "^1.2.2",
|
|
15
20
|
"@radix-ui/react-tabs": "^1.1.3",
|
|
16
21
|
"@radix-ui/react-toggle": "^1.1.2",
|
|
17
22
|
"@radix-ui/react-toggle-group": "^1.1.2",
|
|
23
|
+
"@radix-ui/react-tooltip": "^1.2.3",
|
|
18
24
|
"@tailwindcss/vite": "^4.0.6",
|
|
19
25
|
"@tanstack/react-query": "^5.66.5",
|
|
20
26
|
"@tanstack/react-query-devtools": "^5.66.5",
|
|
@@ -22,37 +28,44 @@
|
|
|
22
28
|
"@tanstack/react-router-devtools": "^1.114.3",
|
|
23
29
|
"@tanstack/react-router-with-query": "^1.114.3",
|
|
24
30
|
"@tanstack/react-start": "^1.114.3",
|
|
31
|
+
"@tanstack/react-store": "^0.7.0",
|
|
25
32
|
"@tanstack/router-plugin": "^1.114.3",
|
|
26
|
-
"@uiw/codemirror-theme-
|
|
33
|
+
"@uiw/codemirror-theme-github": "^4.23.10",
|
|
27
34
|
"@uiw/react-codemirror": "^4.23.10",
|
|
28
35
|
"class-variance-authority": "^0.7.1",
|
|
29
36
|
"clsx": "^2.1.1",
|
|
30
37
|
"execa": "^9.5.2",
|
|
38
|
+
"jotai-tanstack-query": "^0.9.0",
|
|
31
39
|
"lucide-react": "^0.476.0",
|
|
40
|
+
"next-themes": "^0.4.6",
|
|
32
41
|
"react": "^19.0.0",
|
|
33
42
|
"react-codemirror-merge": "^4.23.10",
|
|
34
43
|
"react-dom": "^19.0.0",
|
|
44
|
+
"sonner": "^2.0.3",
|
|
35
45
|
"tailwind-merge": "^3.0.2",
|
|
36
46
|
"tailwindcss": "^4.0.6",
|
|
37
47
|
"tailwindcss-animate": "^1.0.7",
|
|
38
48
|
"vinxi": "^0.5.3",
|
|
39
49
|
"vite-tsconfig-paths": "^5.1.4",
|
|
40
|
-
"
|
|
41
|
-
"@tanstack/cta-
|
|
50
|
+
"zustand": "^5.0.3",
|
|
51
|
+
"@tanstack/cta-framework-react-cra": "0.10.0-alpha.20",
|
|
52
|
+
"@tanstack/cta-engine": "0.10.0-alpha.20",
|
|
53
|
+
"@tanstack/cta-framework-solid": "0.10.0-alpha.20"
|
|
42
54
|
},
|
|
43
55
|
"devDependencies": {
|
|
44
56
|
"@testing-library/dom": "^10.4.0",
|
|
45
57
|
"@testing-library/react": "^16.2.0",
|
|
46
|
-
"@types/node": "^22.
|
|
58
|
+
"@types/node": "^22.14.1",
|
|
47
59
|
"@types/react": "^19.0.8",
|
|
48
60
|
"@types/react-dom": "^19.0.3",
|
|
49
61
|
"@vitejs/plugin-react": "^4.3.4",
|
|
62
|
+
"@vitest/coverage-v8": "3.1.1",
|
|
50
63
|
"jsdom": "^26.0.0",
|
|
51
64
|
"typescript": "^5.7.2",
|
|
52
65
|
"vite": "^6.1.0",
|
|
53
66
|
"vitest": "^3.0.5",
|
|
54
67
|
"web-vitals": "^4.2.4"
|
|
55
68
|
},
|
|
56
|
-
"version": "0.10.0-alpha.
|
|
69
|
+
"version": "0.10.0-alpha.20",
|
|
57
70
|
"scripts": {}
|
|
58
71
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 54 33"><g clip-path="url(#a)"><path fill="#38bdf8" fill-rule="evenodd" d="M27 0c-7.2 0-11.7 3.6-13.5 10.8 2.7-3.6 5.85-4.95 9.45-4.05 2.054.513 3.522 2.004 5.147 3.653C30.744 13.09 33.808 16.2 40.5 16.2c7.2 0 11.7-3.6 13.5-10.8-2.7 3.6-5.85 4.95-9.45 4.05-2.054-.513-3.522-2.004-5.147-3.653C36.756 3.11 33.692 0 27 0zM13.5 16.2C6.3 16.2 1.8 19.8 0 27c2.7-3.6 5.85-4.95 9.45-4.05 2.054.514 3.522 2.004 5.147 3.653C17.244 29.29 20.308 32.4 27 32.4c7.2 0 11.7-3.6 13.5-10.8-2.7 3.6-5.85 4.95-9.45 4.05-2.054-.513-3.522-2.004-5.147-3.653C23.256 19.31 20.192 16.2 13.5 16.2z" clip-rule="evenodd"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h54v32.4H0z"/></clipPath></defs></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 0 256 256" width="256" height="256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M20 0h216c11.046 0 20 8.954 20 20v216c0 11.046-8.954 20-20 20H20c-11.046 0-20-8.954-20-20V20C0 8.954 8.954 0 20 0Z" fill="#3178C6"/><path d="M150.518 200.475v27.62c4.492 2.302 9.805 4.028 15.938 5.179 6.133 1.151 12.597 1.726 19.393 1.726 6.622 0 12.914-.633 18.874-1.899 5.96-1.266 11.187-3.352 15.678-6.257 4.492-2.906 8.048-6.704 10.669-11.394 2.62-4.689 3.93-10.486 3.93-17.391 0-5.006-.749-9.394-2.246-13.163a30.748 30.748 0 0 0-6.479-10.055c-2.821-2.935-6.205-5.567-10.149-7.898-3.945-2.33-8.394-4.531-13.347-6.602-3.628-1.497-6.881-2.949-9.761-4.359-2.879-1.41-5.327-2.848-7.342-4.316-2.016-1.467-3.571-3.021-4.665-4.661-1.094-1.64-1.641-3.495-1.641-5.567 0-1.899.489-3.61 1.468-5.135s2.362-2.834 4.147-3.927c1.785-1.094 3.973-1.942 6.565-2.547 2.591-.604 5.471-.906 8.638-.906 2.304 0 4.737.173 7.299.518 2.563.345 5.14.877 7.732 1.597a53.669 53.669 0 0 1 7.558 2.719 41.7 41.7 0 0 1 6.781 3.797v-25.807c-4.204-1.611-8.797-2.805-13.778-3.582-4.981-.777-10.697-1.165-17.147-1.165-6.565 0-12.784.705-18.658 2.115-5.874 1.409-11.043 3.61-15.506 6.602-4.463 2.993-7.99 6.805-10.582 11.437-2.591 4.632-3.887 10.17-3.887 16.615 0 8.228 2.375 15.248 7.127 21.06 4.751 5.811 11.963 10.731 21.638 14.759a291.458 291.458 0 0 1 10.625 4.575c3.283 1.496 6.119 3.049 8.509 4.66 2.39 1.611 4.276 3.366 5.658 5.265 1.382 1.899 2.073 4.057 2.073 6.474a9.901 9.901 0 0 1-1.296 4.963c-.863 1.524-2.174 2.848-3.93 3.97-1.756 1.122-3.945 1.999-6.565 2.632-2.62.633-5.687.95-9.2.95-5.989 0-11.92-1.05-17.794-3.151-5.875-2.1-11.317-5.25-16.327-9.451Zm-46.036-68.733H140V109H41v22.742h35.345V233h28.137V131.742Z" fill="#FFF"/></svg>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { StreamItem } from '@/types'
|
|
2
|
+
|
|
3
|
+
export default function StatusList({
|
|
4
|
+
streamItems,
|
|
5
|
+
finished,
|
|
6
|
+
}: {
|
|
7
|
+
streamItems: Array<StreamItem>
|
|
8
|
+
finished: boolean
|
|
9
|
+
}) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="flex flex-col gap-2">
|
|
12
|
+
{streamItems.map((item, index) => (
|
|
13
|
+
<div key={item.id} className="flex items-center gap-2">
|
|
14
|
+
<item.icon
|
|
15
|
+
className={`w-4 h-4 ${index === streamItems.length - 1 && !finished ? 'text-green-500 animate-spin' : ''}`}
|
|
16
|
+
/>
|
|
17
|
+
{item.message}
|
|
18
|
+
</div>
|
|
19
|
+
))}
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AddOnInfo } from '@/types'
|
|
2
|
+
|
|
3
|
+
import { Dialog, DialogContent } from '@/components/ui/dialog'
|
|
4
|
+
|
|
5
|
+
export default function CustomAddOnDialog({
|
|
6
|
+
addOn,
|
|
7
|
+
onClose,
|
|
8
|
+
}: {
|
|
9
|
+
addOn?: AddOnInfo
|
|
10
|
+
onClose: () => void
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<Dialog modal open={!!addOn} onOpenChange={onClose}>
|
|
14
|
+
<DialogContent className="sm:min-w-[425px] sm:max-w-fit">
|
|
15
|
+
<div className="flex flex-row">
|
|
16
|
+
{addOn?.smallLogo && (
|
|
17
|
+
<img
|
|
18
|
+
src={`data:image/svg+xml,${encodeURIComponent(addOn.smallLogo)}`}
|
|
19
|
+
alt={addOn.name}
|
|
20
|
+
className="w-15"
|
|
21
|
+
/>
|
|
22
|
+
)}
|
|
23
|
+
<div className="flex flex-col ml-4 gap-4">
|
|
24
|
+
<p className="text-lg font-bold">{addOn?.name}</p>
|
|
25
|
+
<p className="text-sm text-gray-500">{addOn?.description}</p>
|
|
26
|
+
<a
|
|
27
|
+
href={addOn?.link}
|
|
28
|
+
target="_blank"
|
|
29
|
+
rel="noopener noreferrer"
|
|
30
|
+
className="text-sm text-blue-500 underline"
|
|
31
|
+
>
|
|
32
|
+
More information on {addOn?.name}
|
|
33
|
+
</a>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</DialogContent>
|
|
37
|
+
</Dialog>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Sidebar,
|
|
3
|
+
SidebarContent,
|
|
4
|
+
SidebarFooter,
|
|
5
|
+
SidebarGroup,
|
|
6
|
+
SidebarHeader,
|
|
7
|
+
} from '@/components/ui/sidebar'
|
|
8
|
+
|
|
9
|
+
import SelectedAddOns from '@/components/sidebar-items/add-ons'
|
|
10
|
+
import RunAddOns from '@/components/sidebar-items/run-add-ons'
|
|
11
|
+
import RunCreateApp from '@/components/sidebar-items/run-create-app'
|
|
12
|
+
import ProjectName from '@/components/sidebar-items/project-name'
|
|
13
|
+
import ModeSelector from '@/components/sidebar-items/mode-selector'
|
|
14
|
+
import TypescriptSwitch from '@/components/sidebar-items/typescript-switch'
|
|
15
|
+
import StarterDialog from '@/components/sidebar-items/starter'
|
|
16
|
+
|
|
17
|
+
import { useApplicationMode, useReady } from '@/store/project'
|
|
18
|
+
|
|
19
|
+
export function AppSidebar() {
|
|
20
|
+
const ready = useReady()
|
|
21
|
+
const mode = useApplicationMode()
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Sidebar>
|
|
25
|
+
<SidebarHeader className="flex justify-center items-center">
|
|
26
|
+
<img src="/tanstack.png" className="w-3/5" />
|
|
27
|
+
</SidebarHeader>
|
|
28
|
+
<SidebarContent>
|
|
29
|
+
{ready && (
|
|
30
|
+
<>
|
|
31
|
+
{mode === 'setup' && (
|
|
32
|
+
<SidebarGroup>
|
|
33
|
+
<ProjectName />
|
|
34
|
+
<ModeSelector />
|
|
35
|
+
<TypescriptSwitch />
|
|
36
|
+
</SidebarGroup>
|
|
37
|
+
)}
|
|
38
|
+
<SidebarGroup>
|
|
39
|
+
<SelectedAddOns />
|
|
40
|
+
</SidebarGroup>
|
|
41
|
+
{mode === 'setup' && (
|
|
42
|
+
<SidebarGroup>
|
|
43
|
+
<StarterDialog />
|
|
44
|
+
</SidebarGroup>
|
|
45
|
+
)}
|
|
46
|
+
</>
|
|
47
|
+
)}
|
|
48
|
+
</SidebarContent>
|
|
49
|
+
<SidebarFooter className="mb-5">
|
|
50
|
+
<RunAddOns />
|
|
51
|
+
<RunCreateApp />
|
|
52
|
+
</SidebarFooter>
|
|
53
|
+
</Sidebar>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { toast } from 'sonner'
|
|
3
|
+
import { TicketPlusIcon } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
import { Button } from '@/components/ui/button'
|
|
6
|
+
import { Input } from '@/components/ui/input'
|
|
7
|
+
import {
|
|
8
|
+
Dialog,
|
|
9
|
+
DialogContent,
|
|
10
|
+
DialogFooter,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
} from '@/components/ui/dialog'
|
|
14
|
+
|
|
15
|
+
import { addCustomAddOn, useAddOns, useRouterMode } from '@/store/project'
|
|
16
|
+
import { loadRemoteAddOn } from '@/lib/api'
|
|
17
|
+
|
|
18
|
+
export default function CustomAddOnDialog() {
|
|
19
|
+
const [url, setUrl] = useState('')
|
|
20
|
+
const [open, setOpen] = useState(false)
|
|
21
|
+
|
|
22
|
+
const mode = useRouterMode()
|
|
23
|
+
const { toggleAddOn } = useAddOns()
|
|
24
|
+
|
|
25
|
+
async function onImport() {
|
|
26
|
+
const data = await loadRemoteAddOn(url)
|
|
27
|
+
|
|
28
|
+
if ('error' in data) {
|
|
29
|
+
toast.error('Failed to load add-on', {
|
|
30
|
+
description: data.error,
|
|
31
|
+
})
|
|
32
|
+
} else {
|
|
33
|
+
addCustomAddOn(data)
|
|
34
|
+
if (data.modes.includes(mode)) {
|
|
35
|
+
toggleAddOn(data.id)
|
|
36
|
+
}
|
|
37
|
+
setOpen(false)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div>
|
|
43
|
+
<Button
|
|
44
|
+
variant="secondary"
|
|
45
|
+
className="w-full"
|
|
46
|
+
onClick={() => {
|
|
47
|
+
setUrl('')
|
|
48
|
+
setOpen(true)
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
<TicketPlusIcon className="w-4 h-4" />
|
|
52
|
+
Import Custom Add-On
|
|
53
|
+
</Button>
|
|
54
|
+
<Dialog modal open={open}>
|
|
55
|
+
<DialogContent className="sm:min-w-[425px] sm:max-w-fit">
|
|
56
|
+
<DialogHeader>
|
|
57
|
+
<DialogTitle>Import Custom Add-On</DialogTitle>
|
|
58
|
+
</DialogHeader>
|
|
59
|
+
<div>
|
|
60
|
+
<Input
|
|
61
|
+
value={url}
|
|
62
|
+
onChange={(e) => setUrl(e.target.value)}
|
|
63
|
+
placeholder="https://github.com/myorg/myproject/add-on.json"
|
|
64
|
+
className="min-w-lg w-full"
|
|
65
|
+
onKeyDown={(e) => {
|
|
66
|
+
if (e.key === 'Enter') {
|
|
67
|
+
onImport()
|
|
68
|
+
}
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
<DialogFooter>
|
|
73
|
+
<Button onClick={onImport}>Import</Button>
|
|
74
|
+
</DialogFooter>
|
|
75
|
+
</DialogContent>
|
|
76
|
+
</Dialog>
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react'
|
|
2
|
+
import { FileText, Folder } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
import FileViewer from './file-viewer'
|
|
5
|
+
import FileTree from './file-tree'
|
|
6
|
+
|
|
7
|
+
import type { FileTreeItem } from '@/types'
|
|
8
|
+
|
|
9
|
+
import { Label } from '@/components/ui/label'
|
|
10
|
+
import { Checkbox } from '@/components/ui/checkbox'
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
useApplicationMode,
|
|
14
|
+
useDryRun,
|
|
15
|
+
useFilters,
|
|
16
|
+
useOriginalOutput,
|
|
17
|
+
useProjectLocalFiles,
|
|
18
|
+
useReady,
|
|
19
|
+
} from '@/store/project'
|
|
20
|
+
|
|
21
|
+
import { getFileClass, twClasses } from '@/file-classes'
|
|
22
|
+
|
|
23
|
+
export function Filters() {
|
|
24
|
+
const { includedFiles, toggleFilter } = useFilters()
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="p-2 rounded-md bg-gray-900 file-filters">
|
|
28
|
+
<div className="text-center text-sm mb-2">File Filters</div>
|
|
29
|
+
<div className="flex flex-row flex-wrap gap-y-2">
|
|
30
|
+
<div className="flex flex-row items-center gap-2 w-1/3">
|
|
31
|
+
<Checkbox
|
|
32
|
+
id="unchanged"
|
|
33
|
+
checked={includedFiles.includes('unchanged')}
|
|
34
|
+
className="w-4 h-4"
|
|
35
|
+
onCheckedChange={() => toggleFilter('unchanged')}
|
|
36
|
+
/>
|
|
37
|
+
<Label htmlFor="unchanged" className={twClasses.unchanged}>
|
|
38
|
+
Unchanged
|
|
39
|
+
</Label>
|
|
40
|
+
</div>
|
|
41
|
+
<div className="flex flex-row items-center gap-2 w-1/3">
|
|
42
|
+
<Checkbox
|
|
43
|
+
id="added"
|
|
44
|
+
checked={includedFiles.includes('added')}
|
|
45
|
+
className="w-4 h-4"
|
|
46
|
+
onCheckedChange={() => toggleFilter('added')}
|
|
47
|
+
/>
|
|
48
|
+
<Label htmlFor="added" className={twClasses.added}>
|
|
49
|
+
Added
|
|
50
|
+
</Label>
|
|
51
|
+
</div>
|
|
52
|
+
<div className="flex flex-row items-center gap-2 w-1/3">
|
|
53
|
+
<Checkbox
|
|
54
|
+
id="modified"
|
|
55
|
+
checked={includedFiles.includes('modified')}
|
|
56
|
+
className="w-4 h-4"
|
|
57
|
+
onCheckedChange={() => toggleFilter('modified')}
|
|
58
|
+
/>
|
|
59
|
+
<Label htmlFor="modified" className={twClasses.modified}>
|
|
60
|
+
Modified
|
|
61
|
+
</Label>
|
|
62
|
+
</div>
|
|
63
|
+
<div className="flex flex-row items-center gap-2 w-1/3">
|
|
64
|
+
<Checkbox
|
|
65
|
+
id="deleted"
|
|
66
|
+
checked={includedFiles.includes('deleted')}
|
|
67
|
+
className="w-4 h-4"
|
|
68
|
+
onCheckedChange={() => toggleFilter('deleted')}
|
|
69
|
+
/>
|
|
70
|
+
<Label htmlFor="deleted" className={twClasses.deleted}>
|
|
71
|
+
Deleted
|
|
72
|
+
</Label>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="flex flex-row items-center gap-2 w-1/3">
|
|
75
|
+
<Checkbox
|
|
76
|
+
id="overwritten"
|
|
77
|
+
checked={includedFiles.includes('overwritten')}
|
|
78
|
+
className="w-4 h-4"
|
|
79
|
+
onCheckedChange={() => toggleFilter('overwritten')}
|
|
80
|
+
/>
|
|
81
|
+
<Label htmlFor="overwritten" className={twClasses.overwritten}>
|
|
82
|
+
Overwritten
|
|
83
|
+
</Label>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default function FileNavigator() {
|
|
91
|
+
const [selectedFile, setSelectedFile] = useState<string | null>(
|
|
92
|
+
'./package.json',
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const projectFiles = useOriginalOutput()
|
|
96
|
+
const localTree = useProjectLocalFiles()
|
|
97
|
+
const dryRunOutput = useDryRun()
|
|
98
|
+
|
|
99
|
+
const mode = useApplicationMode()
|
|
100
|
+
|
|
101
|
+
const tree = dryRunOutput.files
|
|
102
|
+
const originalTree =
|
|
103
|
+
mode === 'setup' ? dryRunOutput.files : projectFiles.files
|
|
104
|
+
const deletedFiles = dryRunOutput.deletedFiles
|
|
105
|
+
|
|
106
|
+
const [originalFileContents, setOriginalFileContents] = useState<string>()
|
|
107
|
+
const [modifiedFileContents, setModifiedFileContents] = useState<string>()
|
|
108
|
+
|
|
109
|
+
const { includedFiles } = useFilters()
|
|
110
|
+
|
|
111
|
+
const fileTree = useMemo(() => {
|
|
112
|
+
const treeData: Array<FileTreeItem> = []
|
|
113
|
+
|
|
114
|
+
const allFileSet = Array.from(
|
|
115
|
+
new Set([
|
|
116
|
+
...Object.keys(tree),
|
|
117
|
+
...Object.keys(localTree),
|
|
118
|
+
...Object.keys(originalTree),
|
|
119
|
+
]),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
allFileSet.sort().forEach((file) => {
|
|
123
|
+
const strippedFile = file.replace('./', '')
|
|
124
|
+
const parts = strippedFile.split('/')
|
|
125
|
+
|
|
126
|
+
let currentLevel = treeData
|
|
127
|
+
parts.forEach((part, index) => {
|
|
128
|
+
const existingNode = currentLevel.find((node) => node.name === part)
|
|
129
|
+
if (existingNode) {
|
|
130
|
+
currentLevel = existingNode.children || []
|
|
131
|
+
} else {
|
|
132
|
+
const fileInfo = getFileClass(
|
|
133
|
+
file,
|
|
134
|
+
tree,
|
|
135
|
+
originalTree,
|
|
136
|
+
localTree,
|
|
137
|
+
deletedFiles,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
index === parts.length - 1 &&
|
|
142
|
+
!includedFiles.includes(fileInfo.fileClass)
|
|
143
|
+
) {
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
if (index === parts.length - 1 && file === selectedFile) {
|
|
147
|
+
setModifiedFileContents(fileInfo.modifiedFile)
|
|
148
|
+
setOriginalFileContents(fileInfo.originalFile)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const newNode: FileTreeItem = {
|
|
152
|
+
id: parts.slice(0, index + 1).join('/'),
|
|
153
|
+
name: part,
|
|
154
|
+
fullPath: strippedFile,
|
|
155
|
+
children: index < parts.length - 1 ? [] : undefined,
|
|
156
|
+
icon:
|
|
157
|
+
index < parts.length - 1
|
|
158
|
+
? () => <Folder className="w-4 h-4 mr-2" />
|
|
159
|
+
: () => <FileText className="w-4 h-4 mr-2" />,
|
|
160
|
+
onClick:
|
|
161
|
+
index === parts.length - 1
|
|
162
|
+
? () => {
|
|
163
|
+
setSelectedFile(file)
|
|
164
|
+
setModifiedFileContents(fileInfo.modifiedFile)
|
|
165
|
+
setOriginalFileContents(fileInfo.originalFile)
|
|
166
|
+
}
|
|
167
|
+
: undefined,
|
|
168
|
+
className: twClasses[fileInfo.fileClass],
|
|
169
|
+
...fileInfo,
|
|
170
|
+
contents: tree[file] || localTree[file] || originalTree[file],
|
|
171
|
+
}
|
|
172
|
+
currentLevel.push(newNode)
|
|
173
|
+
currentLevel = newNode.children!
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
return treeData
|
|
178
|
+
}, [tree, originalTree, localTree, includedFiles])
|
|
179
|
+
|
|
180
|
+
const ready = useReady()
|
|
181
|
+
|
|
182
|
+
if (!ready) {
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<div
|
|
188
|
+
className={`flex flex-row border-1 rounded-md mr-10 p-2 inset-shadow-gray-600 inset-shadow-sm`}
|
|
189
|
+
>
|
|
190
|
+
<div className="w-1/4 max-w-1/4 pr-2">
|
|
191
|
+
{mode === 'add' && <Filters />}
|
|
192
|
+
<FileTree selectedFile={selectedFile} tree={fileTree} />
|
|
193
|
+
</div>
|
|
194
|
+
<div className="max-w-3/4 w-3/4 pl-2">
|
|
195
|
+
{selectedFile && modifiedFileContents ? (
|
|
196
|
+
<FileViewer
|
|
197
|
+
filePath={selectedFile}
|
|
198
|
+
originalFile={originalFileContents}
|
|
199
|
+
modifiedFile={modifiedFileContents}
|
|
200
|
+
/>
|
|
201
|
+
) : null}
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
@@ -1,77 +1,35 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
import { FileText, Folder } from 'lucide-react'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import type { FileTreeItem } from '@/types.js'
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import { TreeView } from '@/components/ui/tree-view'
|
|
7
7
|
|
|
8
8
|
export default function FileTree({
|
|
9
|
-
|
|
9
|
+
selectedFile,
|
|
10
10
|
tree,
|
|
11
|
-
originalTree,
|
|
12
|
-
onFileSelected,
|
|
13
|
-
extraTreeItems = [],
|
|
14
11
|
}: {
|
|
15
|
-
|
|
16
|
-
tree:
|
|
17
|
-
originalTree: Record<string, string>
|
|
18
|
-
onFileSelected: (file: string) => void
|
|
19
|
-
extraTreeItems?: Array<TreeDataItem>
|
|
12
|
+
selectedFile: string | null
|
|
13
|
+
tree: Array<FileTreeItem>
|
|
20
14
|
}) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Object.keys(tree)
|
|
32
|
-
.sort()
|
|
33
|
-
.forEach((file) => {
|
|
34
|
-
const parts = file.replace(`${prefix}/`, '').split('/')
|
|
35
|
-
|
|
36
|
-
let currentLevel = treeData
|
|
37
|
-
parts.forEach((part, index) => {
|
|
38
|
-
const existingNode = currentLevel.find((node) => node.name === part)
|
|
39
|
-
if (existingNode) {
|
|
40
|
-
currentLevel = existingNode.children || []
|
|
41
|
-
} else {
|
|
42
|
-
const newNode: TreeDataItem = {
|
|
43
|
-
id: index === parts.length - 1 ? file : `${file}-${index}`,
|
|
44
|
-
name: part,
|
|
45
|
-
children: index < parts.length - 1 ? [] : undefined,
|
|
46
|
-
icon:
|
|
47
|
-
index < parts.length - 1
|
|
48
|
-
? () => <Folder className="w-4 h-4 mr-2" />
|
|
49
|
-
: () => <FileText className="w-4 h-4 mr-2" />,
|
|
50
|
-
onClick:
|
|
51
|
-
index === parts.length - 1
|
|
52
|
-
? () => {
|
|
53
|
-
onFileSelected(file)
|
|
54
|
-
}
|
|
55
|
-
: undefined,
|
|
56
|
-
className:
|
|
57
|
-
index === parts.length - 1 && changed(file)
|
|
58
|
-
? 'text-green-300'
|
|
59
|
-
: '',
|
|
60
|
-
}
|
|
61
|
-
currentLevel.push(newNode)
|
|
62
|
-
currentLevel = newNode.children!
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
})
|
|
66
|
-
return [...extraTreeItems, ...treeData]
|
|
67
|
-
}, [prefix, tree, originalTree, extraTreeItems])
|
|
15
|
+
const initialExpandedItemIds = useMemo(
|
|
16
|
+
() => [
|
|
17
|
+
'src',
|
|
18
|
+
'src/routes',
|
|
19
|
+
'src/components',
|
|
20
|
+
'src/components/ui',
|
|
21
|
+
'src/lib',
|
|
22
|
+
],
|
|
23
|
+
[],
|
|
24
|
+
)
|
|
68
25
|
|
|
69
26
|
return (
|
|
70
27
|
<TreeView
|
|
71
|
-
|
|
28
|
+
initialSelectedItemId={selectedFile?.replace('./', '') ?? undefined}
|
|
29
|
+
initialExpandedItemIds={initialExpandedItemIds}
|
|
30
|
+
data={tree}
|
|
72
31
|
defaultNodeIcon={() => <Folder className="w-4 h-4 mr-2" />}
|
|
73
32
|
defaultLeafIcon={() => <FileText className="w-4 h-4 mr-2" />}
|
|
74
|
-
className="max-w-1/4 w-1/4 pr-2"
|
|
75
33
|
/>
|
|
76
34
|
)
|
|
77
35
|
}
|
|
@@ -6,7 +6,15 @@ import { json } from '@codemirror/lang-json'
|
|
|
6
6
|
import { css } from '@codemirror/lang-css'
|
|
7
7
|
import { html } from '@codemirror/lang-html'
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { githubDarkInit } from '@uiw/codemirror-theme-github'
|
|
10
|
+
|
|
11
|
+
const theme = githubDarkInit({
|
|
12
|
+
settings: {
|
|
13
|
+
background: 'oklch(0.07 0.005 285.823)',
|
|
14
|
+
foreground: '#c9d1d9',
|
|
15
|
+
gutterBackground: 'oklch(0.22 0.005 285.823)',
|
|
16
|
+
},
|
|
17
|
+
})
|
|
10
18
|
|
|
11
19
|
export default function FileViewer({
|
|
12
20
|
originalFile,
|
|
@@ -41,7 +49,7 @@ export default function FileViewer({
|
|
|
41
49
|
return (
|
|
42
50
|
<CodeMirror
|
|
43
51
|
value={modifiedFile}
|
|
44
|
-
theme={
|
|
52
|
+
theme={theme}
|
|
45
53
|
height="100vh"
|
|
46
54
|
width="100%"
|
|
47
55
|
readOnly
|
|
@@ -51,7 +59,7 @@ export default function FileViewer({
|
|
|
51
59
|
)
|
|
52
60
|
}
|
|
53
61
|
return (
|
|
54
|
-
<CodeMirrorMerge orientation="a-b" theme={
|
|
62
|
+
<CodeMirrorMerge orientation="a-b" theme={theme} className="text-lg">
|
|
55
63
|
<CodeMirrorMerge.Original value={originalFile} extensions={[language]} />
|
|
56
64
|
<CodeMirrorMerge.Modified value={modifiedFile} extensions={[language]} />
|
|
57
65
|
</CodeMirrorMerge>
|