@iyulab/u-board-designer 0.1.0 → 0.2.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/dist/lib/authoring/CanvasAuthoring.js +10 -2
- package/dist/lib/authoring/DataPanel.js +2 -2
- package/dist/lib/authoring/board-layout.js +3 -3
- package/dist/lib/authoring/scene-mapping.js +3 -2
- package/dist/lib/board/rulebook.d.ts +2 -0
- package/dist/lib/board/rulebook.js +25 -13
- package/dist/lib/labels-ko.js +1 -0
- package/dist/lib/labels.d.ts +2 -0
- package/dist/lib/labels.js +1 -0
- package/package.json +3 -3
|
@@ -12,9 +12,17 @@ import { documentExtent } from '@iyulab/u-board/toolkit';
|
|
|
12
12
|
import { sizedNodes } from '@iyulab/u-board/toolkit';
|
|
13
13
|
import { useFittedView } from '@iyulab/u-board/toolkit';
|
|
14
14
|
import { ViewControls } from '@iyulab/u-board/toolkit';
|
|
15
|
-
import { TOOLBAR_STYLE, GROUP_STYLE } from '@iyulab/u-board/toolkit';
|
|
15
|
+
import { TOOLBAR_STYLE, GROUP_STYLE, mayLoadBackground } from '@iyulab/u-board/toolkit';
|
|
16
16
|
import { ModeSwitch } from './editor-parts.js';
|
|
17
17
|
import { AttributionLine, boardAttributions, showsExampleData } from '@iyulab/u-board/toolkit';
|
|
18
|
+
/** The note that a background picture is left out — in the warning colours, with or without the stylesheet. */
|
|
19
|
+
const BACKGROUND_BLOCKED_STYLE = {
|
|
20
|
+
padding: '2px 8px',
|
|
21
|
+
borderRadius: 4,
|
|
22
|
+
background: 'var(--_ub-warning-bg, var(--ub-warning-bg, #fef3c7))',
|
|
23
|
+
color: 'var(--_ub-warning, var(--ub-warning, #92400e))',
|
|
24
|
+
fontWeight: 600,
|
|
25
|
+
};
|
|
18
26
|
/**
|
|
19
27
|
* The canvas of a board being edited, drawn from its picture: in Edit, a canvas-kit `KonvaDesigner` moves, sizes,
|
|
20
28
|
* selects and deletes the parts — each widget, rendered through the same path a viewer uses (`resolveDocument` +
|
|
@@ -141,7 +149,7 @@ export function CanvasAuthoring({ doc, updateDoc, mode, onModeChange, documentAc
|
|
|
141
149
|
const paneStyle = followsContainer
|
|
142
150
|
? { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column' }
|
|
143
151
|
: { display: 'flex', flexDirection: 'column' };
|
|
144
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "ub-authoring__toolbar", style: TOOLBAR_STYLE, children: [_jsxs("div", { className: "ub-authoring__group", style: GROUP_STYLE, children: [parts.connect && (_jsx("button", { type: "button", className: "ub-action", onClick: handleConnect, disabled: !pair, title: pair ? undefined : labels.connectHint, children: labels.addConnector })), _jsx("button", { type: "button", className: "ub-action", onClick: handleDelete, disabled: selectedIds.length === 0, children: labels.deleteSelected })] }), documentActions, _jsx(ModeSwitch, { mode: mode, onEdit: () => onModeChange('edit'), onView: handleViewMode, labels: labels }), _jsx("div", { className: "ub-authoring__group", style: GROUP_STYLE, children: _jsx(ViewControls, { view: view, onFit: extent ? () => fitTo(extent) : undefined, labels: labels }) })] }), _jsxs("div", { className: "ub-authoring__panes", style: { display: 'flex', gap: 'var(--ub-space-4, 16px)', flex: 1, minHeight: 0 }, children: [_jsxs("div", { className: "ub-authoring__pane", style: paneStyle, children: [mode === 'edit' ? (_jsx("div", { className: "ub-authoring__surface", "data-appearance": doc.appearance ?? 'light', style: { flex: 1, minHeight: 0 }, role: "presentation", onKeyDown: handleEditorKey, children: _jsx(KonvaDesigner, { ref: designerRef, width: width, height: height, scene: scene, transform: view.transform, onTransformChange: view.onUserTransform, onViewportResize: view.onViewportResize, onSceneChange: handleSceneChange, onSelectionChange: handleSelectionChange, overlays: editorOverlays, ariaLabel: labels.editorRegion }) })) : preview ? (_jsx("div", { className: "ub-authoring__surface", "data-appearance": doc.appearance ?? 'light', style: { flex: 1, minHeight: 0 }, children: _jsx(Viewer, { width: width, height: height, scene: preview.scene, overlays: preview.overlays, transform: view.transform, onTransformChange: view.onUserTransform, onViewportResize: view.onViewportResize, ariaLabel: labels.previewRegion }) })) : (_jsx("p", { className: "ub-authoring__status", children: labels.resolving })), mode === 'view' && preview && _jsx(AttributionLine, { attributions: attributions, example: example, labels: labels })] }), _jsx("div", { className: "ub-authoring__panel", style: { flex: '0 0 var(--ub-panel-width, 320px)', minWidth: 0, overflowY: 'auto' }, children: selectedIds.length > 1 ? (_jsx("p", { className: "ub-panel__hint", children: labels.multipleSelected.replace('{count}', String(selectedIds.length)) })) : ((parts.panel(selectedIds[0] ?? null) ?? _jsx("p", { className: "ub-panel__hint", children: labels.layoutOnlyHint }))) })] })] }));
|
|
152
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "ub-authoring__toolbar", style: TOOLBAR_STYLE, children: [_jsxs("div", { className: "ub-authoring__group", style: GROUP_STYLE, children: [parts.connect && (_jsx("button", { type: "button", className: "ub-action", onClick: handleConnect, disabled: !pair, title: pair ? undefined : labels.connectHint, children: labels.addConnector })), _jsx("button", { type: "button", className: "ub-action", onClick: handleDelete, disabled: selectedIds.length === 0, children: labels.deleteSelected })] }), documentActions, _jsx(ModeSwitch, { mode: mode, onEdit: () => onModeChange('edit'), onView: handleViewMode, labels: labels }), _jsx("div", { className: "ub-authoring__group", style: GROUP_STYLE, children: _jsx(ViewControls, { view: view, onFit: extent ? () => fitTo(extent) : undefined, labels: labels }) }), doc.background.image && !mayLoadBackground(doc.background.image.src) && (_jsx("span", { role: "status", style: BACKGROUND_BLOCKED_STYLE, children: labels.backgroundBlocked }))] }), _jsxs("div", { className: "ub-authoring__panes", style: { display: 'flex', gap: 'var(--ub-space-4, 16px)', flex: 1, minHeight: 0 }, children: [_jsxs("div", { className: "ub-authoring__pane", style: paneStyle, children: [mode === 'edit' ? (_jsx("div", { className: "ub-authoring__surface", "data-appearance": doc.appearance ?? 'light', style: { flex: 1, minHeight: 0 }, role: "presentation", onKeyDown: handleEditorKey, children: _jsx(KonvaDesigner, { ref: designerRef, width: width, height: height, scene: scene, transform: view.transform, onTransformChange: view.onUserTransform, onViewportResize: view.onViewportResize, onSceneChange: handleSceneChange, onSelectionChange: handleSelectionChange, overlays: editorOverlays, ariaLabel: labels.editorRegion }) })) : preview ? (_jsx("div", { className: "ub-authoring__surface", "data-appearance": doc.appearance ?? 'light', style: { flex: 1, minHeight: 0 }, children: _jsx(Viewer, { width: width, height: height, scene: preview.scene, overlays: preview.overlays, transform: view.transform, onTransformChange: view.onUserTransform, onViewportResize: view.onViewportResize, ariaLabel: labels.previewRegion }) })) : (_jsx("p", { className: "ub-authoring__status", children: labels.resolving })), mode === 'view' && preview && _jsx(AttributionLine, { attributions: attributions, example: example, labels: labels })] }), _jsx("div", { className: "ub-authoring__panel", style: { flex: '0 0 var(--ub-panel-width, 320px)', minWidth: 0, overflowY: 'auto' }, children: selectedIds.length > 1 ? (_jsx("p", { className: "ub-panel__hint", children: labels.multipleSelected.replace('{count}', String(selectedIds.length)) })) : ((parts.panel(selectedIds[0] ?? null) ?? _jsx("p", { className: "ub-panel__hint", children: labels.layoutOnlyHint }))) })] })] }));
|
|
145
153
|
}
|
|
146
154
|
const NODE_IN_PLACE_STYLE = { position: 'relative', width: '100%', height: '100%' };
|
|
147
155
|
// Above the node's box, so it never covers the widget; the tag is a hint, not part of the board.
|
|
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { FIELD_STYLE } from './field-style.js';
|
|
3
3
|
import { DEFAULT_DESIGNER_LABELS } from '../labels.js';
|
|
4
4
|
import { useState } from 'react';
|
|
5
|
-
import { ERROR_STYLE, GROUP_STYLE } from '@iyulab/u-board/toolkit';
|
|
5
|
+
import { ERROR_STYLE, GROUP_STYLE, isRelativeRule } from '@iyulab/u-board/toolkit';
|
|
6
6
|
import { MappingEditor } from './MappingEditor.js';
|
|
7
7
|
/** Whether new data may take `name`: a name parts can address (`<data>[<key>]`), not one taken. */
|
|
8
8
|
const usable = (name, taken) => /^[\w-]+$/.test(name.trim()) && !Object.hasOwn(taken, name.trim());
|
|
@@ -87,7 +87,7 @@ function DataRead({ name, query, sources, look, statuses, labels, set, setField,
|
|
|
87
87
|
const fields = query.fields ?? {};
|
|
88
88
|
const known = sources.some(s => s.id === query.from) ? sources : [...sources, { id: query.from }];
|
|
89
89
|
const text = (label, value, change) => (_jsxs("label", { className: "ub-panel__field", style: FIELD_STYLE, children: [label, _jsx("input", { type: "text", value: value ?? '', onChange: e => change(e.target.value) })] }));
|
|
90
|
-
return (_jsxs("fieldset", { className: "ub-panel__group", style: { minWidth: 0 }, children: [_jsx("legend", { children: _jsx("code", { children: name }) }), _jsxs("label", { className: "ub-panel__field", style: FIELD_STYLE, children: [labels.dataFrom, _jsx("select", { value: query.from, onChange: e => set({ from: e.target.value }), children: known.map(source => (_jsx("option", { value: source.id, children: source.label ?? source.id }, source.id))) })] }), query.ref !== undefined ? (_jsx("p", { className: "ub-panel__hint", children: labels.dataByRef })) : (_jsxs(_Fragment, { children: [text(labels.dataPath, query.path, path => set({ path })), text(labels.dataList, query.list, list => set({ list })), text(labels.dataKey, query.key, key => set({ key })), _jsx(ReadDetails, { query: query, labels: labels, set: set })] })), _jsxs("fieldset", { className: "ub-panel__group", style: { minWidth: 0 }, children: [_jsx("legend", { children: labels.dataFields }), Object.entries(fields).map(([fieldName, f]) => (_jsxs("fieldset", { className: "ub-panel__group", style: { minWidth: 0 }, children: [_jsx("legend", { children: _jsx("code", { children: fieldName }) }), text(labels.dataFieldLabel, f.label, label => setField(fieldName, { label })), text(labels.dataFieldUnit, f.unit, unit => setField(fieldName, { unit })), _jsxs("label", { className: "ub-panel__field", style: FIELD_STYLE, children: [labels.dataFieldDigits, _jsx("input", { type: "number", min: 0, step: 1, value: f.digits ?? '', onChange: e => setField(fieldName, { digits: e.target.value === '' ? undefined : Number(e.target.value) }) })] }), text(labels.dataFieldFormat, f.format, format => setField(fieldName, { format })), _jsx(MappingEditor, { legend: labels.dataFieldState, value: f.state, raw: true, names: statuses, onChange: state => setField(fieldName, { state }), labels: labels }), _jsx(MappingEditor, { legend: labels.dataFieldNames, value: f.names, onChange: names => setField(fieldName, { names: names === 'raw' ? undefined : names }), labels: labels }), _jsx("button", { type: "button", className: "ub-action", onClick: () => setField(fieldName, null), children: labels.dataFieldRemove.replace('{name}', fieldName) })] }, fieldName))), _jsxs("div", { className: "ub-panel__actions", style: GROUP_STYLE, children: [_jsxs("label", { className: "ub-panel__field", style: FIELD_STYLE, children: [labels.dataFieldNew, _jsx("input", { type: "text", value: field, onChange: e => setFieldName(e.target.value) })] }), _jsx("button", { type: "button", className: "ub-action", disabled: !usableField(field, fields), onClick: () => {
|
|
90
|
+
return (_jsxs("fieldset", { className: "ub-panel__group", style: { minWidth: 0 }, children: [_jsx("legend", { children: _jsx("code", { children: name }) }), _jsxs("label", { className: "ub-panel__field", style: FIELD_STYLE, children: [labels.dataFrom, _jsx("select", { value: query.from, onChange: e => set({ from: e.target.value }), children: known.map(source => (_jsx("option", { value: source.id, children: source.label ?? source.id }, source.id))) })] }), query.ref !== undefined ? (_jsx("p", { className: "ub-panel__hint", children: labels.dataByRef })) : (_jsxs(_Fragment, { children: [text(labels.dataPath, query.path, path => set({ path })), text(labels.dataList, query.list, list => set({ list })), text(labels.dataKey, query.key, key => set({ key })), _jsx(ReadDetails, { query: query, labels: labels, set: set })] })), _jsxs("fieldset", { className: "ub-panel__group", style: { minWidth: 0 }, children: [_jsx("legend", { children: labels.dataFields }), Object.entries(fields).map(([fieldName, f]) => (_jsxs("fieldset", { className: "ub-panel__group", style: { minWidth: 0 }, children: [_jsx("legend", { children: _jsx("code", { children: fieldName }) }), text(labels.dataFieldLabel, f.label, label => setField(fieldName, { label })), text(labels.dataFieldUnit, f.unit, unit => setField(fieldName, { unit })), _jsxs("label", { className: "ub-panel__field", style: FIELD_STYLE, children: [labels.dataFieldDigits, _jsx("input", { type: "number", min: 0, step: 1, value: f.digits ?? '', onChange: e => setField(fieldName, { digits: e.target.value === '' ? undefined : Number(e.target.value) }) })] }), text(labels.dataFieldFormat, f.format, format => setField(fieldName, { format })), isRelativeRule(f.state) ? (_jsxs("p", { className: "ub-panel__hint", children: [labels.dataFieldStateRelative, ": ", _jsx("code", { children: JSON.stringify(f.state.relative) })] })) : (_jsx(MappingEditor, { legend: labels.dataFieldState, value: f.state, raw: true, names: statuses, onChange: state => setField(fieldName, { state }), labels: labels })), _jsx(MappingEditor, { legend: labels.dataFieldNames, value: f.names, onChange: names => setField(fieldName, { names: names === 'raw' ? undefined : names }), labels: labels }), _jsx("button", { type: "button", className: "ub-action", onClick: () => setField(fieldName, null), children: labels.dataFieldRemove.replace('{name}', fieldName) })] }, fieldName))), _jsxs("div", { className: "ub-panel__actions", style: GROUP_STYLE, children: [_jsxs("label", { className: "ub-panel__field", style: FIELD_STYLE, children: [labels.dataFieldNew, _jsx("input", { type: "text", value: field, onChange: e => setFieldName(e.target.value) })] }), _jsx("button", { type: "button", className: "ub-action", disabled: !usableField(field, fields), onClick: () => {
|
|
91
91
|
setField(field.trim(), {});
|
|
92
92
|
setFieldName('');
|
|
93
93
|
}, children: labels.dataFieldAdd })] })] }), look && (_jsxs("div", { className: "ub-panel__actions", style: GROUP_STYLE, children: [_jsx("button", { type: "button", className: "ub-action", disabled: found === 'reading', onClick: () => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// else of the board changes.
|
|
5
5
|
import { DEFAULT_NODE_HEIGHT, DEFAULT_NODE_WIDTH } from '@iyulab/u-board/toolkit';
|
|
6
6
|
import { GRID_DEFAULT_SPAN } from '@iyulab/u-board/picture';
|
|
7
|
-
import { isDataPlace } from '@iyulab/u-board/toolkit';
|
|
7
|
+
import { isDataPlace, isFlowPlace } from '@iyulab/u-board/toolkit';
|
|
8
8
|
/**
|
|
9
9
|
* Whether the editor lays this board out: a canvas, or a grid — of parts, canvases, and grids of parts and canvases in
|
|
10
10
|
* its cells (one level, as the picture draws them). A grid in a cell is laid out on its own (`cellBoard`): the picture
|
|
@@ -155,8 +155,8 @@ export function placesItself(board, id) {
|
|
|
155
155
|
const placed = board.layout.place[part];
|
|
156
156
|
if (placed === undefined || rest.length > 0)
|
|
157
157
|
return false;
|
|
158
|
-
// Items placed by their own fields are not dragged:
|
|
159
|
-
return key === undefined ? Array.isArray(placed) : board.parts[part]?.each !== undefined && !Array.isArray(placed) && !isDataPlace(placed);
|
|
158
|
+
// Items placed by their own fields, or by a rule, are not dragged: the fields and the rule say where they are.
|
|
159
|
+
return key === undefined ? Array.isArray(placed) : board.parts[part]?.each !== undefined && !Array.isArray(placed) && !isDataPlace(placed) && !isFlowPlace(placed);
|
|
160
160
|
}
|
|
161
161
|
/**
|
|
162
162
|
* The board with its canvas layout as the picture `after` shows it, the picture having been `before`: each part (or
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Scene } from '@canvas-kit/core';
|
|
2
|
-
import { sizedNodes } from '@iyulab/u-board/toolkit';
|
|
2
|
+
import { mayLoadBackground, sizedNodes } from '@iyulab/u-board/toolkit';
|
|
3
3
|
import { decorationObjects } from '@iyulab/u-board/toolkit';
|
|
4
4
|
import { captionObjects, connectorObjects } from '@iyulab/u-board/toolkit';
|
|
5
5
|
/** The stroke a rect decoration is drawn with in the designer when the document does not set its own — distinct
|
|
@@ -37,7 +37,8 @@ export function documentToScene(doc, movable, selectable) {
|
|
|
37
37
|
: [...doc.nodes, ...doc.connectors, ...(doc.decorations ?? [])]).map(part => part.id));
|
|
38
38
|
// A part not edited here, and every piece of it: drawn, never selected or moved.
|
|
39
39
|
const add = (object) => scene.add(object.id && !own.has(partOf(object.id)) ? { ...object, locked: true } : object);
|
|
40
|
-
|
|
40
|
+
// A picture outside the origins the host allowed is not requested here either: the board is drawn on an empty background.
|
|
41
|
+
if (doc.background.image && mayLoadBackground(doc.background.image.src)) {
|
|
41
42
|
add({
|
|
42
43
|
type: 'image',
|
|
43
44
|
x: 0,
|
|
@@ -3,3 +3,5 @@ import { type Board } from '@iyulab/u-board/domain';
|
|
|
3
3
|
export declare const RULEBOOK_EXAMPLE: Board;
|
|
4
4
|
/** The rulebook, as Markdown. */
|
|
5
5
|
export declare function boardRulebook(): string;
|
|
6
|
+
/** The options every `chart.*` kind takes — the data columns and the like — said once under the table, not on each chart's row. */
|
|
7
|
+
export declare function sharedChartOptions(): string[];
|
|
@@ -49,14 +49,15 @@ ${['board', 'statuses', 'symbols', 'elements', 'data', 'presets', 'parts', 'layo
|
|
|
49
49
|
A data read asks an HTTP connector a \`path\` — the usual read. Only an adapter the host names itself is given its own
|
|
50
50
|
\`ref\` instead (one item, its \`fields\` only, never with \`path\`).
|
|
51
51
|
Parts name a read (\`"pumps"\`, the whole list) or one item of it (\`"pumps[P1]"\`, by its \`key\`). Data stays raw —
|
|
52
|
-
names, units, places and statuses live in \`fields\`, never in the data.
|
|
52
|
+
names, units, places and statuses live in \`fields\`, never in the data. A field's \`at\` points into one item, so with a \`list\` what sits beside it (a count, a total) is out of reach: read that
|
|
53
|
+
with a read of its own and no \`list\` — \`{ from: 'plant', path: '/plant/pumps?limit=1', fields: { total: { at: '/total' } } }\`. With a \`list\`, \`time\` is a field of each item (an item lacking it has no value); the answer's own is \`answerTime\`.
|
|
53
54
|
|
|
54
55
|
${keys('query')}
|
|
55
56
|
|
|
56
57
|
A field is \`{ at?, label?, unit?, digits?, format?, names?, state? }\`; it reads \`/<its name>\` of the item unless \`at\`
|
|
57
58
|
says where. \`state\` says how the value is doing, as a status: \`"raw"\` (the value is a status name), a map
|
|
58
59
|
(\`{ "trip": "down", "*": "off" }\`, \`*\` for the rest) or bands (\`["ok", 1, "warn", 5, "alarm"]\` — the status below the
|
|
59
|
-
first number, then from each number up). \`names\` takes the same two forms for
|
|
60
|
+
first number, then from each number up); a time's \`state\` can follow the clock: \`{ "relative": ["past", -2700, "now", 1, "next", 2700, "later"] }\` — bands over the seconds from now to it (negative: gone by). \`names\` takes the same two forms for what a value is shown as (a word, a count of marks). A field can also be computed from the items of the list: \`derive: { op: count | sum | min | max | avg, field?, where? }\` — a part that reads the list (no item) shows it, a \`state\` on it can raise a status. A field that holds a list of objects says what its items' fields mean with \`items\`.`,
|
|
60
61
|
`## statuses — how each status looks
|
|
61
62
|
|
|
62
63
|
Tones (always there): ${code(TONES)}. A board names its own statuses, each with a \`tone\`, a \`label\`, and optionally a
|
|
@@ -65,30 +66,32 @@ label, glyph. Never write colours for statuses.`,
|
|
|
65
66
|
`## parts — what, of what, which fields
|
|
66
67
|
|
|
67
68
|
\`{ "as": <kind>, "of": "<data>" | "<data>[<key>]", "show": "<field>" | ["<field>", …], "title"?, …options }\`. \`{{field}}\`
|
|
68
|
-
in a title or a label reads a field of \`of
|
|
69
|
+
in a title or a label reads a field of \`of\` (a status card's \`subtitle\` · \`note\` too; header \`kicker\` · \`subtitle\` are fixed text, \`clock\` is \`true\`); \`{{key}}\` is the item's key. \`presets\` hold shared settings a part takes
|
|
69
70
|
with \`use\`; \`each: "<data>"\` makes one part per item (ids \`<part>/<key>\`) — it takes no \`of\`, and is placed once. A \`group\` is several parts on a small
|
|
70
71
|
canvas of their own — its parts and links read the group's \`of\` unless they name their own.
|
|
71
72
|
|
|
72
73
|
The board's own options take fields of what the part reads: a \`status-card\`'s \`meter\` is \`"<field>"\` or
|
|
73
|
-
\`{ "field", "min"?, "max"
|
|
74
|
+
\`{ "field", "min"?, "max"? (a number, or a field of the item), "form"?: "bar" | "ring" }\`, its \`state\` (and a \`stat-list\`'s) the field whose status colours
|
|
74
75
|
it, a \`stat\`'s \`trend\` a field holding its recent values, a \`list\`'s \`primary\` · \`secondary\` · \`trailing\` ·
|
|
75
|
-
\`badge\` · \`time\` fields of each row. Any other option is the widget's own — only those listed.
|
|
76
|
+
\`badge\` · \`time\` fields of each row. A part that reads one item takes \`highlight: "<boolean field>"\`: a ring while it is true, apart from the status. Any other option is the widget's own — only those listed.
|
|
76
77
|
|
|
77
78
|
| kind | shows | reads data | options |
|
|
78
79
|
|---|---|---|---|
|
|
79
|
-
${PART_KINDS.map(kindRow).join('\n')}
|
|
80
|
+
${PART_KINDS.map(kindRow).join('\n')}
|
|
81
|
+
|
|
82
|
+
Every \`chart.*\` also takes ${code(sharedChartOptions())}.`,
|
|
80
83
|
`## layout — where, per view kind
|
|
81
84
|
|
|
82
85
|
- canvas: \`{ "kind": "canvas", "size": [w, h], "background"?, "place": { "<part>": [x, y, w?, h?] }, "shapes"?, "links"? }\` —
|
|
83
86
|
an \`each\` part is placed item by item inside its own entry (\`{ "P1": [x, y, w, h], … }\`), or — when the items change and cannot be
|
|
84
87
|
named — by their own fields: \`"quake": { "at": ["lon", "lat"], "frame": [-180, 90, 180, -90], "size": [14, 14] }\` puts each item
|
|
85
88
|
at the point its two fields say; \`frame\` is the values at the canvas's left, top, right and bottom edges (the canvas's own
|
|
86
|
-
coordinates when left out), \`size\` the box centred on the point (give one for a marker
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
coordinates when left out), \`size\` the box centred on the point (give one for a marker: a dot alone is [14, 14]; one with a name or note draws
|
|
90
|
+
it right of the dot — about [150, 40] — and it is the box, not the dot, that is centred). An item with no number in a field, or
|
|
91
|
+
outside the frame, is not drawn — \`look\` counts them. The data needs a \`key\`; the projection is linear (right for a plan or a flat map). A part is placed one way, not both. Or by a rule: \`"tile": { "flow": { "cell": [w, h], "columns": n, "gap"?, "at"? }, "by"?: "<field>", "boxes"?: { "columns": n, "label"?: "{{key}}" } }\` — items flow in columns, or with \`by\` are grouped into labelled boxes (\`flow.pad\` [sides, top] kept clear inside). A shape is a \`box\` (with \`label\`), a \`text\` at a point,
|
|
89
92
|
a \`line\` through points or a \`path\` (absolute M · L · C · Q · Z).
|
|
90
93
|
- grid: \`{ "kind": "grid", "items": ["<part>", ["<part>", span], { "grid": [ … ], "span": n }, { "canvas": { … }, "span": n }] }\` —
|
|
91
|
-
12 columns; a grid
|
|
94
|
+
12 columns; an \`each\` part's items are cells (\`["pumps", 2]\`: six to a row); a grid in an item stacks what it holds.
|
|
92
95
|
|
|
93
96
|
A link joins two parts:
|
|
94
97
|
|
|
@@ -101,8 +104,10 @@ Use a built-in whenever one shows the thing — a title, a status and a few valu
|
|
|
101
104
|
— every \`{{hole}}\` is a declared prop (\`number\` · \`string\` · \`boolean\` · \`list\` · \`object\`). A part places it with
|
|
102
105
|
\`"as": "element", "element": "tank"\`, fixed \`props\` and \`bind\` (\`{ "level": "<field>", "tone": "<field>.tone" }\`). What markup cannot draw runs as a
|
|
103
106
|
\`"script"\` in place of the template — in a sandboxed frame with no network, its props through \`ub.onProps\`, \`"resources"\` the
|
|
104
|
-
https:// origins it loads from. The look is \`board.skin
|
|
105
|
-
palette
|
|
107
|
+
https:// origins it loads from. The look is \`board.skin\`, once for the board; parts never set fonts or colours:
|
|
108
|
+
\`{ "palette"?: { "paper", "ink", "muted", "rule" }, "font"? · "display"? · "mono"? (font stacks), "frame"?, "radius"? (px), "shadow"?: "none" | "soft" | "hard" }\`
|
|
109
|
+
— a frame is ${code(FRAMES)}; paper is the ground the widgets sit on, ink their text, muted the quieter text, rule their lines; an element's CSS reads them as
|
|
110
|
+
\`var(--u-widget-bg)\` · \`var(--u-widget-text)\` · \`var(--u-widget-text-secondary)\` · \`var(--u-widget-border)\`; a background picture follows the skin when it writes a colour as \`var(--u-widget-bg, #f6f4ef)\` (its own colour is the fallback).`,
|
|
106
111
|
`## triggers — what the board does
|
|
107
112
|
|
|
108
113
|
A trigger is \`{ "on", "when"?, "do": [ … ] }\`. \`on\` a part clicked (\`{ "event": "click", "part" }\`), an event its element
|
|
@@ -128,9 +133,16 @@ ${JSON.stringify(RULEBOOK_EXAMPLE, null, 1)}
|
|
|
128
133
|
\`\`\``,
|
|
129
134
|
].join('\n\n');
|
|
130
135
|
}
|
|
136
|
+
const optionsOf = (k) => [...k.own, ...k.options];
|
|
137
|
+
/** The options every `chart.*` kind takes — the data columns and the like — said once under the table, not on each chart's row. */
|
|
138
|
+
export function sharedChartOptions() {
|
|
139
|
+
const charts = PART_KINDS.filter(k => k.kind.startsWith('chart.'));
|
|
140
|
+
return charts.length === 0 ? [] : optionsOf(charts[0]).filter(option => charts.every(k => optionsOf(k).includes(option)));
|
|
141
|
+
}
|
|
131
142
|
/** A kind's row: its name, what it shows, whether it reads data, and its options — the board's own, then the widget's. */
|
|
132
143
|
function kindRow(k) {
|
|
133
144
|
const reads = { needs: 'yes', may: 'may', no: 'no' }[k.of];
|
|
134
|
-
const
|
|
145
|
+
const shared = k.kind.startsWith('chart.') ? sharedChartOptions() : [];
|
|
146
|
+
const options = optionsOf(k).filter(option => !shared.includes(option));
|
|
135
147
|
return `| \`${k.kind}\` | ${k.shows.replace(/\|/g, '/')} | ${reads} | ${options.length > 0 ? options.map(o => `\`${o}\``).join(' ') : '—'} |`;
|
|
136
148
|
}
|
package/dist/lib/labels-ko.js
CHANGED
|
@@ -131,6 +131,7 @@ export const KO_AUTHORING_LABELS = {
|
|
|
131
131
|
dataFieldFormat: '서식',
|
|
132
132
|
dataFieldRemove: '필드 {name} 지우기',
|
|
133
133
|
dataFieldState: '상태',
|
|
134
|
+
dataFieldStateRelative: '상태가 시계를 따름(지금부터 그 시각까지의 초에 대한 구간) — 보드 JSON에서 고친다',
|
|
134
135
|
dataFieldNames: '낱말',
|
|
135
136
|
mappingNone: '(없음)',
|
|
136
137
|
mappingRaw: '값이 곧 상태 이름',
|
package/dist/lib/labels.d.ts
CHANGED
|
@@ -184,6 +184,8 @@ export interface AuthoringLabels {
|
|
|
184
184
|
dataFieldRemove: string;
|
|
185
185
|
/** A field's status — how its value is doing — and the words it is shown as, as the data panel sets them. */
|
|
186
186
|
dataFieldState: string;
|
|
187
|
+
/** A status that follows the clock (`{ relative: bands }`) — shown as it is; it is written in the board's JSON. */
|
|
188
|
+
dataFieldStateRelative: string;
|
|
187
189
|
dataFieldNames: string;
|
|
188
190
|
mappingNone: string;
|
|
189
191
|
mappingRaw: string;
|
package/dist/lib/labels.js
CHANGED
|
@@ -131,6 +131,7 @@ export const DEFAULT_AUTHORING_LABELS = {
|
|
|
131
131
|
dataFieldFormat: 'Format',
|
|
132
132
|
dataFieldRemove: 'Remove field {name}',
|
|
133
133
|
dataFieldState: 'Status',
|
|
134
|
+
dataFieldStateRelative: 'Status follows the clock (bands over the seconds from now) — written in the board’s JSON',
|
|
134
135
|
dataFieldNames: 'Words',
|
|
135
136
|
mappingNone: '(none)',
|
|
136
137
|
mappingRaw: 'The value is a status name',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iyulab/u-board-designer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The authoring tool for U-Board boards, and the check and rulebook an assistant writes by",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"@canvas-kit/core": "^0.8.0",
|
|
48
48
|
"@canvas-kit/designer": "^0.10.0",
|
|
49
49
|
"@canvas-kit/viewer": "^0.6.1",
|
|
50
|
-
"@iyulab/u-board": "^0.
|
|
51
|
-
"@iyulab/u-widgets": "^0.
|
|
50
|
+
"@iyulab/u-board": "^0.39.0",
|
|
51
|
+
"@iyulab/u-widgets": "^0.45.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@playwright/test": "^1.62.1",
|