pgkiln 0.72.0 → 0.74.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/140_item_region_types.sql +27 -0
- package/db/migrations/141_workspace_resources.sql +115 -0
- package/docs/apex-feature-parity.md +91 -12
- package/docs/guide/01-installation.md +1 -0
- package/docs/guide/03-builder.md +47 -4
- package/docs/guide/04-pages-and-regions.md +41 -1
- package/docs/guide/05-items.md +43 -3
- package/docs/guide/11-from-apex.md +3 -0
- package/docs/guide/12-development.md +25 -3
- package/docs/guide/16-files.md +29 -0
- package/docs/guide/19-rest-data-sources.md +41 -3
- package/examples/hr/hr_75_item_region_types.sql +81 -0
- package/examples/hr/hr_76_remote_servers.sql +48 -0
- package/package.json +1 -1
- package/public/app.css +48 -0
- package/public/app.js +377 -1
- package/src/ai/service.ts +10 -1
- package/src/app.ts +2 -0
- package/src/builder/advisor.ts +18 -2
- package/src/builder/ai.ts +29 -3
- package/src/builder/arrange.ts +7 -0
- package/src/builder/components.ts +5 -5
- package/src/builder/designer.ts +9 -3
- package/src/builder/home.ts +3 -0
- package/src/builder/i18n/de.ts +6 -0
- package/src/builder/i18n/en.ts +6 -0
- package/src/builder/i18n/es.ts +6 -0
- package/src/builder/i18n/fr.ts +6 -0
- package/src/builder/i18n/it.ts +6 -0
- package/src/builder/i18n/ja.ts +6 -0
- package/src/builder/i18n/ko.ts +6 -0
- package/src/builder/i18n/nl.ts +6 -0
- package/src/builder/i18n/pt.ts +6 -0
- package/src/builder/i18n/zh-cn.ts +6 -0
- package/src/builder/i18n/zh-tw.ts +6 -0
- package/src/builder/network-access.ts +33 -0
- package/src/builder/region-help.ts +8 -2
- package/src/builder/rest-catalogs.ts +22 -14
- package/src/builder/routes.ts +5 -1
- package/src/builder/static-files.ts +14 -4
- package/src/builder/websources.ts +12 -6
- package/src/builder/workflows.ts +2 -1
- package/src/builder/workspace-resources.ts +528 -0
- package/src/i18n/ar.ts +13 -0
- package/src/i18n/cs.ts +13 -0
- package/src/i18n/da.ts +13 -0
- package/src/i18n/de.ts +13 -0
- package/src/i18n/el.ts +13 -0
- package/src/i18n/es.ts +13 -0
- package/src/i18n/fi.ts +13 -0
- package/src/i18n/fr.ts +13 -0
- package/src/i18n/he.ts +13 -0
- package/src/i18n/it.ts +13 -0
- package/src/i18n/ja.ts +13 -0
- package/src/i18n/ko.ts +13 -0
- package/src/i18n/nb.ts +13 -0
- package/src/i18n/pl.ts +13 -0
- package/src/i18n/pt.ts +13 -0
- package/src/i18n/ru.ts +13 -0
- package/src/i18n/sv.ts +13 -0
- package/src/i18n/tr.ts +13 -0
- package/src/i18n/uk.ts +13 -0
- package/src/i18n/zh.ts +13 -0
- package/src/i18n.ts +26 -0
- package/src/metadata.ts +22 -5
- package/src/remote-servers.ts +133 -0
- package/src/restcatalog.ts +21 -10
- package/src/restsync.ts +1 -1
- package/src/runtime/context.ts +2 -0
- package/src/runtime/items.ts +32 -3
- package/src/runtime/page-regions.ts +50 -0
- package/src/runtime/region-cache.ts +1 -1
- package/src/runtime/regions.ts +11 -0
- package/src/runtime/render.ts +3 -17
- package/src/runtime/report.ts +55 -3
- package/src/runtime/static-files.ts +46 -17
- package/src/runtime/template-components.ts +2 -1
- package/src/websources.ts +43 -18
- package/src/workspace-export.ts +80 -1
package/docs/guide/05-items.md
CHANGED
|
@@ -23,6 +23,9 @@ page, optionally inside a region. Name them `P<page>_<NAME>` (uppercase letters,
|
|
|
23
23
|
| `switch` | on/off switch | `true` / `false` |
|
|
24
24
|
| `checkbox_group` | several checkboxes | **colon-separated** return values, e.g. `10:30`; (137) `{"switches": true}` shows each as a switch, one per line |
|
|
25
25
|
| `multiselect` | multi-select list | **colon-separated** return values |
|
|
26
|
+
| `shuttle` | (140) two lists, *Available* and *Selected*, with buttons to move values across and put the chosen ones in order ([Shuttle](#shuttle-select-one-and-select-many)); a multi-select list without JavaScript | **colon-separated** return values, in the chosen order |
|
|
27
|
+
| `select_one` | (140) a search field over the list of values: typing filters a list under it ([Select One](#shuttle-select-one-and-select-many)); a select list without JavaScript | the chosen return value |
|
|
28
|
+
| `select_many` | (140) as `select_one`, for several values shown as chips with a remove button; a multi-select list without JavaScript | **colon-separated** return values |
|
|
26
29
|
| `display` | read-only text (shows the display value for list items) | whatever was set |
|
|
27
30
|
| `hidden` | not rendered | set by URL (with checksum), fetch or processes |
|
|
28
31
|
| `file` | file upload ([chapter 16](16-files.md)); on phones the camera with `{"capture": "environment"}`, photos made smaller with `{"max_px": 1600}` ([chapter 17](17-mobile.md)) | the id of the uploaded temporary file (a uuid) |
|
|
@@ -191,7 +194,8 @@ pages and applications without sign-in keep nothing.
|
|
|
191
194
|
## Lists of values
|
|
192
195
|
|
|
193
196
|
A list of values (LOV) provides the options of select lists, radio groups, checkbox groups,
|
|
194
|
-
multi-selects, popup LOVs, grid columns and display
|
|
197
|
+
multi-selects, shuttles, Select One and Select Many items, popup LOVs, grid columns and display
|
|
198
|
+
items. Three forms:
|
|
195
199
|
|
|
196
200
|
1. **A SELECT returning (display, return)**. Bind variables are allowed:
|
|
197
201
|
```sql
|
|
@@ -205,8 +209,9 @@ multi-selects, popup LOVs, grid columns and display items. Three forms:
|
|
|
205
209
|
list can also read a web service: give it a **REST data source** and query the rows from
|
|
206
210
|
`rest` ([chapter 19](19-rest-data-sources.md#lists-of-values)).
|
|
207
211
|
|
|
208
|
-
On submit, the value of a select list, radio group, checkbox group, multi-select
|
|
209
|
-
value of a multi-value item), and of a grid column with a list
|
|
212
|
+
On submit, the value of a select list, radio group, checkbox group, multi-select, shuttle, Select
|
|
213
|
+
One, Select Many or popup LOV (each value of a multi-value item), and of a grid column with a list
|
|
214
|
+
of values, must be one its list
|
|
210
215
|
returns at that moment, for that user: a list that leaves out departments a user may not pick also
|
|
211
216
|
keeps a crafted request from choosing them. Otherwise the item gets *"choose a value from the
|
|
212
217
|
list"*. For a list that may legitimately miss the current value, set `"any_value": true` in the
|
|
@@ -241,6 +246,41 @@ new search starts over at the first page. A browser without `IntersectionObserve
|
|
|
241
246
|
The HR example's page 26 (*Pick an employee*) has one with name, job and department columns;
|
|
242
247
|
page 47 (*Component details*) one over 6,000 rows, 100 a page.
|
|
243
248
|
|
|
249
|
+
## Shuttle, Select One and Select Many
|
|
250
|
+
|
|
251
|
+
(sprint 140; APEX: Shuttle, and 24.2's Select One and Select Many) Three list items for choosing from
|
|
252
|
+
a list of values, each a plain select list without JavaScript (the page saves the same values):
|
|
253
|
+
|
|
254
|
+
- **Shuttle** (`shuttle`): two lists side by side, *Available* and *Selected* (stacked on phones),
|
|
255
|
+
with a filter field over the available values. Move values across with the buttons between the
|
|
256
|
+
lists (*Move all* », *Move* ›, *Remove* ‹, *Remove all* «), by double-clicking one, or with the
|
|
257
|
+
keyboard: **Enter** moves the marked values across, **Delete** removes them from *Selected*, and in
|
|
258
|
+
*Selected* **Alt+↑ / Alt+↓** move them up or down (with **Shift**: to the top or bottom; the buttons
|
|
259
|
+
⤒ ↑ ↓ ⤓ do the same). The value is the chosen return values **in their order**, colon-separated
|
|
260
|
+
(`7566:7902`), so `string_to_array(:P50_MEMBERS, ':') with ordinality` keeps the order. Without
|
|
261
|
+
JavaScript the shuttle is a multi-select list (the chosen values first, in their order); a value
|
|
262
|
+
posted from it comes in the list's order.
|
|
263
|
+
- **Select One** (`select_one`): a search field. Clicking it or typing opens a list box under it
|
|
264
|
+
that shows the list's display values containing what was typed (case-insensitive); **↑ / ↓**
|
|
265
|
+
move through it, **Enter** chooses, **Escape** closes. Emptying the field chooses no value.
|
|
266
|
+
`{"null_label": "- None -"}` names the empty entry, `false` leaves it out (a required item).
|
|
267
|
+
- **Select Many** (`select_many`): the same, for several values: the chosen ones are chips above the
|
|
268
|
+
field, each with a remove button (named *Remove …* for screen readers); **Backspace** in the
|
|
269
|
+
empty field removes the last one, Enter on a chosen value in the list removes it again. The
|
|
270
|
+
value is colon-separated, like a multi-select.
|
|
271
|
+
|
|
272
|
+
Like the other list items they are checked against their list of values on submit, take
|
|
273
|
+
`cascade_parents` (a cascading shuttle is reloaded with its new values), show their display values
|
|
274
|
+
(comma-separated, in order) when read-only, and dynamic actions see the colon-separated value
|
|
275
|
+
(*Set value* with `A:B` chooses those values). The list box searches the rows the page read
|
|
276
|
+
(`max_rows`, 5,000 by default): for lists of tens of thousands of rows use a [Popup LOV](#popup-lov),
|
|
277
|
+
which searches on the server. Texts such as *Move all*, *Selected* and *Remove {value}* are runtime
|
|
278
|
+
messages (`shuttle.*`, `lov.search`, `item.tag_remove`) an application can replace with a text
|
|
279
|
+
message of the same name.
|
|
280
|
+
|
|
281
|
+
HR page 50 (*Project team*, Administration) has a Select One of departments, a shuttle of that
|
|
282
|
+
department's employees (cascading) and a Select Many of skills.
|
|
283
|
+
|
|
244
284
|
## Cascading lists of values
|
|
245
285
|
|
|
246
286
|
A list that depends on another item, for example managers filtered by department:
|
|
@@ -59,6 +59,9 @@ What's missing is listed in the [feature parity matrix](../apex-feature-parity.m
|
|
|
59
59
|
| `APEX_DATA_EXPORT.EXPORT` | `meta.data_export(query, format, options)` (CSV, JSON, XML, HTML) and the download process's `{"export": "xlsx"}` (also PDF) ([chapter 6](06-processing.md#data-export-from-sql)) |
|
|
60
60
|
| `APEX_LANG.LANG`, dynamic translations | Shared Components → Dynamic translations and `meta.lang(text)` ([chapter 14](14-globalization.md#dynamic-translations)) |
|
|
61
61
|
| Display Image, Geocoded Address items | Item types `image` and `address` (geocoder: `GEOCODER_URL`) ([chapter 5](05-items.md#display-image-and-geocoded-address)) |
|
|
62
|
+
| Shuttle, Select One, Select Many (24.2) items | Item types `shuttle` (ordered, colon-separated), `select_one` and `select_many` (a search field over the list of values; chips): plain selects without JavaScript ([chapter 5](05-items.md#shuttle-select-one-and-select-many)) |
|
|
63
|
+
| Classic Report region (report templates, pagination types) | Region type `classic_report`: `report_template` `standard`, `value_pairs`, `badges` or `media_list`; `pagination` `total`, `range` or `none` ([chapter 4](04-pages-and-regions.md#classic_report-classic-report)) |
|
|
64
|
+
| Breadcrumb region (Breadcrumb Bar), Help Text region and page help | Region types `breadcrumb` (from each page's *Breadcrumb parent*; no separate breadcrumb shared component) and `help_text`; the page's *Help text* (Markdown) ([chapter 4](04-pages-and-regions.md#breadcrumb-and-help_text)) |
|
|
62
65
|
| Translated applications (XLIFF), `APEX_LANG.MESSAGE`, `&APP_TEXT$NAME.` | Translations in the app (XLIFF/CSV import and export), `meta.message()`, `&APP_TEXT$NAME.` ([chapter 14](14-globalization.md)) |
|
|
63
66
|
| Application date format mask | Settings → Globalization → Date format (Oracle-style masks) |
|
|
64
67
|
| Number format masks (`FML999G999G990D00`) on columns and items | `{"formats": {...}}` on report, grid and cards columns, `{"format_mask": "..."}` on charts and number/display items ([chapter 14](14-globalization.md#number-formats)) |
|
|
@@ -37,7 +37,8 @@ src/
|
|
|
37
37
|
restcatalog.ts (095) REST source catalogs: checks, fromOpenApi (OpenAPI 3 / Swagger 2 → services: parameters with "key",
|
|
38
38
|
rows and columns from the response schema, write-back from collection and item paths, notes), catalog
|
|
39
39
|
files, load/insert/replace, sourceFromService
|
|
40
|
-
workspace-export.ts (sprint 67) a workspace's components as a file (pgkiln-workspace/1) and importWorkspace (one transaction)
|
|
40
|
+
workspace-export.ts (sprint 67) a workspace's components as a file (pgkiln-workspace/1) and importWorkspace (one transaction);
|
|
41
|
+
(sprint 141) workspace_files, remote_servers (an existing server keeps its base URL), web_credentials (no secrets)
|
|
41
42
|
team.ts (sprint 113) Team Development's data: workspaceDevelopers (members and administrators: assignees, @mentions),
|
|
42
43
|
issueHtml (markdownHtml + mentions), recordMentions, logIssue (history), teamSetup, teamCounts, pageIssues,
|
|
43
44
|
exportTeam / importTeam (the workspace export's "team" part)
|
|
@@ -134,6 +135,10 @@ src/
|
|
|
134
135
|
network-acl.ts (sprint 118, WS-9) workspace network access lists (meta.workspace.network_*): scopeForApp/scopeForWorkspace
|
|
135
136
|
(no cache), parseHostList, recordRefusal/purgeRefusals (meta.network_refusal, 7 days), mailHostAddress
|
|
136
137
|
(a workspace mail service's SMTP host: both lists, resolved once, the checked address connected to)
|
|
138
|
+
remote-servers.ts (sprint 141, AP-7) remote servers (meta.remote_server): serverProblems, serverUrl (PGKILN_SERVER_* env var
|
|
139
|
+
wins over base_url), joinUrl, resolveSource (a REST source on a server: full URL + the server's default
|
|
140
|
+
credential; used by loadSource/fetchSource/callOperation), catalog and AI service URLs (owner pool,
|
|
141
|
+
the application's own workspace only)
|
|
137
142
|
datapump.ts (sprint 103, DA-16) Schema Export / Import jobs (meta.dump_job): findTools (pg_dump/pg_restore ≥ the
|
|
138
143
|
server's major), startExport (pg_dump, argument array, PGPASSWORD), readToc/parseToc (pg_restore --list),
|
|
139
144
|
startImport (pg_restore -f - run in one transaction between schema renames; data-only in FK order),
|
|
@@ -220,7 +225,12 @@ src/
|
|
|
220
225
|
processes.ts download (file or zip from a query, safe headers), workflow processes, configuration checks of chains
|
|
221
226
|
logic.ts computations (the page's and (102) the application's), branches (page, URL, function returning a URL, another application) and their conditions
|
|
222
227
|
shortcuts.ts (103) "NAME" shortcuts in static content, help texts and confirm messages
|
|
228
|
+
static-files.ts (068) static application files (/a/<alias>/static/<name>), the <script>/<link> tags of a page; (141) static
|
|
229
|
+
workspace files (/a/<alias>/workspace-files/<name>, the application's own workspace's only, owner pool);
|
|
230
|
+
#APP_FILES# / #WORKSPACE_FILES# are substituted in context.ts substitute and template-components.ts
|
|
223
231
|
render.ts page chrome (nav, breadcrumb), dynamic action JSON, theme (the page's nonce'd <style>, light/dark and style switches)
|
|
232
|
+
page-regions.ts (sprint 140) breadcrumb and help_text regions: breadcrumbLinks (also the title bar's trail, left out when the
|
|
233
|
+
page has a breadcrumb region), the page's help text (meta.page.help) as Markdown with escaped &ITEM. values
|
|
224
234
|
styles.ts base styles (BASE_STYLES: Iris, Lavender, Standard; baseStyleOf → html data-style), Theme Roller style variants: fixed lists (fonts, sizes, corners), parseStyle/appStyles checks, the request's
|
|
225
235
|
style (user choice, default), themeCss() (only hex values and constants reach the CSS); (sprint 117) NAV_APPEARANCES
|
|
226
236
|
(Pillar Light / Dark), navAppearance() → the <body> classes nav-pillar-*, the header strip (--strip)
|
|
@@ -228,7 +238,10 @@ src/
|
|
|
228
238
|
regions.ts region shell + chart (drill-down links, gauge settings)/cards/dynamic dispatch with row limits, lazy placeholder and cache, buttons (menu buttons, badges; (124) menu entries {action} → data-menu-entry for menu_entry dynamic actions)
|
|
229
239
|
report.ts, report-views.ts (group by, pivot, chart), compute.ts (computed column expressions), grid.ts (aggregates, row actions, Actions menu, single row view, layoutFromForm), grid-layout.ts (column layouts: clean, arrange, per user), master-detail.ts (signed master row selection, details), facets.ts (facetChart: a facet's counts as a chart, sprint 65), items.ts
|
|
230
240
|
(report.ts: paging with row ranges and max_rows, keyset paging (keysetPlan, seekCondition, signed r<id>_k), pagerNav, streamed CSV/Excel downloads with a cursor;
|
|
231
|
-
|
|
241
|
+
(140) classic_report through renderReport: reportState drops the interactive settings, classicTemplate (value_pairs, badges, media_list),
|
|
242
|
+
pagination total/range/none;
|
|
243
|
+
items.ts: lovOptions, searchLov/lovLookup for popup LOVs, served by POST /a/:alias/:page/lov/:item/search in routes.ts;
|
|
244
|
+
LOV_TYPES / LOV_CHECKED / MULTI_VALUE: the list item types (shuttle, select_one, select_many since 140) checked on submit and colon-separated)
|
|
232
245
|
region-cache.ts region caching (keys per scope, CSRF placeholder, invalidation on submit) and lazy regions (GET …/region/:id is in routes.ts)
|
|
233
246
|
gantt-move.ts (sprint 69) Gantt drag and drop: ganttEditable, targetTime, moveGanttTask (the task in the region's
|
|
234
247
|
query, gantt_move with :TASK_ID/:NEW_START/:NEW_END); the dragging itself is in app.js
|
|
@@ -375,6 +388,10 @@ src/
|
|
|
375
388
|
list (workspaceAdmin), Check a URL (nothing called), the workspaces (administrators), refused calls
|
|
376
389
|
workspace-mail.ts (sprint 118, WS-9) a workspace's own mail service (/builder/workspaces/:id/mail, WORKSPACE_<id>): its
|
|
377
390
|
administrators only, write-only secret, checks through mail.ts runCheck with the workspace's guard
|
|
391
|
+
workspace-resources.ts (sprint 141, AP-7) Workspace utilities → Static workspace files (/builder/workspace-files), Remote servers
|
|
392
|
+
(/builder/remote-servers: URL now, network verdict, used by; renaming moves sources/catalogs) and Web
|
|
393
|
+
credentials (/builder/workspace-credentials: write-only secrets): the current workspace, its developers see,
|
|
394
|
+
its administrators change
|
|
378
395
|
shared.ts Shared Components and access control
|
|
379
396
|
designer.ts page designer: component tree (with computations and branches), layout canvas and gallery, property editor, toolbar;
|
|
380
397
|
(sprint 86) GET /builder/apps/:app/pages/:no, /builder/pages/:id redirects browsers there (designerUrl in ui.ts)
|
|
@@ -519,7 +536,8 @@ public/
|
|
|
519
536
|
(sprint 117) navigation tree states, RTL via logical properties, body.nav-pillar-*, u-color-*-subtle, the busy bar)
|
|
520
537
|
app.js client runtime: dialogs (dialog_closed actions), popup LOVs (sprint 115: the next page loads on scroll through an
|
|
521
538
|
IntersectionObserver, ‹/More without one), dynamic actions (focus, classes, messages; sprint 115: wait_popup /
|
|
522
|
-
remove_wait_popup as a modal <dialog class="wait-popup">, pgkiln.waitPopup(), the menu_entry event),
|
|
539
|
+
remove_wait_popup as a modal <dialog class="wait-popup">, pgkiln.waitPopup(), the menu_entry event), (sprint 140) shuttle()
|
|
540
|
+
and selectPick() (Select One / Many: combobox + listbox, chips) over the item's hidden <select>, grids (add/duplicate rows, master-detail refresh, move/resize columns, copy/paste of cell ranges), menus, lazy regions, maps (Leaflet layers, marker clusters, heat layer, layer legend, area/distance filter) (no inline JS)
|
|
523
541
|
code-editor.js, .css builder code editor: enhances <textarea data-code>, highlighting, suggestions (no dependencies);
|
|
524
542
|
(sprint 108) the current line, and read-only <pre data-code-view="sql|plpgsql|json|html"> highlighted;
|
|
525
543
|
its colours are the --ce-* tokens in builder.css
|
|
@@ -545,6 +563,7 @@ test/
|
|
|
545
563
|
globalization.test.ts masks on HR page 29, time zones, the de/fr/es texts
|
|
546
564
|
files.test.ts file items: storage, limits, downloads, temporary files
|
|
547
565
|
items.test.ts rich text, Markdown, rating, combobox, date range, password reveal and QR code items
|
|
566
|
+
item-region-types.test.ts (sprint 140) shuttle, select one/many, classic report, breadcrumb and help text: gallery, runtime, export, Advisor, HR page 50
|
|
548
567
|
dataload.test.ts parsing, Load Data, the data_load process
|
|
549
568
|
sampledata.test.ts Sample Data: CHECK parsing, proposals, option errors, seeds and streams, preview/insert/rollback, parents first, downloads, saved generators
|
|
550
569
|
unload.test.ts Unload Data: CSV/JSON/XLSX/XML output, read back with Load Data, read-only and one-statement checks, streaming
|
|
@@ -622,6 +641,8 @@ test/
|
|
|
622
641
|
blueprint-from-app.test.ts a blueprint of HR: checks clean, pages and menu, notes, a working copy with sample rows, workspaces
|
|
623
642
|
rest-catalogs.test.ts OpenAPI/Swagger reading, parameter keys, catalog files, the builder's pages, a data source from a service
|
|
624
643
|
workspace-transfer.test.ts a workspace's export, import into another workspace (added, kept, replaced, all or nothing), groups, access
|
|
644
|
+
workspace-resources.test.ts (sprint 141) remote servers (checks, env var, HR's CRM moves its sources, credentials, renames, catalogs,
|
|
645
|
+
AI services, the Advisor), static workspace files styling two applications, workspace credentials, the export
|
|
625
646
|
tree-charts.test.ts treemap and sunburst (the hierarchy, cycles, squarify), timeline (lanes, rows, points and periods)
|
|
626
647
|
gantt-move.test.ts an editable Gantt's markup, the move route, dates, who may drag, the builder's settings
|
|
627
648
|
find-workspace.test.ts developers' addresses, builder mail, the workspaces mail, the same answer, limits
|
|
@@ -641,6 +662,7 @@ test/
|
|
|
641
662
|
e2e/worksheet.test.ts (sprint 91) the SQL Worksheet in a browser: run keys, tabs, navigator, drag and drop, splitters, phone, no JavaScript;
|
|
642
663
|
(107) transaction mode: badge, reload, Rollback, closing a tab, Autocommit switch, beforeunload
|
|
643
664
|
e2e/items.test.ts sprint 26 item types in a browser: editors, tags, stars, dates, reveal; without JavaScript
|
|
665
|
+
e2e/item-region-types.test.ts (sprint 140) the shuttle (buttons, keyboard, order) and Select One / Many in a browser; without JavaScript; 390 px
|
|
644
666
|
e2e/designer.test.ts page designer: panes per width, drag and drop, keyboard, Arrange buttons, builder theme
|
|
645
667
|
e2e/calendar.test.ts calendar drag and drop and create on click, view switching, chart drill-down
|
|
646
668
|
e2e/gantt.test.ts Gantt drag and drop: moving a task, its end, the keyboard, a click still drills down, the phone
|
package/docs/guide/16-files.md
CHANGED
|
@@ -21,6 +21,8 @@ This chapter covers three things that deal with files:
|
|
|
21
21
|
| Interactive report → Download → CSV / Excel / PDF | Actions → **Download CSV / Excel / PDF** |
|
|
22
22
|
| Shared Components → Report Layouts | Shared Components → **Report layouts** |
|
|
23
23
|
| Print (browser) | Actions → **Print**, and a print stylesheet on every page |
|
|
24
|
+
| Static Application Files (`#APP_FILES#`) | Shared Components → **Static application files** ([chapter 3](03-builder.md#static-application-files)) |
|
|
25
|
+
| Static Workspace Files (`#WORKSPACE_FILES#`) | Workspace utilities → **Static workspace files** ([below](#static-workspace-files)) |
|
|
24
26
|
|
|
25
27
|
## File upload items
|
|
26
28
|
|
|
@@ -206,6 +208,33 @@ address or the credential. Downloads are limited to `MAX_UPLOAD_MB`.
|
|
|
206
208
|
Large files in bytea columns are fine up to tens of megabytes. For bigger files or large numbers
|
|
207
209
|
of them, store them in object storage and keep the key in the table.
|
|
208
210
|
|
|
211
|
+
## Static workspace files
|
|
212
|
+
|
|
213
|
+
Files a developer ships with the applications, rather than files users upload: CSS, JavaScript, JSON, images, fonts
|
|
214
|
+
and PDFs. An application has its own (Shared Components → **Static application files**,
|
|
215
|
+
[chapter 3](03-builder.md#static-application-files)); since sprint 141 a **workspace** has them too (Workspace
|
|
216
|
+
utilities → **Static workspace files**; APEX: Static Workspace Files), shared by every application of the workspace,
|
|
217
|
+
for example one stylesheet with the company's colours.
|
|
218
|
+
|
|
219
|
+
| Write | Means |
|
|
220
|
+
|---|---|
|
|
221
|
+
| `#WORKSPACE_FILES#brand.css` in *Every page loads* (or a page's *JavaScript and CSS files*) | Every page (that page) loads the workspace's `brand.css` with `<link>` (a `.js` file with `<script src defer>`), after pgkiln's own, in list order with the application's own files |
|
|
222
|
+
| `#WORKSPACE_FILES#logo.png` in HTML, a template, a list entry, a link | `/a/<alias>/workspace-files/logo.png` |
|
|
223
|
+
| `#APP_FILES#guide.pdf` | `/a/<alias>/static/guide.pdf`, the application's own file |
|
|
224
|
+
|
|
225
|
+
Each application serves the workspace's files from its own address, `/a/<alias>/workspace-files/<name>`, to anyone
|
|
226
|
+
(don't put secrets in them): only its own workspace's files, never another workspace's. Pages link to
|
|
227
|
+
`…?v=<version>`, which browsers keep for a year; a change gives a new version.
|
|
228
|
+
|
|
229
|
+
The rules are those of static application files: names of letters, digits, `_`, `-` and `.` with an extension from a
|
|
230
|
+
fixed list; **no HTML** (the database refuses it too); up to 5 MB a file; `X-Content-Type-Options: nosniff`; an SVG or
|
|
231
|
+
PDF opened on its own is sandboxed, so a script in it never runs. The workspace's administrators upload, write, rename
|
|
232
|
+
and delete them; its developers see the list (with the applications and pages that load each file) and open a file in
|
|
233
|
+
a sandboxed preview. They travel with the [workspace export](03-builder.md#export-and-import), not with an
|
|
234
|
+
application's.
|
|
235
|
+
|
|
236
|
+
HR example (part 76): `brand.css` is a workspace file; HR loads it as `#WORKSPACE_FILES#brand.css`.
|
|
237
|
+
|
|
209
238
|
## Data loading
|
|
210
239
|
|
|
211
240
|
Both ways of loading accept:
|
|
@@ -84,6 +84,12 @@ Shared Components → **Web credentials** → + Add.
|
|
|
84
84
|
| OAuth2 password, Refresh token | `oauth2`: the password of the OAuth2 user (`password` grant), or a refresh token obtained elsewhere (`refresh_token` grant). Write-only like the secret, each with its own **Remove** button |
|
|
85
85
|
| Valid for URLs | URL prefixes the credential may be sent to, e.g. `https://api.example.com/v2/`. Recommended: a source or process that points elsewhere fails instead of sending the secret |
|
|
86
86
|
|
|
87
|
+
**Workspace web credentials** (sprint 141): Workspace utilities → **Web credentials** holds credentials with the same
|
|
88
|
+
properties for every application of the workspace ([chapter 3](03-builder.md#static-workspace-files-remote-servers-and-web-credentials)).
|
|
89
|
+
Wherever a credential is named (a source, the `invoke_api` process and step, `meta.web_request`, a remote server's
|
|
90
|
+
default credential, object storage), the application's own credential of that name is used, else the workspace's.
|
|
91
|
+
Another workspace's credentials are never found.
|
|
92
|
+
|
|
87
93
|
For `oauth2`, pgkiln asks the token endpoint for an access token, keeps it until 30 seconds before
|
|
88
94
|
it expires, renews it with the refresh token when the endpoint gave one, and asks for a new one when
|
|
89
95
|
the service answers 401. Access tokens are kept in the server's memory only.
|
|
@@ -118,9 +124,10 @@ Shared Components → **REST data sources** → + Add.
|
|
|
118
124
|
| Property | Meaning |
|
|
119
125
|
|---|---|
|
|
120
126
|
| Name | Uppercase, e.g. `COUNTRIES` |
|
|
121
|
-
|
|
|
127
|
+
| Remote server | Optional (sprint 141): a [remote server](#remote-servers) of the workspace, e.g. `CRM`; the URL is then the path after its base URL |
|
|
128
|
+
| URL | `https://api.example.com/v1/cities/{city}/weather`: `{name}` is a path parameter. The host is fixed: parameters can only come after it. With a remote server: `/v1/cities/{city}/weather` |
|
|
122
129
|
| Method | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |
|
|
123
|
-
| Web credential | The name of a web credential, or empty |
|
|
130
|
+
| Web credential | The name of a web credential (the application's, else the workspace's), or empty: on a remote server, the server's default credential |
|
|
124
131
|
| Parameters | JSON: `[{"name": "city", "in": "path", "default": "&P1_CITY.", "required": true}, {"name": "units", "in": "query", "default": "metric"}]`. `in` is `path`, `query`, `header` or `body`. Defaults may use `&ITEM.` substitutions. `key`: the service's own name for a query, header or body parameter when it isn't lower case with `_`, e.g. `{"name": "page_size", "in": "query", "key": "pageSize"}`; `Authorization`, `Cookie`, `Host` and the transport headers can't be header parameters |
|
|
125
132
|
| Headers | JSON: extra request headers, e.g. `{"Accept-Language": "en"}`. Not for secrets (use a web credential); `Authorization`, `Cookie`, `Host` and the transport headers can't be set |
|
|
126
133
|
| Body | For `POST`, `PUT`, `PATCH`: a JSON template, `{name}` becomes the parameter's value as a JSON string. Empty: the `body` parameters as a JSON object |
|
|
@@ -143,6 +150,30 @@ call; define them so reports keep their columns when the service changes.
|
|
|
143
150
|
Path parameters are URL-encoded (`.` and `..` are refused), query parameters appended, header
|
|
144
151
|
parameters sent as headers (one line only), so a value from an item can never change the host.
|
|
145
152
|
|
|
153
|
+
### Remote servers
|
|
154
|
+
|
|
155
|
+
A **remote server** (sprint 141; APEX: Remote Servers) is a named base URL of the workspace, kept under Workspace
|
|
156
|
+
utilities → **Remote servers** ([chapter 3](03-builder.md#static-workspace-files-remote-servers-and-web-credentials)):
|
|
157
|
+
a static id (`CRM`), this installation's base URL (`http://127.0.0.1:3100`) and/or an environment variable
|
|
158
|
+
`PGKILN_SERVER_CRM` that wins when set, and an optional default web credential. A source on a server keeps only its
|
|
159
|
+
path (`/a/hr/rest/v1/departments`); every call uses the server's URL **of that moment**, so changing the server (or
|
|
160
|
+
setting `PGKILN_SERVER_CRM=https://crm.example.com` on the production server) moves every source on it at once,
|
|
161
|
+
including write-back, synchronisation, the builder's *Test* and `invoke_api`.
|
|
162
|
+
|
|
163
|
+
- The resolved URL passes every check a full URL does: the allow-list, the workspace's
|
|
164
|
+
[network access list](03-builder.md#network-access) (whose page lists each server's host) and the address checks
|
|
165
|
+
when the connection is made. A server pointed at a private address is refused unless the installation allows it.
|
|
166
|
+
- The path is a path: it starts with `/` or `?` (or is empty), with no `//` or `..` segments, so it can't name another
|
|
167
|
+
host.
|
|
168
|
+
- A server that doesn't exist in the application's workspace fails the call with *Remote server X does not exist in
|
|
169
|
+
this application's workspace*. An application export names the server; it imports where the server is missing, and
|
|
170
|
+
the import message and the [Advisor](03-builder.md#advisor) (a warning) name it.
|
|
171
|
+
- A [REST source catalog](#rest-source-catalogs) can be on a server too, and so can an
|
|
172
|
+
[AI service](03-builder.md#ai-services).
|
|
173
|
+
|
|
174
|
+
HR example (part 76): server **CRM** stands for the HR API on `http://127.0.0.1:3100`; DEPARTMENTS, DEPARTMENT and
|
|
175
|
+
EMPLOYEES_API name it.
|
|
176
|
+
|
|
146
177
|
### Regions on a REST data source
|
|
147
178
|
|
|
148
179
|
A region's **REST data source** property (page designer → Source) makes it read the source's
|
|
@@ -262,6 +293,9 @@ A catalog comes from:
|
|
|
262
293
|
- a **catalog file** downloaded from another workspace or installation (*Download* on the catalog's page);
|
|
263
294
|
- **by hand**: a name and base URL, then *Add a service*.
|
|
264
295
|
|
|
296
|
+
A catalog may name a **remote server** of the workspace (the catalog's page, *Remote server*; sprint 141): its services
|
|
297
|
+
are then paths after the server's URL, and the base URL is optional.
|
|
298
|
+
|
|
265
299
|
Reading an OpenAPI document, pgkiln takes for each `GET`, `POST`, `PUT`, `PATCH` and `DELETE` operation:
|
|
266
300
|
|
|
267
301
|
| From the document | Becomes |
|
|
@@ -279,7 +313,8 @@ YAML documents: convert them to JSON first (most services offer `openapi.json` t
|
|
|
279
313
|
|
|
280
314
|
**Making a data source from a catalog**: an application's Shared Components → REST data sources → + Add shows *From a
|
|
281
315
|
REST source catalog* when the application's workspace has catalogs: pick a service, optionally another name and one
|
|
282
|
-
of the application's
|
|
316
|
+
of the web credentials (the application's or the workspace's). The data source is a **copy** (URL = base URL + path; on a
|
|
317
|
+
catalog with a **remote server**, sprint 141: the server and the path, so the source moves with the server): changing or deleting the
|
|
283
318
|
catalog later doesn't change it, and *Replace from a document* on a catalog's page replaces only the catalog's
|
|
284
319
|
services. Credentials never belong to a catalog: each application signs in with its own web credential.
|
|
285
320
|
|
|
@@ -335,6 +370,9 @@ values, processes and workflow steps refer to sources and credentials by name, s
|
|
|
335
370
|
internal addresses need `PGKILN_REST_PRIVATE_HOSTS` as well.
|
|
336
371
|
- A workspace's [network access list](03-builder.md#network-access) narrows the allow-list for its
|
|
337
372
|
applications (it can't widen it).
|
|
373
|
+
- A [remote server](#remote-servers)'s URL is checked like any other URL; its environment variable can only be a
|
|
374
|
+
`PGKILN_SERVER_…` name, so no other part of the server's environment becomes a URL. Only the workspace's
|
|
375
|
+
administrators change its servers and credentials.
|
|
338
376
|
- Secrets are encrypted with a key outside the database, write-only in the builder, excluded from
|
|
339
377
|
exports and from the runtime role, and never logged. Use **Valid for URLs** so a credential is
|
|
340
378
|
only ever sent to its own service.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 75: the item and region types of sprint 140 (AP-4;
|
|
3
|
+
-- migration 140; docs/guide/05-items.md, 04-pages-and-regions.md)
|
|
4
|
+
--
|
|
5
|
+
-- Page 50 "Project team" (Administration → Project team) puts a team
|
|
6
|
+
-- together the way an APEX page would:
|
|
7
|
+
--
|
|
8
|
+
-- - P50_DEPTNO, a Select One (a searchable single pick) of the
|
|
9
|
+
-- departments;
|
|
10
|
+
-- - P50_MEMBERS, a Shuttle of that department's employees (a cascading
|
|
11
|
+
-- list of values: choosing another department reloads it). The chosen
|
|
12
|
+
-- employees keep their order (Move up / Move down), stored
|
|
13
|
+
-- colon-separated like a multiselect: 7839:7566:7902;
|
|
14
|
+
-- - P50_SKILLS, a Select Many (chips) of a static list of values.
|
|
15
|
+
-- Without JavaScript the three are a plain select list and two
|
|
16
|
+
-- multi-selection lists, and Save works the same;
|
|
17
|
+
-- - "Chosen team", a Classic Report (no Actions menu) of the chosen
|
|
18
|
+
-- employees in the shuttle's order, and "Head count", a classic report
|
|
19
|
+
-- with the badges template;
|
|
20
|
+
-- - a Breadcrumb region in the Breadcrumb bar position (the title bar
|
|
21
|
+
-- then leaves its own trail out), and a Help Text region showing the
|
|
22
|
+
-- page's help text (Page → Help text, Markdown).
|
|
23
|
+
-- Save keeps the values in the session only: the page has no table.
|
|
24
|
+
-- =====================================================================
|
|
25
|
+
|
|
26
|
+
insert into meta.page (app_id, page_no, name, title, parent_page, help)
|
|
27
|
+
select id, 50, 'Project team', 'Project team', 1,
|
|
28
|
+
$h$Put a **project team** together:
|
|
29
|
+
|
|
30
|
+
1. Choose a *department* (type to search the list).
|
|
31
|
+
2. Move its employees from **Available** to **Selected** (double-click, Enter, or the › and » buttons) and put them in order with the arrow buttons.
|
|
32
|
+
3. Choose the skills the team needs, then **Save**.
|
|
33
|
+
|
|
34
|
+
Team lead for now: &APP_USER..$h$
|
|
35
|
+
from meta.app where alias = 'hr';
|
|
36
|
+
|
|
37
|
+
insert into meta.region (page_id, seq, title, type, columns, template, position, source, config)
|
|
38
|
+
select p.id, r.seq, r.title, r.type, r.cols, r.template, r.position, r.source, r.config::jsonb
|
|
39
|
+
from meta.page p join meta.app a on a.id = p.app_id, (values
|
|
40
|
+
(5, 'Breadcrumb', 'breadcrumb', 12, 'plain', 'breadcrumb', null, '{}'),
|
|
41
|
+
(10, 'Team', 'static', 8, 'standard', null, null, '{}'),
|
|
42
|
+
(20, 'How it works', 'help_text', 4, 'standard', null, null, '{}'),
|
|
43
|
+
(30, 'Chosen team', 'classic_report', 8, 'standard', null,
|
|
44
|
+
'select e.empno, e.ename, e.job, d.dname
|
|
45
|
+
from unnest(string_to_array(:P50_MEMBERS, '':'')) with ordinality m(empno, n)
|
|
46
|
+
join hr.emp e on e.empno::text = m.empno
|
|
47
|
+
left join hr.dept d on d.deptno = e.deptno
|
|
48
|
+
order by m.n',
|
|
49
|
+
'{"headings": {"empno": "No.", "ename": "Name", "job": "Job", "dname": "Department"}, "sortable": false, "pagination": "none", "empty": "Nobody chosen yet: move employees to Selected and save."}'),
|
|
50
|
+
(40, 'Head count', 'classic_report', 4, 'standard', null,
|
|
51
|
+
'select d.dname as label, count(e.empno) as value
|
|
52
|
+
from hr.dept d left join hr.emp e on e.deptno = d.deptno
|
|
53
|
+
group by d.dname order by d.dname',
|
|
54
|
+
'{"report_template": "badges", "pagination": "none"}')
|
|
55
|
+
) as r (seq, title, type, cols, template, position, source, config)
|
|
56
|
+
where a.alias = 'hr' and p.page_no = 50;
|
|
57
|
+
|
|
58
|
+
insert into meta.item (page_id, region_id, seq, name, label, type, lov, config, help)
|
|
59
|
+
select p.id, r.id, i.seq, i.name, i.label, i.type, i.lov, i.config::jsonb, i.help
|
|
60
|
+
from meta.page p join meta.app a on a.id = p.app_id
|
|
61
|
+
join meta.region r on r.page_id = p.id and r.title = 'Team', (values
|
|
62
|
+
(10, 'P50_DEPTNO', 'Department', 'select_one', 'select dname, deptno from hr.dept order by 1', '{"wide": true}', null),
|
|
63
|
+
(20, 'P50_MEMBERS', 'Team members', 'shuttle',
|
|
64
|
+
'select ename || '' ('' || coalesce(job, ''-'') || '')'' as d, empno as r from hr.emp where deptno = :P50_DEPTNO order by ename',
|
|
65
|
+
'{"cascade_parents": "P50_DEPTNO", "wide": true}', 'In the order they lead the project.'),
|
|
66
|
+
(30, 'P50_SKILLS', 'Skills needed', 'select_many',
|
|
67
|
+
'STATIC:SQL;SQL,PL/pgSQL;PLPGSQL,TypeScript;TS,Design;DESIGN,Testing;TEST,Accessibility;A11Y',
|
|
68
|
+
'{"wide": true}', null)
|
|
69
|
+
) as i (seq, name, label, type, lov, config, help)
|
|
70
|
+
where a.alias = 'hr' and p.page_no = 50;
|
|
71
|
+
|
|
72
|
+
insert into meta.button (page_id, region_id, seq, name, label, action, hot)
|
|
73
|
+
select p.id, r.id, 10, 'SAVE', 'Save', 'submit', true
|
|
74
|
+
from meta.page p join meta.app a on a.id = p.app_id
|
|
75
|
+
join meta.region r on r.page_id = p.id and r.title = 'Team'
|
|
76
|
+
where a.alias = 'hr' and p.page_no = 50;
|
|
77
|
+
|
|
78
|
+
insert into meta.nav_entry (app_id, parent_id, seq, label, icon, target_page)
|
|
79
|
+
select a.id, n.id, 70, 'Project team', 'users', 50
|
|
80
|
+
from meta.app a join meta.nav_entry n on n.app_id = a.id and n.label = 'Administration' and n.parent_id is null
|
|
81
|
+
where a.alias = 'hr';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 76: a remote server and a static workspace file
|
|
3
|
+
-- (sprint 141, AP-7; migration 141; docs/guide/19-rest-data-sources.md
|
|
4
|
+
-- "Remote servers", docs/guide/16-files.md "Static workspace files")
|
|
5
|
+
--
|
|
6
|
+
-- Remote server CRM (Workspace utilities → Remote servers) stands for the
|
|
7
|
+
-- web service of part 23: the HR example's own REST API on
|
|
8
|
+
-- http://127.0.0.1:3100. The REST data sources DEPARTMENTS, DEPARTMENT and
|
|
9
|
+
-- EMPLOYEES_API now name the server and keep only their path
|
|
10
|
+
-- (/a/hr/rest/v1/departments, ...). Moving the API to another host changes
|
|
11
|
+
-- one value: the server's base URL, e.g. https://crm.example.com, or the
|
|
12
|
+
-- server's environment variable PGKILN_SERVER_CRM, which wins when it is
|
|
13
|
+
-- set (one value per environment: development, test, production). Every
|
|
14
|
+
-- source on the server follows at once; the new host must still be on the
|
|
15
|
+
-- allow-list (PGKILN_REST_ALLOWED_HOSTS) and the workspace's network
|
|
16
|
+
-- access list.
|
|
17
|
+
--
|
|
18
|
+
-- brand.css is a static workspace file: every application of the
|
|
19
|
+
-- workspace may load it. HR loads it on every page as
|
|
20
|
+
-- #WORKSPACE_FILES#brand.css (Shared Components → Static application
|
|
21
|
+
-- files → Every page loads); another application of the workspace adds the
|
|
22
|
+
-- same line to share the styles.
|
|
23
|
+
-- =====================================================================
|
|
24
|
+
|
|
25
|
+
insert into meta.remote_server (workspace_id, static_id, name, description, base_url, env_var)
|
|
26
|
+
select meta.app_workspace(a.id), 'CRM', 'HR API (CRM)',
|
|
27
|
+
'The HR example''s REST API, served by this pgkiln server. Set PGKILN_SERVER_CRM on another environment.',
|
|
28
|
+
'http://127.0.0.1:3100', 'PGKILN_SERVER_CRM'
|
|
29
|
+
from meta.app a where a.alias = 'hr'
|
|
30
|
+
on conflict (workspace_id, static_id) do nothing;
|
|
31
|
+
|
|
32
|
+
update meta.rest_source s
|
|
33
|
+
set server = 'CRM', url = substr(s.url, length('http://127.0.0.1:3100') + 1)
|
|
34
|
+
from meta.app a
|
|
35
|
+
where a.id = s.app_id and a.alias = 'hr' and s.server is null
|
|
36
|
+
and s.name in ('DEPARTMENTS', 'DEPARTMENT', 'EMPLOYEES_API') and s.url like 'http://127.0.0.1:3100/%';
|
|
37
|
+
|
|
38
|
+
insert into meta.workspace_file (workspace_id, name, mime, content)
|
|
39
|
+
select meta.app_workspace(a.id), 'brand.css', 'text/css', convert_to($css$/* A static workspace file (Workspace utilities → Static workspace files):
|
|
40
|
+
the applications of the workspace load it as #WORKSPACE_FILES#brand.css. */
|
|
41
|
+
.region-header h2 { letter-spacing: .01em; }
|
|
42
|
+
.ws-brand { color: var(--accent, #2b62c4); font-weight: 600; }
|
|
43
|
+
$css$, 'utf8')
|
|
44
|
+
from meta.app a where a.alias = 'hr'
|
|
45
|
+
on conflict (workspace_id, name) do nothing;
|
|
46
|
+
|
|
47
|
+
update meta.app set static_includes = static_includes || '{#WORKSPACE_FILES#brand.css}'
|
|
48
|
+
where alias = 'hr' and not ('#WORKSPACE_FILES#brand.css' = any (static_includes));
|
package/package.json
CHANGED
package/public/app.css
CHANGED
|
@@ -1693,6 +1693,54 @@ td.tc-cell { overflow-wrap: anywhere; }
|
|
|
1693
1693
|
}
|
|
1694
1694
|
.tag-remove:hover, .tag-remove:focus-visible { background: var(--surface-3); }
|
|
1695
1695
|
|
|
1696
|
+
/* (140) shuttle: two lists, the move buttons between them and the order buttons after them (stacked on phones) */
|
|
1697
|
+
.shuttle-ui { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto; gap: 0.5rem; align-items: stretch; }
|
|
1698
|
+
.shuttle-col { display: flex; flex-direction: column; gap: 0.35rem; min-width: 0; }
|
|
1699
|
+
.shuttle-col .shuttle-list { flex: 1; min-height: 9rem; }
|
|
1700
|
+
.shuttle-moves, .shuttle-order { display: flex; flex-direction: column; justify-content: center; gap: 0.3rem; }
|
|
1701
|
+
.shuttle-btn { min-width: 2.4rem; justify-content: center; font-size: 1rem; line-height: 1; }
|
|
1702
|
+
[dir=rtl] .shuttle-moves .shuttle-btn span { display: inline-block; transform: scaleX(-1); }
|
|
1703
|
+
@media (max-width: 640px) {
|
|
1704
|
+
.shuttle-ui { grid-template-columns: minmax(0, 1fr) auto; }
|
|
1705
|
+
.shuttle-col:first-child { grid-column: 1 / -1; }
|
|
1706
|
+
.shuttle-moves { grid-column: 1 / -1; flex-direction: row; justify-content: center; }
|
|
1707
|
+
.shuttle-moves .shuttle-btn span { display: inline-block; transform: rotate(90deg); }
|
|
1708
|
+
}
|
|
1709
|
+
/* (140) select one / select many: a search field with a list box under it; chips above it */
|
|
1710
|
+
.select-pick-ui { position: relative; display: flex; flex-direction: column; gap: 0.3rem; min-width: 0; }
|
|
1711
|
+
.select-pick-list {
|
|
1712
|
+
position: absolute; z-index: 30; top: 100%; inset-inline: 0; margin: 0.15rem 0 0; padding: 0.25rem 0; list-style: none; max-height: 16rem; overflow-y: auto;
|
|
1713
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: calc(var(--radius) * 0.75); box-shadow: var(--shadow);
|
|
1714
|
+
}
|
|
1715
|
+
.select-pick-list[hidden] { display: none; }
|
|
1716
|
+
.select-pick-list li { padding: 0.4rem 0.7rem; cursor: pointer; overflow-wrap: anywhere; }
|
|
1717
|
+
.select-pick-list li[aria-selected=true] { font-weight: 600; }
|
|
1718
|
+
.select-pick-list li[aria-selected=true]::before { content: "✓ "; }
|
|
1719
|
+
.select-pick-list li.is-active, .select-pick-list li:hover { background: var(--surface-3); }
|
|
1720
|
+
.select-pick-list li.select-pick-none { cursor: default; color: var(--muted); }
|
|
1721
|
+
/* (140) a breadcrumb region and a help text region */
|
|
1722
|
+
.region-crumbs .crumbs { font-size: 0.9rem; margin: 0; }
|
|
1723
|
+
.region-crumbs .crumbs [aria-current] { color: var(--text); font-weight: 600; }
|
|
1724
|
+
.help-text { overflow-wrap: anywhere; }
|
|
1725
|
+
.help-text > :first-child { margin-top: 0; }
|
|
1726
|
+
.help-text > :last-child { margin-bottom: 0; }
|
|
1727
|
+
/* (140) classic report templates */
|
|
1728
|
+
.cr-pairs { display: flex; flex-direction: column; gap: 0.75rem; }
|
|
1729
|
+
.cr-pairs dl { display: grid; grid-template-columns: minmax(6rem, max-content) minmax(0, 1fr); gap: 0.25rem 1rem; margin: 0; padding-bottom: 0.75rem; border-bottom: 1px solid var(--border); }
|
|
1730
|
+
.cr-pairs dl:last-child { border-bottom: 0; padding-bottom: 0; }
|
|
1731
|
+
.cr-pairs dt { font-weight: 600; color: var(--muted); overflow-wrap: anywhere; }
|
|
1732
|
+
.cr-pairs dd { margin: 0; overflow-wrap: anywhere; }
|
|
1733
|
+
.cr-badges { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 9rem), 1fr)); gap: 0.6rem; list-style: none; margin: 0; padding: 0; }
|
|
1734
|
+
.cr-badges li { display: flex; flex-direction: column; align-items: center; gap: 0.2rem; padding: 0.7rem; border: 1px solid var(--border); border-radius: var(--radius); text-align: center; overflow-wrap: anywhere; }
|
|
1735
|
+
.cr-badges .cr-badge-value { font-size: 1.4rem; font-weight: 700; }
|
|
1736
|
+
.cr-badges .cr-badge-label { font-size: 0.85rem; color: var(--muted); }
|
|
1737
|
+
.cr-media { list-style: none; margin: 0; padding: 0; }
|
|
1738
|
+
.cr-media li { padding: 0.6rem 0; border-bottom: 1px solid var(--border); overflow-wrap: anywhere; }
|
|
1739
|
+
.cr-media li:last-child { border-bottom: 0; }
|
|
1740
|
+
.cr-media .cr-media-title { font-weight: 600; }
|
|
1741
|
+
.cr-media .cr-media-text { margin: 0.15rem 0 0; color: var(--muted); }
|
|
1742
|
+
@media (max-width: 640px) { .cr-pairs dl { grid-template-columns: minmax(0, 1fr); } }
|
|
1743
|
+
|
|
1696
1744
|
.date-range { display: flex; flex-wrap: wrap; gap: 0.4rem 0.8rem; }
|
|
1697
1745
|
.range-part { display: flex; align-items: center; gap: 0.4rem; flex: 1 1 12rem; min-width: 0; }
|
|
1698
1746
|
.range-part .sub { font-size: 0.85rem; color: var(--muted); min-width: 2.2rem; }
|