@yanqirenshi/model.deployment 0.1.0 → 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/README.md +2 -1
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +6 -29
- package/dist/cjs/check.js +6 -29
- package/dist/cjs/index.js +2 -2
- package/dist/cjs/patch.js +30 -151
- package/dist/index.js +2 -2
- package/dist/patch.d.ts +2 -18
- package/dist/patch.d.ts.map +1 -1
- package/dist/patch.js +12 -133
- package/dist/types.d.ts +7 -17
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/check.ts +7 -40
- package/src/index.ts +2 -2
- package/src/patch.ts +17 -176
- package/src/types.ts +8 -16
package/README.md
CHANGED
|
@@ -26,7 +26,8 @@ d3.deployment は、このパッケージの既定値(枠線・背景・線の s
|
|
|
26
26
|
model.deployment・colonoscope の間に横の依存はありません。組み合わせるのは利用側だけです。
|
|
27
27
|
これは [Foolsgolds/Assholes#138](https://github.com/Foolsgolds/Assholes/issues/138) の派生([#146](https://github.com/Foolsgolds/Assholes/issues/146))で、
|
|
28
28
|
ノードと線の図(graph 系: [model.unchi](../model.unchi) / [model.sitemap](../model.sitemap) / model.deployment)の 3 つ目です。
|
|
29
|
-
`check` / `movePatch`
|
|
29
|
+
`check` / `movePatch` / `mergePatch` と入力の読み取り部品は共通部品 [model.core](../model.core) のものに
|
|
30
|
+
model.deployment の型を付けて公開しています([#151](https://github.com/Foolsgolds/Assholes/issues/151))。
|
|
30
31
|
|
|
31
32
|
## レコードの形
|
|
32
33
|
|
package/dist/check.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"
|
|
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
CHANGED
|
@@ -2,21 +2,13 @@
|
|
|
2
2
|
// スキーマ(schema.ts)に合っているデータを渡す。戻り値はエラーのメッセージの配列(空なら問題なし)。
|
|
3
3
|
//
|
|
4
4
|
// iHub の server/validation/rules.mjs の checkGraph('deployment', data) と同じ検査・同じ文言。
|
|
5
|
-
//
|
|
5
|
+
// 検査そのものは graph 系(unchi / sitemap / deployment)で共通なので model.core の checkGraph にあり、
|
|
6
|
+
// ここでは model.deployment の型を付けて公開する(#151)。
|
|
6
7
|
// メッセージの先頭はコレクション名で、prefix を渡すと 'deployment/nodes: ...' のように付けられる。
|
|
8
|
+
import { checkGraph, flattenNodes, findById as findInList } from '@yanqirenshi/model.core';
|
|
7
9
|
/** 入れ子のノード(children)を平らにする(親 → 子 の順) */
|
|
8
10
|
export function flatten(nodes) {
|
|
9
|
-
return nodes
|
|
10
|
-
}
|
|
11
|
-
// list の中で id が重複していればエラーにする。id の集合を返す。
|
|
12
|
-
function checkUnique(errors, label, list) {
|
|
13
|
-
const values = new Set();
|
|
14
|
-
for (const d of list) {
|
|
15
|
-
if (values.has(d.id))
|
|
16
|
-
errors.push(`${label}: id が重複しています。(id=${String(d.id)})`);
|
|
17
|
-
values.add(d.id);
|
|
18
|
-
}
|
|
19
|
-
return values;
|
|
11
|
+
return flattenNodes(nodes);
|
|
20
12
|
}
|
|
21
13
|
/**
|
|
22
14
|
* 図全体を検査する。
|
|
@@ -27,24 +19,9 @@ function checkUnique(errors, label, list) {
|
|
|
27
19
|
* @param prefix メッセージのコレクション名の前に付ける文字列(例: 'deployment/')
|
|
28
20
|
*/
|
|
29
21
|
export function check(data, prefix = '') {
|
|
30
|
-
|
|
31
|
-
const node_ids = checkUnique(errors, `${prefix}nodes`, flatten(data.nodes || []));
|
|
32
|
-
checkUnique(errors, `${prefix}edges`, data.edges || []);
|
|
33
|
-
for (const edge of data.edges || [])
|
|
34
|
-
for (const end of ['from', 'to'])
|
|
35
|
-
if (!edge[end] || !node_ids.has(edge[end].id))
|
|
36
|
-
errors.push(`${prefix}edges: ${end}.id のノードが見つかりません。(id=${String(edge.id)}, ${end}.id=${String(edge[end]?.id)})`);
|
|
37
|
-
return errors;
|
|
22
|
+
return checkGraph(data, prefix);
|
|
38
23
|
}
|
|
39
24
|
/** ノード(子を含む)か線を id で探す(無ければ null) */
|
|
40
25
|
export function findById(list, id) {
|
|
41
|
-
|
|
42
|
-
if (d.id === id)
|
|
43
|
-
return d;
|
|
44
|
-
const children = d.children;
|
|
45
|
-
const hit = children ? findById(children, id) : null;
|
|
46
|
-
if (hit)
|
|
47
|
-
return hit;
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
26
|
+
return findInList(list, id);
|
|
50
27
|
}
|
package/dist/cjs/check.js
CHANGED
|
@@ -3,25 +3,17 @@
|
|
|
3
3
|
// スキーマ(schema.ts)に合っているデータを渡す。戻り値はエラーのメッセージの配列(空なら問題なし)。
|
|
4
4
|
//
|
|
5
5
|
// iHub の server/validation/rules.mjs の checkGraph('deployment', data) と同じ検査・同じ文言。
|
|
6
|
-
//
|
|
6
|
+
// 検査そのものは graph 系(unchi / sitemap / deployment)で共通なので model.core の checkGraph にあり、
|
|
7
|
+
// ここでは model.deployment の型を付けて公開する(#151)。
|
|
7
8
|
// メッセージの先頭はコレクション名で、prefix を渡すと 'deployment/nodes: ...' のように付けられる。
|
|
8
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
10
|
exports.flatten = flatten;
|
|
10
11
|
exports.check = check;
|
|
11
12
|
exports.findById = findById;
|
|
13
|
+
const model_core_1 = require("@yanqirenshi/model.core");
|
|
12
14
|
/** 入れ子のノード(children)を平らにする(親 → 子 の順) */
|
|
13
15
|
function flatten(nodes) {
|
|
14
|
-
return
|
|
15
|
-
}
|
|
16
|
-
// list の中で id が重複していればエラーにする。id の集合を返す。
|
|
17
|
-
function checkUnique(errors, label, list) {
|
|
18
|
-
const values = new Set();
|
|
19
|
-
for (const d of list) {
|
|
20
|
-
if (values.has(d.id))
|
|
21
|
-
errors.push(`${label}: id が重複しています。(id=${String(d.id)})`);
|
|
22
|
-
values.add(d.id);
|
|
23
|
-
}
|
|
24
|
-
return values;
|
|
16
|
+
return (0, model_core_1.flattenNodes)(nodes);
|
|
25
17
|
}
|
|
26
18
|
/**
|
|
27
19
|
* 図全体を検査する。
|
|
@@ -32,24 +24,9 @@ function checkUnique(errors, label, list) {
|
|
|
32
24
|
* @param prefix メッセージのコレクション名の前に付ける文字列(例: 'deployment/')
|
|
33
25
|
*/
|
|
34
26
|
function check(data, prefix = '') {
|
|
35
|
-
|
|
36
|
-
const node_ids = checkUnique(errors, `${prefix}nodes`, flatten(data.nodes || []));
|
|
37
|
-
checkUnique(errors, `${prefix}edges`, data.edges || []);
|
|
38
|
-
for (const edge of data.edges || [])
|
|
39
|
-
for (const end of ['from', 'to'])
|
|
40
|
-
if (!edge[end] || !node_ids.has(edge[end].id))
|
|
41
|
-
errors.push(`${prefix}edges: ${end}.id のノードが見つかりません。(id=${String(edge.id)}, ${end}.id=${String(edge[end]?.id)})`);
|
|
42
|
-
return errors;
|
|
27
|
+
return (0, model_core_1.checkGraph)(data, prefix);
|
|
43
28
|
}
|
|
44
29
|
/** ノード(子を含む)か線を id で探す(無ければ null) */
|
|
45
30
|
function findById(list, id) {
|
|
46
|
-
|
|
47
|
-
if (d.id === id)
|
|
48
|
-
return d;
|
|
49
|
-
const children = d.children;
|
|
50
|
-
const hit = children ? findById(children, id) : null;
|
|
51
|
-
if (hit)
|
|
52
|
-
return hit;
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
31
|
+
return (0, model_core_1.findById)(list, id);
|
|
55
32
|
}
|
package/dist/cjs/index.js
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
// 編集できる項目: fieldsOf(record) / NODE_FIELDS / EDGE_FIELDS
|
|
9
9
|
// patch への変換: movePatch(node, move) / applyPatch(record, values) / mergePatch(record, patch)
|
|
10
10
|
//
|
|
11
|
-
// graph 系(unchi / sitemap / deployment)の 3 つ目。check / flatten / movePatch / mergePatch / getByPath / isColor
|
|
12
|
-
//
|
|
11
|
+
// graph 系(unchi / sitemap / deployment)の 3 つ目。check / flatten / findById / movePatch / mergePatch / getByPath / isColor と
|
|
12
|
+
// 入力の読み取り部品は model.core(#151)のもので、ここでは model.deployment の型を付けて公開している。
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
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
15
|
var defaults_js_1 = require("./defaults.js");
|
package/dist/cjs/patch.js
CHANGED
|
@@ -4,25 +4,22 @@
|
|
|
4
4
|
// 戻り値は { collection, id, patch }。利用側(iHub など)は図の種類を知らずにこれを保存すればよい。
|
|
5
5
|
// 入力の検証に失敗したときは ModelError を投げる(メッセージは利用者にそのまま見せられる日本語)。
|
|
6
6
|
// model.sitemap の patch.ts を元にしている(ラベルは label.text、種類は NODE / COMPONENT、線に矢印の有無がある)。
|
|
7
|
+
// 読み取り部品(toColor など)と patch の道具は model.core にある(#151)。
|
|
7
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.ModelError = void 0;
|
|
9
|
+
exports.isColor = exports.mergePatch = exports.getByPath = exports.ModelError = void 0;
|
|
9
10
|
exports.collectionOf = collectionOf;
|
|
10
11
|
exports.titleOf = titleOf;
|
|
11
|
-
exports.getByPath = getByPath;
|
|
12
|
-
exports.mergePatch = mergePatch;
|
|
13
12
|
exports.movePatch = movePatch;
|
|
14
|
-
exports.isColor = isColor;
|
|
15
13
|
exports.applyPatch = applyPatch;
|
|
14
|
+
const model_core_1 = require("@yanqirenshi/model.core");
|
|
16
15
|
const defaults_js_1 = require("./defaults.js");
|
|
17
16
|
const fields_js_1 = require("./fields.js");
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
exports.ModelError = ModelError;
|
|
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; } });
|
|
26
23
|
/** レコードを置くコレクション */
|
|
27
24
|
function collectionOf(record) {
|
|
28
25
|
return (0, defaults_js_1.isEdge)(record) ? 'edges' : 'nodes';
|
|
@@ -36,139 +33,21 @@ function titleOf(record) {
|
|
|
36
33
|
return text;
|
|
37
34
|
return `${record.type || ''} ${String(record.id)}`.trim();
|
|
38
35
|
}
|
|
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
36
|
/**
|
|
64
37
|
* ドラッグで動かしたノードの位置を保存する patch。
|
|
65
38
|
* 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。
|
|
66
39
|
* 子のノード(COMPONENT を含む)の position は親の中での位置なので、親を動かしても子の patch は要らない。
|
|
67
40
|
*/
|
|
68
41
|
function movePatch(node, move) {
|
|
69
|
-
|
|
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;
|
|
42
|
+
return (0, model_core_1.movePatch)(node, move, 'nodes');
|
|
131
43
|
}
|
|
132
|
-
//
|
|
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
|
-
*/
|
|
44
|
+
// インスペクタの入力のうち、図に描いている値(withDefaults)から変わった項目だけ
|
|
157
45
|
function changedValues(record, values) {
|
|
158
|
-
|
|
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;
|
|
46
|
+
return (0, model_core_1.changedValues)((0, defaults_js_1.withDefaults)(record), values, (0, fields_js_1.fieldsOf)(record));
|
|
171
47
|
}
|
|
48
|
+
// 幅・高さは 0 以上(0 は「子に合わせる」)。
|
|
49
|
+
// 枠線・線の太さは 0 より大きい数(d3.deployment は 0 を「書いていない」と見て既定に倒すので、0 は入れられない)
|
|
50
|
+
const LENGTH = { hint: '(0 で子に合わせる)' };
|
|
172
51
|
// ノードの入力 → patch(label.text / type / description / link.url / size / background / border)
|
|
173
52
|
function nodePatch(node, values) {
|
|
174
53
|
const patch = {};
|
|
@@ -177,36 +56,36 @@ function nodePatch(node, values) {
|
|
|
177
56
|
case 'label.text':
|
|
178
57
|
// 名称は空にできない(d3.deployment は名称の無いノードに '????????' を描く)
|
|
179
58
|
if (value === '')
|
|
180
|
-
throw new ModelError('名称を入力してください。');
|
|
181
|
-
put(patch, path, value);
|
|
59
|
+
throw new model_core_1.ModelError('名称を入力してください。');
|
|
60
|
+
(0, model_core_1.put)(patch, path, value);
|
|
182
61
|
break;
|
|
183
62
|
case 'type':
|
|
184
|
-
put(patch, path, toOption(value, defaults_js_1.NODE_TYPES, '種類'));
|
|
63
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toOption)(value, defaults_js_1.NODE_TYPES, '種類'));
|
|
185
64
|
break;
|
|
186
65
|
case 'description':
|
|
187
|
-
put(patch, path, toOptionalText(value));
|
|
66
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toOptionalText)(value));
|
|
188
67
|
break;
|
|
189
68
|
case 'link.url':
|
|
190
69
|
// URL を空にするとリンクごと除く(d3.deployment は url の無い link を描かない)
|
|
191
70
|
if (value === '')
|
|
192
71
|
patch.link = null;
|
|
193
72
|
else
|
|
194
|
-
put(patch, path, value);
|
|
73
|
+
(0, model_core_1.put)(patch, path, value);
|
|
195
74
|
break;
|
|
196
75
|
case 'size.w':
|
|
197
|
-
put(patch, path, toLength(value, '幅'));
|
|
76
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toLength)(value, '幅', LENGTH));
|
|
198
77
|
break;
|
|
199
78
|
case 'size.h':
|
|
200
|
-
put(patch, path, toLength(value, '高さ'));
|
|
79
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toLength)(value, '高さ', LENGTH));
|
|
201
80
|
break;
|
|
202
81
|
case 'background.color':
|
|
203
|
-
put(patch, path, toColor(value, '背景色'));
|
|
82
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toColor)(value, '背景色'));
|
|
204
83
|
break;
|
|
205
84
|
case 'border.color':
|
|
206
|
-
put(patch, path, toColor(value, '枠線の色'));
|
|
85
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toColor)(value, '枠線の色'));
|
|
207
86
|
break;
|
|
208
87
|
case 'border.width':
|
|
209
|
-
put(patch, path, toWidth(value, '枠線の太さ'));
|
|
88
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toWidth)(value, '枠線の太さ'));
|
|
210
89
|
break;
|
|
211
90
|
}
|
|
212
91
|
}
|
|
@@ -220,19 +99,19 @@ function edgePatch(edge, values) {
|
|
|
220
99
|
switch (path) {
|
|
221
100
|
case 'from.position':
|
|
222
101
|
case 'to.position':
|
|
223
|
-
put(patch, path, toDegree(value, `${END_LABELS[path.split('.')[0]]}の位置`));
|
|
102
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toDegree)(value, `${END_LABELS[path.split('.')[0]]}の位置`));
|
|
224
103
|
break;
|
|
225
104
|
case 'stroke.color':
|
|
226
|
-
put(patch, path, toColor(value, '線の色'));
|
|
105
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toColor)(value, '線の色'));
|
|
227
106
|
break;
|
|
228
107
|
case 'stroke.width':
|
|
229
|
-
put(patch, path, toWidth(value, '線の太さ'));
|
|
108
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toWidth)(value, '線の太さ'));
|
|
230
109
|
break;
|
|
231
110
|
case 'stroke.marker.start':
|
|
232
|
-
put(patch, path, toBoolean(value, '始点の矢印'));
|
|
111
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toBoolean)(value, '始点の矢印'));
|
|
233
112
|
break;
|
|
234
113
|
case 'stroke.marker.end':
|
|
235
|
-
put(patch, path, toBoolean(value, '終点の矢印'));
|
|
114
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toBoolean)(value, '終点の矢印'));
|
|
236
115
|
break;
|
|
237
116
|
}
|
|
238
117
|
}
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
// 編集できる項目: fieldsOf(record) / NODE_FIELDS / EDGE_FIELDS
|
|
8
8
|
// patch への変換: movePatch(node, move) / applyPatch(record, values) / mergePatch(record, patch)
|
|
9
9
|
//
|
|
10
|
-
// graph 系(unchi / sitemap / deployment)の 3 つ目。check / flatten / movePatch / mergePatch / getByPath / isColor
|
|
11
|
-
//
|
|
10
|
+
// graph 系(unchi / sitemap / deployment)の 3 つ目。check / flatten / findById / movePatch / mergePatch / getByPath / isColor と
|
|
11
|
+
// 入力の読み取り部品は model.core(#151)のもので、ここでは model.deployment の型を付けて公開している。
|
|
12
12
|
export { NODE_TYPES, NODE_STYLE_DEFAULTS, LABEL_DEFAULTS, NODE_DEFAULTS, EDGE_DEFAULTS, isEdge, nodeStyle, edgeStyle, degreeOf, withDefaults, } from './defaults.js';
|
|
13
13
|
export { NODES_SCHEMA, EDGES_SCHEMA, SCHEMAS } from './schema.js';
|
|
14
14
|
export { check, flatten, findById } from './check.js';
|
package/dist/patch.d.ts
CHANGED
|
@@ -1,31 +1,15 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export declare class ModelError extends Error {
|
|
4
|
-
constructor(message: string);
|
|
5
|
-
}
|
|
1
|
+
import type { DeploymentChange, DeploymentCollection, DeploymentMove, DeploymentNodeRecord, DeploymentRecord, DeploymentValues } from './types.js';
|
|
2
|
+
export { ModelError, getByPath, mergePatch, isColor } from '@yanqirenshi/model.core';
|
|
6
3
|
/** レコードを置くコレクション */
|
|
7
4
|
export declare function collectionOf(record: DeploymentRecord): DeploymentCollection;
|
|
8
5
|
/** インスペクタの見出し。ラベルの文字、無ければ「種類 id」(線は「接続元 → 接続先」) */
|
|
9
6
|
export declare function titleOf(record: DeploymentRecord): string;
|
|
10
|
-
/** ドット区切りの path で値を取り出す */
|
|
11
|
-
export declare function getByPath(obj: unknown, path: string): unknown;
|
|
12
|
-
/**
|
|
13
|
-
* JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
|
|
14
|
-
* null の項目は除く。保存したあと、表示を先に更新するときに使う。
|
|
15
|
-
*/
|
|
16
|
-
export declare function mergePatch<T extends object>(record: T, patch: MergePatch): T;
|
|
17
7
|
/**
|
|
18
8
|
* ドラッグで動かしたノードの位置を保存する patch。
|
|
19
9
|
* 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。
|
|
20
10
|
* 子のノード(COMPONENT を含む)の position は親の中での位置なので、親を動かしても子の patch は要らない。
|
|
21
11
|
*/
|
|
22
12
|
export declare function movePatch(node: DeploymentNodeRecord, move: DeploymentMove): DeploymentChange;
|
|
23
|
-
/**
|
|
24
|
-
* 色として読める文字列か。
|
|
25
|
-
* ブラウザでは CSS.supports('color', v) で判定する。Node など CSS の無い環境では、
|
|
26
|
-
* #rgb / #rrggbb / #rrggbbaa・rgb() などの関数表記・英字だけの色名、を通す緩い判定にする。
|
|
27
|
-
*/
|
|
28
|
-
export declare function isColor(value: string): boolean;
|
|
29
13
|
/**
|
|
30
14
|
* インスペクタの「適用」の値を、保存する patch に変換する。
|
|
31
15
|
* 図に描いている値から変えた項目だけを patch に入れる(変えていない既定の値まで書き込まないため)。
|
package/dist/patch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../src/patch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../src/patch.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACE,gBAAgB,EAAE,oBAAoB,EAAwB,cAAc,EAAE,oBAAoB,EAC9G,gBAAgB,EAAE,gBAAgB,EACnC,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAErF,oBAAoB;AACpB,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,oBAAoB,CAE3E;AAED,mDAAmD;AACnD,wBAAgB,OAAO,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAUxD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,cAAc,GAAG,gBAAgB,CAE5F;AAmGD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAM/F"}
|
package/dist/patch.js
CHANGED
|
@@ -3,15 +3,12 @@
|
|
|
3
3
|
// 戻り値は { collection, id, patch }。利用側(iHub など)は図の種類を知らずにこれを保存すればよい。
|
|
4
4
|
// 入力の検証に失敗したときは ModelError を投げる(メッセージは利用者にそのまま見せられる日本語)。
|
|
5
5
|
// model.sitemap の patch.ts を元にしている(ラベルは label.text、種類は NODE / COMPONENT、線に矢印の有無がある)。
|
|
6
|
+
// 読み取り部品(toColor など)と patch の道具は model.core にある(#151)。
|
|
7
|
+
import { ModelError, changedValues as changedValuesOf, movePatch as movePatchTo, put, toBoolean, toColor, toDegree, toLength, toOption, toOptionalText, toWidth, } from '@yanqirenshi/model.core';
|
|
6
8
|
import { NODE_TYPES, isEdge, withDefaults } from './defaults.js';
|
|
7
9
|
import { fieldsOf } from './fields.js';
|
|
8
|
-
|
|
9
|
-
export
|
|
10
|
-
constructor(message) {
|
|
11
|
-
super(message);
|
|
12
|
-
this.name = 'ModelError';
|
|
13
|
-
}
|
|
14
|
-
}
|
|
10
|
+
// model.core のものをそのまま公開する(利用側が model.deployment だけを読めばよいように)
|
|
11
|
+
export { ModelError, getByPath, mergePatch, isColor } from '@yanqirenshi/model.core';
|
|
15
12
|
/** レコードを置くコレクション */
|
|
16
13
|
export function collectionOf(record) {
|
|
17
14
|
return isEdge(record) ? 'edges' : 'nodes';
|
|
@@ -25,139 +22,21 @@ export function titleOf(record) {
|
|
|
25
22
|
return text;
|
|
26
23
|
return `${record.type || ''} ${String(record.id)}`.trim();
|
|
27
24
|
}
|
|
28
|
-
/** ドット区切りの path で値を取り出す */
|
|
29
|
-
export function getByPath(obj, path) {
|
|
30
|
-
return path.split('.').reduce((acc, key) => (acc == null ? acc : acc[key]), obj);
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
|
|
34
|
-
* null の項目は除く。保存したあと、表示を先に更新するときに使う。
|
|
35
|
-
*/
|
|
36
|
-
export function mergePatch(record, patch) {
|
|
37
|
-
const out = { ...record };
|
|
38
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
39
|
-
if (value === null) {
|
|
40
|
-
delete out[key];
|
|
41
|
-
}
|
|
42
|
-
else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
43
|
-
const base = out[key];
|
|
44
|
-
out[key] = mergePatch(base && typeof base === 'object' && !Array.isArray(base) ? base : {}, value);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
out[key] = value;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return out;
|
|
51
|
-
}
|
|
52
25
|
/**
|
|
53
26
|
* ドラッグで動かしたノードの位置を保存する patch。
|
|
54
27
|
* 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。
|
|
55
28
|
* 子のノード(COMPONENT を含む)の position は親の中での位置なので、親を動かしても子の patch は要らない。
|
|
56
29
|
*/
|
|
57
30
|
export function movePatch(node, move) {
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
collection: 'nodes',
|
|
61
|
-
id: node.id,
|
|
62
|
-
patch: {
|
|
63
|
-
position: {
|
|
64
|
-
x: Math.round(base.x + move.dx),
|
|
65
|
-
y: Math.round(base.y + move.dy),
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
// ---- 入力の読み取り ------------------------------------------------------------
|
|
71
|
-
function toNumber(value) {
|
|
72
|
-
const n = Number(value);
|
|
73
|
-
return value !== '' && Number.isFinite(n) ? n : null;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* 色として読める文字列か。
|
|
77
|
-
* ブラウザでは CSS.supports('color', v) で判定する。Node など CSS の無い環境では、
|
|
78
|
-
* #rgb / #rrggbb / #rrggbbaa・rgb() などの関数表記・英字だけの色名、を通す緩い判定にする。
|
|
79
|
-
*/
|
|
80
|
-
export function isColor(value) {
|
|
81
|
-
if (value === '')
|
|
82
|
-
return false;
|
|
83
|
-
const css = globalThis.CSS;
|
|
84
|
-
if (css && typeof css.supports === 'function')
|
|
85
|
-
return css.supports('color', value);
|
|
86
|
-
return /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)
|
|
87
|
-
|| /^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(.+\)$/i.test(value)
|
|
88
|
-
|| /^[a-z]+$/i.test(value);
|
|
89
|
-
}
|
|
90
|
-
function toColor(value, label) {
|
|
91
|
-
if (!isColor(value))
|
|
92
|
-
throw new ModelError(`${label}は、色 (#ff0000 や red など) で入力してください。`);
|
|
93
|
-
return value;
|
|
94
|
-
}
|
|
95
|
-
// 0 より大きい数(枠線・線の太さ。d3.deployment は 0 を「書いていない」と見て既定に倒すので、0 は入れられない)
|
|
96
|
-
function toWidth(value, label) {
|
|
97
|
-
const n = toNumber(value);
|
|
98
|
-
if (n === null || n <= 0)
|
|
99
|
-
throw new ModelError(`${label}は、0 より大きい数で入力してください。`);
|
|
100
|
-
return n;
|
|
101
|
-
}
|
|
102
|
-
// 0 以上の整数(幅・高さ。0 は「子に合わせる」)
|
|
103
|
-
function toLength(value, label) {
|
|
104
|
-
const n = toNumber(value);
|
|
105
|
-
if (n === null || n < 0)
|
|
106
|
-
throw new ModelError(`${label}は 0 以上の数で入力してください。(0 で子に合わせる)`);
|
|
107
|
-
return Math.round(n);
|
|
108
|
-
}
|
|
109
|
-
// 角度(0 以上 360 未満の整数に正規化)
|
|
110
|
-
function toDegree(value, label) {
|
|
111
|
-
const n = toNumber(value);
|
|
112
|
-
if (n === null)
|
|
113
|
-
throw new ModelError(`${label}は、角度 (数) で入力してください。`);
|
|
114
|
-
return ((Math.round(n) % 360) + 360) % 360;
|
|
115
|
-
}
|
|
116
|
-
function toOption(value, options, label) {
|
|
117
|
-
if (!options.includes(value))
|
|
118
|
-
throw new ModelError(`${label}は、${options.join(' / ')} のどれかで入力してください。`);
|
|
119
|
-
return value;
|
|
31
|
+
return movePatchTo(node, move, 'nodes');
|
|
120
32
|
}
|
|
121
|
-
//
|
|
122
|
-
function toBoolean(value, label) {
|
|
123
|
-
if (value !== 'true' && value !== 'false')
|
|
124
|
-
throw new ModelError(`${label}は、true / false のどちらかで入力してください。`);
|
|
125
|
-
return value === 'true';
|
|
126
|
-
}
|
|
127
|
-
// 空にできる文字列。空なら null(JSON Merge Patch で項目を除く)
|
|
128
|
-
function toOptionalText(value) {
|
|
129
|
-
return value === '' ? null : value;
|
|
130
|
-
}
|
|
131
|
-
// patch の入れ子に値を置く(patch.border.color = v のように)
|
|
132
|
-
function put(patch, path, value) {
|
|
133
|
-
const keys = path.split('.');
|
|
134
|
-
let cur = patch;
|
|
135
|
-
for (const key of keys.slice(0, -1)) {
|
|
136
|
-
if (!cur[key] || typeof cur[key] !== 'object')
|
|
137
|
-
cur[key] = {};
|
|
138
|
-
cur = cur[key];
|
|
139
|
-
}
|
|
140
|
-
cur[keys[keys.length - 1]] = value;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* インスペクタの入力のうち、図に描いている値(withDefaults で埋めた値)から変わった項目だけを返す。
|
|
144
|
-
* 前後の空白は除く。読み取り専用の項目と、values に無い項目は含めない。
|
|
145
|
-
*/
|
|
33
|
+
// インスペクタの入力のうち、図に描いている値(withDefaults)から変わった項目だけ
|
|
146
34
|
function changedValues(record, values) {
|
|
147
|
-
|
|
148
|
-
const out = new Map();
|
|
149
|
-
for (const field of fieldsOf(record)) {
|
|
150
|
-
if (field.type === 'readonly')
|
|
151
|
-
continue;
|
|
152
|
-
const value = values[field.path];
|
|
153
|
-
if (value === undefined)
|
|
154
|
-
continue;
|
|
155
|
-
const current = getByPath(effective, field.path);
|
|
156
|
-
if (value.trim() !== String(current ?? ''))
|
|
157
|
-
out.set(field.path, value.trim());
|
|
158
|
-
}
|
|
159
|
-
return out;
|
|
35
|
+
return changedValuesOf(withDefaults(record), values, fieldsOf(record));
|
|
160
36
|
}
|
|
37
|
+
// 幅・高さは 0 以上(0 は「子に合わせる」)。
|
|
38
|
+
// 枠線・線の太さは 0 より大きい数(d3.deployment は 0 を「書いていない」と見て既定に倒すので、0 は入れられない)
|
|
39
|
+
const LENGTH = { hint: '(0 で子に合わせる)' };
|
|
161
40
|
// ノードの入力 → patch(label.text / type / description / link.url / size / background / border)
|
|
162
41
|
function nodePatch(node, values) {
|
|
163
42
|
const patch = {};
|
|
@@ -183,10 +62,10 @@ function nodePatch(node, values) {
|
|
|
183
62
|
put(patch, path, value);
|
|
184
63
|
break;
|
|
185
64
|
case 'size.w':
|
|
186
|
-
put(patch, path, toLength(value, '幅'));
|
|
65
|
+
put(patch, path, toLength(value, '幅', LENGTH));
|
|
187
66
|
break;
|
|
188
67
|
case 'size.h':
|
|
189
|
-
put(patch, path, toLength(value, '高さ'));
|
|
68
|
+
put(patch, path, toLength(value, '高さ', LENGTH));
|
|
190
69
|
break;
|
|
191
70
|
case 'background.color':
|
|
192
71
|
put(patch, path, toColor(value, '背景色'));
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MergePatch, ModelField, ModelFieldType, ModelMove, ModelValues } from '@yanqirenshi/model.core';
|
|
1
2
|
import type { Background, Border, Font, Position, Size, Stroke } from '@yanqirenshi/types';
|
|
2
3
|
/** レコードの id(nodes / edges とも。ノードは子も含めて一意) */
|
|
3
4
|
export type DeploymentId = number | string;
|
|
@@ -57,10 +58,8 @@ export interface DeploymentGraph {
|
|
|
57
58
|
}
|
|
58
59
|
/** レコードを置くコレクション */
|
|
59
60
|
export type DeploymentCollection = 'nodes' | 'edges';
|
|
60
|
-
/** JSON Merge Patch(RFC 7386)。null
|
|
61
|
-
export type MergePatch
|
|
62
|
-
[key: string]: unknown;
|
|
63
|
-
};
|
|
61
|
+
/** JSON Merge Patch(RFC 7386)。null は「その項目を除く」。model.core の MergePatch と同じ(iHub の PATCH /{collection}/{id} が受け取る形) */
|
|
62
|
+
export type { MergePatch };
|
|
64
63
|
/** 保存する変更。利用側は図の種類を知らずにこれを保存すればよい */
|
|
65
64
|
export interface DeploymentChange {
|
|
66
65
|
collection: DeploymentCollection;
|
|
@@ -68,22 +67,13 @@ export interface DeploymentChange {
|
|
|
68
67
|
patch: MergePatch;
|
|
69
68
|
}
|
|
70
69
|
/** ドラッグの移動量(世界座標。d3.deployment の callbacks.node.dragend が渡す move と同じ) */
|
|
71
|
-
export
|
|
72
|
-
dx: number;
|
|
73
|
-
dy: number;
|
|
74
|
-
}
|
|
70
|
+
export type DeploymentMove = ModelMove;
|
|
75
71
|
/**
|
|
76
72
|
* 編集できる項目。colonoscope の ColonoscopeField(path / label / type)と同じ形で、
|
|
77
73
|
* 取りうる値の一覧 options だけ足している(型は構造的に互換。colonoscope には依存しない)。
|
|
78
74
|
*/
|
|
79
|
-
export type DeploymentFieldType =
|
|
80
|
-
export
|
|
81
|
-
/** レコードの項目のパス(ドット区切り。例: 'border.color')。写し先ではなく実際の項目を指す */
|
|
82
|
-
path: string;
|
|
83
|
-
label: string;
|
|
84
|
-
type: DeploymentFieldType;
|
|
85
|
-
options?: readonly string[];
|
|
86
|
-
}
|
|
75
|
+
export type DeploymentFieldType = ModelFieldType;
|
|
76
|
+
export type DeploymentField = ModelField;
|
|
87
77
|
/** インスペクタの「適用」が渡す値(path → 入力した文字列) */
|
|
88
|
-
export type DeploymentValues =
|
|
78
|
+
export type DeploymentValues = ModelValues;
|
|
89
79
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE3F,6CAA6C;AAC7C,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,CAAC;AAE3C,qCAAqC;AACrC,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,WAAW,CAAC;AAEtD,cAAc;AACd,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED,wDAAwD;AACxD,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,YAAY,CAAC;IACjB,IAAI,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC;IACnC,KAAK,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACnC,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,mBAAmB;IACnB,IAAI,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9D,QAAQ,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,mEAAmE;AACnE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,YAAY,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,6BAA6B;AAC7B,MAAM,WAAW,oBAAqB,SAAQ,MAAM;IAClD,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC7C;AAED,aAAa;AACb,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,YAAY,CAAC;IACjB,IAAI,EAAE,iBAAiB,CAAC;IACxB,EAAE,EAAE,iBAAiB,CAAC;IACtB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;AAE3E,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAC/B,KAAK,CAAC,EAAE,oBAAoB,EAAE,CAAC;CAChC;AAED,oBAAoB;AACpB,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,OAAO,CAAC;AAErD,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC9G,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE3F,6CAA6C;AAC7C,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,CAAC;AAE3C,qCAAqC;AACrC,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,WAAW,CAAC;AAEtD,cAAc;AACd,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED,wDAAwD;AACxD,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,YAAY,CAAC;IACjB,IAAI,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC;IACnC,KAAK,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACnC,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,mBAAmB;IACnB,IAAI,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9D,QAAQ,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,mEAAmE;AACnE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,YAAY,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,6BAA6B;AAC7B,MAAM,WAAW,oBAAqB,SAAQ,MAAM;IAClD,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC7C;AAED,aAAa;AACb,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,YAAY,CAAC;IACjB,IAAI,EAAE,iBAAiB,CAAC;IACxB,EAAE,EAAE,iBAAiB,CAAC;IACtB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;AAE3E,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAC/B,KAAK,CAAC,EAAE,oBAAoB,EAAE,CAAC;CAChC;AAED,oBAAoB;AACpB,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,OAAO,CAAC;AAErD,qHAAqH;AACrH,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,oBAAoB,CAAC;IACjC,EAAE,EAAE,YAAY,CAAC;IACjB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,yEAAyE;AACzE,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC;AAEvC;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC;AAEjD,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC;AAEzC,sCAAsC;AACtC,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yanqirenshi/model.deployment",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Diagram model for deployment diagrams (d3.deployment) — JSON Schema, consistency rules, drawing defaults, editable fields and patch conversion. Pure data and functions: no React / d3 dependency",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
+
"@yanqirenshi/model.core": "^0.1.0",
|
|
40
41
|
"@yanqirenshi/types": "^0.1.2"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
package/src/check.ts
CHANGED
|
@@ -2,28 +2,17 @@
|
|
|
2
2
|
// スキーマ(schema.ts)に合っているデータを渡す。戻り値はエラーのメッセージの配列(空なら問題なし)。
|
|
3
3
|
//
|
|
4
4
|
// iHub の server/validation/rules.mjs の checkGraph('deployment', data) と同じ検査・同じ文言。
|
|
5
|
-
//
|
|
5
|
+
// 検査そのものは graph 系(unchi / sitemap / deployment)で共通なので model.core の checkGraph にあり、
|
|
6
|
+
// ここでは model.deployment の型を付けて公開する(#151)。
|
|
6
7
|
// メッセージの先頭はコレクション名で、prefix を渡すと 'deployment/nodes: ...' のように付けられる。
|
|
7
8
|
|
|
9
|
+
import { checkGraph, flattenNodes, findById as findInList } from '@yanqirenshi/model.core';
|
|
10
|
+
|
|
8
11
|
import type { DeploymentGraph, DeploymentId, DeploymentNodeRecord, DeploymentRecord } from './types.js';
|
|
9
12
|
|
|
10
13
|
/** 入れ子のノード(children)を平らにする(親 → 子 の順) */
|
|
11
14
|
export function flatten(nodes: DeploymentNodeRecord[]): DeploymentNodeRecord[] {
|
|
12
|
-
return nodes
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// list の中で id が重複していればエラーにする。id の集合を返す。
|
|
16
|
-
function checkUnique(errors: string[], label: string, list: { id: DeploymentId }[]): Set<DeploymentId> {
|
|
17
|
-
const values = new Set<DeploymentId>();
|
|
18
|
-
|
|
19
|
-
for (const d of list) {
|
|
20
|
-
if (values.has(d.id))
|
|
21
|
-
errors.push(`${label}: id が重複しています。(id=${String(d.id)})`);
|
|
22
|
-
|
|
23
|
-
values.add(d.id);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return values;
|
|
15
|
+
return flattenNodes(nodes);
|
|
27
16
|
}
|
|
28
17
|
|
|
29
18
|
/**
|
|
@@ -35,32 +24,10 @@ function checkUnique(errors: string[], label: string, list: { id: DeploymentId }
|
|
|
35
24
|
* @param prefix メッセージのコレクション名の前に付ける文字列(例: 'deployment/')
|
|
36
25
|
*/
|
|
37
26
|
export function check(data: DeploymentGraph, prefix = ''): string[] {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const node_ids = checkUnique(errors, `${prefix}nodes`, flatten(data.nodes || []));
|
|
41
|
-
|
|
42
|
-
checkUnique(errors, `${prefix}edges`, data.edges || []);
|
|
43
|
-
|
|
44
|
-
for (const edge of data.edges || [])
|
|
45
|
-
for (const end of ['from', 'to'] as const)
|
|
46
|
-
if (!edge[end] || !node_ids.has(edge[end].id))
|
|
47
|
-
errors.push(`${prefix}edges: ${end}.id のノードが見つかりません。(id=${String(edge.id)}, ${end}.id=${String(edge[end]?.id)})`);
|
|
48
|
-
|
|
49
|
-
return errors;
|
|
27
|
+
return checkGraph(data, prefix);
|
|
50
28
|
}
|
|
51
29
|
|
|
52
30
|
/** ノード(子を含む)か線を id で探す(無ければ null) */
|
|
53
31
|
export function findById<T extends DeploymentRecord>(list: readonly T[] | undefined, id: DeploymentId): T | null {
|
|
54
|
-
|
|
55
|
-
if (d.id === id)
|
|
56
|
-
return d;
|
|
57
|
-
|
|
58
|
-
const children = (d as DeploymentNodeRecord).children;
|
|
59
|
-
const hit = children ? findById(children as T[], id) : null;
|
|
60
|
-
|
|
61
|
-
if (hit)
|
|
62
|
-
return hit;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return null;
|
|
32
|
+
return findInList(list, id);
|
|
66
33
|
}
|
package/src/index.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
// 編集できる項目: fieldsOf(record) / NODE_FIELDS / EDGE_FIELDS
|
|
8
8
|
// patch への変換: movePatch(node, move) / applyPatch(record, values) / mergePatch(record, patch)
|
|
9
9
|
//
|
|
10
|
-
// graph 系(unchi / sitemap / deployment)の 3 つ目。check / flatten / movePatch / mergePatch / getByPath / isColor
|
|
11
|
-
//
|
|
10
|
+
// graph 系(unchi / sitemap / deployment)の 3 つ目。check / flatten / findById / movePatch / mergePatch / getByPath / isColor と
|
|
11
|
+
// 入力の読み取り部品は model.core(#151)のもので、ここでは model.deployment の型を付けて公開している。
|
|
12
12
|
|
|
13
13
|
export type {
|
|
14
14
|
DeploymentId, DeploymentNodeType, DeploymentNodeLabel, DeploymentNodeRecord, DeploymentEdgeEnd, DeploymentEdgeStroke,
|
package/src/patch.ts
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
// 戻り値は { collection, id, patch }。利用側(iHub など)は図の種類を知らずにこれを保存すればよい。
|
|
4
4
|
// 入力の検証に失敗したときは ModelError を投げる(メッセージは利用者にそのまま見せられる日本語)。
|
|
5
5
|
// model.sitemap の patch.ts を元にしている(ラベルは label.text、種類は NODE / COMPONENT、線に矢印の有無がある)。
|
|
6
|
+
// 読み取り部品(toColor など)と patch の道具は model.core にある(#151)。
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
ModelError, changedValues as changedValuesOf, movePatch as movePatchTo, put,
|
|
10
|
+
toBoolean, toColor, toDegree, toLength, toOption, toOptionalText, toWidth,
|
|
11
|
+
} from '@yanqirenshi/model.core';
|
|
6
12
|
|
|
7
13
|
import { NODE_TYPES, isEdge, withDefaults } from './defaults.js';
|
|
8
14
|
import { fieldsOf } from './fields.js';
|
|
@@ -11,13 +17,8 @@ import type {
|
|
|
11
17
|
DeploymentRecord, DeploymentValues,
|
|
12
18
|
} from './types.js';
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
export
|
|
16
|
-
constructor(message: string) {
|
|
17
|
-
super(message);
|
|
18
|
-
this.name = 'ModelError';
|
|
19
|
-
}
|
|
20
|
-
}
|
|
20
|
+
// model.core のものをそのまま公開する(利用側が model.deployment だけを読めばよいように)
|
|
21
|
+
export { ModelError, getByPath, mergePatch, isColor } from '@yanqirenshi/model.core';
|
|
21
22
|
|
|
22
23
|
/** レコードを置くコレクション */
|
|
23
24
|
export function collectionOf(record: DeploymentRecord): DeploymentCollection {
|
|
@@ -37,184 +38,24 @@ export function titleOf(record: DeploymentRecord): string {
|
|
|
37
38
|
return `${record.type || ''} ${String(record.id)}`.trim();
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
/** ドット区切りの path で値を取り出す */
|
|
41
|
-
export function getByPath(obj: unknown, path: string): unknown {
|
|
42
|
-
return path.split('.').reduce<unknown>(
|
|
43
|
-
(acc, key) => (acc == null ? acc : (acc as Record<string, unknown>)[key]),
|
|
44
|
-
obj,
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
|
|
50
|
-
* null の項目は除く。保存したあと、表示を先に更新するときに使う。
|
|
51
|
-
*/
|
|
52
|
-
export function mergePatch<T extends object>(record: T, patch: MergePatch): T {
|
|
53
|
-
const out: Record<string, unknown> = { ...(record as Record<string, unknown>) };
|
|
54
|
-
|
|
55
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
56
|
-
if (value === null) {
|
|
57
|
-
delete out[key];
|
|
58
|
-
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
59
|
-
const base = out[key];
|
|
60
|
-
|
|
61
|
-
out[key] = mergePatch(
|
|
62
|
-
base && typeof base === 'object' && !Array.isArray(base) ? (base as object) : {},
|
|
63
|
-
value as MergePatch,
|
|
64
|
-
);
|
|
65
|
-
} else {
|
|
66
|
-
out[key] = value;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return out as T;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
41
|
/**
|
|
74
42
|
* ドラッグで動かしたノードの位置を保存する patch。
|
|
75
43
|
* 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。
|
|
76
44
|
* 子のノード(COMPONENT を含む)の position は親の中での位置なので、親を動かしても子の patch は要らない。
|
|
77
45
|
*/
|
|
78
46
|
export function movePatch(node: DeploymentNodeRecord, move: DeploymentMove): DeploymentChange {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
collection: 'nodes',
|
|
83
|
-
id: node.id,
|
|
84
|
-
patch: {
|
|
85
|
-
position: {
|
|
86
|
-
x: Math.round(base.x + move.dx),
|
|
87
|
-
y: Math.round(base.y + move.dy),
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
};
|
|
47
|
+
return movePatchTo(node, move, 'nodes');
|
|
91
48
|
}
|
|
92
49
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
function toNumber(value: string): number | null {
|
|
96
|
-
const n = Number(value);
|
|
97
|
-
|
|
98
|
-
return value !== '' && Number.isFinite(n) ? n : null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* 色として読める文字列か。
|
|
103
|
-
* ブラウザでは CSS.supports('color', v) で判定する。Node など CSS の無い環境では、
|
|
104
|
-
* #rgb / #rrggbb / #rrggbbaa・rgb() などの関数表記・英字だけの色名、を通す緩い判定にする。
|
|
105
|
-
*/
|
|
106
|
-
export function isColor(value: string): boolean {
|
|
107
|
-
if (value === '')
|
|
108
|
-
return false;
|
|
109
|
-
|
|
110
|
-
const css = (globalThis as { CSS?: { supports?: (p: string, v: string) => boolean } }).CSS;
|
|
111
|
-
|
|
112
|
-
if (css && typeof css.supports === 'function')
|
|
113
|
-
return css.supports('color', value);
|
|
114
|
-
|
|
115
|
-
return /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)
|
|
116
|
-
|| /^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(.+\)$/i.test(value)
|
|
117
|
-
|| /^[a-z]+$/i.test(value);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function toColor(value: string, label: string): string {
|
|
121
|
-
if (!isColor(value))
|
|
122
|
-
throw new ModelError(`${label}は、色 (#ff0000 や red など) で入力してください。`);
|
|
123
|
-
|
|
124
|
-
return value;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// 0 より大きい数(枠線・線の太さ。d3.deployment は 0 を「書いていない」と見て既定に倒すので、0 は入れられない)
|
|
128
|
-
function toWidth(value: string, label: string): number {
|
|
129
|
-
const n = toNumber(value);
|
|
130
|
-
|
|
131
|
-
if (n === null || n <= 0)
|
|
132
|
-
throw new ModelError(`${label}は、0 より大きい数で入力してください。`);
|
|
133
|
-
|
|
134
|
-
return n;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// 0 以上の整数(幅・高さ。0 は「子に合わせる」)
|
|
138
|
-
function toLength(value: string, label: string): number {
|
|
139
|
-
const n = toNumber(value);
|
|
140
|
-
|
|
141
|
-
if (n === null || n < 0)
|
|
142
|
-
throw new ModelError(`${label}は 0 以上の数で入力してください。(0 で子に合わせる)`);
|
|
143
|
-
|
|
144
|
-
return Math.round(n);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// 角度(0 以上 360 未満の整数に正規化)
|
|
148
|
-
function toDegree(value: string, label: string): number {
|
|
149
|
-
const n = toNumber(value);
|
|
150
|
-
|
|
151
|
-
if (n === null)
|
|
152
|
-
throw new ModelError(`${label}は、角度 (数) で入力してください。`);
|
|
153
|
-
|
|
154
|
-
return ((Math.round(n) % 360) + 360) % 360;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function toOption(value: string, options: readonly string[], label: string): string {
|
|
158
|
-
if (!options.includes(value))
|
|
159
|
-
throw new ModelError(`${label}は、${options.join(' / ')} のどれかで入力してください。`);
|
|
160
|
-
|
|
161
|
-
return value;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// true / false
|
|
165
|
-
function toBoolean(value: string, label: string): boolean {
|
|
166
|
-
if (value !== 'true' && value !== 'false')
|
|
167
|
-
throw new ModelError(`${label}は、true / false のどちらかで入力してください。`);
|
|
168
|
-
|
|
169
|
-
return value === 'true';
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// 空にできる文字列。空なら null(JSON Merge Patch で項目を除く)
|
|
173
|
-
function toOptionalText(value: string): string | null {
|
|
174
|
-
return value === '' ? null : value;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// patch の入れ子に値を置く(patch.border.color = v のように)
|
|
178
|
-
function put(patch: MergePatch, path: string, value: unknown): void {
|
|
179
|
-
const keys = path.split('.');
|
|
180
|
-
let cur = patch;
|
|
181
|
-
|
|
182
|
-
for (const key of keys.slice(0, -1)) {
|
|
183
|
-
if (!cur[key] || typeof cur[key] !== 'object')
|
|
184
|
-
cur[key] = {};
|
|
185
|
-
|
|
186
|
-
cur = cur[key] as MergePatch;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
cur[keys[keys.length - 1]] = value;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* インスペクタの入力のうち、図に描いている値(withDefaults で埋めた値)から変わった項目だけを返す。
|
|
194
|
-
* 前後の空白は除く。読み取り専用の項目と、values に無い項目は含めない。
|
|
195
|
-
*/
|
|
50
|
+
// インスペクタの入力のうち、図に描いている値(withDefaults)から変わった項目だけ
|
|
196
51
|
function changedValues(record: DeploymentRecord, values: DeploymentValues): Map<string, string> {
|
|
197
|
-
|
|
198
|
-
const out = new Map<string, string>();
|
|
199
|
-
|
|
200
|
-
for (const field of fieldsOf(record)) {
|
|
201
|
-
if (field.type === 'readonly')
|
|
202
|
-
continue;
|
|
203
|
-
|
|
204
|
-
const value = values[field.path];
|
|
205
|
-
|
|
206
|
-
if (value === undefined)
|
|
207
|
-
continue;
|
|
208
|
-
|
|
209
|
-
const current = getByPath(effective, field.path);
|
|
210
|
-
|
|
211
|
-
if (value.trim() !== String(current ?? ''))
|
|
212
|
-
out.set(field.path, value.trim());
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return out;
|
|
52
|
+
return changedValuesOf(withDefaults(record), values, fieldsOf(record));
|
|
216
53
|
}
|
|
217
54
|
|
|
55
|
+
// 幅・高さは 0 以上(0 は「子に合わせる」)。
|
|
56
|
+
// 枠線・線の太さは 0 より大きい数(d3.deployment は 0 を「書いていない」と見て既定に倒すので、0 は入れられない)
|
|
57
|
+
const LENGTH = { hint: '(0 で子に合わせる)' };
|
|
58
|
+
|
|
218
59
|
// ノードの入力 → patch(label.text / type / description / link.url / size / background / border)
|
|
219
60
|
function nodePatch(node: DeploymentNodeRecord, values: DeploymentValues): MergePatch {
|
|
220
61
|
const patch: MergePatch = {};
|
|
@@ -242,10 +83,10 @@ function nodePatch(node: DeploymentNodeRecord, values: DeploymentValues): MergeP
|
|
|
242
83
|
put(patch, path, value);
|
|
243
84
|
break;
|
|
244
85
|
case 'size.w':
|
|
245
|
-
put(patch, path, toLength(value, '幅'));
|
|
86
|
+
put(patch, path, toLength(value, '幅', LENGTH));
|
|
246
87
|
break;
|
|
247
88
|
case 'size.h':
|
|
248
|
-
put(patch, path, toLength(value, '高さ'));
|
|
89
|
+
put(patch, path, toLength(value, '高さ', LENGTH));
|
|
249
90
|
break;
|
|
250
91
|
case 'background.color':
|
|
251
92
|
put(patch, path, toColor(value, '背景色'));
|
package/src/types.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// iHub では data/deployment/{nodes,edges}.json の 1 要素。d3.deployment が正規化したあとの datum(_id / _core / _position を持つもの)ではない。
|
|
5
5
|
// ノードと線の図なので model.unchi / model.sitemap と形が近い。ラベルは label.text(unchi と同じ。sitemap は label.contents)。
|
|
6
6
|
|
|
7
|
+
import type { MergePatch, ModelField, ModelFieldType, ModelMove, ModelValues } from '@yanqirenshi/model.core';
|
|
7
8
|
import type { Background, Border, Font, Position, Size, Stroke } from '@yanqirenshi/types';
|
|
8
9
|
|
|
9
10
|
/** レコードの id(nodes / edges とも。ノードは子も含めて一意) */
|
|
@@ -68,8 +69,8 @@ export interface DeploymentGraph {
|
|
|
68
69
|
/** レコードを置くコレクション */
|
|
69
70
|
export type DeploymentCollection = 'nodes' | 'edges';
|
|
70
71
|
|
|
71
|
-
/** JSON Merge Patch(RFC 7386)。null
|
|
72
|
-
export type
|
|
72
|
+
/** JSON Merge Patch(RFC 7386)。null は「その項目を除く」。model.core の MergePatch と同じ(iHub の PATCH /{collection}/{id} が受け取る形) */
|
|
73
|
+
export type { MergePatch };
|
|
73
74
|
|
|
74
75
|
/** 保存する変更。利用側は図の種類を知らずにこれを保存すればよい */
|
|
75
76
|
export interface DeploymentChange {
|
|
@@ -79,24 +80,15 @@ export interface DeploymentChange {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
/** ドラッグの移動量(世界座標。d3.deployment の callbacks.node.dragend が渡す move と同じ) */
|
|
82
|
-
export
|
|
83
|
-
dx: number;
|
|
84
|
-
dy: number;
|
|
85
|
-
}
|
|
83
|
+
export type DeploymentMove = ModelMove;
|
|
86
84
|
|
|
87
85
|
/**
|
|
88
86
|
* 編集できる項目。colonoscope の ColonoscopeField(path / label / type)と同じ形で、
|
|
89
87
|
* 取りうる値の一覧 options だけ足している(型は構造的に互換。colonoscope には依存しない)。
|
|
90
88
|
*/
|
|
91
|
-
export type DeploymentFieldType =
|
|
92
|
-
|
|
93
|
-
export
|
|
94
|
-
/** レコードの項目のパス(ドット区切り。例: 'border.color')。写し先ではなく実際の項目を指す */
|
|
95
|
-
path: string;
|
|
96
|
-
label: string;
|
|
97
|
-
type: DeploymentFieldType;
|
|
98
|
-
options?: readonly string[];
|
|
99
|
-
}
|
|
89
|
+
export type DeploymentFieldType = ModelFieldType;
|
|
90
|
+
|
|
91
|
+
export type DeploymentField = ModelField;
|
|
100
92
|
|
|
101
93
|
/** インスペクタの「適用」が渡す値(path → 入力した文字列) */
|
|
102
|
-
export type DeploymentValues =
|
|
94
|
+
export type DeploymentValues = ModelValues;
|