pgkiln 0.44.0 → 0.46.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/102_app_computations.sql +345 -0
- package/db/migrations/103_shortcuts.sql +341 -0
- package/db/migrations/104_report_queries.sql +413 -0
- package/db/migrations/105_component_settings_groups.sql +367 -0
- package/db/migrations/106_form_designer.sql +102 -0
- package/db/migrations/107_app_version.sql +9 -0
- package/docs/apex-feature-parity.md +15 -12
- package/docs/guide/02-concepts.md +24 -0
- package/docs/guide/03-builder.md +69 -13
- package/docs/guide/04-pages-and-regions.md +84 -3
- package/docs/guide/05-items.md +14 -2
- package/docs/guide/06-processing.md +22 -0
- package/docs/guide/09-reference.md +14 -1
- package/docs/guide/11-from-apex.md +2 -1
- package/docs/guide/12-development.md +8 -2
- package/docs/guide/16-files.md +23 -0
- package/docs/guide/18-cli.md +4 -0
- package/examples/hr/hr_61_form_designer.sql +80 -0
- package/package.json +1 -1
- package/public/app.css +79 -2
- package/public/app.js +307 -35
- package/public/builder.css +21 -1
- package/public/builder.js +67 -0
- package/public/sw.js +2 -1
- package/src/app.ts +9 -1
- package/src/appfiles.ts +18 -2
- package/src/builder/advisor.ts +7 -2
- package/src/builder/appwizard.ts +22 -6
- package/src/builder/arrange.ts +33 -12
- package/src/builder/code-editor.ts +7 -2
- package/src/builder/components.ts +86 -4
- package/src/builder/designer.ts +44 -17
- package/src/builder/errors.ts +65 -0
- package/src/builder/form-designer.ts +229 -0
- package/src/builder/forms.ts +1 -1
- package/src/builder/locks.ts +6 -0
- package/src/builder/newapp.ts +2 -1
- package/src/builder/object-browser.ts +81 -18
- package/src/builder/region-help.ts +2 -0
- package/src/builder/region-settings.ts +2 -0
- package/src/builder/reporter.ts +1 -1
- package/src/builder/routes.ts +7 -5
- package/src/builder/search.ts +3 -3
- package/src/builder/shared.ts +41 -8
- package/src/builder/subscriptions.ts +30 -4
- package/src/builder/ui.ts +2 -0
- package/src/cli/replace.ts +16 -2
- package/src/i18n/ar.ts +62 -0
- package/src/i18n/cs.ts +62 -0
- package/src/i18n/da.ts +62 -0
- package/src/i18n/de.ts +62 -0
- package/src/i18n/el.ts +62 -0
- package/src/i18n/es.ts +62 -0
- package/src/i18n/fi.ts +62 -0
- package/src/i18n/fr.ts +62 -0
- package/src/i18n/he.ts +62 -0
- package/src/i18n/it.ts +62 -0
- package/src/i18n/ja.ts +62 -0
- package/src/i18n/ko.ts +62 -0
- package/src/i18n/nb.ts +62 -0
- package/src/i18n/pl.ts +62 -0
- package/src/i18n/pt.ts +62 -0
- package/src/i18n/ru.ts +62 -0
- package/src/i18n/sv.ts +62 -0
- package/src/i18n/tr.ts +62 -0
- package/src/i18n/uk.ts +62 -0
- package/src/i18n/zh.ts +62 -0
- package/src/i18n.ts +124 -0
- package/src/metadata.ts +18 -3
- package/src/runtime/component-settings.ts +42 -0
- package/src/runtime/context.ts +4 -0
- package/src/runtime/devbar.ts +1 -1
- package/src/runtime/documents.ts +29 -3
- package/src/runtime/engine.ts +2 -2
- package/src/runtime/facet-state.ts +10 -5
- package/src/runtime/facets.ts +29 -5
- package/src/runtime/form-designer.ts +676 -0
- package/src/runtime/grid.ts +36 -11
- package/src/runtime/items.ts +47 -32
- package/src/runtime/lists.ts +1 -1
- package/src/runtime/logic.ts +12 -7
- package/src/runtime/pdf.ts +2 -1
- package/src/runtime/regions.ts +8 -3
- package/src/runtime/render.ts +8 -3
- package/src/runtime/report.ts +4 -3
- package/src/runtime/routes.ts +7 -3
- package/src/runtime/shortcuts.ts +43 -0
- package/src/runtime/smart-filters.ts +2 -1
- package/src/runtime/tasks.ts +6 -3
- package/src/security.ts +2 -1
- package/src/subscriptions.ts +67 -0
package/src/runtime/facets.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { savepoint } from '../db.ts';
|
|
|
4
4
|
import { html, raw, type Raw } from '../html.ts';
|
|
5
5
|
import type { Region } from '../metadata.ts';
|
|
6
6
|
import { bindValues, publicError, stripSemicolon, toState, type PageContext } from './context.ts';
|
|
7
|
-
import { heading } from './items.ts';
|
|
7
|
+
import { appDateMask, appDateReader, dateField, heading } from './items.ts';
|
|
8
8
|
import { fieldsOf, regionUrl } from './report.ts';
|
|
9
9
|
import { renderChartBody } from './charts.ts';
|
|
10
10
|
import {
|
|
@@ -54,7 +54,7 @@ const col = (name: string) => `"__q".${pg.escapeIdentifier(name)}`;
|
|
|
54
54
|
|
|
55
55
|
/** The search and every other facet's filter, as conditions (their values in p). */
|
|
56
56
|
function othersWhere(ctx: PageContext, fs: FacetSource, except: string, p: SqlParams, withSearch = true) {
|
|
57
|
-
const where = facetWhere(ctx.params, fs.report.id, fs.all, fs.cols, p, except);
|
|
57
|
+
const where = facetWhere(ctx.params, fs.report.id, fs.all, fs.cols, p, except, appDateReader(ctx));
|
|
58
58
|
if (withSearch && fs.search) where.unshift(searchSql(fs.search, p, hidesColumns(fs.report) ? userColumnNames(fs.report, [...fs.cols.keys()]) : undefined));
|
|
59
59
|
return where;
|
|
60
60
|
}
|
|
@@ -99,6 +99,9 @@ export async function rangeCounts(ctx: PageContext, fs: FacetSource, f: FacetDef
|
|
|
99
99
|
return res.rows[0] as number[];
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/** (sprint 84) The URL parameter of a checkbox facet's order: "name", else by count. Not a filter: Apply and Reset keep it. */
|
|
103
|
+
export const facetSortKey = (reportId: number, column: string) => `r${reportId}_fo_${column}`;
|
|
104
|
+
|
|
102
105
|
/** (sprint 78) Checkbox facet values shown before "Show all" (APEX shows seven). */
|
|
103
106
|
export const FACET_SHOWN = 7;
|
|
104
107
|
|
|
@@ -141,13 +144,28 @@ export async function renderFacets(ctx: PageContext, r: Region): Promise<Raw> {
|
|
|
141
144
|
const k = facetKeys(report.id);
|
|
142
145
|
const formId = `ff${r.id}`;
|
|
143
146
|
const defs = facetDefs(r.config).filter((f) => fs.cols.has(f.column));
|
|
144
|
-
const active = new Set(facetFilters(ctx.params, report.id, fs.all).map((f) => f.column));
|
|
147
|
+
const active = new Set(facetFilters(ctx.params, report.id, fs.all, undefined, appDateReader(ctx)).map((f) => f.column));
|
|
145
148
|
const groups: Raw[] = [];
|
|
146
149
|
const clearLink = (f: FacetDef) =>
|
|
147
150
|
active.has(f.column)
|
|
148
151
|
? html` <a class="facet-clear" href="${regionUrl(ctx, report, (p) => { for (const n of facetParamNames(report.id, f.column)) p.delete(n); p.delete(`r${report.id}_p`); })}" aria-label="${t('facets.clear_one', { facet: facetLabel(ctx, f) })}">${t('facets.clear_short')}</a>`
|
|
149
152
|
: '';
|
|
150
153
|
|
|
154
|
+
// (sprint 84; APEX: a facet's ⋮ menu) Clear, and the values sorted by count or by name; links, so it works without JavaScript
|
|
155
|
+
const facetMenu = (f: FacetDef, label: string, byName: boolean) => {
|
|
156
|
+
const sortHref = (by: 'count' | 'name') => regionUrl(ctx, report, (p) => {
|
|
157
|
+
if (by === 'name') p.set(facetSortKey(report.id, f.column), 'name');
|
|
158
|
+
else p.delete(facetSortKey(report.id, f.column));
|
|
159
|
+
});
|
|
160
|
+
const current = (on: boolean) => (on ? raw(' aria-current="true"') : '');
|
|
161
|
+
return html`<details class="menu facet-menu"><summary aria-label="${t('facets.menu', { facet: label })}"><span aria-hidden="true">⋮</span></summary>
|
|
162
|
+
<div class="menu-panel"><div class="menu-section menu-links">
|
|
163
|
+
${active.has(f.column) ? html`<a href="${regionUrl(ctx, report, (p) => { for (const n of facetParamNames(report.id, f.column)) p.delete(n); p.delete(`r${report.id}_p`); })}">${t('facets.clear_short')}</a>` : ''}
|
|
164
|
+
<a href="${sortHref('count')}"${current(!byName)}>${t('facets.sort_count')}</a>
|
|
165
|
+
<a href="${sortHref('name')}"${current(byName)}>${t('facets.sort_name')}</a>
|
|
166
|
+
</div></div></details>`;
|
|
167
|
+
};
|
|
168
|
+
|
|
151
169
|
// the search facet: the report's own search, in the facet panel
|
|
152
170
|
if (r.config.search === true) {
|
|
153
171
|
groups.push(html`<div class="facet facet-search">
|
|
@@ -169,10 +187,13 @@ export async function renderFacets(ctx: PageContext, r: Region): Promise<Raw> {
|
|
|
169
187
|
const selected = new Set(ctx.params.getAll(k.values + f.column));
|
|
170
188
|
// keep selected values visible even when their count dropped to 0
|
|
171
189
|
for (const s of selected) if (s && !values.some((x) => x.v === s)) values.push({ v: s, n: 0 });
|
|
190
|
+
// (sprint 84; APEX: the facet's menu) the values by count (the query's order) or by name
|
|
191
|
+
const byName = ctx.params.get(facetSortKey(report.id, f.column)) === 'name';
|
|
172
192
|
const excluded = f.exclude && ctx.params.get(k.exclude + f.column) === '1';
|
|
173
193
|
// (sprint 78) a boolean's values as Yes and No, not true and false
|
|
174
194
|
const bool = fs.cols.get(f.column) === 16;
|
|
175
195
|
const shown = (v: string) => (bool && (v === 'true' || v === 'false') ? t(v === 'true' ? 'item.yes' : 'item.no') : (toState(v) ?? ''));
|
|
196
|
+
if (byName) values.sort((a, b) => shown(a.v).localeCompare(shown(b.v), ctx.locale.lang, { numeric: true, sensitivity: 'base' }));
|
|
176
197
|
const chart = facetChart(ctx, report, f, label, values.map((x) => ({
|
|
177
198
|
label: shown(x.v), n: x.n,
|
|
178
199
|
apply: (p) => { p.delete(k.values + f.column); p.delete(k.exclude + f.column); p.append(k.values + f.column, x.v); },
|
|
@@ -181,7 +202,7 @@ export async function renderFacets(ctx: PageContext, r: Region): Promise<Raw> {
|
|
|
181
202
|
html`<label class="check facet-value"><input type="checkbox" form="${formId}" name="${k.values}${f.column}" value="${x.v}"${selected.has(x.v) ? raw(' checked') : ''} data-facet>
|
|
182
203
|
<span class="facet-label">${shown(x.v)}</span><span class="facet-count">${x.n}</span></label>`;
|
|
183
204
|
groups.push(html`<fieldset class="facet${excluded ? ' facet-excluded' : ''}">
|
|
184
|
-
<legend>${label}${clearLink(f)}</legend
|
|
205
|
+
<legend>${label}${clearLink(f)}</legend>${facetMenu(f, label, byName)}
|
|
185
206
|
${chart}
|
|
186
207
|
${values.slice(0, FACET_SHOWN).map(value)}
|
|
187
208
|
${values.length > FACET_SHOWN
|
|
@@ -219,7 +240,10 @@ export async function renderFacets(ctx: PageContext, r: Region): Promise<Raw> {
|
|
|
219
240
|
const fid = `${formId}_${f.column.replace(/[^A-Za-z0-9_]/g, '_')}`;
|
|
220
241
|
const input = (which: 'from' | 'to', value: string) =>
|
|
221
242
|
html`<label class="sr-only" for="${fid}_${which}">${label}: ${t(`facets.${which}`)}</label>
|
|
222
|
-
|
|
243
|
+
${kind === 'date'
|
|
244
|
+
// (sprint 84) a date in the application's date format, with pgkiln's date picker
|
|
245
|
+
? dateField(ctx, { id: `${fid}_${which}`, iso: isCustom ? value : '', mask: appDateMask(ctx), attrs: html` form="${formId}" name="${which === 'from' ? k.from : k.to}${f.column}"` })
|
|
246
|
+
: html`<input id="${fid}_${which}" type="number" step="any" form="${formId}" name="${which === 'from' ? k.from : k.to}${f.column}" value="${isCustom ? value : ''}" placeholder="${t(`facets.${which}`)}">`}`;
|
|
223
247
|
const rangeChart = facetChart(ctx, report, f, label, f.ranges.map((x, i) => ({
|
|
224
248
|
label: f.type === 'star' ? ctx.locale.t(Number(x.from) === 1 ? 'facets.star_up' : 'facets.stars_up', { n: String(x.from) }) : rangeLabel(x, t),
|
|
225
249
|
n: counts![i],
|