@table-bro/mcp 0.0.0-stage → 1.2.0
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/LICENSE +21 -0
- package/README.md +97 -2
- package/dist/docs/docs.d.ts +21 -0
- package/dist/docs/docs.js +103 -0
- package/dist/docs/pages.d.ts +28 -0
- package/dist/docs/pages.js +2734 -0
- package/dist/docs/topics.d.ts +11 -0
- package/dist/docs/topics.js +410 -0
- package/dist/extension.d.ts +23 -0
- package/dist/extension.js +1 -0
- package/dist/generate/angular.d.ts +26 -0
- package/dist/generate/angular.js +241 -0
- package/dist/generate/cells.d.ts +22 -0
- package/dist/generate/cells.js +157 -0
- package/dist/generate/code.d.ts +48 -0
- package/dist/generate/code.js +153 -0
- package/dist/generate/config.d.ts +16 -0
- package/dist/generate/config.js +95 -0
- package/dist/generate/fillings.d.ts +49 -0
- package/dist/generate/fillings.js +130 -0
- package/dist/generate/filters.d.ts +36 -0
- package/dist/generate/filters.js +254 -0
- package/dist/generate/generate-code.d.ts +34 -0
- package/dist/generate/generate-code.js +59 -0
- package/dist/generate/names.d.ts +52 -0
- package/dist/generate/names.js +93 -0
- package/dist/generate/rows.d.ts +5 -0
- package/dist/generate/rows.js +22 -0
- package/dist/generate/scaffold.d.ts +35 -0
- package/dist/generate/scaffold.js +62 -0
- package/dist/generate/service.d.ts +16 -0
- package/dist/generate/service.js +320 -0
- package/dist/generate/setup.d.ts +34 -0
- package/dist/generate/setup.js +158 -0
- package/dist/generate/vanilla.d.ts +10 -0
- package/dist/generate/vanilla.js +160 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +10 -0
- package/dist/project/inspect-project.d.ts +53 -0
- package/dist/project/inspect-project.js +223 -0
- package/dist/server/main.d.ts +2 -0
- package/dist/server/main.js +8 -0
- package/dist/server/server.d.ts +17 -0
- package/dist/server/server.js +212 -0
- package/dist/spec/builtin-specs.d.ts +13 -0
- package/dist/spec/builtin-specs.js +167 -0
- package/dist/spec/css-variables.d.ts +22 -0
- package/dist/spec/css-variables.js +54 -0
- package/dist/spec/schema.d.ts +19 -0
- package/dist/spec/schema.js +278 -0
- package/dist/spec/skin-config.d.ts +30 -0
- package/dist/spec/skin-config.js +67 -0
- package/dist/spec/spec.d.ts +157 -0
- package/dist/spec/spec.js +2 -0
- package/dist/spec/validate-spec.d.ts +54 -0
- package/dist/spec/validate-spec.js +979 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +71 -4
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { cssVariables } from '../spec/css-variables.js';
|
|
2
|
+
import { byColumns, cellCode } from './cells.js';
|
|
3
|
+
import { imports, importsField, indentBlock, literal, quote, raw } from './code.js';
|
|
4
|
+
import { configCode } from './config.js';
|
|
5
|
+
import { BroFiltersCode } from './filters.js';
|
|
6
|
+
import { BroNames } from './names.js';
|
|
7
|
+
import { cssVarsRule, rowTypeCode } from './rows.js';
|
|
8
|
+
import { serviceCode } from './service.js';
|
|
9
|
+
/**
|
|
10
|
+
* The table for an Angular project: the type of the row, the page component with <table-bro-ng> and its cols, rows and
|
|
11
|
+
* config, the cells, the filters. The files go in the order a reader needs them.
|
|
12
|
+
*/
|
|
13
|
+
export function angularTable(spec, skin, fillings, tagSuffix = '', extensions = [], simulateServer = false) {
|
|
14
|
+
const names = BroNames.of(spec, tagSuffix);
|
|
15
|
+
const folder = names.folder;
|
|
16
|
+
const config = spec.config ?? {};
|
|
17
|
+
const server = Boolean(config.server);
|
|
18
|
+
const filters = new BroFiltersCode(spec, fillings);
|
|
19
|
+
const hasFilters = filters.filters.length > 0;
|
|
20
|
+
const specRows = Boolean(spec.rows?.length) && !server;
|
|
21
|
+
// The page and the server row details load from the simulated server, or from the API of the spec
|
|
22
|
+
const simulated = server && (simulateServer || Boolean(spec.api));
|
|
23
|
+
const source = !server ? undefined : spec.api ? (spec.api.paging ? 'api-pages' : 'api') : simulated ? 'simulated' : 'project';
|
|
24
|
+
const cells = (spec.cells ?? []).map(cell => cellCode(spec, cell, 'angular', fillings.cells?.[cell.name]?.[skin], tagSuffix, simulated));
|
|
25
|
+
const files = [];
|
|
26
|
+
const todo = [];
|
|
27
|
+
for (const cell of byColumns(spec, cells)) {
|
|
28
|
+
files.push({ path: `${folder}/${cell.file}.ts`, content: cell.content, description: cellDescription(cell) });
|
|
29
|
+
if (!cell.filled) {
|
|
30
|
+
todo.push(`${folder}/${cell.file}.ts: write the template of the ${cell.cell.kind === 'details' ? 'row details' : 'cell'}: ${cell.cell.shows}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const conf = configCode(spec, skin, fillings, ' ', tagSuffix, extensions);
|
|
34
|
+
const cssVars = Object.entries(spec.cssVars ?? {});
|
|
35
|
+
// The filters matter little to a reader: their component comes after the cells
|
|
36
|
+
if (hasFilters) {
|
|
37
|
+
files.push({ path: `${folder}/${names.filtersFile}.ts`, content: filters.angularComponent(skin), description: 'the filters of the drawer: their form, which gives the table its filter.' });
|
|
38
|
+
}
|
|
39
|
+
// The changes of the styles close the code: the CSS variables that the table overrides
|
|
40
|
+
if (cssVars.length) {
|
|
41
|
+
const tableVars = cssVars.filter(([name]) => cssVariables.find(item => item.name === name)?.target !== 'drawer');
|
|
42
|
+
// The container of the drawer reads its own variables
|
|
43
|
+
const drawerVars = hasFilters ? cssVars.filter(([name]) => cssVariables.find(item => item.name === name)?.target !== 'table') : [];
|
|
44
|
+
const rules = [
|
|
45
|
+
...(tableVars.length ? [cssVarsRule(names.tableId, tableVars)] : []),
|
|
46
|
+
...(drawerVars.length ? [cssVarsRule(names.drawerId, drawerVars)] : [])
|
|
47
|
+
];
|
|
48
|
+
files.push({
|
|
49
|
+
path: `${folder}/${names.kebab}-table.css`,
|
|
50
|
+
content: `/* The CSS variables of the table: global styles, as the table and its drawer read them on their own elements */\n${rules.join('\n\n')}\n`,
|
|
51
|
+
description: 'the changes of the styles: the CSS variables of the table that override the ones of its skin.'
|
|
52
|
+
});
|
|
53
|
+
todo.push(`Add ${folder}/${names.kebab}-table.css to "styles" of angular.json, or import it into the global styles of the application`);
|
|
54
|
+
}
|
|
55
|
+
if (!specRows && !server) {
|
|
56
|
+
todo.push(`${folder}/${names.pageFile}.ts: load the rows and set them with this.rows.set(rows)`);
|
|
57
|
+
}
|
|
58
|
+
if (source === 'simulated') {
|
|
59
|
+
todo.push(`${folder}/${names.serviceFile}.ts: replace the simulated server of loadPage with a request to your API, and map its parameters and its answer`);
|
|
60
|
+
}
|
|
61
|
+
else if (source === 'project') {
|
|
62
|
+
todo.push(`${folder}/${names.serviceFile}.ts: the address of your API in loadPage, and the map of the query of the table to its parameters and of its answer to the rows with their total`);
|
|
63
|
+
}
|
|
64
|
+
// The model, the page with the cols, the rows and the config, and the service of a server table come first
|
|
65
|
+
files.unshift({ path: `${folder}/${names.kebab}.ts`, content: rowTypeCode(spec), description: modelDescription(hasFilters) }, {
|
|
66
|
+
path: `${folder}/${names.pageFile}.ts`,
|
|
67
|
+
content: pageCode(spec, skin, cells, filters, conf, specRows, tagSuffix, source),
|
|
68
|
+
description: `the page of the table. Here are its columns, \`cols\`, its rows, \`rows\`, and its config, \`conf\`${server ? ', with the loading of its pages' : ''}.`
|
|
69
|
+
}, ...(source
|
|
70
|
+
? [{
|
|
71
|
+
path: `${folder}/${names.serviceFile}.ts`,
|
|
72
|
+
content: serviceCode(spec, source, hasFilters ? filters : undefined),
|
|
73
|
+
description: 'the service of the table: it sends the request of a query of the table to the API, and turns its answer into the rows of a page.'
|
|
74
|
+
}]
|
|
75
|
+
: []));
|
|
76
|
+
return { files, todo };
|
|
77
|
+
}
|
|
78
|
+
function pageCode(spec, skin, cells, filters, conf, specRows, tagSuffix, source) {
|
|
79
|
+
const names = BroNames.of(spec, tagSuffix);
|
|
80
|
+
const config = spec.config ?? {};
|
|
81
|
+
const server = Boolean(config.server);
|
|
82
|
+
const hasFilters = filters.filters.length > 0;
|
|
83
|
+
const material = (skin === 'mat');
|
|
84
|
+
const angularCells = cells.filter(cell => cell.angular);
|
|
85
|
+
const actions = [
|
|
86
|
+
...(spec.cells ?? []).filter(cell => cell.kind !== 'details').flatMap(cell => cell.actions ?? []),
|
|
87
|
+
...(spec.toolbar?.buttons ?? []).map(button => button.action),
|
|
88
|
+
...(spec.toolbar?.menu?.items ?? []).map(item => item.action)
|
|
89
|
+
];
|
|
90
|
+
const detailsButton = spec.cols.some(col => col.component && 'element' in col.component && col.component.element === 'details-button');
|
|
91
|
+
// The service of a server table sends its requests and maps them: the page delivers the rows to the table
|
|
92
|
+
const row = spec.rowType;
|
|
93
|
+
const angularModules = {
|
|
94
|
+
'@angular/core': ['ChangeDetectionStrategy', 'Component', 'signal', ...((angularCells.length || server) ? ['inject'] : []), ...(angularCells.length ? ['Injector'] : []), ...((hasFilters && !material) ? ['CUSTOM_ELEMENTS_SCHEMA'] : [])],
|
|
95
|
+
'@angular/elements': angularCells.length ? ['createCustomElement'] : []
|
|
96
|
+
};
|
|
97
|
+
const tableBroModules = {
|
|
98
|
+
...conf.imports,
|
|
99
|
+
'@table-bro/core': [...conf.imports['@table-bro/core'], 'type BroCol', ...(actions.length ? ['type BroActionDetail'] : []), ...(server ? ['type BroEmptyState', 'type BroQuery'] : []), ...((hasFilters && !material) ? ['defineTableBroFilterDrawerContainer'] : []), ...(detailsButton ? ['events'] : [])],
|
|
100
|
+
'@table-bro/angular': [...(server ? ['type BroServerContainer', 'type BroServerPage'] : ['type BroContainer']), ...((hasFilters && material) ? [] : ['TableBroNg'])],
|
|
101
|
+
'@table-bro/angular-material': [...(conf.imports['@table-bro/angular-material'] ?? []), ...((hasFilters && material) ? ['TableBroFilterDrawerContainerNg'] : [])]
|
|
102
|
+
};
|
|
103
|
+
const localModules = {
|
|
104
|
+
[`./${names.kebab}`]: [`type ${spec.rowType}`, ...(hasFilters ? [`type ${names.filtersType}`] : [])],
|
|
105
|
+
...(source ? { [`./${names.serviceFile}`]: [names.serviceClass] } : {}),
|
|
106
|
+
...Object.fromEntries(cells.map(cell => [`./${cell.file}`, [cell.className]])),
|
|
107
|
+
...(hasFilters ? { [`./${names.filtersFile}`]: [names.filtersClass] } : {})
|
|
108
|
+
};
|
|
109
|
+
const componentImports = {
|
|
110
|
+
...((hasFilters && material) ? { '@table-bro/angular-material': ['TableBroFilterDrawerContainerNg'] } : { '@table-bro/angular': ['TableBroNg'] }),
|
|
111
|
+
...(hasFilters ? { [`./${names.filtersFile}`]: [names.filtersClass] } : {})
|
|
112
|
+
};
|
|
113
|
+
const shadows = (config.shadowsEnabled !== false);
|
|
114
|
+
const inputs = [
|
|
115
|
+
'[cols]="cols()" [rows]="rows()" [conf]="conf()"',
|
|
116
|
+
...(server ? ['[total]="total()" [rowsToAppend]="rowsToAppend()" [failed]="failed()" [emptyState]="emptyState()"'] : []),
|
|
117
|
+
...(hasFilters ? ['[filter]="filter()"'] : []),
|
|
118
|
+
...(server ? ['(query)="onQueryTable($event)"'] : []),
|
|
119
|
+
...(actions.length ? ['(action)="onTableAction($event)"'] : [])
|
|
120
|
+
];
|
|
121
|
+
const element = (tag, attributes) => {
|
|
122
|
+
const head = `<${tag} ${attributes}`;
|
|
123
|
+
const pad = ' '.repeat(tag.length + 2);
|
|
124
|
+
return `${head}\n${inputs.map(line => pad + line).join('\n')}>`;
|
|
125
|
+
};
|
|
126
|
+
const tableNg = element('table-bro-ng', `${(shadows && !(hasFilters && material)) ? 'class="bro-table-shadow-container" ' : ''}tableId="${names.tableId}"`);
|
|
127
|
+
let template;
|
|
128
|
+
if (hasFilters && material) {
|
|
129
|
+
template = `${element('table-bro-filter-drawer-container-ng', `id="${names.drawerId}" tableId="${names.tableId}"`)}\n <${names.filtersSelector} (changeFilter)="filter.set($event)"></${names.filtersSelector}>\n</table-bro-filter-drawer-container-ng>`;
|
|
130
|
+
}
|
|
131
|
+
else if (hasFilters) {
|
|
132
|
+
template = `<table-bro-filter-drawer-container id="${names.drawerId}" skin="${skin}">\n${indentBlock(`${tableNg}</table-bro-ng>`, ' ')}\n\n <${names.filtersSelector} slot="filters" (changeFilter)="filter.set($event)"></${names.filtersSelector}>\n</table-bro-filter-drawer-container>`;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
template = `${tableNg}</table-bro-ng>`;
|
|
136
|
+
}
|
|
137
|
+
const members = [
|
|
138
|
+
` cols = signal<BroCol<${spec.rowType}>[]>(${literal(colsValue(spec, skin, cells), ' ')});`,
|
|
139
|
+
'',
|
|
140
|
+
` rows = signal<${spec.rowType}[] | undefined>(${specRows ? literal(spec.rows, ' ') : 'undefined'});`,
|
|
141
|
+
'',
|
|
142
|
+
` conf = signal<BroConfig<${spec.rowType}>>(${conf.code.replace(/^ {4}/gm, ' ').replace(/\n {2}\}$/, '\n }')});`
|
|
143
|
+
];
|
|
144
|
+
if (server) {
|
|
145
|
+
members.push('', ' total = signal<number>(0);', ` rowsToAppend = signal<${spec.rowType}[] | undefined>(undefined);`, ' failed = signal<boolean>(false);', ' /** The state of an empty answer: no rows found for a query with a search or filters, else no data. */', ' emptyState = signal<BroEmptyState | undefined>(undefined);');
|
|
146
|
+
}
|
|
147
|
+
if (source) {
|
|
148
|
+
members.push('', ` private service = inject(${names.serviceClass});`, ' /** The filters and the sort of the rows the table has: another query starts from the first page. */', ' private loadedQuery: string = \'\';', ' /** The last query: an answer to an older one comes late and is left out. */', ` private lastQuery?: BroQuery<${row}>;`);
|
|
149
|
+
}
|
|
150
|
+
if (hasFilters) {
|
|
151
|
+
members.push('', ' /** The filters of the drawer: the table takes them through its input filter. */', ` filter = signal<${names.filtersType} | undefined>(undefined);`);
|
|
152
|
+
}
|
|
153
|
+
if (angularCells.length) {
|
|
154
|
+
members.push('', ' private injector = inject(Injector);');
|
|
155
|
+
}
|
|
156
|
+
const constructor = [];
|
|
157
|
+
if (cells.length) {
|
|
158
|
+
constructor.push(' this.defineCustomElements();');
|
|
159
|
+
}
|
|
160
|
+
if (hasFilters && !material) {
|
|
161
|
+
constructor.push(' defineTableBroFilterDrawerContainer();');
|
|
162
|
+
}
|
|
163
|
+
if (!specRows && !server) {
|
|
164
|
+
constructor.push('', ' // TODO: load the rows and set them with this.rows.set(rows)');
|
|
165
|
+
}
|
|
166
|
+
// The table asks for the next pages and for the first page of other filters; the page asks for its first page
|
|
167
|
+
if (server) {
|
|
168
|
+
constructor.push('', ` this.onQueryTable({page: 1, pageSize: ${config.pageSize ?? 5}});`);
|
|
169
|
+
}
|
|
170
|
+
// The page starts its table in initTable of BroContainer
|
|
171
|
+
members.push('', ' constructor() {', ' this.initTable();', ' }', '', ' initTable() {', ...(constructor.length ? constructor.filter((line, index) => line || index) : [' // The table starts with the rows of the spec: nothing more to start']), ' }');
|
|
172
|
+
if (source) {
|
|
173
|
+
members.push('', ` onQueryTable(query: BroQuery<${row}>) {`, ' const queryKey: string = JSON.stringify([query.filter, query.sort]);', '', ' this.lastQuery = query;', '', ` this.service.loadPage(query).then(({rows, total}: BroServerPage<${row}>) => {`, ' if (query !== this.lastQuery) {', ' return;', ' }', '', ' // One batch for one delivery: the rows go with the total of the same answer', ' this.failed.set(false);', ' this.total.set(total);', ' this.emptyState.set(Object.values(query.filter ?? {}).some(value => value !== undefined && value !== null && value !== \'\') ? \'not-found\' : undefined);', '', ' // Other filters or another sort give the first page anew; the next pages of the same query add to it', ' if (queryKey !== this.loadedQuery) {', ' this.loadedQuery = queryKey;', ' this.rows.set(rows);', ' } else {', ' this.rowsToAppend.set(rows);', ' }', ' }, () => {', ' // A failed load shows the failure state of the table', ' if (query === this.lastQuery) {', ' this.failed.set(true);', ' }', ' });', ' }');
|
|
174
|
+
}
|
|
175
|
+
if (actions.length) {
|
|
176
|
+
members.push('', ` onTableAction(event: BroActionDetail<${spec.rowType}>) {`, ' switch (event.action) {', ...[...new Set(actions)].flatMap(action => [` case ${quote(action)}:`, ` // TODO: ${rowAction(spec, action) ? 'the row is event.data?.row' : 'the action of the toolbar'}`, ' break;']), ' }', ' }');
|
|
177
|
+
}
|
|
178
|
+
if (cells.length) {
|
|
179
|
+
members.push('', ' /** The cells are Custom Elements: the table creates them by their tags, so they are defined before it. */', ' private defineCustomElements() {', ` if (customElements.get(${quote(cells[0].tag)})) {`, ' return;', ' }', '', ...cells.map(cell => cell.angular ? ` customElements.define(${quote(cell.tag)}, createCustomElement(${cell.className}, {injector: this.injector}));` : ` customElements.define(${quote(cell.tag)}, ${cell.className});`), ' }');
|
|
180
|
+
}
|
|
181
|
+
return [
|
|
182
|
+
imports({ ...angularModules, ...tableBroModules, ...localModules }),
|
|
183
|
+
'',
|
|
184
|
+
...conf.preamble.flatMap(declaration => [declaration, '']),
|
|
185
|
+
'@Component({',
|
|
186
|
+
` selector: '${names.pageSelector}',`,
|
|
187
|
+
` template: \`\n${indentBlock(template, ' ')}\`,`,
|
|
188
|
+
' changeDetection: ChangeDetectionStrategy.OnPush,',
|
|
189
|
+
...((hasFilters && !material) ? [' // <table-bro-filter-drawer-container> is an element of table-bro', ' schemas: [CUSTOM_ELEMENTS_SCHEMA],'] : []),
|
|
190
|
+
importsField(componentImports),
|
|
191
|
+
'})',
|
|
192
|
+
`export class ${names.pageClass} implements ${source ? `BroServerContainer<${row}>` : `BroContainer<${row}>`} {`,
|
|
193
|
+
...members,
|
|
194
|
+
'}',
|
|
195
|
+
''
|
|
196
|
+
].join('\n');
|
|
197
|
+
}
|
|
198
|
+
/** The columns of the API: an own cell by its tag, a ready element by the tag of the skin. */
|
|
199
|
+
export function colsValue(spec, skin, cells) {
|
|
200
|
+
const fields = new Map(spec.fields.map(field => [field.name, field]));
|
|
201
|
+
return spec.cols.map(col => {
|
|
202
|
+
const field = col.name ? fields.get(col.name) : undefined;
|
|
203
|
+
const dataType = col.dataType ?? ((field?.type === 'number' || field?.type === 'boolean') ? field.type : undefined);
|
|
204
|
+
let component;
|
|
205
|
+
if (col.component && 'cell' in col.component) {
|
|
206
|
+
const name = col.component.cell;
|
|
207
|
+
const cell = cells.find(item => item.cell.name === name);
|
|
208
|
+
component = { selector: cell.tag, event: cell.cell.actions?.length ? 'action' : undefined };
|
|
209
|
+
}
|
|
210
|
+
else if (col.component) {
|
|
211
|
+
component = elementComponent(col.component.element, skin, col.name);
|
|
212
|
+
}
|
|
213
|
+
// Numbers stand at the right edge of their column, as in the tables of Material
|
|
214
|
+
const className = (dataType === 'number') ? 'bro-col_text-right' : undefined;
|
|
215
|
+
return { title: col.title, tooltip: col.tooltip, name: col.name, className, dataType, sortKey: col.sortKey, component };
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/** A ready element of the skins: its tag depends on the skin. */
|
|
219
|
+
function elementComponent(element, skin, name) {
|
|
220
|
+
switch (element) {
|
|
221
|
+
case 'tags':
|
|
222
|
+
return { selector: `bro-${skin}-tags` };
|
|
223
|
+
case 'switch':
|
|
224
|
+
// The switch of the bs and nv skins takes its value from the attribute checked
|
|
225
|
+
return { selector: (skin === 'mat') ? 'bro-mat-slide-toggle' : 'bro-switch', attrs: { checked: raw(`(_col, row) => row.${name}`) } };
|
|
226
|
+
default:
|
|
227
|
+
// The table opens the row details on the action bro-details-toggle of the button
|
|
228
|
+
return { selector: 'bro-details-button', attrs: { title: 'Details', name: raw('events.detailsToggleEvent') }, event: 'action' };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function rowAction(spec, action) {
|
|
232
|
+
return (spec.cells ?? []).some(cell => cell.actions?.includes(action));
|
|
233
|
+
}
|
|
234
|
+
/** The line about the model: the type of a row, and of the filters of the drawer. */
|
|
235
|
+
export function modelDescription(hasFilters) {
|
|
236
|
+
return `the model: the type of a row${hasFilters ? ' and of the filters of the drawer' : ''}.`;
|
|
237
|
+
}
|
|
238
|
+
/** The line about a cell: what it shows, or the row details. */
|
|
239
|
+
export function cellDescription(cell) {
|
|
240
|
+
return (cell.cell.kind === 'details') ? `the row details: ${cell.cell.shows}.` : `a cell: ${cell.cell.shows}.`;
|
|
241
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { BroSpecCell, BroTableSpec } from '../spec/spec.js';
|
|
2
|
+
import { type BroCellFilling } from './fillings.js';
|
|
3
|
+
import type { BroTarget } from './setup.js';
|
|
4
|
+
export interface BroCellCode {
|
|
5
|
+
cell: BroSpecCell;
|
|
6
|
+
/** The file from the folder of the page, without the extension. */
|
|
7
|
+
file: string;
|
|
8
|
+
className: string;
|
|
9
|
+
tag: string;
|
|
10
|
+
/** An Angular component, registered with createCustomElement, or a Custom Element in Vanilla JS. */
|
|
11
|
+
angular: boolean;
|
|
12
|
+
content: string;
|
|
13
|
+
/** The cell has its filling; without it the scaffold has a TODO in place of the template. */
|
|
14
|
+
filled: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** The cells in the order a reader meets them: by their first columns, then the row details. */
|
|
17
|
+
export declare function byColumns(spec: BroTableSpec, cells: BroCellCode[]): BroCellCode[];
|
|
18
|
+
/**
|
|
19
|
+
* The scaffold of a cell or of the row details with its filling: an Angular component in an Angular project, with the
|
|
20
|
+
* markup of the skin in its template, and a Custom Element in Vanilla JS for a page without a framework.
|
|
21
|
+
*/
|
|
22
|
+
export declare function cellCode(spec: BroTableSpec, cell: BroSpecCell, target: BroTarget, filling?: BroCellFilling, tagSuffix?: string, simulated?: boolean): BroCellCode;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { htmlComment, imports, importsField, indentBlock, lineComment, templateText } from './code.js';
|
|
2
|
+
import { angularClasses, templateClasses } from './fillings.js';
|
|
3
|
+
import { BroNames } from './names.js';
|
|
4
|
+
/** The cells in the order a reader meets them: by their first columns, then the row details. */
|
|
5
|
+
export function byColumns(spec, cells) {
|
|
6
|
+
const column = (code) => {
|
|
7
|
+
const index = spec.cols.findIndex(col => col.component && 'cell' in col.component && col.component.cell === code.cell.name);
|
|
8
|
+
return (code.cell.kind === 'details' || index < 0) ? spec.cols.length : index;
|
|
9
|
+
};
|
|
10
|
+
return [...cells].sort((a, b) => column(a) - column(b));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The scaffold of a cell or of the row details with its filling: an Angular component in an Angular project, with the
|
|
14
|
+
* markup of the skin in its template, and a Custom Element in Vanilla JS for a page without a framework.
|
|
15
|
+
*/
|
|
16
|
+
export function cellCode(spec, cell, target, filling, tagSuffix = '', simulated = false) {
|
|
17
|
+
const names = BroNames.of(spec, tagSuffix);
|
|
18
|
+
const angular = (target === 'angular');
|
|
19
|
+
const code = {
|
|
20
|
+
cell,
|
|
21
|
+
file: names.cellFile(cell.name),
|
|
22
|
+
className: names.cellClass(cell.name),
|
|
23
|
+
tag: names.tag(cell.name),
|
|
24
|
+
angular,
|
|
25
|
+
content: '',
|
|
26
|
+
filled: angular ? Boolean(filling?.template) : Boolean(filling?.render)
|
|
27
|
+
};
|
|
28
|
+
code.content = angular ? angularCell(spec, code, filling, simulated) : vanillaCell(spec, code, filling);
|
|
29
|
+
return code;
|
|
30
|
+
}
|
|
31
|
+
function angularCell(spec, code, filling, simulated) {
|
|
32
|
+
const { cell } = code;
|
|
33
|
+
const details = (cell.kind === 'details');
|
|
34
|
+
const server = details && Boolean(spec.config?.rowDetailsServer);
|
|
35
|
+
const actions = !details && Boolean(cell.actions?.length);
|
|
36
|
+
const angularModules = {
|
|
37
|
+
'@angular/core': ['ChangeDetectionStrategy', 'Component', 'Input', ...((actions || server) ? ['EventEmitter', 'Output'] : []), ...((server && simulated) ? ['type OnInit'] : [])]
|
|
38
|
+
};
|
|
39
|
+
const localModules = {
|
|
40
|
+
[`./${BroNames.of(spec).kebab}`]: [`type ${spec.rowType}`]
|
|
41
|
+
};
|
|
42
|
+
// The classes of the template by their modules, for the field imports of the component
|
|
43
|
+
const classes = {};
|
|
44
|
+
const template = filling?.template?.trim() || `<!-- TODO: ${htmlComment(cell.shows)}; the fields of the row: ${cell.fields.join(', ')} -->`;
|
|
45
|
+
const imported = [
|
|
46
|
+
...templateClasses(template).map(name => ({ name, from: angularClasses[name] })),
|
|
47
|
+
...(filling?.imports ?? []).map(item => (typeof item === 'string') ? { name: item, from: angularClasses[item] } : item)
|
|
48
|
+
];
|
|
49
|
+
for (const { name, from } of imported) {
|
|
50
|
+
const modules = from.startsWith('.') ? localModules : angularModules;
|
|
51
|
+
if (!Object.values(classes).flat().includes(name)) {
|
|
52
|
+
(modules[from] ??= []).push(name);
|
|
53
|
+
(classes[from] ??= []).push(name);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const decorator = [
|
|
57
|
+
` template: \`\n${indentBlock(templateText(template), ' ')}\`,`,
|
|
58
|
+
...(filling?.styles?.trim() ? [` styles: \`\n${indentBlock(templateText(filling.styles.trim()), ' ')}\`,`] : []),
|
|
59
|
+
` changeDetection: ChangeDetectionStrategy.OnPush${Object.keys(classes).length ? ',' : ''}`,
|
|
60
|
+
...(Object.keys(classes).length ? [importsField(classes)] : [])
|
|
61
|
+
];
|
|
62
|
+
const members = details
|
|
63
|
+
? [
|
|
64
|
+
` @Input() row!: ${spec.rowType};`,
|
|
65
|
+
...(server
|
|
66
|
+
? [
|
|
67
|
+
'',
|
|
68
|
+
' /** Set for the details that load from the server: emit loaded with the loaded part, 1 at the end. */',
|
|
69
|
+
' @Input() server?: string;',
|
|
70
|
+
'',
|
|
71
|
+
' @Output(events.detailsServerLoadedEvent) loaded: EventEmitter<number> = new EventEmitter<number>();',
|
|
72
|
+
...(simulated
|
|
73
|
+
? [
|
|
74
|
+
'',
|
|
75
|
+
' /** The simulated server gives the details in a second: your request for them takes its place. */',
|
|
76
|
+
' ngOnInit() {',
|
|
77
|
+
' if (this.server) {',
|
|
78
|
+
' setTimeout(() => this.loaded.emit(1), 1000);',
|
|
79
|
+
' }',
|
|
80
|
+
' }'
|
|
81
|
+
]
|
|
82
|
+
: [])
|
|
83
|
+
]
|
|
84
|
+
: [])
|
|
85
|
+
]
|
|
86
|
+
: [
|
|
87
|
+
` @Input() col!: BroCol<${spec.rowType}>;`,
|
|
88
|
+
` @Input() row!: ${spec.rowType};`,
|
|
89
|
+
...(actions ? ['', ' @Output() action: EventEmitter<string> = new EventEmitter<string>();'] : [])
|
|
90
|
+
];
|
|
91
|
+
if (filling?.members?.trim()) {
|
|
92
|
+
members.push('', indentBlock(filling.members.trim(), ' '));
|
|
93
|
+
}
|
|
94
|
+
return [
|
|
95
|
+
imports({
|
|
96
|
+
...angularModules,
|
|
97
|
+
'@table-bro/core': details ? ['type BroRowDetailsElement', ...(server ? ['events'] : [])] : ['type BroCellElement', 'type BroCol'],
|
|
98
|
+
...localModules
|
|
99
|
+
}),
|
|
100
|
+
'',
|
|
101
|
+
'@Component({',
|
|
102
|
+
...decorator,
|
|
103
|
+
'})',
|
|
104
|
+
`export class ${code.className} implements ${details ? 'BroRowDetailsElement' : 'BroCellElement'}<${spec.rowType}>${(server && simulated) ? ', OnInit' : ''} {`,
|
|
105
|
+
...members,
|
|
106
|
+
'}',
|
|
107
|
+
''
|
|
108
|
+
].join('\n');
|
|
109
|
+
}
|
|
110
|
+
function vanillaCell(spec, code, filling) {
|
|
111
|
+
const { cell } = code;
|
|
112
|
+
const details = (cell.kind === 'details');
|
|
113
|
+
const server = details && Boolean(spec.config?.rowDetailsServer);
|
|
114
|
+
const actions = !details && Boolean(cell.actions?.length);
|
|
115
|
+
const render = filling?.render?.trim() || `// TODO: ${lineComment(cell.shows)}; the fields of the row: ${cell.fields.join(', ')}. Values go in with textContent`;
|
|
116
|
+
const body = [
|
|
117
|
+
...(details ? [` row?: ${spec.rowType};`] : [` col?: BroCol<${spec.rowType}>;`, ` row?: ${spec.rowType};`]),
|
|
118
|
+
'',
|
|
119
|
+
' /** A move in the DOM connects the element again; it keeps its markup. */',
|
|
120
|
+
' private built: boolean = false;',
|
|
121
|
+
'',
|
|
122
|
+
' connectedCallback(): void {',
|
|
123
|
+
' if (this.built) {',
|
|
124
|
+
' return;',
|
|
125
|
+
' }',
|
|
126
|
+
'',
|
|
127
|
+
' this.built = true;',
|
|
128
|
+
'',
|
|
129
|
+
' this.render();',
|
|
130
|
+
' }',
|
|
131
|
+
'',
|
|
132
|
+
' private render(): void {',
|
|
133
|
+
indentBlock(render, ' '),
|
|
134
|
+
' }'
|
|
135
|
+
];
|
|
136
|
+
if (actions) {
|
|
137
|
+
body.push('', ' /** Sends an action of the cell: the table gives it to the page with the column and the row. */', ' private sendAction(action: string): void {', ' this.dispatchEvent(new CustomEvent(\'action\', {detail: action}));', ' }');
|
|
138
|
+
}
|
|
139
|
+
if (server) {
|
|
140
|
+
body.push('', ' /** For the details that load from the server (the attribute server): the loaded part, 1 at the end. */', ' private reportLoaded(part: number): void {', ' this.dispatchEvent(new CustomEvent(events.detailsServerLoadedEvent, {detail: part}));', ' }');
|
|
141
|
+
}
|
|
142
|
+
if (filling?.members?.trim()) {
|
|
143
|
+
body.push('', indentBlock(filling.members.trim(), ' '));
|
|
144
|
+
}
|
|
145
|
+
return [
|
|
146
|
+
imports({
|
|
147
|
+
'@table-bro/core': server ? ['events'] : [],
|
|
148
|
+
'type @table-bro/core': details ? ['BroRowDetailsElement'] : ['BroCellElement', 'BroCol'],
|
|
149
|
+
[`type ./${BroNames.of(spec).kebab}`]: [spec.rowType]
|
|
150
|
+
}),
|
|
151
|
+
'',
|
|
152
|
+
`export class ${code.className} extends HTMLElement implements ${details ? 'BroRowDetailsElement' : 'BroCellElement'}<${spec.rowType}> {`,
|
|
153
|
+
...body,
|
|
154
|
+
'}',
|
|
155
|
+
''
|
|
156
|
+
].join('\n');
|
|
157
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Code to print as it is inside a literal, as a function in a config. */
|
|
2
|
+
export declare class BroRawCode {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
constructor(code: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function raw(code: string): BroRawCode;
|
|
7
|
+
/**
|
|
8
|
+
* Prints a value as a TypeScript literal in the style of the code of Table, Bro: single quotes, an object with one field
|
|
9
|
+
* on a line, objects of an array joined with "}, {", no trailing commas. Undefined fields are left out.
|
|
10
|
+
*/
|
|
11
|
+
export declare function literal(value: unknown, indent?: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* A key of an object literal in the style of the code of Table, Bro: a key in camelCase without quotes, any other in quotes.
|
|
14
|
+
* @param key - the name of the key
|
|
15
|
+
*/
|
|
16
|
+
export declare function objectKey(key: string): string;
|
|
17
|
+
/** A string literal: the line terminators of JavaScript would end it, so they go in as escapes. */
|
|
18
|
+
export declare function quote(value: string): string;
|
|
19
|
+
/** A text of the spec for a comment of one line: a line break would end the comment, and the rest would be code. */
|
|
20
|
+
export declare function lineComment(value: string): string;
|
|
21
|
+
/** A text of the spec for a block comment of one line: its end, the star and the slash, would make the rest code. */
|
|
22
|
+
export declare function blockComment(value: string): string;
|
|
23
|
+
/** A text of the spec for an HTML comment: two hyphens could end it, and the rest would be markup. */
|
|
24
|
+
export declare function htmlComment(value: string): string;
|
|
25
|
+
/** A text for a template literal: the backticks and the placeholders of the text stay text. */
|
|
26
|
+
export declare function templateText(value: string): string;
|
|
27
|
+
/** Moves the lines after the first one by the indent: a block of code inside other code. */
|
|
28
|
+
export declare function reindent(code: string, indent: string): string;
|
|
29
|
+
/** Indents every non-empty line of a block. */
|
|
30
|
+
export declare function indentBlock(code: string, indent: string): string;
|
|
31
|
+
export declare function escapeHtml(value: string): string;
|
|
32
|
+
/** A text for an Angular template: also its interpolation and its blocks stay text. */
|
|
33
|
+
export declare function escapeTemplate(value: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Import lines grouped as in the code of Table, Bro: a block per group of the modules, the "from" of a block aligned,
|
|
36
|
+
* the lines of a block sorted by module, one line per module. A name with the prefix "type " is a type: import type
|
|
37
|
+
* {...} when the module gives only types, an inline type next to the values otherwise, as verbatimModuleSyntax takes
|
|
38
|
+
* them. A module with the prefix "type " gives only types.
|
|
39
|
+
* @param modules - the names to import by their modules
|
|
40
|
+
*/
|
|
41
|
+
export declare function imports(modules: Record<string, string[]>): string;
|
|
42
|
+
/**
|
|
43
|
+
* The field imports of an Angular decorator as in the code of Table, Bro: one entry per line, in the groups of the
|
|
44
|
+
* imports between blank lines, each group sorted by name.
|
|
45
|
+
* @param modules - the classes of the field by their modules
|
|
46
|
+
*/
|
|
47
|
+
export declare function importsField(modules: Record<string, string[]>): string;
|
|
48
|
+
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/** Code to print as it is inside a literal, as a function in a config. */
|
|
2
|
+
export class BroRawCode {
|
|
3
|
+
code;
|
|
4
|
+
constructor(code) {
|
|
5
|
+
this.code = code;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export function raw(code) {
|
|
9
|
+
return new BroRawCode(code);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Prints a value as a TypeScript literal in the style of the code of Table, Bro: single quotes, an object with one field
|
|
13
|
+
* on a line, objects of an array joined with "}, {", no trailing commas. Undefined fields are left out.
|
|
14
|
+
*/
|
|
15
|
+
export function literal(value, indent = '') {
|
|
16
|
+
const inner = indent + ' ';
|
|
17
|
+
if (value instanceof BroRawCode) {
|
|
18
|
+
return reindent(value.code, indent);
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === 'string') {
|
|
21
|
+
return quote(value);
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
if (!value.length) {
|
|
25
|
+
return '[]';
|
|
26
|
+
}
|
|
27
|
+
if (value.every(item => item === null || typeof item !== 'object')) {
|
|
28
|
+
return `[${value.map(item => literal(item)).join(', ')}]`;
|
|
29
|
+
}
|
|
30
|
+
if (value.every(item => isPlainObject(item))) {
|
|
31
|
+
return `[\n${inner}${value.map(item => literal(item, inner)).join(', ')}\n${indent}]`;
|
|
32
|
+
}
|
|
33
|
+
return `[\n${value.map(item => inner + literal(item, inner)).join(',\n')}\n${indent}]`;
|
|
34
|
+
}
|
|
35
|
+
if (isPlainObject(value)) {
|
|
36
|
+
const entries = Object.entries(value).filter(([, item]) => item !== undefined);
|
|
37
|
+
if (!entries.length) {
|
|
38
|
+
return '{}';
|
|
39
|
+
}
|
|
40
|
+
return `{\n${entries.map(([key, item]) => `${inner}${objectKey(key)}: ${literal(item, inner)}`).join(',\n')}\n${indent}}`;
|
|
41
|
+
}
|
|
42
|
+
return String(value);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A key of an object literal in the style of the code of Table, Bro: a key in camelCase without quotes, any other in quotes.
|
|
46
|
+
* @param key - the name of the key
|
|
47
|
+
*/
|
|
48
|
+
export function objectKey(key) {
|
|
49
|
+
return /^[a-z][a-zA-Z0-9]*$/.test(key) ? key : quote(key);
|
|
50
|
+
}
|
|
51
|
+
/** A string literal: the line terminators of JavaScript would end it, so they go in as escapes. */
|
|
52
|
+
export function quote(value) {
|
|
53
|
+
return `'${value.replace(/[\\'\n\r\u2028\u2029]/g, char => stringEscapes[char])}'`;
|
|
54
|
+
}
|
|
55
|
+
const stringEscapes = { '\\': '\\\\', '\'': '\\\'', '\n': '\\n', '\r': '\\r', '\u2028': '\\u2028', '\u2029': '\\u2029' };
|
|
56
|
+
/** A text of the spec for a comment of one line: a line break would end the comment, and the rest would be code. */
|
|
57
|
+
export function lineComment(value) {
|
|
58
|
+
return value.replace(/[\n\r\u2028\u2029]+/g, ' ');
|
|
59
|
+
}
|
|
60
|
+
/** A text of the spec for a block comment of one line: its end, the star and the slash, would make the rest code. */
|
|
61
|
+
export function blockComment(value) {
|
|
62
|
+
return lineComment(value).replace(/\*\//g, '*\\/');
|
|
63
|
+
}
|
|
64
|
+
/** A text of the spec for an HTML comment: two hyphens could end it, and the rest would be markup. */
|
|
65
|
+
export function htmlComment(value) {
|
|
66
|
+
return lineComment(value).replace(/-(?=-)/g, '- ');
|
|
67
|
+
}
|
|
68
|
+
/** A text for a template literal: the backticks and the placeholders of the text stay text. */
|
|
69
|
+
export function templateText(value) {
|
|
70
|
+
return value.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
71
|
+
}
|
|
72
|
+
/** Moves the lines after the first one by the indent: a block of code inside other code. */
|
|
73
|
+
export function reindent(code, indent) {
|
|
74
|
+
return code.split('\n').map((line, index) => (index && line) ? indent + line : line).join('\n');
|
|
75
|
+
}
|
|
76
|
+
/** Indents every non-empty line of a block. */
|
|
77
|
+
export function indentBlock(code, indent) {
|
|
78
|
+
return code.split('\n').map(line => line ? indent + line : line).join('\n');
|
|
79
|
+
}
|
|
80
|
+
export function escapeHtml(value) {
|
|
81
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
82
|
+
}
|
|
83
|
+
/** A text for an Angular template: also its interpolation and its blocks stay text. */
|
|
84
|
+
export function escapeTemplate(value) {
|
|
85
|
+
return escapeHtml(value).replace(/\{/g, '{').replace(/\}/g, '}').replace(/@/g, '@');
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Import lines grouped as in the code of Table, Bro: a block per group of the modules, the "from" of a block aligned,
|
|
89
|
+
* the lines of a block sorted by module, one line per module. A name with the prefix "type " is a type: import type
|
|
90
|
+
* {...} when the module gives only types, an inline type next to the values otherwise, as verbatimModuleSyntax takes
|
|
91
|
+
* them. A module with the prefix "type " gives only types.
|
|
92
|
+
* @param modules - the names to import by their modules
|
|
93
|
+
*/
|
|
94
|
+
export function imports(modules) {
|
|
95
|
+
const groups = [[], [], [], []];
|
|
96
|
+
const merged = new Map();
|
|
97
|
+
Object.entries(modules).forEach(([key, names]) => {
|
|
98
|
+
const module = key.replace(/^type /, '');
|
|
99
|
+
const kinds = merged.get(module) ?? new Map();
|
|
100
|
+
names.forEach(entry => {
|
|
101
|
+
const name = entry.replace(/^type /, '');
|
|
102
|
+
const type = key.startsWith('type ') || entry.startsWith('type ');
|
|
103
|
+
kinds.set(name, (kinds.get(name) ?? true) && type);
|
|
104
|
+
});
|
|
105
|
+
merged.set(module, kinds);
|
|
106
|
+
});
|
|
107
|
+
[...merged.entries()]
|
|
108
|
+
.filter(([, kinds]) => kinds.size)
|
|
109
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
110
|
+
.forEach(([module, kinds]) => {
|
|
111
|
+
const types = [...kinds.values()].every(Boolean);
|
|
112
|
+
const list = [...kinds.keys()].sort(byName).map(name => (kinds.get(name) && !types) ? `type ${name}` : name).join(', ');
|
|
113
|
+
groups[importGroup(module)].push([`import ${types ? 'type ' : ''}{${list}}`, `from '${module}';`]);
|
|
114
|
+
});
|
|
115
|
+
return groups
|
|
116
|
+
.filter(lines => lines.length)
|
|
117
|
+
.map(lines => {
|
|
118
|
+
const width = Math.max(...lines.map(([head]) => head.length));
|
|
119
|
+
return lines.map(([head, tail]) => `${head.padEnd(width)} ${tail}`).join('\n');
|
|
120
|
+
})
|
|
121
|
+
.join('\n\n');
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The field imports of an Angular decorator as in the code of Table, Bro: one entry per line, in the groups of the
|
|
125
|
+
* imports between blank lines, each group sorted by name.
|
|
126
|
+
* @param modules - the classes of the field by their modules
|
|
127
|
+
*/
|
|
128
|
+
export function importsField(modules) {
|
|
129
|
+
const groups = [[], [], [], []];
|
|
130
|
+
Object.entries(modules).forEach(([module, names]) => groups[importGroup(module)].push(...names));
|
|
131
|
+
return ` imports: [\n${groups.filter(names => names.length).map(names => [...new Set(names)].sort((a, b) => a.localeCompare(b, 'en')).map(name => ` ${name}`).join(',\n')).join(',\n\n')}\n ]`;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The group of a module among the imports, as the ESLint settings of Table, Bro order them: 0 Angular, 1 the UI
|
|
135
|
+
* libraries, Angular Material and Table, Bro among them, 2 other libraries, 3 the application.
|
|
136
|
+
* @param module - the module of an import
|
|
137
|
+
*/
|
|
138
|
+
function importGroup(module) {
|
|
139
|
+
if (module.startsWith('.')) {
|
|
140
|
+
return 3;
|
|
141
|
+
}
|
|
142
|
+
if (/^@angular\/(?!material(\/|$)|cdk(\/|$))/.test(module)) {
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
return /^(@angular\/(material|cdk)(\/|$)|bootstrap$|@table-bro(-extended)?\/)/.test(module) ? 1 : 2;
|
|
146
|
+
}
|
|
147
|
+
/** The order of the names in an import: case-insensitive, as in the code of Table, Bro. */
|
|
148
|
+
function byName(a, b) {
|
|
149
|
+
return a.toLowerCase().localeCompare(b.toLowerCase()) || a.localeCompare(b);
|
|
150
|
+
}
|
|
151
|
+
export function isPlainObject(value) {
|
|
152
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof BroRawCode);
|
|
153
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BroMcpExtension } from '../extension.js';
|
|
2
|
+
import type { BroSpecSkin, BroTableSpec } from '../spec/spec.js';
|
|
3
|
+
import type { BroSpecFillings } from './fillings.js';
|
|
4
|
+
export interface BroConfigCode {
|
|
5
|
+
/** The config as a literal. */
|
|
6
|
+
code: string;
|
|
7
|
+
/** Declarations before the config, as the short titles of shortColumnTitle. */
|
|
8
|
+
preamble: string[];
|
|
9
|
+
/** The names the config imports, by module; a type with the prefix "type ", as imports() takes it. */
|
|
10
|
+
imports: Record<string, string[]>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The config of the table from the spec: the functions and the settings of the skin come from the data of the spec,
|
|
14
|
+
* only the bodies of the callbacks and of the custom rules from a model.
|
|
15
|
+
*/
|
|
16
|
+
export declare function configCode(spec: BroTableSpec, skin: BroSpecSkin, fillings: BroSpecFillings, indent: string, tagSuffix?: string, extensions?: BroMcpExtension[]): BroConfigCode;
|