@yanqirenshi/model.deployment 0.0.0-stage → 0.1.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.
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ // UI の操作(ドラッグ・インスペクタの適用)を、保存する patch(JSON Merge Patch)に変換する。
3
+ //
4
+ // 戻り値は { collection, id, patch }。利用側(iHub など)は図の種類を知らずにこれを保存すればよい。
5
+ // 入力の検証に失敗したときは ModelError を投げる(メッセージは利用者にそのまま見せられる日本語)。
6
+ // model.sitemap の patch.ts を元にしている(ラベルは label.text、種類は NODE / COMPONENT、線に矢印の有無がある)。
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.ModelError = void 0;
9
+ exports.collectionOf = collectionOf;
10
+ exports.titleOf = titleOf;
11
+ exports.getByPath = getByPath;
12
+ exports.mergePatch = mergePatch;
13
+ exports.movePatch = movePatch;
14
+ exports.isColor = isColor;
15
+ exports.applyPatch = applyPatch;
16
+ const defaults_js_1 = require("./defaults.js");
17
+ const fields_js_1 = require("./fields.js");
18
+ /** 入力の検証エラー。message は利用者向け */
19
+ class ModelError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = 'ModelError';
23
+ }
24
+ }
25
+ exports.ModelError = ModelError;
26
+ /** レコードを置くコレクション */
27
+ function collectionOf(record) {
28
+ return (0, defaults_js_1.isEdge)(record) ? 'edges' : 'nodes';
29
+ }
30
+ /** インスペクタの見出し。ラベルの文字、無ければ「種類 id」(線は「接続元 → 接続先」) */
31
+ function titleOf(record) {
32
+ if ((0, defaults_js_1.isEdge)(record))
33
+ return `${String(record.from.id)} → ${String(record.to.id)}`;
34
+ const text = record.label && typeof record.label === 'object' ? record.label.text : null;
35
+ if (typeof text === 'string' && text !== '')
36
+ return text;
37
+ return `${record.type || ''} ${String(record.id)}`.trim();
38
+ }
39
+ /** ドット区切りの path で値を取り出す */
40
+ function getByPath(obj, path) {
41
+ return path.split('.').reduce((acc, key) => (acc == null ? acc : acc[key]), obj);
42
+ }
43
+ /**
44
+ * JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
45
+ * null の項目は除く。保存したあと、表示を先に更新するときに使う。
46
+ */
47
+ function mergePatch(record, patch) {
48
+ const out = { ...record };
49
+ for (const [key, value] of Object.entries(patch)) {
50
+ if (value === null) {
51
+ delete out[key];
52
+ }
53
+ else if (typeof value === 'object' && !Array.isArray(value)) {
54
+ const base = out[key];
55
+ out[key] = mergePatch(base && typeof base === 'object' && !Array.isArray(base) ? base : {}, value);
56
+ }
57
+ else {
58
+ out[key] = value;
59
+ }
60
+ }
61
+ return out;
62
+ }
63
+ /**
64
+ * ドラッグで動かしたノードの位置を保存する patch。
65
+ * 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。
66
+ * 子のノード(COMPONENT を含む)の position は親の中での位置なので、親を動かしても子の patch は要らない。
67
+ */
68
+ function movePatch(node, move) {
69
+ const base = node.position || { x: 0, y: 0 };
70
+ return {
71
+ collection: 'nodes',
72
+ id: node.id,
73
+ patch: {
74
+ position: {
75
+ x: Math.round(base.x + move.dx),
76
+ y: Math.round(base.y + move.dy),
77
+ },
78
+ },
79
+ };
80
+ }
81
+ // ---- 入力の読み取り ------------------------------------------------------------
82
+ function toNumber(value) {
83
+ const n = Number(value);
84
+ return value !== '' && Number.isFinite(n) ? n : null;
85
+ }
86
+ /**
87
+ * 色として読める文字列か。
88
+ * ブラウザでは CSS.supports('color', v) で判定する。Node など CSS の無い環境では、
89
+ * #rgb / #rrggbb / #rrggbbaa・rgb() などの関数表記・英字だけの色名、を通す緩い判定にする。
90
+ */
91
+ function isColor(value) {
92
+ if (value === '')
93
+ return false;
94
+ const css = globalThis.CSS;
95
+ if (css && typeof css.supports === 'function')
96
+ return css.supports('color', value);
97
+ return /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)
98
+ || /^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(.+\)$/i.test(value)
99
+ || /^[a-z]+$/i.test(value);
100
+ }
101
+ function toColor(value, label) {
102
+ if (!isColor(value))
103
+ throw new ModelError(`${label}は、色 (#ff0000 や red など) で入力してください。`);
104
+ return value;
105
+ }
106
+ // 0 より大きい数(枠線・線の太さ。d3.deployment は 0 を「書いていない」と見て既定に倒すので、0 は入れられない)
107
+ function toWidth(value, label) {
108
+ const n = toNumber(value);
109
+ if (n === null || n <= 0)
110
+ throw new ModelError(`${label}は、0 より大きい数で入力してください。`);
111
+ return n;
112
+ }
113
+ // 0 以上の整数(幅・高さ。0 は「子に合わせる」)
114
+ function toLength(value, label) {
115
+ const n = toNumber(value);
116
+ if (n === null || n < 0)
117
+ throw new ModelError(`${label}は 0 以上の数で入力してください。(0 で子に合わせる)`);
118
+ return Math.round(n);
119
+ }
120
+ // 角度(0 以上 360 未満の整数に正規化)
121
+ function toDegree(value, label) {
122
+ const n = toNumber(value);
123
+ if (n === null)
124
+ throw new ModelError(`${label}は、角度 (数) で入力してください。`);
125
+ return ((Math.round(n) % 360) + 360) % 360;
126
+ }
127
+ function toOption(value, options, label) {
128
+ if (!options.includes(value))
129
+ throw new ModelError(`${label}は、${options.join(' / ')} のどれかで入力してください。`);
130
+ return value;
131
+ }
132
+ // true / false
133
+ function toBoolean(value, label) {
134
+ if (value !== 'true' && value !== 'false')
135
+ throw new ModelError(`${label}は、true / false のどちらかで入力してください。`);
136
+ return value === 'true';
137
+ }
138
+ // 空にできる文字列。空なら null(JSON Merge Patch で項目を除く)
139
+ function toOptionalText(value) {
140
+ return value === '' ? null : value;
141
+ }
142
+ // patch の入れ子に値を置く(patch.border.color = v のように)
143
+ function put(patch, path, value) {
144
+ const keys = path.split('.');
145
+ let cur = patch;
146
+ for (const key of keys.slice(0, -1)) {
147
+ if (!cur[key] || typeof cur[key] !== 'object')
148
+ cur[key] = {};
149
+ cur = cur[key];
150
+ }
151
+ cur[keys[keys.length - 1]] = value;
152
+ }
153
+ /**
154
+ * インスペクタの入力のうち、図に描いている値(withDefaults で埋めた値)から変わった項目だけを返す。
155
+ * 前後の空白は除く。読み取り専用の項目と、values に無い項目は含めない。
156
+ */
157
+ function changedValues(record, values) {
158
+ const effective = (0, defaults_js_1.withDefaults)(record);
159
+ const out = new Map();
160
+ for (const field of (0, fields_js_1.fieldsOf)(record)) {
161
+ if (field.type === 'readonly')
162
+ continue;
163
+ const value = values[field.path];
164
+ if (value === undefined)
165
+ continue;
166
+ const current = getByPath(effective, field.path);
167
+ if (value.trim() !== String(current ?? ''))
168
+ out.set(field.path, value.trim());
169
+ }
170
+ return out;
171
+ }
172
+ // ノードの入力 → patch(label.text / type / description / link.url / size / background / border)
173
+ function nodePatch(node, values) {
174
+ const patch = {};
175
+ for (const [path, value] of changedValues(node, values)) {
176
+ switch (path) {
177
+ case 'label.text':
178
+ // 名称は空にできない(d3.deployment は名称の無いノードに '????????' を描く)
179
+ if (value === '')
180
+ throw new ModelError('名称を入力してください。');
181
+ put(patch, path, value);
182
+ break;
183
+ case 'type':
184
+ put(patch, path, toOption(value, defaults_js_1.NODE_TYPES, '種類'));
185
+ break;
186
+ case 'description':
187
+ put(patch, path, toOptionalText(value));
188
+ break;
189
+ case 'link.url':
190
+ // URL を空にするとリンクごと除く(d3.deployment は url の無い link を描かない)
191
+ if (value === '')
192
+ patch.link = null;
193
+ else
194
+ put(patch, path, value);
195
+ break;
196
+ case 'size.w':
197
+ put(patch, path, toLength(value, '幅'));
198
+ break;
199
+ case 'size.h':
200
+ put(patch, path, toLength(value, '高さ'));
201
+ break;
202
+ case 'background.color':
203
+ put(patch, path, toColor(value, '背景色'));
204
+ break;
205
+ case 'border.color':
206
+ put(patch, path, toColor(value, '枠線の色'));
207
+ break;
208
+ case 'border.width':
209
+ put(patch, path, toWidth(value, '枠線の太さ'));
210
+ break;
211
+ }
212
+ }
213
+ return patch;
214
+ }
215
+ const END_LABELS = { from: '接続元', to: '接続先' };
216
+ // 線の入力 → patch(from / to の角度・stroke・矢印)
217
+ function edgePatch(edge, values) {
218
+ const patch = {};
219
+ for (const [path, value] of changedValues(edge, values)) {
220
+ switch (path) {
221
+ case 'from.position':
222
+ case 'to.position':
223
+ put(patch, path, toDegree(value, `${END_LABELS[path.split('.')[0]]}の位置`));
224
+ break;
225
+ case 'stroke.color':
226
+ put(patch, path, toColor(value, '線の色'));
227
+ break;
228
+ case 'stroke.width':
229
+ put(patch, path, toWidth(value, '線の太さ'));
230
+ break;
231
+ case 'stroke.marker.start':
232
+ put(patch, path, toBoolean(value, '始点の矢印'));
233
+ break;
234
+ case 'stroke.marker.end':
235
+ put(patch, path, toBoolean(value, '終点の矢印'));
236
+ break;
237
+ }
238
+ }
239
+ // 矢印を片方だけ書くと d3.deployment の Edge.normalize は marker を丸ごと入力で置き換える(無い側は undefined =
240
+ // start は偽、end は truthy 判定で偽)ので、片方を変えたときはもう片方も描いている値で埋める
241
+ const marker = patch.stroke?.marker;
242
+ if (marker) {
243
+ const effective = (0, defaults_js_1.withDefaults)(edge).stroke.marker;
244
+ if (marker.start === undefined)
245
+ marker.start = effective.start;
246
+ if (marker.end === undefined)
247
+ marker.end = effective.end;
248
+ }
249
+ return patch;
250
+ }
251
+ /**
252
+ * インスペクタの「適用」の値を、保存する patch に変換する。
253
+ * 図に描いている値から変えた項目だけを patch に入れる(変えていない既定の値まで書き込まないため)。
254
+ * 変更が無ければ patch は {}。
255
+ */
256
+ function applyPatch(record, values) {
257
+ return {
258
+ collection: collectionOf(record),
259
+ id: record.id,
260
+ patch: (0, defaults_js_1.isEdge)(record) ? edgePatch(record, values) : nodePatch(record, values),
261
+ };
262
+ }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ // d3.deployment の入力データの JSON Schema(2020-12)。
3
+ //
4
+ // iHub の server/schemas/deployment/{nodes,edges}.schema.json を移したもの。
5
+ // 検査するのは、図の構造にかかわる項目(id・type・size・position・children・link、線の from / to)だけで、
6
+ // 見た目の項目(label・border・background・padding・description)は d3.deployment の入力の形のまま書けるよう
7
+ // additionalProperties を閉じていない(iHub 版は閉じていた)。
8
+ //
9
+ // x-id-start はスキーマの検証には使わない注釈(コレクションが空のときに最初に振る id)。
10
+ // Ajv で使うときは addKeyword({ keyword: 'x-id-start', schemaType: 'number' }) を足す。
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.SCHEMAS = exports.EDGES_SCHEMA = exports.NODES_SCHEMA = void 0;
13
+ const defaults_js_1 = require("./defaults.js");
14
+ const ID = { type: ['integer', 'string'] };
15
+ const POINT = {
16
+ type: 'object',
17
+ properties: { x: { type: 'number' }, y: { type: 'number' } },
18
+ required: ['x', 'y'],
19
+ };
20
+ exports.NODES_SCHEMA = {
21
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
22
+ title: 'システム構成図のノード',
23
+ description: 'd3.deployment に渡すノード。children に子のノードを入れ子で持つ。id は子のノードも含めて一意にする。position は親の中での位置で、ドラッグで動かすと書き換わる。link を付けると、ノードの左下にリンクを出す。見た目の項目 (label・padding・border・background・description) は d3.deployment の入力の形のまま書ける。',
24
+ 'x-id-start': 1,
25
+ type: 'array',
26
+ items: { $ref: '#/$defs/node' },
27
+ $defs: {
28
+ node: {
29
+ type: 'object',
30
+ properties: {
31
+ id: ID,
32
+ type: { enum: [...defaults_js_1.NODE_TYPES] },
33
+ label: {
34
+ type: 'object',
35
+ properties: { text: { type: ['string', 'null'] } },
36
+ },
37
+ description: { type: ['string', 'null'] },
38
+ size: {
39
+ type: 'object',
40
+ properties: { w: { type: 'number', minimum: 0 }, h: { type: 'number', minimum: 0 } },
41
+ required: ['w', 'h'],
42
+ },
43
+ position: POINT,
44
+ link: {
45
+ type: ['object', 'null'],
46
+ properties: { url: { type: ['string', 'null'] } },
47
+ },
48
+ children: { type: 'array', items: { $ref: '#/$defs/node' } },
49
+ },
50
+ required: ['id'],
51
+ additionalProperties: true,
52
+ },
53
+ },
54
+ };
55
+ exports.EDGES_SCHEMA = {
56
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
57
+ title: 'システム構成図の線',
58
+ description: 'ノードの間の線。from.id / to.id はノード (子を含む) の id。position は接続位置の角度 (0=真下・時計回り。省略すると 90=左)。stroke.marker は両端の矢印の有無 (既定: 終点だけ)。',
59
+ 'x-id-start': 100,
60
+ type: 'array',
61
+ items: {
62
+ type: 'object',
63
+ properties: {
64
+ id: ID,
65
+ from: { $ref: '#/$defs/end' },
66
+ to: { $ref: '#/$defs/end' },
67
+ stroke: {
68
+ type: 'object',
69
+ properties: {
70
+ color: { type: 'string' },
71
+ width: { type: 'number' },
72
+ marker: {
73
+ type: 'object',
74
+ properties: { start: { type: 'boolean' }, end: { type: 'boolean' } },
75
+ },
76
+ },
77
+ },
78
+ },
79
+ required: ['id', 'from', 'to'],
80
+ additionalProperties: true,
81
+ },
82
+ $defs: {
83
+ end: {
84
+ type: 'object',
85
+ properties: {
86
+ id: ID,
87
+ position: { type: 'number' },
88
+ },
89
+ required: ['id'],
90
+ },
91
+ },
92
+ };
93
+ /** コレクション名 → スキーマ */
94
+ exports.SCHEMAS = {
95
+ nodes: exports.NODES_SCHEMA,
96
+ edges: exports.EDGES_SCHEMA,
97
+ };
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ // model.deployment の公開型。
3
+ //
4
+ // ここで言う「レコード」は、d3.deployment の data() に渡す入力そのもの({ nodes, edges })の 1 要素。
5
+ // iHub では data/deployment/{nodes,edges}.json の 1 要素。d3.deployment が正規化したあとの datum(_id / _core / _position を持つもの)ではない。
6
+ // ノードと線の図なので model.unchi / model.sitemap と形が近い。ラベルは label.text(unchi と同じ。sitemap は label.contents)。
7
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,61 @@
1
+ import type { Background, Border, Font, Position } from '@yanqirenshi/types';
2
+ import type { DeploymentEdgeRecord, DeploymentNodeRecord, DeploymentNodeType, DeploymentRecord } from './types.js';
3
+ /** ノードの種類 */
4
+ export declare const NODE_TYPES: readonly DeploymentNodeType[];
5
+ /** ノードの枠線・背景の既定(assh0le の Stroke.template / Background.template) */
6
+ export declare const NODE_STYLE_DEFAULTS: {
7
+ readonly border: Required<Pick<Border, "color" | "width" | "type">>;
8
+ readonly background: Required<Pick<Background, "color" | "opacity">>;
9
+ };
10
+ /**
11
+ * ラベルの既定(assh0le の Label.template)。text が無いとき d3.deployment は '????????' を描く
12
+ * (withDefaults では '' を入れる — 「書いていない」ことをインスペクタで見せるため)。
13
+ */
14
+ export declare const LABEL_DEFAULTS: {
15
+ readonly placeholder: "????????";
16
+ readonly position: Position;
17
+ readonly font: Font;
18
+ };
19
+ /** ノードの既定(Padding / Position.template)。size の 0 は「子に合わせる」 */
20
+ export declare const NODE_DEFAULTS: {
21
+ readonly type: DeploymentNodeType;
22
+ readonly padding: 0;
23
+ readonly position: Position;
24
+ };
25
+ /** 線の見た目(色・太さ・両端の矢印の有無) */
26
+ export interface DeploymentEdgeStyle {
27
+ color: string;
28
+ width: number;
29
+ marker: {
30
+ start: boolean;
31
+ end: boolean;
32
+ };
33
+ }
34
+ /** 線の既定(Edge.dataTemplate の stroke、端点の角度は Geometry.getPortLineTo の 90=左) */
35
+ export declare const EDGE_DEFAULTS: {
36
+ readonly stroke: DeploymentEdgeStyle;
37
+ readonly position: 90;
38
+ };
39
+ /** 線か(from と to を持つ)。ノードは持たない */
40
+ export declare function isEdge(record: DeploymentRecord | null | undefined): record is DeploymentEdgeRecord;
41
+ /** ノードが描かれる枠線・背景(既定に入力を重ねた、実際に描かれる値) */
42
+ export declare function nodeStyle(node: DeploymentNodeRecord): typeof NODE_STYLE_DEFAULTS;
43
+ /**
44
+ * 線が描かれる値(Edge.normalize と同じ: stroke の color / width / marker はそれぞれ truthy ならそれ)。
45
+ * marker は入力があれば丸ごと置き換わる(片方だけ書くと、もう片方は偽)。
46
+ * d3.deployment のペインタが描くのは終点の矢印(marker.end)だけで、start は描かない。
47
+ */
48
+ export declare function edgeStyle(edge: DeploymentEdgeRecord): {
49
+ stroke: DeploymentEdgeStyle;
50
+ };
51
+ /** 端点の角度(無ければ 90。Geometry.getPortLineTo と同じ: 0 は 0、それ以外の falsy は 90) */
52
+ export declare function degreeOf(position: number | undefined | null): number;
53
+ /**
54
+ * レコードに、d3.deployment が既定で描いている値を重ねて返す(入力は書き換えない)。
55
+ * インスペクタの初期値に使う。children は重ねない(子は右クリックしたときに自分で通す)。
56
+ *
57
+ * 重ねる項目: ノードは type・padding・position・label.text(無ければ '')・border・background、
58
+ * 線は stroke(矢印を含む)・from.position・to.position。size は view が子に合わせて埋めるので触らない。
59
+ */
60
+ export declare function withDefaults<T extends DeploymentRecord>(record: T): T;
61
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnH,aAAa;AACb,eAAO,MAAM,UAAU,EAAE,SAAS,kBAAkB,EAA0B,CAAC;AAE/E,oEAAoE;AACpE,eAAO,MAAM,mBAAmB;qBAC+B,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC;yBAChE,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,SAAS,CAAC,CAAC;CACtF,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,cAAc;;uBAEG,QAAQ;mBACI,IAAI;CACpC,CAAC;AAEX,6DAA6D;AAC7D,eAAO,MAAM,aAAa;mBACR,kBAAkB;;uBAEN,QAAQ;CAC5B,CAAC;AAEX,2BAA2B;AAC3B,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C;AAED,4EAA4E;AAC5E,eAAO,MAAM,aAAa;qBAKnB,mBAAmB;;CAEhB,CAAC;AAEX,iCAAiC;AACjC,wBAAgB,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,IAAI,oBAAoB,CAElG;AAgBD,wCAAwC;AACxC,wBAAgB,SAAS,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,mBAAmB,CAKhF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,oBAAoB,GAAG;IAAE,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAUrF;AAED,wEAAwE;AACxE,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAKpE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,gBAAgB,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAuBrE"}
@@ -0,0 +1,111 @@
1
+ // d3.deployment が「書いていない項目」に既定で描く値。
2
+ //
3
+ // 値の出どころは、d3.deployment の datamodels/Node.js が使う共通基盤 assh0le のデータクラス
4
+ // (Stroke.template = 枠線、Background.template = 背景、Label.template = ラベル、Padding / Position.template)と、
5
+ // datamodels/Edge.js の dataTemplate(stroke と矢印)、Geometry.getPortLineTo(角度の既定 90)。
6
+ // d3.deployment はこのファイルを読む(#146)ので、ここが正。
7
+ //
8
+ // d3.deployment は border.type を正規化するが描画には使わない(dasharray を付けない)ので、モデルも項目にしない。
9
+ /** ノードの種類 */
10
+ export const NODE_TYPES = ['NODE', 'COMPONENT'];
11
+ /** ノードの枠線・背景の既定(assh0le の Stroke.template / Background.template) */
12
+ export const NODE_STYLE_DEFAULTS = {
13
+ border: { color: '#afafb0', width: 1, type: 'solid' },
14
+ background: { color: '#fff', opacity: 1.0 },
15
+ };
16
+ /**
17
+ * ラベルの既定(assh0le の Label.template)。text が無いとき d3.deployment は '????????' を描く
18
+ * (withDefaults では '' を入れる — 「書いていない」ことをインスペクタで見せるため)。
19
+ */
20
+ export const LABEL_DEFAULTS = {
21
+ placeholder: '????????',
22
+ position: { x: 0, y: 0 },
23
+ font: { size: 16, color: '#333333' },
24
+ };
25
+ /** ノードの既定(Padding / Position.template)。size の 0 は「子に合わせる」 */
26
+ export const NODE_DEFAULTS = {
27
+ type: 'NODE',
28
+ padding: 0,
29
+ position: { x: 0, y: 0 },
30
+ };
31
+ /** 線の既定(Edge.dataTemplate の stroke、端点の角度は Geometry.getPortLineTo の 90=左) */
32
+ export const EDGE_DEFAULTS = {
33
+ stroke: {
34
+ color: '#333',
35
+ width: 1.5,
36
+ marker: { start: false, end: true },
37
+ },
38
+ position: 90,
39
+ };
40
+ /** 線か(from と to を持つ)。ノードは持たない */
41
+ export function isEdge(record) {
42
+ return !!(record && record.from && record.to);
43
+ }
44
+ /** 既定に入力の指定(truthy な項目だけ。assh0le の Stroke.build / Background.build と同じ判定)を重ねる */
45
+ function mergeStyle(defaults, given) {
46
+ const out = { ...defaults };
47
+ for (const key of Object.keys(given || {})) {
48
+ const v = given[key];
49
+ if (v)
50
+ out[key] = v;
51
+ }
52
+ return out;
53
+ }
54
+ /** ノードが描かれる枠線・背景(既定に入力を重ねた、実際に描かれる値) */
55
+ export function nodeStyle(node) {
56
+ return {
57
+ border: mergeStyle(NODE_STYLE_DEFAULTS.border, node.border),
58
+ background: mergeStyle(NODE_STYLE_DEFAULTS.background, node.background),
59
+ };
60
+ }
61
+ /**
62
+ * 線が描かれる値(Edge.normalize と同じ: stroke の color / width / marker はそれぞれ truthy ならそれ)。
63
+ * marker は入力があれば丸ごと置き換わる(片方だけ書くと、もう片方は偽)。
64
+ * d3.deployment のペインタが描くのは終点の矢印(marker.end)だけで、start は描かない。
65
+ */
66
+ export function edgeStyle(edge) {
67
+ return {
68
+ stroke: {
69
+ color: edge.stroke?.color || EDGE_DEFAULTS.stroke.color,
70
+ width: edge.stroke?.width || EDGE_DEFAULTS.stroke.width,
71
+ marker: edge.stroke?.marker
72
+ ? { start: !!edge.stroke.marker.start, end: !!edge.stroke.marker.end }
73
+ : { ...EDGE_DEFAULTS.stroke.marker },
74
+ },
75
+ };
76
+ }
77
+ /** 端点の角度(無ければ 90。Geometry.getPortLineTo と同じ: 0 は 0、それ以外の falsy は 90) */
78
+ export function degreeOf(position) {
79
+ if (position === 0)
80
+ return 0;
81
+ return position ? position : EDGE_DEFAULTS.position;
82
+ }
83
+ /**
84
+ * レコードに、d3.deployment が既定で描いている値を重ねて返す(入力は書き換えない)。
85
+ * インスペクタの初期値に使う。children は重ねない(子は右クリックしたときに自分で通す)。
86
+ *
87
+ * 重ねる項目: ノードは type・padding・position・label.text(無ければ '')・border・background、
88
+ * 線は stroke(矢印を含む)・from.position・to.position。size は view が子に合わせて埋めるので触らない。
89
+ */
90
+ export function withDefaults(record) {
91
+ if (isEdge(record)) {
92
+ return {
93
+ ...record,
94
+ ...edgeStyle(record),
95
+ from: { ...record.from, position: degreeOf(record.from.position) },
96
+ to: { ...record.to, position: degreeOf(record.to.position) },
97
+ };
98
+ }
99
+ const node = record;
100
+ const style = nodeStyle(node);
101
+ const label = node.label && typeof node.label === 'object' ? node.label : {};
102
+ return {
103
+ ...node,
104
+ type: node.type || NODE_DEFAULTS.type,
105
+ label: { ...label, text: typeof label.text === 'string' ? label.text : '' },
106
+ padding: node.padding || node.padding === 0 ? node.padding : NODE_DEFAULTS.padding,
107
+ position: { ...NODE_DEFAULTS.position, ...(node.position || {}) },
108
+ border: style.border,
109
+ background: style.background,
110
+ };
111
+ }
@@ -0,0 +1,8 @@
1
+ import type { DeploymentField, DeploymentRecord } from './types.js';
2
+ /** ノードの項目 */
3
+ export declare const NODE_FIELDS: readonly DeploymentField[];
4
+ /** 線の項目。接続位置はノードの中心から見た角度(0=真下・時計回り。90=左 / 180=真上 / 270=右) */
5
+ export declare const EDGE_FIELDS: readonly DeploymentField[];
6
+ /** レコードに応じた項目の一覧(線 / ノード) */
7
+ export declare function fieldsOf(record: DeploymentRecord): DeploymentField[];
8
+ //# sourceMappingURL=fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAIpE,aAAa;AACb,eAAO,MAAM,WAAW,EAAE,SAAS,eAAe,EAWjD,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,WAAW,EAAE,SAAS,eAAe,EAUjD,CAAC;AAEF,6BAA6B;AAC7B,wBAAgB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,eAAe,EAAE,CAEpE"}
package/dist/fields.js ADDED
@@ -0,0 +1,40 @@
1
+ // 編集できる項目の一覧。インスペクタ(colonoscope の ModelInspector)に渡す。
2
+ //
3
+ // path はレコードの実際の項目を指す。既定値は withDefaults() で重ねてから渡すので、
4
+ // 書いていない項目(枠線の色など)も、図に描かれている値が初期値として出る。
5
+ // type: 'readonly' は表示だけ。options は取りうる値の一覧(検証にも使う)。
6
+ //
7
+ // iHub の SystemConfigurationDiagram.js の fields(label.text / type / id)から始め、
8
+ // d3.deployment が描く説明(ツールチップ)・リンク・幅・高さ・背景色・枠線の色 / 太さ、線の矢印を足した。
9
+ // border.type は d3.deployment が描かないので出さない。
10
+ import { NODE_TYPES, isEdge } from './defaults.js';
11
+ const BOOLEAN_OPTIONS = ['true', 'false'];
12
+ /** ノードの項目 */
13
+ export const NODE_FIELDS = [
14
+ { path: 'label.text', label: '名称', type: 'text' },
15
+ { path: 'type', label: '種類', type: 'text', options: NODE_TYPES },
16
+ { path: 'id', label: 'ID', type: 'readonly' },
17
+ { path: 'description', label: '説明', type: 'textarea' },
18
+ { path: 'link.url', label: 'URL', type: 'text' },
19
+ { path: 'size.w', label: '幅', type: 'number' },
20
+ { path: 'size.h', label: '高さ', type: 'number' },
21
+ { path: 'background.color', label: '背景色', type: 'text' },
22
+ { path: 'border.color', label: '枠線の色', type: 'text' },
23
+ { path: 'border.width', label: '枠線の太さ', type: 'number' },
24
+ ];
25
+ /** 線の項目。接続位置はノードの中心から見た角度(0=真下・時計回り。90=左 / 180=真上 / 270=右) */
26
+ export const EDGE_FIELDS = [
27
+ { path: 'id', label: 'ID', type: 'readonly' },
28
+ { path: 'from.id', label: '接続元のノード', type: 'readonly' },
29
+ { path: 'from.position', label: '接続元の位置 (角度)', type: 'number' },
30
+ { path: 'to.id', label: '接続先のノード', type: 'readonly' },
31
+ { path: 'to.position', label: '接続先の位置 (角度)', type: 'number' },
32
+ { path: 'stroke.color', label: '線の色', type: 'text' },
33
+ { path: 'stroke.width', label: '線の太さ', type: 'number' },
34
+ // 始点の矢印(marker.start)は d3.deployment のペインタが描かないので項目にしない
35
+ { path: 'stroke.marker.end', label: '終点の矢印', type: 'text', options: BOOLEAN_OPTIONS },
36
+ ];
37
+ /** レコードに応じた項目の一覧(線 / ノード) */
38
+ export function fieldsOf(record) {
39
+ return isEdge(record) ? [...EDGE_FIELDS] : [...NODE_FIELDS];
40
+ }
@@ -0,0 +1,8 @@
1
+ export type { DeploymentId, DeploymentNodeType, DeploymentNodeLabel, DeploymentNodeRecord, DeploymentEdgeEnd, DeploymentEdgeStroke, DeploymentEdgeRecord, DeploymentRecord, DeploymentGraph, DeploymentCollection, MergePatch, DeploymentChange, DeploymentMove, DeploymentFieldType, DeploymentField, DeploymentValues, } from './types.js';
2
+ export { NODE_TYPES, NODE_STYLE_DEFAULTS, LABEL_DEFAULTS, NODE_DEFAULTS, EDGE_DEFAULTS, isEdge, nodeStyle, edgeStyle, degreeOf, withDefaults, } from './defaults.js';
3
+ export type { DeploymentEdgeStyle } from './defaults.js';
4
+ export { NODES_SCHEMA, EDGES_SCHEMA, SCHEMAS } from './schema.js';
5
+ export { check, flatten, findById } from './check.js';
6
+ export { NODE_FIELDS, EDGE_FIELDS, fieldsOf } from './fields.js';
7
+ export { ModelError, collectionOf, titleOf, getByPath, mergePatch, movePatch, applyPatch, isColor } from './patch.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,YAAY,EACV,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,oBAAoB,EACpH,oBAAoB,EAAE,gBAAgB,EAAE,eAAe,EAAE,oBAAoB,EAAE,UAAU,EAAE,gBAAgB,EAC3G,cAAc,EAAE,mBAAmB,EAAE,eAAe,EAAE,gBAAgB,GACvE,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,UAAU,EAAE,mBAAmB,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAC7E,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,GACrD,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ // model.deployment — システム構成図(デプロイメント図。d3.deployment)の「図ごとの知識」。描画はしない(react / d3 に依存しない)。
2
+ //
3
+ // スキーマ: SCHEMAS / NODES_SCHEMA / EDGES_SCHEMA(JSON Schema 2020-12)
4
+ // 整合性ルール: check(data)(id の重複・参照先の存在)/ flatten / findById
5
+ // 既定値の補完: withDefaults(record) / nodeStyle / edgeStyle / degreeOf
6
+ // NODE_TYPES / NODE_STYLE_DEFAULTS / LABEL_DEFAULTS / NODE_DEFAULTS / EDGE_DEFAULTS(d3.deployment が読む)
7
+ // 編集できる項目: fieldsOf(record) / NODE_FIELDS / EDGE_FIELDS
8
+ // patch への変換: movePatch(node, move) / applyPatch(record, values) / mergePatch(record, patch)
9
+ //
10
+ // graph 系(unchi / sitemap / deployment)の 3 つ目。check / flatten / movePatch / mergePatch / getByPath / isColor は
11
+ // model.unchi / model.sitemap と同じ内容の複製で、共通化は別 issue で判断する(#146 の 7)。
12
+ export { NODE_TYPES, NODE_STYLE_DEFAULTS, LABEL_DEFAULTS, NODE_DEFAULTS, EDGE_DEFAULTS, isEdge, nodeStyle, edgeStyle, degreeOf, withDefaults, } from './defaults.js';
13
+ export { NODES_SCHEMA, EDGES_SCHEMA, SCHEMAS } from './schema.js';
14
+ export { check, flatten, findById } from './check.js';
15
+ export { NODE_FIELDS, EDGE_FIELDS, fieldsOf } from './fields.js';
16
+ export { ModelError, collectionOf, titleOf, getByPath, mergePatch, movePatch, applyPatch, isColor } from './patch.js';