@prestack/thread-ui 0.1.8 → 0.1.10
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/CHANGELOG.md +364 -0
- package/dist/ui/attach.js +1 -1
- package/dist/ui/attach.ts +1 -1
- package/dist/ui/compositions.d.ts +6 -0
- package/dist/ui/compositions.js +69 -16
- package/dist/ui/compositions.ts +75 -17
- package/dist/ui/generators/dev.js +15 -11
- package/dist/ui/generators/dev.ts +13 -11
- package/dist/ui/generators/routes.js +78 -12
- package/dist/ui/generators/routes.ts +95 -14
- package/dist/ui/variant.d.ts +1 -3
- package/dist/ui/variant.ts +1 -4
- package/package.json +2 -2
|
@@ -3,12 +3,13 @@ import { argv, stderr, stdout } from 'node:process'
|
|
|
3
3
|
|
|
4
4
|
import ts from 'typescript'
|
|
5
5
|
|
|
6
|
-
import { existsSync, readFileSync, realpathSync, writeFileSync } from '@prestack/thread/integrations/disk'
|
|
6
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from '@prestack/thread/integrations/disk'
|
|
7
7
|
import { exit, invoked } from '@prestack/thread/core/invoked'
|
|
8
|
-
import { valuesIn } from '@prestack/thread/core/rows'
|
|
8
|
+
import { listBounds, nestedBounds, rowsIn, valuesIn, withField, withRow, withoutField, withoutRow } from '@prestack/thread/core/rows'
|
|
9
9
|
import { named as packageOf } from '@prestack/thread/core/scope'
|
|
10
10
|
import { findTree, inTree } from '@prestack/thread/core/tree'
|
|
11
11
|
import { PERMITS_AT } from '@prestack/thread/operations/access'
|
|
12
|
+
import { said } from '@prestack/thread/operations/attach'
|
|
12
13
|
import { ACTOR_AT, COMPILED } from '@prestack/thread/operations/capabilities'
|
|
13
14
|
|
|
14
15
|
import { compiledAt, nameOf as compositionOf } from '../compositions.ts'
|
|
@@ -36,6 +37,15 @@ const GRAMMAR = `${COMPILED}/ui/islands/map.ts`
|
|
|
36
37
|
|
|
37
38
|
const MOVES = 'navigateTo'
|
|
38
39
|
|
|
40
|
+
const PAGE_ROW = 'page'
|
|
41
|
+
|
|
42
|
+
const WRITER = 'thread routes'
|
|
43
|
+
|
|
44
|
+
const PAGE_SAID =
|
|
45
|
+
'The page a browser gets at the address of this screen: `/` when it is the first page this app declares, and its own ' +
|
|
46
|
+
'name otherwise. `thread routes` writes the page and this row from where the screen sits, and moves or deletes both ' +
|
|
47
|
+
'when that changes.'
|
|
48
|
+
|
|
39
49
|
function served(at: string): boolean {
|
|
40
50
|
try {
|
|
41
51
|
const held = JSON.parse(readFileSync(join(at, 'package.json'), 'utf8')) as {
|
|
@@ -386,6 +396,51 @@ function page(name: string, at: string, route: string): string {
|
|
|
386
396
|
].join('\n')
|
|
387
397
|
}
|
|
388
398
|
|
|
399
|
+
interface Repaged {
|
|
400
|
+
readonly id: string
|
|
401
|
+
readonly shown: string
|
|
402
|
+
readonly route: string | undefined
|
|
403
|
+
readonly was: string | undefined
|
|
404
|
+
readonly where: string | undefined
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const pageShaped = (path: string): boolean => path === pageAt(`/${path.split('/').slice(1, -1).join('/')}`)
|
|
408
|
+
|
|
409
|
+
function unpaged(one: Repaged): string {
|
|
410
|
+
if (one.where === undefined) {
|
|
411
|
+
return (
|
|
412
|
+
`\`${one.id}\` is presented as a ${one.shown}, so it has no page, and its row still derives ${String(one.was)}. ` +
|
|
413
|
+
`Run \`${WRITER}\`, which takes that row out and deletes the page.`
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
return (
|
|
417
|
+
`\`${one.id}\` answers ${String(one.route)}, so its page is ${one.where}, and its row ` +
|
|
418
|
+
`${one.was === undefined ? 'derives no page' : `says ${one.was}`}. Run \`${WRITER}\`, which writes that row from ` +
|
|
419
|
+
'where the screen now sits and puts the page there.'
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function repaged(text: string, moved: readonly Repaged[]): string | undefined {
|
|
424
|
+
let next = text
|
|
425
|
+
for (const one of moved) {
|
|
426
|
+
const list = listBounds(next, 'declares')
|
|
427
|
+
const inside = list === undefined ? undefined : nestedBounds(next, list, one.id, 'derives')
|
|
428
|
+
if (inside !== undefined) next = withoutRow(next, inside, PAGE_ROW)
|
|
429
|
+
const rows = listBounds(next, 'declares')
|
|
430
|
+
if (rows === undefined) return undefined
|
|
431
|
+
const within = nestedBounds(next, rows, one.id, 'derives')
|
|
432
|
+
if (one.where === undefined) {
|
|
433
|
+
if (within !== undefined && rowsIn(next, within).length === 0) next = withoutField(next, rows, one.id, 'derives')
|
|
434
|
+
continue
|
|
435
|
+
}
|
|
436
|
+
const row = said({ id: PAGE_ROW, at: one.where, describes: PAGE_SAID, version: 1, by: WRITER, runs: `${WRITER} --check` }, ' ')
|
|
437
|
+
const put = within === undefined ? withField(next, rows, one.id, 'derives', `[\n${row}\n ]`) : withRow(next, within, row)
|
|
438
|
+
if (put === undefined) return undefined
|
|
439
|
+
next = put
|
|
440
|
+
}
|
|
441
|
+
return next
|
|
442
|
+
}
|
|
443
|
+
|
|
389
444
|
function gate(typed: boolean): readonly string[] {
|
|
390
445
|
const said = typed ? ' as { of?: Record<string, string> }' : ''
|
|
391
446
|
return [
|
|
@@ -647,6 +702,7 @@ async function run(): Promise<number> {
|
|
|
647
702
|
const entry = entryAmong(rows, shownOf)
|
|
648
703
|
|
|
649
704
|
const made: { readonly path: string; readonly text: string }[] = []
|
|
705
|
+
const moved: Repaged[] = []
|
|
650
706
|
const routes = new Map<string, string | undefined>()
|
|
651
707
|
const screens: MapIsland['screens'][number][] = []
|
|
652
708
|
const reaches = reachable(at)
|
|
@@ -658,7 +714,7 @@ async function run(): Promise<number> {
|
|
|
658
714
|
stderr.write(
|
|
659
715
|
`routes: ${row.at} states \`route: '${typed}'\`, and a route is never typed: the first page an app declares ` +
|
|
660
716
|
'answers `/`, every other page answers its own name, and a sheet has no route. Take `route:` out of the ' +
|
|
661
|
-
|
|
717
|
+
`file with \`thread edit ${row.at} --find <text> --replace <text>\`, which changes that one stretch of it.\n`,
|
|
662
718
|
)
|
|
663
719
|
return 1
|
|
664
720
|
}
|
|
@@ -700,20 +756,18 @@ async function run(): Promise<number> {
|
|
|
700
756
|
entry: row.id === entry,
|
|
701
757
|
shows: [...shows],
|
|
702
758
|
})
|
|
703
|
-
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
if (
|
|
707
|
-
stderr.write(
|
|
708
|
-
`routes: \`${row.id}\` answers ${route}, so its page is ${where}, and its row ` +
|
|
709
|
-
`${stated === undefined ? 'derives no page' : `says ${stated}`}. Run \`thread create screen edit\` on it — ` +
|
|
710
|
-
'the row is rewritten from where the screen now lives.\n',
|
|
711
|
-
)
|
|
712
|
-
return 1
|
|
713
|
-
}
|
|
759
|
+
const where = route === undefined ? undefined : pageAt(route)
|
|
760
|
+
const was = (row.derives ?? []).find((one) => one.id === PAGE_ROW)?.at
|
|
761
|
+
if (was !== where) moved.push({ id: row.id, shown: shownAs, route, was, where })
|
|
762
|
+
if (route === undefined || where === undefined) continue
|
|
714
763
|
made.push({ path: where, text: page(name, row.at, route) })
|
|
715
764
|
}
|
|
716
765
|
|
|
766
|
+
if (moved.length > 0 && argv.includes('--check')) {
|
|
767
|
+
for (const one of moved) stderr.write(`routes: ${unpaged(one)}\n`)
|
|
768
|
+
return 1
|
|
769
|
+
}
|
|
770
|
+
|
|
717
771
|
const { links, wrong } = linksOf(at, rows, compositions, routes, wordingFirst(keyProps(at)))
|
|
718
772
|
if (wrong.length > 0) {
|
|
719
773
|
for (const one of wrong) stderr.write(`routes: ${one}\n`)
|
|
@@ -749,6 +803,33 @@ async function run(): Promise<number> {
|
|
|
749
803
|
const paged = map.screens.filter((one) => one.path !== undefined).length
|
|
750
804
|
const counted = `${String(map.screens.length)} screen(s), ${String(paged)} route(s), ${String(map.links.length)} link(s)`
|
|
751
805
|
|
|
806
|
+
if (moved.length > 0) {
|
|
807
|
+
const declaredIn = relative(at, screensAt)
|
|
808
|
+
const rewritten = repaged(readFileSync(screensAt, 'utf8'), moved)
|
|
809
|
+
if (rewritten === undefined) {
|
|
810
|
+
stderr.write(
|
|
811
|
+
`routes: ${declaredIn} could not be given the page row of each screen, so nothing was written. ` +
|
|
812
|
+
`\`thread read ${declaredIn}\` prints what it states now.\n`,
|
|
813
|
+
)
|
|
814
|
+
return 1
|
|
815
|
+
}
|
|
816
|
+
writeFileSync(screensAt, rewritten, 'utf8')
|
|
817
|
+
const answered = new Set(made.map((one) => one.path))
|
|
818
|
+
const home = dirname(join(at, pageAt('/')))
|
|
819
|
+
for (const one of moved) {
|
|
820
|
+
stdout.write(
|
|
821
|
+
one.where === undefined
|
|
822
|
+
? `routes: \`${one.id}\` has no route, so its row no longer derives ${String(one.was)}\n`
|
|
823
|
+
: `routes: \`${one.id}\` answers ${String(one.route)}, so its row now derives ${one.where}\n`,
|
|
824
|
+
)
|
|
825
|
+
if (one.was === undefined || answered.has(one.was) || !pageShaped(one.was) || !existsSync(join(at, one.was))) continue
|
|
826
|
+
rmSync(join(at, one.was))
|
|
827
|
+
const folder = dirname(join(at, one.was))
|
|
828
|
+
if (folder !== home && readdirSync(folder).length === 0) rmSync(folder, { recursive: true, force: true })
|
|
829
|
+
stdout.write(`routes: deleted ${one.was}, because no screen answers there any more\n`)
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
752
833
|
const refused = landed(at, made)
|
|
753
834
|
if (refused !== undefined) return refused
|
|
754
835
|
|
package/dist/ui/variant.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { FREE, type Material, type Setting } from './material.ts';
|
|
2
|
-
import type { MATERIALS } from './materials.ts';
|
|
3
2
|
import type { Holds, Token, Wearing } from './token.ts';
|
|
4
3
|
import { holds } from './signals.ts';
|
|
5
4
|
interface Stateful {
|
|
@@ -10,7 +9,6 @@ interface Stateful {
|
|
|
10
9
|
}[];
|
|
11
10
|
}
|
|
12
11
|
type SignalId = (typeof holds)[number]['id'] | (string & {});
|
|
13
|
-
type MaterialId = (typeof MATERIALS)['declares'][number]['id'] | (string & {});
|
|
14
12
|
declare const BOUND: {
|
|
15
13
|
readonly background: readonly ['colour', 'gradient', 'texture', 'noise', 'img'];
|
|
16
14
|
readonly borderColor: readonly ['colour'];
|
|
@@ -69,7 +67,7 @@ type VariantSetting = {
|
|
|
69
67
|
export interface Variant {
|
|
70
68
|
readonly id: string;
|
|
71
69
|
readonly version?: number;
|
|
72
|
-
readonly material?:
|
|
70
|
+
readonly material?: string;
|
|
73
71
|
readonly type?: string;
|
|
74
72
|
readonly icon?: string;
|
|
75
73
|
readonly sets: readonly VariantSetting[];
|
package/dist/ui/variant.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { BOUND as MATERIAL_BOUND, compose, composed, FREE, type Material, type Setting } from './material.ts'
|
|
2
|
-
import type { MATERIALS } from './materials.ts'
|
|
3
2
|
import type { Holds, Token, Wearing } from './token.ts'
|
|
4
3
|
import { coexist, holds, isSignal, selectorOf, signalNames } from './signals.ts'
|
|
5
4
|
|
|
@@ -14,8 +13,6 @@ interface Stateful {
|
|
|
14
13
|
|
|
15
14
|
type SignalId = (typeof holds)[number]['id'] | (string & {})
|
|
16
15
|
|
|
17
|
-
type MaterialId = (typeof MATERIALS)['declares'][number]['id'] | (string & {})
|
|
18
|
-
|
|
19
16
|
const BOUND = {
|
|
20
17
|
background: ['colour', 'gradient', 'texture', 'noise', 'img'],
|
|
21
18
|
borderColor: ['colour'],
|
|
@@ -87,7 +84,7 @@ export interface Variant {
|
|
|
87
84
|
|
|
88
85
|
readonly version?: number
|
|
89
86
|
|
|
90
|
-
readonly material?:
|
|
87
|
+
readonly material?: string
|
|
91
88
|
|
|
92
89
|
readonly type?: string
|
|
93
90
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prestack/thread-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -17,6 +17,6 @@
|
|
|
17
17
|
"react-dom": "^19.0.0",
|
|
18
18
|
"typescript": "^6.0.0",
|
|
19
19
|
"zod": "^4.5.4",
|
|
20
|
-
"@prestack/thread": "^0.1.
|
|
20
|
+
"@prestack/thread": "^0.1.21"
|
|
21
21
|
}
|
|
22
22
|
}
|