@tanstack/cta-ui-base 0.15.4
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/LICENSE +21 -0
- package/components.json +21 -0
- package/dist/app.d.ts +1 -0
- package/dist/app.js +10 -0
- package/dist/components/add-on-info-dialog.d.ts +5 -0
- package/dist/components/add-on-info-dialog.js +5 -0
- package/dist/components/background-animation.d.ts +1 -0
- package/dist/components/background-animation.js +144 -0
- package/dist/components/cta-provider.d.ts +3 -0
- package/dist/components/cta-provider.js +11 -0
- package/dist/components/cta-sidebar.d.ts +1 -0
- package/dist/components/cta-sidebar.js +15 -0
- package/dist/components/custom-add-on-dialog.d.ts +1 -0
- package/dist/components/custom-add-on-dialog.js +38 -0
- package/dist/components/file-navigator.d.ts +2 -0
- package/dist/components/file-navigator.js +86 -0
- package/dist/components/file-tree.d.ts +5 -0
- package/dist/components/file-tree.js +14 -0
- package/dist/components/file-viewer.d.ts +5 -0
- package/dist/components/file-viewer.js +40 -0
- package/dist/components/header.d.ts +1 -0
- package/dist/components/header.js +5 -0
- package/dist/components/icons/tailwind.d.ts +3 -0
- package/dist/components/icons/tailwind.js +5 -0
- package/dist/components/icons/tanstack.d.ts +3 -0
- package/dist/components/icons/tanstack.js +5 -0
- package/dist/components/icons/typescript.d.ts +3 -0
- package/dist/components/icons/typescript.js +5 -0
- package/dist/components/query-provider.d.ts +3 -0
- package/dist/components/query-provider.js +7 -0
- package/dist/components/sidebar-items/add-ons.d.ts +1 -0
- package/dist/components/sidebar-items/add-ons.js +27 -0
- package/dist/components/sidebar-items/mode-selector.d.ts +1 -0
- package/dist/components/sidebar-items/mode-selector.js +19 -0
- package/dist/components/sidebar-items/project-name.d.ts +1 -0
- package/dist/components/sidebar-items/project-name.js +12 -0
- package/dist/components/sidebar-items/run-add-ons.d.ts +1 -0
- package/dist/components/sidebar-items/run-add-ons.js +25 -0
- package/dist/components/sidebar-items/run-create-app.d.ts +1 -0
- package/dist/components/sidebar-items/run-create-app.js +28 -0
- package/dist/components/sidebar-items/sidebar-container.d.ts +3 -0
- package/dist/components/sidebar-items/sidebar-container.js +4 -0
- package/dist/components/sidebar-items/sidebar-group.d.ts +3 -0
- package/dist/components/sidebar-items/sidebar-group.js +4 -0
- package/dist/components/sidebar-items/starter.d.ts +1 -0
- package/dist/components/sidebar-items/starter.js +42 -0
- package/dist/components/sidebar-items/typescript-switch.d.ts +1 -0
- package/dist/components/sidebar-items/typescript-switch.js +18 -0
- package/dist/components/starters-carousel.d.ts +3 -0
- package/dist/components/starters-carousel.js +12 -0
- package/dist/components/startup-dialog.d.ts +1 -0
- package/dist/components/startup-dialog.js +30 -0
- package/dist/components/status-list.d.ts +5 -0
- package/dist/components/status-list.js +4 -0
- package/dist/components/toaster.d.ts +4 -0
- package/dist/components/toaster.js +15 -0
- package/dist/components/ui/button.d.ts +10 -0
- package/dist/components/ui/button.js +32 -0
- package/dist/components/ui/carousel.d.ts +20 -0
- package/dist/components/ui/carousel.js +90 -0
- package/dist/components/ui/checkbox.d.ts +4 -0
- package/dist/components/ui/checkbox.js +9 -0
- package/dist/components/ui/dialog.d.ts +15 -0
- package/dist/components/ui/dialog.js +36 -0
- package/dist/components/ui/dropdown-menu.d.ts +25 -0
- package/dist/components/ui/dropdown-menu.js +51 -0
- package/dist/components/ui/input.d.ts +3 -0
- package/dist/components/ui/input.js +7 -0
- package/dist/components/ui/label.d.ts +4 -0
- package/dist/components/ui/label.js +8 -0
- package/dist/components/ui/popover.d.ts +7 -0
- package/dist/components/ui/popover.js +17 -0
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/separator.js +9 -0
- package/dist/components/ui/sheet.d.ts +13 -0
- package/dist/components/ui/sheet.js +40 -0
- package/dist/components/ui/skeleton.d.ts +2 -0
- package/dist/components/ui/skeleton.js +6 -0
- package/dist/components/ui/sonner.d.ts +3 -0
- package/dist/components/ui/sonner.js +12 -0
- package/dist/components/ui/switch.d.ts +4 -0
- package/dist/components/ui/switch.js +8 -0
- package/dist/components/ui/table.d.ts +10 -0
- package/dist/components/ui/table.js +28 -0
- package/dist/components/ui/tabs.d.ts +7 -0
- package/dist/components/ui/tabs.js +17 -0
- package/dist/components/ui/toggle-group.d.ts +7 -0
- package/dist/components/ui/toggle-group.js +20 -0
- package/dist/components/ui/toggle.d.ts +9 -0
- package/dist/components/ui/toggle.js +27 -0
- package/dist/components/ui/tooltip.d.ts +7 -0
- package/dist/components/ui/tooltip.js +18 -0
- package/dist/components/ui/tree-view.d.ts +25 -0
- package/dist/components/ui/tree-view.js +151 -0
- package/dist/file-classes.d.ts +8 -0
- package/dist/file-classes.js +41 -0
- package/dist/hooks/use-mounted.d.ts +1 -0
- package/dist/hooks/use-mounted.js +8 -0
- package/dist/hooks/use-preferred-reduced-motion.d.ts +5 -0
- package/dist/hooks/use-preferred-reduced-motion.js +20 -0
- package/dist/hooks/use-streaming-status.d.ts +6 -0
- package/dist/hooks/use-streaming-status.js +55 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/lib/api.d.ts +14 -0
- package/dist/lib/api.js +74 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/store/add-ons.d.ts +7 -0
- package/dist/store/add-ons.js +59 -0
- package/dist/store/project.d.ts +76 -0
- package/dist/store/project.js +269 -0
- package/package.json +50 -0
- package/src/app.tsx +28 -0
- package/src/components/add-on-info-dialog.tsx +39 -0
- package/src/components/background-animation.tsx +224 -0
- package/src/components/cta-provider.tsx +22 -0
- package/src/components/cta-sidebar.tsx +43 -0
- package/src/components/custom-add-on-dialog.tsx +79 -0
- package/src/components/file-navigator.tsx +207 -0
- package/src/components/file-tree.tsx +35 -0
- package/src/components/file-viewer.tsx +67 -0
- package/src/components/header.tsx +29 -0
- package/src/components/icons/tailwind.tsx +26 -0
- package/src/components/icons/tanstack.tsx +338 -0
- package/src/components/icons/typescript.tsx +23 -0
- package/src/components/query-provider.tsx +10 -0
- package/src/components/sidebar-items/add-ons.tsx +94 -0
- package/src/components/sidebar-items/mode-selector.tsx +56 -0
- package/src/components/sidebar-items/project-name.tsx +32 -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/sidebar-container.tsx +11 -0
- package/src/components/sidebar-items/sidebar-group.tsx +11 -0
- package/src/components/sidebar-items/starter.tsx +123 -0
- package/src/components/sidebar-items/typescript-switch.tsx +58 -0
- package/src/components/starters-carousel.tsx +41 -0
- package/src/components/startup-dialog.tsx +72 -0
- package/src/components/status-list.tsx +22 -0
- package/src/components/toaster.tsx +29 -0
- package/src/components/ui/button.tsx +61 -0
- package/src/components/ui/carousel.tsx +239 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/dialog.tsx +138 -0
- 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/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +24 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/table.tsx +114 -0
- package/src/components/ui/tabs.tsx +64 -0
- package/src/components/ui/toggle-group.tsx +72 -0
- package/src/components/ui/toggle.tsx +49 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/components/ui/tree-view.tsx +497 -0
- package/src/file-classes.ts +54 -0
- package/src/hooks/use-mounted.ts +9 -0
- package/src/hooks/use-preferred-reduced-motion.ts +27 -0
- package/src/hooks/use-streaming-status.ts +70 -0
- package/src/index.ts +44 -0
- package/src/lib/api.ts +100 -0
- package/src/lib/utils.ts +8 -0
- package/src/store/add-ons.ts +81 -0
- package/src/store/project.ts +345 -0
- package/src/types.d.ts +109 -0
- package/tests/store/add-ons.test.ts +222 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +6 -0
package/src/lib/api.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { SerializedOptions } from '@tanstack/cta-engine'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
AddOnInfo,
|
|
5
|
+
DryRunOutput,
|
|
6
|
+
InitialData,
|
|
7
|
+
StarterInfo,
|
|
8
|
+
} from '../types'
|
|
9
|
+
|
|
10
|
+
// @ts-ignore - import.meta.env is not available in the browser
|
|
11
|
+
const baseUrl = import.meta.env.VITE_API_BASE_URL || ''
|
|
12
|
+
|
|
13
|
+
export async function createAppStreaming(
|
|
14
|
+
options: SerializedOptions,
|
|
15
|
+
chosenAddOns: Array<string>,
|
|
16
|
+
projectStarter?: StarterInfo,
|
|
17
|
+
) {
|
|
18
|
+
return await fetch(`${baseUrl}/api/create-app`, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
options: {
|
|
22
|
+
...options,
|
|
23
|
+
chosenAddOns,
|
|
24
|
+
starter: projectStarter?.url || undefined,
|
|
25
|
+
},
|
|
26
|
+
}),
|
|
27
|
+
headers: {
|
|
28
|
+
'Content-Type': 'application/json',
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function addToAppStreaming(chosenAddOns: Array<string>) {
|
|
34
|
+
return await fetch(`${baseUrl}/api/add-to-app`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
addOns: chosenAddOns,
|
|
38
|
+
}),
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function shutdown() {
|
|
46
|
+
return fetch(`${baseUrl}/api/shutdown`, {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function loadRemoteAddOn(url: string) {
|
|
52
|
+
const response = await fetch(`${baseUrl}/api/load-remote-add-on?url=${url}`)
|
|
53
|
+
return (await response.json()) as AddOnInfo | { error: string }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function loadRemoteStarter(url: string) {
|
|
57
|
+
const response = await fetch(`${baseUrl}/api/load-starter?url=${url}`)
|
|
58
|
+
return (await response.json()) as StarterInfo | { error: string }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const initialDataRequest = fetch(`${baseUrl}/api/initial-payload`)
|
|
62
|
+
|
|
63
|
+
export async function loadInitialData() {
|
|
64
|
+
const payloadReq = await initialDataRequest
|
|
65
|
+
return (await payloadReq.json()) as InitialData
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function dryRunCreateApp(
|
|
69
|
+
options: SerializedOptions,
|
|
70
|
+
chosenAddOns: Array<string>,
|
|
71
|
+
projectStarter?: StarterInfo,
|
|
72
|
+
) {
|
|
73
|
+
const outputReq = await fetch(`${baseUrl}/api/dry-run-create-app`, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: {
|
|
76
|
+
'Content-Type': 'application/json',
|
|
77
|
+
},
|
|
78
|
+
body: JSON.stringify({
|
|
79
|
+
options: {
|
|
80
|
+
...options,
|
|
81
|
+
chosenAddOns: chosenAddOns,
|
|
82
|
+
starter: projectStarter?.url,
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
})
|
|
86
|
+
return outputReq.json() as Promise<DryRunOutput>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function dryRunAddToApp(addOns: Array<string>) {
|
|
90
|
+
const outputReq = await fetch(`${baseUrl}/api/dry-run-add-to-app`, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: {
|
|
93
|
+
'Content-Type': 'application/json',
|
|
94
|
+
},
|
|
95
|
+
body: JSON.stringify({
|
|
96
|
+
addOns,
|
|
97
|
+
}),
|
|
98
|
+
})
|
|
99
|
+
return outputReq.json() as Promise<DryRunOutput>
|
|
100
|
+
}
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { AddOnInfo } from '../types'
|
|
2
|
+
|
|
3
|
+
export function getAddOnStatus(
|
|
4
|
+
availableAddOns: Array<AddOnInfo>,
|
|
5
|
+
chosenAddOns: Array<string>,
|
|
6
|
+
originalAddOns: Array<string>,
|
|
7
|
+
) {
|
|
8
|
+
const addOnMap = new Map<
|
|
9
|
+
string,
|
|
10
|
+
{
|
|
11
|
+
enabled: boolean
|
|
12
|
+
selected: boolean
|
|
13
|
+
dependedUpon: boolean
|
|
14
|
+
}
|
|
15
|
+
>()
|
|
16
|
+
|
|
17
|
+
for (const addOn of availableAddOns) {
|
|
18
|
+
addOnMap.set(addOn.id, {
|
|
19
|
+
selected: false,
|
|
20
|
+
enabled: true,
|
|
21
|
+
dependedUpon: false,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Guard against cycles in the dependency graph. The results won't be great. But it won't crash.
|
|
26
|
+
function cycleGuardedSelectAndDisableDependsOn(startingAddOnId: string) {
|
|
27
|
+
const visited = new Set<string>()
|
|
28
|
+
function selectAndDisableDependsOn(addOnId: string) {
|
|
29
|
+
if (visited.has(addOnId)) {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
visited.add(addOnId)
|
|
33
|
+
const selectedAddOn = availableAddOns.find(
|
|
34
|
+
(addOn) => addOn.id === addOnId,
|
|
35
|
+
)
|
|
36
|
+
if (selectedAddOn) {
|
|
37
|
+
for (const dependsOnId of selectedAddOn.dependsOn || []) {
|
|
38
|
+
const dependsOnAddOn = addOnMap.get(dependsOnId)
|
|
39
|
+
if (dependsOnAddOn) {
|
|
40
|
+
dependsOnAddOn.selected = true
|
|
41
|
+
dependsOnAddOn.enabled = false
|
|
42
|
+
dependsOnAddOn.dependedUpon = true
|
|
43
|
+
selectAndDisableDependsOn(dependsOnId)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const addOn = addOnMap.get(addOnId)
|
|
47
|
+
if (addOn) {
|
|
48
|
+
addOn.selected = true
|
|
49
|
+
if (!addOn.dependedUpon) {
|
|
50
|
+
addOn.enabled = true
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
selectAndDisableDependsOn(startingAddOnId)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const addOn of originalAddOns) {
|
|
59
|
+
const addOnInfo = addOnMap.get(addOn)
|
|
60
|
+
if (addOnInfo) {
|
|
61
|
+
addOnInfo.selected = true
|
|
62
|
+
addOnInfo.enabled = false
|
|
63
|
+
addOnInfo.dependedUpon = true
|
|
64
|
+
}
|
|
65
|
+
cycleGuardedSelectAndDisableDependsOn(addOn)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const addOnId of chosenAddOns) {
|
|
69
|
+
cycleGuardedSelectAndDisableDependsOn(addOnId)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Object.fromEntries(
|
|
73
|
+
Array.from(addOnMap.entries()).map(([v, addOn]) => [
|
|
74
|
+
v,
|
|
75
|
+
{
|
|
76
|
+
enabled: addOn.enabled,
|
|
77
|
+
selected: addOn.selected,
|
|
78
|
+
},
|
|
79
|
+
]),
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo } from 'react'
|
|
2
|
+
import { create } from 'zustand'
|
|
3
|
+
import { persist } from 'zustand/middleware'
|
|
4
|
+
import { useQuery } from '@tanstack/react-query'
|
|
5
|
+
|
|
6
|
+
import { dryRunAddToApp, dryRunCreateApp, loadInitialData } from '../lib/api'
|
|
7
|
+
|
|
8
|
+
import { getAddOnStatus } from './add-ons'
|
|
9
|
+
|
|
10
|
+
import type { SerializedOptions } from '@tanstack/cta-engine'
|
|
11
|
+
|
|
12
|
+
import type { AddOnInfo, DryRunOutput, StarterInfo } from '../types'
|
|
13
|
+
|
|
14
|
+
export const useProjectOptions = create<
|
|
15
|
+
SerializedOptions & { initialized: boolean }
|
|
16
|
+
>(() => ({
|
|
17
|
+
initialized: false,
|
|
18
|
+
framework: '',
|
|
19
|
+
mode: '',
|
|
20
|
+
projectName: '',
|
|
21
|
+
targetDir: '',
|
|
22
|
+
typescript: true,
|
|
23
|
+
tailwind: true,
|
|
24
|
+
git: true,
|
|
25
|
+
chosenAddOns: [],
|
|
26
|
+
packageManager: 'pnpm',
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
const useInitialData = () =>
|
|
30
|
+
useQuery({
|
|
31
|
+
queryKey: ['initial-data'],
|
|
32
|
+
queryFn: loadInitialData,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
export const useReady = () => {
|
|
36
|
+
const { data } = useInitialData()
|
|
37
|
+
return data !== undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const useForcedRouterMode = () => useInitialData().data?.forcedRouterMode
|
|
41
|
+
const useForcedAddOns = () => useInitialData().data?.forcedAddOns
|
|
42
|
+
|
|
43
|
+
export const useRegistry = () => useInitialData().data?.registry
|
|
44
|
+
export const useProjectLocalFiles = () => useInitialData().data?.localFiles
|
|
45
|
+
export const useOriginalOutput = () => useInitialData().data?.output
|
|
46
|
+
export const useOriginalOptions = () => useInitialData().data?.options
|
|
47
|
+
export const useOriginalSelectedAddOns = () =>
|
|
48
|
+
useOriginalOptions()?.chosenAddOns
|
|
49
|
+
export const useApplicationMode = () => useInitialData().data?.applicationMode
|
|
50
|
+
export const useAddOnsByMode = () => useInitialData().data?.addOns
|
|
51
|
+
export const useSupportedModes = () => useInitialData().data?.supportedModes
|
|
52
|
+
|
|
53
|
+
const useApplicationSettings = create<{
|
|
54
|
+
includeFiles: Array<string>
|
|
55
|
+
}>(() => ({
|
|
56
|
+
includeFiles: ['unchanged', 'added', 'modified', 'deleted', 'overwritten'],
|
|
57
|
+
}))
|
|
58
|
+
|
|
59
|
+
const useMutableAddOns = create<{
|
|
60
|
+
userSelectedAddOns: Array<string>
|
|
61
|
+
customAddOns: Array<AddOnInfo>
|
|
62
|
+
}>(() => ({
|
|
63
|
+
userSelectedAddOns: [],
|
|
64
|
+
customAddOns: [],
|
|
65
|
+
}))
|
|
66
|
+
|
|
67
|
+
export const useProjectStarter = create<{
|
|
68
|
+
projectStarter: StarterInfo | undefined
|
|
69
|
+
}>(() => ({
|
|
70
|
+
projectStarter: undefined,
|
|
71
|
+
}))
|
|
72
|
+
|
|
73
|
+
export function addCustomAddOn(addOn: AddOnInfo) {
|
|
74
|
+
useMutableAddOns.setState((state) => ({
|
|
75
|
+
customAddOns: [...state.customAddOns, addOn],
|
|
76
|
+
}))
|
|
77
|
+
if (addOn.modes.includes(useProjectOptions.getState().mode)) {
|
|
78
|
+
useMutableAddOns.setState((state) => ({
|
|
79
|
+
userSelectedAddOns: [...state.userSelectedAddOns, addOn.id],
|
|
80
|
+
}))
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function useAddOns() {
|
|
85
|
+
const ready = useReady()
|
|
86
|
+
|
|
87
|
+
const routerMode = useRouterMode()
|
|
88
|
+
const originalSelectedAddOns = useOriginalSelectedAddOns()
|
|
89
|
+
const addOnsByMode = useAddOnsByMode()
|
|
90
|
+
const forcedAddOns = useForcedAddOns()
|
|
91
|
+
const { userSelectedAddOns, customAddOns } = useMutableAddOns()
|
|
92
|
+
const projectStarter = useProjectStarter().projectStarter
|
|
93
|
+
|
|
94
|
+
const availableAddOns = useMemo(() => {
|
|
95
|
+
if (!ready) return []
|
|
96
|
+
const baseAddOns = addOnsByMode?.[routerMode] || []
|
|
97
|
+
return [
|
|
98
|
+
...baseAddOns,
|
|
99
|
+
...customAddOns.filter((addOn) => addOn.modes.includes(routerMode)),
|
|
100
|
+
]
|
|
101
|
+
}, [ready, routerMode, addOnsByMode, customAddOns])
|
|
102
|
+
|
|
103
|
+
const addOnState = useMemo(() => {
|
|
104
|
+
if (!ready) return {}
|
|
105
|
+
const originalAddOns: Set<string> = new Set()
|
|
106
|
+
for (const addOn of projectStarter?.dependsOn || []) {
|
|
107
|
+
originalAddOns.add(addOn)
|
|
108
|
+
}
|
|
109
|
+
for (const addOn of originalSelectedAddOns) {
|
|
110
|
+
originalAddOns.add(addOn)
|
|
111
|
+
}
|
|
112
|
+
for (const addOn of forcedAddOns || []) {
|
|
113
|
+
originalAddOns.add(addOn)
|
|
114
|
+
}
|
|
115
|
+
return getAddOnStatus(
|
|
116
|
+
availableAddOns,
|
|
117
|
+
userSelectedAddOns,
|
|
118
|
+
Array.from(originalAddOns),
|
|
119
|
+
)
|
|
120
|
+
}, [
|
|
121
|
+
ready,
|
|
122
|
+
availableAddOns,
|
|
123
|
+
userSelectedAddOns,
|
|
124
|
+
originalSelectedAddOns,
|
|
125
|
+
projectStarter?.dependsOn,
|
|
126
|
+
forcedAddOns,
|
|
127
|
+
])
|
|
128
|
+
|
|
129
|
+
const chosenAddOns = useMemo(() => {
|
|
130
|
+
if (!ready) return []
|
|
131
|
+
const addOns = new Set(
|
|
132
|
+
Object.keys(addOnState).filter((addOn) => addOnState[addOn].selected),
|
|
133
|
+
)
|
|
134
|
+
for (const addOn of forcedAddOns || []) {
|
|
135
|
+
addOns.add(addOn)
|
|
136
|
+
}
|
|
137
|
+
return Array.from(addOns)
|
|
138
|
+
}, [ready, addOnState, forcedAddOns])
|
|
139
|
+
|
|
140
|
+
const toggleAddOn = useCallback(
|
|
141
|
+
(addOnId: string) => {
|
|
142
|
+
if (!ready) return
|
|
143
|
+
if (addOnState[addOnId].enabled) {
|
|
144
|
+
if (addOnState[addOnId].selected) {
|
|
145
|
+
useMutableAddOns.setState((state) => ({
|
|
146
|
+
userSelectedAddOns: state.userSelectedAddOns.filter(
|
|
147
|
+
(addOn) => addOn !== addOnId,
|
|
148
|
+
),
|
|
149
|
+
}))
|
|
150
|
+
} else {
|
|
151
|
+
useMutableAddOns.setState((state) => ({
|
|
152
|
+
userSelectedAddOns: [...state.userSelectedAddOns, addOnId],
|
|
153
|
+
}))
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
[ready, addOnState],
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
toggleAddOn,
|
|
162
|
+
chosenAddOns,
|
|
163
|
+
availableAddOns,
|
|
164
|
+
userSelectedAddOns,
|
|
165
|
+
originalSelectedAddOns,
|
|
166
|
+
addOnState,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const useHasProjectStarter = () =>
|
|
171
|
+
useProjectStarter((state) => state.projectStarter === undefined)
|
|
172
|
+
|
|
173
|
+
export const useModeEditable = () => {
|
|
174
|
+
const ready = useReady()
|
|
175
|
+
const forcedRouterMode = useForcedRouterMode()
|
|
176
|
+
const hasProjectStarter = useHasProjectStarter()
|
|
177
|
+
return ready ? !forcedRouterMode && hasProjectStarter : false
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export const useTypeScriptEditable = () => {
|
|
181
|
+
const ready = useReady()
|
|
182
|
+
const hasProjectStarter = useHasProjectStarter()
|
|
183
|
+
const routerMode = useRouterMode()
|
|
184
|
+
return ready ? hasProjectStarter && routerMode === 'code-router' : false
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export const useTailwindEditable = () => {
|
|
188
|
+
const ready = useReady()
|
|
189
|
+
const hasProjectStarter = useHasProjectStarter()
|
|
190
|
+
const routerMode = useRouterMode()
|
|
191
|
+
return ready ? hasProjectStarter && routerMode === 'code-router' : false
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export const useProjectName = () =>
|
|
195
|
+
useProjectOptions((state) => state.projectName)
|
|
196
|
+
|
|
197
|
+
export const useRouterMode = () => {
|
|
198
|
+
const ready = useReady()
|
|
199
|
+
const forcedRouterMode = useForcedRouterMode()
|
|
200
|
+
const userMode = useProjectOptions((state) => state.mode)
|
|
201
|
+
return ready ? forcedRouterMode || userMode : 'file-router'
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function useFilters() {
|
|
205
|
+
const ready = useReady()
|
|
206
|
+
const includedFiles = useApplicationSettings((state) => state.includeFiles)
|
|
207
|
+
|
|
208
|
+
const toggleFilter = useCallback(
|
|
209
|
+
(filter: string) => {
|
|
210
|
+
if (!ready) return
|
|
211
|
+
useApplicationSettings.setState((state) => ({
|
|
212
|
+
includeFiles: state.includeFiles.includes(filter)
|
|
213
|
+
? state.includeFiles.filter((f) => f !== filter)
|
|
214
|
+
: [...state.includeFiles, filter],
|
|
215
|
+
}))
|
|
216
|
+
},
|
|
217
|
+
[ready],
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
includedFiles,
|
|
222
|
+
toggleFilter,
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function useDryRun() {
|
|
227
|
+
const ready = useReady()
|
|
228
|
+
const applicationMode = useApplicationMode()
|
|
229
|
+
const { initialized, ...projectOptions } = useProjectOptions()
|
|
230
|
+
const { userSelectedAddOns, chosenAddOns } = useAddOns()
|
|
231
|
+
const projectStarter = useProjectStarter().projectStarter
|
|
232
|
+
|
|
233
|
+
const { data: dryRunOutput } = useQuery<DryRunOutput>({
|
|
234
|
+
queryKey: [
|
|
235
|
+
'dry-run',
|
|
236
|
+
applicationMode,
|
|
237
|
+
JSON.stringify(projectOptions),
|
|
238
|
+
JSON.stringify(userSelectedAddOns),
|
|
239
|
+
projectStarter?.url,
|
|
240
|
+
],
|
|
241
|
+
queryFn: async () => {
|
|
242
|
+
if (applicationMode === 'none' || !ready || !initialized) {
|
|
243
|
+
return {
|
|
244
|
+
files: {},
|
|
245
|
+
commands: [],
|
|
246
|
+
deletedFiles: [],
|
|
247
|
+
}
|
|
248
|
+
} else if (applicationMode === 'setup') {
|
|
249
|
+
return dryRunCreateApp(projectOptions, chosenAddOns, projectStarter)
|
|
250
|
+
} else {
|
|
251
|
+
return dryRunAddToApp(userSelectedAddOns)
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
enabled: ready,
|
|
255
|
+
initialData: {
|
|
256
|
+
files: {},
|
|
257
|
+
commands: [],
|
|
258
|
+
deletedFiles: [],
|
|
259
|
+
},
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
return dryRunOutput
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
type StartupDialogState = {
|
|
266
|
+
open: boolean
|
|
267
|
+
dontShowAgain: boolean
|
|
268
|
+
setOpen: (open: boolean) => void
|
|
269
|
+
setDontShowAgain: (dontShowAgain: boolean) => void
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export const useStartupDialog = create<StartupDialogState>()(
|
|
273
|
+
persist(
|
|
274
|
+
(set) => ({
|
|
275
|
+
open: false,
|
|
276
|
+
dontShowAgain: false,
|
|
277
|
+
setOpen: (open) => set({ open }),
|
|
278
|
+
setDontShowAgain: (dontShowAgain) => set({ dontShowAgain }),
|
|
279
|
+
}),
|
|
280
|
+
{
|
|
281
|
+
name: 'startup-dialog',
|
|
282
|
+
partialize: (state) => ({
|
|
283
|
+
dontShowAgain: state.dontShowAgain,
|
|
284
|
+
}),
|
|
285
|
+
merge: (persistedState: unknown, currentState) => {
|
|
286
|
+
if (
|
|
287
|
+
persistedState &&
|
|
288
|
+
(persistedState as { dontShowAgain?: boolean }).dontShowAgain
|
|
289
|
+
) {
|
|
290
|
+
currentState.open = false
|
|
291
|
+
} else {
|
|
292
|
+
currentState.open = true
|
|
293
|
+
}
|
|
294
|
+
return currentState
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
),
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
export const setProjectName = (projectName: string) =>
|
|
301
|
+
useProjectOptions.setState({
|
|
302
|
+
projectName,
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
export const setRouterMode = (mode: string) =>
|
|
306
|
+
useProjectOptions.setState({
|
|
307
|
+
mode,
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
export function setTypeScript(typescript: boolean) {
|
|
311
|
+
useProjectOptions.setState({
|
|
312
|
+
typescript,
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function setTailwind(tailwind: boolean) {
|
|
317
|
+
useProjectOptions.setState({
|
|
318
|
+
tailwind,
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function setProjectStarter(starter: StarterInfo | undefined) {
|
|
323
|
+
useProjectStarter.setState(() => ({
|
|
324
|
+
projectStarter: starter,
|
|
325
|
+
}))
|
|
326
|
+
if (starter) {
|
|
327
|
+
useProjectOptions.setState({
|
|
328
|
+
mode: starter.mode,
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function useManager() {
|
|
334
|
+
const ready = useReady()
|
|
335
|
+
const originalOptions = useOriginalOptions()
|
|
336
|
+
|
|
337
|
+
useEffect(() => {
|
|
338
|
+
if (ready) {
|
|
339
|
+
useProjectOptions.setState({
|
|
340
|
+
...originalOptions,
|
|
341
|
+
initialized: true,
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
}, [ready])
|
|
345
|
+
}
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { StatusStepType } from '@tanstack/cta-engine'
|
|
2
|
+
|
|
3
|
+
export type ApplicationMode = 'add' | 'setup' | 'none'
|
|
4
|
+
|
|
5
|
+
export type StarterInfo = {
|
|
6
|
+
url: string
|
|
7
|
+
id: string
|
|
8
|
+
name: string
|
|
9
|
+
description: string
|
|
10
|
+
version: string
|
|
11
|
+
author: string
|
|
12
|
+
license: string
|
|
13
|
+
mode: string
|
|
14
|
+
typescript: boolean
|
|
15
|
+
tailwind: boolean
|
|
16
|
+
banner?: string
|
|
17
|
+
dependsOn?: Array<string>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Files
|
|
21
|
+
|
|
22
|
+
export type DryRunOutput = {
|
|
23
|
+
files: Record<string, string>
|
|
24
|
+
commands: Array<{
|
|
25
|
+
command: string
|
|
26
|
+
args: Array<string>
|
|
27
|
+
}>
|
|
28
|
+
deletedFiles: Array<string>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type AddOnInfo = {
|
|
32
|
+
id: string
|
|
33
|
+
name: string
|
|
34
|
+
description: string
|
|
35
|
+
type: 'add-on' | 'example' | 'starter' | 'toolchain'
|
|
36
|
+
modes: Array<string>
|
|
37
|
+
smallLogo?: string
|
|
38
|
+
logo?: string
|
|
39
|
+
link: string
|
|
40
|
+
dependsOn?: Array<string>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type FileClass =
|
|
44
|
+
| 'unchanged'
|
|
45
|
+
| 'added'
|
|
46
|
+
| 'modified'
|
|
47
|
+
| 'deleted'
|
|
48
|
+
| 'overwritten'
|
|
49
|
+
|
|
50
|
+
export type FileTreeItem = TreeDataItem & {
|
|
51
|
+
contents: string
|
|
52
|
+
fullPath: string
|
|
53
|
+
fileClass: FileClass | undefined
|
|
54
|
+
originalFile?: string
|
|
55
|
+
modifiedFile?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type Registry = {
|
|
59
|
+
starters: Array<{
|
|
60
|
+
name: string
|
|
61
|
+
description: string
|
|
62
|
+
url: string
|
|
63
|
+
banner?: string
|
|
64
|
+
}>
|
|
65
|
+
'add-ons': Array<{
|
|
66
|
+
name: string
|
|
67
|
+
description: string
|
|
68
|
+
url: string
|
|
69
|
+
}>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type InitialData = {
|
|
73
|
+
supportedModes: Record<
|
|
74
|
+
string,
|
|
75
|
+
{
|
|
76
|
+
displayName: string
|
|
77
|
+
description: string
|
|
78
|
+
forceTypescript: boolean
|
|
79
|
+
}
|
|
80
|
+
>
|
|
81
|
+
options: SerializedOptions
|
|
82
|
+
output: GeneratorOutput
|
|
83
|
+
localFiles: Record<string, string>
|
|
84
|
+
addOns: Record<string, Array<AddOnInfo>>
|
|
85
|
+
applicationMode: ApplicationMode
|
|
86
|
+
forcedRouterMode?: string
|
|
87
|
+
forcedAddOns?: Array<string>
|
|
88
|
+
registry?: Registry | undefined
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type EventItem = {
|
|
92
|
+
msgType: 'start'
|
|
93
|
+
id: string
|
|
94
|
+
type: StatusStepType
|
|
95
|
+
message: string
|
|
96
|
+
}
|
|
97
|
+
export type EventFinish = {
|
|
98
|
+
msgType: 'finish'
|
|
99
|
+
id: string
|
|
100
|
+
message: string
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type StreamEvent = EventItem | EventFinish
|
|
104
|
+
|
|
105
|
+
export type StreamItem = {
|
|
106
|
+
id: string
|
|
107
|
+
icon: typeof FileIcon
|
|
108
|
+
message: string
|
|
109
|
+
}
|