@graview/core 0.1.10 → 0.1.14
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 +41 -9
- package/dist/address.d.ts +31 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +53 -0
- package/dist/address.js.map +1 -0
- package/dist/app.d.ts +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/arrange.d.ts +13 -183
- package/dist/arrange.d.ts.map +1 -1
- package/dist/arrange.js +12 -470
- package/dist/arrange.js.map +1 -1
- package/dist/arrangement.d.ts +169 -0
- package/dist/arrangement.d.ts.map +1 -0
- package/dist/arrangement.js +220 -0
- package/dist/arrangement.js.map +1 -0
- package/dist/arranging.d.ts +28 -0
- package/dist/arranging.d.ts.map +1 -0
- package/dist/arranging.js +264 -0
- package/dist/arranging.js.map +1 -0
- package/dist/blocks.d.ts +5 -0
- package/dist/blocks.d.ts.map +1 -0
- package/dist/blocks.js +14 -0
- package/dist/blocks.js.map +1 -0
- package/dist/capabilities.js +1 -1
- package/dist/capabilities.js.map +1 -1
- package/dist/check.d.ts +8 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +19 -0
- package/dist/check.js.map +1 -0
- package/dist/cli/check/brand.d.ts.map +1 -1
- package/dist/cli/check/brand.js +21 -0
- package/dist/cli/check/brand.js.map +1 -1
- package/dist/cli/check/hidden-reads.d.ts +4 -0
- package/dist/cli/check/hidden-reads.d.ts.map +1 -0
- package/dist/cli/check/hidden-reads.js +183 -0
- package/dist/cli/check/hidden-reads.js.map +1 -0
- package/dist/cli/check/index.d.ts +1 -0
- package/dist/cli/check/index.d.ts.map +1 -1
- package/dist/cli/check/index.js +1 -0
- package/dist/cli/check/index.js.map +1 -1
- package/dist/cli/check/lenses.d.ts.map +1 -1
- package/dist/cli/check/lenses.js +29 -1
- package/dist/cli/check/lenses.js.map +1 -1
- package/dist/cli/check/views.d.ts.map +1 -1
- package/dist/cli/check/views.js +5 -1
- package/dist/cli/check/views.js.map +1 -1
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +2 -1
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +1 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/describe.d.ts.map +1 -1
- package/dist/cli/describe.js +23 -1
- package/dist/cli/describe.js.map +1 -1
- package/dist/cli/docs.js +1 -1
- package/dist/cli/docs.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/columns.d.ts +105 -0
- package/dist/columns.d.ts.map +1 -0
- package/dist/columns.js +143 -0
- package/dist/columns.js.map +1 -0
- package/dist/conformance/fixtures.d.ts.map +1 -1
- package/dist/conformance/fixtures.js +19 -1
- package/dist/conformance/fixtures.js.map +1 -1
- package/dist/conformance/index.js +1 -1
- package/dist/conformance/index.js.map +1 -1
- package/dist/describe.d.ts +2 -0
- package/dist/describe.d.ts.map +1 -1
- package/dist/describe.js +7 -0
- package/dist/describe.js.map +1 -1
- package/dist/document/act-findings.d.ts +5 -0
- package/dist/document/act-findings.d.ts.map +1 -0
- package/dist/document/act-findings.js +84 -0
- package/dist/document/act-findings.js.map +1 -0
- package/dist/document/blocks.d.ts +11 -6
- package/dist/document/blocks.d.ts.map +1 -1
- package/dist/document/blocks.js +21 -31
- package/dist/document/blocks.js.map +1 -1
- package/dist/document/compile-checked.d.ts +8 -0
- package/dist/document/compile-checked.d.ts.map +1 -0
- package/dist/document/compile-checked.js +43 -0
- package/dist/document/compile-checked.js.map +1 -0
- package/dist/document/compile.d.ts +22 -9
- package/dist/document/compile.d.ts.map +1 -1
- package/dist/document/compile.js +116 -52
- package/dist/document/compile.js.map +1 -1
- package/dist/document/describe-coverage.d.ts +37 -0
- package/dist/document/describe-coverage.d.ts.map +1 -0
- package/dist/document/describe-coverage.js +162 -0
- package/dist/document/describe-coverage.js.map +1 -0
- package/dist/document/describe-place.d.ts.map +1 -1
- package/dist/document/describe-place.js +43 -1
- package/dist/document/describe-place.js.map +1 -1
- package/dist/document/diff.d.ts +5 -0
- package/dist/document/diff.d.ts.map +1 -1
- package/dist/document/diff.js +18 -4
- package/dist/document/diff.js.map +1 -1
- package/dist/document/edit.d.ts +1 -1
- package/dist/document/edit.d.ts.map +1 -1
- package/dist/document/edit.js +155 -10
- package/dist/document/edit.js.map +1 -1
- package/dist/document/expr/analyze.d.ts.map +1 -1
- package/dist/document/expr/analyze.js +2 -1
- package/dist/document/expr/analyze.js.map +1 -1
- package/dist/document/expr/evaluate.d.ts.map +1 -1
- package/dist/document/expr/evaluate.js +26 -2
- package/dist/document/expr/evaluate.js.map +1 -1
- package/dist/document/expr/parse.d.ts +2 -1
- package/dist/document/expr/parse.d.ts.map +1 -1
- package/dist/document/expr/parse.js +6 -3
- package/dist/document/expr/parse.js.map +1 -1
- package/dist/document/expr/print.d.ts.map +1 -1
- package/dist/document/expr/print.js +4 -2
- package/dist/document/expr/print.js.map +1 -1
- package/dist/document/far-end.d.ts +15 -0
- package/dist/document/far-end.d.ts.map +1 -0
- package/dist/document/far-end.js +26 -0
- package/dist/document/far-end.js.map +1 -0
- package/dist/document/graview-template.d.ts +20 -12
- package/dist/document/graview-template.d.ts.map +1 -1
- package/dist/document/graview-template.js +5 -44
- package/dist/document/graview-template.js.map +1 -1
- package/dist/document/index.d.ts +5 -11
- package/dist/document/index.d.ts.map +1 -1
- package/dist/document/index.js +3 -6
- package/dist/document/index.js.map +1 -1
- package/dist/document/instantiate-template.d.ts +14 -0
- package/dist/document/instantiate-template.d.ts.map +1 -0
- package/dist/document/instantiate-template.js +46 -0
- package/dist/document/instantiate-template.js.map +1 -0
- package/dist/document/migrate.d.ts.map +1 -1
- package/dist/document/migrate.js +7 -5
- package/dist/document/migrate.js.map +1 -1
- package/dist/document/range.d.ts +17 -0
- package/dist/document/range.d.ts.map +1 -0
- package/dist/document/range.js +46 -0
- package/dist/document/range.js.map +1 -0
- package/dist/document/schema.d.ts +34 -2
- package/dist/document/schema.d.ts.map +1 -1
- package/dist/document/schema.js +25 -1
- package/dist/document/schema.js.map +1 -1
- package/dist/document/template.d.ts +18 -2
- package/dist/document/template.d.ts.map +1 -1
- package/dist/document/template.js +27 -9
- package/dist/document/template.js.map +1 -1
- package/dist/document/thumbnail.d.ts +26 -4
- package/dist/document/thumbnail.d.ts.map +1 -1
- package/dist/document/thumbnail.js +165 -50
- package/dist/document/thumbnail.js.map +1 -1
- package/dist/document/to-document.d.ts.map +1 -1
- package/dist/document/to-document.js +18 -2
- package/dist/document/to-document.js.map +1 -1
- package/dist/document/views.d.ts +11 -0
- package/dist/document/views.d.ts.map +1 -1
- package/dist/document/views.js +76 -12
- package/dist/document/views.js.map +1 -1
- package/dist/figures.d.ts +3 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/figures.js +14 -0
- package/dist/figures.js.map +1 -0
- package/dist/index.d.ts +3 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -9
- package/dist/index.js.map +1 -1
- package/dist/mutations/define-mutation.d.ts.map +1 -1
- package/dist/mutations/define-mutation.js +9 -1
- package/dist/mutations/define-mutation.js.map +1 -1
- package/dist/mutations/derive-edits.d.ts +8 -0
- package/dist/mutations/derive-edits.d.ts.map +1 -1
- package/dist/mutations/derive-edits.js +30 -7
- package/dist/mutations/derive-edits.js.map +1 -1
- package/dist/mutations/form.d.ts +2 -0
- package/dist/mutations/form.d.ts.map +1 -1
- package/dist/mutations/form.js +1 -0
- package/dist/mutations/form.js.map +1 -1
- package/dist/mutations/node-ref.d.ts +4 -1
- package/dist/mutations/node-ref.d.ts.map +1 -1
- package/dist/mutations/node-ref.js +6 -0
- package/dist/mutations/node-ref.js.map +1 -1
- package/dist/mutations/types.d.ts +28 -0
- package/dist/mutations/types.d.ts.map +1 -1
- package/dist/places.d.ts +11 -0
- package/dist/places.d.ts.map +1 -1
- package/dist/places.js +19 -1
- package/dist/places.js.map +1 -1
- package/dist/refusal.d.ts.map +1 -1
- package/dist/refusal.js +4 -0
- package/dist/refusal.js.map +1 -1
- package/dist/refused.d.ts +20 -0
- package/dist/refused.d.ts.map +1 -0
- package/dist/refused.js +24 -0
- package/dist/refused.js.map +1 -0
- package/dist/scaffold/domain.js +1 -1
- package/dist/scaffold/from-template.d.ts.map +1 -1
- package/dist/scaffold/from-template.js +3 -3
- package/dist/scaffold/from-template.js.map +1 -1
- package/dist/scaffold/project.d.ts.map +1 -1
- package/dist/scaffold/project.js +7 -0
- package/dist/scaffold/project.js.map +1 -1
- package/dist/scene-districts.d.ts +5 -1
- package/dist/scene-districts.d.ts.map +1 -1
- package/dist/scene-districts.js +11 -4
- package/dist/scene-districts.js.map +1 -1
- package/dist/scene.d.ts +7 -0
- package/dist/scene.d.ts.map +1 -0
- package/dist/scene.js +16 -0
- package/dist/scene.js.map +1 -0
- package/dist/search.d.ts +1 -1
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +1 -1
- package/dist/search.js.map +1 -1
- package/dist/theme/derive.d.ts.map +1 -1
- package/dist/theme/derive.js +5 -4
- package/dist/theme/derive.js.map +1 -1
- package/dist/theme/types.d.ts +9 -0
- package/dist/theme/types.d.ts.map +1 -1
- package/dist/theme/types.js +5 -0
- package/dist/theme/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +21 -1
package/dist/document/edit.js
CHANGED
|
@@ -3,6 +3,8 @@ import { ExprSyntaxError, parseExpr } from "./expr/parse.js";
|
|
|
3
3
|
import { printExpr } from "./expr/print.js";
|
|
4
4
|
import { error } from "./findings.js";
|
|
5
5
|
import { coerce } from "./migrate.js";
|
|
6
|
+
import { outsideRange, rangeWords } from "./range.js";
|
|
7
|
+
import { farEnd } from "./far-end.js";
|
|
6
8
|
import { ActSpec, EDGE_NAME, EdgeSpec, FIELD_NAME, FIELD_TYPES, FieldSpec, KindSpec, NAME, RuleSpec, } from "./schema.js";
|
|
7
9
|
import { SHIPPED_LENSES, isShippedLens, SHIPPED_LENS_NAMES } from "../places.js";
|
|
8
10
|
import { placeSlug } from "../views/types.js";
|
|
@@ -18,6 +20,7 @@ export const EDIT_OPS = [
|
|
|
18
20
|
"rename-field",
|
|
19
21
|
"retype-field",
|
|
20
22
|
"set-options",
|
|
23
|
+
"set-range",
|
|
21
24
|
"set-required",
|
|
22
25
|
"set-default",
|
|
23
26
|
"remove-field",
|
|
@@ -56,6 +59,7 @@ const SHAPES = {
|
|
|
56
59
|
"rename-field": z.object({ op: z.literal("rename-field"), kind: kindName, field: fieldName, to: fieldName }).strict(),
|
|
57
60
|
"retype-field": z.object({ op: z.literal("retype-field"), kind: kindName, field: fieldName, type: z.enum(FIELD_TYPES), options: z.array(z.string().min(1).max(80)).min(1).max(100).optional(), of: z.enum(["string", "number", "date"]).optional(), format: z.enum(["money", "percent", "duration"]).optional() }).strict(),
|
|
58
61
|
"set-options": z.object({ op: z.literal("set-options"), kind: kindName, field: fieldName, add: z.array(z.string().min(1).max(80)).optional(), remove: z.array(z.string().min(1).max(80)).optional() }).strict(),
|
|
62
|
+
"set-range": z.object({ op: z.literal("set-range"), kind: kindName, field: fieldName, min: z.union([z.number(), z.null()]).optional(), max: z.union([z.number(), z.null()]).optional(), step: z.union([z.number().positive(), z.null()]).optional() }).strict(),
|
|
59
63
|
"set-required": z.object({ op: z.literal("set-required"), kind: kindName, field: fieldName, required: z.boolean() }).strict(),
|
|
60
64
|
"set-default": z.object({ op: z.literal("set-default"), kind: kindName, field: fieldName, default: z.unknown() }).strict(),
|
|
61
65
|
"remove-field": z.object({ op: z.literal("remove-field"), kind: kindName, field: fieldName }).strict(),
|
|
@@ -66,7 +70,15 @@ const SHAPES = {
|
|
|
66
70
|
"remove-act": z.object({ op: z.literal("remove-act"), act: actName }).strict(),
|
|
67
71
|
"add-rule": z.object({ op: z.literal("add-rule"), rule: actName, replace: z.boolean().optional() }).passthrough(),
|
|
68
72
|
"remove-rule": z.object({ op: z.literal("remove-rule"), rule: actName }).strict(),
|
|
69
|
-
"set-brand": z
|
|
73
|
+
"set-brand": z
|
|
74
|
+
.object({
|
|
75
|
+
op: z.literal("set-brand"),
|
|
76
|
+
accent: z.union([z.string(), z.null()]).optional(),
|
|
77
|
+
name: z.string().min(1).max(60).optional(),
|
|
78
|
+
currency: z.union([z.string(), z.null()]).optional(),
|
|
79
|
+
locale: z.union([z.string(), z.null()]).optional(),
|
|
80
|
+
})
|
|
81
|
+
.strict(),
|
|
70
82
|
"set-label": z.object({ op: z.literal("set-label"), kind: kindName, field: fieldName.optional(), label: z.union([z.string().min(1).max(300), z.null()]) }).strict(),
|
|
71
83
|
"set-describe": z.object({ op: z.literal("set-describe"), kind: kindName, describe: z.union([z.string().min(1).max(300), z.null()]) }).strict(),
|
|
72
84
|
// A kind's slot; the front page (`slot: "home"`, no kind); or a blocks lens's blocks, by its title (FR-84).
|
|
@@ -148,6 +160,12 @@ function kindsOf(doc, e, ctx) {
|
|
|
148
160
|
return kindsOf(doc, e.set, ctx);
|
|
149
161
|
case "call": {
|
|
150
162
|
const lit = e.args[0]?.t === "lit" && typeof e.args[0].value === "string" ? e.args[0].value : undefined;
|
|
163
|
+
// A walk from every member of a set (FR-101): its relation is its second word.
|
|
164
|
+
const walked = e.args.length === 2 && e.args[1]?.t === "lit" && typeof e.args[1].value === "string" ? e.args[1].value : undefined;
|
|
165
|
+
if (e.fn === "out" && walked)
|
|
166
|
+
return targets(doc, kindsOf(doc, e.args[0], ctx), walked);
|
|
167
|
+
if (e.fn === "in" && walked)
|
|
168
|
+
return sources(doc, walked);
|
|
151
169
|
if (e.fn === "out" && lit)
|
|
152
170
|
return targets(doc, "*", lit);
|
|
153
171
|
if (e.fn === "in" && lit)
|
|
@@ -212,6 +230,12 @@ function mapNames(doc, e, ctx, rename) {
|
|
|
212
230
|
const name = rename({ role: x.fn === "all" ? "kind" : "edge", kinds: NONE, name: literal });
|
|
213
231
|
args = name === literal ? [...x.args] : [{ ...first, value: name }];
|
|
214
232
|
}
|
|
233
|
+
else if ((x.fn === "out" || x.fn === "in") && x.args.length === 2 && x.args[1].t === "lit" && typeof x.args[1].value === "string") {
|
|
234
|
+
// out(S, 'edge'): the set is read where the walk stands, and its relation renamed like any walk's (FR-101).
|
|
235
|
+
const relation = x.args[1];
|
|
236
|
+
const name = rename({ role: "edge", kinds: NONE, name: relation.value });
|
|
237
|
+
args = [go(first, at), name === relation.value ? relation : { ...relation, value: name }];
|
|
238
|
+
}
|
|
215
239
|
else if (((x.fn === "every" || x.fn === "some") && x.args.length === 2) || (x.fn === "sort" && x.args.length >= 2)) {
|
|
216
240
|
// Read once per member, with the member as its subject: its names are the members'.
|
|
217
241
|
args = [go(x.args[0], at), go(x.args[1], kindsOf(doc, x.args[0], at)), ...x.args.slice(2).map((a) => go(a, at))];
|
|
@@ -341,8 +365,8 @@ function proseSwap(from, to) {
|
|
|
341
365
|
}
|
|
342
366
|
// ── what an act touches ──────────────────────────────────────────────────────
|
|
343
367
|
const subjectKinds = (act) => (act.on ? new Set(asArray(act.on)) : NONE);
|
|
344
|
-
/** The kinds each `set` in an act writes to: its subject, a record it made, or (for a `$ref` target) any kind. */
|
|
345
|
-
function setTargets(act) {
|
|
368
|
+
/** The kinds each `set` in an act writes to: its subject, a record it made, the other end of what it connects (FR-115), or (for a `$ref` target) any kind. */
|
|
369
|
+
function setTargets(act, doc) {
|
|
346
370
|
const out = [];
|
|
347
371
|
const made = new Map();
|
|
348
372
|
if (act.creates)
|
|
@@ -353,6 +377,11 @@ function setTargets(act) {
|
|
|
353
377
|
});
|
|
354
378
|
if (act.sets)
|
|
355
379
|
out.push({ set: act.sets, kinds: subjectKinds(act), where: "sets" });
|
|
380
|
+
if (act.setsOther) {
|
|
381
|
+
const relation = act.connects ?? act.severs;
|
|
382
|
+
const end = doc && relation ? farEnd(doc.kinds, relation, asArray(act.on)) : undefined;
|
|
383
|
+
out.push({ set: act.setsOther, kinds: end && end.kinds !== "*" ? new Set(end.kinds) : "*", where: "setsOther" });
|
|
384
|
+
}
|
|
356
385
|
(act.effects ?? []).forEach((e, index) => {
|
|
357
386
|
if (e.create)
|
|
358
387
|
out.push({ set: e.set ?? {}, kinds: new Set([e.create]), where: "create", index });
|
|
@@ -449,6 +478,8 @@ class Editor {
|
|
|
449
478
|
return this.retypeField(i, e);
|
|
450
479
|
case "set-options":
|
|
451
480
|
return this.setOptions(i, e);
|
|
481
|
+
case "set-range":
|
|
482
|
+
return this.setRange(i, e);
|
|
452
483
|
case "set-required": {
|
|
453
484
|
const f = this.field(i, e.kind, e.field);
|
|
454
485
|
if (!f)
|
|
@@ -471,6 +502,9 @@ class Editor {
|
|
|
471
502
|
else {
|
|
472
503
|
if (f.type === "enum" && !(f.options ?? []).includes(e.default))
|
|
473
504
|
return this.fail(i, "default", `"${String(e.default)}" is not one of ${e.field}'s options (${(f.options ?? []).join(", ")})`);
|
|
505
|
+
const outside = outsideRange(e.default, f, e.field);
|
|
506
|
+
if (outside)
|
|
507
|
+
return this.fail(i, "default", outside);
|
|
474
508
|
f.default = e.default;
|
|
475
509
|
this.said.push(`${e.kind}'s ${e.field} starts as ${JSON.stringify(e.default)} on new records.`);
|
|
476
510
|
}
|
|
@@ -499,16 +533,45 @@ class Editor {
|
|
|
499
533
|
return;
|
|
500
534
|
}
|
|
501
535
|
case "set-brand": {
|
|
536
|
+
// The colours and the money (FR-100) are set apart: what an edit does not name stays as it was.
|
|
537
|
+
const brand = { ...(this.doc.brand ?? {}) };
|
|
502
538
|
if (e.accent === null) {
|
|
503
|
-
delete
|
|
539
|
+
delete brand["accent"];
|
|
504
540
|
this.said.push("The app goes back to Graview's colours.");
|
|
505
541
|
}
|
|
506
|
-
else {
|
|
542
|
+
else if (e.accent !== undefined) {
|
|
507
543
|
if (!/^#[0-9a-fA-F]{6}$/.test(e.accent))
|
|
508
544
|
return this.fail(i, "accent", 'an accent is a colour like "#c2577a"');
|
|
509
|
-
|
|
545
|
+
brand["accent"] = e.accent;
|
|
510
546
|
this.said.push(`The app's accent colour becomes ${e.accent}.`);
|
|
511
547
|
}
|
|
548
|
+
if (e.currency === null) {
|
|
549
|
+
delete brand["currency"];
|
|
550
|
+
this.said.push("Money is said with no currency.");
|
|
551
|
+
}
|
|
552
|
+
else if (e.currency !== undefined) {
|
|
553
|
+
if (!/^[A-Z]{3}$/.test(e.currency))
|
|
554
|
+
return this.fail(i, "currency", 'a currency is its three-letter code, like "USD" or "EUR"');
|
|
555
|
+
brand["currency"] = e.currency;
|
|
556
|
+
this.said.push(`Money is said in ${e.currency}.`);
|
|
557
|
+
}
|
|
558
|
+
if (e.locale === null)
|
|
559
|
+
delete brand["locale"];
|
|
560
|
+
else if (e.locale !== undefined) {
|
|
561
|
+
if (!/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(e.locale))
|
|
562
|
+
return this.fail(i, "locale", 'a locale is a language tag, like "en-US" or "de-DE"');
|
|
563
|
+
brand["locale"] = e.locale;
|
|
564
|
+
this.said.push(`Money is written for ${e.locale}.`);
|
|
565
|
+
}
|
|
566
|
+
if (e.name) {
|
|
567
|
+
brand["name"] = e.name;
|
|
568
|
+
this.said.push(`The app's wordmark says ${e.name}.`);
|
|
569
|
+
}
|
|
570
|
+
// A brand that holds only a name holds nothing to apply.
|
|
571
|
+
if (Object.keys(brand).every((key) => key === "name"))
|
|
572
|
+
delete this.doc.brand;
|
|
573
|
+
else
|
|
574
|
+
this.doc.brand = brand;
|
|
512
575
|
return;
|
|
513
576
|
}
|
|
514
577
|
case "set-label": {
|
|
@@ -699,6 +762,8 @@ class Editor {
|
|
|
699
762
|
if (!Array.isArray(e.options?.blocks) || e.options.blocks.length === 0)
|
|
700
763
|
return this.fail(i, "options.blocks", 'a lens drawn from blocks has blocks: "options": {"blocks": [{"headline": "…"}, {"list": "all(\'<kind>\')", "as": "card"}]}');
|
|
701
764
|
}
|
|
765
|
+
if (name === "columns" && !this.columnsHold(i, e.bindings))
|
|
766
|
+
return;
|
|
702
767
|
if (was)
|
|
703
768
|
lenses[at] = lens;
|
|
704
769
|
else
|
|
@@ -721,6 +786,35 @@ class Editor {
|
|
|
721
786
|
}
|
|
722
787
|
this.said.push(retitled ? `The lens "${String(was["title"])}" is now "${e.title}"${where}${follows}.` : `The lens "${e.title}" changes${where}.`);
|
|
723
788
|
}
|
|
789
|
+
/**
|
|
790
|
+
* A STATUS BOARD'S BINDINGS (FR-97), held before it is added: each kind it
|
|
791
|
+
* binds is a kind, and its `column` is a field of that kind whose values
|
|
792
|
+
* are choices — the columns. Refused at the binding's path otherwise, so a
|
|
793
|
+
* chat is told which word to change rather than shown an empty board.
|
|
794
|
+
*/
|
|
795
|
+
columnsHold(i, bindings) {
|
|
796
|
+
const example = '"bindings": {"<kind>": {"column": "<a choice field, like status>"}}';
|
|
797
|
+
if (!isObject(bindings) || Object.keys(bindings).length === 0)
|
|
798
|
+
return (this.fail(i, "bindings", "a board binds a kind's choice field to its columns", example), false);
|
|
799
|
+
for (const [kind, roles] of Object.entries(bindings)) {
|
|
800
|
+
if (!isObject(this.doc.kinds[kind]))
|
|
801
|
+
return (this.fail(i, `bindings.${kind}`, `"${kind}" is not a kind this app has; it has ${Object.keys(this.doc.kinds).join(", ")}`), false);
|
|
802
|
+
const field = isObject(roles) ? roles["column"] : undefined;
|
|
803
|
+
const fields = this.doc.kinds[kind].fields;
|
|
804
|
+
if (typeof field !== "string")
|
|
805
|
+
return (this.fail(i, `bindings.${kind}.column`, `the board does not say which field of ${kind} its columns are`, example), false);
|
|
806
|
+
if (!(field in fields))
|
|
807
|
+
return (this.fail(i, `bindings.${kind}.column`, `${kind} has no field "${field}"; it has ${Object.keys(fields).join(", ")}`), false);
|
|
808
|
+
if (fields[field]["type"] !== "enum") {
|
|
809
|
+
const choices = Object.keys(fields).filter((one) => fields[one]["type"] === "enum");
|
|
810
|
+
return (this.fail(i, `bindings.${kind}.column`, `${kind}'s ${field} is not a choice, so it has no columns`, choices.length > 0 ? `bind one of ${choices.join(", ")}` : `give ${kind} a choice field first: add-field with "type": "enum" and its "options", in the order the columns go`), false);
|
|
811
|
+
}
|
|
812
|
+
const others = Object.keys(roles).filter((role) => role !== "column");
|
|
813
|
+
if (others.length > 0)
|
|
814
|
+
return (this.fail(i, `bindings.${kind}.${others[0]}`, `a board binds only "column"`), false);
|
|
815
|
+
}
|
|
816
|
+
return true;
|
|
817
|
+
}
|
|
724
818
|
removeLens(i, e) {
|
|
725
819
|
const index = this.lensIndex(i, e.title, e.on);
|
|
726
820
|
if (index < 0)
|
|
@@ -1140,12 +1234,43 @@ class Editor {
|
|
|
1140
1234
|
f.format = e.format;
|
|
1141
1235
|
else if (e.type !== "number" && e.type !== "integer")
|
|
1142
1236
|
delete f.format;
|
|
1237
|
+
// A range is a number's (FR-114): a field that is no longer one lets it go.
|
|
1238
|
+
if (e.type !== "number" && e.type !== "integer")
|
|
1239
|
+
for (const key of ["min", "max", "step"])
|
|
1240
|
+
delete f[key];
|
|
1143
1241
|
if (f.default !== undefined && coerce(f.default, was, f) === undefined)
|
|
1144
1242
|
delete f.default;
|
|
1145
1243
|
else if (f.default !== undefined)
|
|
1146
1244
|
f.default = coerce(f.default, was, f);
|
|
1147
1245
|
this.said.push(`${e.kind}'s ${e.field} changes from ${was} to ${e.type}; values that convert are kept.`);
|
|
1148
1246
|
}
|
|
1247
|
+
/** A number's range (FR-114): a key given sets it, `null` clears it, one left out stays. */
|
|
1248
|
+
setRange(i, e) {
|
|
1249
|
+
const f = this.field(i, e.kind, e.field);
|
|
1250
|
+
if (!f)
|
|
1251
|
+
return;
|
|
1252
|
+
if (f.type !== "number" && f.type !== "integer")
|
|
1253
|
+
return this.fail(i, "field", `${e.field} is ${f.type === "integer" ? "an" : "a"} ${f.type}, not a number; a range belongs on a number or integer field`);
|
|
1254
|
+
const next = { ...f };
|
|
1255
|
+
for (const key of ["min", "max", "step"]) {
|
|
1256
|
+
if (e[key] === null)
|
|
1257
|
+
delete next[key];
|
|
1258
|
+
else if (e[key] !== undefined)
|
|
1259
|
+
next[key] = e[key];
|
|
1260
|
+
}
|
|
1261
|
+
if (next.min !== undefined && next.max !== undefined && next.min > next.max)
|
|
1262
|
+
return this.fail(i, "max", `the most (${next.max}) is below the least (${next.min})`);
|
|
1263
|
+
const outside = next.default !== undefined ? outsideRange(next.default, next, e.field) : undefined;
|
|
1264
|
+
if (outside)
|
|
1265
|
+
return this.fail(i, "default", `${e.field}'s default, ${outside.replace(/ is /, ", is ")}; change the default first`);
|
|
1266
|
+
for (const key of ["min", "max", "step"]) {
|
|
1267
|
+
if (next[key] === undefined)
|
|
1268
|
+
delete f[key];
|
|
1269
|
+
else
|
|
1270
|
+
f[key] = next[key];
|
|
1271
|
+
}
|
|
1272
|
+
this.said.push(`${e.kind}'s ${e.field} takes ${rangeWords(f)}.`);
|
|
1273
|
+
}
|
|
1149
1274
|
setOptions(i, e) {
|
|
1150
1275
|
const f = this.field(i, e.kind, e.field);
|
|
1151
1276
|
if (!f)
|
|
@@ -1172,7 +1297,7 @@ class Editor {
|
|
|
1172
1297
|
}
|
|
1173
1298
|
// An act that sets a dropped option cannot stand.
|
|
1174
1299
|
for (const [name, act] of Object.entries(this.doc.acts ?? {})) {
|
|
1175
|
-
for (const t of setTargets(act))
|
|
1300
|
+
for (const t of setTargets(act, this.doc))
|
|
1176
1301
|
if (hasKind(t.kinds, e.kind) && typeof t.set[e.field] === "string" && remove.includes(t.set[e.field])) {
|
|
1177
1302
|
this.removeActQuietly(name);
|
|
1178
1303
|
out.push(`the act ${name} (it set that option) is removed`);
|
|
@@ -1420,6 +1545,8 @@ class Editor {
|
|
|
1420
1545
|
act.connects = r.to;
|
|
1421
1546
|
if (act.severs === r.from)
|
|
1422
1547
|
act.severs = r.to;
|
|
1548
|
+
if (Array.isArray(act.replaces))
|
|
1549
|
+
act.replaces = act.replaces.map((e) => (e === r.from ? r.to : e));
|
|
1423
1550
|
for (const e of act.effects ?? []) {
|
|
1424
1551
|
if (e.connect === r.from)
|
|
1425
1552
|
e.connect = r.to;
|
|
@@ -1429,7 +1556,7 @@ class Editor {
|
|
|
1429
1556
|
}
|
|
1430
1557
|
if (r.t === "field") {
|
|
1431
1558
|
const args = new Map();
|
|
1432
|
-
for (const t of setTargets(act)) {
|
|
1559
|
+
for (const t of setTargets(act, doc)) {
|
|
1433
1560
|
if (!hasKind(t.kinds, r.kind) || !(r.from in t.set))
|
|
1434
1561
|
continue;
|
|
1435
1562
|
if (t.kinds === "*" && Object.entries(doc.kinds).some(([k, s]) => k !== r.kind && s.fields[r.from]))
|
|
@@ -1441,6 +1568,8 @@ class Editor {
|
|
|
1441
1568
|
args.set(r.from, r.to);
|
|
1442
1569
|
if (t.where === "sets")
|
|
1443
1570
|
act.sets = renamed;
|
|
1571
|
+
else if (t.where === "setsOther")
|
|
1572
|
+
act.setsOther = renamed;
|
|
1444
1573
|
else
|
|
1445
1574
|
act.effects[t.index].set = renamed;
|
|
1446
1575
|
}
|
|
@@ -1469,7 +1598,7 @@ class Editor {
|
|
|
1469
1598
|
act.allowedWhen = argRename?.has(r.from) && r.t === "field" && !hasKind(on, r.kind) ? rewriteBinding(guard, r.from, r.to) : guard;
|
|
1470
1599
|
if (act.refusal !== undefined)
|
|
1471
1600
|
act.refusal = tmplAt(act.refusal, on);
|
|
1472
|
-
for (const t of setTargets(act))
|
|
1601
|
+
for (const t of setTargets(act, doc))
|
|
1473
1602
|
for (const [k, v] of Object.entries(t.set))
|
|
1474
1603
|
if (isObject(v) && typeof v["expr"] === "string")
|
|
1475
1604
|
t.set[k] = { expr: exprAt(v["expr"], t.kinds === "*" ? on : t.kinds) };
|
|
@@ -1624,9 +1753,15 @@ class Editor {
|
|
|
1624
1753
|
let uses = exprMentions(doc, act.allowedWhen, on, r) || templateMentions(doc, act.refusal, on, r);
|
|
1625
1754
|
if (r.t === "edge")
|
|
1626
1755
|
uses ||= act.connects === r.from || act.severs === r.from || (act.effects ?? []).some((e) => e.connect === r.from || e.sever === r.from);
|
|
1756
|
+
// A relation it only replaced goes from the list; one that replaced nothing else goes with it (FR-115).
|
|
1757
|
+
if (r.t === "edge" && !uses && Array.isArray(act.replaces) && act.replaces.includes(r.from)) {
|
|
1758
|
+
act.replaces = act.replaces.filter((e) => e !== r.from);
|
|
1759
|
+
if (act.replaces.length === 0)
|
|
1760
|
+
delete act.replaces;
|
|
1761
|
+
}
|
|
1627
1762
|
if (r.t === "field" && !worked) {
|
|
1628
1763
|
// An act that sets other things too loses only this field; one that did nothing else goes.
|
|
1629
|
-
for (const t of setTargets(act)) {
|
|
1764
|
+
for (const t of setTargets(act, doc)) {
|
|
1630
1765
|
if (!hasKind(t.kinds, r.kind) || t.kinds === "*")
|
|
1631
1766
|
continue;
|
|
1632
1767
|
if (r.from in t.set)
|
|
@@ -1646,6 +1781,8 @@ class Editor {
|
|
|
1646
1781
|
delete act.args;
|
|
1647
1782
|
if (act.sets && Object.keys(act.sets).length === 0)
|
|
1648
1783
|
delete act.sets;
|
|
1784
|
+
if (act.setsOther && Object.keys(act.setsOther).length === 0)
|
|
1785
|
+
delete act.setsOther;
|
|
1649
1786
|
const doesSomething = act.creates || act.writes || act.sets || act.connects || act.severs || act.removes || (act.effects ?? []).some((e) => e.create || e.connect || e.sever || e.remove || (e.set && Object.keys(e.set).length > 0));
|
|
1650
1787
|
if (!doesSomething)
|
|
1651
1788
|
uses = true;
|
|
@@ -2064,6 +2201,9 @@ function rewriteBlock(doc, b, ctx, r, kind) {
|
|
|
2064
2201
|
next[key] = rewriteTemplate(doc, next[key], ctx, r);
|
|
2065
2202
|
if (typeof next["figure"] === "string")
|
|
2066
2203
|
next["figure"] = rewriteExpr(doc, next["figure"], ctx, r);
|
|
2204
|
+
// A figure's and a meter's label is a template (FR-99); a field's is words.
|
|
2205
|
+
if ((typeof next["figure"] === "string" || "progress" in next) && typeof next["label"] === "string")
|
|
2206
|
+
next["label"] = rewriteTemplate(doc, next["label"], ctx, r);
|
|
2067
2207
|
if (typeof next["list"] === "string") {
|
|
2068
2208
|
const members = listMembers(doc, next["list"], ctx);
|
|
2069
2209
|
next["list"] = rewriteExpr(doc, next["list"], ctx, r);
|
|
@@ -2111,6 +2251,11 @@ function pruneBlock(doc, b, ctx, r, kind) {
|
|
|
2111
2251
|
for (const k of ["value", "max"])
|
|
2112
2252
|
if (exprMentions(doc, b["progress"][k], ctx, r))
|
|
2113
2253
|
return null;
|
|
2254
|
+
// A label that says what is gone goes; the figure or meter stays (FR-99).
|
|
2255
|
+
if ((typeof b["figure"] === "string" || "progress" in b) && typeof b["label"] === "string" && templateMentions(doc, b["label"], ctx, r)) {
|
|
2256
|
+
const { label: _gone, ...kept } = b;
|
|
2257
|
+
return kept;
|
|
2258
|
+
}
|
|
2114
2259
|
if (typeof b["list"] === "string") {
|
|
2115
2260
|
if (exprMentions(doc, b["list"], ctx, r))
|
|
2116
2261
|
return null;
|