create-visualbuild-app 0.1.0 → 1.0.0
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/README.md +306 -123
- package/docs/user-guide.md +759 -0
- package/package.json +92 -85
- package/scripts/create-builder-app.mjs +513 -501
- package/scripts/vb-dev.mjs +41 -32
- package/scripts/vb-generate.mjs +332 -224
- package/templates/default/app/.vercelignore +6 -0
- package/templates/default/app/README.md +56 -21
- package/templates/default/app/visualbuild/components.json +3 -0
- package/templates/default/app/visualbuild/config.d.ts +48 -0
- package/templates/default/app/visualbuild.config.ts +38 -0
- package/templates/default/builder/README.md +37 -21
- package/visual-app-builder/server/parseReactPage.ts +776 -571
- package/visual-app-builder/shared/createReactComponentSource.d.mts +2 -0
- package/visual-app-builder/shared/createReactComponentSource.mjs +45 -0
- package/visual-app-builder/shared/generateReactSource.d.mts +57 -37
- package/visual-app-builder/shared/generateReactSource.mjs +608 -443
- package/visual-app-builder/shared/npmBuildCommand.d.mts +17 -0
- package/visual-app-builder/shared/npmBuildCommand.mjs +26 -0
- package/visual-app-builder/shared/syncCustomComponentSource.d.mts +13 -0
- package/visual-app-builder/shared/syncCustomComponentSource.mjs +345 -0
- package/visual-app-builder/shared/vercelDeployment.d.mts +31 -0
- package/visual-app-builder/shared/vercelDeployment.mjs +266 -0
- package/visual-app-builder/shared/visualbuildConfig.d.mts +144 -0
- package/visual-app-builder/shared/visualbuildConfig.mjs +578 -0
- package/visual-app-builder/src/App.tsx +1090 -874
- package/visual-app-builder/src/components/Canvas.tsx +1116 -1059
- package/visual-app-builder/src/components/CodePanel.tsx +1147 -812
- package/visual-app-builder/src/components/ComponentSidebar.tsx +365 -302
- package/visual-app-builder/src/components/DeploymentModal.tsx +295 -0
- package/visual-app-builder/src/components/PageSwitcher.tsx +133 -133
- package/visual-app-builder/src/components/ProjectExplorer.tsx +1054 -1054
- package/visual-app-builder/src/components/PropertiesPanel.tsx +792 -692
- package/visual-app-builder/src/components/Topbar.tsx +257 -128
- package/visual-app-builder/src/data/componentRegistry.tsx +613 -292
- package/visual-app-builder/src/data/tailwindClassCatalog.ts +95 -0
- package/visual-app-builder/src/index.css +383 -111
- package/visual-app-builder/src/stores/useAppStore.ts +2385 -1265
- package/visual-app-builder/src/theme.ts +71 -0
- package/visual-app-builder/src/types/index.ts +350 -261
- package/visual-app-builder/src/utils/codegen.ts +90 -66
- package/visual-app-builder/src/utils/deployment.ts +54 -0
- package/visual-app-builder/src/utils/projectPersistence.ts +218 -177
- package/visual-app-builder/vite.config.ts +1946 -1479
|
@@ -1,66 +1,90 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createPageSourcePath as createAstPageSourcePath,
|
|
3
|
-
generateAppSource,
|
|
4
|
-
generateLayoutSource,
|
|
5
|
-
generateNodeSource,
|
|
6
|
-
generatePageSource,
|
|
7
|
-
getPageSourcePath as getAstPageSourcePath,
|
|
8
|
-
toPageComponentName as toAstPageComponentName,
|
|
9
|
-
} from '../../shared/generateReactSource.mjs'
|
|
10
|
-
import type {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
createPageSourcePath as createAstPageSourcePath,
|
|
3
|
+
generateAppSource,
|
|
4
|
+
generateLayoutSource,
|
|
5
|
+
generateNodeSource,
|
|
6
|
+
generatePageSource,
|
|
7
|
+
getPageSourcePath as getAstPageSourcePath,
|
|
8
|
+
toPageComponentName as toAstPageComponentName,
|
|
9
|
+
} from '../../shared/generateReactSource.mjs'
|
|
10
|
+
import type {
|
|
11
|
+
AppShell,
|
|
12
|
+
ComponentType,
|
|
13
|
+
CustomComponentDefinition,
|
|
14
|
+
Page,
|
|
15
|
+
VisualNode,
|
|
16
|
+
} from '../types'
|
|
17
|
+
|
|
18
|
+
export function generatePageCode(
|
|
19
|
+
page: Page,
|
|
20
|
+
customComponents: CustomComponentDefinition[] = []
|
|
21
|
+
): string {
|
|
22
|
+
return generatePageSource(page, customComponents).trimEnd()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function generateAppCode(
|
|
26
|
+
pages: Page[],
|
|
27
|
+
appShell: AppShell,
|
|
28
|
+
options?: {
|
|
29
|
+
appFile?: string
|
|
30
|
+
layoutFile?: string
|
|
31
|
+
emitLayout?: boolean
|
|
32
|
+
}
|
|
33
|
+
): string {
|
|
34
|
+
return generateAppSource(pages, appShell, options).trimEnd()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function generateLayoutCode(
|
|
38
|
+
appShell: AppShell,
|
|
39
|
+
customComponents: CustomComponentDefinition[] = [],
|
|
40
|
+
options?: { layoutFile?: string }
|
|
41
|
+
): string {
|
|
42
|
+
return generateLayoutSource(appShell, customComponents, options).trimEnd()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function generateProjectSchema(
|
|
46
|
+
pages: Page[],
|
|
47
|
+
appShell: AppShell,
|
|
48
|
+
projectName = 'my-app'
|
|
49
|
+
): string {
|
|
50
|
+
return JSON.stringify(
|
|
51
|
+
{
|
|
52
|
+
meta: {
|
|
53
|
+
projectName,
|
|
54
|
+
framework: 'react',
|
|
55
|
+
styling: 'tailwind',
|
|
56
|
+
},
|
|
57
|
+
appShell,
|
|
58
|
+
pages,
|
|
59
|
+
},
|
|
60
|
+
null,
|
|
61
|
+
2
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function generateComponentSource(type: ComponentType): string {
|
|
66
|
+
return `export function ${type}() {
|
|
67
|
+
return null
|
|
68
|
+
}`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function toPageComponentName(name: string): string {
|
|
72
|
+
return toAstPageComponentName(name)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getPageSourcePath(
|
|
76
|
+
page: Pick<Page, 'name' | 'sourcePath'>
|
|
77
|
+
): string {
|
|
78
|
+
return getAstPageSourcePath(page)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createPageSourcePath(
|
|
82
|
+
name: string,
|
|
83
|
+
pagesDir?: string
|
|
84
|
+
): string {
|
|
85
|
+
return createAstPageSourcePath(name, pagesDir)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function generateNodeCode(node: VisualNode, indent: string): string {
|
|
89
|
+
return generateNodeSource(node, indent)
|
|
90
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface DeploymentConfiguration {
|
|
2
|
+
provider: 'vercel'
|
|
3
|
+
tokenConfigured: boolean
|
|
4
|
+
projectName: string
|
|
5
|
+
teamId: string
|
|
6
|
+
outputDirectory: string
|
|
7
|
+
sourcePolicy: 'static-build-output-only'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface DeploymentResult {
|
|
11
|
+
id: string
|
|
12
|
+
url: string
|
|
13
|
+
inspectorUrl?: string
|
|
14
|
+
readyState: 'READY'
|
|
15
|
+
fileCount: number
|
|
16
|
+
totalBytes: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function loadDeploymentConfiguration() {
|
|
20
|
+
const response = await fetch('/__visualbuild/deploy')
|
|
21
|
+
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(await readDeploymentError(response))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (await response.json()) as DeploymentConfiguration
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function deployProjectToVercel(
|
|
30
|
+
projectName: string,
|
|
31
|
+
teamId: string
|
|
32
|
+
) {
|
|
33
|
+
const response = await fetch('/__visualbuild/deploy', {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify({ projectName, teamId }),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error(await readDeploymentError(response))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (await response.json()) as DeploymentResult
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function readDeploymentError(response: Response) {
|
|
49
|
+
const payload = (await response.json().catch(() => null)) as {
|
|
50
|
+
error?: string
|
|
51
|
+
} | null
|
|
52
|
+
|
|
53
|
+
return payload?.error ?? `Deployment failed (HTTP ${response.status})`
|
|
54
|
+
}
|
|
@@ -1,177 +1,218 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
1
|
+
import type {
|
|
2
|
+
AppShell,
|
|
3
|
+
CustomComponentDefinition,
|
|
4
|
+
Page,
|
|
5
|
+
VisualBuildRuntimeConfig,
|
|
6
|
+
} from '../types'
|
|
7
|
+
|
|
8
|
+
export interface ProjectSchema {
|
|
9
|
+
meta: {
|
|
10
|
+
projectName: string
|
|
11
|
+
framework: string
|
|
12
|
+
styling: string
|
|
13
|
+
}
|
|
14
|
+
appShell: AppShell
|
|
15
|
+
pages: Page[]
|
|
16
|
+
runtimeProjectId?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ImportedProjectPage = Page
|
|
20
|
+
|
|
21
|
+
export interface GeneratorDiagnostic {
|
|
22
|
+
severity: 'error'
|
|
23
|
+
phase: 'generate'
|
|
24
|
+
code: string
|
|
25
|
+
title: string
|
|
26
|
+
message: string
|
|
27
|
+
path: string
|
|
28
|
+
suggestion?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class ProjectSaveError extends Error {
|
|
32
|
+
diagnostic?: GeneratorDiagnostic
|
|
33
|
+
|
|
34
|
+
constructor(message: string, diagnostic?: GeneratorDiagnostic) {
|
|
35
|
+
super(message)
|
|
36
|
+
this.name = 'ProjectSaveError'
|
|
37
|
+
this.diagnostic = diagnostic
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getGeneratorDiagnostic(error: unknown) {
|
|
42
|
+
return error instanceof ProjectSaveError ? error.diagnostic : undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createProjectSchema(
|
|
46
|
+
pages: Page[],
|
|
47
|
+
appShell: AppShell,
|
|
48
|
+
projectName = 'my-app'
|
|
49
|
+
): ProjectSchema {
|
|
50
|
+
return {
|
|
51
|
+
meta: {
|
|
52
|
+
projectName,
|
|
53
|
+
framework: 'react',
|
|
54
|
+
styling: 'tailwind',
|
|
55
|
+
},
|
|
56
|
+
appShell,
|
|
57
|
+
pages,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function loadProjectSchema(): Promise<ProjectSchema | null> {
|
|
62
|
+
const response = await fetch('/__visualbuild/pages')
|
|
63
|
+
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const schema = (await response.json()) as ProjectSchema
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
...schema,
|
|
72
|
+
runtimeProjectId:
|
|
73
|
+
response.headers.get('X-VisualBuild-Project-Id') ?? undefined,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function loadProjectConfig(): Promise<{
|
|
78
|
+
customComponents: CustomComponentDefinition[]
|
|
79
|
+
} & VisualBuildRuntimeConfig> {
|
|
80
|
+
const response = await fetch('/__visualbuild/config')
|
|
81
|
+
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
const payload = (await response.json().catch(() => null)) as {
|
|
84
|
+
error?: string
|
|
85
|
+
} | null
|
|
86
|
+
throw new Error(payload?.error ?? 'Failed to load visualbuild.config.ts')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (await response.json()) as {
|
|
90
|
+
customComponents: CustomComponentDefinition[]
|
|
91
|
+
} & VisualBuildRuntimeConfig
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function saveProjectSchema(
|
|
95
|
+
pages: Page[],
|
|
96
|
+
appShell: AppShell,
|
|
97
|
+
projectName: string
|
|
98
|
+
) {
|
|
99
|
+
const response = await fetch('/__visualbuild/pages', {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: {
|
|
102
|
+
'Content-Type': 'application/json',
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify(createProjectSchema(pages, appShell, projectName), null, 2),
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
const responseBody = await response.text()
|
|
109
|
+
let payload: {
|
|
110
|
+
error?: string
|
|
111
|
+
diagnostic?: GeneratorDiagnostic
|
|
112
|
+
} | null = null
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
payload = JSON.parse(responseBody) as {
|
|
116
|
+
error?: string
|
|
117
|
+
diagnostic?: GeneratorDiagnostic
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// Older servers may still return a plain error string.
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
throw new ProjectSaveError(
|
|
124
|
+
payload?.error ||
|
|
125
|
+
responseBody ||
|
|
126
|
+
'Failed to save VisualBuild project',
|
|
127
|
+
payload?.diagnostic
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
notifyProjectFilesChanged()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function importProjectPage(
|
|
135
|
+
path: string,
|
|
136
|
+
name: string,
|
|
137
|
+
route: string
|
|
138
|
+
) {
|
|
139
|
+
const response = await fetch('/__visualbuild/import-page', {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: {
|
|
142
|
+
'Content-Type': 'application/json',
|
|
143
|
+
},
|
|
144
|
+
body: JSON.stringify({ path, name, route }),
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
const payload = (await response.json().catch(() => null)) as {
|
|
149
|
+
error?: string
|
|
150
|
+
} | null
|
|
151
|
+
throw new Error(payload?.error ?? 'Failed to import page source')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return (await response.json()) as ImportedProjectPage
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function registerProjectComponent(path: string) {
|
|
158
|
+
const response = await fetch('/__visualbuild/register-component', {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
headers: {
|
|
161
|
+
'Content-Type': 'application/json',
|
|
162
|
+
},
|
|
163
|
+
body: JSON.stringify({ path }),
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
if (!response.ok) {
|
|
167
|
+
const payload = (await response.json().catch(() => null)) as {
|
|
168
|
+
error?: string
|
|
169
|
+
} | null
|
|
170
|
+
throw new Error(payload?.error ?? 'Failed to register component source')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return (await response.json()) as CustomComponentDefinition
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function syncTailwindClasses(pages: Page[], appShell: AppShell) {
|
|
177
|
+
const classNames = collectProjectClassNames(pages, appShell)
|
|
178
|
+
const response = await fetch('/__visualbuild/tailwind', {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: {
|
|
181
|
+
'Content-Type': 'text/plain',
|
|
182
|
+
},
|
|
183
|
+
body: classNames.join('\n'),
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
(await response.text()) || 'Failed to compile Tailwind classes'
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function collectProjectClassNames(pages: Page[], appShell: AppShell) {
|
|
194
|
+
const classNames = new Set<string>()
|
|
195
|
+
const visitNode = (node: Page['root']) => {
|
|
196
|
+
const value = String(node.props.className ?? '').trim()
|
|
197
|
+
|
|
198
|
+
if (value) {
|
|
199
|
+
classNames.add(value)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
node.children.forEach(visitNode)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (appShell.header) visitNode(appShell.header)
|
|
206
|
+
if (appShell.footer) visitNode(appShell.footer)
|
|
207
|
+
|
|
208
|
+
pages.forEach((page) => {
|
|
209
|
+
visitNode(page.root)
|
|
210
|
+
page.components.forEach(visitNode)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
return [...classNames]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function notifyProjectFilesChanged() {
|
|
217
|
+
window.dispatchEvent(new Event('visualbuild:filesystem-changed'))
|
|
218
|
+
}
|