@liquidcars/atlas-layout 0.1.15 → 0.1.17
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 +8 -0
- package/package.json +1 -1
- package/src/index.js +21 -4
- package/src/spec.js +9 -1
package/README.md
CHANGED
|
@@ -173,6 +173,14 @@ relations:
|
|
|
173
173
|
|
|
174
174
|
`label` es el título corto visible junto a la conexión, `text` permite documentar su significado y `url` enlaza documentación externa desde el panel de información. `mode` es la forma recomendada y el compilador siempre la emite normalizada. Para facilitar la migración se aceptan también `visual`, `render`, `type`, `direction`, `bidirectional: true` y `broken: true`, además de los alias `two-way`, `both` e `interrupted`. La forma abreviada puede incluir el modo como cuarto valor: `[from, to, label, mode]`. Un modo desconocido conserva la relación como `directed` y añade el diagnóstico `UNKNOWN_RELATION_MODE`.
|
|
175
175
|
|
|
176
|
+
Las relaciones también pueden declararse en `relations` dentro de cualquier entidad
|
|
177
|
+
`type: container`. El compilador aplana las relaciones raíz y anidadas en un único
|
|
178
|
+
array de runtime. La exportación visual usa una forma canónica: cada relación se
|
|
179
|
+
guarda en el contenedor común más profundo de sus dos extremos; si no existe un
|
|
180
|
+
contenedor común, se guarda en `relations` en la raíz. Así, compilar, editar y
|
|
181
|
+
volver a exportar no depende de dónde se declaró originalmente la conexión.
|
|
182
|
+
|
|
183
|
+
|
|
176
184
|
En un documento Markdown dividido, el bloque de relaciones contiene una lista YAML:
|
|
177
185
|
|
|
178
186
|
````markdown
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -127,6 +127,7 @@ function normalizeEntityTypes(flat, geometryCatalog, diagnostics) {
|
|
|
127
127
|
const declared = typeof declaredValue === "string" ? declaredValue.trim().toLowerCase() : "";
|
|
128
128
|
const childIds = flat.childrenById.get(entity.id) || [];
|
|
129
129
|
const hasChildren = childIds.length > 0 || (Array.isArray(source.children) && source.children.length > 0);
|
|
130
|
+
const hasRelations = Array.isArray(source.relations) && source.relations.length > 0;
|
|
130
131
|
|
|
131
132
|
if (declaredValue != null && typeof declaredValue !== "string") {
|
|
132
133
|
diagnostic(diagnostics, "fatal", "UNKNOWN_ENTITY_TYPE",
|
|
@@ -140,6 +141,10 @@ function normalizeEntityTypes(flat, geometryCatalog, diagnostics) {
|
|
|
140
141
|
diagnostic(diagnostics, "fatal", "CONTAINER_TYPE_REQUIRED_FOR_CHILDREN",
|
|
141
142
|
"Entity '" + entity.id + "' declares children and must set type: container.",
|
|
142
143
|
{ entityId: entity.id });
|
|
144
|
+
} else if (hasRelations && !["container", "shell"].includes(declared)) {
|
|
145
|
+
diagnostic(diagnostics, "fatal", "CONTAINER_TYPE_REQUIRED_FOR_RELATIONS",
|
|
146
|
+
"Entity '" + entity.id + "' declares relations and must set type: container.",
|
|
147
|
+
{ entityId: entity.id });
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
if (declared && !["item", "container", "shell"].includes(declared)) {
|
|
@@ -148,7 +153,7 @@ function normalizeEntityTypes(flat, geometryCatalog, diagnostics) {
|
|
|
148
153
|
{ entityId: entity.id, type: source.type });
|
|
149
154
|
}
|
|
150
155
|
|
|
151
|
-
const type = hasChildren || declared === "container" || declared === "shell" ? "container" : "item";
|
|
156
|
+
const type = hasChildren || hasRelations || declared === "container" || declared === "shell" ? "container" : "item";
|
|
152
157
|
entity.type = type;
|
|
153
158
|
|
|
154
159
|
if (Object.hasOwn(source, "open") && typeof source.open !== "boolean") {
|
|
@@ -175,6 +180,7 @@ function flattenEntities(input, diagnostics) {
|
|
|
175
180
|
const entities = [];
|
|
176
181
|
const childrenById = new Map();
|
|
177
182
|
const sourceById = new Map();
|
|
183
|
+
const relations = [];
|
|
178
184
|
const seen = new Set();
|
|
179
185
|
|
|
180
186
|
function visit(items, parent = null) {
|
|
@@ -193,6 +199,8 @@ function flattenEntities(input, diagnostics) {
|
|
|
193
199
|
const entity = { ...raw };
|
|
194
200
|
delete entity.children;
|
|
195
201
|
delete entity.layout;
|
|
202
|
+
delete entity.relations;
|
|
203
|
+
if (Array.isArray(raw.relations)) relations.push(...raw.relations);
|
|
196
204
|
entity.__atlasExplicitSize = Array.isArray(raw.size) && raw.size.length === 3;
|
|
197
205
|
entity.parent = parent ?? raw.parent ?? null;
|
|
198
206
|
entity.type = raw.type;
|
|
@@ -216,7 +224,7 @@ function flattenEntities(input, diagnostics) {
|
|
|
216
224
|
if (!siblings.includes(entity.id)) siblings.push(entity.id);
|
|
217
225
|
}
|
|
218
226
|
}
|
|
219
|
-
return { entities, childrenById, sourceById };
|
|
227
|
+
return { entities, childrenById, sourceById, relations };
|
|
220
228
|
}
|
|
221
229
|
|
|
222
230
|
function normalizeRelationMode(relation, diagnostics, index, shorthandMode) {
|
|
@@ -1193,16 +1201,25 @@ function projectedAtlasSource(input, visibleIds) {
|
|
|
1193
1201
|
}
|
|
1194
1202
|
}
|
|
1195
1203
|
|
|
1204
|
+
const relationIsVisible = relation => {
|
|
1205
|
+
const from = Array.isArray(relation) ? relation[0] : relation?.from;
|
|
1206
|
+
const to = Array.isArray(relation) ? relation[1] : relation?.to;
|
|
1207
|
+
return requested.has(from) && requested.has(to);
|
|
1208
|
+
};
|
|
1196
1209
|
function filterEntities(items) {
|
|
1197
1210
|
return (Array.isArray(items) ? items : []).flatMap(entity => {
|
|
1198
1211
|
if (!entity?.id || !requested.has(entity.id)) return [];
|
|
1199
1212
|
const copy = { ...entity };
|
|
1200
1213
|
if (Array.isArray(entity.children)) copy.children = filterEntities(entity.children);
|
|
1214
|
+
if (Array.isArray(entity.relations)) {
|
|
1215
|
+
copy.relations = entity.relations.filter(relationIsVisible);
|
|
1216
|
+
if (!copy.relations.length) delete copy.relations;
|
|
1217
|
+
}
|
|
1201
1218
|
return [copy];
|
|
1202
1219
|
});
|
|
1203
1220
|
}
|
|
1204
1221
|
source.entities = filterEntities(source.entities);
|
|
1205
|
-
source.relations = (source.relations || []).filter(
|
|
1222
|
+
source.relations = (source.relations || []).filter(relationIsVisible);
|
|
1206
1223
|
|
|
1207
1224
|
const childrenByParent = new Map();
|
|
1208
1225
|
function index(items, nestedParent = null) {
|
|
@@ -1849,7 +1866,7 @@ function compileResolvedAtlasModel(input, options = {}) {
|
|
|
1849
1866
|
normalizeEntityTypes(flat, options.geometryCatalog || [], diagnostics);
|
|
1850
1867
|
if (!flat.entities.length) diagnostic(diagnostics, "fatal", "NO_VALID_ENTITIES", "The model does not contain any valid entities.");
|
|
1851
1868
|
const validIds = new Set(flat.entities.map(entity => entity.id));
|
|
1852
|
-
const relations = normalizeRelations(source.relations, validIds, diagnostics);
|
|
1869
|
+
const relations = normalizeRelations([...(source.relations || []), ...flat.relations], validIds, diagnostics);
|
|
1853
1870
|
const modelStyle = mergeStyle(DEFAULT_MODEL_STYLE, source.style || {});
|
|
1854
1871
|
if (!Object.keys(modelStyle.edges || {}).length) delete modelStyle.edges;
|
|
1855
1872
|
resolveStyles(flat.entities, flat.sourceById, flat.childrenById, palette, diagnostics, modelStyle);
|
package/src/spec.js
CHANGED
|
@@ -36,6 +36,7 @@ export const ATLAS_LAYOUT_SPEC_V1 = freeze({
|
|
|
36
36
|
open: field("Initial expansion state for a container; omitted means closed.", [true, false]),
|
|
37
37
|
parent: field("Parent entity id.", null, { type: "string", authoring: "absolute" }),
|
|
38
38
|
children: field("Nested entities.", null, { context: "entity", sequence: true }),
|
|
39
|
+
relations: field("Connections canonically owned by this container.", null, { context: "relation", sequence: true }),
|
|
39
40
|
geometry: field("Geometry id supplied by Atlas or a registered pack.", null, { valueSource: "geometryCatalog" }),
|
|
40
41
|
geometryOptions: field("Typed parameters supplied by the selected geometry.", null, { context: "geometryOptions" }),
|
|
41
42
|
geometryPalette: field("Local palette overrides supplied by the selected geometry.", null, { context: "geometryPalette" }),
|
|
@@ -184,7 +185,14 @@ export const ATLAS_LAYOUT_SPEC_V1 = freeze({
|
|
|
184
185
|
offset: field("Masonry row offset; defaults to half-cell.", ["half-cell"], { type: "string" })
|
|
185
186
|
})
|
|
186
187
|
}),
|
|
187
|
-
label: freeze({
|
|
188
|
+
label: freeze({
|
|
189
|
+
label: "Label appearance",
|
|
190
|
+
properties: freeze({
|
|
191
|
+
color: field("Label colour.", null, { valueSource: "palette" }),
|
|
192
|
+
opacity: field("Label opacity.", null, { type: "number" }),
|
|
193
|
+
anchor: field("Label placement relative to the geometry.", ["auto", "top", "bottom", "left", "right"])
|
|
194
|
+
})
|
|
195
|
+
}),
|
|
188
196
|
edges: freeze({ label: "Edge appearance", properties: freeze({ color: field("Edge colour.", null, { valueSource: "palette" }), opacity: field("Edge opacity.", null, { type: "number" }) }) }),
|
|
189
197
|
background: freeze({ label: "Background", properties: freeze({ color: field("Background colour.", null, { type: "color" }), glow: field("Background glow colour.", null, { type: "color" }) }) }),
|
|
190
198
|
lighting: freeze({ label: "Lighting", properties: freeze({ ambient: field("Ambient intensity.", null, { type: "number" }), directional: field("Directional intensity.", null, { type: "number" }) }) }),
|