@appsemble/lang-sdk 0.36.2-test.0 → 0.36.2-test.2
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/package.json +1 -1
- package/schemas/BlockDefinition.js +4 -0
- package/schemas/DeviceGridLayoutDefinition.d.ts +2 -0
- package/schemas/DeviceGridLayoutDefinition.js +18 -0
- package/schemas/GridLayoutDefinition.d.ts +2 -0
- package/schemas/GridLayoutDefinition.js +22 -0
- package/schemas/GridSpacingDefinition.d.ts +2 -0
- package/schemas/GridSpacingDefinition.js +26 -0
- package/schemas/PageDefinition.js +3 -0
- package/schemas/PageLayoutDefinition.d.ts +2 -0
- package/schemas/PageLayoutDefinition.js +18 -0
- package/schemas/SubPage.js +3 -0
- package/schemas/index.d.ts +4 -0
- package/schemas/index.js +4 -0
- package/types/block.d.ts +4 -0
- package/types/core.d.ts +3 -0
- package/types/gridLayout.d.ts +19 -0
- package/types/gridLayout.js +2 -0
- package/types/index.d.ts +1 -0
- package/types/index.js +1 -0
- package/validation.js +144 -0
package/package.json
CHANGED
|
@@ -89,6 +89,10 @@ The exact meaning of the parameters depends on the block type.
|
|
|
89
89
|
},
|
|
90
90
|
},
|
|
91
91
|
events: { $ref: '#/components/schemas/EventsDefinition' },
|
|
92
|
+
gridArea: {
|
|
93
|
+
type: 'string',
|
|
94
|
+
description: 'Grid area defined in the page layout definition',
|
|
95
|
+
},
|
|
92
96
|
},
|
|
93
97
|
};
|
|
94
98
|
//# sourceMappingURL=BlockDefinition.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const DeviceGridLayoutDefinition = {
|
|
2
|
+
type: 'object',
|
|
3
|
+
description: 'Grid layout definition for each device breakpoint',
|
|
4
|
+
additionalProperties: false,
|
|
5
|
+
properties: {
|
|
6
|
+
spacing: {
|
|
7
|
+
$ref: '#/components/schemas/GridSpacingDefinition',
|
|
8
|
+
description: 'Spacing configuration for the grid',
|
|
9
|
+
default: { unit: '1rem', gap: 1, padding: 1 },
|
|
10
|
+
},
|
|
11
|
+
layout: {
|
|
12
|
+
$ref: '#/components/schemas/GridLayoutDefinition',
|
|
13
|
+
description: 'Layout configuration for the grid',
|
|
14
|
+
default: { columns: 1, template: ['main'] },
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=DeviceGridLayoutDefinition.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const GridLayoutDefinition = {
|
|
2
|
+
type: 'object',
|
|
3
|
+
description: 'Grid layout configuration for columns and template areas',
|
|
4
|
+
additionalProperties: false,
|
|
5
|
+
properties: {
|
|
6
|
+
columns: {
|
|
7
|
+
type: 'number',
|
|
8
|
+
description: 'Number of columns in the grid',
|
|
9
|
+
default: 1,
|
|
10
|
+
minimum: 1,
|
|
11
|
+
},
|
|
12
|
+
template: {
|
|
13
|
+
type: 'array',
|
|
14
|
+
description: 'Grid template areas - each string represents a row',
|
|
15
|
+
items: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
},
|
|
18
|
+
default: ['main'],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=GridLayoutDefinition.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const GridSpacingDefinition = {
|
|
2
|
+
type: 'object',
|
|
3
|
+
description: 'Define the spacing for grid layout of the page',
|
|
4
|
+
additionalProperties: false,
|
|
5
|
+
properties: {
|
|
6
|
+
unit: {
|
|
7
|
+
type: 'string',
|
|
8
|
+
description: 'CSS unit for spacing calculations (e.g., "1rem", "8px")',
|
|
9
|
+
default: '1rem',
|
|
10
|
+
pattern: '^-?(\\d+\\.?\\d*|\\.\\d+)(rem|em|px|%|vh|vw|vmin|vmax|ch|ex|cm|mm|in|pt|pc)$',
|
|
11
|
+
},
|
|
12
|
+
gap: {
|
|
13
|
+
type: 'number',
|
|
14
|
+
description: 'Gap multiplier between grid items',
|
|
15
|
+
default: 1,
|
|
16
|
+
minimum: 0,
|
|
17
|
+
},
|
|
18
|
+
padding: {
|
|
19
|
+
type: 'number',
|
|
20
|
+
description: 'Padding multiplier around the grid',
|
|
21
|
+
default: 1,
|
|
22
|
+
minimum: 0,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=GridSpacingDefinition.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const PageLayoutDefinition = {
|
|
2
|
+
type: 'object',
|
|
3
|
+
description: 'Grid layout of the page, define as an anchor to re-use across multiple pages',
|
|
4
|
+
additionalProperties: false,
|
|
5
|
+
minProperties: 1,
|
|
6
|
+
properties: {
|
|
7
|
+
mobile: {
|
|
8
|
+
$ref: '#/components/schemas/DeviceGridLayoutDefinition',
|
|
9
|
+
},
|
|
10
|
+
tablet: {
|
|
11
|
+
$ref: '#/components/schemas/DeviceGridLayoutDefinition',
|
|
12
|
+
},
|
|
13
|
+
desktop: {
|
|
14
|
+
$ref: '#/components/schemas/DeviceGridLayoutDefinition',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=PageLayoutDefinition.js.map
|
package/schemas/SubPage.js
CHANGED
|
@@ -8,6 +8,9 @@ export const SubPage = {
|
|
|
8
8
|
$ref: '#/components/schemas/RemapperDefinition',
|
|
9
9
|
description: 'The name of the sub page.',
|
|
10
10
|
},
|
|
11
|
+
layout: {
|
|
12
|
+
$ref: '#/components/schemas/PageLayoutDefinition',
|
|
13
|
+
},
|
|
11
14
|
roles: {
|
|
12
15
|
type: 'array',
|
|
13
16
|
description: `The list of roles that are allowed to view this page.
|
package/schemas/index.d.ts
CHANGED
|
@@ -120,3 +120,7 @@ export * from './SecurityRoleDefinition.js';
|
|
|
120
120
|
export * from './TabsPageDefinition.js';
|
|
121
121
|
export * from './Theme.js';
|
|
122
122
|
export * from './WebhookDefinition.js';
|
|
123
|
+
export * from './DeviceGridLayoutDefinition.js';
|
|
124
|
+
export * from './GridLayoutDefinition.js';
|
|
125
|
+
export * from './GridSpacingDefinition.js';
|
|
126
|
+
export * from './PageLayoutDefinition.js';
|
package/schemas/index.js
CHANGED
|
@@ -120,4 +120,8 @@ export * from './SecurityRoleDefinition.js';
|
|
|
120
120
|
export * from './TabsPageDefinition.js';
|
|
121
121
|
export * from './Theme.js';
|
|
122
122
|
export * from './WebhookDefinition.js';
|
|
123
|
+
export * from './DeviceGridLayoutDefinition.js';
|
|
124
|
+
export * from './GridLayoutDefinition.js';
|
|
125
|
+
export * from './GridSpacingDefinition.js';
|
|
126
|
+
export * from './PageLayoutDefinition.js';
|
|
123
127
|
//# sourceMappingURL=index.js.map
|
package/types/block.d.ts
CHANGED
package/types/core.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { type ResourceDefinition } from './resource.js';
|
|
|
9
9
|
import { type ViewRole } from './roles.js';
|
|
10
10
|
import { type Security } from './security.js';
|
|
11
11
|
import { type Theme } from './theme.js';
|
|
12
|
+
import { type PageLayoutDefinition } from './gridLayout.js';
|
|
12
13
|
export type SettingName = 'email' | 'languages' | 'name' | 'password' | 'phoneNumber' | 'picture';
|
|
13
14
|
export interface AppDefinition {
|
|
14
15
|
/**
|
|
@@ -225,10 +226,12 @@ export interface SubPageDefinition {
|
|
|
225
226
|
name: Remapper;
|
|
226
227
|
roles?: ViewRole[];
|
|
227
228
|
blocks: BlockDefinition[];
|
|
229
|
+
layout?: PageLayoutDefinition;
|
|
228
230
|
}
|
|
229
231
|
export interface BasicPageDefinition extends BasePageDefinition {
|
|
230
232
|
type?: 'page';
|
|
231
233
|
blocks: BlockDefinition[];
|
|
234
|
+
layout?: PageLayoutDefinition;
|
|
232
235
|
}
|
|
233
236
|
export interface ContainerPageDefinition extends BasePageDefinition {
|
|
234
237
|
type: 'container';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type RequireAtLeastOne } from 'type-fest';
|
|
2
|
+
export interface GridLayoutDefinition {
|
|
3
|
+
columns: number;
|
|
4
|
+
template: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface GridLayoutSpacingDefinition {
|
|
7
|
+
unit: string;
|
|
8
|
+
padding: number;
|
|
9
|
+
gap: number;
|
|
10
|
+
}
|
|
11
|
+
export interface DeviceGridLayoutDefinition {
|
|
12
|
+
layout: GridLayoutDefinition;
|
|
13
|
+
spacing: GridLayoutSpacingDefinition;
|
|
14
|
+
}
|
|
15
|
+
export type PageLayoutDefinition = RequireAtLeastOne<{
|
|
16
|
+
mobile: DeviceGridLayoutDefinition;
|
|
17
|
+
tablet: DeviceGridLayoutDefinition;
|
|
18
|
+
desktop: DeviceGridLayoutDefinition;
|
|
19
|
+
}>;
|
package/types/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from './roles.js';
|
|
|
15
15
|
export * from './security.js';
|
|
16
16
|
export * from './theme.js';
|
|
17
17
|
export * from './member.js';
|
|
18
|
+
export * from './gridLayout.js';
|
|
18
19
|
export interface ActionType {
|
|
19
20
|
/**
|
|
20
21
|
* Whether or not app creators are required to define this action.
|
package/types/index.js
CHANGED
package/validation.js
CHANGED
|
@@ -299,6 +299,149 @@ function validateController(definition, controllerImplementations, report) {
|
|
|
299
299
|
},
|
|
300
300
|
});
|
|
301
301
|
}
|
|
302
|
+
function getTemplateAreas(layoutDefinition) {
|
|
303
|
+
const areas = new Set();
|
|
304
|
+
if (!layoutDefinition) {
|
|
305
|
+
return areas;
|
|
306
|
+
}
|
|
307
|
+
for (const deviceDefinition of Object.values(layoutDefinition)) {
|
|
308
|
+
if (deviceDefinition?.layout?.template) {
|
|
309
|
+
for (const row of deviceDefinition.layout.template) {
|
|
310
|
+
for (const area of row.split(' ')) {
|
|
311
|
+
if (area && area !== '.') {
|
|
312
|
+
areas.add(area);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return areas;
|
|
319
|
+
}
|
|
320
|
+
function validateTemplateAreasAreRectangular(template, report, path) {
|
|
321
|
+
const grid = template.map((row) => row.split(' '));
|
|
322
|
+
function isAreaRectangular(areaName) {
|
|
323
|
+
const cells = [];
|
|
324
|
+
for (const [row, rowCells] of grid.entries()) {
|
|
325
|
+
for (const [col, cell] of rowCells.entries()) {
|
|
326
|
+
if (cell === areaName) {
|
|
327
|
+
cells.push({ row, col });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (cells.length === 0) {
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
let minRow = Infinity;
|
|
335
|
+
let maxRow = -Infinity;
|
|
336
|
+
let minCol = Infinity;
|
|
337
|
+
let maxCol = -Infinity;
|
|
338
|
+
for (const cell of cells) {
|
|
339
|
+
minRow = Math.min(minRow, cell.row);
|
|
340
|
+
maxRow = Math.max(maxRow, cell.row);
|
|
341
|
+
minCol = Math.min(minCol, cell.col);
|
|
342
|
+
maxCol = Math.max(maxCol, cell.col);
|
|
343
|
+
}
|
|
344
|
+
const expectedCount = (maxRow - minRow + 1) * (maxCol - minCol + 1);
|
|
345
|
+
if (cells.length !== expectedCount) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
for (let row = minRow; row <= maxRow; row += 1) {
|
|
349
|
+
for (let col = minCol; col <= maxCol; col += 1) {
|
|
350
|
+
if (grid[row][col] !== areaName) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
const areas = new Set();
|
|
358
|
+
for (const row of grid) {
|
|
359
|
+
for (const cell of row) {
|
|
360
|
+
if (cell && cell !== '.') {
|
|
361
|
+
areas.add(cell);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
for (const area of areas) {
|
|
366
|
+
if (!isAreaRectangular(area)) {
|
|
367
|
+
report(template, `grid area '${area}' does not form a single contiguous rectangle`, path);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function validatePageLayoutDefinition(layoutDefinition, report, path) {
|
|
372
|
+
if (!layoutDefinition) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
for (const [deviceName, deviceDefinition] of Object.entries(layoutDefinition)) {
|
|
376
|
+
if (!deviceDefinition?.layout?.template || !deviceDefinition?.layout?.columns) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (deviceDefinition.layout.template.some((row) => row.split(' ').length !== deviceDefinition.layout.columns)) {
|
|
380
|
+
report(deviceDefinition.layout.template, 'template needs to be the same length as number of columns', [...path, deviceName, 'layout', 'template']);
|
|
381
|
+
}
|
|
382
|
+
validateTemplateAreasAreRectangular(deviceDefinition.layout.template, report, [
|
|
383
|
+
...path,
|
|
384
|
+
deviceName,
|
|
385
|
+
'layout',
|
|
386
|
+
'template',
|
|
387
|
+
]);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function validateBlockGridAreas(blocks, layoutDefinition, report, path) {
|
|
391
|
+
const templateAreas = getTemplateAreas(layoutDefinition);
|
|
392
|
+
for (const [idx, block] of blocks.entries()) {
|
|
393
|
+
if (block.gridArea && !templateAreas.has(block.gridArea)) {
|
|
394
|
+
report(block.gridArea, `does not match any area defined in the layout template. Available areas: ${[...templateAreas].join(', ') || 'none'}`, [...path, idx, 'gridArea']);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function validateSubPageLayout(layout, blocks, report, path) {
|
|
399
|
+
validatePageLayoutDefinition(layout, report, path);
|
|
400
|
+
if (layout && blocks) {
|
|
401
|
+
validateBlockGridAreas(blocks, layout, report, [...path, 'blocks']);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function validateGridLayout(definition, report) {
|
|
405
|
+
iterApp(definition, {
|
|
406
|
+
onPage(page, path) {
|
|
407
|
+
// Basic page
|
|
408
|
+
if (page.type === 'page' || page.type === undefined) {
|
|
409
|
+
const basicPage = page;
|
|
410
|
+
validateSubPageLayout(basicPage.layout, basicPage.blocks, report, path);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
// Tabs page
|
|
414
|
+
if (page.type === 'tabs') {
|
|
415
|
+
const tabsPage = page;
|
|
416
|
+
if (tabsPage.tabs) {
|
|
417
|
+
for (const [tabIdx, tab] of tabsPage.tabs.entries()) {
|
|
418
|
+
validateSubPageLayout(tab.layout, tab.blocks, report, [...path, 'tabs', tabIdx]);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
else if (tabsPage.definition?.foreach) {
|
|
422
|
+
validateSubPageLayout(tabsPage.definition.foreach.layout, tabsPage.definition.foreach.blocks, report, [...path, 'definition', 'foreach']);
|
|
423
|
+
}
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
// Flow page
|
|
427
|
+
if (page.type === 'flow') {
|
|
428
|
+
const flowPage = page;
|
|
429
|
+
for (const [stepIdx, step] of flowPage.steps.entries()) {
|
|
430
|
+
validateSubPageLayout(step.layout, step.blocks, report, [...path, 'steps', stepIdx]);
|
|
431
|
+
}
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
// Loop page
|
|
435
|
+
if (page.type === 'loop') {
|
|
436
|
+
const loopPage = page;
|
|
437
|
+
validateSubPageLayout(loopPage.foreach.layout, loopPage.foreach.blocks, report, [
|
|
438
|
+
...path,
|
|
439
|
+
'foreach',
|
|
440
|
+
]);
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
}
|
|
302
445
|
function validateBlocks(definition, blockVersions, report) {
|
|
303
446
|
iterApp(definition, {
|
|
304
447
|
onBlock(block, path) {
|
|
@@ -1241,6 +1384,7 @@ export async function validateAppDefinition(definition, getBlockVersions, contro
|
|
|
1241
1384
|
validateActions(definition, report);
|
|
1242
1385
|
validateEvents(definition, blockVersionMap, report);
|
|
1243
1386
|
validateUniquePageNames(definition, report);
|
|
1387
|
+
validateGridLayout(definition, report);
|
|
1244
1388
|
}
|
|
1245
1389
|
catch (error) {
|
|
1246
1390
|
report(null, `Unexpected error: ${error instanceof Error ? error.message : error}`, []);
|