@yanqirenshi/model.deployment 0.0.0-stage → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018-2026 Satoshi Iwasaki (yanqirenshi@gmail.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,131 @@
1
- # Temporary Holding Version
1
+ # @yanqirenshi/model.deployment
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ システム構成図(デプロイメント図。view = `d3.deployment`)の**図ごとの知識**を、描画から独立させたパッケージです。
4
+ react / d3 に依存しない、純粋なデータと関数だけを持ちます。
5
+ d3.deployment は、このパッケージの既定値(枠線・背景・線の stroke と矢印・端点の角度)を読みます。
6
+
7
+ | 持つもの | 入口 |
8
+ |---|---|
9
+ | 入力データのスキーマ(JSON Schema 2020-12) | `SCHEMAS.nodes` / `SCHEMAS.edges` |
10
+ | コレクションをまたぐ整合性ルール(id の重複・参照先の存在) | `check(data)` |
11
+ | d3.deployment が既定で描いている値 | `withDefaults(record)` / `nodeStyle` / `edgeStyle`、`NODE_STYLE_DEFAULTS` など |
12
+ | 編集できる項目の一覧(インスペクタ用) | `fieldsOf(record)` |
13
+ | UI の操作を保存する patch に変える変換 | `movePatch(node, move)` / `applyPatch(record, values)` |
14
+
15
+ 利用側(iHub など)は、モデルが返す `{ collection, id, patch }` を保存するだけで、図の種類を知らずに書けます。
16
+
17
+ ## 位置づけ
18
+
19
+ ```
20
+ 利用側 ──▶ d3.deployment (view) ──▶ model.deployment (既定値を読む)
21
+ ──▶ colonoscope (ModelInspector)
22
+ ──▶ model.deployment
23
+ ──▶ 自前の永続化
24
+ ```
25
+
26
+ model.deployment・colonoscope の間に横の依存はありません。組み合わせるのは利用側だけです。
27
+ これは [Foolsgolds/Assholes#138](https://github.com/Foolsgolds/Assholes/issues/138) の派生([#146](https://github.com/Foolsgolds/Assholes/issues/146))で、
28
+ ノードと線の図(graph 系: [model.unchi](../model.unchi) / [model.sitemap](../model.sitemap) / model.deployment)の 3 つ目です。
29
+ `check` / `movePatch` / `mergePatch` と入力の読み取り部品は共通部品 [model.core](../model.core) のものに
30
+ model.deployment の型を付けて公開しています([#151](https://github.com/Foolsgolds/Assholes/issues/151))。
31
+
32
+ ## レコードの形
33
+
34
+ d3.deployment の `data()` に渡す入力そのもの(`nodes` / `edges` の 1 要素)です。
35
+
36
+ ```js
37
+ {
38
+ nodes: [{ id, type: 'NODE' | 'COMPONENT', label: { text, position, font }, description,
39
+ size: { w, h }, position: { x, y }, padding, border, background, link: { url },
40
+ children: [ /* 同じ形 */ ] }],
41
+ edges: [{ id, from: { id, position }, to: { id, position },
42
+ stroke: { color, width, marker: { start, end } } }],
43
+ }
44
+ ```
45
+
46
+ model.unchi(ポンチ絵)との違い: ノードの種類は NODE / COMPONENT の 2 つ、`description` はツールチップ、線の矢印は `stroke.marker.end`(真偽値。描かれるのは終点だけ)です。
47
+
48
+ ## 使い方
49
+
50
+ ```js
51
+ import D3Deployment, { Rectum } from '@yanqirenshi/d3.deployment';
52
+ import { ModelInspector } from '@yanqirenshi/colonoscope';
53
+ import {
54
+ check, fieldsOf, titleOf, withDefaults, movePatch, applyPatch, mergePatch, findById, ModelError,
55
+ } from '@yanqirenshi/model.deployment';
56
+
57
+ const errors = check({ nodes, edges }); // [] なら問題なし
58
+
59
+ const rectum = new Rectum({
60
+ callbacks: {
61
+ node: {
62
+ // ドラッグの終わり → 位置の patch(core は入力のレコード)
63
+ dragend: (core, node, move) => save(movePatch(core, move)),
64
+ // → { collection: 'nodes', id: 1, patch: { position: { x: 120, y: 40 } } }
65
+ contextmenu: (core) => setTarget({ collection: 'nodes', id: core.id }),
66
+ },
67
+ edge: { contextmenu: (core) => setTarget({ collection: 'edges', id: core.id }) },
68
+ },
69
+ });
70
+
71
+ const record = findById(data[target.collection], target.id); // ノードは子も探す
72
+ <ModelInspector
73
+ target={record && withDefaults(record)}
74
+ fields={fieldsOf}
75
+ title={titleOf}
76
+ onApply={(values) => {
77
+ try {
78
+ save(applyPatch(record, values)); // 変えた項目だけが patch に入る
79
+ } catch (e) {
80
+ if (e instanceof ModelError) alert(e.message); // 利用者向けの日本語
81
+ }
82
+ }}
83
+ />
84
+ ```
85
+
86
+ ### `applyPatch` の検証
87
+
88
+ | 項目 | 規則 |
89
+ |---|---|
90
+ | 名称(`label.text`) | 空にできない(d3.deployment は名称の無いノードに `????????` を描く) |
91
+ | 種類(`type`) | `NODE` / `COMPONENT` |
92
+ | 説明 | 空にすると項目を除く |
93
+ | URL(`link.url`) | 空にすると `link` ごと除く |
94
+ | 幅・高さ | 0 以上。整数に丸める(0 で子に合わせる) |
95
+ | 色(背景・枠線・線) | ブラウザでは `CSS.supports('color', v)`。CSS の無い環境では `#hex` / 関数表記 / 英字の色名を通す緩い判定 |
96
+ | 枠線・線の太さ | 0 より大きい数(d3.deployment は 0 を「書いていない」と見て既定に倒す) |
97
+ | 接続位置(角度) | 数。0 以上 360 未満の整数に正規化 |
98
+ | 終点の矢印 | `true` / `false`。d3.deployment は `marker` を丸ごと置き換えるので、patch には始点側も描いている値で埋めて入れる |
99
+
100
+ 図に描いている値(`withDefaults` の結果)から**変えた項目だけ**を patch に入れます。
101
+
102
+ ### 編集できる項目(`fieldsOf`)
103
+
104
+ - ノード: 名称・種類・ID・説明・URL・幅・高さ・背景色・枠線の色 / 太さ
105
+ - 線: ID・接続元 / 接続先のノードと位置(角度)・線の色 / 太さ・終点の矢印
106
+
107
+ `border.type` と始点の矢印(`marker.start`)は d3.deployment が描かないので項目にしません。
108
+
109
+ ### 既定値の出どころ
110
+
111
+ d3.deployment は共通基盤 assh0le のデータクラス(`Stroke` / `Background` / `Label` / `Padding` / `Position`)で入力を正規化していました。
112
+ 枠線・背景の既定はここに移し(`nodeStyle`)、assh0le のテンプレートと同じ値であることをテストで突き合わせています。
113
+ ラベルの位置・フォント・余白・位置の正規化は assh0le のまま(値はこのパッケージの定数と同じ)。
114
+
115
+ ## スコープ外(いまは持たない)
116
+
117
+ - `label.position` / `label.font` / `padding` の編集、ノード・線の追加削除
118
+ - ファイル・HTTP などの永続化(利用側の実装)
119
+
120
+ ## 配布形式
121
+
122
+ ESM(`dist/`)に加えて CommonJS 版(`dist/cjs/`)も出します。
123
+ Babel 系の `d3.deployment` を jest(CommonJS)でテストするためで、d3.deployment の jest が ESM を読めるようになったら外せます。
124
+
125
+ ## コマンド
126
+
127
+ ```bash
128
+ npm run build -w @yanqirenshi/model.deployment # tsc で dist/(ESM + d.ts)と dist/cjs/(CommonJS)へビルド
129
+ npm run typecheck -w @yanqirenshi/model.deployment
130
+ npm test -w @yanqirenshi/model.deployment # vitest(assh0le のテンプレートとの突き合わせを含む)
131
+ ```
@@ -0,0 +1,15 @@
1
+ import type { DeploymentGraph, DeploymentId, DeploymentNodeRecord, DeploymentRecord } from './types.js';
2
+ /** 入れ子のノード(children)を平らにする(親 → 子 の順) */
3
+ export declare function flatten(nodes: DeploymentNodeRecord[]): DeploymentNodeRecord[];
4
+ /**
5
+ * 図全体を検査する。
6
+ * - nodes(子を含む)の id が重複していない
7
+ * - edges の id が重複していない
8
+ * - edges の from.id / to.id がノードに存在する
9
+ *
10
+ * @param prefix メッセージのコレクション名の前に付ける文字列(例: 'deployment/')
11
+ */
12
+ export declare function check(data: DeploymentGraph, prefix?: string): string[];
13
+ /** ノード(子を含む)か線を id で探す(無ければ null) */
14
+ export declare function findById<T extends DeploymentRecord>(list: readonly T[] | undefined, id: DeploymentId): T | null;
15
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAExG,wCAAwC;AACxC,wBAAgB,OAAO,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,oBAAoB,EAAE,CAE7E;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE,CAElE;AAED,qCAAqC;AACrC,wBAAgB,QAAQ,CAAC,CAAC,SAAS,gBAAgB,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,GAAG,SAAS,EAAE,EAAE,EAAE,YAAY,GAAG,CAAC,GAAG,IAAI,CAE/G"}
package/dist/check.js ADDED
@@ -0,0 +1,27 @@
1
+ // コレクションをまたぐ整合性の検査(id の重複・参照先の存在)。
2
+ // スキーマ(schema.ts)に合っているデータを渡す。戻り値はエラーのメッセージの配列(空なら問題なし)。
3
+ //
4
+ // iHub の server/validation/rules.mjs の checkGraph('deployment', data) と同じ検査・同じ文言。
5
+ // 検査そのものは graph 系(unchi / sitemap / deployment)で共通なので model.core の checkGraph にあり、
6
+ // ここでは model.deployment の型を付けて公開する(#151)。
7
+ // メッセージの先頭はコレクション名で、prefix を渡すと 'deployment/nodes: ...' のように付けられる。
8
+ import { checkGraph, flattenNodes, findById as findInList } from '@yanqirenshi/model.core';
9
+ /** 入れ子のノード(children)を平らにする(親 → 子 の順) */
10
+ export function flatten(nodes) {
11
+ return flattenNodes(nodes);
12
+ }
13
+ /**
14
+ * 図全体を検査する。
15
+ * - nodes(子を含む)の id が重複していない
16
+ * - edges の id が重複していない
17
+ * - edges の from.id / to.id がノードに存在する
18
+ *
19
+ * @param prefix メッセージのコレクション名の前に付ける文字列(例: 'deployment/')
20
+ */
21
+ export function check(data, prefix = '') {
22
+ return checkGraph(data, prefix);
23
+ }
24
+ /** ノード(子を含む)か線を id で探す(無ければ null) */
25
+ export function findById(list, id) {
26
+ return findInList(list, id);
27
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ // コレクションをまたぐ整合性の検査(id の重複・参照先の存在)。
3
+ // スキーマ(schema.ts)に合っているデータを渡す。戻り値はエラーのメッセージの配列(空なら問題なし)。
4
+ //
5
+ // iHub の server/validation/rules.mjs の checkGraph('deployment', data) と同じ検査・同じ文言。
6
+ // 検査そのものは graph 系(unchi / sitemap / deployment)で共通なので model.core の checkGraph にあり、
7
+ // ここでは model.deployment の型を付けて公開する(#151)。
8
+ // メッセージの先頭はコレクション名で、prefix を渡すと 'deployment/nodes: ...' のように付けられる。
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.flatten = flatten;
11
+ exports.check = check;
12
+ exports.findById = findById;
13
+ const model_core_1 = require("@yanqirenshi/model.core");
14
+ /** 入れ子のノード(children)を平らにする(親 → 子 の順) */
15
+ function flatten(nodes) {
16
+ return (0, model_core_1.flattenNodes)(nodes);
17
+ }
18
+ /**
19
+ * 図全体を検査する。
20
+ * - nodes(子を含む)の id が重複していない
21
+ * - edges の id が重複していない
22
+ * - edges の from.id / to.id がノードに存在する
23
+ *
24
+ * @param prefix メッセージのコレクション名の前に付ける文字列(例: 'deployment/')
25
+ */
26
+ function check(data, prefix = '') {
27
+ return (0, model_core_1.checkGraph)(data, prefix);
28
+ }
29
+ /** ノード(子を含む)か線を id で探す(無ければ null) */
30
+ function findById(list, id) {
31
+ return (0, model_core_1.findById)(list, id);
32
+ }
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ // d3.deployment が「書いていない項目」に既定で描く値。
3
+ //
4
+ // 値の出どころは、d3.deployment の datamodels/Node.js が使う共通基盤 assh0le のデータクラス
5
+ // (Stroke.template = 枠線、Background.template = 背景、Label.template = ラベル、Padding / Position.template)と、
6
+ // datamodels/Edge.js の dataTemplate(stroke と矢印)、Geometry.getPortLineTo(角度の既定 90)。
7
+ // d3.deployment はこのファイルを読む(#146)ので、ここが正。
8
+ //
9
+ // d3.deployment は border.type を正規化するが描画には使わない(dasharray を付けない)ので、モデルも項目にしない。
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.EDGE_DEFAULTS = exports.NODE_DEFAULTS = exports.LABEL_DEFAULTS = exports.NODE_STYLE_DEFAULTS = exports.NODE_TYPES = void 0;
12
+ exports.isEdge = isEdge;
13
+ exports.nodeStyle = nodeStyle;
14
+ exports.edgeStyle = edgeStyle;
15
+ exports.degreeOf = degreeOf;
16
+ exports.withDefaults = withDefaults;
17
+ /** ノードの種類 */
18
+ exports.NODE_TYPES = ['NODE', 'COMPONENT'];
19
+ /** ノードの枠線・背景の既定(assh0le の Stroke.template / Background.template) */
20
+ exports.NODE_STYLE_DEFAULTS = {
21
+ border: { color: '#afafb0', width: 1, type: 'solid' },
22
+ background: { color: '#fff', opacity: 1.0 },
23
+ };
24
+ /**
25
+ * ラベルの既定(assh0le の Label.template)。text が無いとき d3.deployment は '????????' を描く
26
+ * (withDefaults では '' を入れる — 「書いていない」ことをインスペクタで見せるため)。
27
+ */
28
+ exports.LABEL_DEFAULTS = {
29
+ placeholder: '????????',
30
+ position: { x: 0, y: 0 },
31
+ font: { size: 16, color: '#333333' },
32
+ };
33
+ /** ノードの既定(Padding / Position.template)。size の 0 は「子に合わせる」 */
34
+ exports.NODE_DEFAULTS = {
35
+ type: 'NODE',
36
+ padding: 0,
37
+ position: { x: 0, y: 0 },
38
+ };
39
+ /** 線の既定(Edge.dataTemplate の stroke、端点の角度は Geometry.getPortLineTo の 90=左) */
40
+ exports.EDGE_DEFAULTS = {
41
+ stroke: {
42
+ color: '#333',
43
+ width: 1.5,
44
+ marker: { start: false, end: true },
45
+ },
46
+ position: 90,
47
+ };
48
+ /** 線か(from と to を持つ)。ノードは持たない */
49
+ function isEdge(record) {
50
+ return !!(record && record.from && record.to);
51
+ }
52
+ /** 既定に入力の指定(truthy な項目だけ。assh0le の Stroke.build / Background.build と同じ判定)を重ねる */
53
+ function mergeStyle(defaults, given) {
54
+ const out = { ...defaults };
55
+ for (const key of Object.keys(given || {})) {
56
+ const v = given[key];
57
+ if (v)
58
+ out[key] = v;
59
+ }
60
+ return out;
61
+ }
62
+ /** ノードが描かれる枠線・背景(既定に入力を重ねた、実際に描かれる値) */
63
+ function nodeStyle(node) {
64
+ return {
65
+ border: mergeStyle(exports.NODE_STYLE_DEFAULTS.border, node.border),
66
+ background: mergeStyle(exports.NODE_STYLE_DEFAULTS.background, node.background),
67
+ };
68
+ }
69
+ /**
70
+ * 線が描かれる値(Edge.normalize と同じ: stroke の color / width / marker はそれぞれ truthy ならそれ)。
71
+ * marker は入力があれば丸ごと置き換わる(片方だけ書くと、もう片方は偽)。
72
+ * d3.deployment のペインタが描くのは終点の矢印(marker.end)だけで、start は描かない。
73
+ */
74
+ function edgeStyle(edge) {
75
+ return {
76
+ stroke: {
77
+ color: edge.stroke?.color || exports.EDGE_DEFAULTS.stroke.color,
78
+ width: edge.stroke?.width || exports.EDGE_DEFAULTS.stroke.width,
79
+ marker: edge.stroke?.marker
80
+ ? { start: !!edge.stroke.marker.start, end: !!edge.stroke.marker.end }
81
+ : { ...exports.EDGE_DEFAULTS.stroke.marker },
82
+ },
83
+ };
84
+ }
85
+ /** 端点の角度(無ければ 90。Geometry.getPortLineTo と同じ: 0 は 0、それ以外の falsy は 90) */
86
+ function degreeOf(position) {
87
+ if (position === 0)
88
+ return 0;
89
+ return position ? position : exports.EDGE_DEFAULTS.position;
90
+ }
91
+ /**
92
+ * レコードに、d3.deployment が既定で描いている値を重ねて返す(入力は書き換えない)。
93
+ * インスペクタの初期値に使う。children は重ねない(子は右クリックしたときに自分で通す)。
94
+ *
95
+ * 重ねる項目: ノードは type・padding・position・label.text(無ければ '')・border・background、
96
+ * 線は stroke(矢印を含む)・from.position・to.position。size は view が子に合わせて埋めるので触らない。
97
+ */
98
+ function withDefaults(record) {
99
+ if (isEdge(record)) {
100
+ return {
101
+ ...record,
102
+ ...edgeStyle(record),
103
+ from: { ...record.from, position: degreeOf(record.from.position) },
104
+ to: { ...record.to, position: degreeOf(record.to.position) },
105
+ };
106
+ }
107
+ const node = record;
108
+ const style = nodeStyle(node);
109
+ const label = node.label && typeof node.label === 'object' ? node.label : {};
110
+ return {
111
+ ...node,
112
+ type: node.type || exports.NODE_DEFAULTS.type,
113
+ label: { ...label, text: typeof label.text === 'string' ? label.text : '' },
114
+ padding: node.padding || node.padding === 0 ? node.padding : exports.NODE_DEFAULTS.padding,
115
+ position: { ...exports.NODE_DEFAULTS.position, ...(node.position || {}) },
116
+ border: style.border,
117
+ background: style.background,
118
+ };
119
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ // 編集できる項目の一覧。インスペクタ(colonoscope の ModelInspector)に渡す。
3
+ //
4
+ // path はレコードの実際の項目を指す。既定値は withDefaults() で重ねてから渡すので、
5
+ // 書いていない項目(枠線の色など)も、図に描かれている値が初期値として出る。
6
+ // type: 'readonly' は表示だけ。options は取りうる値の一覧(検証にも使う)。
7
+ //
8
+ // iHub の SystemConfigurationDiagram.js の fields(label.text / type / id)から始め、
9
+ // d3.deployment が描く説明(ツールチップ)・リンク・幅・高さ・背景色・枠線の色 / 太さ、線の矢印を足した。
10
+ // border.type は d3.deployment が描かないので出さない。
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.EDGE_FIELDS = exports.NODE_FIELDS = void 0;
13
+ exports.fieldsOf = fieldsOf;
14
+ const defaults_js_1 = require("./defaults.js");
15
+ const BOOLEAN_OPTIONS = ['true', 'false'];
16
+ /** ノードの項目 */
17
+ exports.NODE_FIELDS = [
18
+ { path: 'label.text', label: '名称', type: 'text' },
19
+ { path: 'type', label: '種類', type: 'text', options: defaults_js_1.NODE_TYPES },
20
+ { path: 'id', label: 'ID', type: 'readonly' },
21
+ { path: 'description', label: '説明', type: 'textarea' },
22
+ { path: 'link.url', label: 'URL', type: 'text' },
23
+ { path: 'size.w', label: '幅', type: 'number' },
24
+ { path: 'size.h', label: '高さ', type: 'number' },
25
+ { path: 'background.color', label: '背景色', type: 'text' },
26
+ { path: 'border.color', label: '枠線の色', type: 'text' },
27
+ { path: 'border.width', label: '枠線の太さ', type: 'number' },
28
+ ];
29
+ /** 線の項目。接続位置はノードの中心から見た角度(0=真下・時計回り。90=左 / 180=真上 / 270=右) */
30
+ exports.EDGE_FIELDS = [
31
+ { path: 'id', label: 'ID', type: 'readonly' },
32
+ { path: 'from.id', label: '接続元のノード', type: 'readonly' },
33
+ { path: 'from.position', label: '接続元の位置 (角度)', type: 'number' },
34
+ { path: 'to.id', label: '接続先のノード', type: 'readonly' },
35
+ { path: 'to.position', label: '接続先の位置 (角度)', type: 'number' },
36
+ { path: 'stroke.color', label: '線の色', type: 'text' },
37
+ { path: 'stroke.width', label: '線の太さ', type: 'number' },
38
+ // 始点の矢印(marker.start)は d3.deployment のペインタが描かないので項目にしない
39
+ { path: 'stroke.marker.end', label: '終点の矢印', type: 'text', options: BOOLEAN_OPTIONS },
40
+ ];
41
+ /** レコードに応じた項目の一覧(線 / ノード) */
42
+ function fieldsOf(record) {
43
+ return (0, defaults_js_1.isEdge)(record) ? [...exports.EDGE_FIELDS] : [...exports.NODE_FIELDS];
44
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ // model.deployment — システム構成図(デプロイメント図。d3.deployment)の「図ごとの知識」。描画はしない(react / d3 に依存しない)。
3
+ //
4
+ // スキーマ: SCHEMAS / NODES_SCHEMA / EDGES_SCHEMA(JSON Schema 2020-12)
5
+ // 整合性ルール: check(data)(id の重複・参照先の存在)/ flatten / findById
6
+ // 既定値の補完: withDefaults(record) / nodeStyle / edgeStyle / degreeOf
7
+ // NODE_TYPES / NODE_STYLE_DEFAULTS / LABEL_DEFAULTS / NODE_DEFAULTS / EDGE_DEFAULTS(d3.deployment が読む)
8
+ // 編集できる項目: fieldsOf(record) / NODE_FIELDS / EDGE_FIELDS
9
+ // patch への変換: movePatch(node, move) / applyPatch(record, values) / mergePatch(record, patch)
10
+ //
11
+ // graph 系(unchi / sitemap / deployment)の 3 つ目。check / flatten / findById / movePatch / mergePatch / getByPath / isColor と
12
+ // 入力の読み取り部品は model.core(#151)のもので、ここでは model.deployment の型を付けて公開している。
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.isColor = exports.applyPatch = exports.movePatch = exports.mergePatch = exports.getByPath = exports.titleOf = exports.collectionOf = exports.ModelError = exports.fieldsOf = exports.EDGE_FIELDS = exports.NODE_FIELDS = exports.findById = exports.flatten = exports.check = exports.SCHEMAS = exports.EDGES_SCHEMA = exports.NODES_SCHEMA = exports.withDefaults = exports.degreeOf = exports.edgeStyle = exports.nodeStyle = exports.isEdge = exports.EDGE_DEFAULTS = exports.NODE_DEFAULTS = exports.LABEL_DEFAULTS = exports.NODE_STYLE_DEFAULTS = exports.NODE_TYPES = void 0;
15
+ var defaults_js_1 = require("./defaults.js");
16
+ Object.defineProperty(exports, "NODE_TYPES", { enumerable: true, get: function () { return defaults_js_1.NODE_TYPES; } });
17
+ Object.defineProperty(exports, "NODE_STYLE_DEFAULTS", { enumerable: true, get: function () { return defaults_js_1.NODE_STYLE_DEFAULTS; } });
18
+ Object.defineProperty(exports, "LABEL_DEFAULTS", { enumerable: true, get: function () { return defaults_js_1.LABEL_DEFAULTS; } });
19
+ Object.defineProperty(exports, "NODE_DEFAULTS", { enumerable: true, get: function () { return defaults_js_1.NODE_DEFAULTS; } });
20
+ Object.defineProperty(exports, "EDGE_DEFAULTS", { enumerable: true, get: function () { return defaults_js_1.EDGE_DEFAULTS; } });
21
+ Object.defineProperty(exports, "isEdge", { enumerable: true, get: function () { return defaults_js_1.isEdge; } });
22
+ Object.defineProperty(exports, "nodeStyle", { enumerable: true, get: function () { return defaults_js_1.nodeStyle; } });
23
+ Object.defineProperty(exports, "edgeStyle", { enumerable: true, get: function () { return defaults_js_1.edgeStyle; } });
24
+ Object.defineProperty(exports, "degreeOf", { enumerable: true, get: function () { return defaults_js_1.degreeOf; } });
25
+ Object.defineProperty(exports, "withDefaults", { enumerable: true, get: function () { return defaults_js_1.withDefaults; } });
26
+ var schema_js_1 = require("./schema.js");
27
+ Object.defineProperty(exports, "NODES_SCHEMA", { enumerable: true, get: function () { return schema_js_1.NODES_SCHEMA; } });
28
+ Object.defineProperty(exports, "EDGES_SCHEMA", { enumerable: true, get: function () { return schema_js_1.EDGES_SCHEMA; } });
29
+ Object.defineProperty(exports, "SCHEMAS", { enumerable: true, get: function () { return schema_js_1.SCHEMAS; } });
30
+ var check_js_1 = require("./check.js");
31
+ Object.defineProperty(exports, "check", { enumerable: true, get: function () { return check_js_1.check; } });
32
+ Object.defineProperty(exports, "flatten", { enumerable: true, get: function () { return check_js_1.flatten; } });
33
+ Object.defineProperty(exports, "findById", { enumerable: true, get: function () { return check_js_1.findById; } });
34
+ var fields_js_1 = require("./fields.js");
35
+ Object.defineProperty(exports, "NODE_FIELDS", { enumerable: true, get: function () { return fields_js_1.NODE_FIELDS; } });
36
+ Object.defineProperty(exports, "EDGE_FIELDS", { enumerable: true, get: function () { return fields_js_1.EDGE_FIELDS; } });
37
+ Object.defineProperty(exports, "fieldsOf", { enumerable: true, get: function () { return fields_js_1.fieldsOf; } });
38
+ var patch_js_1 = require("./patch.js");
39
+ Object.defineProperty(exports, "ModelError", { enumerable: true, get: function () { return patch_js_1.ModelError; } });
40
+ Object.defineProperty(exports, "collectionOf", { enumerable: true, get: function () { return patch_js_1.collectionOf; } });
41
+ Object.defineProperty(exports, "titleOf", { enumerable: true, get: function () { return patch_js_1.titleOf; } });
42
+ Object.defineProperty(exports, "getByPath", { enumerable: true, get: function () { return patch_js_1.getByPath; } });
43
+ Object.defineProperty(exports, "mergePatch", { enumerable: true, get: function () { return patch_js_1.mergePatch; } });
44
+ Object.defineProperty(exports, "movePatch", { enumerable: true, get: function () { return patch_js_1.movePatch; } });
45
+ Object.defineProperty(exports, "applyPatch", { enumerable: true, get: function () { return patch_js_1.applyPatch; } });
46
+ Object.defineProperty(exports, "isColor", { enumerable: true, get: function () { return patch_js_1.isColor; } });
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,141 @@
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
+ // 読み取り部品(toColor など)と patch の道具は model.core にある(#151)。
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.isColor = exports.mergePatch = exports.getByPath = exports.ModelError = void 0;
10
+ exports.collectionOf = collectionOf;
11
+ exports.titleOf = titleOf;
12
+ exports.movePatch = movePatch;
13
+ exports.applyPatch = applyPatch;
14
+ const model_core_1 = require("@yanqirenshi/model.core");
15
+ const defaults_js_1 = require("./defaults.js");
16
+ const fields_js_1 = require("./fields.js");
17
+ // model.core のものをそのまま公開する(利用側が model.deployment だけを読めばよいように)
18
+ var model_core_2 = require("@yanqirenshi/model.core");
19
+ Object.defineProperty(exports, "ModelError", { enumerable: true, get: function () { return model_core_2.ModelError; } });
20
+ Object.defineProperty(exports, "getByPath", { enumerable: true, get: function () { return model_core_2.getByPath; } });
21
+ Object.defineProperty(exports, "mergePatch", { enumerable: true, get: function () { return model_core_2.mergePatch; } });
22
+ Object.defineProperty(exports, "isColor", { enumerable: true, get: function () { return model_core_2.isColor; } });
23
+ /** レコードを置くコレクション */
24
+ function collectionOf(record) {
25
+ return (0, defaults_js_1.isEdge)(record) ? 'edges' : 'nodes';
26
+ }
27
+ /** インスペクタの見出し。ラベルの文字、無ければ「種類 id」(線は「接続元 → 接続先」) */
28
+ function titleOf(record) {
29
+ if ((0, defaults_js_1.isEdge)(record))
30
+ return `${String(record.from.id)} → ${String(record.to.id)}`;
31
+ const text = record.label && typeof record.label === 'object' ? record.label.text : null;
32
+ if (typeof text === 'string' && text !== '')
33
+ return text;
34
+ return `${record.type || ''} ${String(record.id)}`.trim();
35
+ }
36
+ /**
37
+ * ドラッグで動かしたノードの位置を保存する patch。
38
+ * 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。
39
+ * 子のノード(COMPONENT を含む)の position は親の中での位置なので、親を動かしても子の patch は要らない。
40
+ */
41
+ function movePatch(node, move) {
42
+ return (0, model_core_1.movePatch)(node, move, 'nodes');
43
+ }
44
+ // インスペクタの入力のうち、図に描いている値(withDefaults)から変わった項目だけ
45
+ function changedValues(record, values) {
46
+ return (0, model_core_1.changedValues)((0, defaults_js_1.withDefaults)(record), values, (0, fields_js_1.fieldsOf)(record));
47
+ }
48
+ // 幅・高さは 0 以上(0 は「子に合わせる」)。
49
+ // 枠線・線の太さは 0 より大きい数(d3.deployment は 0 を「書いていない」と見て既定に倒すので、0 は入れられない)
50
+ const LENGTH = { hint: '(0 で子に合わせる)' };
51
+ // ノードの入力 → patch(label.text / type / description / link.url / size / background / border)
52
+ function nodePatch(node, values) {
53
+ const patch = {};
54
+ for (const [path, value] of changedValues(node, values)) {
55
+ switch (path) {
56
+ case 'label.text':
57
+ // 名称は空にできない(d3.deployment は名称の無いノードに '????????' を描く)
58
+ if (value === '')
59
+ throw new model_core_1.ModelError('名称を入力してください。');
60
+ (0, model_core_1.put)(patch, path, value);
61
+ break;
62
+ case 'type':
63
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toOption)(value, defaults_js_1.NODE_TYPES, '種類'));
64
+ break;
65
+ case 'description':
66
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toOptionalText)(value));
67
+ break;
68
+ case 'link.url':
69
+ // URL を空にするとリンクごと除く(d3.deployment は url の無い link を描かない)
70
+ if (value === '')
71
+ patch.link = null;
72
+ else
73
+ (0, model_core_1.put)(patch, path, value);
74
+ break;
75
+ case 'size.w':
76
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toLength)(value, '幅', LENGTH));
77
+ break;
78
+ case 'size.h':
79
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toLength)(value, '高さ', LENGTH));
80
+ break;
81
+ case 'background.color':
82
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toColor)(value, '背景色'));
83
+ break;
84
+ case 'border.color':
85
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toColor)(value, '枠線の色'));
86
+ break;
87
+ case 'border.width':
88
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toWidth)(value, '枠線の太さ'));
89
+ break;
90
+ }
91
+ }
92
+ return patch;
93
+ }
94
+ const END_LABELS = { from: '接続元', to: '接続先' };
95
+ // 線の入力 → patch(from / to の角度・stroke・矢印)
96
+ function edgePatch(edge, values) {
97
+ const patch = {};
98
+ for (const [path, value] of changedValues(edge, values)) {
99
+ switch (path) {
100
+ case 'from.position':
101
+ case 'to.position':
102
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toDegree)(value, `${END_LABELS[path.split('.')[0]]}の位置`));
103
+ break;
104
+ case 'stroke.color':
105
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toColor)(value, '線の色'));
106
+ break;
107
+ case 'stroke.width':
108
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toWidth)(value, '線の太さ'));
109
+ break;
110
+ case 'stroke.marker.start':
111
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toBoolean)(value, '始点の矢印'));
112
+ break;
113
+ case 'stroke.marker.end':
114
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toBoolean)(value, '終点の矢印'));
115
+ break;
116
+ }
117
+ }
118
+ // 矢印を片方だけ書くと d3.deployment の Edge.normalize は marker を丸ごと入力で置き換える(無い側は undefined =
119
+ // start は偽、end は truthy 判定で偽)ので、片方を変えたときはもう片方も描いている値で埋める
120
+ const marker = patch.stroke?.marker;
121
+ if (marker) {
122
+ const effective = (0, defaults_js_1.withDefaults)(edge).stroke.marker;
123
+ if (marker.start === undefined)
124
+ marker.start = effective.start;
125
+ if (marker.end === undefined)
126
+ marker.end = effective.end;
127
+ }
128
+ return patch;
129
+ }
130
+ /**
131
+ * インスペクタの「適用」の値を、保存する patch に変換する。
132
+ * 図に描いている値から変えた項目だけを patch に入れる(変えていない既定の値まで書き込まないため)。
133
+ * 変更が無ければ patch は {}。
134
+ */
135
+ function applyPatch(record, values) {
136
+ return {
137
+ collection: collectionOf(record),
138
+ id: record.id,
139
+ patch: (0, defaults_js_1.isEdge)(record) ? edgePatch(record, values) : nodePatch(record, values),
140
+ };
141
+ }