@yanqirenshi/model.sitemap 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 -2
- 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 -1
- package/dist/cjs/patch.js +27 -137
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/patch.d.ts +2 -18
- package/dist/patch.d.ts.map +1 -1
- package/dist/patch.js +14 -124
- 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 -1
- package/src/patch.ts +19 -163
- package/src/types.ts +8 -16
package/README.md
CHANGED
|
@@ -25,8 +25,8 @@ d3.sitemap は、このパッケージの既定値(ラベル・枠線・背景
|
|
|
25
25
|
|
|
26
26
|
model.sitemap・colonoscope の間に横の依存はありません。組み合わせるのは利用側だけです。
|
|
27
27
|
これは [Foolsgolds/Assholes#138](https://github.com/Foolsgolds/Assholes/issues/138) の派生([#145](https://github.com/Foolsgolds/Assholes/issues/145))です。
|
|
28
|
-
ノードと線の図なので [model.unchi](../model.unchi) と形が近く、`check` / `movePatch`
|
|
29
|
-
(
|
|
28
|
+
ノードと線の図なので [model.unchi](../model.unchi) と形が近く、`check` / `movePatch` / `mergePatch` と入力の読み取り部品は
|
|
29
|
+
共通部品 [model.core](../model.core) のものに model.sitemap の型を付けて公開しています([#151](https://github.com/Foolsgolds/Assholes/issues/151))。
|
|
30
30
|
|
|
31
31
|
## レコードの形
|
|
32
32
|
|
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,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE5F,wCAAwC;AACxC,wBAAgB,OAAO,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,iBAAiB,EAAE,CAEvE;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE,CAE/D;AAED,qCAAqC;AACrC,wBAAgB,QAAQ,CAAC,CAAC,SAAS,aAAa,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,GAAG,SAAS,EAAE,EAAE,EAAE,SAAS,GAAG,CAAC,GAAG,IAAI,CAEzG"}
|
package/dist/check.js
CHANGED
|
@@ -2,21 +2,13 @@
|
|
|
2
2
|
// スキーマ(schema.ts)に合っているデータを渡す。戻り値はエラーのメッセージの配列(空なら問題なし)。
|
|
3
3
|
//
|
|
4
4
|
// iHub の server/validation/rules.mjs の checkGraph('sitemap', data) と同じ検査・同じ文言。
|
|
5
|
-
//
|
|
5
|
+
// 検査そのものは graph 系(unchi / sitemap / deployment)で共通なので model.core の checkGraph にあり、
|
|
6
|
+
// ここでは model.sitemap の型を付けて公開する(#151)。
|
|
6
7
|
// メッセージの先頭はコレクション名で、prefix を渡すと 'sitemap/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 メッセージのコレクション名の前に付ける文字列(例: 'sitemap/')
|
|
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('sitemap', data) と同じ検査・同じ文言。
|
|
6
|
-
//
|
|
6
|
+
// 検査そのものは graph 系(unchi / sitemap / deployment)で共通なので model.core の checkGraph にあり、
|
|
7
|
+
// ここでは model.sitemap の型を付けて公開する(#151)。
|
|
7
8
|
// メッセージの先頭はコレクション名で、prefix を渡すと 'sitemap/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 メッセージのコレクション名の前に付ける文字列(例: 'sitemap/')
|
|
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,7 +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)
|
|
11
|
+
// graph 系(unchi / sitemap / deployment)で共通の check / flatten / findById / movePatch / mergePatch / getByPath / isColor と
|
|
12
|
+
// 入力の読み取り部品は model.core(#151)のもので、ここでは model.sitemap の型を付けて公開している。
|
|
12
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
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 = void 0;
|
|
14
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.unchi の patch.ts を元にしている(ラベルは label.contents、リンクは link.url、マーカーと border.type は無い)。
|
|
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.sitemap だけを読めばよいように)
|
|
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';
|
|
@@ -34,128 +31,21 @@ function titleOf(record) {
|
|
|
34
31
|
const contents = record.label && typeof record.label === 'object' ? record.label.contents : null;
|
|
35
32
|
return typeof contents === 'string' && contents !== '' ? contents : String(record.id);
|
|
36
33
|
}
|
|
37
|
-
/** ドット区切りの path で値を取り出す */
|
|
38
|
-
function getByPath(obj, path) {
|
|
39
|
-
return path.split('.').reduce((acc, key) => (acc == null ? acc : acc[key]), obj);
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
|
|
43
|
-
* null の項目は除く。保存したあと、表示を先に更新するときに使う。
|
|
44
|
-
*/
|
|
45
|
-
function mergePatch(record, patch) {
|
|
46
|
-
const out = { ...record };
|
|
47
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
48
|
-
if (value === null) {
|
|
49
|
-
delete out[key];
|
|
50
|
-
}
|
|
51
|
-
else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
52
|
-
const base = out[key];
|
|
53
|
-
out[key] = mergePatch(base && typeof base === 'object' && !Array.isArray(base) ? base : {}, value);
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
out[key] = value;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return out;
|
|
60
|
-
}
|
|
61
34
|
/**
|
|
62
35
|
* ドラッグで動かしたノードの位置を保存する patch。
|
|
63
36
|
* 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。
|
|
64
37
|
* 子のノードの position は親の中での位置なので、親を動かしても子の patch は要らない。
|
|
65
38
|
*/
|
|
66
39
|
function movePatch(node, move) {
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
collection: 'nodes',
|
|
70
|
-
id: node.id,
|
|
71
|
-
patch: {
|
|
72
|
-
position: {
|
|
73
|
-
x: Math.round(base.x + move.dx),
|
|
74
|
-
y: Math.round(base.y + move.dy),
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
// ---- 入力の読み取り ------------------------------------------------------------
|
|
80
|
-
function toNumber(value) {
|
|
81
|
-
const n = Number(value);
|
|
82
|
-
return value !== '' && Number.isFinite(n) ? n : null;
|
|
40
|
+
return (0, model_core_1.movePatch)(node, move, 'nodes');
|
|
83
41
|
}
|
|
84
|
-
|
|
85
|
-
* 色として読める文字列か。
|
|
86
|
-
* ブラウザでは CSS.supports('color', v) で判定する。Node など CSS の無い環境では、
|
|
87
|
-
* #rgb / #rrggbb / #rrggbbaa・rgb() などの関数表記・英字だけの色名、を通す緩い判定にする。
|
|
88
|
-
*/
|
|
89
|
-
function isColor(value) {
|
|
90
|
-
if (value === '')
|
|
91
|
-
return false;
|
|
92
|
-
const css = globalThis.CSS;
|
|
93
|
-
if (css && typeof css.supports === 'function')
|
|
94
|
-
return css.supports('color', value);
|
|
95
|
-
return /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)
|
|
96
|
-
|| /^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(.+\)$/i.test(value)
|
|
97
|
-
|| /^[a-z]+$/i.test(value);
|
|
98
|
-
}
|
|
99
|
-
function toColor(value, label) {
|
|
100
|
-
if (!isColor(value))
|
|
101
|
-
throw new ModelError(`${label}は、色 (#ff0000 や red など) で入力してください。`);
|
|
102
|
-
return value;
|
|
103
|
-
}
|
|
104
|
-
// 0 以上の数(枠線・線の太さ。0 は線なし)
|
|
105
|
-
function toWidth(value, label) {
|
|
106
|
-
const n = toNumber(value);
|
|
107
|
-
if (n === null || n < 0)
|
|
108
|
-
throw new ModelError(`${label}は、0 以上の数で入力してください。`);
|
|
109
|
-
return n;
|
|
110
|
-
}
|
|
111
|
-
// 0 以上の整数(幅・高さ。0 は「子に合わせる」)
|
|
112
|
-
function toLength(value, label) {
|
|
113
|
-
const n = toNumber(value);
|
|
114
|
-
if (n === null || n < 0)
|
|
115
|
-
throw new ModelError(`${label}は 0 以上の数で入力してください。(0 で子に合わせる)`);
|
|
116
|
-
return Math.round(n);
|
|
117
|
-
}
|
|
118
|
-
// 角度(0 以上 360 未満の整数に正規化)
|
|
119
|
-
function toDegree(value, label) {
|
|
120
|
-
const n = toNumber(value);
|
|
121
|
-
if (n === null)
|
|
122
|
-
throw new ModelError(`${label}は、角度 (数) で入力してください。`);
|
|
123
|
-
return ((Math.round(n) % 360) + 360) % 360;
|
|
124
|
-
}
|
|
125
|
-
// 空にできる文字列。空なら null(JSON Merge Patch で項目を除く)
|
|
126
|
-
function toOptionalText(value) {
|
|
127
|
-
return value === '' ? null : value;
|
|
128
|
-
}
|
|
129
|
-
// patch の入れ子に値を置く(patch.border.color = v のように)
|
|
130
|
-
function put(patch, path, value) {
|
|
131
|
-
const keys = path.split('.');
|
|
132
|
-
let cur = patch;
|
|
133
|
-
for (const key of keys.slice(0, -1)) {
|
|
134
|
-
if (!cur[key] || typeof cur[key] !== 'object')
|
|
135
|
-
cur[key] = {};
|
|
136
|
-
cur = cur[key];
|
|
137
|
-
}
|
|
138
|
-
cur[keys[keys.length - 1]] = value;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* インスペクタの入力のうち、図に描いている値(withDefaults で埋めた値)から変わった項目だけを返す。
|
|
142
|
-
* 前後の空白は除く。読み取り専用の項目と、values に無い項目は含めない。
|
|
143
|
-
*/
|
|
42
|
+
// インスペクタの入力のうち、図に描いている値(withDefaults)から変わった項目だけ
|
|
144
43
|
function changedValues(record, values) {
|
|
145
|
-
|
|
146
|
-
const out = new Map();
|
|
147
|
-
for (const field of (0, fields_js_1.fieldsOf)(record)) {
|
|
148
|
-
if (field.type === 'readonly')
|
|
149
|
-
continue;
|
|
150
|
-
const value = values[field.path];
|
|
151
|
-
if (value === undefined)
|
|
152
|
-
continue;
|
|
153
|
-
const current = getByPath(effective, field.path);
|
|
154
|
-
if (value.trim() !== String(current ?? ''))
|
|
155
|
-
out.set(field.path, value.trim());
|
|
156
|
-
}
|
|
157
|
-
return out;
|
|
44
|
+
return (0, model_core_1.changedValues)((0, defaults_js_1.withDefaults)(record), values, (0, fields_js_1.fieldsOf)(record));
|
|
158
45
|
}
|
|
46
|
+
// 幅・高さは 0 以上(0 は「子に合わせる」)。枠線・線の太さは 0 以上(0 は線なし)
|
|
47
|
+
const LENGTH = { hint: '(0 で子に合わせる)' };
|
|
48
|
+
const WIDTH = { allowZero: true };
|
|
159
49
|
// ノードの入力 → patch(label.contents / node_type / link.url / size / background / border)
|
|
160
50
|
function nodePatch(node, values) {
|
|
161
51
|
const patch = {};
|
|
@@ -164,33 +54,33 @@ function nodePatch(node, values) {
|
|
|
164
54
|
case 'label.contents':
|
|
165
55
|
// 名称は空にできない(画面の箱に文字が無くなる)
|
|
166
56
|
if (value === '')
|
|
167
|
-
throw new ModelError('名称を入力してください。');
|
|
168
|
-
put(patch, path, value);
|
|
57
|
+
throw new model_core_1.ModelError('名称を入力してください。');
|
|
58
|
+
(0, model_core_1.put)(patch, path, value);
|
|
169
59
|
break;
|
|
170
60
|
case 'node_type':
|
|
171
|
-
put(patch, path, toOptionalText(value));
|
|
61
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toOptionalText)(value));
|
|
172
62
|
break;
|
|
173
63
|
case 'link.url':
|
|
174
64
|
// URL を空にするとリンクごと除く(d3.sitemap は url の無い link を描かない)
|
|
175
65
|
if (value === '')
|
|
176
66
|
patch.link = null;
|
|
177
67
|
else
|
|
178
|
-
put(patch, path, value);
|
|
68
|
+
(0, model_core_1.put)(patch, path, value);
|
|
179
69
|
break;
|
|
180
70
|
case 'size.w':
|
|
181
|
-
put(patch, path, toLength(value, '幅'));
|
|
71
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toLength)(value, '幅', LENGTH));
|
|
182
72
|
break;
|
|
183
73
|
case 'size.h':
|
|
184
|
-
put(patch, path, toLength(value, '高さ'));
|
|
74
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toLength)(value, '高さ', LENGTH));
|
|
185
75
|
break;
|
|
186
76
|
case 'background.color':
|
|
187
|
-
put(patch, path, toColor(value, '背景色'));
|
|
77
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toColor)(value, '背景色'));
|
|
188
78
|
break;
|
|
189
79
|
case 'border.color':
|
|
190
|
-
put(patch, path, toColor(value, '枠線の色'));
|
|
80
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toColor)(value, '枠線の色'));
|
|
191
81
|
break;
|
|
192
82
|
case 'border.width':
|
|
193
|
-
put(patch, path, toWidth(value, '枠線の太さ'));
|
|
83
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toWidth)(value, '枠線の太さ', WIDTH));
|
|
194
84
|
break;
|
|
195
85
|
}
|
|
196
86
|
}
|
|
@@ -204,13 +94,13 @@ function edgePatch(edge, values) {
|
|
|
204
94
|
switch (path) {
|
|
205
95
|
case 'from.position':
|
|
206
96
|
case 'to.position':
|
|
207
|
-
put(patch, path, toDegree(value, `${END_LABELS[path.split('.')[0]]}の位置`));
|
|
97
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toDegree)(value, `${END_LABELS[path.split('.')[0]]}の位置`));
|
|
208
98
|
break;
|
|
209
99
|
case 'stroke.color':
|
|
210
|
-
put(patch, path, toColor(value, '線の色'));
|
|
100
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toColor)(value, '線の色'));
|
|
211
101
|
break;
|
|
212
102
|
case 'stroke.width':
|
|
213
|
-
put(patch, path, toWidth(value, '線の太さ'));
|
|
103
|
+
(0, model_core_1.put)(patch, path, (0, model_core_1.toWidth)(value, '線の太さ', WIDTH));
|
|
214
104
|
break;
|
|
215
105
|
}
|
|
216
106
|
}
|
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,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,iBAAiB,EAAE,cAAc,EAAE,iBAAiB,EAAE,aAAa,EACjH,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,GACvH,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,mBAAmB,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EACjE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,GACrD,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,7 +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)
|
|
10
|
+
// graph 系(unchi / sitemap / deployment)で共通の check / flatten / findById / movePatch / mergePatch / getByPath / isColor と
|
|
11
|
+
// 入力の読み取り部品は model.core(#151)のもので、ここでは model.sitemap の型を付けて公開している。
|
|
11
12
|
export { NODE_STYLE_DEFAULTS, LABEL_DEFAULTS, NODE_DEFAULTS, EDGE_DEFAULTS, isEdge, nodeStyle, edgeStyle, degreeOf, withDefaults, } from './defaults.js';
|
|
12
13
|
export { NODES_SCHEMA, EDGES_SCHEMA, SCHEMAS } from './schema.js';
|
|
13
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 { SitemapChange, SitemapCollection, SitemapMove, SitemapNodeRecord, SitemapRecord, SitemapValues } from './types.js';
|
|
2
|
+
export { ModelError, getByPath, mergePatch, isColor } from '@yanqirenshi/model.core';
|
|
6
3
|
/** レコードを置くコレクション */
|
|
7
4
|
export declare function collectionOf(record: SitemapRecord): SitemapCollection;
|
|
8
5
|
/** インスペクタの見出し。ラベルの文字、無ければ id(線は「遷移元 → 遷移先」) */
|
|
9
6
|
export declare function titleOf(record: SitemapRecord): 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: SitemapNodeRecord, move: SitemapMove): SitemapChange;
|
|
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,aAAa,EAAE,iBAAiB,EAAqB,WAAW,EAAE,iBAAiB,EAAE,aAAa,EAC9G,aAAa,EACd,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,aAAa,GAAG,iBAAiB,CAErE;AAED,+CAA+C;AAC/C,wBAAgB,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAOrD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,WAAW,GAAG,aAAa,CAEnF;AA+ED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,aAAa,GAAG,aAAa,CAMtF"}
|
package/dist/patch.js
CHANGED
|
@@ -3,15 +3,12 @@
|
|
|
3
3
|
// 戻り値は { collection, id, patch }。利用側(iHub など)は図の種類を知らずにこれを保存すればよい。
|
|
4
4
|
// 入力の検証に失敗したときは ModelError を投げる(メッセージは利用者にそのまま見せられる日本語)。
|
|
5
5
|
// model.unchi の patch.ts を元にしている(ラベルは label.contents、リンクは link.url、マーカーと border.type は無い)。
|
|
6
|
+
// 読み取り部品(toColor など)と patch の道具は model.core にある(#151)。
|
|
7
|
+
import { ModelError, changedValues as changedValuesOf, movePatch as movePatchTo, put, toColor, toDegree, toLength, toOptionalText, toWidth, } from '@yanqirenshi/model.core';
|
|
6
8
|
import { 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.sitemap だけを読めばよいように)
|
|
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';
|
|
@@ -23,128 +20,21 @@ export function titleOf(record) {
|
|
|
23
20
|
const contents = record.label && typeof record.label === 'object' ? record.label.contents : null;
|
|
24
21
|
return typeof contents === 'string' && contents !== '' ? contents : String(record.id);
|
|
25
22
|
}
|
|
26
|
-
/** ドット区切りの path で値を取り出す */
|
|
27
|
-
export function getByPath(obj, path) {
|
|
28
|
-
return path.split('.').reduce((acc, key) => (acc == null ? acc : acc[key]), obj);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
|
|
32
|
-
* null の項目は除く。保存したあと、表示を先に更新するときに使う。
|
|
33
|
-
*/
|
|
34
|
-
export function mergePatch(record, patch) {
|
|
35
|
-
const out = { ...record };
|
|
36
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
37
|
-
if (value === null) {
|
|
38
|
-
delete out[key];
|
|
39
|
-
}
|
|
40
|
-
else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
41
|
-
const base = out[key];
|
|
42
|
-
out[key] = mergePatch(base && typeof base === 'object' && !Array.isArray(base) ? base : {}, value);
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
out[key] = value;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return out;
|
|
49
|
-
}
|
|
50
23
|
/**
|
|
51
24
|
* ドラッグで動かしたノードの位置を保存する patch。
|
|
52
25
|
* 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。
|
|
53
26
|
* 子のノードの position は親の中での位置なので、親を動かしても子の patch は要らない。
|
|
54
27
|
*/
|
|
55
28
|
export function movePatch(node, move) {
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
collection: 'nodes',
|
|
59
|
-
id: node.id,
|
|
60
|
-
patch: {
|
|
61
|
-
position: {
|
|
62
|
-
x: Math.round(base.x + move.dx),
|
|
63
|
-
y: Math.round(base.y + move.dy),
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
// ---- 入力の読み取り ------------------------------------------------------------
|
|
69
|
-
function toNumber(value) {
|
|
70
|
-
const n = Number(value);
|
|
71
|
-
return value !== '' && Number.isFinite(n) ? n : null;
|
|
29
|
+
return movePatchTo(node, move, 'nodes');
|
|
72
30
|
}
|
|
73
|
-
|
|
74
|
-
* 色として読める文字列か。
|
|
75
|
-
* ブラウザでは CSS.supports('color', v) で判定する。Node など CSS の無い環境では、
|
|
76
|
-
* #rgb / #rrggbb / #rrggbbaa・rgb() などの関数表記・英字だけの色名、を通す緩い判定にする。
|
|
77
|
-
*/
|
|
78
|
-
export function isColor(value) {
|
|
79
|
-
if (value === '')
|
|
80
|
-
return false;
|
|
81
|
-
const css = globalThis.CSS;
|
|
82
|
-
if (css && typeof css.supports === 'function')
|
|
83
|
-
return css.supports('color', value);
|
|
84
|
-
return /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)
|
|
85
|
-
|| /^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(.+\)$/i.test(value)
|
|
86
|
-
|| /^[a-z]+$/i.test(value);
|
|
87
|
-
}
|
|
88
|
-
function toColor(value, label) {
|
|
89
|
-
if (!isColor(value))
|
|
90
|
-
throw new ModelError(`${label}は、色 (#ff0000 や red など) で入力してください。`);
|
|
91
|
-
return value;
|
|
92
|
-
}
|
|
93
|
-
// 0 以上の数(枠線・線の太さ。0 は線なし)
|
|
94
|
-
function toWidth(value, label) {
|
|
95
|
-
const n = toNumber(value);
|
|
96
|
-
if (n === null || n < 0)
|
|
97
|
-
throw new ModelError(`${label}は、0 以上の数で入力してください。`);
|
|
98
|
-
return n;
|
|
99
|
-
}
|
|
100
|
-
// 0 以上の整数(幅・高さ。0 は「子に合わせる」)
|
|
101
|
-
function toLength(value, label) {
|
|
102
|
-
const n = toNumber(value);
|
|
103
|
-
if (n === null || n < 0)
|
|
104
|
-
throw new ModelError(`${label}は 0 以上の数で入力してください。(0 で子に合わせる)`);
|
|
105
|
-
return Math.round(n);
|
|
106
|
-
}
|
|
107
|
-
// 角度(0 以上 360 未満の整数に正規化)
|
|
108
|
-
function toDegree(value, label) {
|
|
109
|
-
const n = toNumber(value);
|
|
110
|
-
if (n === null)
|
|
111
|
-
throw new ModelError(`${label}は、角度 (数) で入力してください。`);
|
|
112
|
-
return ((Math.round(n) % 360) + 360) % 360;
|
|
113
|
-
}
|
|
114
|
-
// 空にできる文字列。空なら null(JSON Merge Patch で項目を除く)
|
|
115
|
-
function toOptionalText(value) {
|
|
116
|
-
return value === '' ? null : value;
|
|
117
|
-
}
|
|
118
|
-
// patch の入れ子に値を置く(patch.border.color = v のように)
|
|
119
|
-
function put(patch, path, value) {
|
|
120
|
-
const keys = path.split('.');
|
|
121
|
-
let cur = patch;
|
|
122
|
-
for (const key of keys.slice(0, -1)) {
|
|
123
|
-
if (!cur[key] || typeof cur[key] !== 'object')
|
|
124
|
-
cur[key] = {};
|
|
125
|
-
cur = cur[key];
|
|
126
|
-
}
|
|
127
|
-
cur[keys[keys.length - 1]] = value;
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* インスペクタの入力のうち、図に描いている値(withDefaults で埋めた値)から変わった項目だけを返す。
|
|
131
|
-
* 前後の空白は除く。読み取り専用の項目と、values に無い項目は含めない。
|
|
132
|
-
*/
|
|
31
|
+
// インスペクタの入力のうち、図に描いている値(withDefaults)から変わった項目だけ
|
|
133
32
|
function changedValues(record, values) {
|
|
134
|
-
|
|
135
|
-
const out = new Map();
|
|
136
|
-
for (const field of fieldsOf(record)) {
|
|
137
|
-
if (field.type === 'readonly')
|
|
138
|
-
continue;
|
|
139
|
-
const value = values[field.path];
|
|
140
|
-
if (value === undefined)
|
|
141
|
-
continue;
|
|
142
|
-
const current = getByPath(effective, field.path);
|
|
143
|
-
if (value.trim() !== String(current ?? ''))
|
|
144
|
-
out.set(field.path, value.trim());
|
|
145
|
-
}
|
|
146
|
-
return out;
|
|
33
|
+
return changedValuesOf(withDefaults(record), values, fieldsOf(record));
|
|
147
34
|
}
|
|
35
|
+
// 幅・高さは 0 以上(0 は「子に合わせる」)。枠線・線の太さは 0 以上(0 は線なし)
|
|
36
|
+
const LENGTH = { hint: '(0 で子に合わせる)' };
|
|
37
|
+
const WIDTH = { allowZero: true };
|
|
148
38
|
// ノードの入力 → patch(label.contents / node_type / link.url / size / background / border)
|
|
149
39
|
function nodePatch(node, values) {
|
|
150
40
|
const patch = {};
|
|
@@ -167,10 +57,10 @@ function nodePatch(node, values) {
|
|
|
167
57
|
put(patch, path, value);
|
|
168
58
|
break;
|
|
169
59
|
case 'size.w':
|
|
170
|
-
put(patch, path, toLength(value, '幅'));
|
|
60
|
+
put(patch, path, toLength(value, '幅', LENGTH));
|
|
171
61
|
break;
|
|
172
62
|
case 'size.h':
|
|
173
|
-
put(patch, path, toLength(value, '高さ'));
|
|
63
|
+
put(patch, path, toLength(value, '高さ', LENGTH));
|
|
174
64
|
break;
|
|
175
65
|
case 'background.color':
|
|
176
66
|
put(patch, path, toColor(value, '背景色'));
|
|
@@ -179,7 +69,7 @@ function nodePatch(node, values) {
|
|
|
179
69
|
put(patch, path, toColor(value, '枠線の色'));
|
|
180
70
|
break;
|
|
181
71
|
case 'border.width':
|
|
182
|
-
put(patch, path, toWidth(value, '枠線の太さ'));
|
|
72
|
+
put(patch, path, toWidth(value, '枠線の太さ', WIDTH));
|
|
183
73
|
break;
|
|
184
74
|
}
|
|
185
75
|
}
|
|
@@ -199,7 +89,7 @@ function edgePatch(edge, values) {
|
|
|
199
89
|
put(patch, path, toColor(value, '線の色'));
|
|
200
90
|
break;
|
|
201
91
|
case 'stroke.width':
|
|
202
|
-
put(patch, path, toWidth(value, '線の太さ'));
|
|
92
|
+
put(patch, path, toWidth(value, '線の太さ', WIDTH));
|
|
203
93
|
break;
|
|
204
94
|
}
|
|
205
95
|
}
|
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 SitemapId = number | string;
|
|
@@ -61,10 +62,8 @@ export interface SitemapGraph {
|
|
|
61
62
|
}
|
|
62
63
|
/** レコードを置くコレクション */
|
|
63
64
|
export type SitemapCollection = 'nodes' | 'edges';
|
|
64
|
-
/** JSON Merge Patch(RFC 7386)。null
|
|
65
|
-
export type MergePatch
|
|
66
|
-
[key: string]: unknown;
|
|
67
|
-
};
|
|
65
|
+
/** JSON Merge Patch(RFC 7386)。null は「その項目を除く」。model.core の MergePatch と同じ(iHub の PATCH /{collection}/{id} が受け取る形) */
|
|
66
|
+
export type { MergePatch };
|
|
68
67
|
/** 保存する変更。利用側は図の種類を知らずにこれを保存すればよい */
|
|
69
68
|
export interface SitemapChange {
|
|
70
69
|
collection: SitemapCollection;
|
|
@@ -72,22 +71,13 @@ export interface SitemapChange {
|
|
|
72
71
|
patch: MergePatch;
|
|
73
72
|
}
|
|
74
73
|
/** ドラッグの移動量(世界座標。d3.sitemap の callbacks.node.dragend が渡す move と同じ) */
|
|
75
|
-
export
|
|
76
|
-
dx: number;
|
|
77
|
-
dy: number;
|
|
78
|
-
}
|
|
74
|
+
export type SitemapMove = ModelMove;
|
|
79
75
|
/**
|
|
80
76
|
* 編集できる項目。colonoscope の ColonoscopeField(path / label / type)と同じ形で、
|
|
81
77
|
* 取りうる値の一覧 options だけ足している(型は構造的に互換。colonoscope には依存しない)。
|
|
82
78
|
*/
|
|
83
|
-
export type SitemapFieldType =
|
|
84
|
-
export
|
|
85
|
-
/** レコードの項目のパス(ドット区切り。例: 'link.url')。写し先ではなく実際の項目を指す */
|
|
86
|
-
path: string;
|
|
87
|
-
label: string;
|
|
88
|
-
type: SitemapFieldType;
|
|
89
|
-
options?: readonly string[];
|
|
90
|
-
}
|
|
79
|
+
export type SitemapFieldType = ModelFieldType;
|
|
80
|
+
export type SitemapField = ModelField;
|
|
91
81
|
/** インスペクタの「適用」が渡す値(path → 入力した文字列) */
|
|
92
|
-
export type SitemapValues =
|
|
82
|
+
export type SitemapValues = ModelValues;
|
|
93
83
|
//# 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":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE3F,6CAA6C;AAC7C,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAExC,2BAA2B;AAC3B,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,qBAAqB;IACrB,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC;CAC7D;AAED,kCAAkC;AAClC,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wDAAwD;AACxD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,SAAS,CAAC;IACd,kBAAkB;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAChC,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,IAAI,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACrE,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,mEAAmE;AACnE,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,SAAS,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,iBAAiB;AACjB,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,SAAS,CAAC;IACd,IAAI,EAAE,cAAc,CAAC;IACrB,EAAE,EAAE,cAAc,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAElE,qCAAqC;AACrC,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAC7B;AAED,oBAAoB;AACpB,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,OAAO,CAAC;AAElD,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yanqirenshi/model.sitemap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Diagram model for sitemap diagrams (d3.sitemap) — 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('sitemap', data) と同じ検査・同じ文言。
|
|
5
|
-
//
|
|
5
|
+
// 検査そのものは graph 系(unchi / sitemap / deployment)で共通なので model.core の checkGraph にあり、
|
|
6
|
+
// ここでは model.sitemap の型を付けて公開する(#151)。
|
|
6
7
|
// メッセージの先頭はコレクション名で、prefix を渡すと 'sitemap/nodes: ...' のように付けられる。
|
|
7
8
|
|
|
9
|
+
import { checkGraph, flattenNodes, findById as findInList } from '@yanqirenshi/model.core';
|
|
10
|
+
|
|
8
11
|
import type { SitemapGraph, SitemapId, SitemapNodeRecord, SitemapRecord } from './types.js';
|
|
9
12
|
|
|
10
13
|
/** 入れ子のノード(children)を平らにする(親 → 子 の順) */
|
|
11
14
|
export function flatten(nodes: SitemapNodeRecord[]): SitemapNodeRecord[] {
|
|
12
|
-
return nodes
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// list の中で id が重複していればエラーにする。id の集合を返す。
|
|
16
|
-
function checkUnique(errors: string[], label: string, list: { id: SitemapId }[]): Set<SitemapId> {
|
|
17
|
-
const values = new Set<SitemapId>();
|
|
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: SitemapId }[])
|
|
|
35
24
|
* @param prefix メッセージのコレクション名の前に付ける文字列(例: 'sitemap/')
|
|
36
25
|
*/
|
|
37
26
|
export function check(data: SitemapGraph, 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 SitemapRecord>(list: readonly T[] | undefined, id: SitemapId): T | null {
|
|
54
|
-
|
|
55
|
-
if (d.id === id)
|
|
56
|
-
return d;
|
|
57
|
-
|
|
58
|
-
const children = (d as SitemapNodeRecord).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,7 +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)
|
|
10
|
+
// graph 系(unchi / sitemap / deployment)で共通の check / flatten / findById / movePatch / mergePatch / getByPath / isColor と
|
|
11
|
+
// 入力の読み取り部品は model.core(#151)のもので、ここでは model.sitemap の型を付けて公開している。
|
|
11
12
|
|
|
12
13
|
export type {
|
|
13
14
|
SitemapId, SitemapNodeLabel, SitemapNodeLink, SitemapNodeRecord, SitemapEdgeEnd, SitemapEdgeRecord, SitemapRecord,
|
package/src/patch.ts
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
// 戻り値は { collection, id, patch }。利用側(iHub など)は図の種類を知らずにこれを保存すればよい。
|
|
4
4
|
// 入力の検証に失敗したときは ModelError を投げる(メッセージは利用者にそのまま見せられる日本語)。
|
|
5
5
|
// model.unchi の patch.ts を元にしている(ラベルは label.contents、リンクは link.url、マーカーと border.type は無い)。
|
|
6
|
+
// 読み取り部品(toColor など)と patch の道具は model.core にある(#151)。
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
ModelError, changedValues as changedValuesOf, movePatch as movePatchTo, put,
|
|
10
|
+
toColor, toDegree, toLength, toOptionalText, toWidth,
|
|
11
|
+
} from '@yanqirenshi/model.core';
|
|
6
12
|
|
|
7
13
|
import { isEdge, withDefaults } from './defaults.js';
|
|
8
14
|
import { fieldsOf } from './fields.js';
|
|
@@ -11,13 +17,8 @@ import type {
|
|
|
11
17
|
SitemapValues,
|
|
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.sitemap だけを読めばよいように)
|
|
21
|
+
export { ModelError, getByPath, mergePatch, isColor } from '@yanqirenshi/model.core';
|
|
21
22
|
|
|
22
23
|
/** レコードを置くコレクション */
|
|
23
24
|
export function collectionOf(record: SitemapRecord): SitemapCollection {
|
|
@@ -34,169 +35,24 @@ export function titleOf(record: SitemapRecord): string {
|
|
|
34
35
|
return typeof contents === 'string' && contents !== '' ? contents : String(record.id);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
/** ドット区切りの path で値を取り出す */
|
|
38
|
-
export function getByPath(obj: unknown, path: string): unknown {
|
|
39
|
-
return path.split('.').reduce<unknown>(
|
|
40
|
-
(acc, key) => (acc == null ? acc : (acc as Record<string, unknown>)[key]),
|
|
41
|
-
obj,
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
|
|
47
|
-
* null の項目は除く。保存したあと、表示を先に更新するときに使う。
|
|
48
|
-
*/
|
|
49
|
-
export function mergePatch<T extends object>(record: T, patch: MergePatch): T {
|
|
50
|
-
const out: Record<string, unknown> = { ...(record as Record<string, unknown>) };
|
|
51
|
-
|
|
52
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
53
|
-
if (value === null) {
|
|
54
|
-
delete out[key];
|
|
55
|
-
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
56
|
-
const base = out[key];
|
|
57
|
-
|
|
58
|
-
out[key] = mergePatch(
|
|
59
|
-
base && typeof base === 'object' && !Array.isArray(base) ? (base as object) : {},
|
|
60
|
-
value as MergePatch,
|
|
61
|
-
);
|
|
62
|
-
} else {
|
|
63
|
-
out[key] = value;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return out as T;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
38
|
/**
|
|
71
39
|
* ドラッグで動かしたノードの位置を保存する patch。
|
|
72
40
|
* 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。
|
|
73
41
|
* 子のノードの position は親の中での位置なので、親を動かしても子の patch は要らない。
|
|
74
42
|
*/
|
|
75
43
|
export function movePatch(node: SitemapNodeRecord, move: SitemapMove): SitemapChange {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
collection: 'nodes',
|
|
80
|
-
id: node.id,
|
|
81
|
-
patch: {
|
|
82
|
-
position: {
|
|
83
|
-
x: Math.round(base.x + move.dx),
|
|
84
|
-
y: Math.round(base.y + move.dy),
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ---- 入力の読み取り ------------------------------------------------------------
|
|
91
|
-
|
|
92
|
-
function toNumber(value: string): number | null {
|
|
93
|
-
const n = Number(value);
|
|
94
|
-
|
|
95
|
-
return value !== '' && Number.isFinite(n) ? n : null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* 色として読める文字列か。
|
|
100
|
-
* ブラウザでは CSS.supports('color', v) で判定する。Node など CSS の無い環境では、
|
|
101
|
-
* #rgb / #rrggbb / #rrggbbaa・rgb() などの関数表記・英字だけの色名、を通す緩い判定にする。
|
|
102
|
-
*/
|
|
103
|
-
export function isColor(value: string): boolean {
|
|
104
|
-
if (value === '')
|
|
105
|
-
return false;
|
|
106
|
-
|
|
107
|
-
const css = (globalThis as { CSS?: { supports?: (p: string, v: string) => boolean } }).CSS;
|
|
108
|
-
|
|
109
|
-
if (css && typeof css.supports === 'function')
|
|
110
|
-
return css.supports('color', value);
|
|
111
|
-
|
|
112
|
-
return /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)
|
|
113
|
-
|| /^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(.+\)$/i.test(value)
|
|
114
|
-
|| /^[a-z]+$/i.test(value);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function toColor(value: string, label: string): string {
|
|
118
|
-
if (!isColor(value))
|
|
119
|
-
throw new ModelError(`${label}は、色 (#ff0000 や red など) で入力してください。`);
|
|
120
|
-
|
|
121
|
-
return value;
|
|
44
|
+
return movePatchTo(node, move, 'nodes');
|
|
122
45
|
}
|
|
123
46
|
|
|
124
|
-
//
|
|
125
|
-
function toWidth(value: string, label: string): number {
|
|
126
|
-
const n = toNumber(value);
|
|
127
|
-
|
|
128
|
-
if (n === null || n < 0)
|
|
129
|
-
throw new ModelError(`${label}は、0 以上の数で入力してください。`);
|
|
130
|
-
|
|
131
|
-
return n;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 0 以上の整数(幅・高さ。0 は「子に合わせる」)
|
|
135
|
-
function toLength(value: string, label: string): number {
|
|
136
|
-
const n = toNumber(value);
|
|
137
|
-
|
|
138
|
-
if (n === null || n < 0)
|
|
139
|
-
throw new ModelError(`${label}は 0 以上の数で入力してください。(0 で子に合わせる)`);
|
|
140
|
-
|
|
141
|
-
return Math.round(n);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// 角度(0 以上 360 未満の整数に正規化)
|
|
145
|
-
function toDegree(value: string, label: string): number {
|
|
146
|
-
const n = toNumber(value);
|
|
147
|
-
|
|
148
|
-
if (n === null)
|
|
149
|
-
throw new ModelError(`${label}は、角度 (数) で入力してください。`);
|
|
150
|
-
|
|
151
|
-
return ((Math.round(n) % 360) + 360) % 360;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// 空にできる文字列。空なら null(JSON Merge Patch で項目を除く)
|
|
155
|
-
function toOptionalText(value: string): string | null {
|
|
156
|
-
return value === '' ? null : value;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// patch の入れ子に値を置く(patch.border.color = v のように)
|
|
160
|
-
function put(patch: MergePatch, path: string, value: unknown): void {
|
|
161
|
-
const keys = path.split('.');
|
|
162
|
-
let cur = patch;
|
|
163
|
-
|
|
164
|
-
for (const key of keys.slice(0, -1)) {
|
|
165
|
-
if (!cur[key] || typeof cur[key] !== 'object')
|
|
166
|
-
cur[key] = {};
|
|
167
|
-
|
|
168
|
-
cur = cur[key] as MergePatch;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
cur[keys[keys.length - 1]] = value;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* インスペクタの入力のうち、図に描いている値(withDefaults で埋めた値)から変わった項目だけを返す。
|
|
176
|
-
* 前後の空白は除く。読み取り専用の項目と、values に無い項目は含めない。
|
|
177
|
-
*/
|
|
47
|
+
// インスペクタの入力のうち、図に描いている値(withDefaults)から変わった項目だけ
|
|
178
48
|
function changedValues(record: SitemapRecord, values: SitemapValues): Map<string, string> {
|
|
179
|
-
|
|
180
|
-
const out = new Map<string, string>();
|
|
181
|
-
|
|
182
|
-
for (const field of fieldsOf(record)) {
|
|
183
|
-
if (field.type === 'readonly')
|
|
184
|
-
continue;
|
|
185
|
-
|
|
186
|
-
const value = values[field.path];
|
|
187
|
-
|
|
188
|
-
if (value === undefined)
|
|
189
|
-
continue;
|
|
190
|
-
|
|
191
|
-
const current = getByPath(effective, field.path);
|
|
192
|
-
|
|
193
|
-
if (value.trim() !== String(current ?? ''))
|
|
194
|
-
out.set(field.path, value.trim());
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return out;
|
|
49
|
+
return changedValuesOf(withDefaults(record), values, fieldsOf(record));
|
|
198
50
|
}
|
|
199
51
|
|
|
52
|
+
// 幅・高さは 0 以上(0 は「子に合わせる」)。枠線・線の太さは 0 以上(0 は線なし)
|
|
53
|
+
const LENGTH = { hint: '(0 で子に合わせる)' };
|
|
54
|
+
const WIDTH = { allowZero: true };
|
|
55
|
+
|
|
200
56
|
// ノードの入力 → patch(label.contents / node_type / link.url / size / background / border)
|
|
201
57
|
function nodePatch(node: SitemapNodeRecord, values: SitemapValues): MergePatch {
|
|
202
58
|
const patch: MergePatch = {};
|
|
@@ -221,10 +77,10 @@ function nodePatch(node: SitemapNodeRecord, values: SitemapValues): MergePatch {
|
|
|
221
77
|
put(patch, path, value);
|
|
222
78
|
break;
|
|
223
79
|
case 'size.w':
|
|
224
|
-
put(patch, path, toLength(value, '幅'));
|
|
80
|
+
put(patch, path, toLength(value, '幅', LENGTH));
|
|
225
81
|
break;
|
|
226
82
|
case 'size.h':
|
|
227
|
-
put(patch, path, toLength(value, '高さ'));
|
|
83
|
+
put(patch, path, toLength(value, '高さ', LENGTH));
|
|
228
84
|
break;
|
|
229
85
|
case 'background.color':
|
|
230
86
|
put(patch, path, toColor(value, '背景色'));
|
|
@@ -233,7 +89,7 @@ function nodePatch(node: SitemapNodeRecord, values: SitemapValues): MergePatch {
|
|
|
233
89
|
put(patch, path, toColor(value, '枠線の色'));
|
|
234
90
|
break;
|
|
235
91
|
case 'border.width':
|
|
236
|
-
put(patch, path, toWidth(value, '枠線の太さ'));
|
|
92
|
+
put(patch, path, toWidth(value, '枠線の太さ', WIDTH));
|
|
237
93
|
break;
|
|
238
94
|
}
|
|
239
95
|
}
|
|
@@ -257,7 +113,7 @@ function edgePatch(edge: SitemapEdgeRecord, values: SitemapValues): MergePatch {
|
|
|
257
113
|
put(patch, path, toColor(value, '線の色'));
|
|
258
114
|
break;
|
|
259
115
|
case 'stroke.width':
|
|
260
|
-
put(patch, path, toWidth(value, '線の太さ'));
|
|
116
|
+
put(patch, path, toWidth(value, '線の太さ', WIDTH));
|
|
261
117
|
break;
|
|
262
118
|
}
|
|
263
119
|
}
|
package/src/types.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// ノードと線の図なので model.unchi と形が近いが、ラベルは label.contents(unchi は label.text)で、
|
|
6
6
|
// ノードにリンク(link.url)と URL パス(path)、画面の種類(node_type)がある。
|
|
7
7
|
|
|
8
|
+
import type { MergePatch, ModelField, ModelFieldType, ModelMove, ModelValues } from '@yanqirenshi/model.core';
|
|
8
9
|
import type { Background, Border, Font, Position, Size, Stroke } from '@yanqirenshi/types';
|
|
9
10
|
|
|
10
11
|
/** レコードの id(nodes / edges とも。ノードは子も含めて一意) */
|
|
@@ -73,8 +74,8 @@ export interface SitemapGraph {
|
|
|
73
74
|
/** レコードを置くコレクション */
|
|
74
75
|
export type SitemapCollection = 'nodes' | 'edges';
|
|
75
76
|
|
|
76
|
-
/** JSON Merge Patch(RFC 7386)。null
|
|
77
|
-
export type
|
|
77
|
+
/** JSON Merge Patch(RFC 7386)。null は「その項目を除く」。model.core の MergePatch と同じ(iHub の PATCH /{collection}/{id} が受け取る形) */
|
|
78
|
+
export type { MergePatch };
|
|
78
79
|
|
|
79
80
|
/** 保存する変更。利用側は図の種類を知らずにこれを保存すればよい */
|
|
80
81
|
export interface SitemapChange {
|
|
@@ -84,24 +85,15 @@ export interface SitemapChange {
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
/** ドラッグの移動量(世界座標。d3.sitemap の callbacks.node.dragend が渡す move と同じ) */
|
|
87
|
-
export
|
|
88
|
-
dx: number;
|
|
89
|
-
dy: number;
|
|
90
|
-
}
|
|
88
|
+
export type SitemapMove = ModelMove;
|
|
91
89
|
|
|
92
90
|
/**
|
|
93
91
|
* 編集できる項目。colonoscope の ColonoscopeField(path / label / type)と同じ形で、
|
|
94
92
|
* 取りうる値の一覧 options だけ足している(型は構造的に互換。colonoscope には依存しない)。
|
|
95
93
|
*/
|
|
96
|
-
export type SitemapFieldType =
|
|
97
|
-
|
|
98
|
-
export
|
|
99
|
-
/** レコードの項目のパス(ドット区切り。例: 'link.url')。写し先ではなく実際の項目を指す */
|
|
100
|
-
path: string;
|
|
101
|
-
label: string;
|
|
102
|
-
type: SitemapFieldType;
|
|
103
|
-
options?: readonly string[];
|
|
104
|
-
}
|
|
94
|
+
export type SitemapFieldType = ModelFieldType;
|
|
95
|
+
|
|
96
|
+
export type SitemapField = ModelField;
|
|
105
97
|
|
|
106
98
|
/** インスペクタの「適用」が渡す値(path → 入力した文字列) */
|
|
107
|
-
export type SitemapValues =
|
|
99
|
+
export type SitemapValues = ModelValues;
|