@gjsify/adwaita-app 0.16.5
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/README.md +106 -0
- package/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/application.js +1 -0
- package/lib/esm/dev-hooks.js +1 -0
- package/lib/esm/dialogs.js +1 -0
- package/lib/esm/file-dialog.js +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/loading-stack.js +1 -0
- package/lib/esm/nav-model.js +1 -0
- package/lib/esm/nav-shell.js +1 -0
- package/lib/esm/toast.js +1 -0
- package/lib/esm/types.js +0 -0
- package/lib/esm/view-loader.js +1 -0
- package/lib/types/application.d.ts +56 -0
- package/lib/types/dev-hooks.d.ts +21 -0
- package/lib/types/dev-hooks.spec.d.ts +2 -0
- package/lib/types/dialogs.d.ts +21 -0
- package/lib/types/file-dialog.d.ts +23 -0
- package/lib/types/index.d.ts +16 -0
- package/lib/types/loading-stack.d.ts +21 -0
- package/lib/types/nav-model.d.ts +10 -0
- package/lib/types/nav-model.spec.d.ts +2 -0
- package/lib/types/nav-shell.d.ts +38 -0
- package/lib/types/toast.d.ts +10 -0
- package/lib/types/types.d.ts +37 -0
- package/lib/types/view-loader.d.ts +37 -0
- package/lib/types/view-loader.spec.d.ts +2 -0
- package/package.json +83 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @gjsify/adwaita-app
|
|
2
|
+
|
|
3
|
+
Native Adwaita application shell for GJS/GTK apps. It wires the boilerplate every
|
|
4
|
+
native Adwaita app repeats — the `runAsync` lifecycle, the opt-in
|
|
5
|
+
[`@gjsify/devtools`](../devtools) control plane, a startup CSS bootstrap, standard
|
|
6
|
+
`app.quit`/`app.about` actions, a data-driven `Adw.NavigationSplitView` nav shell,
|
|
7
|
+
an async-view mounter, and promise-based dialog/toast/file helpers.
|
|
8
|
+
|
|
9
|
+
It is **composition-first**: opt-in wiring for the parts that are pure
|
|
10
|
+
boilerplate, never a wrapper that hides `Adw`/`Gtk`. You still write your views as
|
|
11
|
+
plain `Gtk.Widget`s.
|
|
12
|
+
|
|
13
|
+
> GJS-only (`runtimes.gjs = polyfill`, everything else `none`). Tier 2.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
gjsify install @gjsify/adwaita-app
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import Adw from '@girs/adw-1';
|
|
25
|
+
import { runAdwaitaApp, createNavShell, type NavItem } from '@gjsify/adwaita-app';
|
|
26
|
+
|
|
27
|
+
const NAV: NavItem[] = [
|
|
28
|
+
{ id: 'overview', label: 'Overview', icon: 'go-home-symbolic' },
|
|
29
|
+
{ id: 'reports', label: 'Reports', icon: 'x-office-spreadsheet-symbolic' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
class MainWindow extends Adw.ApplicationWindow {
|
|
33
|
+
static { imports.gi.GObject.registerClass({ GTypeName: 'MyMainWindow' }, MainWindow); }
|
|
34
|
+
constructor(app: Adw.Application) {
|
|
35
|
+
super({ application: app, defaultWidth: 1000, defaultHeight: 700 });
|
|
36
|
+
const shell = createNavShell(this, {
|
|
37
|
+
items: NAV,
|
|
38
|
+
sidebarTitle: 'My App',
|
|
39
|
+
onSelect: (item) => shell.stack.set_visible_child_name(item.id),
|
|
40
|
+
});
|
|
41
|
+
shell.stack.add_named(buildOverview(), 'overview');
|
|
42
|
+
shell.stack.add_named(buildReports(), 'reports');
|
|
43
|
+
this.set_content(shell.widget);
|
|
44
|
+
shell.selectById('overview');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await runAdwaitaApp({
|
|
49
|
+
applicationId: 'org.example.App',
|
|
50
|
+
createWindow: (app) => new MainWindow(app),
|
|
51
|
+
css: '/* optional app CSS, loaded display-wide on startup */',
|
|
52
|
+
about: { applicationName: 'My App', version: '1.0.0', developerName: 'Me' },
|
|
53
|
+
// devtools omitted → gated on the GJSIFY_DEVTOOLS env var (safe in prod).
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`runAdwaitaApp` uses `Adw.Application.runAsync()` — **not** sync `run()` — so a
|
|
58
|
+
synchronous view load does not hang its spinner (GJS does not flush the
|
|
59
|
+
promise-job queue under `run()`).
|
|
60
|
+
|
|
61
|
+
## API
|
|
62
|
+
|
|
63
|
+
### Application
|
|
64
|
+
|
|
65
|
+
- `runAdwaitaApp(options): Promise<number>` — construct + run, resolve with the
|
|
66
|
+
exit code. `AdwaitaAppOptions`: `applicationId`, `createWindow`, optional
|
|
67
|
+
`flags`, `css`, `about` (`AboutInfo`), `quitAction` (default on, `<primary>q`),
|
|
68
|
+
`devtools` (`true` | `InstallDevtoolsOptions` | omitted = env-gated),
|
|
69
|
+
`onStartup`.
|
|
70
|
+
- `AdwaitaApp` — the configured `Adw.Application` subclass, if you need the
|
|
71
|
+
instance instead of `runAdwaitaApp`.
|
|
72
|
+
|
|
73
|
+
### Navigation shell
|
|
74
|
+
|
|
75
|
+
- `createNavShell(window, options): NavShell` — builds the
|
|
76
|
+
`Adw.NavigationSplitView` (sidebar `Gtk.ListBox` + content `Gtk.Stack`) into
|
|
77
|
+
`window` (which owns the responsive `Adw.Breakpoint`, default `max-width: 720px`).
|
|
78
|
+
Returns `{ widget, stack, contentHeader, selectById, selectByIndex }`.
|
|
79
|
+
- `resolveInitialNavIndex(items, wantedId?)`, `findNavItem(items, id)` — pure
|
|
80
|
+
helpers (e.g. to turn a `${PREFIX}_VIEW` dev hook into a start index).
|
|
81
|
+
|
|
82
|
+
### Async view mounting
|
|
83
|
+
|
|
84
|
+
- `LoadToken` + `loadIntoStack({ stack, token, load, fill, onError? })` — show a
|
|
85
|
+
loading page, run `load` (sync or async), then `fill` + show content — dropping
|
|
86
|
+
a result a newer reload superseded, and showing the error page on failure.
|
|
87
|
+
|
|
88
|
+
### Interaction helpers
|
|
89
|
+
|
|
90
|
+
- `confirmDialog(parent, { heading, body?, confirmLabel?, cancelLabel?, destructive? }): Promise<boolean>`
|
|
91
|
+
- `errorDialog(parent, heading, body?): Promise<void>`
|
|
92
|
+
- `registerToastOverlay(overlay)` + `showToast(title, timeout?)`
|
|
93
|
+
- `pickFile(parent, { title?, filters? }): Promise<string | null>` /
|
|
94
|
+
`saveFile(parent, { title?, filters?, initialName? }): Promise<string | null>`
|
|
95
|
+
|
|
96
|
+
### Dev hooks
|
|
97
|
+
|
|
98
|
+
- `readAppDevHooks({ prefix, env? }): { view?, file?, debug }` — the
|
|
99
|
+
`${PREFIX}_VIEW` / `${PREFIX}_FILE` / `${PREFIX}_DEBUG` env pattern (e.g.
|
|
100
|
+
`MYAPP_VIEW=reports myapp` to open straight to a view in dev).
|
|
101
|
+
|
|
102
|
+
## See also
|
|
103
|
+
|
|
104
|
+
- [`@gjsify/devtools`](../devtools) — the DBus control plane wired in on startup.
|
|
105
|
+
- [`@gjsify/storybook`](../storybook) — the component browser this shell was
|
|
106
|
+
generalized from ([ADR 0009](../../../docs/adr/0009-native-adwaita-app-shell.md)).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.defineProperty,__name=(t,n)=>e(t,`name`,{value:n,configurable:!0});export{__name};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";import t from"@girs/gdk-4.0";import n from"@girs/gio-2.0";import r from"@girs/gobject-2.0";import i from"@girs/gtk-4.0";import{installDevtools as a}from"@gjsify/devtools";var o=class AdwaitaApp extends e.Application{static{r.registerClass({GTypeName:`GjsifyAdwaitaApp`},AdwaitaApp)}constructor(e){super({application_id:e.applicationId,flags:e.flags??n.ApplicationFlags.DEFAULT_FLAGS}),this._window=null,this._options=e,this._initActions(),this.connect(`startup`,()=>this._onStartup()),this.connect(`activate`,()=>this._onActivate())}_initActions(){if(this._options.quitAction!==!1){let e=new n.SimpleAction({name:`quit`});e.connect(`activate`,()=>this.quit()),this.add_action(e),this.set_accels_for_action(`app.quit`,[`<primary>q`])}if(this._options.about){let e=new n.SimpleAction({name:`about`});e.connect(`activate`,()=>this._showAbout()),this.add_action(e)}}_onStartup(){this._options.css&&this._loadCss(this._options.css),this._installDevtools(),this._options.onStartup?.(this)}_loadCss(e){let n=t.Display.get_default();if(!n){console.error(`@gjsify/adwaita-app: no default display; CSS not applied`);return}let r=new i.CssProvider;r.load_from_string(e),i.StyleContext.add_provider_for_display(n,r,i.STYLE_PROVIDER_PRIORITY_APPLICATION)}_installDevtools(){let e=this._options.devtools;if(e===!1)return;let t=typeof e==`object`?e:{enabled:e||void 0};a(this,t)}_onActivate(){this._window||=this._options.createWindow(this),this._window.present()}_showAbout(){let t=this._options.about;if(!t)return;let n=new e.AboutDialog({application_name:t.applicationName,application_icon:t.applicationIcon??this._options.applicationId});t.version&&(n.version=t.version),t.developerName&&(n.developer_name=t.developerName),t.website&&(n.website=t.website),t.issueUrl&&(n.issue_url=t.issueUrl),t.license&&(n.license=t.license),t.comments&&(n.comments=t.comments),t.copyright&&(n.copyright=t.copyright),t.developers?.length&&n.set_developers(t.developers),n.present(this.get_active_window())}};async function runAdwaitaApp(e){return new o(e).runAsync([imports.system.programInvocationName,...ARGV])}r.type_ensure(o.$gtype);export{o as AdwaitaApp,runAdwaitaApp};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";function isTruthy(e){if(e==null||e===``)return!1;let t=e.toLowerCase();return t!==`0`&&t!==`false`&&t!==`no`}function readAppDevHooks(e){let t=e.env??globalThis.process?.env??{},n=e.prefix;return{view:t[`${n}_VIEW`]||void 0,file:t[`${n}_FILE`]||void 0,debug:isTruthy(t[`${n}_DEBUG`])}}export{readAppDevHooks};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";function confirmDialog(t,n){return new Promise(r=>{let i=new e.AlertDialog({heading:n.heading,body:n.body??``});i.add_response(`cancel`,n.cancelLabel??`Cancel`),i.add_response(`confirm`,n.confirmLabel??`OK`),i.set_response_appearance(`confirm`,n.destructive?e.ResponseAppearance.DESTRUCTIVE:e.ResponseAppearance.SUGGESTED),i.set_default_response(`confirm`),i.set_close_response(`cancel`),i.connect(`response`,(e,t)=>r(t===`confirm`)),i.present(t)})}function errorDialog(t,n,r){return new Promise(i=>{let a=new e.AlertDialog({heading:n,body:r??``});a.add_response(`ok`,`OK`),a.set_default_response(`ok`),a.set_close_response(`ok`),a.connect(`response`,()=>i()),a.present(t)})}export{confirmDialog,errorDialog};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"@girs/gio-2.0";import t from"@girs/gtk-4.0";function buildFilters(n){if(!n?.length)return;let r=new e.ListStore({itemType:t.FileFilter.$gtype});for(let e of n){let n=new t.FileFilter;n.set_name(e.name);for(let t of e.patterns??[])n.add_pattern(t);for(let t of e.mimeTypes??[])n.add_mime_type(t);r.append(n)}return r}function pickFile(e,n={}){return new Promise(r=>{let i=new t.FileDialog({title:n.title??`Open File`}),a=buildFilters(n.filters);a&&i.set_filters(a),i.open(e,null,(e,t)=>{try{r(i.open_finish(t)?.get_path()??null)}catch{r(null)}})})}function saveFile(e,n={}){return new Promise(r=>{let i=new t.FileDialog({title:n.title??`Save File`});n.initialName&&i.set_initial_name(n.initialName);let a=buildFilters(n.filters);a&&i.set_filters(a),i.save(e,null,(e,t)=>{try{r(i.save_finish(t)?.get_path()??null)}catch{r(null)}})})}export{pickFile,saveFile};
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AdwaitaApp as e,runAdwaitaApp as t}from"./application.js";import{readAppDevHooks as n}from"./dev-hooks.js";import{confirmDialog as r,errorDialog as i}from"./dialogs.js";import{pickFile as a,saveFile as o}from"./file-dialog.js";import{createNavShell as s}from"./nav-shell.js";import{LoadToken as c,loadIntoStack as l}from"./view-loader.js";import{LoadingStack as u}from"./loading-stack.js";import{registerToastOverlay as d,showToast as f}from"./toast.js";import{findNavItem as p,resolveInitialNavIndex as m}from"./nav-model.js";export{e as AdwaitaApp,c as LoadToken,u as LoadingStack,r as confirmDialog,s as createNavShell,i as errorDialog,p as findNavItem,l as loadIntoStack,a as pickFile,n as readAppDevHooks,d as registerToastOverlay,m as resolveInitialNavIndex,t as runAdwaitaApp,o as saveFile,f as showToast};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";import t from"@girs/gobject-2.0";import n from"@girs/gtk-4.0";var r=class LoadingStack extends n.Stack{static{t.registerClass({GTypeName:`GjsifyLoadingStack`},LoadingStack)}constructor(t={}){super(t);let r=new n.Box({orientation:n.Orientation.VERTICAL,halign:n.Align.CENTER,valign:n.Align.CENTER,hexpand:!0,vexpand:!0});r.append(new e.Spinner),this.add_named(r,`loading`),this._content=new e.Bin({hexpand:!0,vexpand:!0}),this.add_named(this._content,`content`),this._error=new e.StatusPage({iconName:`dialog-error-symbolic`,title:`Something went wrong`}),this.add_named(this._error,`error`),this.set_visible_child_name(`loading`)}setContent(e){this._content.set_child(e)}setError(e,t){this._error.set_title(e),this._error.set_description(t??``)}showLoading(){this.set_visible_child_name(`loading`)}showContent(){this.set_visible_child_name(`content`)}showError(){this.set_visible_child_name(`error`)}};t.type_ensure(r.$gtype);export{r as LoadingStack};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";function resolveInitialNavIndex(e,t){if(e.length===0)return-1;if(!t)return 0;let n=e.findIndex(e=>e.id===t);return n>=0?n:0}function findNavItem(e,t){return e.find(e=>e.id===t)}export{findNavItem,resolveInitialNavIndex};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";import t from"@girs/gtk-4.0";function buildNavRow(e){let n=new t.ListBoxRow,r=new t.Box({orientation:t.Orientation.HORIZONTAL,spacing:12,marginTop:8,marginBottom:8,marginStart:12,marginEnd:12});e.icon&&r.append(new t.Image({iconName:e.icon}));let i=new t.Box({orientation:t.Orientation.VERTICAL,hexpand:!0});return i.append(new t.Label({label:e.label,xalign:0})),e.subtitle&&i.append(new t.Label({label:e.subtitle,xalign:0,cssClasses:[`dim-label`,`caption`]})),r.append(i),n.set_child(r),n}function createNavShell(n,r){let i=new e.NavigationSplitView,a=new t.Stack({transitionType:t.StackTransitionType.CROSSFADE,hexpand:!0,vexpand:!0}),o=new t.ListBox({cssClasses:[`navigation-sidebar`]}),s=new e.HeaderBar,c=new e.HeaderBar;r.sidebarTitle&&c.set_title_widget(new e.WindowTitle({title:r.sidebarTitle,subtitle:``})),r.sidebarHeaderStart&&c.pack_start(r.sidebarHeaderStart),r.sidebarHeaderEnd&&c.pack_end(r.sidebarHeaderEnd);let l=new e.ToolbarView;l.add_top_bar(c),l.set_content(o),i.set_sidebar(new e.NavigationPage({title:r.sidebarTitle??`Menu`,child:l}));let u=new e.ToolbarView;u.add_top_bar(s),u.set_content(a),i.set_content(new e.NavigationPage({title:r.sidebarTitle??``,child:u}));for(let e of r.items)o.append(buildNavRow(e));o.connect(`row-selected`,(e,t)=>{if(!t)return;let n=t.get_index(),a=r.items[n];a&&(r.onSelect(a,n),i.get_collapsed()&&i.set_show_content(!0))});let d=r.collapseWidth??720,f=e.BreakpointCondition.parse(`max-width: ${d}px`);if(f){let t=new e.Breakpoint({condition:f});t.add_setter(i,`collapsed`,!0),n.add_breakpoint(t)}let selectByIndex=e=>{let t=o.get_row_at_index(e);t&&o.select_row(t)};return{widget:i,stack:a,contentHeader:s,selectByIndex,selectById(e){let t=r.items.findIndex(t=>t.id===e);selectByIndex(t>=0?t:0)}}}export{createNavShell};
|
package/lib/esm/toast.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";let t=null;function registerToastOverlay(e){t=e}function showToast(n,r=3){if(!t){console.warn(`@gjsify/adwaita-app: showToast() before registerToastOverlay()`);return}t.add_toast(new e.Toast({title:n,timeout:r}))}export{registerToastOverlay,showToast};
|
package/lib/esm/types.js
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";var LoadToken=class{constructor(){this._current=0}next(){return++this._current}get current(){return this._current}};function loadIntoStack(e){let t=e.loadingName??`loading`,n=e.contentName??`content`,r=e.errorName??`error`;e.stack.set_visible_child_name(t);let i=e.token.next();Promise.resolve().then(e.load).then(t=>{i===e.token.current&&(e.fill(t),e.stack.set_visible_child_name(n))}).catch(t=>{i===e.token.current&&(e.onError?.(t),e.stack.set_visible_child_name(r))})}export{LoadToken,loadIntoStack};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import Adw from '@girs/adw-1';
|
|
2
|
+
import Gio from '@girs/gio-2.0';
|
|
3
|
+
import Gtk from '@girs/gtk-4.0';
|
|
4
|
+
import { type InstallDevtoolsOptions } from '@gjsify/devtools';
|
|
5
|
+
import type { AboutInfo } from './types.js';
|
|
6
|
+
/** Options for {@link AdwaitaApp} / {@link runAdwaitaApp}. */
|
|
7
|
+
export interface AdwaitaAppOptions {
|
|
8
|
+
/** GApplication id, e.g. `org.example.App`. */
|
|
9
|
+
applicationId: string;
|
|
10
|
+
/** GApplication flags. Default `Gio.ApplicationFlags.DEFAULT_FLAGS`. */
|
|
11
|
+
flags?: Gio.ApplicationFlags;
|
|
12
|
+
/** Build the main window on first `activate`. Called once. */
|
|
13
|
+
createWindow: (app: Adw.Application) => Gtk.Window;
|
|
14
|
+
/** CSS string applied display-wide on `startup` (via `Gtk.CssProvider`). */
|
|
15
|
+
css?: string;
|
|
16
|
+
/** When set, wires an `app.about` action opening an `Adw.AboutDialog`. */
|
|
17
|
+
about?: AboutInfo;
|
|
18
|
+
/** Wire `app.quit` (`<primary>q`). Default `true`. */
|
|
19
|
+
quitAction?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Devtools control plane: `true` force-enables, an object passes
|
|
22
|
+
* {@link InstallDevtoolsOptions} through, omitted leaves it gated on the
|
|
23
|
+
* `GJSIFY_DEVTOOLS` env var (safe in production either way).
|
|
24
|
+
*/
|
|
25
|
+
devtools?: boolean | InstallDevtoolsOptions;
|
|
26
|
+
/** Extra work on `startup`, after CSS + devtools are wired. */
|
|
27
|
+
onStartup?: (app: Adw.Application) => void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A ready-to-run `Adw.Application` configured from {@link AdwaitaAppOptions}.
|
|
31
|
+
* Prefer {@link runAdwaitaApp} unless you need the instance.
|
|
32
|
+
*/
|
|
33
|
+
export declare class AdwaitaApp extends Adw.Application {
|
|
34
|
+
private readonly _options;
|
|
35
|
+
private _window;
|
|
36
|
+
constructor(options: AdwaitaAppOptions);
|
|
37
|
+
private _initActions;
|
|
38
|
+
private _onStartup;
|
|
39
|
+
private _loadCss;
|
|
40
|
+
private _installDevtools;
|
|
41
|
+
private _onActivate;
|
|
42
|
+
private _showAbout;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Construct and run an {@link AdwaitaApp}, resolving with its exit code when the
|
|
46
|
+
* last window closes. The whole per-project launcher collapses to:
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { runAdwaitaApp } from '@gjsify/adwaita-app';
|
|
50
|
+
* await runAdwaitaApp({
|
|
51
|
+
* applicationId: 'org.example.App',
|
|
52
|
+
* createWindow: (app) => new MyWindow(app),
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function runAdwaitaApp(options: AdwaitaAppOptions): Promise<number>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Resolved dev-hook values. */
|
|
2
|
+
export interface AppDevHooks {
|
|
3
|
+
/** `${PREFIX}_VIEW` — open straight to this view id on startup. */
|
|
4
|
+
view?: string;
|
|
5
|
+
/** `${PREFIX}_FILE` — auto-load this file/document on startup. */
|
|
6
|
+
file?: string;
|
|
7
|
+
/** `${PREFIX}_DEBUG` — emit verbose load logging. */
|
|
8
|
+
debug: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Options for {@link readAppDevHooks}. */
|
|
11
|
+
export interface ReadAppDevHooksOptions {
|
|
12
|
+
/** Env-var prefix without a trailing underscore, e.g. `BH_APP` or `ER_APP`. */
|
|
13
|
+
prefix: string;
|
|
14
|
+
/** Env map to read from. Defaults to `globalThis.process?.env`. */
|
|
15
|
+
env?: Record<string, string | undefined>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Read the `${prefix}_VIEW` / `${prefix}_FILE` / `${prefix}_DEBUG` dev hooks.
|
|
19
|
+
* Empty-string values are treated as unset.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readAppDevHooks(options: ReadAppDevHooksOptions): AppDevHooks;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type Gtk from '@girs/gtk-4.0';
|
|
2
|
+
/** Options for {@link confirmDialog}. */
|
|
3
|
+
export interface ConfirmOptions {
|
|
4
|
+
/** Dialog heading. */
|
|
5
|
+
heading: string;
|
|
6
|
+
/** Body text under the heading. */
|
|
7
|
+
body?: string;
|
|
8
|
+
/** Confirm-button label. Default `OK`. */
|
|
9
|
+
confirmLabel?: string;
|
|
10
|
+
/** Cancel-button label. Default `Cancel`. */
|
|
11
|
+
cancelLabel?: string;
|
|
12
|
+
/** Style the confirm button as destructive (red) instead of suggested. */
|
|
13
|
+
destructive?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Present a confirm/cancel `Adw.AlertDialog`; resolves `true` on confirm, `false`
|
|
17
|
+
* on cancel/dismiss. `parent` anchors the dialog (typically the window).
|
|
18
|
+
*/
|
|
19
|
+
export declare function confirmDialog(parent: Gtk.Widget, options: ConfirmOptions): Promise<boolean>;
|
|
20
|
+
/** Present a single-button error `Adw.AlertDialog`; resolves when dismissed. */
|
|
21
|
+
export declare function errorDialog(parent: Gtk.Widget, heading: string, body?: string): Promise<void>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Gtk from '@girs/gtk-4.0';
|
|
2
|
+
/** A named file filter (glob patterns and/or MIME types). */
|
|
3
|
+
export interface FileFilterSpec {
|
|
4
|
+
/** Human-readable filter name. */
|
|
5
|
+
name: string;
|
|
6
|
+
/** Glob patterns, e.g. `*.json`. */
|
|
7
|
+
patterns?: string[];
|
|
8
|
+
/** MIME types, e.g. `application/pdf`. */
|
|
9
|
+
mimeTypes?: string[];
|
|
10
|
+
}
|
|
11
|
+
/** Options for {@link pickFile} / {@link saveFile}. */
|
|
12
|
+
export interface PickFileOptions {
|
|
13
|
+
/** Dialog title. */
|
|
14
|
+
title?: string;
|
|
15
|
+
/** File filters offered in the dialog. */
|
|
16
|
+
filters?: FileFilterSpec[];
|
|
17
|
+
/** Pre-filled file name (save dialogs). */
|
|
18
|
+
initialName?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Open-file dialog; resolves the chosen path, or `null` if cancelled. */
|
|
21
|
+
export declare function pickFile(parent: Gtk.Window, options?: PickFileOptions): Promise<string | null>;
|
|
22
|
+
/** Save-file dialog; resolves the chosen path, or `null` if cancelled. */
|
|
23
|
+
export declare function saveFile(parent: Gtk.Window, options?: PickFileOptions): Promise<string | null>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { AdwaitaApp, runAdwaitaApp } from './application.js';
|
|
2
|
+
export type { AdwaitaAppOptions } from './application.js';
|
|
3
|
+
export { createNavShell } from './nav-shell.js';
|
|
4
|
+
export type { NavShell, NavShellOptions } from './nav-shell.js';
|
|
5
|
+
export { LoadToken, loadIntoStack } from './view-loader.js';
|
|
6
|
+
export type { LoadIntoStackOptions } from './view-loader.js';
|
|
7
|
+
export { LoadingStack } from './loading-stack.js';
|
|
8
|
+
export { confirmDialog, errorDialog } from './dialogs.js';
|
|
9
|
+
export type { ConfirmOptions } from './dialogs.js';
|
|
10
|
+
export { registerToastOverlay, showToast } from './toast.js';
|
|
11
|
+
export { pickFile, saveFile } from './file-dialog.js';
|
|
12
|
+
export type { FileFilterSpec, PickFileOptions } from './file-dialog.js';
|
|
13
|
+
export { readAppDevHooks } from './dev-hooks.js';
|
|
14
|
+
export type { AppDevHooks, ReadAppDevHooksOptions } from './dev-hooks.js';
|
|
15
|
+
export { findNavItem, resolveInitialNavIndex } from './nav-model.js';
|
|
16
|
+
export type { AboutInfo, NavItem } from './types.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Gtk from '@girs/gtk-4.0';
|
|
2
|
+
/**
|
|
3
|
+
* A `Gtk.Stack` pre-wired with the three named pages {@link loadIntoStack}
|
|
4
|
+
* switches between: `loading` (a centered `Adw.Spinner`), `content` (a settable
|
|
5
|
+
* child), and `error` (an `Adw.StatusPage`). Starts on `loading`.
|
|
6
|
+
*/
|
|
7
|
+
export declare class LoadingStack extends Gtk.Stack {
|
|
8
|
+
private readonly _content;
|
|
9
|
+
private readonly _error;
|
|
10
|
+
constructor(params?: Partial<Gtk.Stack.ConstructorProps>);
|
|
11
|
+
/** Set the widget shown on the `content` page. */
|
|
12
|
+
setContent(widget: Gtk.Widget): void;
|
|
13
|
+
/** Customize the `error` page's title (and optional description). */
|
|
14
|
+
setError(title: string, description?: string): void;
|
|
15
|
+
/** Show the loading spinner. */
|
|
16
|
+
showLoading(): void;
|
|
17
|
+
/** Show the content page. */
|
|
18
|
+
showContent(): void;
|
|
19
|
+
/** Show the error page (optionally setting its text first via {@link setError}). */
|
|
20
|
+
showError(): void;
|
|
21
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { NavItem } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the sidebar row index to select on startup.
|
|
4
|
+
*
|
|
5
|
+
* @returns the index of the item whose id matches `wantedId`; `0` when there is
|
|
6
|
+
* no match or no `wantedId` but the list is non-empty; `-1` for an empty list.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveInitialNavIndex(items: readonly NavItem[], wantedId?: string): number;
|
|
9
|
+
/** Find a nav item by id, or `undefined`. */
|
|
10
|
+
export declare function findNavItem(items: readonly NavItem[], id: string): NavItem | undefined;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import Adw from '@girs/adw-1';
|
|
2
|
+
import Gtk from '@girs/gtk-4.0';
|
|
3
|
+
import type { NavItem } from './types.js';
|
|
4
|
+
/** Options for {@link createNavShell}. */
|
|
5
|
+
export interface NavShellOptions {
|
|
6
|
+
/** Sidebar rows + stack child ids, in order. */
|
|
7
|
+
items: readonly NavItem[];
|
|
8
|
+
/** Fired when the selected item changes (row-selected). */
|
|
9
|
+
onSelect: (item: NavItem, index: number) => void;
|
|
10
|
+
/** Sidebar header title. */
|
|
11
|
+
sidebarTitle?: string;
|
|
12
|
+
/** Widget packed at the start of the sidebar header (e.g. an open button). */
|
|
13
|
+
sidebarHeaderStart?: Gtk.Widget;
|
|
14
|
+
/** Widget packed at the end of the sidebar header (e.g. a menu button). */
|
|
15
|
+
sidebarHeaderEnd?: Gtk.Widget;
|
|
16
|
+
/** Collapse the split view below this width (px). Default `720`. */
|
|
17
|
+
collapseWidth?: number;
|
|
18
|
+
}
|
|
19
|
+
/** The pieces a consumer wires into its window + views. */
|
|
20
|
+
export interface NavShell {
|
|
21
|
+
/** Top-level widget — set as the window content. */
|
|
22
|
+
widget: Adw.NavigationSplitView;
|
|
23
|
+
/** Content stack — add view widgets via `add_named(view, item.id)`. */
|
|
24
|
+
stack: Gtk.Stack;
|
|
25
|
+
/** Content header bar — add title widgets / buttons. */
|
|
26
|
+
contentHeader: Adw.HeaderBar;
|
|
27
|
+
/** Select a nav item by its id (falls back to the first item). */
|
|
28
|
+
selectById(id: string): void;
|
|
29
|
+
/** Select a nav item by index. */
|
|
30
|
+
selectByIndex(index: number): void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build the nav shell into `window` (which owns the responsive breakpoint).
|
|
34
|
+
* Returns the split view to set as content plus the stack + content header to
|
|
35
|
+
* fill. Row selection drives `onSelect`; a collapsed shell reveals the content
|
|
36
|
+
* pane on selection.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createNavShell(window: Adw.ApplicationWindow, options: NavShellOptions): NavShell;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Adw from '@girs/adw-1';
|
|
2
|
+
/** Register the active window's toast overlay. Call once, at window build. */
|
|
3
|
+
export declare function registerToastOverlay(toastOverlay: Adw.ToastOverlay): void;
|
|
4
|
+
/**
|
|
5
|
+
* Show a toast on the registered overlay. No-ops with a warning if
|
|
6
|
+
* {@link registerToastOverlay} has not been called yet.
|
|
7
|
+
*
|
|
8
|
+
* @param timeout auto-dismiss seconds; `0` = sticky. Default `3`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function showToast(title: string, timeout?: number): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** A single entry in the navigation sidebar / view stack. */
|
|
2
|
+
export interface NavItem {
|
|
3
|
+
/** Stable id used as the `Gtk.Stack` child name and for `selectById`. */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Sidebar row label. */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Optional symbolic icon name for the sidebar row. */
|
|
8
|
+
icon?: string;
|
|
9
|
+
/** Optional dim caption under the label. */
|
|
10
|
+
subtitle?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Fields for the standard `app.about` dialog. All optional except the name;
|
|
14
|
+
* omitted fields are simply not set on the `Adw.AboutDialog`.
|
|
15
|
+
*/
|
|
16
|
+
export interface AboutInfo {
|
|
17
|
+
/** Application name shown at the top of the dialog. */
|
|
18
|
+
applicationName: string;
|
|
19
|
+
/** Version string (e.g. `1.2.0`). */
|
|
20
|
+
version?: string;
|
|
21
|
+
/** "Developer" / vendor line. */
|
|
22
|
+
developerName?: string;
|
|
23
|
+
/** Icon name; defaults to the application id. */
|
|
24
|
+
applicationIcon?: string;
|
|
25
|
+
/** Homepage URL. */
|
|
26
|
+
website?: string;
|
|
27
|
+
/** Issue-tracker URL. */
|
|
28
|
+
issueUrl?: string;
|
|
29
|
+
/** License text or SPDX id (free-form, shown verbatim). */
|
|
30
|
+
license?: string;
|
|
31
|
+
/** Developer credit lines. */
|
|
32
|
+
developers?: string[];
|
|
33
|
+
/** Copyright line. */
|
|
34
|
+
copyright?: string;
|
|
35
|
+
/** Extra description shown under the header. */
|
|
36
|
+
comments?: string;
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type Gtk from '@girs/gtk-4.0';
|
|
2
|
+
/**
|
|
3
|
+
* Monotonic counter guarding against stale async reloads: each load takes a
|
|
4
|
+
* `next()` ticket and only applies its result while it is still `current`.
|
|
5
|
+
*/
|
|
6
|
+
export declare class LoadToken {
|
|
7
|
+
private _current;
|
|
8
|
+
/** Begin a new load; returns its ticket. */
|
|
9
|
+
next(): number;
|
|
10
|
+
/** The most recently issued ticket. */
|
|
11
|
+
get current(): number;
|
|
12
|
+
}
|
|
13
|
+
/** Options for {@link loadIntoStack}. */
|
|
14
|
+
export interface LoadIntoStackOptions<T> {
|
|
15
|
+
/** The `Gtk.Stack` whose visible child is swapped between the three pages. */
|
|
16
|
+
stack: Gtk.Stack;
|
|
17
|
+
/** Shared token so a later reload supersedes an in-flight one. */
|
|
18
|
+
token: LoadToken;
|
|
19
|
+
/** Produce the view data (sync or async). */
|
|
20
|
+
load: () => T | Promise<T>;
|
|
21
|
+
/** Populate the content page from the loaded data. */
|
|
22
|
+
fill: (data: T) => void;
|
|
23
|
+
/** Stack child name shown while loading. Default `loading`. */
|
|
24
|
+
loadingName?: string;
|
|
25
|
+
/** Stack child name shown on success. Default `content`. */
|
|
26
|
+
contentName?: string;
|
|
27
|
+
/** Stack child name shown on failure. Default `error`. */
|
|
28
|
+
errorName?: string;
|
|
29
|
+
/** Called with the error before the error page is shown. */
|
|
30
|
+
onError?: (error: unknown) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Run `load`, then `fill` + show the content page — unless a newer reload has
|
|
34
|
+
* started (result dropped) or `load` throws (error page shown). Always shows the
|
|
35
|
+
* loading page first. Never rejects.
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadIntoStack<T>(options: LoadIntoStackOptions<T>): void;
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gjsify/adwaita-app",
|
|
3
|
+
"version": "0.16.5",
|
|
4
|
+
"description": "Native Adwaita application shell for GJS/GTK apps — Adw.Application lifecycle (runAsync + devtools + CSS), a data-driven NavigationSplitView nav shell, an async-view mounter, and promise dialog/toast/file helpers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "lib/esm/index.js",
|
|
7
|
+
"types": "lib/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/types/index.d.ts",
|
|
11
|
+
"default": "./lib/esm/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"lib"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"clear": "rm -rf lib tmp tsconfig.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
|
|
19
|
+
"check": "gjsify tsc --noEmit",
|
|
20
|
+
"build": "gjsify run build:gjsify && gjsify run build:types",
|
|
21
|
+
"build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
|
|
22
|
+
"build:types": "gjsify tsc",
|
|
23
|
+
"build:test": "gjsify run build:test:gjs && gjsify run build:test:node",
|
|
24
|
+
"build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
|
|
25
|
+
"build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
|
|
26
|
+
"test": "gjsify run build:gjsify && gjsify run build:test && gjsify run test:gjs",
|
|
27
|
+
"test:gjs": "gjsify run test.gjs.mjs",
|
|
28
|
+
"test:node": "node test.node.mjs",
|
|
29
|
+
"storybook": "gjsify storybook",
|
|
30
|
+
"storybook:build": "gjsify storybook --build-only --out dist/storybook.gjs.mjs"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"gjs",
|
|
34
|
+
"gtk",
|
|
35
|
+
"adwaita",
|
|
36
|
+
"application",
|
|
37
|
+
"shell",
|
|
38
|
+
"navigation",
|
|
39
|
+
"framework"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@girs/adw-1": "^4.1.0",
|
|
43
|
+
"@girs/gdk-4.0": "^4.1.0",
|
|
44
|
+
"@girs/gio-2.0": "^4.1.0",
|
|
45
|
+
"@girs/gjs": "^4.1.0",
|
|
46
|
+
"@girs/glib-2.0": "^4.1.0",
|
|
47
|
+
"@girs/gobject-2.0": "^4.1.0",
|
|
48
|
+
"@girs/gtk-4.0": "^4.1.0",
|
|
49
|
+
"@gjsify/devtools": "^0.16.5"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@gjsify/cli": "^0.16.5",
|
|
53
|
+
"@gjsify/storybook": "^0.16.5",
|
|
54
|
+
"@gjsify/unit": "^0.16.5",
|
|
55
|
+
"@types/node": "^25.9.2",
|
|
56
|
+
"typescript": "^6.0.3"
|
|
57
|
+
},
|
|
58
|
+
"gjsify": {
|
|
59
|
+
"runtimes": {
|
|
60
|
+
"gjs": "polyfill",
|
|
61
|
+
"node": "none",
|
|
62
|
+
"browser": "none",
|
|
63
|
+
"nativescript": "none"
|
|
64
|
+
},
|
|
65
|
+
"tier": 2,
|
|
66
|
+
"storybook": {
|
|
67
|
+
"applicationId": "org.gjsify.AdwaitaAppStorybook",
|
|
68
|
+
"title": "Adwaita App",
|
|
69
|
+
"stories": "stories",
|
|
70
|
+
"globals": "auto"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"license": "MIT",
|
|
74
|
+
"repository": {
|
|
75
|
+
"type": "git",
|
|
76
|
+
"url": "git+https://github.com/gjsify/gjsify.git",
|
|
77
|
+
"directory": "packages/framework/adwaita-app"
|
|
78
|
+
},
|
|
79
|
+
"bugs": {
|
|
80
|
+
"url": "https://github.com/gjsify/gjsify/issues"
|
|
81
|
+
},
|
|
82
|
+
"homepage": "https://github.com/gjsify/gjsify/tree/main/packages/framework/adwaita-app#readme"
|
|
83
|
+
}
|