mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-14 19:16:37 +00:00
Compare commits
3
Commits
b8d344ae60
...
03d784e0ed
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03d784e0ed | ||
|
|
324a7eac87 | ||
|
|
75e6b2b20c |
@@ -36,6 +36,9 @@ nothing is listening, so it stays useful in a bare checkout.
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `unit/test_list_filter.js` | no | `ListFilter` search/filter/sort/count/sticky, and the installed-plugins config **extracted verbatim** from `plugins_manager.js` so the test can't drift from it |
|
| `unit/test_list_filter.js` | no | `ListFilter` search/filter/sort/count/sticky, and the installed-plugins config **extracted verbatim** from `plugins_manager.js` so the test can't drift from it |
|
||||||
| `unit/test_render_cards.js` | no | `renderInstalledCards` markup, both empty states, and HTML-escaping of hostile plugin metadata |
|
| `unit/test_render_cards.js` | no | `renderInstalledCards` markup, both empty states, and HTML-escaping of hostile plugin metadata |
|
||||||
|
| `unit/test_style_editor_element_keys.js` | no | `elementKeys()` from `widgets/style-editor.js`: a hand-written `customization.layout` entry with no style block of its own still gets a row, since the widget claims the whole `layout` block from the generic fallback renderer |
|
||||||
|
| `unit/test_style_editor_layout_leaf_columns.js` | no | `columnsFor()` from `widgets/style-editor.js`: a layout-only key whose own value is a leaf (no x/y sub-object, e.g. a `show_logo` toggle) gets a self-keyed column instead of a blank, uneditable row |
|
||||||
|
| `unit/test_style_editor_layout_leaf_collision.js` | no | `columnsFor()` from `widgets/style-editor.js`: a layout-only leaf key still gets its own column even when its name collides with an unrelated element's style sub-field or another layout axis's sub-field |
|
||||||
| `dom/test_installed_dom.js` | yes | The toolbar in a real DOM: pill/search/sort interaction, the HTMX partial re-swap, and a `getComputedStyle` check that `.filter-pill[data-active]` really matches the emitted markup |
|
| `dom/test_installed_dom.js` | yes | The toolbar in a real DOM: pill/search/sort interaction, the HTMX partial re-swap, and a `getComputedStyle` check that `.filter-pill[data-active]` really matches the emitted markup |
|
||||||
| `dom/test_store_dom.js` | yes | Store pagination, per-page, category, tri-state Installed button, and persistence across a re-boot, against the live registry |
|
| `dom/test_store_dom.js` | yes | Store pagination, per-page, category, tri-state Installed button, and persistence across a re-boot, against the live registry |
|
||||||
| `dom/test_no_double_fetch.js` | yes | Loads the **whole** `plugins_manager.js` and counts requests: typing in the store search must filter the cached list, not refetch `/api/v3/plugins/store/list` |
|
| `dom/test_no_double_fetch.js` | yes | Loads the **whole** `plugins_manager.js` and counts requests: typing in the store search must filter the cached list, not refetch `/api/v3/plugins/store/list` |
|
||||||
|
|||||||
+3
-1
@@ -15,7 +15,9 @@ const fs = require('fs');
|
|||||||
|
|
||||||
const BASE = process.env.BASE || 'http://localhost:5000';
|
const BASE = process.env.BASE || 'http://localhost:5000';
|
||||||
const UNIT = ['unit/test_list_filter.js', 'unit/test_render_cards.js',
|
const UNIT = ['unit/test_list_filter.js', 'unit/test_render_cards.js',
|
||||||
'unit/test_html_escaping.js'];
|
'unit/test_html_escaping.js', 'unit/test_style_editor_element_keys.js',
|
||||||
|
'unit/test_style_editor_layout_leaf_columns.js',
|
||||||
|
'unit/test_style_editor_layout_leaf_collision.js'];
|
||||||
const DOM = ['dom/test_installed_dom.js', 'dom/test_store_dom.js', 'dom/test_no_double_fetch.js',
|
const DOM = ['dom/test_installed_dom.js', 'dom/test_store_dom.js', 'dom/test_no_double_fetch.js',
|
||||||
'dom/test_tools_sections.js'];
|
'dom/test_tools_sections.js'];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
// Regression test for style-editor.js's elementKeys(), extracted verbatim
|
||||||
|
// from the shipped widget (own/ownObj/elementKeys have no DOM dependency) so
|
||||||
|
// the test can't drift from the real implementation.
|
||||||
|
//
|
||||||
|
// render() in style-editor.js claims the whole `layout` child of a
|
||||||
|
// customization schema as the widget's own -- removing it from the generic
|
||||||
|
// fallback renderer entirely, because posting the same offset twice from
|
||||||
|
// two controls is worse than owning slightly too much. That is only safe
|
||||||
|
// when the style editor's own table actually renders a row for every key
|
||||||
|
// declared under `layout`. A hand-written schema can put a key under
|
||||||
|
// `layout` that never got its own top-level style block -- a logo, a
|
||||||
|
// timeout indicator, a possession arrow with a position but no font or
|
||||||
|
// colour -- and elementKeys() is what table() uses to decide which rows to
|
||||||
|
// draw. Miss one there and the fallback section that used to show it
|
||||||
|
// disappears with nothing replacing it.
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const V3 = path.resolve(__dirname, '../../../web_interface/static/v3');
|
||||||
|
|
||||||
|
const src = fs.readFileSync(path.join(V3, 'js/widgets/style-editor.js'), 'utf8');
|
||||||
|
const start = src.indexOf('function own(obj, key)');
|
||||||
|
const endMark = 'function titleOf(schema, key)';
|
||||||
|
const end = src.indexOf(endMark);
|
||||||
|
if (start === -1 || end === -1) {
|
||||||
|
console.error('FAIL: could not locate own()/elementKeys() in style-editor.js -- slice markers need updating');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
eval(src.slice(start, end)); // defines own/ownObj/elementKeys
|
||||||
|
|
||||||
|
let failures = 0;
|
||||||
|
function ok(desc, cond) {
|
||||||
|
if (cond) { console.log(`ok - ${desc}`); }
|
||||||
|
else { console.log(`FAIL - ${desc}`); failures++; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// A hand-written schema: score_text has a real style block (and a layout
|
||||||
|
// offset), home_logo has ONLY a layout offset + scale -- no font/colour of
|
||||||
|
// its own, the shape _element_block_from_spec never gives a plain
|
||||||
|
// offsets-only element -- and "possession" lives only under layout, with no
|
||||||
|
// sibling block in customization.properties at all (the case a hand-authored
|
||||||
|
// schema can produce that the compact x-style-elements expander cannot).
|
||||||
|
const schema = {
|
||||||
|
properties: {
|
||||||
|
score_text: {
|
||||||
|
type: 'object',
|
||||||
|
'x-style-managed': true,
|
||||||
|
properties: {
|
||||||
|
font: { type: 'string' },
|
||||||
|
text_color: { type: 'array' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
score_text: { type: 'object', properties: { x_offset: {}, y_offset: {} } },
|
||||||
|
possession: { type: 'object', properties: { x_offset: {}, y_offset: {} } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const keys = elementKeys(schema);
|
||||||
|
ok('the declared style element is included', keys.indexOf('score_text') !== -1);
|
||||||
|
ok('a layout-only key with no style block of its own is still included',
|
||||||
|
keys.indexOf('possession') !== -1);
|
||||||
|
ok('layout and modes containers themselves are never treated as elements',
|
||||||
|
keys.indexOf('layout') === -1 && keys.indexOf('modes') === -1);
|
||||||
|
ok('nothing is duplicated', keys.length === new Set(keys).size);
|
||||||
|
|
||||||
|
// A schema with no layout block at all must behave exactly as before --
|
||||||
|
// this is the overwhelmingly common case and must not gain a phantom row.
|
||||||
|
const noLayout = {
|
||||||
|
properties: {
|
||||||
|
score_text: {
|
||||||
|
type: 'object',
|
||||||
|
'x-style-managed': true,
|
||||||
|
properties: { font: { type: 'string' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ok('a schema without a layout block is unaffected',
|
||||||
|
elementKeys(noLayout).join(',') === 'score_text');
|
||||||
|
|
||||||
|
// Every layout key already covered by a style element must not be listed
|
||||||
|
// twice (order: declared elements first, then layout-only extras).
|
||||||
|
const covered = {
|
||||||
|
properties: {
|
||||||
|
score_text: {
|
||||||
|
type: 'object', 'x-style-managed': true,
|
||||||
|
properties: { font: { type: 'string' } },
|
||||||
|
},
|
||||||
|
home_logo: {
|
||||||
|
type: 'object', 'x-style-managed': true,
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
score_text: { type: 'object', properties: { x_offset: {} } },
|
||||||
|
home_logo: { type: 'object', properties: { x_offset: {}, scale: {} } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ok('a layout key matching an existing element is not appended again',
|
||||||
|
elementKeys(covered).join(',') === 'score_text,home_logo');
|
||||||
|
|
||||||
|
if (failures) { console.log(`\n${failures} failure(s)`); process.exit(1); }
|
||||||
|
console.log('\nall checks passed');
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
// Regression test for style-editor.js's columnsFor(), extracted verbatim
|
||||||
|
// from the shipped widget (see test_style_editor_layout_leaf_columns.js for
|
||||||
|
// the base case this builds on).
|
||||||
|
//
|
||||||
|
// A layout-only leaf key (e.g. a bare "scale" toggle directly under
|
||||||
|
// `layout`, no x/y object underneath) used to be keyed in the internal
|
||||||
|
// `seen` map by its own bare field name. If some *other* element's style
|
||||||
|
// block, or another element's layout axis block, happened to declare a
|
||||||
|
// sub-field with that exact same name (e.g. "scale" is also a real
|
||||||
|
// COLUMN_ORDER axis name most elements use), the `!seen.has(key)` guard
|
||||||
|
// skipped creating the leaf's column -- it silently reused the existing
|
||||||
|
// 'element'/'layout' column instead. That column's row binding in
|
||||||
|
// elementRow() then looked the field up under the wrong schema location for
|
||||||
|
// the leaf's own row, found nothing, and rendered a blank cell: the same
|
||||||
|
// "silently disappears" bug the leaf-column fix was meant to close, just
|
||||||
|
// reached through a name collision instead of a missing column altogether.
|
||||||
|
// This pins the fix: layout-leaf columns are keyed under a namespaced id so
|
||||||
|
// they can never be shadowed by an unrelated column sharing their name.
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const V3 = path.resolve(__dirname, '../../../web_interface/static/v3');
|
||||||
|
|
||||||
|
const src = fs.readFileSync(path.join(V3, 'js/widgets/style-editor.js'), 'utf8');
|
||||||
|
const start = src.indexOf('function own(obj, key)');
|
||||||
|
const endMark = 'function control(opts)';
|
||||||
|
const end = src.indexOf(endMark);
|
||||||
|
if (start === -1 || end === -1) {
|
||||||
|
console.error('FAIL: could not locate own()/columnsFor() in style-editor.js -- slice markers need updating');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
eval(src.slice(start, end)); // defines own/ownObj/elementKeys/columnsFor
|
||||||
|
|
||||||
|
let failures = 0;
|
||||||
|
function ok(desc, cond) {
|
||||||
|
if (cond) { console.log(`ok - ${desc}`); }
|
||||||
|
else { console.log(`FAIL - ${desc}`); failures++; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// `possession` is a real style element whose layout axis block declares a
|
||||||
|
// "scale" sub-field, which seeds seen.set('scale', 'layout'). `timeout_flag`
|
||||||
|
// is layout-only and a bare leaf valued directly under layout, but its own
|
||||||
|
// key is *also* "scale" -- an unrelated collision with that same name.
|
||||||
|
const schema = {
|
||||||
|
properties: {
|
||||||
|
possession: {
|
||||||
|
type: 'object',
|
||||||
|
'x-style-managed': true,
|
||||||
|
properties: { font: { type: 'string' } },
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
// The collision: a second element literally named "scale", entirely
|
||||||
|
// unrelated to the layout-only leaf below sharing that string.
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
possession: { type: 'object', properties: { scale: { type: 'number' } } },
|
||||||
|
scale: { type: 'boolean', default: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = columnsFor(schema);
|
||||||
|
const leafColumns = columns.filter(function (c) { return c.where === 'layout-leaf'; });
|
||||||
|
|
||||||
|
ok('the colliding layout axis sub-field still gets its own column',
|
||||||
|
columns.some(function (c) { return c.key === 'scale' && c.where === 'layout'; }));
|
||||||
|
ok('the layout-only leaf key still gets its own column despite the name collision',
|
||||||
|
leafColumns.length === 1 && leafColumns[0].key === 'scale');
|
||||||
|
ok('the leaf column is distinct from the colliding element/layout column',
|
||||||
|
columns.filter(function (c) { return c.key === 'scale'; }).length === 2);
|
||||||
|
|
||||||
|
if (failures) { console.log(`\n${failures} failure(s)`); process.exit(1); }
|
||||||
|
console.log('\nall checks passed');
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
// Regression test for style-editor.js's columnsFor(), extracted verbatim
|
||||||
|
// from the shipped widget (own/ownObj/elementKeys/columnsFor have no DOM
|
||||||
|
// dependency) so the test can't drift from the real implementation.
|
||||||
|
//
|
||||||
|
// elementKeys() already appends a layout-only key that never got its own
|
||||||
|
// top-level style block (see test_style_editor_element_keys.js), so table()
|
||||||
|
// draws a row for it. But render() still claims the whole `layout` child as
|
||||||
|
// the widget's own regardless of what that row actually shows -- and
|
||||||
|
// columnsFor() only produced a column for a layout-only key whose own value
|
||||||
|
// is an *object* with sub-fields (x_offset, y_offset, ...). A hand-written
|
||||||
|
// schema can instead put a plain leaf value directly under layout -- a
|
||||||
|
// "show_logo" toggle, a timeout indicator's on/off flag -- with no x/y
|
||||||
|
// object underneath it. That key still got a row (data-element="show_logo"),
|
||||||
|
// but zero columns ever matched it, so the row rendered with every cell
|
||||||
|
// blank: no control, and no way back to the fallback since layout was
|
||||||
|
// removed wholesale. This pins the fix: such a key gets its own
|
||||||
|
// self-keyed 'layout-leaf' column.
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const V3 = path.resolve(__dirname, '../../../web_interface/static/v3');
|
||||||
|
|
||||||
|
const src = fs.readFileSync(path.join(V3, 'js/widgets/style-editor.js'), 'utf8');
|
||||||
|
const start = src.indexOf('function own(obj, key)');
|
||||||
|
const endMark = 'function control(opts)';
|
||||||
|
const end = src.indexOf(endMark);
|
||||||
|
if (start === -1 || end === -1) {
|
||||||
|
console.error('FAIL: could not locate own()/columnsFor() in style-editor.js -- slice markers need updating');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
eval(src.slice(start, end)); // defines own/ownObj/elementKeys/columnsFor
|
||||||
|
|
||||||
|
let failures = 0;
|
||||||
|
function ok(desc, cond) {
|
||||||
|
if (cond) { console.log(`ok - ${desc}`); }
|
||||||
|
else { console.log(`FAIL - ${desc}`); failures++; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// score_text is a real style element; possession is layout-only but shaped
|
||||||
|
// like an object (x/y); show_logo is layout-only and a bare leaf value.
|
||||||
|
const schema = {
|
||||||
|
properties: {
|
||||||
|
score_text: {
|
||||||
|
type: 'object',
|
||||||
|
'x-style-managed': true,
|
||||||
|
properties: { font: { type: 'string' }, text_color: { type: 'array' } },
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
score_text: { type: 'object', properties: { x_offset: {}, y_offset: {} } },
|
||||||
|
possession: { type: 'object', properties: { x_offset: {}, y_offset: {} } },
|
||||||
|
show_logo: { type: 'boolean', default: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = columnsFor(schema);
|
||||||
|
const byKey = {};
|
||||||
|
columns.forEach(function (c) { byKey[c.key] = c; });
|
||||||
|
|
||||||
|
ok('an element style field still gets its own column', byKey.font && byKey.font.where === 'element');
|
||||||
|
ok('a layout-only object key still contributes its x/y columns',
|
||||||
|
byKey.x_offset && byKey.x_offset.where === 'layout');
|
||||||
|
ok('a layout-only leaf key gets a column keyed to itself',
|
||||||
|
!!byKey.show_logo);
|
||||||
|
ok('...marked as a layout-leaf column, not a shared sub-field',
|
||||||
|
byKey.show_logo && byKey.show_logo.where === 'layout-leaf');
|
||||||
|
ok('nothing is duplicated', columns.length === new Set(columns.map(function (c) { return c.key; })).size);
|
||||||
|
|
||||||
|
// A schema with no leaf-valued layout key must not gain a phantom column --
|
||||||
|
// this is the common case (every existing scoreboard) and must be unaffected.
|
||||||
|
const noLeaf = {
|
||||||
|
properties: {
|
||||||
|
score_text: {
|
||||||
|
type: 'object', 'x-style-managed': true,
|
||||||
|
properties: { font: { type: 'string' } },
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: 'object',
|
||||||
|
properties: { score_text: { type: 'object', properties: { x_offset: {} } } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ok('a schema with only object-shaped layout keys gets no layout-leaf column',
|
||||||
|
columnsFor(noLeaf).every(function (c) { return c.where !== 'layout-leaf'; }));
|
||||||
|
|
||||||
|
if (failures) { console.log(`\n${failures} failure(s)`); process.exit(1); }
|
||||||
|
console.log('\nall checks passed');
|
||||||
@@ -159,7 +159,19 @@
|
|||||||
});
|
});
|
||||||
// Nothing marked means the schema never went through expansion, so
|
// Nothing marked means the schema never went through expansion, so
|
||||||
// fall back to the shape test rather than rendering an empty table.
|
// fall back to the shape test rather than rendering an empty table.
|
||||||
return managed.length ? managed : shaped;
|
var keys = managed.length ? managed : shaped;
|
||||||
|
|
||||||
|
// A hand-written layout block can offset something that never got
|
||||||
|
// a style block of its own -- a logo, a timeout indicator, a
|
||||||
|
// possession arrow with a position but no font, colour or size to
|
||||||
|
// set. render() claims the whole `layout` child as ours regardless
|
||||||
|
// (posting every covered offset twice from two controls is worse),
|
||||||
|
// so a key that only lives under layout still needs a row here, or
|
||||||
|
// it loses its only control the moment that fallback section is
|
||||||
|
// dropped.
|
||||||
|
var layoutKeys = Object.keys(ownObj(props, 'layout').properties || {});
|
||||||
|
var extra = layoutKeys.filter(function (k) { return keys.indexOf(k) === -1; });
|
||||||
|
return keys.concat(extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
function titleOf(schema, key) {
|
function titleOf(schema, key) {
|
||||||
@@ -370,9 +382,12 @@
|
|||||||
/**
|
/**
|
||||||
* The columns a table needs, derived from the schema rather than fixed.
|
* The columns a table needs, derived from the schema rather than fixed.
|
||||||
*
|
*
|
||||||
* Two sources: the sub-fields elements declare (font, font_size,
|
* Three sources: the sub-fields elements declare (font, font_size,
|
||||||
* text_color, visible, align) and the sub-fields their layout blocks
|
* text_color, visible, align), the sub-fields their layout blocks
|
||||||
* declare (x_offset, y_offset, scale).
|
* declare (x_offset, y_offset, scale), and a layout-only key whose own
|
||||||
|
* value *is* the field to set -- a plain "show_logo" toggle has no x/y
|
||||||
|
* object underneath it, so it gets a column keyed to itself rather than
|
||||||
|
* to a shared sub-field name.
|
||||||
*/
|
*/
|
||||||
function columnsFor(schema) {
|
function columnsFor(schema) {
|
||||||
var props = schema.properties || {};
|
var props = schema.properties || {};
|
||||||
@@ -384,8 +399,23 @@
|
|||||||
elementKeys(schema).forEach(function (key) {
|
elementKeys(schema).forEach(function (key) {
|
||||||
Object.keys(ownObj(props, key).properties || {}).forEach(
|
Object.keys(ownObj(props, key).properties || {}).forEach(
|
||||||
function (f) { seen.set(f, 'element'); });
|
function (f) { seen.set(f, 'element'); });
|
||||||
Object.keys(ownObj(layoutProps, key).properties || {}).forEach(
|
var layoutEntry = own(layoutProps, key);
|
||||||
function (f) { seen.set(f, 'layout'); });
|
if (layoutEntry && typeof layoutEntry === 'object' && layoutEntry.properties) {
|
||||||
|
Object.keys(layoutEntry.properties).forEach(
|
||||||
|
function (f) { seen.set(f, 'layout'); });
|
||||||
|
} else if (layoutEntry) {
|
||||||
|
// layout-only and a leaf: nothing else will share this
|
||||||
|
// column, but leaving it out drops the field's only control
|
||||||
|
// the moment the wholesale `layout` claim removes its
|
||||||
|
// fallback (#569 review). Keyed under a namespaced id, not
|
||||||
|
// the bare field name -- an unrelated element's own style
|
||||||
|
// sub-field or another layout axis can share this exact
|
||||||
|
// name, and reusing that shared slot would give this row a
|
||||||
|
// column typed 'element'/'layout' that it doesn't declare,
|
||||||
|
// dropping the control right back out (#569 follow-up).
|
||||||
|
var leafId = 'layout-leaf:' + key;
|
||||||
|
if (!seen.has(leafId)) { seen.set(leafId, 'layout-leaf'); }
|
||||||
|
}
|
||||||
});
|
});
|
||||||
var known = COLUMN_ORDER.filter(function (f) { return seen.get(f); });
|
var known = COLUMN_ORDER.filter(function (f) { return seen.get(f); });
|
||||||
// Anything the schema declares that this file has never heard of
|
// Anything the schema declares that this file has never heard of
|
||||||
@@ -394,10 +424,12 @@
|
|||||||
return COLUMN_ORDER.indexOf(f) === -1;
|
return COLUMN_ORDER.indexOf(f) === -1;
|
||||||
}).sort();
|
}).sort();
|
||||||
return known.concat(extra).map(function (f) {
|
return known.concat(extra).map(function (f) {
|
||||||
|
var isLeafId = f.indexOf('layout-leaf:') === 0;
|
||||||
|
var fieldKey = isLeafId ? f.slice('layout-leaf:'.length) : f;
|
||||||
return {
|
return {
|
||||||
key: f,
|
key: fieldKey,
|
||||||
where: seen.get(f),
|
where: seen.get(f),
|
||||||
label: own(COLUMN_LABELS, f) || f.replace(/_/g, ' ')
|
label: own(COLUMN_LABELS, fieldKey) || fieldKey.replace(/_/g, ' ')
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -471,8 +503,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
opts.columns.forEach(function (col) {
|
opts.columns.forEach(function (col) {
|
||||||
|
var isLeaf = col.where === 'layout-leaf';
|
||||||
var inLayout = col.where === 'layout';
|
var inLayout = col.where === 'layout';
|
||||||
var prop = inLayout ? own(axes, col.key) : own(props, col.key);
|
// A leaf column only applies to the one row named after it --
|
||||||
|
// every other row leaves it blank, same as an element that
|
||||||
|
// doesn't declare a shared sub-field.
|
||||||
|
var prop = isLeaf ? (col.key === key ? own(layoutProps, key) : null)
|
||||||
|
: inLayout ? own(axes, col.key) : own(props, col.key);
|
||||||
var cell;
|
var cell;
|
||||||
if (!prop) {
|
if (!prop) {
|
||||||
// This element does not declare that field; keep the grid
|
// This element does not declare that field; keep the grid
|
||||||
@@ -480,13 +517,14 @@
|
|||||||
row.appendChild(el('span'));
|
row.appendChild(el('span'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var path = inLayout ? ['layout', key, col.key] : [key, col.key];
|
var path = isLeaf ? ['layout', key]
|
||||||
var base = inLayout ? opts.layoutPrefix + '.' + key
|
: inLayout ? ['layout', key, col.key] : [key, col.key];
|
||||||
|
var base = (isLeaf || inLayout) ? opts.layoutPrefix + '.' + key
|
||||||
: opts.prefix + '.' + key;
|
: opts.prefix + '.' + key;
|
||||||
var node = control({
|
var node = control({
|
||||||
key: col.key,
|
key: col.key,
|
||||||
prop: prop,
|
prop: prop,
|
||||||
name: base + '.' + col.key,
|
name: isLeaf ? base : base + '.' + col.key,
|
||||||
current: effective(value, path, prop, optional),
|
current: effective(value, path, prop, optional),
|
||||||
optional: optional,
|
optional: optional,
|
||||||
fonts: opts.fonts,
|
fonts: opts.fonts,
|
||||||
|
|||||||
Reference in New Issue
Block a user