@alstar/studio 0.0.0-beta.15 → 0.0.0-beta.18
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/block.ts +0 -14
- package/components/AdminPanel.ts +11 -5
- package/components/BlockFieldRenderer.ts +26 -20
- package/components/BlockRenderer.ts +4 -4
- package/components/Entries.ts +1 -1
- package/components/Entry.ts +13 -7
- package/components/FieldRenderer.ts +14 -8
- package/components/LivePreview.ts +37 -0
- package/components/Render.ts +8 -3
- package/components/SiteLayout.ts +1 -4
- package/components/fields/Markdown.ts +10 -3
- package/components/fields/Reference.ts +71 -0
- package/components/fields/Slug.ts +6 -6
- package/components/fields/Text.ts +13 -8
- package/components/fields/index.ts +2 -1
- package/components/icons.ts +3 -0
- package/components/settings/ApiKeys.ts +4 -4
- package/components/settings/Backup.ts +3 -3
- package/components/settings/Users.ts +1 -1
- package/index.ts +11 -10
- package/package.json +5 -6
- package/pages/entry/[id].ts +7 -1
- package/pages/error.ts +7 -6
- package/pages/login.ts +1 -1
- package/pages/register.ts +2 -2
- package/public/studio/css/admin-panel.css +27 -9
- package/public/studio/css/blocks-field.css +25 -0
- package/public/studio/css/entry-page.css +4 -0
- package/public/studio/css/entry.css +35 -0
- package/public/studio/css/field.css +14 -0
- package/public/studio/css/live-preview.css +25 -0
- package/public/studio/css/settings.css +4 -0
- package/public/studio/js/live-preview.js +26 -0
- package/public/studio/js/markdown-editor.js +6 -0
- package/public/studio/js/sortable-list.js +6 -4
- package/public/studio/main.css +11 -12
- package/public/studio/main.js +1 -0
- package/queries/block.ts +127 -105
- package/queries/index.ts +3 -2
- package/schemas.ts +1 -1
- package/types.ts +51 -75
- package/utils/define.ts +3 -1
- package/utils/get-or-create-row.ts +2 -1
- package/utils/refresher.ts +56 -0
- package/utils/renderSSE.ts +8 -3
- package/utils/startup-log.ts +4 -4
- package/queries/block-2.ts +0 -339
- package/queries/db-types.ts +0 -15
- package/queries/getBlockTrees-2.ts +0 -71
- package/queries/getBlocks.ts +0 -214
- package/queries/structure-types.ts +0 -97
- package/utils/buildBlocksTree.ts +0 -44
package/queries/block.ts
CHANGED
|
@@ -2,97 +2,69 @@ import { db } from '@alstar/db'
|
|
|
2
2
|
import { sql } from '../utils/sql.ts'
|
|
3
3
|
import { type DBBlockResult } from '../types.ts'
|
|
4
4
|
|
|
5
|
-
function buildForest(blocks: DBBlockResult[]): DBBlockResult[] {
|
|
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
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
roots.forEach(sortChildren)
|
|
57
|
-
|
|
58
|
-
return roots[0]
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function transformBlocksTree(
|
|
62
|
-
block: DBBlockResult,
|
|
63
|
-
isBlocksChild?: boolean,
|
|
64
|
-
): DBBlockResult {
|
|
65
|
-
const fields: Record<string, DBBlockResult> = {}
|
|
66
|
-
let hasFields = false
|
|
67
|
-
|
|
68
|
-
for (const child of block.children ?? []) {
|
|
69
|
-
const transformedChild = transformBlocksTree(
|
|
70
|
-
child,
|
|
71
|
-
child.type === 'blocks',
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
if (!isBlocksChild) {
|
|
75
|
-
hasFields = true
|
|
76
|
-
fields[transformedChild.name] = transformedChild
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if(hasFields) {
|
|
81
|
-
block.fields = fields
|
|
82
|
-
}
|
|
5
|
+
// function buildForest(blocks: DBBlockResult[]): DBBlockResult[] {
|
|
6
|
+
// const map = new Map<number, DBBlockResult>()
|
|
7
|
+
// const roots: DBBlockResult[] = []
|
|
8
|
+
|
|
9
|
+
// for (const block of blocks) {
|
|
10
|
+
// block.blocks = []
|
|
11
|
+
// map.set(block.id, block)
|
|
12
|
+
// }
|
|
13
|
+
|
|
14
|
+
// for (const block of blocks) {
|
|
15
|
+
// if (block.parent_id === null) {
|
|
16
|
+
// roots.push(block)
|
|
17
|
+
// } else {
|
|
18
|
+
// const parent = map.get(block.parent_id)
|
|
19
|
+
// if (parent) parent.blocks!.push(block)
|
|
20
|
+
// }
|
|
21
|
+
// }
|
|
22
|
+
|
|
23
|
+
// // Sort blocks by sort_order recursively
|
|
24
|
+
// const sortChildren = (node: DBBlockResult) => {
|
|
25
|
+
// node.blocks!.sort((a, b) => a.sort_order - b.sort_order)
|
|
26
|
+
// node.blocks!.forEach(sortChildren)
|
|
27
|
+
// }
|
|
28
|
+
// roots.forEach(sortChildren)
|
|
29
|
+
|
|
30
|
+
// return roots
|
|
31
|
+
// }
|
|
32
|
+
|
|
33
|
+
// function transformBlocksTree(
|
|
34
|
+
// block: DBBlockResult,
|
|
35
|
+
// isBlocksChild?: boolean,
|
|
36
|
+
// ): DBBlockResult {
|
|
37
|
+
// const fields: Record<string, DBBlockResult> = {}
|
|
38
|
+
// let hasFields = false
|
|
39
|
+
|
|
40
|
+
// for (const child of block.blocks ?? []) {
|
|
41
|
+
// const transformedChild = transformBlocksTree(
|
|
42
|
+
// child,
|
|
43
|
+
// child.type === 'blocks',
|
|
44
|
+
// )
|
|
45
|
+
|
|
46
|
+
// if (!isBlocksChild) {
|
|
47
|
+
// hasFields = true
|
|
48
|
+
// fields[transformedChild.name] = transformedChild
|
|
49
|
+
// }
|
|
50
|
+
// }
|
|
51
|
+
|
|
52
|
+
// if(hasFields) {
|
|
53
|
+
// block.fields = fields
|
|
54
|
+
// }
|
|
83
55
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
56
|
+
// if (!isBlocksChild) {
|
|
57
|
+
// delete block.blocks
|
|
58
|
+
// } else {
|
|
59
|
+
// delete block.fields
|
|
60
|
+
// }
|
|
89
61
|
|
|
90
|
-
|
|
91
|
-
}
|
|
62
|
+
// return block
|
|
63
|
+
// }
|
|
92
64
|
|
|
93
|
-
function transformForest(blocks: DBBlockResult[]): DBBlockResult[] {
|
|
94
|
-
|
|
95
|
-
}
|
|
65
|
+
// function transformForest(blocks: DBBlockResult[]): DBBlockResult[] {
|
|
66
|
+
// return blocks.map((block) => transformBlocksTree(block))
|
|
67
|
+
// }
|
|
96
68
|
|
|
97
69
|
function rootQuery(filterSql: string, depthLimit?: number) {
|
|
98
70
|
const depthLimitClause =
|
|
@@ -218,24 +190,72 @@ function buildFilterSql(params: Record<string, any>) {
|
|
|
218
190
|
return { filterSql, sqlParams }
|
|
219
191
|
}
|
|
220
192
|
|
|
221
|
-
export function roots(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
): DBBlockResult[] | [] {
|
|
227
|
-
|
|
193
|
+
// export function roots(
|
|
194
|
+
// params: Record<string, any>,
|
|
195
|
+
// options?: {
|
|
196
|
+
// depth?: number
|
|
197
|
+
// },
|
|
198
|
+
// ): DBBlockResult[] | [] {
|
|
199
|
+
// const { filterSql, sqlParams } = buildFilterSql(params)
|
|
200
|
+
|
|
201
|
+
// const query = rootQuery(filterSql, options?.depth)
|
|
202
|
+
// const rows = db.database
|
|
203
|
+
// .prepare(query)
|
|
204
|
+
// .all(sqlParams) as unknown as DBBlockResult[]
|
|
205
|
+
|
|
206
|
+
// if (!rows.length) return []
|
|
207
|
+
|
|
208
|
+
// const forest = buildForest(rows)
|
|
209
|
+
|
|
210
|
+
// return transformForest(forest)
|
|
211
|
+
// }
|
|
212
|
+
|
|
213
|
+
type DBRow = {
|
|
214
|
+
id: number
|
|
215
|
+
created_at: string
|
|
216
|
+
updated_at: string
|
|
217
|
+
name: string
|
|
218
|
+
label: string
|
|
219
|
+
type: string
|
|
220
|
+
sort_order: number
|
|
221
|
+
value: string | null
|
|
222
|
+
options: string | null
|
|
223
|
+
status: 'enabled' | 'disabled'
|
|
224
|
+
parent_id: number | null
|
|
225
|
+
depth: number
|
|
226
|
+
}
|
|
228
227
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
228
|
+
type TODO = any
|
|
229
|
+
|
|
230
|
+
function buildTree2(items: DBRow[]): TODO | null {
|
|
231
|
+
const map = new Map<number, TODO>();
|
|
233
232
|
|
|
234
|
-
|
|
233
|
+
// First pass: clone items into map
|
|
234
|
+
for (const item of items) {
|
|
235
|
+
map.set(item.id, { ...item });
|
|
236
|
+
}
|
|
235
237
|
|
|
236
|
-
|
|
238
|
+
let root: TODO | null = null;
|
|
237
239
|
|
|
238
|
-
|
|
240
|
+
// Second pass: assign blocks to parents
|
|
241
|
+
for (const item of map.values()) {
|
|
242
|
+
if (item.parent_id === null) {
|
|
243
|
+
root = item; // Root node
|
|
244
|
+
} else {
|
|
245
|
+
const parent = map.get(item.parent_id);
|
|
246
|
+
if (parent) {
|
|
247
|
+
if(parent.type === 'blocks') {
|
|
248
|
+
if (!parent.blocks) parent.blocks = [];
|
|
249
|
+
parent.blocks.push(item);
|
|
250
|
+
} else {
|
|
251
|
+
if (!parent.fields) parent.fields = {};
|
|
252
|
+
parent.fields[item.name] = item;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return root;
|
|
239
259
|
}
|
|
240
260
|
|
|
241
261
|
export function root(
|
|
@@ -247,13 +267,15 @@ export function root(
|
|
|
247
267
|
const query = rootQuery(filterSql, options?.depth)
|
|
248
268
|
const rows = db.database
|
|
249
269
|
.prepare(query)
|
|
250
|
-
.all(sqlParams) as unknown as
|
|
270
|
+
.all(sqlParams) as unknown as DBRow[]
|
|
251
271
|
|
|
252
272
|
if (!rows.length) return null
|
|
253
273
|
|
|
254
|
-
const tree = buildTree(rows)
|
|
274
|
+
// const tree = buildTree(rows)
|
|
275
|
+
|
|
276
|
+
const tree = buildTree2(rows)
|
|
255
277
|
|
|
256
|
-
return transformBlocksTree(tree)
|
|
278
|
+
return tree // transformBlocksTree(tree)
|
|
257
279
|
}
|
|
258
280
|
|
|
259
281
|
export function block(params: Record<string, any>) {
|
package/queries/index.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export * as query from './block
|
|
2
|
-
export { getBlockTrees } from './getBlockTrees.ts'
|
|
1
|
+
export * as query from './block.ts'
|
|
2
|
+
export { getBlockTrees } from './getBlockTrees.ts'
|
|
3
|
+
export { blockWithChildren } from './block-with-children.ts'
|
package/schemas.ts
CHANGED
package/types.ts
CHANGED
|
@@ -7,51 +7,20 @@ import {
|
|
|
7
7
|
BlockInstance,
|
|
8
8
|
FieldInstance,
|
|
9
9
|
} from './utils/define.ts'
|
|
10
|
+
import { type HtmlEscapedString } from 'hono/utils/html'
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
export type DeepReadonly<T> =
|
|
13
|
-
T extends (...args: any[]) => any // functions stay as-is
|
|
14
|
-
? T
|
|
15
|
-
: T extends any[] // arrays/tuples
|
|
16
|
-
? { [K in keyof T]: DeepReadonly<T[K]> }
|
|
17
|
-
: T extends object // objects
|
|
18
|
-
? { [K in keyof T]: DeepReadonly<T[K]> }
|
|
19
|
-
: T; // primitives
|
|
20
|
-
|
|
21
|
-
export type PrimitiveField = {
|
|
22
|
-
name: string
|
|
23
|
-
label: string
|
|
24
|
-
type: 'text' | 'slug' | 'markdown' | 'image'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export type BlockField = {
|
|
28
|
-
name: string
|
|
29
|
-
label: string
|
|
30
|
-
type: 'blocks'
|
|
31
|
-
children: Record<string, Field | Block>
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export type Field = PrimitiveField | BlockField
|
|
35
|
-
|
|
36
|
-
export type Block = {
|
|
37
|
-
name: string
|
|
38
|
-
label: string
|
|
39
|
-
type: string
|
|
40
|
-
fields: Record<string, Field | Block>
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type Structure = Record<string, BlockDefStructure>
|
|
44
|
-
// export type Structure = Record<string, BlockDefStructure>
|
|
12
|
+
export type BlockStatus = 'enabled' | 'disabled'
|
|
45
13
|
|
|
46
|
-
|
|
47
|
-
type FieldType = 'text' | 'slug' | 'markdown' | 'image'
|
|
14
|
+
type FieldType = 'text' | 'slug' | 'markdown' | 'image' | 'reference'
|
|
48
15
|
|
|
49
16
|
interface BaseField {
|
|
50
17
|
label: string
|
|
51
18
|
type: FieldType
|
|
52
19
|
description?: string
|
|
20
|
+
presentation?: 'svg'
|
|
53
21
|
}
|
|
54
22
|
|
|
23
|
+
// text fields
|
|
55
24
|
interface TextField extends BaseField {
|
|
56
25
|
type: 'text' | 'slug' | 'markdown'
|
|
57
26
|
}
|
|
@@ -60,6 +29,7 @@ interface TextFieldStructure extends TextField {
|
|
|
60
29
|
instanceOf: typeof FieldInstance
|
|
61
30
|
}
|
|
62
31
|
|
|
32
|
+
// image field
|
|
63
33
|
interface ImageField extends BaseField {
|
|
64
34
|
type: 'image'
|
|
65
35
|
}
|
|
@@ -68,46 +38,49 @@ interface ImageFieldStructure extends ImageField {
|
|
|
68
38
|
instanceOf: typeof FieldInstance
|
|
69
39
|
}
|
|
70
40
|
|
|
41
|
+
// reference fields
|
|
42
|
+
interface ReferenceField extends BaseField {
|
|
43
|
+
type: 'reference'
|
|
44
|
+
to: string | string[]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ReferenceFieldStructure extends ReferenceField {
|
|
48
|
+
instanceOf: typeof FieldInstance
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type FieldDef = TextField | ImageField | ReferenceField
|
|
52
|
+
export type FieldDefStructure = TextFieldStructure | ImageFieldStructure | ReferenceFieldStructure
|
|
53
|
+
|
|
54
|
+
// blocks fields
|
|
71
55
|
export interface BlocksFieldDef {
|
|
72
56
|
label: string
|
|
73
|
-
type: 'blocks'
|
|
74
57
|
description?: string
|
|
75
|
-
|
|
58
|
+
blocks: Record<string, BlockDefStructure | FieldDefStructure>
|
|
76
59
|
}
|
|
77
60
|
|
|
78
61
|
export interface BlocksFieldDefStructure extends BlocksFieldDef {
|
|
79
62
|
instanceOf: typeof BlockFieldInstance
|
|
80
63
|
}
|
|
81
64
|
|
|
82
|
-
export type
|
|
83
|
-
export type FieldDefStructure = TextFieldStructure | ImageFieldStructure
|
|
65
|
+
export type BlockFields = Record<string, FieldDefStructure | BlocksFieldDefStructure>
|
|
84
66
|
|
|
85
|
-
|
|
86
|
-
|
|
67
|
+
// block
|
|
68
|
+
export type BlockDef<T extends BlockFields> = {
|
|
87
69
|
type: string
|
|
88
|
-
|
|
70
|
+
label: string
|
|
89
71
|
description?: string
|
|
72
|
+
preview?: {
|
|
73
|
+
field: keyof T
|
|
74
|
+
} | {
|
|
75
|
+
slug: string
|
|
76
|
+
},
|
|
77
|
+
fields: T,
|
|
90
78
|
}
|
|
91
79
|
|
|
92
|
-
export
|
|
80
|
+
export type BlockDefStructure = BlockDef<BlockFields> & {
|
|
93
81
|
instanceOf: typeof BlockInstance
|
|
94
82
|
}
|
|
95
83
|
|
|
96
|
-
// type DBDefaults = {
|
|
97
|
-
// id: number
|
|
98
|
-
// created_at: string
|
|
99
|
-
// updated_at: string
|
|
100
|
-
// name: string
|
|
101
|
-
// label: string
|
|
102
|
-
// // type: string
|
|
103
|
-
// sort_order: number
|
|
104
|
-
// value: string
|
|
105
|
-
// options: string | null
|
|
106
|
-
// status: 'enabled' | 'disabled'
|
|
107
|
-
// parent_id: number | null
|
|
108
|
-
// depth: number
|
|
109
|
-
// }
|
|
110
|
-
|
|
111
84
|
type BaseDBResult = {
|
|
112
85
|
id: number
|
|
113
86
|
created_at: string
|
|
@@ -117,7 +90,7 @@ type BaseDBResult = {
|
|
|
117
90
|
sort_order: number
|
|
118
91
|
value: string | null
|
|
119
92
|
options: any
|
|
120
|
-
status:
|
|
93
|
+
status: BlockStatus
|
|
121
94
|
parent_id: number | null
|
|
122
95
|
depth: number
|
|
123
96
|
}
|
|
@@ -127,31 +100,34 @@ export type DBPrimitiveFieldResult = BaseDBResult & {
|
|
|
127
100
|
}
|
|
128
101
|
|
|
129
102
|
export type DBBlockFieldResult = BaseDBResult & {
|
|
130
|
-
|
|
131
|
-
children: DBBlockResult[]
|
|
103
|
+
blocks: DBBlockResult[]
|
|
132
104
|
}
|
|
133
105
|
|
|
134
106
|
export type DBBlockResult = BaseDBResult & {
|
|
135
107
|
type: string
|
|
136
|
-
fields: Record<string,
|
|
108
|
+
fields: Record<string, DBResult>
|
|
137
109
|
}
|
|
138
110
|
|
|
139
|
-
export type
|
|
140
|
-
|
|
141
|
-
export type DBBlock = Block & {
|
|
142
|
-
id: number
|
|
143
|
-
created_at: string
|
|
144
|
-
updated_at: string
|
|
145
|
-
value: string | null
|
|
146
|
-
sort_order: number | null
|
|
147
|
-
parent_id: number | null
|
|
148
|
-
options: number | null
|
|
149
|
-
}
|
|
111
|
+
export type DBResult = DBPrimitiveFieldResult | DBBlockFieldResult | DBBlockResult
|
|
150
112
|
|
|
151
|
-
export type
|
|
113
|
+
export type Structure = Record<string, BlockDefStructure>
|
|
152
114
|
|
|
153
115
|
export type StudioConfig = {
|
|
116
|
+
siteName: string
|
|
117
|
+
admin?: {
|
|
118
|
+
logo?: HtmlEscapedString | Promise<HtmlEscapedString>
|
|
119
|
+
}
|
|
120
|
+
honoConfig: HonoOptions<BlankEnv>
|
|
121
|
+
fileBasedRouter: boolean,
|
|
122
|
+
port: number
|
|
123
|
+
structure: Structure
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export type StudioConfigInput = {
|
|
154
127
|
siteName?: string
|
|
128
|
+
admin?: {
|
|
129
|
+
logo?: HtmlEscapedString | Promise<HtmlEscapedString>
|
|
130
|
+
}
|
|
155
131
|
honoConfig?: HonoOptions<BlankEnv>
|
|
156
132
|
fileBasedRouter?: boolean,
|
|
157
133
|
port?: number
|
package/utils/define.ts
CHANGED
|
@@ -30,7 +30,9 @@ export function defineBlockField(
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export function defineBlock(block: types.BlockDef): types.
|
|
33
|
+
export function defineBlock<const O extends types.BlockFields>(block: types.BlockDef<O>): types.BlockDef<O> & {
|
|
34
|
+
instanceOf: typeof BlockInstance;
|
|
35
|
+
} {
|
|
34
36
|
return { ...block, instanceOf: BlockInstance }
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
BlocksFieldDefStructure,
|
|
6
6
|
FieldDefStructure,
|
|
7
7
|
} from '../types.ts'
|
|
8
|
+
import { BlockFieldInstance } from './define.ts'
|
|
8
9
|
|
|
9
10
|
export function getOrCreateRow(props: {
|
|
10
11
|
parentId: string | number | null
|
|
@@ -32,7 +33,7 @@ export function getOrCreateRow(props: {
|
|
|
32
33
|
const change = db.insertInto('blocks', {
|
|
33
34
|
name: name?.toString(),
|
|
34
35
|
label: field.label?.toString(),
|
|
35
|
-
type: field.type?.toString(),
|
|
36
|
+
type: field.instanceOf === BlockFieldInstance ? 'blocks' : field.type?.toString(),
|
|
36
37
|
sort_order: sortOrder,
|
|
37
38
|
parent_id: parentId,
|
|
38
39
|
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { type Context } from 'hono'
|
|
4
|
+
import { html } from './html.ts'
|
|
5
|
+
|
|
6
|
+
let resolvers = new Set<(value: unknown) => void>()
|
|
7
|
+
|
|
8
|
+
async function setupRefreshPromise() {
|
|
9
|
+
return new Promise(resolve => {
|
|
10
|
+
resolvers.add(resolve)
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function requestRefresh() {
|
|
15
|
+
resolvers.forEach(resolve => resolve(true))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const refresher = ({ root, exclude }: { root: string, exclude: string }) => {
|
|
19
|
+
const watcher = fs.watch(path.resolve(root), { recursive: true })
|
|
20
|
+
|
|
21
|
+
watcher.on('change', () => requestRefresh())
|
|
22
|
+
|
|
23
|
+
process.on('exit', () => requestRefresh())
|
|
24
|
+
process.on('SIGHUP', () => process.exit(128 + 1))
|
|
25
|
+
process.on('SIGINT', () => process.exit(128 + 2))
|
|
26
|
+
process.on('SIGTERM', () => process.exit(128 + 15))
|
|
27
|
+
|
|
28
|
+
return async (c: Context) => {
|
|
29
|
+
await setupRefreshPromise()
|
|
30
|
+
|
|
31
|
+
return c.text('')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const refreshClient = (port: number) => html`<script defer type="module">
|
|
36
|
+
function reload() {
|
|
37
|
+
const retry = async () => {
|
|
38
|
+
if (await fetch('http://localhost:${port}').catch(() => false))
|
|
39
|
+
window.location.reload()
|
|
40
|
+
else requestAnimationFrame(retry)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
retry()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(
|
|
47
|
+
'%c REFRESHER ACTIVE ',
|
|
48
|
+
'color: green; background: lightgreen; border-radius: 2px'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const response = await fetch('http://localhost:${port}/refresh').catch(
|
|
52
|
+
() => false
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
reload()
|
|
56
|
+
</script>`
|
package/utils/renderSSE.ts
CHANGED
|
@@ -4,10 +4,14 @@ import { type SSEStreamingApi } from 'hono/streaming'
|
|
|
4
4
|
import { type Context } from 'hono'
|
|
5
5
|
|
|
6
6
|
export const renderSSE = async (stream: SSEStreamingApi, c: Context) => {
|
|
7
|
-
const
|
|
7
|
+
const componentPaths = c.req.header('render')
|
|
8
8
|
const props = c.req.header('props')
|
|
9
9
|
|
|
10
|
-
if (
|
|
10
|
+
if (!componentPaths) return
|
|
11
|
+
|
|
12
|
+
for (const componentPath of componentPaths.split(' ')) {
|
|
13
|
+
if (!componentPath) return
|
|
14
|
+
|
|
11
15
|
try {
|
|
12
16
|
const partialToRender = await import(
|
|
13
17
|
path.join('../', 'components', componentPath + '.ts')
|
|
@@ -18,8 +22,9 @@ export const renderSSE = async (stream: SSEStreamingApi, c: Context) => {
|
|
|
18
22
|
|
|
19
23
|
await stream.writeSSE({
|
|
20
24
|
event: 'datastar-patch-elements',
|
|
21
|
-
data: `elements ${
|
|
25
|
+
data: component.split('\n').map((line: string) => `elements ${line}\n`).join(''),
|
|
22
26
|
})
|
|
27
|
+
|
|
23
28
|
} catch (error) {
|
|
24
29
|
console.log(error)
|
|
25
30
|
}
|
package/utils/startup-log.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import packageJSON from '../package.json' with { type: 'json' }
|
|
2
2
|
|
|
3
|
-
export default ({ port
|
|
3
|
+
export default ({ port }: { port: number }) => {
|
|
4
4
|
console.log('\x1b[32m%s\x1b[0m', '╭───────────────╮')
|
|
5
5
|
console.log('\x1b[32m%s\x1b[0m', '│ Alstar Studio │')
|
|
6
6
|
console.log('\x1b[32m%s\x1b[0m', '╰───────────────╯')
|
|
@@ -13,7 +13,7 @@ export default ({ port, refresherPort }: { port: number, refresherPort: number }
|
|
|
13
13
|
console.log(' ')
|
|
14
14
|
console.log('\x1b[32m%s\x1b[0m', `Studio:`)
|
|
15
15
|
console.log(`http://localhost:${port}/studio`)
|
|
16
|
-
console.log(' ')
|
|
17
|
-
console.log('\x1b[32m%s\x1b[0m', `Refresher:`)
|
|
18
|
-
console.log(`http://localhost:${refresherPort}`)
|
|
16
|
+
// console.log(' ')
|
|
17
|
+
// console.log('\x1b[32m%s\x1b[0m', `Refresher:`)
|
|
18
|
+
// console.log(`http://localhost:${refresherPort}`)
|
|
19
19
|
}
|