pgkiln 0.82.0 → 0.83.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/db/migrations/153_component_events.sql +21 -0
- package/docs/apex-feature-parity.md +3 -3
- package/docs/guide/03-builder.md +8 -1
- package/docs/guide/04-pages-and-regions.md +2 -1
- package/docs/guide/07-dynamic-actions.md +27 -0
- package/docs/guide/11-from-apex.md +2 -1
- package/docs/guide/12-development.md +9 -1
- package/package.json +1 -1
- package/public/app.css +2 -0
- package/public/app.js +85 -4
- package/public/builder.css +25 -2
- package/public/data-modeler.js +8 -5
- package/public/sql-monitor.js +5 -2
- package/src/builder/ai-builder.ts +50 -46
- package/src/builder/api.ts +49 -49
- package/src/builder/autorest.ts +92 -87
- package/src/builder/changelog.ts +39 -37
- package/src/builder/components.ts +5 -4
- package/src/builder/data-modeler.ts +89 -84
- package/src/builder/data-security.ts +39 -37
- package/src/builder/dataload.ts +69 -65
- package/src/builder/datapump.ts +108 -97
- package/src/builder/ddl-tools.ts +38 -34
- package/src/builder/document-view.ts +11 -11
- package/src/builder/i18n/de.ts +1627 -0
- package/src/builder/i18n/en.ts +1627 -0
- package/src/builder/i18n/es.ts +1627 -0
- package/src/builder/i18n/fr.ts +1627 -0
- package/src/builder/i18n/it.ts +1627 -0
- package/src/builder/i18n/ja.ts +1627 -0
- package/src/builder/i18n/ko.ts +1627 -0
- package/src/builder/i18n/nl.ts +1627 -0
- package/src/builder/i18n/pt.ts +1627 -0
- package/src/builder/i18n/zh-cn.ts +1627 -0
- package/src/builder/i18n/zh-tw.ts +1627 -0
- package/src/builder/i18n.ts +18 -0
- package/src/builder/object-actions.ts +18 -16
- package/src/builder/object-browser.ts +2 -2
- package/src/builder/querybuilder.ts +42 -36
- package/src/builder/quicksql.ts +16 -18
- package/src/builder/region-settings.ts +201 -199
- package/src/builder/report-settings.ts +64 -60
- package/src/builder/rest-modules.ts +145 -143
- package/src/builder/sampledata.ts +59 -52
- package/src/builder/scheduling.ts +180 -159
- package/src/builder/scripts.ts +71 -65
- package/src/builder/sql-charts.ts +36 -30
- package/src/builder/sql-monitor.ts +95 -89
- package/src/builder/sql-utilities.ts +47 -40
- package/src/builder/top-sql.ts +21 -17
- package/src/builder/unload.ts +48 -44
- package/src/builder/worksheet.ts +3 -3
- package/src/cron-schedule.ts +47 -17
- package/src/metadata.ts +3 -1
- package/src/runtime/calendar.ts +3 -2
- package/src/runtime/context.ts +2 -0
- package/src/runtime/grid.ts +1 -0
- package/src/runtime/maps.ts +7 -4
- package/src/runtime/render.ts +6 -1
- package/src/runtime/routes.ts +4 -0
- package/src/runtime/tree.ts +4 -3
|
@@ -18,6 +18,7 @@ import { MAX_EXTRA_LAYERS } from '../runtime/maps.ts';
|
|
|
18
18
|
import { postgis } from '../runtime/spatial.ts';
|
|
19
19
|
import { reporterSettingsForm } from './reporter.ts';
|
|
20
20
|
import { formDesignerSettingsForm } from './form-designer.ts';
|
|
21
|
+
import { scopedTx as tx, tr, withTranslator } from './i18n.ts';
|
|
21
22
|
|
|
22
23
|
// Page designer → a region → Settings: the region's "config" JSON as a form
|
|
23
24
|
// for grid, chart, cards, calendar and faceted search regions (report
|
|
@@ -99,19 +100,19 @@ function columnsHint(cols: { columns: string[] } | { error: string }, expected?:
|
|
|
99
100
|
|
|
100
101
|
function linkFieldset(
|
|
101
102
|
id: (n: string) => string, link: { page?: number; items?: Record<string, string> } | undefined, pages: { page_no: number; name: string }[], what: string,
|
|
102
|
-
{ prefix = 'link', legend = '
|
|
103
|
+
{ prefix = 'link', legend = tx('rs.link'), placeholder = 'P3_ID=#id#', help = tx('rs.link_help') }: { prefix?: string; legend?: string; placeholder?: string; help?: string } = {},
|
|
103
104
|
) {
|
|
104
105
|
return html`<fieldset class="prop-group"><legend>${legend}</legend><div class="form-grid">
|
|
105
|
-
<div class="field"><label class="label" for="${id(`${prefix}_page`)}">${
|
|
106
|
-
<select id="${id(`${prefix}_page`)}" name="${prefix}_page">${opt('', '
|
|
107
|
-
<div class="field" data-wide><label class="label" for="${id(`${prefix}_items`)}"
|
|
106
|
+
<div class="field"><label class="label" for="${id(`${prefix}_page`)}">${tx('rs.links_to_page', { what })}</label>
|
|
107
|
+
<select id="${id(`${prefix}_page`)}" name="${prefix}_page">${opt('', tx('rs.no_link'), link?.page)}${pages.map((p) => opt(String(p.page_no), `${p.page_no}. ${p.name}`, link?.page))}</select></div>
|
|
108
|
+
<div class="field" data-wide><label class="label" for="${id(`${prefix}_items`)}">${tx('rs.set_items')}</label>
|
|
108
109
|
<input id="${id(`${prefix}_items`)}" name="${prefix}_items" value="${linkItemsText(link?.items)}" placeholder="${placeholder}">
|
|
109
110
|
<small class="help">${help}</small></div>
|
|
110
111
|
</div></fieldset>`;
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
const emptyField = (id: (n: string) => string, cfg: Config) => html`<div class="field" data-wide><label class="label" for="${id('empty')}"
|
|
114
|
-
<input id="${id('empty')}" name="empty" value="${cfg.empty ?? ''}" placeholder="
|
|
114
|
+
const emptyField = (id: (n: string) => string, cfg: Config) => html`<div class="field" data-wide><label class="label" for="${id('empty')}">${tx('rs.text_when_there_are_no')}</label>
|
|
115
|
+
<input id="${id('empty')}" name="empty" value="${cfg.empty ?? ''}" placeholder="${tx('rs.no_data_found')}"></div>`;
|
|
115
116
|
|
|
116
117
|
/** Columns in the settings that the query no longer returns stay listed (marked), so they can be cleared. */
|
|
117
118
|
function withStale(names: string[], configured: string[]) {
|
|
@@ -119,7 +120,7 @@ function withStale(names: string[], configured: string[]) {
|
|
|
119
120
|
return { all: [...names, ...[...new Set(configured)].filter((n) => !known.has(n))], known };
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
const staleTag = (known: Set<string>, n: string) => (known.has(n) ? '' : html` <span class="tag tag-error"
|
|
123
|
+
const staleTag = (known: Set<string>, n: string) => (known.has(n) ? '' : html` <span class="tag tag-error">${tx('rs.not_in_query')}</span>`);
|
|
123
124
|
|
|
124
125
|
// ---------------------------------------------------------------- merges (pure, unit tested)
|
|
125
126
|
|
|
@@ -408,13 +409,13 @@ function mergeStyle(b: Body, p: string): Config | undefined {
|
|
|
408
409
|
}
|
|
409
410
|
function styleFields(id: (n: string) => string, p: string, st: Config | undefined) {
|
|
410
411
|
const s = (st ?? {}) as Config;
|
|
411
|
-
return html`<div class="field"><label class="label" for="${id(`${p}_fill_color`)}"
|
|
412
|
+
return html`<div class="field"><label class="label" for="${id(`${p}_fill_color`)}">${tx('rs.fill_colour_lines_polygons')}</label>
|
|
412
413
|
<input id="${id(`${p}_fill_color`)}" name="${p}_fill_color" value="${s.fill_color ?? ''}" placeholder="#2e5c8a" pattern="#[0-9a-fA-F]{6}"></div>
|
|
413
|
-
<div class="field"><label class="label" for="${id(`${p}_stroke_color`)}"
|
|
414
|
+
<div class="field"><label class="label" for="${id(`${p}_stroke_color`)}">${tx('rs.line_colour')}</label>
|
|
414
415
|
<input id="${id(`${p}_stroke_color`)}" name="${p}_stroke_color" value="${s.stroke_color ?? ''}" placeholder="#1b2230" pattern="#[0-9a-fA-F]{6}"></div>
|
|
415
|
-
<div class="field"><label class="label" for="${id(`${p}_stroke_width`)}"
|
|
416
|
+
<div class="field"><label class="label" for="${id(`${p}_stroke_width`)}">${tx('rs.line_width')}</label>
|
|
416
417
|
<input id="${id(`${p}_stroke_width`)}" name="${p}_stroke_width" type="number" min="0" max="20" step="0.5" value="${s.stroke_width ?? ''}"></div>
|
|
417
|
-
<div class="field"><label class="label" for="${id(`${p}_opacity`)}"
|
|
418
|
+
<div class="field"><label class="label" for="${id(`${p}_opacity`)}">${tx('rs.opacity_0_1')}</label>
|
|
418
419
|
<input id="${id(`${p}_opacity`)}" name="${p}_opacity" type="number" min="0" max="1" step="0.05" value="${s.opacity ?? ''}"></div>`;
|
|
419
420
|
}
|
|
420
421
|
|
|
@@ -509,67 +510,67 @@ async function gridFields(appId: number, r: RegionRow, id: (n: string) => string
|
|
|
509
510
|
const lovSelect = (n: string, i: number) => {
|
|
510
511
|
const cur = colCfg[n]?.lov as string | undefined;
|
|
511
512
|
const custom = cur && !lovs.some((l) => `LOV:${l}` === cur.toUpperCase()) ? cur : null;
|
|
512
|
-
return html`<select name="lov_${i}" aria-label="
|
|
513
|
+
return html`<select name="lov_${i}" aria-label="${tx('rs.aria_list_of_values_of', { name: n })}">${opt('', tx('rs.text_field'), cur)}${lovs.map((l) => opt(`LOV:${l}`, l, cur?.toUpperCase()))}${custom ? opt(custom, `custom: ${custom.length > 40 ? custom.slice(0, 40) + '…' : custom}`, cur) : ''}</select>`;
|
|
513
514
|
};
|
|
514
515
|
const rows = all.map((n, i) => html`<tr>
|
|
515
|
-
<td data-label="
|
|
516
|
-
<td data-label="
|
|
517
|
-
<td data-label="
|
|
518
|
-
<td data-label="
|
|
519
|
-
<td data-label="
|
|
520
|
-
<td data-label="
|
|
516
|
+
<td data-label="${tx('rs.column')}"><code>${n}</code>${staleTag(known, n)}<input type="hidden" name="col_${i}" value="${n}"></td>
|
|
517
|
+
<td data-label="${tx('rs.heading')}"><input name="heading_${i}" value="${cfg.headings?.[n] ?? ''}" placeholder="${heading(n)}" aria-label="${tx('rs.aria_heading_of', { name: n })}"></td>
|
|
518
|
+
<td data-label="${tx('rs.shown')}"><input type="checkbox" name="shown_${i}" value="true"${hidden.has(n.toLowerCase()) ? '' : raw(' checked')} aria-label="${tx('rs.aria_show', { name: n })}"></td>
|
|
519
|
+
<td data-label="${tx('rs.read_only')}"><input type="checkbox" name="readonly_${i}" value="true"${readonly.has(n.toLowerCase()) ? raw(' checked') : ''} aria-label="${n} is read-only"></td>
|
|
520
|
+
<td data-label="${tx('rs.required')}"><input type="checkbox" name="required_${i}" value="true"${colCfg[n]?.required ? raw(' checked') : ''} aria-label="${n} is required"></td>
|
|
521
|
+
<td data-label="${tx('rs.edit_as')}">${lovSelect(n, i)}</td>
|
|
521
522
|
</tr>`);
|
|
522
523
|
|
|
523
524
|
return html`${'error' in cols ? columnsHint(cols) : ''}
|
|
524
525
|
<input type="hidden" name="n" value="${all.length}">
|
|
525
|
-
<fieldset class="prop-group"><legend
|
|
526
|
-
<div class="field"><label class="label" for="${id('page_size')}"
|
|
526
|
+
<fieldset class="prop-group"><legend>${tx('rs.behaviour')}</legend><div class="form-grid">
|
|
527
|
+
<div class="field"><label class="label" for="${id('page_size')}">${tx('rs.rows_per_page')}</label>
|
|
527
528
|
<select id="${id('page_size')}" name="page_size">${GRID_PAGE_SIZES.map((x) => opt(x, x, cfg.page_size ?? 25))}</select></div>
|
|
528
|
-
${check('allow_insert', '
|
|
529
|
-
${check('allow_update', '
|
|
530
|
-
${check('allow_delete', '
|
|
529
|
+
${check('allow_insert', tx('rs.users_may_add_rows'), allow.insert !== false)}
|
|
530
|
+
${check('allow_update', tx('rs.users_may_change_rows'), allow.update !== false)}
|
|
531
|
+
${check('allow_delete', tx('rs.users_may_delete_rows'), allow.delete !== false)}
|
|
531
532
|
</div>
|
|
532
|
-
<small class="help"
|
|
533
|
+
<small class="help">${tx('rs.saving_needs_a_process_of')}</small></fieldset>
|
|
533
534
|
<input type="hidden" name="grid_features" value="1">
|
|
534
|
-
<fieldset class="prop-group"><legend
|
|
535
|
-
<div class="field" data-wide><label class="label" for="${id('aggregates')}"
|
|
535
|
+
<fieldset class="prop-group"><legend>${tx('rs.totals_and_columns')}</legend><div class="form-grid">
|
|
536
|
+
<div class="field" data-wide><label class="label" for="${id('aggregates')}">${tx('rs.aggregates_in_the_footer')}</label>
|
|
536
537
|
<input id="${id('aggregates')}" name="aggregates" value="${aggregatesText(cfg.aggregates)}" placeholder="sal=sum,avg; empno=count">
|
|
537
|
-
<small class="help"
|
|
538
|
-
<div class="field"><label class="label" for="${id('frozen')}"
|
|
538
|
+
<small class="help">${tx('rs.column_sum_avg_count_min')}</small></div>
|
|
539
|
+
<div class="field"><label class="label" for="${id('frozen')}">${tx('rs.frozen_columns')}</label>
|
|
539
540
|
<select id="${id('frozen')}" name="frozen">${['0', '1', '2', '3', '4', '5'].map((x) => opt(x, x === '0' ? 'none' : `the first ${x}`, cfg.frozen ?? 0))}</select>
|
|
540
|
-
<small class="help"
|
|
541
|
-
${check('actions', '
|
|
542
|
-
${check('saved_reports', '
|
|
543
|
-
<div class="field"><label class="label" for="${id('public_reports')}"
|
|
544
|
-
<select id="${id('public_reports')}" name="public_reports">${opt('', '
|
|
541
|
+
<small class="help">${tx('rs.stay_in_view_while_the')}</small></div>
|
|
542
|
+
${check('actions', tx('rs.actions_menu_columns_aggregates_saved'), cfg.actions !== false)}
|
|
543
|
+
${check('saved_reports', tx('rs.users_may_save_grid_reports'), cfg.saved_reports !== false)}
|
|
544
|
+
<div class="field"><label class="label" for="${id('public_reports')}">${tx('rs.public_reports_by')}</label>
|
|
545
|
+
<select id="${id('public_reports')}" name="public_reports">${opt('', tx('rs.nobody'), cfg.public_reports)}${authz.map((n) => opt(n, n, cfg.public_reports))}</select></div>
|
|
545
546
|
</div></fieldset>
|
|
546
547
|
<input type="hidden" name="grid_views" value="1">
|
|
547
|
-
<fieldset class="prop-group"><legend
|
|
548
|
-
${GRID_USER_VIEWS.map((v) => check(`view_${v}`, GRID_VIEW_LABELS[v], !Array.isArray(cfg.views) || cfg.views.includes(v)))}
|
|
548
|
+
<fieldset class="prop-group"><legend>${tx('rs.views')}</legend><div class="form-grid">
|
|
549
|
+
${GRID_USER_VIEWS.map((v) => check(`view_${v}`, tx.dyn(`rs.gridview.${v}`, GRID_VIEW_LABELS[v]), !Array.isArray(cfg.views) || cfg.views.includes(v)))}
|
|
549
550
|
</div>
|
|
550
|
-
<small class="help"
|
|
551
|
-
<fieldset class="prop-group"><legend
|
|
552
|
-
${check('row_actions', '
|
|
553
|
-
${check('row_duplicate', '
|
|
554
|
-
${check('row_delete', '
|
|
551
|
+
<small class="help">${tx('rs.views_users_may_switch_to')}</small></fieldset>
|
|
552
|
+
<fieldset class="prop-group"><legend>${tx('rs.row_actions')}</legend><div class="form-grid">
|
|
553
|
+
${check('row_actions', tx('rs.row_actions_menu'), !!cfg.row_actions)}
|
|
554
|
+
${check('row_duplicate', tx('rs.duplicate'), cfg.row_actions?.duplicate !== false)}
|
|
555
|
+
${check('row_delete', tx('rs.delete'), cfg.row_actions?.delete !== false)}
|
|
555
556
|
</div></fieldset>
|
|
556
|
-
${linkFieldset(id, cfg.row_actions?.edit, pages, '
|
|
557
|
-
<fieldset class="prop-group"><legend
|
|
558
|
-
<div class="field"><label class="label" for="${id('select_column')}"
|
|
559
|
-
<select id="${id('select_column')}" name="select_column">${opt('', '
|
|
560
|
-
<div class="field"><label class="label" for="${id('select_item')}"
|
|
557
|
+
${linkFieldset(id, cfg.row_actions?.edit, pages, tx('rs.w.edit'), { prefix: 'edit', legend: tx('rs.row_actions_edit'), placeholder: 'P3_ID=#id#' })}
|
|
558
|
+
<fieldset class="prop-group"><legend>${tx('rs.master_detail')}</legend><div class="form-grid">
|
|
559
|
+
<div class="field"><label class="label" for="${id('select_column')}">${tx('rs.as_a_master_column_of')}</label>
|
|
560
|
+
<select id="${id('select_column')}" name="select_column">${opt('', tx('rs.not_a_master'), cfg.select_row?.column)}${all.map((n) => opt(n, n, cfg.select_row?.column))}</select></div>
|
|
561
|
+
<div class="field"><label class="label" for="${id('select_item')}">${tx('rs.goes_into_item')}</label>
|
|
561
562
|
<input id="${id('select_item')}" name="select_item" value="${cfg.select_row?.item ?? ''}" placeholder="P1_ID"></div>
|
|
562
|
-
<div class="field"><label class="label" for="${id('master_item')}"
|
|
563
|
+
<div class="field"><label class="label" for="${id('master_item')}">${tx('rs.as_a_detail_follows_item')}</label>
|
|
563
564
|
<input id="${id('master_item')}" name="master_item" value="${cfg.master?.item ?? ''}" placeholder="P1_ID"></div>
|
|
564
|
-
<div class="field"><label class="label" for="${id('master_column')}"
|
|
565
|
+
<div class="field"><label class="label" for="${id('master_column')}">${tx('rs.and_new_rows_get_it')}</label>
|
|
565
566
|
<input id="${id('master_column')}" name="master_column" value="${cfg.master?.column ?? ''}" placeholder="dept_id"></div>
|
|
566
567
|
</div>
|
|
567
|
-
<small class="help"
|
|
568
|
-
<fieldset class="prop-group"><legend
|
|
568
|
+
<small class="help">${tx('rs.a_master_grid_s_selected')}</small></fieldset>
|
|
569
|
+
<fieldset class="prop-group"><legend>${tx('rs.columns')}</legend>
|
|
569
570
|
${all.length
|
|
570
|
-
? html`<div class="table-wrap"><table class="report report-reflow"><thead><tr><th
|
|
571
|
-
<small class="help"
|
|
572
|
-
: html`<p class="muted"
|
|
571
|
+
? html`<div class="table-wrap"><table class="report report-reflow"><thead><tr><th>${tx('rs.column')}</th><th>${tx('rs.heading')}</th><th>${tx('rs.shown')}</th><th>${tx('rs.read_only')}</th><th>${tx('rs.required')}</th><th>${tx('rs.edit_as')}</th></tr></thead><tbody>${rows}</tbody></table></div>
|
|
572
|
+
<small class="help">${tx('rs.edit_as_picks_a_shared')}</small>`
|
|
573
|
+
: html`<p class="muted">${tx('rs.no_columns_yet')}</p>`}
|
|
573
574
|
</fieldset>`;
|
|
574
575
|
}
|
|
575
576
|
|
|
@@ -577,9 +578,9 @@ async function gridFields(appId: number, r: RegionRow, id: (n: string) => string
|
|
|
577
578
|
/** How a map layer's rows reach the browser: with the page, for the visible area, or as vector tiles. */
|
|
578
579
|
function loadField(id: (n: string) => string, name: string, l: Config) {
|
|
579
580
|
const load = l.tiles === true ? 'tiles' : l.visible_area === true ? 'area' : '';
|
|
580
|
-
return html`<div class="field"><label class="label" for="${id(name)}"
|
|
581
|
-
<select id="${id(name)}" name="${name}">${opt('', '
|
|
582
|
-
<small class="help"
|
|
581
|
+
return html`<div class="field"><label class="label" for="${id(name)}">${tx('rs.load')}</label>
|
|
582
|
+
<select id="${id(name)}" name="${name}">${opt('', tx('rs.all_rows_with_the_page'), load)}${opt('area', tx('rs.rows_in_the_visible_area'), load)}${opt('tiles', tx('rs.vector_tiles_large_data_sets'), load)}</select>
|
|
583
|
+
<small class="help">${tx.code('rs.the_visible_area_and_vector')}</small></div>`;
|
|
583
584
|
}
|
|
584
585
|
|
|
585
586
|
async function mapLayersFieldsets(cfg: Config, appId: number, allPages: { page_no: number; name: string }[], id: (n: string) => string) {
|
|
@@ -589,23 +590,23 @@ async function mapLayersFieldsets(cfg: Config, appId: number, allPages: { page_n
|
|
|
589
590
|
for (const [i, l] of shown.entries()) {
|
|
590
591
|
const p = `layer${i}`;
|
|
591
592
|
const hint = l.source ? columnsHint(await reportColumns(appId, l.source)) : '';
|
|
592
|
-
parts.push(html`<fieldset class="prop-group"><legend>${l.source ?
|
|
593
|
+
parts.push(html`<fieldset class="prop-group"><legend>${l.source ? tx('rs.layer_n', { n: i + 2, name: l.name ?? '' }) : tx('rs.add_layer')}</legend>
|
|
593
594
|
${hint}
|
|
594
595
|
<div class="form-grid">
|
|
595
|
-
<div class="field"><label class="label" for="${id(`${p}_name`)}"
|
|
596
|
+
<div class="field"><label class="label" for="${id(`${p}_name`)}">${tx('rs.name_legend')}</label>
|
|
596
597
|
<input id="${id(`${p}_name`)}" name="${p}_name" value="${l.name ?? ''}" maxlength="60"></div>
|
|
597
|
-
<div class="field"><label class="label" for="${id(`${p}_layer`)}"
|
|
598
|
-
<select id="${id(`${p}_layer`)}" name="${p}_layer">${opt('', '
|
|
598
|
+
<div class="field"><label class="label" for="${id(`${p}_layer`)}">${tx('rs.show_places_as')}</label>
|
|
599
|
+
<select id="${id(`${p}_layer`)}" name="${p}_layer">${opt('', tx('rs.markers'), l.layer)}${opt('heat', tx('rs.heat_map'), l.layer)}${opt('lines', tx('rs.lines'), l.layer)}${opt('polygons', tx('rs.polygons'), l.layer)}${opt('extruded', tx('rs.extruded_polygons_shaded_by_height'), l.layer)}</select></div>
|
|
599
600
|
${styleFields(id, p, l.style)}
|
|
600
601
|
${loadField(id, `${p}_load`, l)}
|
|
601
|
-
${check(`${p}_cluster`, '
|
|
602
|
-
${check(`${p}_hidden`, '
|
|
603
|
-
<div class="field" data-wide><label class="label" for="${id(`${p}_source`)}"
|
|
602
|
+
${check(`${p}_cluster`, tx('rs.group_markers'), l.cluster === true)}
|
|
603
|
+
${check(`${p}_hidden`, tx('rs.off_at_first'), l.hidden === true)}
|
|
604
|
+
<div class="field" data-wide><label class="label" for="${id(`${p}_source`)}">${tx('rs.query')}</label>
|
|
604
605
|
<textarea id="${id(`${p}_source`)}" name="${p}_source" class="code" rows="5" spellcheck="false" data-code="sql" placeholder="select lat, lng, title from …">${l.source ?? ''}</textarea>
|
|
605
|
-
<small class="help"
|
|
606
|
-
<div class="field"><label class="label" for="${id(`${p}_link_page`)}"
|
|
607
|
-
<select id="${id(`${p}_link_page`)}" name="${p}_link_page">${opt('', '
|
|
608
|
-
<div class="field"><label class="label" for="${id(`${p}_link_items`)}"
|
|
606
|
+
<small class="help">${tx('rs.the_same_columns_as_the')}</small></div>
|
|
607
|
+
<div class="field"><label class="label" for="${id(`${p}_link_page`)}">${tx('rs.each_place_links_to_page')}</label>
|
|
608
|
+
<select id="${id(`${p}_link_page`)}" name="${p}_link_page">${opt('', tx('rs.no_link'), l.link?.page)}${allPages.map((x) => opt(String(x.page_no), `${x.page_no}. ${x.name}`, l.link?.page))}</select></div>
|
|
609
|
+
<div class="field"><label class="label" for="${id(`${p}_link_items`)}">${tx('rs.set_items')}</label>
|
|
609
610
|
<input id="${id(`${p}_link_items`)}" name="${p}_link_items" value="${linkItemsText(l.link?.items)}" placeholder="P3_ID=#id#"></div>
|
|
610
611
|
</div></fieldset>`);
|
|
611
612
|
}
|
|
@@ -620,15 +621,15 @@ async function mapReportFieldset(r: RegionRow, pageId: number, appId: number, id
|
|
|
620
621
|
const cols = target ? await reportColumns(appId, await designSql(appId, target)) : null;
|
|
621
622
|
// with PostGIS a geometry/geography column filters too (only the column names are known here)
|
|
622
623
|
const noPosition = cols && 'columns' in cols && !positionColumns(cols.columns) && !(await postgis());
|
|
623
|
-
return html`<fieldset class="prop-group"><legend
|
|
624
|
-
<div class="field"><label class="label" for="${id('report')}"
|
|
625
|
-
<select id="${id('report')}" name="report">${opt('', '
|
|
626
|
-
<small class="help">${reports.length ? '
|
|
627
|
-
<div class="field"><label class="label" for="${id('filter')}"
|
|
628
|
-
<select id="${id('filter')}" name="filter">${opt('', '
|
|
629
|
-
<small class="help"
|
|
624
|
+
return html`<fieldset class="prop-group"><legend>${tx('rs.filter_a_report')}</legend><div class="form-grid">
|
|
625
|
+
<div class="field"><label class="label" for="${id('report')}">${tx('rs.report_region')}</label>
|
|
626
|
+
<select id="${id('report')}" name="report">${opt('', tx('rs.none'), target?.id)}${reports.map((x) => opt(String(x.id), `${x.title ?? tx('rs.untitled')} (#${x.id})`, cfg.report))}</select>
|
|
627
|
+
<small class="help">${reports.length ? tx('rs.map_report_help') : tx('rs.map_report_none')}</small></div>
|
|
628
|
+
<div class="field"><label class="label" for="${id('filter')}">${tx('rs.filter_by')}</label>
|
|
629
|
+
<select id="${id('filter')}" name="filter">${opt('', tx('rs.the_visible_area'), cfg.filter)}${opt('distance', tx('rs.distance_from_the_centre'), cfg.filter)}</select>
|
|
630
|
+
<small class="help">${tx('rs.the_condition_runs_on_the')}</small></div>
|
|
630
631
|
</div>
|
|
631
|
-
${noPosition ? html`<div class="alert alert-error" role="alert"
|
|
632
|
+
${noPosition ? html`<div class="alert alert-error" role="alert">${tx.code('rs.no_position_alert')}</div>` : ''}
|
|
632
633
|
</fieldset>`;
|
|
633
634
|
}
|
|
634
635
|
|
|
@@ -651,38 +652,38 @@ async function facetsFields(r: RegionRow, pageId: number, appId: number, id: (n:
|
|
|
651
652
|
const type = f?.type === 'range' || f?.type === 'star' ? f.type : 'checkbox';
|
|
652
653
|
const custom = type === 'range' && (f?.custom === true || (f?.custom !== false && !(Array.isArray(f?.ranges) && f.ranges.length)));
|
|
653
654
|
return html`<tr>
|
|
654
|
-
<td data-label="
|
|
655
|
-
<td data-label="
|
|
656
|
-
<td data-label="
|
|
657
|
-
<td data-label="
|
|
658
|
-
<td data-label="
|
|
659
|
-
<td data-label="
|
|
660
|
-
<td data-label="
|
|
661
|
-
<td data-label="
|
|
662
|
-
${smart ? '' : html`<td data-label="
|
|
663
|
-
<td data-label="
|
|
655
|
+
<td data-label="${tx('rs.column')}"><code>${n}</code>${staleTag(known, n)}<input type="hidden" name="col_${i}" value="${n}"></td>
|
|
656
|
+
<td data-label="${tx('rs.facet')}"><input type="checkbox" name="on_${i}" value="true"${f ? raw(' checked') : ''} aria-label="${tx('rs.aria_facet_on', { name: n })}"></td>
|
|
657
|
+
<td data-label="${tx('rs.label')}"><input name="label_${i}" value="${f?.label ?? ''}" placeholder="${heading(n)}" aria-label="${tx('rs.aria_label_of', { name: n })}"></td>
|
|
658
|
+
<td data-label="${tx('rs.type')}"><select name="type_${i}" aria-label="${tx('rs.aria_type_of_the_facet_on', { name: n })}">${Object.entries(FACET_TYPE_LABELS).map(([k, l]) => opt(k, tx.dyn(`rs.facet.${k}`, l), type))}</select></td>
|
|
659
|
+
<td data-label="${tx('rs.values_shown')}"><input name="limit_${i}" type="number" min="1" max="50" value="${f?.limit ?? ''}" placeholder="${FACET_LIMIT}" aria-label="${tx('rs.aria_values_shown_for', { name: n })}" class="u-mw6"></td>
|
|
660
|
+
<td data-label="${tx('rs.exclude')}"><input type="checkbox" name="exclude_${i}" value="true"${f?.exclude === true ? raw(' checked') : ''} aria-label="${tx('rs.aria_users_may_exclude_values_of', { name: n })}"></td>
|
|
661
|
+
<td data-label="${tx('rs.ranges')}"><input name="ranges_${i}" value="${rangesText(f?.ranges)}" placeholder="..1000; 1000..3000 = Middle; 3000.." aria-label="${tx('rs.aria_ranges_of', { name: n })}"></td>
|
|
662
|
+
<td data-label="${tx('rs.from_to')}"><input type="checkbox" name="custom_${i}" value="true"${custom ? raw(' checked') : ''} aria-label="${tx('rs.aria_users_may_type_a_range', { name: n })}"></td>
|
|
663
|
+
${smart ? '' : html`<td data-label="${tx('rs.chart')}"><select name="chart_${i}" aria-label="${tx('rs.aria_chart_of_the_facet_on', { name: n })}">${opt('', tx('rs.none_2'), f?.chart)}${opt('bar', tx('rs.bar'), f?.chart)}${opt('pie', tx('rs.pie'), f?.chart)}</select></td>`}
|
|
664
|
+
<td data-label="${tx('rs.order')}"><input name="seq_${i}" type="number" value="${(i + 1) * 10}" aria-label="${tx('rs.aria_order_of', { name: n })}" class="u-mw6"></td>
|
|
664
665
|
</tr>`;
|
|
665
666
|
});
|
|
666
667
|
return html`<input type="hidden" name="n" value="${all.length}">
|
|
667
|
-
<fieldset class="prop-group"><legend
|
|
668
|
-
<div class="field"><label class="label" for="${id('report')}"
|
|
669
|
-
<select id="${id('report')}" name="report">${opt('', '
|
|
670
|
-
<small class="help">${reports.length ?
|
|
668
|
+
<fieldset class="prop-group"><legend>${tx('rs.filters')}</legend><div class="form-grid">
|
|
669
|
+
<div class="field"><label class="label" for="${id('report')}">${tx('rs.report_region')}</label>
|
|
670
|
+
<select id="${id('report')}" name="report">${opt('', tx('rs.choose'), target?.id)}${reports.map((x) => opt(String(x.id), `${x.title ?? tx('rs.untitled')} (#${x.id})`, cfg.report))}</select>
|
|
671
|
+
<small class="help">${reports.length ? tx(smart ? 'rs.report_smart_help' : 'rs.report_facets_help') : tx('rs.report_first')}</small></div>
|
|
671
672
|
${smart
|
|
672
|
-
? html`<div class="field"><label class="label" for="${id('suggestions')}"
|
|
673
|
+
? html`<div class="field"><label class="label" for="${id('suggestions')}">${tx('rs.suggestions_per_facet_0_10')}</label>
|
|
673
674
|
<input id="${id('suggestions')}" name="suggestions" type="number" min="0" max="10" value="${cfg.suggestions ?? 3}"></div>
|
|
674
|
-
<div class="field" data-wide><label class="label" for="${id('placeholder')}"
|
|
675
|
-
<input id="${id('placeholder')}" name="placeholder" maxlength="100" value="${cfg.placeholder ?? ''}" placeholder="
|
|
676
|
-
${check('ask', '
|
|
677
|
-
: html`${check('search', '
|
|
675
|
+
<div class="field" data-wide><label class="label" for="${id('placeholder')}">${tx('rs.placeholder_of_the_search_field')}</label>
|
|
676
|
+
<input id="${id('placeholder')}" name="placeholder" maxlength="100" value="${cfg.placeholder ?? ''}" placeholder="${tx('rs.search_or_filter')}"></div>
|
|
677
|
+
${check('ask', tx('rs.read_the_search_as_a'), cfg.ask === true)}`
|
|
678
|
+
: html`${check('search', tx('rs.a_search_field_searches_all'), cfg.search === true)}`}
|
|
678
679
|
</div></fieldset>
|
|
679
680
|
${cols && 'error' in cols ? columnsHint(cols) : ''}
|
|
680
681
|
${target
|
|
681
|
-
? html`<fieldset class="prop-group"><legend
|
|
682
|
+
? html`<fieldset class="prop-group"><legend>${tx('rs.facets')}</legend>
|
|
682
683
|
${all.length
|
|
683
|
-
? html`<div class="table-wrap"><table class="report report-reflow"><thead><tr><th
|
|
684
|
-
<small class="help"
|
|
685
|
-
: html`<p class="muted"
|
|
684
|
+
? html`<div class="table-wrap"><table class="report report-reflow"><thead><tr><th>${tx('rs.column')}</th><th>${tx('rs.facet')}</th><th>${tx('rs.label')}</th><th>${tx('rs.type')}</th><th>${tx('rs.values_shown')}</th><th>${tx('rs.exclude')}</th><th>${tx('rs.ranges')}</th><th>${tx('rs.from_to')}</th>${smart ? '' : html`<th>${tx('rs.chart')}</th>`}<th>${tx('rs.order')}</th></tr></thead><tbody>${rows}</tbody></table></div>
|
|
685
|
+
<small class="help">${tx.code('rs.facets_help')}${smart ? '' : html` ${tx('rs.facets_help_chart')}`}</small>`
|
|
686
|
+
: html`<p class="muted">${tx('rs.the_report_has_no_columns')}</p>`}
|
|
686
687
|
</fieldset>`
|
|
687
688
|
: ''}`;
|
|
688
689
|
}
|
|
@@ -696,33 +697,33 @@ async function calendarFields(appId: number, r: RegionRow, id: (n: string) => st
|
|
|
696
697
|
const views = calendarViews(cfg);
|
|
697
698
|
const schemes = (await owner.query('select name from meta.authz_scheme where app_id = $1 order by name', [appId])).rows.map((x) => x.name as string);
|
|
698
699
|
return html`${columnsHint(await reportColumns(appId, await designSql(appId, r)), ['start_date', 'title'])}
|
|
699
|
-
<p class="muted u-mt0"
|
|
700
|
-
<fieldset class="prop-group"><legend
|
|
700
|
+
<p class="muted u-mt0">${tx.code('rs.calendar_help')}</p>
|
|
701
|
+
<fieldset class="prop-group"><legend>${tx('rs.views')}</legend>
|
|
701
702
|
<div class="form-grid">
|
|
702
|
-
${CALENDAR_VIEWS.map((v) => check(`view_${v}`, VIEW_LABELS[v], views.includes(v)))}
|
|
703
|
-
<div class="field"><label class="label" for="${id('view')}"
|
|
704
|
-
<select id="${id('view')}" name="view">${CALENDAR_VIEWS.map((v) => opt(v, VIEW_LABELS[v], cfg.view ?? views[0]))}</select></div>
|
|
705
|
-
<div class="field"><label class="label" for="${id('day_start')}"
|
|
703
|
+
${CALENDAR_VIEWS.map((v) => check(`view_${v}`, tx.dyn(`rs.view.${v}`, VIEW_LABELS[v]), views.includes(v)))}
|
|
704
|
+
<div class="field"><label class="label" for="${id('view')}">${tx('rs.shown_first')}</label>
|
|
705
|
+
<select id="${id('view')}" name="view">${CALENDAR_VIEWS.map((v) => opt(v, tx.dyn(`rs.view.${v}`, VIEW_LABELS[v]), cfg.view ?? views[0]))}</select></div>
|
|
706
|
+
<div class="field"><label class="label" for="${id('day_start')}">${tx('rs.week_and_day_views_from')}</label>
|
|
706
707
|
<input id="${id('day_start')}" name="day_start" type="number" min="0" max="23" value="${cfg.day_start ?? ''}" placeholder="8"></div>
|
|
707
|
-
<div class="field"><label class="label" for="${id('day_end')}"
|
|
708
|
+
<div class="field"><label class="label" for="${id('day_end')}">${tx('rs.until_hour')}</label>
|
|
708
709
|
<input id="${id('day_end')}" name="day_end" type="number" min="1" max="24" value="${cfg.day_end ?? ''}" placeholder="18"></div>
|
|
709
710
|
</div>
|
|
710
|
-
<small class="help"
|
|
711
|
-
${linkFieldset(id, cfg.link, pages, '
|
|
712
|
-
${linkFieldset(id, cfg.create, pages, '
|
|
713
|
-
prefix: 'create', legend: '
|
|
714
|
-
help: '
|
|
711
|
+
<small class="help">${tx('rs.users_switch_between_the_ticked')}</small></fieldset>
|
|
712
|
+
${linkFieldset(id, cfg.link, pages, tx('rs.w.event'), { legend: tx('rs.edit_link') })}
|
|
713
|
+
${linkFieldset(id, cfg.create, pages, tx('rs.w.slot'), {
|
|
714
|
+
prefix: 'create', legend: tx('rs.create_on_click'), placeholder: 'P3_START=#start#,P3_END=#end#',
|
|
715
|
+
help: tx('rs.create_help'),
|
|
715
716
|
})}
|
|
716
|
-
<fieldset class="prop-group"><legend
|
|
717
|
-
<div class="field" data-wide><label class="label" for="${id('move')}"
|
|
717
|
+
<fieldset class="prop-group"><legend>${tx('rs.drag_and_drop')}</legend><div class="form-grid">
|
|
718
|
+
<div class="field" data-wide><label class="label" for="${id('move')}">${tx('rs.sql_that_moves_an_event')}</label>
|
|
718
719
|
<textarea id="${id('move')}" name="move" rows="3" data-code="sql" placeholder="update my_schema.event set starts_at = :NEW_START::timestamp, ends_at = :NEW_END::timestamp where id = :EVENT_ID::int">${cfg.move ?? ''}</textarea>
|
|
719
|
-
<small class="help"
|
|
720
|
-
<div class="field"><label class="label" for="${id('key')}"
|
|
720
|
+
<small class="help">${tx.code('rs.runs_as_the_application_s')}</small></div>
|
|
721
|
+
<div class="field"><label class="label" for="${id('key')}">${tx('rs.key_column')}</label>
|
|
721
722
|
<input id="${id('key')}" name="key" value="${cfg.key ?? ''}" placeholder="id"></div>
|
|
722
|
-
<div class="field"><label class="label" for="${id('move_authz')}"
|
|
723
|
-
<select id="${id('move_authz')}" name="move_authz">${opt('', '
|
|
723
|
+
<div class="field"><label class="label" for="${id('move_authz')}">${tx('rs.who_may_drag')}</label>
|
|
724
|
+
<select id="${id('move_authz')}" name="move_authz">${opt('', tx('rs.everyone_who_sees_the_calendar'), cfg.move_authz)}${opt('MUST_NOT_BE_PUBLIC_USER', tx('rs.signed_in_users'), cfg.move_authz)}${schemes.map((n) => opt(n, `Authorization: ${n}`, cfg.move_authz))}</select></div>
|
|
724
725
|
</div>
|
|
725
|
-
<small class="help"
|
|
726
|
+
<small class="help">${tx('rs.only_events_the_user_sees')}</small></fieldset>`;
|
|
726
727
|
}
|
|
727
728
|
|
|
728
729
|
/** The display selector's settings: style and options, and which regions of the page take part. */
|
|
@@ -730,31 +731,34 @@ async function displaySelectorFields(r: RegionRow, pageId: number, id: (n: strin
|
|
|
730
731
|
const cfg = r.config ?? {};
|
|
731
732
|
const others = (await owner.query(`select id, title, type, config->'display_selector' as flag from meta.region
|
|
732
733
|
where page_id = $1 and id <> $2 and type <> 'display_selector' order by seq, id`, [pageId, r.id])).rows;
|
|
733
|
-
return html`<fieldset class="prop-group"><legend
|
|
734
|
-
<div class="field"><label class="label" for="${id('style')}"
|
|
735
|
-
<select id="${id('style')}" name="style">${opt('', '
|
|
736
|
-
<div class="field"><label class="label" for="${id('interval')}"
|
|
734
|
+
return html`<fieldset class="prop-group"><legend>${tx('rs.appearance')}</legend><div class="form-grid">
|
|
735
|
+
<div class="field"><label class="label" for="${id('style')}">${tx('rs.show_as')}</label>
|
|
736
|
+
<select id="${id('style')}" name="style">${opt('', tx('rs.tabs'), cfg.style)}${opt('select', tx('rs.select_list'), cfg.style)}${opt('carousel', tx('rs.carousel_one_at_a_time'), cfg.style)}</select></div>
|
|
737
|
+
<div class="field"><label class="label" for="${id('interval')}">${tx('rs.carousel_next_region_every_seconds')}</label>
|
|
737
738
|
<input id="${id('interval')}" name="interval" type="number" min="1" max="120" value="${cfg.interval ?? ''}" placeholder="empty: only by hand">
|
|
738
|
-
<small class="help"
|
|
739
|
+
<small class="help">${tx('rs.not_while_the_pointer_or')}</small></div>
|
|
739
740
|
</div>
|
|
740
|
-
${check('show_all', '
|
|
741
|
-
${check('remember', '
|
|
742
|
-
<fieldset class="prop-group"><legend
|
|
741
|
+
${check('show_all', tx('rs.show_all_choice_shows_every'), cfg.show_all !== false)}
|
|
742
|
+
${check('remember', tx('rs.remember_the_choice_during_the'), cfg.remember !== false)}</fieldset>
|
|
743
|
+
<fieldset class="prop-group"><legend>${tx('rs.regions')}</legend>
|
|
743
744
|
<input type="hidden" name="members" value="1">
|
|
744
745
|
${others.length
|
|
745
|
-
? html`<div class="table-wrap"><table class="report report-reflow"><thead><tr><th
|
|
746
|
+
? html`<div class="table-wrap"><table class="report report-reflow"><thead><tr><th>${tx('rs.region')}</th><th>${tx('rs.in_a_tab')}</th><th>${tx('rs.tab_name')}</th></tr></thead><tbody>
|
|
746
747
|
${others.map((x) => html`<tr>
|
|
747
|
-
<td data-label="
|
|
748
|
-
<td data-label="
|
|
749
|
-
<td data-label="
|
|
748
|
+
<td data-label="${tx('rs.region')}">${x.title || '(untitled)'} <span class="muted">(${x.type} #${x.id})</span></td>
|
|
749
|
+
<td data-label="${tx('rs.in_a_tab')}"><input type="checkbox" name="member_${x.id}" value="true"${x.flag === true || (typeof x.flag === 'string' && x.flag.trim()) ? raw(' checked') : ''} aria-label="${tx('rs.aria_show_in_the_display_selector', { name: x.title || `region ${x.id}` })}"></td>
|
|
750
|
+
<td data-label="${tx('rs.tab_name')}"><input name="tab_${x.id}" maxlength="60" value="${typeof x.flag === 'string' ? x.flag : ''}" placeholder="${x.title || ''}" aria-label="${tx('rs.aria_tab_name_of', { name: x.title || `region ${x.id}` })}"></td>
|
|
750
751
|
</tr>`)}</tbody></table></div>`
|
|
751
|
-
: html`<p class="muted"
|
|
752
|
-
<small class="help"
|
|
752
|
+
: html`<p class="muted">${tx('rs.there_are_no_other_regions')}</p>`}
|
|
753
|
+
<small class="help">${tx.code('rs.a_chosen_region_gets_display')}</small>
|
|
753
754
|
</fieldset>`;
|
|
754
755
|
}
|
|
755
756
|
|
|
756
757
|
/** The settings form under a region in the page designer, or '' for types without one. */
|
|
757
|
-
|
|
758
|
+
/** (sprint 162) in the developer's builder language: the helpers below use the request's scoped translator */
|
|
759
|
+
export const regionSettingsForm = (pageId: number, appId: number, r: RegionRow, s: Session) => withTranslator(tr(s), () => regionSettingsFormInner(pageId, appId, r, s));
|
|
760
|
+
|
|
761
|
+
async function regionSettingsFormInner(pageId: number, appId: number, r: RegionRow, s: Session): Promise<Raw | ''> {
|
|
758
762
|
if (r.type === 'report') return html`${await reportSettingsForm(pageId, appId, r, s)}${await columnTemplatesForm(pageId, appId, r, s)}${await aiFilterSettingsForm(pageId, appId, r, s)}`;
|
|
759
763
|
if (r.type === 'ai_assistant') return assistantSettingsForm(pageId, appId, r, s);
|
|
760
764
|
if (r.type === 'template_component') return templateRegionForm(pageId, appId, r, s);
|
|
@@ -766,137 +770,135 @@ export async function regionSettingsForm(pageId: number, appId: number, r: Regio
|
|
|
766
770
|
const pages = async () => (await owner.query('select page_no, name from meta.page where app_id = $1 order by page_no', [appId])).rows;
|
|
767
771
|
const schemes = async () => (await owner.query('select name from meta.authz_scheme where app_id = $1 order by name', [appId])).rows.map((x) => x.name as string);
|
|
768
772
|
let body: Raw;
|
|
769
|
-
let title = '
|
|
773
|
+
let title = tx('rs.t.settings');
|
|
770
774
|
switch (r.type as SettingsType) {
|
|
771
775
|
case 'grid':
|
|
772
|
-
title = '
|
|
776
|
+
title = tx('rs.t.grid');
|
|
773
777
|
body = await gridFields(appId, r, id);
|
|
774
778
|
break;
|
|
775
779
|
case 'chart':
|
|
776
|
-
title = '
|
|
780
|
+
title = tx('rs.t.chart');
|
|
777
781
|
body = html`${columnsHint(await reportColumns(appId, await designSql(appId, r)))}
|
|
778
|
-
<p class="muted u-mt0"
|
|
779
|
-
|
|
780
|
-
<
|
|
781
|
-
|
|
782
|
-
<div class="field"><label class="label" for="${id('kind')}">Chart type</label>
|
|
783
|
-
<select id="${id('kind')}" name="kind">${CHART_KINDS.map((k) => opt(k, CHART_LABELS[k], cfg.kind ?? 'bar'))}</select></div>
|
|
782
|
+
<p class="muted u-mt0">${tx.code('rs.chart_help')}</p>
|
|
783
|
+
<fieldset class="prop-group"><legend>${tx('rs.appearance')}</legend><div class="form-grid">
|
|
784
|
+
<div class="field"><label class="label" for="${id('kind')}">${tx('rs.chart_type')}</label>
|
|
785
|
+
<select id="${id('kind')}" name="kind">${CHART_KINDS.map((k) => opt(k, tx.dyn(`rs.chart.${k}`, CHART_LABELS[k]), cfg.kind ?? 'bar'))}</select></div>
|
|
784
786
|
${emptyField(id, cfg)}
|
|
785
787
|
</div></fieldset>
|
|
786
|
-
<fieldset class="prop-group"><legend
|
|
787
|
-
${GAUGE_KEYS.map((k) => html`<div class="field"><label class="label" for="${id(`gauge_${k}`)}">${{
|
|
788
|
+
<fieldset class="prop-group"><legend>${tx('rs.gauge')}</legend><div class="form-grid">
|
|
789
|
+
${GAUGE_KEYS.map((k) => html`<div class="field"><label class="label" for="${id(`gauge_${k}`)}">${tx(`rs.gauge.${k}`)}</label>
|
|
788
790
|
<input id="${id(`gauge_${k}`)}" name="gauge_${k}" type="number" step="any" value="${cfg.gauge?.[k] ?? ''}" placeholder="${k === 'min' ? '0' : k === 'max' ? 'automatic' : ''}"></div>`)}
|
|
789
791
|
</div>
|
|
790
|
-
<small class="help"
|
|
791
|
-
${linkFieldset(id, cfg.link, await pages(), '
|
|
792
|
-
<small class="help"
|
|
793
|
-
<fieldset class="prop-group"><legend
|
|
794
|
-
<div class="field" data-wide><label class="label" for="${id('gantt_move')}"
|
|
792
|
+
<small class="help">${tx('rs.gauge_charts_only_a_warning')}</small></fieldset>
|
|
793
|
+
${linkFieldset(id, cfg.link, await pages(), tx('rs.w.point'))}
|
|
794
|
+
<small class="help">${tx.code('rs.drill_down_column_takes_the')}</small>
|
|
795
|
+
<fieldset class="prop-group"><legend>${tx('rs.gantt_drag_and_drop')}</legend><div class="form-grid">
|
|
796
|
+
<div class="field" data-wide><label class="label" for="${id('gantt_move')}">${tx('rs.sql_that_moves_a_task')}</label>
|
|
795
797
|
<textarea id="${id('gantt_move')}" name="gantt_move" rows="3" data-code="sql" placeholder="update my_schema.task set starts = :NEW_START::date, ends = :NEW_END::date where id = :TASK_ID::int">${cfg.gantt_move ?? ''}</textarea>
|
|
796
|
-
<small class="help"
|
|
797
|
-
<div class="field"><label class="label" for="${id('gantt_move_authz')}"
|
|
798
|
-
<select id="${id('gantt_move_authz')}" name="gantt_move_authz">${opt('', '
|
|
799
|
-
<div class="field"><label class="label" for="${id('gantt_snap')}"
|
|
800
|
-
<select id="${id('gantt_snap')}" name="gantt_snap">${opt('', '
|
|
801
|
-
</div><small class="help"
|
|
798
|
+
<small class="help">${tx.code('rs.gantt_charts_with_a_task')}</small></div>
|
|
799
|
+
<div class="field"><label class="label" for="${id('gantt_move_authz')}">${tx('rs.who_may_drag')}</label>
|
|
800
|
+
<select id="${id('gantt_move_authz')}" name="gantt_move_authz">${opt('', tx('rs.everyone_who_sees_the_chart'), cfg.gantt_move_authz)}${opt('MUST_NOT_BE_PUBLIC_USER', tx('rs.signed_in_users'), cfg.gantt_move_authz)}${(await schemes()).map((n) => opt(n, `Authorization: ${n}`, cfg.gantt_move_authz))}</select></div>
|
|
801
|
+
<div class="field"><label class="label" for="${id('gantt_snap')}">${tx('rs.steps')}</label>
|
|
802
|
+
<select id="${id('gantt_snap')}" name="gantt_snap">${opt('', tx('rs.days_or_hours_when_a'), cfg.gantt_snap)}${opt('day', tx('rs.days'), cfg.gantt_snap)}${opt('hour', tx('rs.hours'), cfg.gantt_snap)}</select></div>
|
|
803
|
+
</div><small class="help">${tx('rs.only_tasks_the_user_sees')}</small></fieldset>`;
|
|
802
804
|
break;
|
|
803
805
|
case 'cards':
|
|
804
|
-
title = '
|
|
806
|
+
title = tx('rs.t.cards');
|
|
805
807
|
body = html`${columnsHint(await reportColumns(appId, await designSql(appId, r)))}
|
|
806
808
|
<p class="muted u-mt0">Cards show the columns ${CARD_COLUMNS.map((c, i) => html`${i ? ', ' : ''}<code>${c}</code>`)}; KPI tiles show title, badge (the value) and icon.</p>
|
|
807
|
-
<fieldset class="prop-group"><legend
|
|
808
|
-
<div class="field"><label class="label" for="${id('style')}"
|
|
809
|
-
<select id="${id('style')}" name="style">${opt('', '
|
|
809
|
+
<fieldset class="prop-group"><legend>${tx('rs.appearance')}</legend><div class="form-grid">
|
|
810
|
+
<div class="field"><label class="label" for="${id('style')}">${tx('rs.style')}</label>
|
|
811
|
+
<select id="${id('style')}" name="style">${opt('', tx('rs.cards'), cfg.style)}${opt('metric', tx('rs.kpi_tiles_metric'), cfg.style)}</select></div>
|
|
810
812
|
${emptyField(id, cfg)}
|
|
811
813
|
</div></fieldset>
|
|
812
|
-
${linkFieldset(id, cfg.link, await pages(), '
|
|
814
|
+
${linkFieldset(id, cfg.link, await pages(), tx('rs.w.card'))}`;
|
|
813
815
|
break;
|
|
814
816
|
case 'calendar':
|
|
815
|
-
title = '
|
|
817
|
+
title = tx('rs.t.calendar');
|
|
816
818
|
body = await calendarFields(appId, r, id, await pages());
|
|
817
819
|
break;
|
|
818
820
|
case 'facets':
|
|
819
|
-
title = '
|
|
821
|
+
title = tx('rs.t.facets');
|
|
820
822
|
body = await facetsFields(r, pageId, appId, id, false);
|
|
821
823
|
break;
|
|
822
824
|
case 'smart_filters':
|
|
823
|
-
title = '
|
|
825
|
+
title = tx('rs.t.smart');
|
|
824
826
|
body = await facetsFields(r, pageId, appId, id, true);
|
|
825
827
|
break;
|
|
826
828
|
case 'display_selector':
|
|
827
|
-
title = '
|
|
829
|
+
title = tx('rs.t.selector');
|
|
828
830
|
body = await displaySelectorFields(r, pageId, id);
|
|
829
831
|
break;
|
|
830
832
|
case 'list': {
|
|
831
|
-
title = '
|
|
833
|
+
title = tx('rs.t.list');
|
|
832
834
|
const lists = (await owner.query('select name, type from meta.list where app_id = $1 order by name', [appId])).rows;
|
|
833
|
-
body = html`<p class="muted u-mt0"
|
|
834
|
-
<fieldset class="prop-group"><legend
|
|
835
|
-
<div class="field"><label class="label" for="${id('list')}"
|
|
836
|
-
<select id="${id('list')}" name="list">${opt('', '
|
|
837
|
-
<div class="field"><label class="label" for="${id('template')}"
|
|
838
|
-
<select id="${id('template')}" name="template">${opt('', '
|
|
835
|
+
body = html`<p class="muted u-mt0">${tx('rs.shows_a_list_from_shared')}</p>
|
|
836
|
+
<fieldset class="prop-group"><legend>${tx('rs.list')}</legend><div class="form-grid">
|
|
837
|
+
<div class="field"><label class="label" for="${id('list')}">${tx('rs.list')}</label>
|
|
838
|
+
<select id="${id('list')}" name="list">${opt('', tx('rs.choose'), cfg.list)}${lists.map((l) => opt(l.name, `${l.name} (${l.type})`, cfg.list))}${cfg.list && !lists.some((l) => l.name === cfg.list) ? opt(cfg.list, `${cfg.list} (missing!)`, cfg.list) : ''}</select></div>
|
|
839
|
+
<div class="field"><label class="label" for="${id('template')}">${tx('rs.template')}</label>
|
|
840
|
+
<select id="${id('template')}" name="template">${opt('', tx('rs.links_nested'), cfg.template)}${opt('badges', tx('rs.badge_list'), cfg.template)}${opt('cards', tx('rs.cards_menu'), cfg.template)}${opt('tabs', tx('rs.tabs'), cfg.template)}${opt('wizard_progress', tx('rs.wizard_progress_numbered_steps'), cfg.template)}</select></div>
|
|
839
841
|
</div></fieldset>`;
|
|
840
842
|
break;
|
|
841
843
|
}
|
|
842
844
|
case 'map': {
|
|
843
|
-
title = '
|
|
845
|
+
title = tx('rs.t.map');
|
|
844
846
|
const allPages = await pages();
|
|
845
847
|
body = html`${columnsHint(await reportColumns(appId, await designSql(appId, r)))}
|
|
846
|
-
<p class="muted u-mt0"
|
|
847
|
-
<fieldset class="prop-group"><legend
|
|
848
|
-
<div class="field"><label class="label" for="${id('layer')}"
|
|
849
|
-
<select id="${id('layer')}" name="layer">${opt('', '
|
|
848
|
+
<p class="muted u-mt0">${tx.code('rs.map_help')}</p>
|
|
849
|
+
<fieldset class="prop-group"><legend>${tx('rs.appearance')}</legend><div class="form-grid">
|
|
850
|
+
<div class="field"><label class="label" for="${id('layer')}">${tx('rs.show_places_as')}</label>
|
|
851
|
+
<select id="${id('layer')}" name="layer">${opt('', tx('rs.markers'), cfg.layer)}${opt('heat', tx('rs.heat_map'), cfg.layer)}${opt('lines', tx('rs.lines'), cfg.layer)}${opt('polygons', tx('rs.polygons'), cfg.layer)}${opt('extruded', tx('rs.extruded_polygons_shaded_by_height'), cfg.layer)}</select></div>
|
|
850
852
|
${styleFields(id, 'style', cfg.style)}
|
|
851
|
-
<div class="field"><label class="label" for="${id('name')}"
|
|
853
|
+
<div class="field"><label class="label" for="${id('name')}">${tx('rs.layer_name_legend')}</label>
|
|
852
854
|
<input id="${id('name')}" name="name" value="${cfg.name ?? ''}" maxlength="60" placeholder="the region title"></div>
|
|
853
|
-
<div class="field"><label class="label" for="${id('height')}"
|
|
854
|
-
<select id="${id('height')}" name="height">${opt('small', '
|
|
855
|
-
<div class="field"><label class="label" for="${id('zoom')}"
|
|
855
|
+
<div class="field"><label class="label" for="${id('height')}">${tx('rs.height')}</label>
|
|
856
|
+
<select id="${id('height')}" name="height">${opt('small', tx('rs.small'), cfg.height)}${opt('', tx('rs.medium'), cfg.height)}${opt('large', tx('rs.large'), cfg.height)}</select></div>
|
|
857
|
+
<div class="field"><label class="label" for="${id('zoom')}">${tx('rs.zoom_for_a_single_place')}</label>
|
|
856
858
|
<input id="${id('zoom')}" name="zoom" type="number" min="1" max="19" value="${cfg.zoom ?? ''}" placeholder="14"></div>
|
|
857
859
|
${loadField(id, 'load', cfg)}
|
|
858
|
-
${check('cluster', '
|
|
860
|
+
${check('cluster', tx('rs.group_markers_that_are_close'), cfg.cluster === true)}
|
|
859
861
|
${emptyField(id, cfg)}
|
|
860
862
|
</div></fieldset>
|
|
861
|
-
${linkFieldset(id, cfg.link, allPages, '
|
|
863
|
+
${linkFieldset(id, cfg.link, allPages, tx('rs.w.place'))}
|
|
862
864
|
${await mapLayersFieldsets(cfg, appId, allPages, id)}
|
|
863
865
|
${await mapReportFieldset(r, pageId, appId, id)}`;
|
|
864
866
|
break;
|
|
865
867
|
}
|
|
866
868
|
case 'tree':
|
|
867
|
-
title = '
|
|
869
|
+
title = tx('rs.t.tree');
|
|
868
870
|
body = html`${columnsHint(await reportColumns(appId, await designSql(appId, r)), ['id', 'parent_id', 'label'])}
|
|
869
|
-
<p class="muted u-mt0"
|
|
870
|
-
<fieldset class="prop-group"><legend
|
|
871
|
-
<div class="field"><label class="label" for="${id('expanded')}"
|
|
871
|
+
<p class="muted u-mt0">${tx.code('rs.tree_help')}</p>
|
|
872
|
+
<fieldset class="prop-group"><legend>${tx('rs.appearance')}</legend><div class="form-grid">
|
|
873
|
+
<div class="field"><label class="label" for="${id('expanded')}">${tx('rs.levels_open_at_first')}</label>
|
|
872
874
|
<input id="${id('expanded')}" name="expanded" type="number" min="0" max="20" value="${cfg.expanded ?? 1}"></div>
|
|
873
875
|
${emptyField(id, cfg)}
|
|
874
876
|
</div></fieldset>
|
|
875
|
-
${linkFieldset(id, cfg.link, await pages(), '
|
|
877
|
+
${linkFieldset(id, cfg.link, await pages(), tx('rs.w.node'))}`;
|
|
876
878
|
break;
|
|
877
879
|
case 'workflows':
|
|
878
|
-
title = '
|
|
879
|
-
body = html`<p class="muted u-mt0"
|
|
880
|
-
<fieldset class="prop-group"><legend
|
|
881
|
-
<div class="field"><label class="label" for="${id('context')}"
|
|
882
|
-
<select id="${id('context')}" name="context">${opt('', '
|
|
880
|
+
title = tx('rs.t.workflows');
|
|
881
|
+
body = html`<p class="muted u-mt0">${tx.code('rs.workflow_help')}</p>
|
|
882
|
+
<fieldset class="prop-group"><legend>${tx('rs.workflows')}</legend><div class="form-grid">
|
|
883
|
+
<div class="field"><label class="label" for="${id('context')}">${tx('rs.show')}</label>
|
|
884
|
+
<select id="${id('context')}" name="context">${opt('', tx('rs.workflows_i_started'), cfg.context)}${opt('admin', tx('rs.workflows_i_administer'), cfg.context)}</select></div>
|
|
883
885
|
${emptyField(id, cfg)}
|
|
884
886
|
</div>
|
|
885
|
-
${check('completed', '
|
|
887
|
+
${check('completed', tx('rs.include_completed_and_terminated_workflows'), cfg.completed === true)}</fieldset>`;
|
|
886
888
|
break;
|
|
887
889
|
case 'tasks':
|
|
888
|
-
title = '
|
|
889
|
-
body = html`<p class="muted u-mt0"
|
|
890
|
-
<fieldset class="prop-group"><legend
|
|
891
|
-
<div class="field"><label class="label" for="${id('context')}"
|
|
892
|
-
<select id="${id('context')}" name="context">${opt('', '
|
|
890
|
+
title = tx('rs.t.tasks');
|
|
891
|
+
body = html`<p class="muted u-mt0">${tx.code('rs.task_help')}</p>
|
|
892
|
+
<fieldset class="prop-group"><legend>${tx('rs.tasks')}</legend><div class="form-grid">
|
|
893
|
+
<div class="field"><label class="label" for="${id('context')}">${tx('rs.show')}</label>
|
|
894
|
+
<select id="${id('context')}" name="context">${opt('', tx('rs.my_tasks_to_act_on'), cfg.context)}${opt('initiated', tx('rs.tasks_i_requested'), cfg.context)}${opt('admin', tx('rs.tasks_i_administer'), cfg.context)}</select></div>
|
|
893
895
|
${emptyField(id, cfg)}
|
|
894
896
|
</div>
|
|
895
|
-
${check('completed', '
|
|
897
|
+
${check('completed', tx('rs.include_completed_and_cancelled_tasks'), cfg.completed === true)}</fieldset>`;
|
|
896
898
|
break;
|
|
897
899
|
}
|
|
898
900
|
return html`<h3 class="u-mt15">${title}</h3>
|
|
899
|
-
<p class="muted u-mt0"
|
|
901
|
+
<p class="muted u-mt0">${tx('rs.these_fields_write_the_region')}</p>
|
|
900
902
|
<form method="post" action="${BASE}/pages/${pageId}/region/${r.id}/settings" class="component-form">${csrf(s)}
|
|
901
903
|
${body!}
|
|
902
904
|
<div class="buttons"><button class="btn btn-hot">Save ${title.toLowerCase()}</button></div>
|
|
@@ -948,7 +950,7 @@ export async function regionSettingsRoutes(app: FastifyInstance) {
|
|
|
948
950
|
[pid, r.id, JSON.stringify(flags)],
|
|
949
951
|
);
|
|
950
952
|
}
|
|
951
|
-
flash(s, '
|
|
953
|
+
flash(s, tr(s)('rs.saved'));
|
|
952
954
|
return back(reply, s, `${BASE}/pages/${pid}?c=region-${rid}`);
|
|
953
955
|
});
|
|
954
956
|
}
|