@yanqirenshi/model.unchi 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/dist/check.d.ts.map +1 -1
- package/dist/check.js +5 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/patch.d.ts +2 -19
- package/dist/patch.d.ts.map +1 -1
- package/dist/patch.js +10 -125
- package/dist/types.d.ts +7 -21
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/check.ts +6 -27
- package/src/index.ts +2 -0
- package/src/patch.ts +14 -165
- package/src/types.ts +8 -20
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,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9D,wCAAwC;AACxC,wBAAgB,OAAO,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE,CAEnE;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE,CAE7D"}
|
package/dist/check.js
CHANGED
|
@@ -2,20 +2,13 @@
|
|
|
2
2
|
// スキーマ(schema.ts)に合っているデータを渡す。戻り値はエラーのメッセージの配列(空なら問題なし)。
|
|
3
3
|
//
|
|
4
4
|
// iHub の server/validation/rules.mjs の checkGraph を移したもの。
|
|
5
|
+
// 検査そのものは graph 系(unchi / sitemap / deployment)で共通なので model.core の checkGraph にあり、
|
|
6
|
+
// ここでは model.unchi の型を付けて公開する(#151)。
|
|
5
7
|
// メッセージの先頭はコレクション名で、prefix を渡すと 'overview/nodes: ...' のように付けられる。
|
|
8
|
+
import { checkGraph, flattenNodes } from '@yanqirenshi/model.core';
|
|
6
9
|
/** 入れ子のノード(children)を平らにする(親 → 子 の順) */
|
|
7
10
|
export function flatten(nodes) {
|
|
8
|
-
return nodes
|
|
9
|
-
}
|
|
10
|
-
// list の中で id が重複していればエラーにする。id の集合を返す。
|
|
11
|
-
function checkUnique(errors, label, list) {
|
|
12
|
-
const values = new Set();
|
|
13
|
-
for (const d of list) {
|
|
14
|
-
if (values.has(d.id))
|
|
15
|
-
errors.push(`${label}: id が重複しています。(id=${String(d.id)})`);
|
|
16
|
-
values.add(d.id);
|
|
17
|
-
}
|
|
18
|
-
return values;
|
|
11
|
+
return flattenNodes(nodes);
|
|
19
12
|
}
|
|
20
13
|
/**
|
|
21
14
|
* 図全体を検査する。
|
|
@@ -26,12 +19,5 @@ function checkUnique(errors, label, list) {
|
|
|
26
19
|
* @param prefix メッセージのコレクション名の前に付ける文字列(例: 'overview/')
|
|
27
20
|
*/
|
|
28
21
|
export function check(data, prefix = '') {
|
|
29
|
-
|
|
30
|
-
const node_ids = checkUnique(errors, `${prefix}nodes`, flatten(data.nodes || []));
|
|
31
|
-
checkUnique(errors, `${prefix}edges`, data.edges || []);
|
|
32
|
-
for (const edge of data.edges || [])
|
|
33
|
-
for (const end of ['from', 'to'])
|
|
34
|
-
if (!edge[end] || !node_ids.has(edge[end].id))
|
|
35
|
-
errors.push(`${prefix}edges: ${end}.id のノードが見つかりません。(id=${String(edge.id)}, ${end}.id=${String(edge[end]?.id)})`);
|
|
36
|
-
return errors;
|
|
22
|
+
return checkGraph(data, prefix);
|
|
37
23
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,YAAY,EACV,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,YAAY,EAAE,eAAe,EAAE,WAAW,EACpG,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,GACzG,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEnE,OAAO,EACL,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,aAAa,EACjG,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,GAC5D,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EACL,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,GACzF,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
// patch への変換: movePatch(node, move) / applyPatch(record, values) / mergePatch(record, patch)
|
|
8
8
|
//
|
|
9
9
|
// view(d3.unchi)・インスペクタ(colonoscope)との間に横の依存は無い。組み合わせるのは利用側。
|
|
10
|
+
// check / flatten / movePatch / mergePatch / getByPath / isColor と入力の読み取り部品は model.core(#151)のもので、
|
|
11
|
+
// ここでは model.unchi の型を付けて公開している。
|
|
10
12
|
export { NODE_CLASSES, BORDER_TYPES, MARKER_TYPES, NODE_STYLE_DEFAULTS, NODE_LABEL_DEFAULTS, EDGE_DEFAULTS, isEdge, classOf, kindOf, nodeStyle, edgeStyle, withDefaults, } from './defaults.js';
|
|
11
13
|
export { NODES_SCHEMA, EDGES_SCHEMA, SCHEMAS } from './schema.js';
|
|
12
14
|
export { check, flatten } from './check.js';
|
package/dist/patch.d.ts
CHANGED
|
@@ -1,32 +1,15 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export declare class ModelError extends Error {
|
|
4
|
-
constructor(message: string);
|
|
5
|
-
}
|
|
1
|
+
import type { UnchiChange, UnchiCollection, UnchiMove, UnchiNodeRecord, UnchiRecord, UnchiValues } from './types.js';
|
|
2
|
+
export { ModelError, getByPath, mergePatch, isColor } from '@yanqirenshi/model.core';
|
|
6
3
|
/** レコードを置くコレクション */
|
|
7
4
|
export declare function collectionOf(record: UnchiRecord): UnchiCollection;
|
|
8
5
|
/** インスペクタの見出し。ラベルの文字、無ければ「種類 id」 */
|
|
9
6
|
export declare function titleOf(record: UnchiRecord): 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
|
* 子のノードの position は親の中での位置なので、親を動かしても子の patch は要らない。
|
|
21
11
|
*/
|
|
22
12
|
export declare function movePatch(node: UnchiNodeRecord, move: UnchiMove): UnchiChange;
|
|
23
|
-
/**
|
|
24
|
-
* 色として読める文字列か。
|
|
25
|
-
* ブラウザでは CSS.supports('color', v) で判定する。Node など CSS の無い環境では、
|
|
26
|
-
* #rgb / #rrggbb / #rrggbbaa・rgb() などの関数表記・英字だけの色名、を通す緩い判定にする
|
|
27
|
-
* (色名の一覧は持たない。環境で判定が変わる点は README に書く)。
|
|
28
|
-
*/
|
|
29
|
-
export declare function isColor(value: string): boolean;
|
|
30
13
|
/**
|
|
31
14
|
* インスペクタの「適用」の値を、保存する patch に変換する。
|
|
32
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":"AAeA,OAAO,KAAK,EACE,WAAW,EAAE,eAAe,EAAmB,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAChH,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,WAAW,GAAG,eAAe,CAEjE;AAED,oCAAoC;AACpC,wBAAgB,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAQnD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,GAAG,WAAW,CAE7E;AAmFD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,WAAW,CAMhF"}
|
package/dist/patch.js
CHANGED
|
@@ -4,16 +4,12 @@
|
|
|
4
4
|
// 入力の検証に失敗したときは ModelError を投げる(メッセージは利用者にそのまま見せられる日本語)。
|
|
5
5
|
//
|
|
6
6
|
// iHub の Overview.js にあった sizePatch / portsPatch / stylePatch / edgeStylePatch / labelPatch /
|
|
7
|
-
// mergeRecord
|
|
7
|
+
// mergeRecord を移したもの。読み取り部品(toColor など)と patch の道具は model.core にある(#151)。
|
|
8
|
+
import { ModelError, changedValues as changedValuesOf, movePatch as movePatchTo, put, toColor, toDegree, toLength, toOption, toWidth, } from '@yanqirenshi/model.core';
|
|
8
9
|
import { BORDER_TYPES, MARKER_TYPES, isEdge, withDefaults } from './defaults.js';
|
|
9
10
|
import { fieldsOf } from './fields.js';
|
|
10
|
-
|
|
11
|
-
export
|
|
12
|
-
constructor(message) {
|
|
13
|
-
super(message);
|
|
14
|
-
this.name = 'ModelError';
|
|
15
|
-
}
|
|
16
|
-
}
|
|
11
|
+
// model.core のものをそのまま公開する(利用側が model.unchi だけを読めばよいように)
|
|
12
|
+
export { ModelError, getByPath, mergePatch, isColor } from '@yanqirenshi/model.core';
|
|
17
13
|
/** レコードを置くコレクション */
|
|
18
14
|
export function collectionOf(record) {
|
|
19
15
|
return isEdge(record) ? 'edges' : 'nodes';
|
|
@@ -26,130 +22,17 @@ export function titleOf(record) {
|
|
|
26
22
|
return text;
|
|
27
23
|
return `${isEdge(record) ? '' : (record.type || '')} ${String(record.id)}`.trim();
|
|
28
24
|
}
|
|
29
|
-
/** ドット区切りの path で値を取り出す */
|
|
30
|
-
export function getByPath(obj, path) {
|
|
31
|
-
return path.split('.').reduce((acc, key) => (acc == null ? acc : acc[key]), obj);
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
|
|
35
|
-
* null の項目は除く。保存したあと、表示を先に更新するときに使う。
|
|
36
|
-
*/
|
|
37
|
-
export function mergePatch(record, patch) {
|
|
38
|
-
const out = { ...record };
|
|
39
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
40
|
-
if (value === null) {
|
|
41
|
-
delete out[key];
|
|
42
|
-
}
|
|
43
|
-
else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
44
|
-
const base = out[key];
|
|
45
|
-
out[key] = mergePatch(base && typeof base === 'object' && !Array.isArray(base) ? base : {}, value);
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
out[key] = value;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return out;
|
|
52
|
-
}
|
|
53
25
|
/**
|
|
54
26
|
* ドラッグで動かしたノードの位置を保存する patch。
|
|
55
27
|
* 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。
|
|
56
28
|
* 子のノードの position は親の中での位置なので、親を動かしても子の patch は要らない。
|
|
57
29
|
*/
|
|
58
30
|
export function movePatch(node, move) {
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
collection: 'nodes',
|
|
62
|
-
id: node.id,
|
|
63
|
-
patch: {
|
|
64
|
-
position: {
|
|
65
|
-
x: Math.round(base.x + move.dx),
|
|
66
|
-
y: Math.round(base.y + move.dy),
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
// ---- 入力の読み取り ------------------------------------------------------------
|
|
72
|
-
// 入力した文字列を数にする。数でないときは null。
|
|
73
|
-
function toNumber(value) {
|
|
74
|
-
const n = Number(value);
|
|
75
|
-
return value !== '' && Number.isFinite(n) ? n : null;
|
|
31
|
+
return movePatchTo(node, move, 'nodes');
|
|
76
32
|
}
|
|
77
|
-
|
|
78
|
-
* 色として読める文字列か。
|
|
79
|
-
* ブラウザでは CSS.supports('color', v) で判定する。Node など CSS の無い環境では、
|
|
80
|
-
* #rgb / #rrggbb / #rrggbbaa・rgb() などの関数表記・英字だけの色名、を通す緩い判定にする
|
|
81
|
-
* (色名の一覧は持たない。環境で判定が変わる点は README に書く)。
|
|
82
|
-
*/
|
|
83
|
-
export function isColor(value) {
|
|
84
|
-
if (value === '')
|
|
85
|
-
return false;
|
|
86
|
-
const css = globalThis.CSS;
|
|
87
|
-
if (css && typeof css.supports === 'function')
|
|
88
|
-
return css.supports('color', value);
|
|
89
|
-
return /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)
|
|
90
|
-
|| /^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(.+\)$/i.test(value)
|
|
91
|
-
|| /^[a-z]+$/i.test(value);
|
|
92
|
-
}
|
|
93
|
-
function toColor(value, label) {
|
|
94
|
-
if (!isColor(value))
|
|
95
|
-
throw new ModelError(`${label}は、色 (#ff0000 や red など) で入力してください。`);
|
|
96
|
-
return value;
|
|
97
|
-
}
|
|
98
|
-
// 0 より大きい数(線の太さ)
|
|
99
|
-
function toWidth(value, label) {
|
|
100
|
-
const n = toNumber(value);
|
|
101
|
-
if (n === null || n <= 0)
|
|
102
|
-
throw new ModelError(`${label}は、0 より大きい数で入力してください。`);
|
|
103
|
-
return n;
|
|
104
|
-
}
|
|
105
|
-
// 1 以上の整数(幅・高さ)
|
|
106
|
-
function toLength(value, label) {
|
|
107
|
-
const n = toNumber(value);
|
|
108
|
-
if (n === null || n < 1)
|
|
109
|
-
throw new ModelError(`${label}は 1 以上の数で入力してください。`);
|
|
110
|
-
return Math.round(n);
|
|
111
|
-
}
|
|
112
|
-
// 角度(0 以上 360 未満の整数に正規化)
|
|
113
|
-
function toDegree(value, label) {
|
|
114
|
-
const n = toNumber(value);
|
|
115
|
-
if (n === null)
|
|
116
|
-
throw new ModelError(`${label}は、角度 (数) で入力してください。`);
|
|
117
|
-
return ((Math.round(n) % 360) + 360) % 360;
|
|
118
|
-
}
|
|
119
|
-
function toOption(value, options, label) {
|
|
120
|
-
if (!options.includes(value))
|
|
121
|
-
throw new ModelError(`${label}は、${options.join(' / ')} のどれかで入力してください。`);
|
|
122
|
-
return value;
|
|
123
|
-
}
|
|
124
|
-
// patch の入れ子に値を置く(patch.border.color = v のように)
|
|
125
|
-
function put(patch, path, value) {
|
|
126
|
-
const keys = path.split('.');
|
|
127
|
-
let cur = patch;
|
|
128
|
-
for (const key of keys.slice(0, -1)) {
|
|
129
|
-
if (!cur[key] || typeof cur[key] !== 'object')
|
|
130
|
-
cur[key] = {};
|
|
131
|
-
cur = cur[key];
|
|
132
|
-
}
|
|
133
|
-
cur[keys[keys.length - 1]] = value;
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* インスペクタの入力のうち、図に描いている値(withDefaults で埋めた値)から変わった項目だけを返す。
|
|
137
|
-
* 前後の空白は除く。読み取り専用の項目と、values に無い項目は含めない。
|
|
138
|
-
*/
|
|
33
|
+
// インスペクタの入力のうち、図に描いている値(withDefaults)から変わった項目だけ
|
|
139
34
|
function changedValues(record, values) {
|
|
140
|
-
|
|
141
|
-
const out = new Map();
|
|
142
|
-
for (const field of fieldsOf(record)) {
|
|
143
|
-
if (field.type === 'readonly')
|
|
144
|
-
continue;
|
|
145
|
-
const value = values[field.path];
|
|
146
|
-
if (value === undefined)
|
|
147
|
-
continue;
|
|
148
|
-
const current = getByPath(effective, field.path);
|
|
149
|
-
if (value.trim() !== String(current ?? ''))
|
|
150
|
-
out.set(field.path, value.trim());
|
|
151
|
-
}
|
|
152
|
-
return out;
|
|
35
|
+
return changedValuesOf(withDefaults(record), values, fieldsOf(record));
|
|
153
36
|
}
|
|
154
37
|
const NODE_LABELS = {
|
|
155
38
|
'size.w': '幅', 'size.h': '高さ', 'background.color': '背景色',
|
|
@@ -162,7 +45,8 @@ function nodePatch(node, values) {
|
|
|
162
45
|
switch (path) {
|
|
163
46
|
case 'size.w':
|
|
164
47
|
case 'size.h':
|
|
165
|
-
|
|
48
|
+
// 1 以上(d3.unchi は 0 の幅・高さを描けない)
|
|
49
|
+
put(patch, path, toLength(value, NODE_LABELS[path], { min: 1 }));
|
|
166
50
|
break;
|
|
167
51
|
case 'background.color':
|
|
168
52
|
case 'border.color':
|
|
@@ -172,6 +56,7 @@ function nodePatch(node, values) {
|
|
|
172
56
|
put(patch, path, toOption(value, BORDER_TYPES, NODE_LABELS[path]));
|
|
173
57
|
break;
|
|
174
58
|
case 'border.width':
|
|
59
|
+
// 0 より大きい数(d3.unchi は 0 を「書いていない」と見て既定に倒す)
|
|
175
60
|
put(patch, path, toWidth(value, NODE_LABELS[path]));
|
|
176
61
|
break;
|
|
177
62
|
case 'label.text':
|
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, Label, Position, Size, Stroke } from '@yanqirenshi/types';
|
|
2
3
|
/** レコードの id(nodes / edges とも。ノードは子も含めて一意) */
|
|
3
4
|
export type UnchiId = number | string;
|
|
@@ -56,13 +57,8 @@ export interface UnchiGraph {
|
|
|
56
57
|
}
|
|
57
58
|
/** レコードを置くコレクション */
|
|
58
59
|
export type UnchiCollection = 'nodes' | 'edges';
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
* iHub の PATCH /{collection}/{id} が受け取る形。
|
|
62
|
-
*/
|
|
63
|
-
export type MergePatch = {
|
|
64
|
-
[key: string]: unknown;
|
|
65
|
-
};
|
|
60
|
+
/** JSON Merge Patch(RFC 7386)。null は「その項目を除く」。model.core の MergePatch と同じ(iHub の PATCH /{collection}/{id} が受け取る形) */
|
|
61
|
+
export type { MergePatch };
|
|
66
62
|
/** 保存すべき変更。利用側は図の種類を知らずにこれを保存すればよい */
|
|
67
63
|
export interface UnchiChange {
|
|
68
64
|
collection: UnchiCollection;
|
|
@@ -70,23 +66,13 @@ export interface UnchiChange {
|
|
|
70
66
|
patch: MergePatch;
|
|
71
67
|
}
|
|
72
68
|
/** ドラッグの移動量(世界座標。d3.unchi の callbacks.node.dragend が渡す move と同じ) */
|
|
73
|
-
export
|
|
74
|
-
dx: number;
|
|
75
|
-
dy: number;
|
|
76
|
-
}
|
|
69
|
+
export type UnchiMove = ModelMove;
|
|
77
70
|
/**
|
|
78
71
|
* 編集できる項目。colonoscope の ColonoscopeField(path / label / type)と同じ形で、
|
|
79
72
|
* 取りうる値の一覧 options だけ足している(型は構造的に互換。colonoscope には依存しない)。
|
|
80
73
|
*/
|
|
81
|
-
export type UnchiFieldType =
|
|
82
|
-
export
|
|
83
|
-
/** レコードの項目のパス(ドット区切り。例: 'border.type')。写し先ではなく実際の項目を指す */
|
|
84
|
-
path: string;
|
|
85
|
-
label: string;
|
|
86
|
-
type: UnchiFieldType;
|
|
87
|
-
/** 取りうる値の一覧(枠線の種類・マーカーなど)。検証に使い、表示側は候補として添える */
|
|
88
|
-
options?: readonly string[];
|
|
89
|
-
}
|
|
74
|
+
export type UnchiFieldType = ModelFieldType;
|
|
75
|
+
export type UnchiField = ModelField;
|
|
90
76
|
/** インスペクタの「適用」が渡す値(path → 入力した文字列) */
|
|
91
|
-
export type UnchiValues =
|
|
77
|
+
export type UnchiValues = ModelValues;
|
|
92
78
|
//# 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,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAElG,6CAA6C;AAC7C,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEtC,kDAAkD;AAClD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE/C,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,KAAK,CAAC,EAAE,cAAc,GAAG,KAAK,GAAG,IAAI,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,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,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,mEAAmE;AACnE,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,kBAAkB;AAClB,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;IACnB,EAAE,EAAE,YAAY,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,eAAe,CAAC;AAE5D,kCAAkC;AAClC,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED,oBAAoB;AACpB,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,OAAO,CAAC;AAEhD
|
|
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,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAElG,6CAA6C;AAC7C,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEtC,kDAAkD;AAClD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE/C,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,KAAK,CAAC,EAAE,cAAc,GAAG,KAAK,GAAG,IAAI,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,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,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,mEAAmE;AACnE,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,kBAAkB;AAClB,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;IACnB,EAAE,EAAE,YAAY,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,eAAe,CAAC;AAE5D,kCAAkC;AAClC,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED,oBAAoB;AACpB,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,OAAO,CAAC;AAEhD,qHAAqH;AACrH,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B,sCAAsC;AACtC,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,eAAe,CAAC;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,oEAAoE;AACpE,MAAM,MAAM,SAAS,GAAG,SAAS,CAAC;AAElC;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,cAAc,CAAC;AAE5C,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC;AAEpC,sCAAsC;AACtC,MAAM,MAAM,WAAW,GAAG,WAAW,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yanqirenshi/model.unchi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Diagram model for d3.unchi (ponchi-e) — 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",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
+
"@yanqirenshi/model.core": "^0.1.0",
|
|
39
40
|
"@yanqirenshi/types": "^0.1.2"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
package/src/check.ts
CHANGED
|
@@ -2,27 +2,17 @@
|
|
|
2
2
|
// スキーマ(schema.ts)に合っているデータを渡す。戻り値はエラーのメッセージの配列(空なら問題なし)。
|
|
3
3
|
//
|
|
4
4
|
// iHub の server/validation/rules.mjs の checkGraph を移したもの。
|
|
5
|
+
// 検査そのものは graph 系(unchi / sitemap / deployment)で共通なので model.core の checkGraph にあり、
|
|
6
|
+
// ここでは model.unchi の型を付けて公開する(#151)。
|
|
5
7
|
// メッセージの先頭はコレクション名で、prefix を渡すと 'overview/nodes: ...' のように付けられる。
|
|
6
8
|
|
|
9
|
+
import { checkGraph, flattenNodes } from '@yanqirenshi/model.core';
|
|
10
|
+
|
|
7
11
|
import type { UnchiGraph, UnchiNodeRecord } from './types.js';
|
|
8
12
|
|
|
9
13
|
/** 入れ子のノード(children)を平らにする(親 → 子 の順) */
|
|
10
14
|
export function flatten(nodes: UnchiNodeRecord[]): UnchiNodeRecord[] {
|
|
11
|
-
return nodes
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// list の中で id が重複していればエラーにする。id の集合を返す。
|
|
15
|
-
function checkUnique(errors: string[], label: string, list: { id: unknown }[]): Set<unknown> {
|
|
16
|
-
const values = new Set<unknown>();
|
|
17
|
-
|
|
18
|
-
for (const d of list) {
|
|
19
|
-
if (values.has(d.id))
|
|
20
|
-
errors.push(`${label}: id が重複しています。(id=${String(d.id)})`);
|
|
21
|
-
|
|
22
|
-
values.add(d.id);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return values;
|
|
15
|
+
return flattenNodes(nodes);
|
|
26
16
|
}
|
|
27
17
|
|
|
28
18
|
/**
|
|
@@ -34,16 +24,5 @@ function checkUnique(errors: string[], label: string, list: { id: unknown }[]):
|
|
|
34
24
|
* @param prefix メッセージのコレクション名の前に付ける文字列(例: 'overview/')
|
|
35
25
|
*/
|
|
36
26
|
export function check(data: UnchiGraph, prefix = ''): string[] {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const node_ids = checkUnique(errors, `${prefix}nodes`, flatten(data.nodes || []));
|
|
40
|
-
|
|
41
|
-
checkUnique(errors, `${prefix}edges`, data.edges || []);
|
|
42
|
-
|
|
43
|
-
for (const edge of data.edges || [])
|
|
44
|
-
for (const end of ['from', 'to'] as const)
|
|
45
|
-
if (!edge[end] || !node_ids.has(edge[end].id))
|
|
46
|
-
errors.push(`${prefix}edges: ${end}.id のノードが見つかりません。(id=${String(edge.id)}, ${end}.id=${String(edge[end]?.id)})`);
|
|
47
|
-
|
|
48
|
-
return errors;
|
|
27
|
+
return checkGraph(data, prefix);
|
|
49
28
|
}
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
// patch への変換: movePatch(node, move) / applyPatch(record, values) / mergePatch(record, patch)
|
|
8
8
|
//
|
|
9
9
|
// view(d3.unchi)・インスペクタ(colonoscope)との間に横の依存は無い。組み合わせるのは利用側。
|
|
10
|
+
// check / flatten / movePatch / mergePatch / getByPath / isColor と入力の読み取り部品は model.core(#151)のもので、
|
|
11
|
+
// ここでは model.unchi の型を付けて公開している。
|
|
10
12
|
|
|
11
13
|
export type {
|
|
12
14
|
UnchiId, UnchiNodeClass, UnchiNodeLabel, UnchiNodeRecord, UnchiEdgeEnd, UnchiEdgeRecord, UnchiRecord,
|
package/src/patch.ts
CHANGED
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
// 入力の検証に失敗したときは ModelError を投げる(メッセージは利用者にそのまま見せられる日本語)。
|
|
5
5
|
//
|
|
6
6
|
// iHub の Overview.js にあった sizePatch / portsPatch / stylePatch / edgeStylePatch / labelPatch /
|
|
7
|
-
// mergeRecord
|
|
7
|
+
// mergeRecord を移したもの。読み取り部品(toColor など)と patch の道具は model.core にある(#151)。
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
ModelError, changedValues as changedValuesOf, movePatch as movePatchTo, put,
|
|
11
|
+
toColor, toDegree, toLength, toOption, toWidth,
|
|
12
|
+
} from '@yanqirenshi/model.core';
|
|
8
13
|
|
|
9
14
|
import { BORDER_TYPES, MARKER_TYPES, isEdge, withDefaults } from './defaults.js';
|
|
10
15
|
import { fieldsOf } from './fields.js';
|
|
@@ -12,13 +17,8 @@ import type {
|
|
|
12
17
|
MergePatch, UnchiChange, UnchiCollection, UnchiEdgeRecord, UnchiMove, UnchiNodeRecord, UnchiRecord, UnchiValues,
|
|
13
18
|
} from './types.js';
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
export
|
|
17
|
-
constructor(message: string) {
|
|
18
|
-
super(message);
|
|
19
|
-
this.name = 'ModelError';
|
|
20
|
-
}
|
|
21
|
-
}
|
|
20
|
+
// model.core のものをそのまま公開する(利用側が model.unchi だけを読めばよいように)
|
|
21
|
+
export { ModelError, getByPath, mergePatch, isColor } from '@yanqirenshi/model.core';
|
|
22
22
|
|
|
23
23
|
/** レコードを置くコレクション */
|
|
24
24
|
export function collectionOf(record: UnchiRecord): UnchiCollection {
|
|
@@ -36,171 +36,18 @@ export function titleOf(record: UnchiRecord): string {
|
|
|
36
36
|
return `${isEdge(record) ? '' : (record.type || '')} ${String(record.id)}`.trim();
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/** ドット区切りの path で値を取り出す */
|
|
40
|
-
export function getByPath(obj: unknown, path: string): unknown {
|
|
41
|
-
return path.split('.').reduce<unknown>(
|
|
42
|
-
(acc, key) => (acc == null ? acc : (acc as Record<string, unknown>)[key]),
|
|
43
|
-
obj,
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
|
|
49
|
-
* null の項目は除く。保存したあと、表示を先に更新するときに使う。
|
|
50
|
-
*/
|
|
51
|
-
export function mergePatch<T extends object>(record: T, patch: MergePatch): T {
|
|
52
|
-
const out: Record<string, unknown> = { ...(record as Record<string, unknown>) };
|
|
53
|
-
|
|
54
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
55
|
-
if (value === null) {
|
|
56
|
-
delete out[key];
|
|
57
|
-
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
58
|
-
const base = out[key];
|
|
59
|
-
|
|
60
|
-
out[key] = mergePatch(
|
|
61
|
-
base && typeof base === 'object' && !Array.isArray(base) ? (base as object) : {},
|
|
62
|
-
value as MergePatch,
|
|
63
|
-
);
|
|
64
|
-
} else {
|
|
65
|
-
out[key] = value;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return out as T;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
39
|
/**
|
|
73
40
|
* ドラッグで動かしたノードの位置を保存する patch。
|
|
74
41
|
* 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。
|
|
75
42
|
* 子のノードの position は親の中での位置なので、親を動かしても子の patch は要らない。
|
|
76
43
|
*/
|
|
77
44
|
export function movePatch(node: UnchiNodeRecord, move: UnchiMove): UnchiChange {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
collection: 'nodes',
|
|
82
|
-
id: node.id,
|
|
83
|
-
patch: {
|
|
84
|
-
position: {
|
|
85
|
-
x: Math.round(base.x + move.dx),
|
|
86
|
-
y: Math.round(base.y + move.dy),
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// ---- 入力の読み取り ------------------------------------------------------------
|
|
93
|
-
|
|
94
|
-
// 入力した文字列を数にする。数でないときは null。
|
|
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
|
-
* (色名の一覧は持たない。環境で判定が変わる点は README に書く)。
|
|
106
|
-
*/
|
|
107
|
-
export function isColor(value: string): boolean {
|
|
108
|
-
if (value === '')
|
|
109
|
-
return false;
|
|
110
|
-
|
|
111
|
-
const css = (globalThis as { CSS?: { supports?: (p: string, v: string) => boolean } }).CSS;
|
|
112
|
-
|
|
113
|
-
if (css && typeof css.supports === 'function')
|
|
114
|
-
return css.supports('color', value);
|
|
115
|
-
|
|
116
|
-
return /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)
|
|
117
|
-
|| /^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(.+\)$/i.test(value)
|
|
118
|
-
|| /^[a-z]+$/i.test(value);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function toColor(value: string, label: string): string {
|
|
122
|
-
if (!isColor(value))
|
|
123
|
-
throw new ModelError(`${label}は、色 (#ff0000 や red など) で入力してください。`);
|
|
124
|
-
|
|
125
|
-
return value;
|
|
45
|
+
return movePatchTo(node, move, 'nodes');
|
|
126
46
|
}
|
|
127
47
|
|
|
128
|
-
//
|
|
129
|
-
function toWidth(value: string, label: string): number {
|
|
130
|
-
const n = toNumber(value);
|
|
131
|
-
|
|
132
|
-
if (n === null || n <= 0)
|
|
133
|
-
throw new ModelError(`${label}は、0 より大きい数で入力してください。`);
|
|
134
|
-
|
|
135
|
-
return n;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// 1 以上の整数(幅・高さ)
|
|
139
|
-
function toLength(value: string, label: string): number {
|
|
140
|
-
const n = toNumber(value);
|
|
141
|
-
|
|
142
|
-
if (n === null || n < 1)
|
|
143
|
-
throw new ModelError(`${label}は 1 以上の数で入力してください。`);
|
|
144
|
-
|
|
145
|
-
return Math.round(n);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// 角度(0 以上 360 未満の整数に正規化)
|
|
149
|
-
function toDegree(value: string, label: string): number {
|
|
150
|
-
const n = toNumber(value);
|
|
151
|
-
|
|
152
|
-
if (n === null)
|
|
153
|
-
throw new ModelError(`${label}は、角度 (数) で入力してください。`);
|
|
154
|
-
|
|
155
|
-
return ((Math.round(n) % 360) + 360) % 360;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function toOption(value: string, options: readonly string[], label: string): string {
|
|
159
|
-
if (!options.includes(value))
|
|
160
|
-
throw new ModelError(`${label}は、${options.join(' / ')} のどれかで入力してください。`);
|
|
161
|
-
|
|
162
|
-
return value;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// patch の入れ子に値を置く(patch.border.color = v のように)
|
|
166
|
-
function put(patch: MergePatch, path: string, value: unknown): void {
|
|
167
|
-
const keys = path.split('.');
|
|
168
|
-
let cur = patch;
|
|
169
|
-
|
|
170
|
-
for (const key of keys.slice(0, -1)) {
|
|
171
|
-
if (!cur[key] || typeof cur[key] !== 'object')
|
|
172
|
-
cur[key] = {};
|
|
173
|
-
|
|
174
|
-
cur = cur[key] as MergePatch;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
cur[keys[keys.length - 1]] = value;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* インスペクタの入力のうち、図に描いている値(withDefaults で埋めた値)から変わった項目だけを返す。
|
|
182
|
-
* 前後の空白は除く。読み取り専用の項目と、values に無い項目は含めない。
|
|
183
|
-
*/
|
|
48
|
+
// インスペクタの入力のうち、図に描いている値(withDefaults)から変わった項目だけ
|
|
184
49
|
function changedValues(record: UnchiRecord, values: UnchiValues): Map<string, string> {
|
|
185
|
-
|
|
186
|
-
const out = new Map<string, string>();
|
|
187
|
-
|
|
188
|
-
for (const field of fieldsOf(record)) {
|
|
189
|
-
if (field.type === 'readonly')
|
|
190
|
-
continue;
|
|
191
|
-
|
|
192
|
-
const value = values[field.path];
|
|
193
|
-
|
|
194
|
-
if (value === undefined)
|
|
195
|
-
continue;
|
|
196
|
-
|
|
197
|
-
const current = getByPath(effective, field.path);
|
|
198
|
-
|
|
199
|
-
if (value.trim() !== String(current ?? ''))
|
|
200
|
-
out.set(field.path, value.trim());
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return out;
|
|
50
|
+
return changedValuesOf(withDefaults(record), values, fieldsOf(record));
|
|
204
51
|
}
|
|
205
52
|
|
|
206
53
|
const NODE_LABELS: Record<string, string> = {
|
|
@@ -216,7 +63,8 @@ function nodePatch(node: UnchiNodeRecord, values: UnchiValues): MergePatch {
|
|
|
216
63
|
switch (path) {
|
|
217
64
|
case 'size.w':
|
|
218
65
|
case 'size.h':
|
|
219
|
-
|
|
66
|
+
// 1 以上(d3.unchi は 0 の幅・高さを描けない)
|
|
67
|
+
put(patch, path, toLength(value, NODE_LABELS[path], { min: 1 }));
|
|
220
68
|
break;
|
|
221
69
|
case 'background.color':
|
|
222
70
|
case 'border.color':
|
|
@@ -226,6 +74,7 @@ function nodePatch(node: UnchiNodeRecord, values: UnchiValues): MergePatch {
|
|
|
226
74
|
put(patch, path, toOption(value, BORDER_TYPES, NODE_LABELS[path]));
|
|
227
75
|
break;
|
|
228
76
|
case 'border.width':
|
|
77
|
+
// 0 より大きい数(d3.unchi は 0 を「書いていない」と見て既定に倒す)
|
|
229
78
|
put(patch, path, toWidth(value, NODE_LABELS[path]));
|
|
230
79
|
break;
|
|
231
80
|
case 'label.text':
|
package/src/types.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// (iHub では data/overview/{nodes,edges}.json の 1 要素)。
|
|
5
5
|
// d3.unchi が正規化したあとの datum(_id / _core を持つもの)ではない。
|
|
6
6
|
|
|
7
|
+
import type { MergePatch, ModelField, ModelFieldType, ModelMove, ModelValues } from '@yanqirenshi/model.core';
|
|
7
8
|
import type { Background, Border, Font, Label, Position, Size, Stroke } from '@yanqirenshi/types';
|
|
8
9
|
|
|
9
10
|
/** レコードの id(nodes / edges とも。ノードは子も含めて一意) */
|
|
@@ -72,11 +73,8 @@ export interface UnchiGraph {
|
|
|
72
73
|
/** レコードを置くコレクション */
|
|
73
74
|
export type UnchiCollection = 'nodes' | 'edges';
|
|
74
75
|
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
* iHub の PATCH /{collection}/{id} が受け取る形。
|
|
78
|
-
*/
|
|
79
|
-
export type MergePatch = { [key: string]: unknown };
|
|
76
|
+
/** JSON Merge Patch(RFC 7386)。null は「その項目を除く」。model.core の MergePatch と同じ(iHub の PATCH /{collection}/{id} が受け取る形) */
|
|
77
|
+
export type { MergePatch };
|
|
80
78
|
|
|
81
79
|
/** 保存すべき変更。利用側は図の種類を知らずにこれを保存すればよい */
|
|
82
80
|
export interface UnchiChange {
|
|
@@ -86,25 +84,15 @@ export interface UnchiChange {
|
|
|
86
84
|
}
|
|
87
85
|
|
|
88
86
|
/** ドラッグの移動量(世界座標。d3.unchi の callbacks.node.dragend が渡す move と同じ) */
|
|
89
|
-
export
|
|
90
|
-
dx: number;
|
|
91
|
-
dy: number;
|
|
92
|
-
}
|
|
87
|
+
export type UnchiMove = ModelMove;
|
|
93
88
|
|
|
94
89
|
/**
|
|
95
90
|
* 編集できる項目。colonoscope の ColonoscopeField(path / label / type)と同じ形で、
|
|
96
91
|
* 取りうる値の一覧 options だけ足している(型は構造的に互換。colonoscope には依存しない)。
|
|
97
92
|
*/
|
|
98
|
-
export type UnchiFieldType =
|
|
99
|
-
|
|
100
|
-
export
|
|
101
|
-
/** レコードの項目のパス(ドット区切り。例: 'border.type')。写し先ではなく実際の項目を指す */
|
|
102
|
-
path: string;
|
|
103
|
-
label: string;
|
|
104
|
-
type: UnchiFieldType;
|
|
105
|
-
/** 取りうる値の一覧(枠線の種類・マーカーなど)。検証に使い、表示側は候補として添える */
|
|
106
|
-
options?: readonly string[];
|
|
107
|
-
}
|
|
93
|
+
export type UnchiFieldType = ModelFieldType;
|
|
94
|
+
|
|
95
|
+
export type UnchiField = ModelField;
|
|
108
96
|
|
|
109
97
|
/** インスペクタの「適用」が渡す値(path → 入力した文字列) */
|
|
110
|
-
export type UnchiValues =
|
|
98
|
+
export type UnchiValues = ModelValues;
|