pgkiln 0.45.0 → 0.47.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/106_form_designer.sql +102 -0
- package/db/migrations/107_app_version.sql +9 -0
- package/db/migrations/108_sql_history.sql +25 -0
- package/docs/apex-feature-parity.md +33 -4
- package/docs/guide/03-builder.md +123 -28
- package/docs/guide/04-pages-and-regions.md +74 -2
- package/docs/guide/05-items.md +14 -2
- package/docs/guide/08-security.md +1 -1
- package/docs/guide/09-reference.md +1 -1
- package/docs/guide/10-tutorial.md +1 -1
- package/docs/guide/11-from-apex.md +2 -1
- package/docs/guide/12-development.md +26 -3
- package/docs/guide/16-files.md +2 -2
- package/examples/hr/hr_61_form_designer.sql +80 -0
- package/package.json +1 -1
- package/public/app.css +62 -2
- package/public/app.js +244 -35
- package/public/builder.css +21 -1
- package/public/builder.js +67 -0
- package/public/sw.js +2 -1
- package/public/worksheet.css +244 -0
- package/public/worksheet.js +665 -0
- package/src/app.ts +9 -1
- package/src/builder/ai-builder.ts +3 -3
- package/src/builder/appwizard.ts +22 -6
- package/src/builder/arrange.ts +33 -12
- package/src/builder/code-editor.ts +1 -1
- package/src/builder/components.ts +1 -1
- 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/locks.ts +6 -0
- package/src/builder/newapp.ts +2 -1
- package/src/builder/object-actions.ts +166 -0
- package/src/builder/object-browser.ts +111 -33
- package/src/builder/querybuilder.ts +1 -1
- 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/sampledata.ts +2 -2
- package/src/builder/scripts.ts +3 -3
- package/src/builder/search.ts +3 -3
- package/src/builder/shared.ts +2 -2
- package/src/builder/sql.ts +15 -73
- package/src/builder/table-editor.ts +219 -0
- package/src/builder/ui.ts +5 -3
- package/src/builder/unload.ts +8 -4
- package/src/builder/worksheet.ts +569 -0
- package/src/builder/workspaces.ts +1 -1
- package/src/cli/replace.ts +10 -0
- 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 +4 -2
- package/src/runtime/devbar.ts +1 -1
- 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 +15 -8
- package/src/runtime/items.ts +33 -21
- package/src/runtime/lists.ts +1 -1
- package/src/runtime/pdf.ts +2 -1
- package/src/runtime/regions.ts +4 -0
- package/src/runtime/render.ts +7 -3
- package/src/runtime/report.ts +2 -2
- package/src/runtime/routes.ts +3 -1
- package/src/runtime/smart-filters.ts +2 -1
- package/src/runtime/tasks.ts +6 -3
- package/src/sqlformat.ts +208 -0
- package/src/sqlplan.ts +133 -0
- package/src/sqlscript.ts +6 -2
- package/src/sqlworksheet.ts +278 -0
- package/src/tabledef.ts +226 -0
- package/src/unload.ts +14 -3
|
@@ -819,7 +819,9 @@ Counts take the search and all *other* facets into account. Three kinds of facet
|
|
|
819
819
|
|
|
820
820
|
- **checkbox** (the default): the most frequent values of a column, each with a checkbox; after the seventh
|
|
821
821
|
the rest sit under *Show all (n)* (open when one of them is chosen), and a boolean's values read *Yes* and
|
|
822
|
-
*No*.
|
|
822
|
+
*No*. Its ⋮ menu (APEX: the facet's menu) has *Clear* (when values are chosen) and *Sort by count* (the
|
|
823
|
+
default) or *Sort by name*; the order is a URL parameter (`r<report id>_fo_<column>=name`) that Apply and
|
|
824
|
+
Reset keep. With
|
|
823
825
|
`"exclude": true` the facet gets an *Exclude the selected values* switch: the chosen values are
|
|
824
826
|
then left out instead (rows where the column is empty stay).
|
|
825
827
|
- **range**: a number or date column in ranges, as radio buttons ("Any", then each range). A
|
|
@@ -1127,7 +1129,7 @@ Attributes (the region's **Settings** in the page designer):
|
|
|
1127
1129
|
| Key | Meaning |
|
|
1128
1130
|
|---|---|
|
|
1129
1131
|
| `list` | The list's name (upper case) |
|
|
1130
|
-
| `template` | `links` (default: nested links), `badges` (a row of tiles with the badge as the value), `cards` (a card per entry with its description), `tabs` (the top-level entries as tabs) or `wizard_progress` (numbered steps: the entry of the current page is the current step, the ones before it are done and link back; APEX: Wizard Progress) |
|
|
1132
|
+
| `template` | `links` (default: nested links), `badges` (a row of tiles with the badge as the value), `cards` (a card per entry with its description; (sprint 88) a colour band and the icon in a round badge, in the chart palette's order), `tabs` (the top-level entries as tabs) or `wizard_progress` (numbered steps: the entry of the current page is the current step, the ones before it are done and link back; APEX: Wizard Progress) |
|
|
1131
1133
|
|
|
1132
1134
|
The HR example's page 31 (Shortcuts) shows a static list with each template (with a badge from an
|
|
1133
1135
|
item, child entries, a manager-only entry and one of an excluded build option) and a department
|
|
@@ -1198,6 +1200,76 @@ region when an application is replaced. The HR example's page 36 (*My reports*)
|
|
|
1198
1200
|
(a view without the user name and photo columns) and leave requests (row level security: an
|
|
1199
1201
|
employee sees only their own), with King's shared report *Salary by department*.
|
|
1200
1202
|
|
|
1203
|
+
### `form_designer` (Form Designer)
|
|
1204
|
+
|
|
1205
|
+
A **Form Designer** region (sprint 85; pgkiln's own, APEX has no counterpart) lets the application's
|
|
1206
|
+
administrators build their own data entry forms in the running application, without a developer
|
|
1207
|
+
and without a new release. Like the [Data Reporter](#data_reporter-data-reporter), the developer
|
|
1208
|
+
decides *what* may be edited; the administrators decide *how* the forms look and who uses them.
|
|
1209
|
+
|
|
1210
|
+
**The developer** places the region (page designer gallery: *Form Designer*) and, in its
|
|
1211
|
+
**Settings**, chooses **who may design forms** (an [authorization scheme](08-security.md); without
|
|
1212
|
+
one nobody can) and adds **data sources**: a table or view each (pgkiln's own `meta` schema and the
|
|
1213
|
+
system schemas are never offered), with a static id, a label and a description. Per source:
|
|
1214
|
+
|
|
1215
|
+
- the **columns** forms may show and change, with a label and, for a select list or radio group,
|
|
1216
|
+
a **list of values**: a shared one of the application (`LOV:NAME`) or `STATIC:Display;value,…`
|
|
1217
|
+
(never SQL: designers can't write queries);
|
|
1218
|
+
- the **key column** that identifies a row (the primary key of a new table; choose one for a view),
|
|
1219
|
+
needed to change and delete rows;
|
|
1220
|
+
- what forms on it **may** do at most: **add**, **change** and **delete** rows.
|
|
1221
|
+
|
|
1222
|
+
A new source offers all its columns except binary and JSON ones, allows adding and (with a key)
|
|
1223
|
+
changing rows, never deleting. Untick generated keys and audit columns; the builder warns when the
|
|
1224
|
+
application's database role lacks a privilege a source needs.
|
|
1225
|
+
|
|
1226
|
+
**Designers** (users that pass the scheme) see *New form* and a *Design form* link under each
|
|
1227
|
+
form. The designer is a plain form (it works without JavaScript): a name, a description, what
|
|
1228
|
+
users may do (within what the source allows) and a row per offered column: *On the form*,
|
|
1229
|
+
*Order*, *Label*, *Help text*, *Required*, *Display* (a field by the column's type; a text area for
|
|
1230
|
+
text; a select list or radio group for a column with a list of values) and a *Default* for new
|
|
1231
|
+
rows, and *In the list* for the columns of the form's list of rows. A form is a **draft** until
|
|
1232
|
+
*Published* is ticked: only designers see drafts.
|
|
1233
|
+
|
|
1234
|
+
**Users** see the published forms as cards. A form with list columns opens on its rows (newest
|
|
1235
|
+
first, paged, `page_size`) with *Add* and an *Edit* link per row; a form without list columns
|
|
1236
|
+
only adds rows: it opens on an empty form, and after *Save* on the next empty one. Fields follow
|
|
1237
|
+
the column's type: a number field, the [date picker](05-items.md#the-date-picker) in the
|
|
1238
|
+
application's date format for dates, the browser's date and time field for timestamps, a
|
|
1239
|
+
checkbox for booleans.
|
|
1240
|
+
|
|
1241
|
+
How it stays safe:
|
|
1242
|
+
|
|
1243
|
+
- rows are read and written as the **application's database role**: grants and [row level
|
|
1244
|
+
security](08-security.md) apply (in the HR example an employee changes only their own meetings);
|
|
1245
|
+
- a form's definition is checked against the region's settings before it renders or saves: only
|
|
1246
|
+
offered columns that exist and can be read, displays the column's type allows, actions the
|
|
1247
|
+
source allows; anything else is dropped. Only the form's fields are written; identifiers are
|
|
1248
|
+
only the developer's schema, table and column names, always quoted; values and row keys are
|
|
1249
|
+
always parameters; a value of a list of values must be in the list;
|
|
1250
|
+
- **lost updates** are refused: a row changed since its form was opened is not overwritten (the
|
|
1251
|
+
user sees what they typed, with a message);
|
|
1252
|
+
- forms are stored in `meta.user_form`, reached by applications only through the view
|
|
1253
|
+
`meta.user_forms` (the forms of the application) and the functions `meta.save_user_form` /
|
|
1254
|
+
`meta.delete_user_form`; saving rows and forms needs the CSRF token and a region the user can
|
|
1255
|
+
see, designing also the region's authorization scheme. Every saved, changed or deleted row and
|
|
1256
|
+
form is in the activity log (event `form_designer`).
|
|
1257
|
+
|
|
1258
|
+
Attributes (written by the region's **Settings**):
|
|
1259
|
+
|
|
1260
|
+
| Key | Default | Meaning |
|
|
1261
|
+
|---|---|---|
|
|
1262
|
+
| `sources` | | `[{"id": "orders", "label": "Orders", "description": "…", "schema": "sales", "table": "orders", "key": "id", "columns": [{"name": "customer", "label": "Customer"}, {"name": "status", "lov": "LOV:ORDER_STATUS"}], "insert": true, "update": true, "delete": false}]` |
|
|
1263
|
+
| `design_authz` | | The authorization scheme designers pass; none: nobody designs |
|
|
1264
|
+
| `page_size` | `25` | Rows per page of a form's list (5 to 200) |
|
|
1265
|
+
| `empty` | `No data found.` | Text when a form's list has no rows |
|
|
1266
|
+
|
|
1267
|
+
The sources travel with the application export (they are the region's attributes); the forms are
|
|
1268
|
+
data of the installation, like Data Reporter reports: not exported, and they stay with their
|
|
1269
|
+
region when an application is replaced. The HR example's page 45 (*Forms*, under Administration)
|
|
1270
|
+
offers departments, meetings and employees (changing only) to administrators, with the published
|
|
1271
|
+
forms *Departments* and *Book a meeting*.
|
|
1272
|
+
|
|
1201
1273
|
### `ai_assistant` (AI assistant)
|
|
1202
1274
|
|
|
1203
1275
|
An **AI assistant** region (APEX: AI Assistant, AI agents and tools) is a chat with an
|
package/docs/guide/05-items.md
CHANGED
|
@@ -11,7 +11,7 @@ page, optionally inside a region. Name them `P<page>_<NAME>` (uppercase letters,
|
|
|
11
11
|
| `text` | text field | text |
|
|
12
12
|
| `textarea` | multi-line text (always full width) | text |
|
|
13
13
|
| `number` | number field (numeric keyboard on phones) | e.g. `1250.50` |
|
|
14
|
-
| `date` |
|
|
14
|
+
| `date` | a text field in the item's date format mask, else the application's date format, else `YYYY-MM-DD`, with pgkiln's [date picker](#the-date-picker) (sprint 84; was the browser's); typed dates are read back in the format | `YYYY-MM-DD` |
|
|
15
15
|
| `datetime` | date-time picker | `YYYY-MM-DDTHH:MM` |
|
|
16
16
|
| `email`, `tel`, `url` | typed fields with the matching phone keyboard | text |
|
|
17
17
|
| `password` | password field; never shown back, empty = unchanged | text |
|
|
@@ -31,7 +31,7 @@ page, optionally inside a region. Name them `P<page>_<NAME>` (uppercase letters,
|
|
|
31
31
|
| `markdown` | Markdown textarea with a toolbar; shown formatted when read-only | the Markdown text |
|
|
32
32
|
| `rating` | star rating (radio buttons drawn as stars) | `1` … `max` (default 5) |
|
|
33
33
|
| `combobox` | free text with suggestions from the list of values; several values become tags | **colon-separated** values, e.g. `SQL:Sales` |
|
|
34
|
-
| `daterange` | two date
|
|
34
|
+
| `daterange` | two date fields, *From* and *To*, in the date format with the date picker (the picker keeps *From* on or before *To*) | `from:to`, e.g. `2026-01-01:2026-06-30` |
|
|
35
35
|
| `qrcode` | display only: the value as a QR code (SVG drawn on the server) | whatever was set |
|
|
36
36
|
| `image` | display only: a picture from a URL in the value, or a BLOB from a query ([Display image](#display-image-and-geocoded-address)) | a URL, or nothing |
|
|
37
37
|
| `address` | a text field with *Check address*: the installation's geocoder offers matching addresses ([Geocoded address](#display-image-and-geocoded-address)) | the address as text |
|
|
@@ -51,6 +51,18 @@ where hiredate between nullif(split_part(:P20_PERIOD, ':', 1), '')::date
|
|
|
51
51
|
and coalesce(nullif(split_part(:P20_PERIOD, ':', 2), '')::date, 'infinity')
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
|
|
55
|
+
### The date picker
|
|
56
|
+
|
|
57
|
+
(sprint 84; APEX: the date picker) A date item, a date range item, an interactive grid's date cell and a
|
|
58
|
+
faceted search's own date range and the task list's vacation rule are text in the date format: the item's date format mask, else the
|
|
59
|
+
application's date format (Settings → Globalization), else `YYYY-MM-DD`. The calendar button next to the
|
|
60
|
+
field (or Alt+↓ in it) opens a month of days under it: the chosen day marked, today outlined, the week
|
|
61
|
+
starting on the language's first day. Arrow keys move a day or a week, Page Up and Page Down a month
|
|
62
|
+
(with Shift a year), Home and End to the week's first and last day, Enter or Space choose, Escape
|
|
63
|
+
closes; *Today* and *Clear* below. Typed dates are read back in the format (and `YYYY-MM-DD` always
|
|
64
|
+
works), so the field works without JavaScript. Timestamp items and grid timestamp cells still use the
|
|
65
|
+
browser's date and time field.
|
|
54
66
|
## Properties
|
|
55
67
|
|
|
56
68
|
| Property | Meaning |
|
|
@@ -507,7 +507,7 @@ matures. Privileges show up in **SQL Workshop → Object Browser**.
|
|
|
507
507
|
A `data_load` process loads files as the app role too, also with a data load definition: a
|
|
508
508
|
definition naming a table the role can't write fails like any other insert, and a process only
|
|
509
509
|
finds the definitions of its own application. XML files with a document type declaration are
|
|
510
|
-
refused ([chapter 16](16-files.md#data-loading)). The SQL Workshop (SQL
|
|
510
|
+
refused ([chapter 16](16-files.md#data-loading)). The SQL Workshop (the SQL Worksheet, SQL Scripts,
|
|
511
511
|
Quick SQL, Load Data, Unload Data, Sample Data) runs as the owner instead, so every developer can change any table: keep
|
|
512
512
|
developer accounts to people you trust with the database.
|
|
513
513
|
|
|
@@ -427,7 +427,7 @@ navigation entries and application processes have the same `build_option` column
|
|
|
427
427
|
| Column | Description |
|
|
428
428
|
|---|---|
|
|
429
429
|
| `page_id`, `seq`, `title` | |
|
|
430
|
-
| `type` | `report`, `grid`, `form`, `chart`, `cards`, `calendar`, `facets`, `smart_filters`, `display_selector`, `map`, `tree`, `template_component`, `list`, `data_reporter`, `ai_assistant`, `tasks`, `workflows`, `static`, `dynamic` |
|
|
430
|
+
| `type` | `report`, `grid`, `form`, `chart`, `cards`, `calendar`, `facets`, `smart_filters`, `display_selector`, `map`, `tree`, `template_component`, `list`, `data_reporter`, `form_designer`, `ai_assistant`, `tasks`, `workflows`, `static`, `dynamic` |
|
|
431
431
|
| `source` | SELECT (or HTML for `static`) |
|
|
432
432
|
| `table_name`, `pk_column` | For `form` and `grid` |
|
|
433
433
|
| `pk_item` | For `form`: the item holding the key |
|
|
@@ -20,7 +20,7 @@ Then open http://127.0.0.1:3100/a/tasks and sign in as `ann` / `ann-password` or
|
|
|
20
20
|
|
|
21
21
|
## Step 1: the table and the business logic
|
|
22
22
|
|
|
23
|
-
Open **SQL Workshop → SQL
|
|
23
|
+
Open **SQL Workshop → SQL Worksheet**, paste this and press **Run Script** (F5):
|
|
24
24
|
|
|
25
25
|
```sql
|
|
26
26
|
create schema tasks;
|
|
@@ -169,7 +169,8 @@ Tips:
|
|
|
169
169
|
together.
|
|
170
170
|
- **Errors are hidden by default**: unexpected errors show a reference number unless the app is in
|
|
171
171
|
debug mode.
|
|
172
|
-
- **Dates**: date items
|
|
172
|
+
- **Dates**: date items, date ranges, grid date cells and facet ranges are text in the application's date format
|
|
173
|
+
(else ISO, `2026-10-01`) with pgkiln's date picker; format masks
|
|
173
174
|
apply to report, grid and cards columns and to number and display items
|
|
174
175
|
([number formats](14-globalization.md#number-formats)).
|
|
175
176
|
- **Modal pages** open over the calling page, and close and refresh it after a successful submit,
|
|
@@ -66,7 +66,12 @@ src/
|
|
|
66
66
|
sampledata-words.ts built-in name, city, company and word lists of Sample Data
|
|
67
67
|
unload.ts Unload Data: unloadStatement() (one SELECT), openUnload() (cursor, batches, CSV/JSON/XLSX/XML encoders on Postgres text values)
|
|
68
68
|
xml.ts safe XML reader (no DTDs or entities, limits) and xmlTable(): rows from a repeating element (unit tested)
|
|
69
|
-
sqlscript.ts SQL scripts: splitScript() (statements, line numbers, psql commands), runScript() (stop/continue, transaction, savepoints)
|
|
69
|
+
sqlscript.ts SQL scripts: splitScript() (statements, line numbers and offsets, psql commands), runScript() (stop/continue, transaction, savepoints)
|
|
70
|
+
sqlworksheet.ts (sprint 91) the worksheet's runs: pickStatements() (at the cursor, a selection, all), runStatements() (a log per
|
|
71
|
+
statement, psql command tags, notices, error line and column), explainStatement() (EXPLAIN / rolled-back ANALYZE)
|
|
72
|
+
tabledef.ts (sprint 95) a table as a definition: readTable(), checkTable(), tableSql() (CREATE, or ALTER from the catalog's version)
|
|
73
|
+
sqlformat.ts (sprint 93) formatSql(): the worksheet's Format (clauses per line, keyword case; strings and comments kept)
|
|
74
|
+
sqlplan.ts (sprint 92) EXPLAIN (FORMAT JSON) as a tree: parsePlan() (self time, slowest step, misestimates), planText()
|
|
70
75
|
quicksql.ts Quick SQL: shorthand parser and PostgreSQL DDL generator (unit tested)
|
|
71
76
|
xlsx.ts Excel writer for report downloads and Unload Data (typed cells, streamed through fflate's Zip)
|
|
72
77
|
automations.ts cron parser, next run in a time zone, scheduler (also meta.task_expire_tick, 094), running automations (the actions run
|
|
@@ -154,6 +159,8 @@ src/
|
|
|
154
159
|
tasks.ts task list region and task actions (approvals); (094) the vacation rule panel and route
|
|
155
160
|
data-reporter.ts Data Reporter region (migration 057): sources from the region's config, checkDef (offered columns, whitelists),
|
|
156
161
|
reportQuery/chartQuery, the list and editor (GET form), save/delete routes (meta.save_data_report)
|
|
162
|
+
form-designer.ts Form Designer region (migration 106): sources from the region's config, checkFormDef (offered columns, displays,
|
|
163
|
+
actions), forms as cards, a form's rows, a row's form (lost update hash), the designer; row/design/delete routes
|
|
157
164
|
workflows.ts workflow console region and its actions
|
|
158
165
|
ai.ts Generate text with AI: the ai_generate process (config checks, &ITEM. as delimited escaped data,
|
|
159
166
|
schemas from items, answers into items), aiInputs/aiOutputs for its dynamic action (route in routes.ts)
|
|
@@ -209,10 +216,18 @@ src/
|
|
|
209
216
|
mail.ts Workspace utilities → Mail services (administrators: providers, write-only secret, allowed senders,
|
|
210
217
|
applications with From and daily limit, test) and Applications → Activity → Mail (retry, cancel)
|
|
211
218
|
shared.ts Shared Components and access control
|
|
212
|
-
designer.ts page designer: component tree (with computations and branches), layout canvas and gallery, property editor, toolbar
|
|
219
|
+
designer.ts page designer: component tree (with computations and branches), layout canvas and gallery, property editor, toolbar;
|
|
220
|
+
(sprint 86) GET /builder/apps/:app/pages/:no, /builder/pages/:id redirects browsers there (designerUrl in ui.ts)
|
|
221
|
+
errors.ts (sprint 86) the builder's HTML 404 and 500 pages for browsers (wantsHtml, a not-found handler and an onSend hook)
|
|
213
222
|
arrange.ts page designer layout changes: move, column span, create from the gallery, undo / redo
|
|
214
|
-
sql.ts SQL Workshop: the home and
|
|
223
|
+
sql.ts SQL Workshop: the home and the session's schema
|
|
224
|
+
worksheet.ts (sprint 91) the SQL Worksheet (Database Actions' SQL; /sql/commands too): navigator (pickSchema, objects with
|
|
225
|
+
columns, objectStatements), toolbar, output tabs, history (meta.sql_history, 108), plan trees, Run as an app's role, (sprint 93) Save / Rename / Download as SQL Scripts, bind and substitution values, POST /sql/worksheet/format; GET /sql/worksheet/navigator
|
|
215
226
|
object-browser.ts (sprint 75) the Object Browser: objects by type, system objects hidden, a table in tabs, column actions as SQL to review
|
|
227
|
+
table-editor.ts (sprint 95) Table Properties: GET/POST /sql/objects/table (create, edit ?o=, ?template=), fromForm(), Preview / Apply
|
|
228
|
+
(one transaction) / Open in the worksheet
|
|
229
|
+
object-actions.ts (sprint 94) an object's ⋯ menu (objectMenu) in the worksheet and the Object Browser: Quick DDL (objectDdl), Drop with
|
|
230
|
+
dependents, Add index / trigger / policy as drafts; GET /sql/objects/action, /sql/objects/panel (Open in a panel)
|
|
216
231
|
scripts.ts SQL Workshop → SQL Scripts: editor, upload/download, run, results per statement, run history
|
|
217
232
|
quicksql.ts SQL Workshop → Quick SQL page (preview, save as script, run)
|
|
218
233
|
querybuilder.ts SQL Workshop → Query Builder: catalog, joins by foreign key or drawn, functions/group by, buildQuery() from the URL; the canvas is in builder.js
|
|
@@ -241,6 +256,7 @@ src/
|
|
|
241
256
|
themeroller.ts Settings → Theme Roller: style variants (add, edit, rename, delete), default style, users may choose
|
|
242
257
|
subscriptions.ts Shared Components → Subscriptions: subscribe, refresh, unsubscribe, subscribers and publish; the note under a component
|
|
243
258
|
reporter.ts page designer: Data Reporter settings (sources: table or view, offered columns, labels, masks; sharing)
|
|
259
|
+
form-designer.ts page designer: Form Designer settings (designers' scheme; sources: key, actions, offered columns, labels, lists of values)
|
|
244
260
|
workingcopies.ts Working copies: list and create, compare with differences, merge or refresh with conflict choices, delete
|
|
245
261
|
rest.ts REST module endpoints list and curl example (Shared Components)
|
|
246
262
|
workflows.ts workflow versions, diagram and instances (Shared Components)
|
|
@@ -274,6 +290,8 @@ public/
|
|
|
274
290
|
app.css theme (light/dark, responsive; --font, --font-size, --radius for style variants; template option classes to-*)
|
|
275
291
|
app.js client runtime: dialogs (dialog_closed actions), popup LOVs, dynamic actions (focus, classes, messages), 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)
|
|
276
292
|
code-editor.js, .css builder code editor: enhances <textarea data-code>, highlighting, suggestions (no dependencies)
|
|
293
|
+
worksheet.js, .css (sprint 91) the SQL Worksheet: run keys with the cursor, output tabs, navigator swap and search, drag and drop, splitters;
|
|
294
|
+
(92) grid sort, value dialog, history filter; (93) worksheet tabs in localStorage, Files, binds dialog, format, find, case
|
|
277
295
|
builder.css builder only: IDE look (dark chrome, icon rail, panes), builder light/dark tokens
|
|
278
296
|
builder.js builder only: tabs, component tree, property filter, drag and drop on the layout
|
|
279
297
|
builder-icons.svg builder only: icons of the rail, toolbar and designer (b-*)
|
|
@@ -293,6 +311,10 @@ test/
|
|
|
293
311
|
unload.test.ts Unload Data: CSV/JSON/XLSX/XML output, read back with Load Data, read-only and one-statement checks, streaming
|
|
294
312
|
app-from-file.test.ts Create → From a file: proposed names and types, app + table + rows + pages, row errors, login, validation
|
|
295
313
|
workshop.test.ts SQL scripts, Quick SQL pages, query builder, data load definitions (Load Data, the process, export)
|
|
314
|
+
tabledef.test.ts (sprint 95) table definitions round trip: CREATE, read, ALTER, read again; mistakes
|
|
315
|
+
table-editor.test.ts (sprint 95) the Table Properties page: preview, apply, a failing apply rolls back, template, worksheet
|
|
316
|
+
object-actions.test.ts (sprint 94) object menus: Quick DDL, Drop with dependents, Add index / trigger / policy, the panel, not-found
|
|
317
|
+
worksheet.test.ts (sprint 91) the SQL Worksheet: statement at the cursor, Run Script log, plans, notices, history, navigator, Run as
|
|
296
318
|
quicksql.test.ts Quick SQL parser and DDL generator
|
|
297
319
|
xml.test.ts XML reader: rows, attributes, paths, refused DTDs and entities, limits
|
|
298
320
|
printing.test.ts report PDFs
|
|
@@ -338,6 +360,7 @@ test/
|
|
|
338
360
|
helpers.ts a cookie-keeping test browser
|
|
339
361
|
e2e/responsive.test.ts browser tests at phone/tablet/desktop widths (Playwright)
|
|
340
362
|
e2e/code-editor.test.ts the code editor in a browser: highlighting, keys, suggestions, touch, screen readers
|
|
363
|
+
e2e/worksheet.test.ts (sprint 91) the SQL Worksheet in a browser: run keys, tabs, navigator, drag and drop, splitters, phone, no JavaScript
|
|
341
364
|
e2e/items.test.ts sprint 26 item types in a browser: editors, tags, stars, dates, reveal; without JavaScript
|
|
342
365
|
e2e/designer.test.ts page designer: panes per width, drag and drop, keyboard, Arrange buttons, builder theme
|
|
343
366
|
e2e/calendar.test.ts calendar drag and drop and create on click, view switching, chart drill-down
|
package/docs/guide/16-files.md
CHANGED
|
@@ -265,7 +265,7 @@ parsing, type inference and limits.)
|
|
|
265
265
|
3. The result shows the rows inserted, updated and skipped, with a link to the table in the
|
|
266
266
|
Object Browser.
|
|
267
267
|
|
|
268
|
-
Load Data runs as the builder's owner connection, like SQL
|
|
268
|
+
Load Data runs as the builder's owner connection, like the SQL Worksheet.
|
|
269
269
|
|
|
270
270
|
### SQL Workshop → Unload Data
|
|
271
271
|
|
|
@@ -352,7 +352,7 @@ Rows to develop and test with (APEX 26.1's Data Generator / "sample data for dev
|
|
|
352
352
|
Definitions are shared by the developers of the installation, like SQL scripts, and not part of
|
|
353
353
|
an application export (they describe tables, not an application).
|
|
354
354
|
|
|
355
|
-
Sample Data runs as the builder's owner connection (like SQL
|
|
355
|
+
Sample Data runs as the builder's owner connection (like the SQL Worksheet: triggers run, row level
|
|
356
356
|
security doesn't apply to the owner), on a connection of its own that is closed afterwards, with a
|
|
357
357
|
statement timeout (`SAMPLE_DATA_STATEMENT_TIMEOUT`, default `5min`) and at most
|
|
358
358
|
`SAMPLE_DATA_MAX_ROWS` rows per run (default 100,000). Table and column names come from the
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 61: a Form Designer (docs/guide/04-pages-and-regions.md,
|
|
3
|
+
-- "Form Designer")
|
|
4
|
+
--
|
|
5
|
+
-- Page 45 "Forms": the application's administrators (authorization scheme
|
|
6
|
+
-- ADMIN) build data entry forms on three tables the developer offers in the
|
|
7
|
+
-- region's settings, and publish them for every employee:
|
|
8
|
+
-- - "Departments": hr.dept, add and change rows;
|
|
9
|
+
-- - "Meetings": hr.meeting, add, change and delete rows; its row level
|
|
10
|
+
-- security still applies (an employee changes only their own meetings);
|
|
11
|
+
-- - "Employees": hr.emp, change rows only, with the job and department
|
|
12
|
+
-- as lists of values.
|
|
13
|
+
-- Two published forms are there to start from: "Departments" (a list of
|
|
14
|
+
-- departments with Add and Edit) and "Book a meeting" (no list: the form
|
|
15
|
+
-- only adds rows). Forms are installation data: not part of the export.
|
|
16
|
+
-- =====================================================================
|
|
17
|
+
|
|
18
|
+
insert into meta.page (app_id, page_no, name, title, parent_page)
|
|
19
|
+
select id, 45, 'Forms', 'Forms', 1 from meta.app where alias = 'hr';
|
|
20
|
+
|
|
21
|
+
insert into meta.region (page_id, seq, title, type, columns, template, config)
|
|
22
|
+
select p.id, 10, 'Form Designer', 'form_designer', 12, 'standard', $j${
|
|
23
|
+
"design_authz": "ADMIN",
|
|
24
|
+
"page_size": 15,
|
|
25
|
+
"sources": [
|
|
26
|
+
{"id": "departments", "label": "Departments", "description": "Department names and locations.",
|
|
27
|
+
"schema": "hr", "table": "dept", "key": "deptno", "insert": true, "update": true, "delete": false,
|
|
28
|
+
"columns": [{"name": "dname", "label": "Department"}, {"name": "loc", "label": "Location"}]},
|
|
29
|
+
{"id": "meetings", "label": "Meetings", "description": "Meetings in the planner; employees change only their own.",
|
|
30
|
+
"schema": "hr", "table": "meeting", "key": "id", "insert": true, "update": true, "delete": true,
|
|
31
|
+
"columns": [{"name": "title", "label": "Title"}, {"name": "starts_at", "label": "Starts"}, {"name": "ends_at", "label": "Ends"}, {"name": "private", "label": "Private"}]},
|
|
32
|
+
{"id": "employees", "label": "Employees", "description": "Job, department and salary of employees.",
|
|
33
|
+
"schema": "hr", "table": "emp", "key": "empno", "insert": false, "update": true, "delete": false,
|
|
34
|
+
"columns": [{"name": "ename", "label": "Name"}, {"name": "job", "label": "Job", "lov": "LOV:JOBS"}, {"name": "deptno", "label": "Department", "lov": "LOV:DEPARTMENTS"},
|
|
35
|
+
{"name": "hiredate", "label": "Hire date"}, {"name": "sal", "label": "Salary"}]}
|
|
36
|
+
]}$j$::jsonb
|
|
37
|
+
from meta.page p join meta.app a on a.id = p.app_id
|
|
38
|
+
where a.alias = 'hr' and p.page_no = 45;
|
|
39
|
+
|
|
40
|
+
-- two published forms to start from (forms are installation data: not part of the export)
|
|
41
|
+
insert into meta.user_form (app_id, region_id, name, description, published, definition, created_by, updated_by)
|
|
42
|
+
select a.id, r.id, f.name, f.description, true, f.definition, 'king', 'king'
|
|
43
|
+
from meta.app a join meta.page p on p.app_id = a.id and p.page_no = 45 join meta.region r on r.page_id = p.id and r.type = 'form_designer',
|
|
44
|
+
(values
|
|
45
|
+
('Departments', 'Add a department or change its name and location.',
|
|
46
|
+
'{"source": "departments",
|
|
47
|
+
"fields": [{"column": "dname", "label": "", "help": "", "required": true, "display": "auto", "default": ""},
|
|
48
|
+
{"column": "loc", "label": "", "help": "The city.", "required": false, "display": "auto", "default": ""}],
|
|
49
|
+
"list": ["dname", "loc"], "actions": {"insert": true, "update": true, "delete": false}}'::jsonb),
|
|
50
|
+
('Book a meeting', 'Put a meeting in the planner.',
|
|
51
|
+
'{"source": "meetings",
|
|
52
|
+
"fields": [{"column": "title", "label": "What", "help": "", "required": true, "display": "auto", "default": ""},
|
|
53
|
+
{"column": "starts_at", "label": "", "help": "", "required": true, "display": "auto", "default": ""},
|
|
54
|
+
{"column": "ends_at", "label": "", "help": "Leave empty for a meeting without an end.", "required": false, "display": "auto", "default": ""},
|
|
55
|
+
{"column": "private", "label": "Only visible to me", "help": "", "required": false, "display": "auto", "default": "false"}],
|
|
56
|
+
"list": [], "actions": {"insert": true, "update": false, "delete": false}}'::jsonb)
|
|
57
|
+
) as f (name, description, definition)
|
|
58
|
+
where a.alias = 'hr';
|
|
59
|
+
|
|
60
|
+
insert into meta.nav_entry (app_id, parent_id, seq, label, icon, target_page)
|
|
61
|
+
select a.id, n.id, 60, 'Forms', 'file', 45
|
|
62
|
+
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
|
|
63
|
+
where a.alias = 'hr';
|
|
64
|
+
|
|
65
|
+
insert into meta.translation (app_id, language, source, target)
|
|
66
|
+
select a.id, 'nl', t.source, t.target
|
|
67
|
+
from meta.app a, (values
|
|
68
|
+
('Forms', 'Formulieren'),
|
|
69
|
+
('Form Designer', 'Formulierbouwer'),
|
|
70
|
+
('Department names and locations.', 'Namen en vestigingsplaatsen van afdelingen.'),
|
|
71
|
+
('Meetings', 'Vergaderingen'),
|
|
72
|
+
('Meetings in the planner; employees change only their own.', 'Vergaderingen in de planner; medewerkers wijzigen alleen hun eigen.'),
|
|
73
|
+
('Job, department and salary of employees.', 'Functie, afdeling en salaris van medewerkers.'),
|
|
74
|
+
('Title', 'Titel'),
|
|
75
|
+
('Starts', 'Begint'),
|
|
76
|
+
('Ends', 'Eindigt'),
|
|
77
|
+
('Private', 'Privé')
|
|
78
|
+
) as t (source, target)
|
|
79
|
+
where a.alias = 'hr'
|
|
80
|
+
on conflict do nothing;
|
package/package.json
CHANGED
package/public/app.css
CHANGED
|
@@ -1152,6 +1152,13 @@ td[data-add]:hover > .cal-add, .cal-add:focus-visible { opacity: 1; }
|
|
|
1152
1152
|
.facet { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
|
|
1153
1153
|
.facet legend { font-weight: 650; font-size: 0.85rem; margin-bottom: 0.3rem; padding: 0; display: flex; gap: 0.5rem; }
|
|
1154
1154
|
.facet-clear { font-weight: 400; font-size: 0.8rem; }
|
|
1155
|
+
.facet:has(> .facet-menu) { position: relative; }
|
|
1156
|
+
.facet:has(> .facet-menu) > legend { padding-right: 1.6rem; }
|
|
1157
|
+
.facet-menu { position: absolute; top: 0; right: 0; }
|
|
1158
|
+
.facet-menu > summary { cursor: pointer; padding: 0 0.35rem; border-radius: 4px; color: var(--muted); }
|
|
1159
|
+
.facet-menu > summary:hover, .facet-menu[open] > summary { background: var(--surface-3); color: inherit; }
|
|
1160
|
+
.facet-menu .menu-panel { left: auto; right: 0; min-width: 11rem; }
|
|
1161
|
+
.facet-menu .menu-links a[aria-current] { font-weight: 650; }
|
|
1155
1162
|
.facet-value { font-weight: 400; font-size: 0.9rem; }
|
|
1156
1163
|
/* (sprint 65) a facet's chart of its counts, collapsed under the legend */
|
|
1157
1164
|
.facet-chart { margin: 0.1rem 0 0.4rem; }
|
|
@@ -1965,9 +1972,25 @@ a.search-hit:hover, a.search-hit:focus-visible { background: var(--accent-soft);
|
|
|
1965
1972
|
.th-menu-values li.muted { padding: 0.35rem 0.9rem; }
|
|
1966
1973
|
.js th[data-col] > a { cursor: pointer; }
|
|
1967
1974
|
|
|
1968
|
-
/* (sprint 76) a date item in the application's date format
|
|
1975
|
+
/* (sprint 76) a date item in the application's date format */
|
|
1969
1976
|
.date-field { position: relative; }
|
|
1970
|
-
|
|
1977
|
+
/* (sprint 84; APEX: the date picker) a month of days under a date field (app.js) */
|
|
1978
|
+
.date-picker { position: absolute; top: calc(100% + 4px); left: 0; z-index: 60; background: var(--surface); color: var(--text);
|
|
1979
|
+
border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 8px 24px rgb(16 24 40 / 18%); padding: 0.5rem; min-width: 17rem; }
|
|
1980
|
+
.date-picker-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.35rem; }
|
|
1981
|
+
.date-picker-title { font-weight: 650; text-transform: capitalize; }
|
|
1982
|
+
.date-picker-grid { border-collapse: collapse; width: 100%; }
|
|
1983
|
+
.date-picker-grid th { font-size: 0.75rem; font-weight: 600; color: var(--muted); padding: 0.2rem 0; text-align: center; }
|
|
1984
|
+
.date-picker-grid td { padding: 1px; text-align: center; }
|
|
1985
|
+
.date-picker-grid button { width: 2.2rem; height: 2.1rem; border: 0; border-radius: 6px; background: none; color: inherit; font: inherit; cursor: pointer; }
|
|
1986
|
+
.date-picker-grid button:hover { background: var(--surface-3); }
|
|
1987
|
+
.date-picker-grid button.date-other { color: var(--muted); }
|
|
1988
|
+
.date-picker-grid button.date-today { box-shadow: inset 0 0 0 1px var(--accent); }
|
|
1989
|
+
.date-picker-grid button.date-selected { background: var(--accent); color: var(--accent-text); }
|
|
1990
|
+
.date-picker-grid button:disabled { opacity: 0.35; cursor: not-allowed; }
|
|
1991
|
+
.date-picker-grid button:focus-visible { outline: 2px solid var(--accent); outline-offset: 0; }
|
|
1992
|
+
.date-picker-foot { display: flex; justify-content: space-between; margin-top: 0.4rem; }
|
|
1993
|
+
.js .grid-table tbody td[data-col]:not(:focus-within) .date-field > .btn { visibility: hidden; }
|
|
1971
1994
|
|
|
1972
1995
|
/* (sprint 77) display positions: banner, breadcrumb bar, a side column, footer; button positions in a region */
|
|
1973
1996
|
.t-pos-banner, .t-pos-breadcrumb { margin-bottom: 1rem; }
|
|
@@ -1982,3 +2005,40 @@ a.search-hit:hover, a.search-hit:focus-visible { background: var(--accent-soft);
|
|
|
1982
2005
|
@media (max-width: 900px) { .t-columns-left, .t-columns-right { grid-template-columns: 1fr; } .t-columns-right > .t-side { order: 0; } }
|
|
1983
2006
|
.region-footer-split { display: flex; justify-content: space-between; gap: 0.5rem; flex-wrap: wrap; }
|
|
1984
2007
|
.region-footer-split > div { display: flex; gap: 0.5rem; }
|
|
2008
|
+
|
|
2009
|
+
/* (sprint 85) the Form Designer region (form-designer.ts): forms as cards with a Design link, the designer's field table, a row's form */
|
|
2010
|
+
.fd-cards > li { display: flex; flex-direction: column; gap: 0.25rem; }
|
|
2011
|
+
.fd-design-link { font-size: 0.85rem; display: inline-flex; align-items: center; gap: 0.3rem; }
|
|
2012
|
+
.fd-fields input.fd-seq { width: 4.5rem; }
|
|
2013
|
+
.fd-fields td select { min-width: 8rem; }
|
|
2014
|
+
.fd-fields td input:not([type=checkbox]):not(.fd-seq) { min-width: 6rem; width: 100%; }
|
|
2015
|
+
.fd-form { margin: 0.75rem 0; }
|
|
2016
|
+
.fd-radios { border: 0; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.25rem; }
|
|
2017
|
+
.fd-new { margin-top: 1rem; }
|
|
2018
|
+
|
|
2019
|
+
/* (sprint 88, GEN-4; APEX: the Cards list template and the hero region) a list's cards each get a colour band
|
|
2020
|
+
and their icon in a round badge, in the chart palette's order; the hero sits on the page background */
|
|
2021
|
+
.list-cards > li { --card-color: var(--series-1); }
|
|
2022
|
+
.list-cards > li:nth-child(8n+2) { --card-color: var(--series-2); }
|
|
2023
|
+
.list-cards > li:nth-child(8n+3) { --card-color: var(--series-3); }
|
|
2024
|
+
.list-cards > li:nth-child(8n+4) { --card-color: var(--series-4); }
|
|
2025
|
+
.list-cards > li:nth-child(8n+5) { --card-color: var(--series-5); }
|
|
2026
|
+
.list-cards > li:nth-child(8n+6) { --card-color: var(--series-6); }
|
|
2027
|
+
.list-cards > li:nth-child(8n+7) { --card-color: var(--series-7); }
|
|
2028
|
+
.list-cards > li:nth-child(8n+8) { --card-color: var(--series-8); }
|
|
2029
|
+
.list-card { border-top: 4px solid var(--card-color); }
|
|
2030
|
+
.list-card-icon { flex: none; display: grid; place-items: center; width: 2.5rem; height: 2.5rem; border-radius: 50%;
|
|
2031
|
+
background: color-mix(in srgb, var(--card-color) 16%, transparent); color: var(--card-color); }
|
|
2032
|
+
.list-card-icon .icon { width: 1.3rem; height: 1.3rem; }
|
|
2033
|
+
.list-card-head { gap: 0.75rem; }
|
|
2034
|
+
.region-hero { background: transparent; padding-left: 0; padding-right: 0; }
|
|
2035
|
+
|
|
2036
|
+
/* (sprint 88, GEN-4; APEX: the page footer) the application's version and Back to top, under the content */
|
|
2037
|
+
.t-footer { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 0.5rem 1rem;
|
|
2038
|
+
margin: 0 1.5rem 1rem; max-width: calc(1500px - 3rem); padding: 0.75rem 0 0.25rem; border-top: 1px solid var(--border); color: var(--muted); font-size: 0.85rem; }
|
|
2039
|
+
@media (max-width: 640px) { .t-footer { margin: 0 0.75rem 1rem; } }
|
|
2040
|
+
.t-footer .t-top { margin-left: auto; display: inline-flex; align-items: center; gap: 0.3rem; color: inherit; }
|
|
2041
|
+
.t-footer .t-top:hover, .t-footer .t-top:focus-visible { color: var(--accent); }
|
|
2042
|
+
.t-footer .icon { width: 1rem; height: 1rem; }
|
|
2043
|
+
@media print { .t-footer { display: none; } }
|
|
2044
|
+
.list-card:hover { border-top-color: var(--card-color); }
|