pgkiln 0.88.0 → 0.90.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/docs/guide/03-builder.md +77 -36
- package/package.json +1 -1
- package/public/builder.css +91 -3
- package/public/builder.js +101 -2
- package/public/palette.js +8 -1
- package/public/worksheet.css +39 -4
- package/public/worksheet.js +15 -1
- package/src/builder/appwizard.ts +45 -22
- package/src/builder/datasets.ts +12 -11
- package/src/builder/designer.ts +15 -6
- package/src/builder/errors.ts +44 -6
- package/src/builder/gallery.ts +5 -5
- package/src/builder/home.ts +78 -67
- package/src/builder/i18n/de.ts +40 -5
- package/src/builder/i18n/en.ts +41 -6
- package/src/builder/i18n/es.ts +41 -6
- package/src/builder/i18n/fr.ts +41 -6
- package/src/builder/i18n/it.ts +41 -6
- package/src/builder/i18n/ja.ts +37 -2
- package/src/builder/i18n/ko.ts +37 -2
- package/src/builder/i18n/nl.ts +41 -6
- package/src/builder/i18n/pt.ts +41 -6
- package/src/builder/i18n/zh-cn.ts +37 -2
- package/src/builder/i18n/zh-tw.ts +37 -2
- package/src/builder/routes.ts +40 -16
- package/src/builder/sql.ts +2 -2
- package/src/builder/ui.ts +47 -33
- package/src/builder/users.ts +15 -13
- package/src/builder/worksheet.ts +33 -24
- package/src/builder/workspace-home.ts +17 -0
- package/src/session.ts +2 -0
package/docs/guide/03-builder.md
CHANGED
|
@@ -225,9 +225,16 @@ department. Every installation has the **Default** workspace, which holds the ap
|
|
|
225
225
|
- **New applications** (Create, from a file, pasted data, existing tables, a blueprint) and **imports** go into the
|
|
226
226
|
current workspace; a working copy stays in its main application's workspace. Imports made with the command line
|
|
227
227
|
(`pgkiln import`) go into Default. Exports don't carry the workspace.
|
|
228
|
-
- **Workspace utilities
|
|
229
|
-
|
|
230
|
-
|
|
228
|
+
- **Workspace utilities** (the rail's **Administration**, `/builder/utilities`; UX-4): the utilities in sections with a
|
|
229
|
+
**filter** field at the top: *People and sign-in* (users, developers, identity providers, LDAP directories, password
|
|
230
|
+
policy), *Connections* ([remote servers, web credentials](#static-workspace-files-remote-servers-and-web-credentials),
|
|
231
|
+
remote databases, REST source catalogs, mail services; for workspace administrators the mail queue and network
|
|
232
|
+
access), *Data and AI* (AI services, vector providers, UI defaults, static workspace files, icons), *Operations*
|
|
233
|
+
(application groups, backups, export and import, the dashboard, OpenTelemetry), the current **workspace** (for its
|
|
234
|
+
administrators: its page, [workspace message and environment banner](#workspace-message-and-environment-banner),
|
|
235
|
+
preferences, extension links, [workspace utilization](#workspace-utilization)) and the **instance** (for
|
|
236
|
+
administrators: instance settings, workspaces, [Database users](#database-users), the installation), then
|
|
237
|
+
**Cross Application Reports** (APEX: Cross Application Reports), each over
|
|
231
238
|
every application of the current workspace: *Application attributes* (type, sign-in, availability, build status,
|
|
232
239
|
debug level, language, database role), *Build options*, *Developer comments*, *Authentication* (sign-in methods,
|
|
233
240
|
identity providers, LDAP, access, the login page, Forgot password), *Authorization schemes*, *Password items* and
|
|
@@ -247,7 +254,7 @@ its developers must not reach other teams' applications or data.
|
|
|
247
254
|
### Workspace message and environment banner
|
|
248
255
|
|
|
249
256
|
Administrators and the workspace's administrators set them on the workspace's page (*Manage service*, or the
|
|
250
|
-
Administration
|
|
257
|
+
Administration → *Workspace message and environment banner*; APEX: Define Workspace Message, Define Environment
|
|
251
258
|
Banner): a **message** (plain text, up to 2000 characters) shown to the workspace's developers on the
|
|
252
259
|
[Workspace home](#workspace-home) (where they can also edit it in place) and the App Builder home,
|
|
253
260
|
and an **environment banner**: a label of up to 40 characters in one of eight colours, shown at the top of every
|
|
@@ -255,7 +262,7 @@ builder page while the workspace is current.
|
|
|
255
262
|
|
|
256
263
|
### Workspace preferences
|
|
257
264
|
|
|
258
|
-
Administrators and the workspace's administrators set them on the workspace's page (Administration
|
|
265
|
+
Administrators and the workspace's administrators set them on the workspace's page (Administration →
|
|
259
266
|
*Workspace preferences*; APEX: Set Workspace Preferences):
|
|
260
267
|
|
|
261
268
|
- **Enable the SQL Workshop**: off, every SQL Workshop page (the SQL Worksheet, the Object Browser, SQL scripts, Load and
|
|
@@ -349,7 +356,7 @@ says what was added, kept and replaced.
|
|
|
349
356
|
|
|
350
357
|
### Workspace utilization
|
|
351
358
|
|
|
352
|
-
Administration
|
|
359
|
+
Administration → **Workspace utilization** (`/builder/workspaces/<id>/utilization`; APEX: Workspace
|
|
353
360
|
Utilization), for administrators and the workspace's administrators: the number of applications, pages, developers,
|
|
354
361
|
page views of the last 30 days and SQL scripts; per application its pages, regions, items, page views, users and the
|
|
355
362
|
size of its static files; and the schemas the workspace's database roles use with their tables and storage.
|
|
@@ -462,11 +469,14 @@ longer sign in. Delete a workspace only when it is shared and empty; its logins
|
|
|
462
469
|
Every builder page has the same frame, modelled on an IDE:
|
|
463
470
|
|
|
464
471
|
- **The icon rail** on the left: Home (the [Workspace home](#workspace-home); the logo leads there too), App Builder,
|
|
465
|
-
SQL Workshop, [App Gallery](#app-gallery), [Data Reporter](#data-reporter-datasets-and-reporting-applications),
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
472
|
+
SQL Workshop, [App Gallery](#app-gallery), [Data Reporter](#data-reporter-datasets-and-reporting-applications),
|
|
473
|
+
Team Development and **Administration** (the workspace utilities, below); at the bottom Help, **Show labels** and your
|
|
474
|
+
account menu (the circle with your initials). (UX-3) The entry of the page you are on is marked, by the page's
|
|
475
|
+
address (an application's pages under App Builder; users, developers, workspaces, instance settings and the other
|
|
476
|
+
utilities under Administration). *Show labels* puts each entry's name next to its icon (*Hide labels* takes them
|
|
477
|
+
away; kept for your session); without them the name shows when you point at an icon or move to it with the
|
|
478
|
+
keyboard. The search is the field at the top of every page (**Ctrl+K**); an application's own search is its
|
|
479
|
+
**Search** tab. Your account menu has the
|
|
470
480
|
**builder theme** (*Dark*, the default; *Light*; or *System*, which follows the operating system),
|
|
471
481
|
Change password and Sign out. The theme is remembered on this device, also after signing out;
|
|
472
482
|
it only changes the builder, not your applications.
|
|
@@ -494,7 +504,9 @@ other sites.
|
|
|
494
504
|
(sprint 86) An address under `/builder` that leads nowhere (a deleted application or page, a typo, an
|
|
495
505
|
application of a workspace you are not a member of) shows a **Page not found** page in the builder's
|
|
496
506
|
theme with a link to the App Builder home; a server error shows **Something went wrong** (the error
|
|
497
|
-
is in the server log, not on the page). Scripts get the plain answers they always got.
|
|
507
|
+
is in the server log, not on the page). Scripts get the plain answers they always got. (UX-10) Signed in, the
|
|
508
|
+
page stays inside the builder (rail, header search, status bar), with a search field filled with the words of the
|
|
509
|
+
address and your recent applications. `/builder/apps/new` and `/builder/apps/create` lead to Create application.
|
|
498
510
|
|
|
499
511
|
### Choosing the builder language
|
|
500
512
|
|
|
@@ -548,6 +560,8 @@ names and help of a shared component's form), the links to this guide, and any S
|
|
|
548
560
|
(sprint 109; APEX: the workspace home page) The page the builder opens after signing in, `/builder/home`, above the
|
|
549
561
|
App Builder and the SQL Workshop. Everything on it is of the current [workspace](#workspaces), and it works without
|
|
550
562
|
JavaScript. The [default-password banner](#signing-in-and-developer-accounts) stays on top until the password is changed.
|
|
563
|
+
(UX-13) For administrators, **Needs attention** under the heading lists what must not wait: failing
|
|
564
|
+
[lockout checks](#lockout-checks) and, with the release check on, a newer pgkiln (details in the System message).
|
|
551
565
|
|
|
552
566
|
- **Tool cards**: App Builder (`/builder/apps`), SQL Workshop (not when the workspace's preferences turn it off),
|
|
553
567
|
**Data Reporter** (when the server has the Data Reporter tool at `/builder/reporter`) and the [App Gallery](#app-gallery).
|
|
@@ -582,12 +596,13 @@ that may not reach the internet. Under the test runner the server never asks Git
|
|
|
582
596
|
The App Builder's home page, `/builder/apps` (`/builder` shows it too, unless the workspace's
|
|
583
597
|
[preferences](#workspace-preferences) send `/builder` to the Workspace home), is laid out like APEX's App Builder:
|
|
584
598
|
|
|
585
|
-
- **
|
|
586
|
-
(applications, pages, active accounts; page views, users, failed
|
|
587
|
-
24 hours, and per application the last 7 days with a link to its Activity page)
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
599
|
+
- **The header** (UX-5): the heading *App Builder*, **Create** (the page's one primary action), **Import** and a
|
|
600
|
+
**⋯** menu with the workspace **Dashboard** (applications, pages, active accounts; page views, users, failed
|
|
601
|
+
sign-ins and errors of the last 24 hours, and per application the last 7 days with a link to its Activity page)
|
|
602
|
+
and **Administration** (the [workspace utilities](#workspaces)). The list, the dashboard and Recent show the
|
|
603
|
+
current workspace. Every builder page follows the same pattern: one heading, one primary (accent) button,
|
|
604
|
+
the other actions beside it or in a ⋯ menu; an application's Pages tab has **Create page** as its primary action
|
|
605
|
+
in the application's header (other tabs: **Run**), and its Tasks list only what the page doesn't offer already.
|
|
591
606
|
- **The applications**, as a report (application id, name, alias, pages, page views of the last 24
|
|
592
607
|
hours, sign-in, last updated, **updated by**, **working copies**, and Edit / Run) or as cards. **Search** filters the list as you type
|
|
593
608
|
(by name, alias or id; without JavaScript, press Enter). Click a column heading, or use
|
|
@@ -714,19 +729,22 @@ you like. `meta.reporting_app_count(workspace_id)` counts a workspace's reportin
|
|
|
714
729
|
|
|
715
730
|
### Creating an application
|
|
716
731
|
|
|
717
|
-
**Create** (
|
|
718
|
-
start from a file, pasted data, a blueprint or the App Gallery instead.
|
|
732
|
+
**Create** (the App Builder's primary button, or `/builder/create`) is one page, like APEX's Create Application
|
|
733
|
+
wizard. Links at the top start from a file, pasted data, a blueprint or the App Gallery instead. (UX-7) The name
|
|
734
|
+
comes first, then the data and its pages, features, appearance and sign-in; **Create application** and Cancel stay
|
|
735
|
+
in a footer at the bottom of the window, next to a one-line summary (*12 pages on hr · App users (login page) ·
|
|
736
|
+
English*) that follows your choices. The sections:
|
|
719
737
|
|
|
720
738
|
| Section | Field | Meaning |
|
|
721
739
|
|---|---|---|
|
|
722
|
-
| Data | Pages on the tables of | The parsing schema: an existing schema, or a new, empty one named after the alias. (sprint 87) Choosing a schema lists its tables under Pages at once, with the name and alias proposed for it (unless you already typed them); without JavaScript, *Show its tables* does it |
|
|
723
740
|
| Application | Name | Display name, shown in the header. Proposed from the schema and selected when you click it, so typing replaces it |
|
|
724
|
-
| |
|
|
725
|
-
|
|
|
726
|
-
|
|
|
741
|
+
| | Alias | Lowercase URL name: `inventory` gives `/a/inventory`. (sprint 87) It follows the name as you type it (*Café Orders* → `cafe-orders`) until you change it yourself |
|
|
742
|
+
| | Icon, Description | Shown on the home page's hero and the About page |
|
|
743
|
+
| Data and pages | Pages on the tables of | The parsing schema: an existing schema, or a new, empty one named after the alias. (sprint 87) Choosing a schema lists its tables at once, with the name and alias proposed for it (unless you already typed them); without JavaScript, *Show its tables* does it (it reloads the page, so choose the schema before typing) |
|
|
744
|
+
| | a page type per table | *Interactive report with form*, *Faceted search*, *Cards*, *Calendar* (a table with a date column), *Master detail* (a table another table refers to), *Interactive grid*, *Report* or *No page*. Tables with a single-column primary key get a modal form with their report, faceted search, cards and calendar; views and tables without such a key can't have a form or a grid. Proposed: a report with form, an interactive grid for a table of more than 100,000 rows, a report for a view. (sprint 87) A table with [row level security](08-security.md) shows *RLS · n* (its number of policies), or *RLS, no policies* in red: the application's role would see no rows. *Dashboard*: a chart per table |
|
|
727
745
|
| Features | | *About page* (in a Help menu), *Access control* (authorization schemes `CONTRIBUTOR` and `READER` next to `ADMIN`, for the roles admin, contributor and reader), *Activity reporting* (page 9010 for administrators: page views per day and the latest activity, from `meta.app_activity`), *Feedback*, *Theme style selection* (users may choose light or dark), *Install as an app* (PWA) and *Push notifications* (needs `PGKILN_SECRET_KEY`) |
|
|
728
|
-
|
|
|
729
|
-
| | Authentication | *App users* (a login page and a user list), *HTTP header* (a trusted reverse proxy names the user, see [chapter 8](08-security.md#http-header-authentication-reverse-proxy)), *Database accounts* (PostgreSQL login roles and their passwords, see [chapter 8](08-security.md#database-accounts-postgresql-roles)), *Custom* (your own PL/pgSQL function, see [chapter 8](08-security.md#custom-authentication-a-plpgsql-function)) or *None* (a public app) |
|
|
746
|
+
| Appearance | Theme style, Navigation, Accent colour | Iris, Lavender or Standard; a side or top menu; an own accent colour |
|
|
747
|
+
| Sign-in | Authentication | *App users* (a login page and a user list), *HTTP header* (a trusted reverse proxy names the user, see [chapter 8](08-security.md#http-header-authentication-reverse-proxy)), *Database accounts* (PostgreSQL login roles and their passwords, see [chapter 8](08-security.md#database-accounts-postgresql-roles)), *Custom* (your own PL/pgSQL function, see [chapter 8](08-security.md#custom-authentication-a-plpgsql-function)) or *None* (a public app) |
|
|
730
748
|
| | First user / Password | The first user; they get the `admin` role. An existing account is reused (its password isn't changed). (sprint 87) Hidden while Authentication is *None* |
|
|
731
749
|
| | Language | The application's primary language |
|
|
732
750
|
| Advanced | Start from | Shown when there are **boilerplate** applications (Settings → Application type): their pages, shared components and settings are copied into the new application, which keeps its own name, alias, role and authentication ([application types](#application-types-and-subscriptions)) |
|
|
@@ -940,7 +958,11 @@ they are **not** run until you choose to.
|
|
|
940
958
|
(sprint 74) Above the pages: a **search** by page name or number (the rows filter while you type),
|
|
941
959
|
**Report** and **Icons** views, and **Create page** (the page wizards below). **Tasks** on the right: run the
|
|
942
960
|
application, create pages (also with AI), (sprint 137) **Add feature page** and **Create page as copy**, Shared
|
|
943
|
-
Components, page groups, export, backups, supporting objects
|
|
961
|
+
Components, page groups, export, backups, supporting objects and lock the application. (UX-9) The report shows a
|
|
962
|
+
page's mode in the property editor's words (*Normal*, *Modal dialog*), and Group stays empty for a page without one.
|
|
963
|
+
(UX-6) Deleting the application is not a task: it is the **Danger zone** at the bottom of Settings → Definition,
|
|
964
|
+
where you type the application's alias to confirm (the server checks it too). Its pages, shared components,
|
|
965
|
+
working copies and backups go with it, so export it first if you may need it again.
|
|
944
966
|
|
|
945
967
|
The **default-password** warning shows on the App Builder home (and Developers) only; dismiss it with × until the
|
|
946
968
|
next sign-in. The avatar keeps a red dot until the password is changed.
|
|
@@ -1072,6 +1094,16 @@ The page designer has three panes, as in APEX's Page Designer. From 1024 pixels
|
|
|
1072
1094
|
side by side; on phones and portrait tablets they are tabs (**Tree**, **Layout**, **Properties**):
|
|
1073
1095
|
choosing a component opens the Properties tab.
|
|
1074
1096
|
|
|
1097
|
+
(UX-2) From 1024 pixels wide the designer is fixed to the window: only the panes scroll, so the toolbar
|
|
1098
|
+
with **Save**, Undo / Redo and the page navigator stays in view, and the property editor's own save button
|
|
1099
|
+
stays at the bottom of its pane. **Ctrl+S** (Cmd+S) saves. A change not saved yet puts a dot on Save, and
|
|
1100
|
+
leaving the page with one asks first. On a window under 800 pixels high the gallery under the layout is only
|
|
1101
|
+
its tabs (*Regions*, *Items*, *Buttons*): choose one to open it, choose the open one to fold it again; the
|
|
1102
|
+
designer remembers which you prefer.
|
|
1103
|
+
|
|
1104
|
+
(UX-6) **Delete page** is in the toolbar's page menu (the wrench), away from Save: type the page number to
|
|
1105
|
+
confirm. Its regions, items, buttons, dynamic actions and processes go with it, as do its lock and comments.
|
|
1106
|
+
|
|
1075
1107
|
- **Left: the component tree**, with four tabs:
|
|
1076
1108
|
- **Rendering**: the page, *Pre-Rendering* (branches, computations and processes that run
|
|
1077
1109
|
before the page is shown), the regions by
|
|
@@ -1087,7 +1119,8 @@ choosing a component opens the Properties tab.
|
|
|
1087
1119
|
application items), linking to Shared Components.
|
|
1088
1120
|
|
|
1089
1121
|
Icons show which components have an authorization scheme, a condition or a build option; type icons are coloured
|
|
1090
|
-
by kind (regions, items, buttons, dynamic actions, processes).
|
|
1122
|
+
by kind (regions, items, buttons, dynamic actions, processes). (UX-12) A component's name comes first: when the
|
|
1123
|
+
pane is narrow its type text is left out (the icon shows the type; hover a component for its name and type). Folders open and close
|
|
1091
1124
|
with a click; with the keyboard, the arrow keys walk the tree (Left / Right close and open a
|
|
1092
1125
|
folder), Enter opens a component.
|
|
1093
1126
|
|
|
@@ -1581,7 +1614,8 @@ on import), downloads it again and removes it. See
|
|
|
1581
1614
|
## Users (the user directory)
|
|
1582
1615
|
|
|
1583
1616
|
**Builder → Users** lists every account with the applications (and roles) it can use. Create
|
|
1584
|
-
accounts here (a password is optional for single sign-on-only accounts)
|
|
1617
|
+
accounts here (a password is optional for single sign-on-only accounts): (UX-8) **Create account** in the header
|
|
1618
|
+
opens a side panel, so the list keeps the page's width. Open an account to edit
|
|
1585
1619
|
its name and e-mail, set or remove its password, deactivate or delete it, and grant, change or
|
|
1586
1620
|
revoke access per application. Password changes, deactivation and access changes end the
|
|
1587
1621
|
affected sessions. See [chapter 8](08-security.md#the-user-directory).
|
|
@@ -1790,7 +1824,7 @@ sending mailboxes (an application access policy or RBAC for Applications).
|
|
|
1790
1824
|
|
|
1791
1825
|
### Mail queue
|
|
1792
1826
|
|
|
1793
|
-
Administration
|
|
1827
|
+
Administration → **Mail queue** (`/builder/mail-queue`; APEX 26.2: Administration → Mail queue): the delivery
|
|
1794
1828
|
health of the mail queue, for administrators (every workspace, or one) and a workspace's administrators (the current
|
|
1795
1829
|
workspace's applications only; other developers get 403). Filter by period (24 hours, 48 hours, 7 days), workspace and
|
|
1796
1830
|
application:
|
|
@@ -1835,7 +1869,7 @@ applications off or send them to a collector of their own as well (through the a
|
|
|
1835
1869
|
|
|
1836
1870
|
### Network access
|
|
1837
1871
|
|
|
1838
|
-
Administration
|
|
1872
|
+
Administration → **Network access** (`/builder/network`; APEX 26.2: workspace network ACLs), for administrators
|
|
1839
1873
|
(any workspace, `?ws=`) and a workspace's administrators (their workspaces): which web services a workspace's
|
|
1840
1874
|
applications may call. The installation's list (`PGKILN_REST_ALLOWED_HOSTS`, set in the server's environment, shown
|
|
1841
1875
|
read-only; the private hosts to administrators only) applies to every workspace. A workspace can choose **Only the
|
|
@@ -2121,7 +2155,8 @@ others (Quick SQL, Query Builder, Load and Unload Data, Sample Data, DDL, Compar
|
|
|
2121
2155
|
|
|
2122
2156
|
(sprint 96; Database Actions: the Launchpad) The home page starts with **Pinned & recently visited**: the pages you
|
|
2123
2157
|
pinned (a tool, a database object from the Object Browser, a saved worksheet from its file menu; at most 24, per
|
|
2124
|
-
workspace) and the SQL Workshop pages you opened last in this browser
|
|
2158
|
+
workspace) and the SQL Workshop pages you opened last in this browser (UX-14: each page once, the latest visit
|
|
2159
|
+
first, without the Launchpad itself or a page that is pinned already). Below, the tools are in three tabs,
|
|
2125
2160
|
**Development**, **Administration** and **Monitoring**, each with a line about what it does and a pin; the **More**
|
|
2126
2161
|
menu uses the same headings.
|
|
2127
2162
|
|
|
@@ -2133,6 +2168,12 @@ the SQL Workshop's connection may use. Without JavaScript the header's field ope
|
|
|
2133
2168
|
- **SQL Worksheet** (`/builder/sql/worksheet`, sprint 91; Database Actions: SQL; was SQL Commands, whose address
|
|
2134
2169
|
`/builder/sql/commands` opens the same page): one screen with a **navigator** on the left, the **editor** with a
|
|
2135
2170
|
toolbar, **output tabs** below and a **status bar**.
|
|
2171
|
+
- (UX-1) The **toolbar is one row**: the worksheet's file menu, **Run Statement**, **Run Script**, **Explain Plan**
|
|
2172
|
+
and **Autotrace** (icons with their name as a tooltip when the window is narrow), **Suggest** and **Check**, the
|
|
2173
|
+
commit mode, fullscreen and a **⋯ menu** with *Continue on error*, Format, Convert case, Find, Load data, Clear
|
|
2174
|
+
and the keyboard shortcuts. **Run as** is in the status bar (its tooltip says who the statements run as).
|
|
2175
|
+
The editor takes about half of the window and the output the other half (drag the splitter; the size is
|
|
2176
|
+
remembered); before the first run the output is only its row of tabs, and choosing a tab opens it.
|
|
2136
2177
|
- The navigator has a schema (the same one as the Object Browser), an object type (*All Objects*, tables, views,
|
|
2137
2178
|
materialized views, indexes, sequences, types, functions, procedures, triggers, policies) and a search field.
|
|
2138
2179
|
A table or view opens to its columns with their types (a key marks the primary key); drag it into the editor
|
|
@@ -2217,11 +2258,11 @@ the SQL Workshop's connection may use. Without JavaScript the header's field ope
|
|
|
2217
2258
|
remembered for the session; the history and the log keep the statement as written.
|
|
2218
2259
|
- **Format** (**Ctrl + F7**) lays out the selection or the whole worksheet: a clause per line, the columns of a select
|
|
2219
2260
|
list and the conditions of a WHERE one per line, joins and subqueries indented, keywords in lower or UPPER case
|
|
2220
|
-
(chosen in the
|
|
2261
|
+
(chosen in the **⋯** menu). Strings, comments and `$$` bodies stay as they are. **Convert case** (**Ctrl + B**)
|
|
2221
2262
|
cycles the selection (or the word at the cursor) through UPPER, lower and Initial caps. **Find and replace**
|
|
2222
2263
|
(**Ctrl + F**, **Ctrl + H**) with a regular expression or exact case, **Go to line** (**Ctrl + G**), and
|
|
2223
|
-
**Ctrl + /** comments the selected lines out or in. The
|
|
2224
|
-
|
|
2264
|
+
**Ctrl + /** comments the selected lines out or in. The **⋯** menu lists every key; **F1** opens this help.
|
|
2265
|
+
*Load data* in the same menu opens Load Data for the navigator's schema.
|
|
2225
2266
|
- Each statement commits on its own (**Autocommit**, like Database Actions). Statements run as the **owner connection** (an isolated workspace's
|
|
2226
2267
|
developer login for its developers), on a connection of their own that is closed afterwards, so `SET ROLE` or
|
|
2227
2268
|
`SET search_path` doesn't leak. **Run as** picks an application of your workspace instead: the statements then
|
|
@@ -2414,7 +2455,7 @@ the SQL Workshop's connection may use. Without JavaScript the header's field ope
|
|
|
2414
2455
|
connections, memory, row security), the installed extensions and the schemas with their size.
|
|
2415
2456
|
- **SQL Monitor** (administrators only): see [SQL Monitor](#sql-monitor) below (it replaced the Database Monitor;
|
|
2416
2457
|
the old address `/builder/sql/utilities/monitor` redirects).
|
|
2417
|
-
- **Database Users** (administrators only; Administration
|
|
2458
|
+
- **Database Users** (administrators only; Administration → *Database users*, `/builder/db-users`; sprint 99,
|
|
2418
2459
|
Database Actions: Database Users): see [Database users](#database-users).
|
|
2419
2460
|
- **Schema Export / Import** (administrators only; `/builder/sql/data-pump`; sprint 103, Database Actions: Data
|
|
2420
2461
|
Pump): see [Schema export and import](#schema-export-and-import).
|
package/package.json
CHANGED
package/public/builder.css
CHANGED
|
@@ -444,7 +444,9 @@ body.ide {
|
|
|
444
444
|
.pd > .tab-panel > .pd-pane-title { display: none; }
|
|
445
445
|
/* with script the designer fills the window and each pane scrolls on its own */
|
|
446
446
|
.js .ide-full .ide-main { height: 100vh; height: 100dvh; display: flex; flex-direction: column; }
|
|
447
|
-
|
|
447
|
+
/* (UX-2) the work area is the containing block and clips: nothing inside (a screen-reader heading placed
|
|
448
|
+
absolutely, a pane's overflow) can make the page itself scroll */
|
|
449
|
+
.js .ide-full .ide-work { flex: 1 1 auto; min-height: 0; position: relative; overflow: hidden; }
|
|
448
450
|
.js .ide-full .pd { height: 100%; min-height: 0; }
|
|
449
451
|
.js .pd-pane { min-height: 0; overflow: auto; }
|
|
450
452
|
.js .pd-center { display: flex; flex-direction: column; overflow: hidden; }
|
|
@@ -484,8 +486,9 @@ body.ide {
|
|
|
484
486
|
}
|
|
485
487
|
.pd-node .icon { width: 16px; height: 16px; color: var(--muted); }
|
|
486
488
|
a.pd-node:hover { background: var(--surface-3); }
|
|
487
|
-
|
|
488
|
-
.pd-
|
|
489
|
+
/* (UX-12) the name keeps its room: the type gives way first (the icon tells it, the tooltip names it) */
|
|
490
|
+
.pd-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; flex: 0 1 auto; }
|
|
491
|
+
.pd-note { margin-left: auto; padding-left: 8px; max-width: 45%; min-width: 0; flex: 0 1000 auto; overflow: hidden; text-overflow: clip; white-space: nowrap; color: var(--muted); font-size: 11.5px; }
|
|
489
492
|
.pd-folder { color: var(--muted); font-weight: 600; cursor: default; }
|
|
490
493
|
.pd-tag { display: inline-flex; }
|
|
491
494
|
.pd-tag .icon { width: 13px; height: 13px; color: var(--warn); }
|
|
@@ -1671,3 +1674,88 @@ a.cp-type:hover, a.cp-type:focus-visible { border-color: var(--accent); }
|
|
|
1671
1674
|
.api-client-actions select { width: auto; min-height: 0; padding-block: 0.2rem; font-size: 0.8125rem; }
|
|
1672
1675
|
.api-client-actions .inline-form { display: flex; gap: 6px; align-items: center; }
|
|
1673
1676
|
.api-clients td.actions { text-align: end; }
|
|
1677
|
+
|
|
1678
|
+
/* (UX-2) Page Designer on a short window: the gallery folded to its tabs (builder.js), Save at hand */
|
|
1679
|
+
@media (min-width: 1024px) {
|
|
1680
|
+
.js .pd-gallery.is-folded { height: auto; min-height: 0; overflow: hidden; }
|
|
1681
|
+
.js .pd-gallery.is-folded .tab-panel { display: none; }
|
|
1682
|
+
}
|
|
1683
|
+
.tb-save.is-dirty { position: relative; }
|
|
1684
|
+
.tb-save.is-dirty::after { content: ""; position: absolute; top: 3px; right: 3px; width: 7px; height: 7px; border-radius: 50%; background: var(--hot); }
|
|
1685
|
+
#pd-p-props .component-form > .buttons { position: sticky; bottom: 0; z-index: 1; margin: 0; padding: 8px 0; background: var(--surface); border-top: 1px solid var(--border); }
|
|
1686
|
+
.pd-note.is-cut { display: none; }
|
|
1687
|
+
|
|
1688
|
+
/* (UX-3) the rail with labels next to its icons (the toggle at its bottom, kept in the session) */
|
|
1689
|
+
.rail-toggle { width: 100%; border: 0; background: none; cursor: pointer; font: inherit; }
|
|
1690
|
+
@media (min-width: 900px) {
|
|
1691
|
+
.ide.rail-labels { --rail-w: 196px; }
|
|
1692
|
+
.rail-labels .rail-list { padding: 0 10px; }
|
|
1693
|
+
.rail-labels .rail-link { display: flex; justify-content: flex-start; gap: 12px; padding: 0 12px; }
|
|
1694
|
+
.rail-labels .rail-link .rail-label { position: static; opacity: 1; transform: none; padding: 0; background: none; color: inherit;
|
|
1695
|
+
box-shadow: none; font-size: 13.5px; font-weight: 500; pointer-events: auto; }
|
|
1696
|
+
.rail-labels .rail-logo { margin-left: 18px; }
|
|
1697
|
+
.rail-labels .rail-user { justify-content: flex-start; padding-left: 14px; }
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
/* (UX-4) Administration: the workspace utilities in sections of compact tiles, with a filter (builder.js) */
|
|
1701
|
+
.adm { max-width: 1180px; }
|
|
1702
|
+
.adm .title-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; }
|
|
1703
|
+
.adm-filter input { width: min(320px, 100%); }
|
|
1704
|
+
.adm-lede { max-width: 75ch; }
|
|
1705
|
+
.adm-section { margin-top: 22px; }
|
|
1706
|
+
.adm-section h2 { font-size: 15px; margin: 0 0 8px; color: var(--text-2, var(--muted)); text-transform: none; }
|
|
1707
|
+
.adm-tiles { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr)); gap: 8px; }
|
|
1708
|
+
.adm-tile { display: flex; flex-direction: column; gap: 2px; height: 100%; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius);
|
|
1709
|
+
background: var(--surface); color: var(--text); text-decoration: none; transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease); }
|
|
1710
|
+
.adm-tile:hover, .adm-tile:focus-visible { border-color: var(--hot); background: var(--surface-2); }
|
|
1711
|
+
.adm-tile-head { display: flex; align-items: center; gap: 8px; }
|
|
1712
|
+
.adm-tile-head .icon { width: 16px; height: 16px; color: var(--hot); flex: none; }
|
|
1713
|
+
.adm-tile-text { color: var(--muted); font-size: 12.5px; line-height: 1.4; }
|
|
1714
|
+
.adm-reports { columns: 2 260px; }
|
|
1715
|
+
|
|
1716
|
+
/* (UX-5) a builder page's header: one heading, one primary (accent) action, secondary actions and a ⋯ menu */
|
|
1717
|
+
.page-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 16px; margin: 0 0 16px; }
|
|
1718
|
+
.page-head h1 { margin: 0; font-size: 22px; }
|
|
1719
|
+
.page-head-sub { margin: 2px 0 0; }
|
|
1720
|
+
.page-head-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
|
1721
|
+
.page-head-actions .btn { display: inline-flex; align-items: center; gap: 6px; }
|
|
1722
|
+
.page-head-actions .icon { width: 15px; height: 15px; }
|
|
1723
|
+
/* (UX-13) the Workspace home's "Needs attention" list for administrators, above the tools */
|
|
1724
|
+
.wh-attention { display: block; margin: 0 0 16px; }
|
|
1725
|
+
.wh-attention h2 { margin: 0 0 4px; font-size: 14px; }
|
|
1726
|
+
.wh-attention ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
|
|
1727
|
+
.wh-attention .icon { width: 15px; height: 15px; vertical-align: -2px; }
|
|
1728
|
+
/* (UX-6) deleting an application or a page: apart from everyday actions, the name typed to confirm */
|
|
1729
|
+
.danger-box { margin-top: 24px; padding: 12px 16px; border: 1px solid color-mix(in srgb, var(--danger) 45%, var(--border)); border-radius: 8px; background: var(--danger-soft); }
|
|
1730
|
+
.danger-box h2 { margin: 0 0 4px; font-size: 15px; color: var(--danger); }
|
|
1731
|
+
.danger-box p { margin: 0 0 8px; }
|
|
1732
|
+
.danger-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px; }
|
|
1733
|
+
.danger-form .field { margin: 0; flex: 1 1 14rem; max-width: 22rem; }
|
|
1734
|
+
.menu-section .danger-form { margin-top: 6px; padding-top: 8px; border-top: 1px solid var(--border); }
|
|
1735
|
+
/* (UX-7) Create application: Create always in reach, with a one-line summary */
|
|
1736
|
+
.aw-footer { position: sticky; bottom: 29px; z-index: 15; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px;
|
|
1737
|
+
margin-top: 16px; padding: 10px 14px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-md); }
|
|
1738
|
+
.aw-footer .buttons { margin: 0; }
|
|
1739
|
+
.aw-summary { margin: 0; min-width: 0; overflow-wrap: anywhere; }
|
|
1740
|
+
.aw-schema-row { align-items: end; }
|
|
1741
|
+
.aw-schema-row .buttons { margin: 0 0 1rem; }
|
|
1742
|
+
/* (UX-10) Page not found inside the builder */
|
|
1743
|
+
.nf-page h1 { margin-top: 8px; }
|
|
1744
|
+
.nf-search { margin: 12px 0 16px; max-width: 32rem; }
|
|
1745
|
+
.nf-page h2 { font-size: 14px; margin: 12px 0 4px; }
|
|
1746
|
+
/* (UX-8) builder reports at laptop width, one rule for all (opt in with .rep-fit): low-value columns (.col-low, under 1150 px of table width) give
|
|
1747
|
+
way first, long text wraps (.col-wrap), and the Actions column (.col-actions) stays on screen at the right */
|
|
1748
|
+
.table-wrap.rep-fit { container-type: inline-size; }
|
|
1749
|
+
@container (max-width: 1150px) { .rep-fit .report .col-low { display: none; } }
|
|
1750
|
+
.ide .report td.col-wrap { white-space: normal; min-width: 12rem; overflow-wrap: anywhere; }
|
|
1751
|
+
.ide .report .col-actions { position: sticky; right: 0; background: var(--surface); box-shadow: inset 1px 0 0 var(--border); }
|
|
1752
|
+
.ide .report th.col-actions { background: var(--surface-2); }
|
|
1753
|
+
/* (UX-8) a side drawer opened from the page header (details.drawer): Create account on Users */
|
|
1754
|
+
.drawer { display: inline-block; }
|
|
1755
|
+
.drawer > summary { list-style: none; cursor: pointer; }
|
|
1756
|
+
.drawer > summary::-webkit-details-marker { display: none; }
|
|
1757
|
+
.drawer-panel { position: fixed; top: 0; right: 0; bottom: 0; z-index: 60; width: min(26rem, 100vw); overflow-y: auto; padding: 16px 18px 24px;
|
|
1758
|
+
background: var(--surface); color: var(--text); border-left: 1px solid var(--border-strong); box-shadow: var(--shadow-lg); text-align: left; }
|
|
1759
|
+
.drawer-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 12px; }
|
|
1760
|
+
.drawer-head h2 { margin: 0; font-size: 16px; }
|
|
1761
|
+
.drawer-panel .buttons { justify-content: flex-end; }
|
package/public/builder.js
CHANGED
|
@@ -508,6 +508,38 @@
|
|
|
508
508
|
const dirty = () => save.classList.add('is-dirty');
|
|
509
509
|
form.addEventListener('input', dirty);
|
|
510
510
|
form.addEventListener('change', dirty);
|
|
511
|
+
// (UX-2) Ctrl+S saves; leaving with unsaved changes asks first (saving doesn't)
|
|
512
|
+
let submitting = false;
|
|
513
|
+
form.addEventListener('submit', () => (submitting = true));
|
|
514
|
+
document.addEventListener('keydown', (e) => {
|
|
515
|
+
if ((e.ctrlKey || e.metaKey) && !e.altKey && e.key.toLowerCase() === 's' && !e.target.closest?.('.ws, dialog')) {
|
|
516
|
+
e.preventDefault();
|
|
517
|
+
form.requestSubmit();
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
window.addEventListener('beforeunload', (e) => {
|
|
521
|
+
if (!submitting && save.classList.contains('is-dirty')) {
|
|
522
|
+
e.preventDefault();
|
|
523
|
+
e.returnValue = '';
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
// (UX-2) on a short window the gallery is only its tabs: choosing a tab opens it, choosing the open one folds it
|
|
528
|
+
const gallery = document.querySelector('.pd-gallery');
|
|
529
|
+
if (gallery) {
|
|
530
|
+
const short = window.matchMedia('(max-height: 800px)');
|
|
531
|
+
const apply = () => gallery.classList.toggle('is-folded', short.matches && store.get('gallery') !== 'open');
|
|
532
|
+
apply();
|
|
533
|
+
short.addEventListener('change', apply);
|
|
534
|
+
gallery.addEventListener('click', (e) => {
|
|
535
|
+
const t = e.target.closest('[role=tab]');
|
|
536
|
+
if (!t || !short.matches) return;
|
|
537
|
+
const folded = gallery.classList.contains('is-folded');
|
|
538
|
+
const wasActive = t.getAttribute('aria-selected') === 'true';
|
|
539
|
+
const open = folded || !wasActive;
|
|
540
|
+
store.set('gallery', open ? 'open' : null);
|
|
541
|
+
gallery.classList.toggle('is-folded', !open);
|
|
542
|
+
}, true); // before the tabs switch, so the tab that was open is known
|
|
511
543
|
}
|
|
512
544
|
}
|
|
513
545
|
|
|
@@ -1011,7 +1043,41 @@
|
|
|
1011
1043
|
setupQueryCanvas();
|
|
1012
1044
|
setupThemePreview();
|
|
1013
1045
|
setupTabs();
|
|
1046
|
+
// (UX-4) Administration: filter the utilities by their text; sections without a match hide
|
|
1047
|
+
const admQ = document.querySelector('[data-adm-filter]');
|
|
1048
|
+
if (admQ) {
|
|
1049
|
+
admQ.closest('.adm-filter').hidden = false;
|
|
1050
|
+
const page = admQ.closest('.adm');
|
|
1051
|
+
const none = page.querySelector('.adm-none');
|
|
1052
|
+
admQ.addEventListener('input', () => {
|
|
1053
|
+
const q = admQ.value.trim().toLowerCase();
|
|
1054
|
+
let any = false;
|
|
1055
|
+
for (const sec of page.querySelectorAll('.adm-section')) {
|
|
1056
|
+
let shown = 0;
|
|
1057
|
+
for (const li of sec.querySelectorAll('li')) {
|
|
1058
|
+
const hit = !q || li.textContent.toLowerCase().includes(q);
|
|
1059
|
+
li.hidden = !hit;
|
|
1060
|
+
if (hit) shown++;
|
|
1061
|
+
}
|
|
1062
|
+
sec.hidden = !shown;
|
|
1063
|
+
if (shown) any = true;
|
|
1064
|
+
}
|
|
1065
|
+
none.hidden = any;
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1014
1068
|
for (const tree of document.querySelectorAll('.pd-tree')) setupTree(tree);
|
|
1069
|
+
// (UX-12) a node's type text only when it fits whole: the name comes first (the icon and tooltip tell the type)
|
|
1070
|
+
const fitNotes = () => {
|
|
1071
|
+
for (const n of document.querySelectorAll('.pd-tree .pd-note')) {
|
|
1072
|
+
n.classList.remove('is-cut');
|
|
1073
|
+
if (n.scrollWidth > n.clientWidth + 1) n.classList.add('is-cut');
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
fitNotes();
|
|
1077
|
+
if ('ResizeObserver' in window) {
|
|
1078
|
+
const pane = document.querySelector('.pd-tree')?.closest('.pd-pane');
|
|
1079
|
+
if (pane) new ResizeObserver(() => requestAnimationFrame(fitNotes)).observe(pane);
|
|
1080
|
+
}
|
|
1015
1081
|
for (const tree of document.querySelectorAll('.pd-tree[data-tree-menu]')) setupTreeMenu(tree);
|
|
1016
1082
|
setupProperties();
|
|
1017
1083
|
setupCanvas();
|
|
@@ -1206,7 +1272,30 @@ document.querySelectorAll('input[data-filter-tree]').forEach((el) => {
|
|
|
1206
1272
|
sync();
|
|
1207
1273
|
});
|
|
1208
1274
|
|
|
1209
|
-
|
|
1275
|
+
// (UX-7) the footer's summary follows the page types, the sign-in and the language
|
|
1276
|
+
const awForm = document.querySelector('form.aw-form');
|
|
1277
|
+
const summary = document.getElementById('aw-summary');
|
|
1278
|
+
const sum = () => {
|
|
1279
|
+
const tpl = document.getElementById('aw-summary-pages');
|
|
1280
|
+
if (!awForm || !summary || !tpl) return;
|
|
1281
|
+
let parts;
|
|
1282
|
+
if (tpl.dataset.schema) {
|
|
1283
|
+
let n = 1;
|
|
1284
|
+
awForm.querySelectorAll('#aw-pages select').forEach((sel) => (n += Number(sel.selectedOptions[0]?.dataset.pages ?? 0)));
|
|
1285
|
+
const chart = awForm.querySelector('#aw-pages input[name="chart"]');
|
|
1286
|
+
if (chart?.checked && n > 1) n += 1;
|
|
1287
|
+
parts = [n === 1 ? tpl.dataset.one : tpl.dataset.many.replace('{n}', String(n))];
|
|
1288
|
+
} else parts = [tpl.dataset.new];
|
|
1289
|
+
for (const name of ['authentication', 'language']) {
|
|
1290
|
+
const sel = awForm.elements.namedItem(name);
|
|
1291
|
+
const text = sel?.selectedOptions?.[0]?.textContent?.replace(/\s*\([a-z-]+\)$/i, '');
|
|
1292
|
+
if (text) parts.push(text);
|
|
1293
|
+
}
|
|
1294
|
+
summary.textContent = parts.join(' · ');
|
|
1295
|
+
};
|
|
1296
|
+
awForm?.addEventListener('change', sum);
|
|
1297
|
+
|
|
1298
|
+
const picker = document.querySelector('select[name="schema"][form="aw-schema-get"]');
|
|
1210
1299
|
const pages = document.getElementById('aw-pages');
|
|
1211
1300
|
const hidden = document.getElementById('aw-schema-fields');
|
|
1212
1301
|
if (!picker || !pages || !hidden) return;
|
|
@@ -1231,12 +1320,13 @@ document.querySelectorAll('input[data-filter-tree]').forEach((el) => {
|
|
|
1231
1320
|
if (alias && !alias.hasAttribute('data-edited')) alias.value = doc.getElementById('f_alias')?.value ?? alias.value;
|
|
1232
1321
|
history.replaceState(null, '', url);
|
|
1233
1322
|
document.getElementById('aw-pages')?.removeAttribute('aria-busy');
|
|
1323
|
+
sum();
|
|
1234
1324
|
} catch {
|
|
1235
1325
|
picker.form.submit();
|
|
1236
1326
|
}
|
|
1237
1327
|
});
|
|
1238
1328
|
// the button is for pages without JavaScript
|
|
1239
|
-
|
|
1329
|
+
document.querySelector('button[form="aw-schema-get"]')?.setAttribute('hidden', '');
|
|
1240
1330
|
})();
|
|
1241
1331
|
|
|
1242
1332
|
// (sprint 101, DA-12) SQL Workshop › Scheduling: the schedule builder's five fields and the common schedules fill the
|
|
@@ -1335,3 +1425,12 @@ document.querySelectorAll('input[data-filter-tree]').forEach((el) => {
|
|
|
1335
1425
|
})();
|
|
1336
1426
|
|
|
1337
1427
|
// (sprint 127) Passkeys: the [data-passkey] buttons are handled by app.js (sprint 131), which every builder page loads too.
|
|
1428
|
+
|
|
1429
|
+
// (UX-8) a side drawer (details.drawer): Close, Escape and a click outside close it; the first field gets the focus
|
|
1430
|
+
document.querySelectorAll('details.drawer').forEach((d) => {
|
|
1431
|
+
const close = () => { d.open = false; d.querySelector('summary')?.focus(); };
|
|
1432
|
+
d.querySelector('[data-drawer-close]')?.addEventListener('click', close);
|
|
1433
|
+
d.addEventListener('toggle', () => { if (d.open) d.querySelector('.drawer-panel input:not([type=hidden]), .drawer-panel select, .drawer-panel textarea')?.focus(); });
|
|
1434
|
+
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && d.open) close(); });
|
|
1435
|
+
document.addEventListener('click', (e) => { if (d.open && !d.contains(e.target)) d.open = false; });
|
|
1436
|
+
});
|
package/public/palette.js
CHANGED
|
@@ -18,15 +18,22 @@
|
|
|
18
18
|
if (/^\/builder\/sql\/./.test(path) && !/\/(panel|navigator|action)$/.test(path)) {
|
|
19
19
|
const title = document.title.replace(/ · Builder$/, '');
|
|
20
20
|
const href = path + location.search;
|
|
21
|
-
|
|
21
|
+
// (UX-14) one entry per page (its path): opened again with other parameters, the newest wins
|
|
22
|
+
const list = (store.get('sw.recent') || []).filter((r) => r && typeof r.href === 'string' && r.href.split('?')[0] !== path);
|
|
22
23
|
list.unshift({ href, title });
|
|
23
24
|
store.set('sw.recent', list.slice(0, 8));
|
|
24
25
|
}
|
|
25
26
|
const recent = document.querySelector('[data-lp-recent]');
|
|
26
27
|
if (recent) {
|
|
27
28
|
const list = store.get('sw.recent') || [];
|
|
29
|
+
// (UX-14) not the page you are on, not what is pinned already, each path once
|
|
30
|
+
const pinned = new Set([...document.querySelectorAll('.lp-pins .lp-chips:not([data-lp-recent]) a[href]')].map((a) => a.getAttribute('href').split('?')[0]));
|
|
31
|
+
const seen = new Set([path]);
|
|
28
32
|
for (const r of list) {
|
|
29
33
|
if (typeof r?.href !== 'string' || !r.href.startsWith('/builder/')) continue;
|
|
34
|
+
const p = r.href.split('?')[0];
|
|
35
|
+
if (seen.has(p) || pinned.has(p)) continue;
|
|
36
|
+
seen.add(p);
|
|
30
37
|
const li = document.createElement('li');
|
|
31
38
|
const a = document.createElement('a');
|
|
32
39
|
a.href = r.href;
|
package/public/worksheet.css
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
screens: navigator | editor over output tabs, filling the window, with a status bar at the bottom. */
|
|
4
4
|
.ws-page { display: flex; flex-direction: column; padding: 8px 10px 0; background: var(--bg); }
|
|
5
5
|
.ws-page > .sw-tabs { margin-bottom: 6px; }
|
|
6
|
-
.ws { --ws-nav: 280px; --ws-out:
|
|
6
|
+
.ws { --ws-nav: 280px; --ws-out: 45%; display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; background: var(--surface);
|
|
7
7
|
border: 1px solid var(--border); border-radius: var(--radius); min-width: 0; }
|
|
8
8
|
.ws-default { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); border: 0; padding: 0; }
|
|
9
9
|
.ws-nav { display: flex; flex-direction: column; min-width: 0; border-bottom: 1px solid var(--border); background: var(--surface-2); }
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
.ws-obj-actions { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px 10px 8px 34px; }
|
|
33
33
|
.ws-obj-actions .btn { font-size: 11px; padding: 1px 6px; }
|
|
34
34
|
|
|
35
|
-
.ws-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
|
|
35
|
+
.ws-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; container-type: inline-size; }
|
|
36
36
|
.ws-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid var(--border); }
|
|
37
37
|
.ws-toolbar .btn { display: inline-flex; align-items: center; gap: 4px; }
|
|
38
38
|
.ws-toolbar .icon { width: 15px; height: 15px; }
|
|
@@ -99,12 +99,16 @@
|
|
|
99
99
|
.js .ide-full .ws-editor .ce { flex: 1 1 auto; min-height: 0; grid-template-rows: minmax(0, 1fr) auto; border: 0; border-radius: 0; }
|
|
100
100
|
.js .ide-full .ws-editor .ce-main { min-height: 0; }
|
|
101
101
|
.js .ide-full .ws-editor .ce textarea.ce-input { height: 100%; min-height: 0; resize: none; }
|
|
102
|
-
.js .ide-full .ws-output { flex: 0
|
|
102
|
+
.js .ide-full .ws-output { flex: 0 1 var(--ws-out); }
|
|
103
|
+
/* (UX-1) the editor keeps at least ten lines; an empty output is only its tab strip until a run */
|
|
104
|
+
.js .ide-full .ws-editor { min-height: 13.5rem; }
|
|
105
|
+
.js .ide-full .ws-output[data-empty] { flex: 0 0 auto; }
|
|
106
|
+
.js .ide-full .ws-output[data-empty] .ws-pane { display: none; }
|
|
103
107
|
.js .ide-full .ws-pane { flex: 1 1 auto; }
|
|
104
108
|
.js .ide-full .ws-nav { overflow: hidden; }
|
|
105
109
|
}
|
|
106
110
|
@media (max-width: 899px) {
|
|
107
|
-
.ws-toolbar .btn span:not(.sr-only) { display: none; }
|
|
111
|
+
.ws-toolbar .btn span:not(.sr-only):not(.ws-dots) { display: none; }
|
|
108
112
|
.ws-toolbar .btn-hot span:not(.sr-only) { display: inline; }
|
|
109
113
|
}
|
|
110
114
|
/* fullscreen: only the editor and its toolbar */
|
|
@@ -299,3 +303,34 @@ details.menu.obj-menu > summary::before, details.menu.obj-menu > summary::after
|
|
|
299
303
|
.ws-txn-badge[data-state="open"] { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 55%, var(--border)); background: color-mix(in srgb, var(--warn) 10%, transparent); font-weight: 600; }
|
|
300
304
|
.ws-txn-badge[data-state="aborted"] { color: var(--danger); border-color: var(--danger); background: var(--danger-soft); font-weight: 600; }
|
|
301
305
|
.ws-txn-alert { margin: 6px 8px; }
|
|
306
|
+
|
|
307
|
+
/* (UX-1) one toolbar row: the run buttons, Suggest / Check, Commit mode, fullscreen and a ⋯ menu with the rest */
|
|
308
|
+
/* it fits on one row; an open transaction (badge, Commit, Rollback) may take a second one */
|
|
309
|
+
.ws-toolbar > * { flex: none; }
|
|
310
|
+
/* the commit mode's options name it (Autocommit / Transaction): no label beside the select */
|
|
311
|
+
.ws-toolbar .ws-txn .ws-runas > span { display: none; }
|
|
312
|
+
@container (max-width: 950px) {
|
|
313
|
+
.ws-toolbar .btn:not(.btn-hot) > span:not(.sr-only):not(.ws-dots) { display: none; }
|
|
314
|
+
}
|
|
315
|
+
@container (max-width: 640px) {
|
|
316
|
+
.ws-toolbar > * { flex: 0 1 auto; min-width: 0; }
|
|
317
|
+
.ws-toolbar .ws-txn { flex-wrap: wrap; }
|
|
318
|
+
}
|
|
319
|
+
.ws-more .menu-panel { min-width: 18rem; max-height: calc(100dvh - 190px); overflow: auto; }
|
|
320
|
+
/* with script the worksheet tabs name the file: the file menu is its icon */
|
|
321
|
+
.js .ws-toolbar .ws-file-name { display: none; }
|
|
322
|
+
.ws-more-tools { display: flex; flex-direction: column; gap: 2px; }
|
|
323
|
+
.ws-menu-item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 6px 8px; border: 0; border-radius: 6px; background: none;
|
|
324
|
+
color: var(--text); font: inherit; text-align: left; text-decoration: none; cursor: pointer; }
|
|
325
|
+
.ws-menu-item:hover, .ws-menu-item:focus-visible { background: var(--surface-2); }
|
|
326
|
+
.ws-menu-item .icon { width: 15px; height: 15px; }
|
|
327
|
+
.ws-menu-item kbd { margin-left: auto; font-size: 11.5px; color: var(--muted); }
|
|
328
|
+
.ws-more-tools .ws-check { padding: 6px 8px; }
|
|
329
|
+
.ws-more-head { display: block; margin-bottom: 4px; }
|
|
330
|
+
/* the code editor's hint bar under the editor: only while it has something to say (Check's result) */
|
|
331
|
+
.ws-editor .ce-bar:not(:has(.ce-msg:not(:empty))) { display: none; }
|
|
332
|
+
.ws-editor .ce-bar :is(.ce-hint, .ce-suggest, .ce-check) { display: none; }
|
|
333
|
+
/* (UX-1) who the statements run as: a chip at the right of the status bar */
|
|
334
|
+
.ws-status-run { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1 1 auto; }
|
|
335
|
+
.ws-runas-chip { flex: none; display: inline-flex; align-items: center; gap: 6px; }
|
|
336
|
+
.ws-runas-chip select { padding: 1px 22px 1px 6px; min-height: 0; font-size: 12.5px; }
|