PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / AutoLaunch / templates / schema.js

schema.js in Extendify 3.2.1, at src/AutoLaunch/templates/schema.js

82 lines 2.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * JSON Schema describes the value; the x- keywords describe what it has no
3 * opinion about — a key's widget, its grouping and the panel's own labels.
4 */
5 const CONTROL_KEYS = {
6 'x-card': 'card',
7 'x-label': 'label',
8 'x-control': 'type',
9 'x-feeds': 'feeds',
10 'x-options': 'options',
11 'x-unlocks': 'unlocks',
12 'x-lockedBy': 'lockedBy',
13 'x-inherits': 'inherits',
14 'x-placeholder': 'placeholder',
15 'x-note': 'note',
16 'x-unit': 'unit',
17 };
18
19 // A length is `12px` at rest and 12 in the input, so the bounds describe the number.
20 const CSS_UNIT = { length: 'px', em: 'em' };
21
22 const withUnit = (number, unit) =>
23 unit && number !== undefined ? `${number}${unit}` : number;
24
25 const withoutUnit = (value, unit) =>
26 unit && typeof value === 'string' && value.endsWith(unit)
27 ? value.slice(0, -unit.length)
28 : value;
29
30 // A value failing its schema becomes the bound or the default, never the page.
31 export const coerce = (shape, value) => {
32 if (value === undefined || value === null || value === '') return value;
33 if (shape.enum && !shape.enum.includes(value)) return shape.default;
34 if (shape.type === 'number') {
35 const unit = CSS_UNIT[shape['x-control']];
36 const number = Number(withoutUnit(value, unit));
37 if (Number.isNaN(number)) return withUnit(shape.default, unit);
38 if (shape.minimum !== undefined && number < shape.minimum) {
39 return withUnit(shape.minimum, unit);
40 }
41 if (shape.maximum !== undefined && number > shape.maximum) {
42 return withUnit(shape.maximum, unit);
43 }
44 return withUnit(number, unit);
45 }
46 if (shape.type === 'string' && typeof value !== 'string')
47 return shape.default;
48 return value;
49 };
50
51 export const controlsFrom = (manifest) =>
52 Object.entries(manifest.properties ?? {}).map(([key, shape]) => {
53 const control = { key };
54 if (key.startsWith('--')) control.cssVar = key;
55 for (const [source, target] of Object.entries(CONTROL_KEYS)) {
56 if (shape[source] !== undefined) control[target] = shape[source];
57 }
58 if (shape.default !== undefined) control.defaultValue = shape.default;
59 if (shape.enum !== undefined) control.enum = shape.enum;
60 if (shape.minimum !== undefined) control.min = shape.minimum;
61 if (shape.maximum !== undefined) control.max = shape.maximum;
62 if (shape.multipleOf !== undefined) control.step = shape.multipleOf;
63 control.coerce = (value) => coerce(shape, value);
64 return control;
65 });
66
67 export const idOf = (manifest) => manifest.$id.split('/').pop();
68
69 /**
70 * Global, then the page, then the template. A level restates only what it
71 * changes — a different default still inherits the type and the bounds.
72 */
73 export const mergeManifests = (manifests) => {
74 const properties = {};
75 for (const manifest of manifests) {
76 for (const [key, shape] of Object.entries(manifest.properties ?? {})) {
77 properties[key] = { ...properties[key], ...shape };
78 }
79 }
80 return { properties };
81 };
82