@multiplatform.one/desktop 6.6.0 → 7.0.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/README.md +30 -12
- package/package.json +4 -3
- package/src/dialog/dialog.spec.ts +44 -0
- package/src/dialog/index.ts +46 -7
- package/src/updater/index.ts +6 -4
- package/types/dialog/index.d.ts.map +1 -1
- package/types/updater/index.d.ts +6 -4
- package/types/updater/index.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @multiplatform.one/desktop
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Capability interfaces for desktop-only concerns — file system, dialogs, paths,
|
|
4
|
+
and updater — behind one import that no-ops (or throws clearly) everywhere the
|
|
5
|
+
capability does not exist.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -10,8 +10,9 @@ throws clearly) on non-desktop platforms.
|
|
|
10
10
|
pnpm add @multiplatform.one/desktop
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Peers (
|
|
14
|
-
`@tauri-apps/plugin-
|
|
13
|
+
Peers (Tauri builds only, all optional): `@tauri-apps/api`,
|
|
14
|
+
`@tauri-apps/plugin-dialog`, `@tauri-apps/plugin-fs`,
|
|
15
|
+
`@tauri-apps/plugin-updater`.
|
|
15
16
|
|
|
16
17
|
## What it owns
|
|
17
18
|
|
|
@@ -21,23 +22,40 @@ Peers (desktop builds only): `@tauri-apps/api`, `@tauri-apps/plugin-dialog`,
|
|
|
21
22
|
- **`paths`** — well-known directories (home, app data, …)
|
|
22
23
|
- **`updater`** — check/install app updates
|
|
23
24
|
|
|
24
|
-
Each API
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
Each API is an interface plus a default implementation, so shared code (for
|
|
26
|
+
example `@multiplatform.one/frappe-ui`'s `FrappeForm` delete confirm) can
|
|
27
|
+
import it unconditionally on every platform.
|
|
28
|
+
|
|
29
|
+
> **State of play.** Tauri is the one backend with a full implementation:
|
|
30
|
+
> each API has a `.tauri` file (selected at build time in Tauri bundles) that
|
|
31
|
+
> rides the matching `@tauri-apps/plugin-*` peer. A Tauri window running the
|
|
32
|
+
> plain web bundle (dev mode) gets the web defaults, which work there — the
|
|
33
|
+
> webview has the browser modals.
|
|
34
|
+
>
|
|
35
|
+
> Outside Tauri: `dialog.message`, `.ask`, `.confirm` and `.prompt` are the
|
|
36
|
+
> browser's modal globals and are therefore **web-only**. On GNOME they
|
|
37
|
+
> **throw** a named error: GJS has no `window.confirm`, and neither silently
|
|
38
|
+
> returning `false` (a confirm button that appears broken) nor silently
|
|
39
|
+
> returning `true` (a delete nobody agreed to) is a defensible default for a
|
|
40
|
+
> confirmation. `dialog.open` and `.save` return `null` on both. `fs` and
|
|
41
|
+
> `paths` report unsupported outside Tauri; `updater` is a no-op there.
|
|
42
|
+
>
|
|
43
|
+
> The GNOME target has real GTK equivalents available (`Gtk.FileDialog`,
|
|
44
|
+
> `Adw.AlertDialog`, `GLib.get_user_*_dir`); wiring them up as
|
|
45
|
+
> `index.gnome.ts` files behind these same interfaces is the natural next
|
|
46
|
+
> step — the interfaces exist precisely so that lands as an implementation
|
|
47
|
+
> rather than an API change.
|
|
27
48
|
|
|
28
49
|
## What it must not do
|
|
29
50
|
|
|
30
|
-
- No UI and no
|
|
31
|
-
plugins, not chrome
|
|
51
|
+
- No UI and no window management — this package wraps capabilities, not chrome
|
|
32
52
|
|
|
33
53
|
## Usage
|
|
34
54
|
|
|
35
55
|
```ts
|
|
36
56
|
import { dialog, fs } from "@multiplatform.one/desktop";
|
|
37
|
-
import { isTauri } from "@multiplatform.one/platform";
|
|
38
57
|
|
|
39
58
|
export async function exportNotes(contents: string) {
|
|
40
|
-
if (!isTauri) return; // web/native fall back elsewhere
|
|
41
59
|
const path = await dialog.save({ defaultPath: "notes.md" });
|
|
42
60
|
if (!path) return;
|
|
43
61
|
await fs.writeTextFile(path, contents);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/desktop",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "Desktop platform APIs (Tauri) for multiplatform.one",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"desktop",
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
|
-
"typecheck": "
|
|
64
|
-
"build": "rm -rf types 2>/dev/null && tsc -p tsconfig.build.json"
|
|
63
|
+
"typecheck": "tsgo --noEmit",
|
|
64
|
+
"build": "rm -rf types 2>/dev/null && tsc -p tsconfig.build.json",
|
|
65
|
+
"test": "vitest run --coverage --coverage.provider=v8 --coverage.reporter=text --coverage.reporter=lcov --coverage.reporter=html"
|
|
65
66
|
}
|
|
66
67
|
}
|
|
@@ -91,3 +91,47 @@ describe("dialog web stubs", () => {
|
|
|
91
91
|
});
|
|
92
92
|
});
|
|
93
93
|
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* GJS's `window` is a react-gnome polyfill stub with none of the modal
|
|
97
|
+
* globals on it. Reproduce that shape rather than deleting `window`, because
|
|
98
|
+
* `typeof window !== "undefined"` is true on GNOME and a guard written only
|
|
99
|
+
* against a missing `window` would sail straight past it.
|
|
100
|
+
*/
|
|
101
|
+
describe("dialog defaults on a platform with no modal globals (GJS)", () => {
|
|
102
|
+
beforeEach(() => {
|
|
103
|
+
for (const name of ["alert", "confirm", "prompt"] as const) {
|
|
104
|
+
vi.stubGlobal(name, undefined);
|
|
105
|
+
Reflect.deleteProperty(window, name);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
afterEach(() => {
|
|
110
|
+
vi.unstubAllGlobals();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("still has a window, exactly as GNOME does", () => {
|
|
114
|
+
expect(typeof window).not.toBe("undefined");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it.each([
|
|
118
|
+
["message", () => dialog.message("hi")],
|
|
119
|
+
["ask", () => dialog.ask("sure?")],
|
|
120
|
+
["confirm", () => dialog.confirm("delete?")],
|
|
121
|
+
["prompt", () => dialog.prompt("name?")],
|
|
122
|
+
])("dialog.%s rejects instead of throwing a bare TypeError", async (api, call) => {
|
|
123
|
+
await expect(call()).rejects.toThrow(
|
|
124
|
+
new RegExp(`dialog\\.${api} needs window\\.(alert|confirm|prompt)`),
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("names the package and points at the GNOME backend that would fix it", async () => {
|
|
129
|
+
await expect(dialog.confirm("delete?")).rejects.toThrow(/@multiplatform.one\/desktop/);
|
|
130
|
+
await expect(dialog.confirm("delete?")).rejects.toThrow(/index\.gnome\.ts/);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("leaves open and save working, since their default is an honest null", async () => {
|
|
134
|
+
expect(await dialog.open()).toBeNull();
|
|
135
|
+
expect(await dialog.save()).toBeNull();
|
|
136
|
+
});
|
|
137
|
+
});
|
package/src/dialog/index.ts
CHANGED
|
@@ -39,26 +39,65 @@ export interface DialogApi {
|
|
|
39
39
|
prompt(message: string, defaultValue?: string): Promise<string | null>;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* The default `message`/`ask`/`confirm`/`prompt` are the browser's modal
|
|
44
|
+
* globals, so they are **web-only** — not the "safe default every platform
|
|
45
|
+
* gets" the docs used to claim.
|
|
46
|
+
*
|
|
47
|
+
* GJS is the case that matters: react-gnome's polyfills define a stub
|
|
48
|
+
* `window` with no `alert`, `confirm` or `prompt` on it, so these used to
|
|
49
|
+
* dereference `undefined` and throw a bare `TypeError` from inside a shared
|
|
50
|
+
* package, at whatever unrelated call site happened to trigger a confirm.
|
|
51
|
+
* Failing with a named error that says which platform is missing which
|
|
52
|
+
* capability is strictly better than that, and better than the other
|
|
53
|
+
* tempting option — quietly returning `false` — because a delete confirm
|
|
54
|
+
* that silently answers "no" looks like a broken button, while one that
|
|
55
|
+
* silently answers "yes" deletes something nobody confirmed.
|
|
56
|
+
*
|
|
57
|
+
* The real fix is `dialog/index.gnome.ts` on `Adw.AlertDialog`, resolved by
|
|
58
|
+
* the `.gnome.*` extensions the GNOME vite config registers. Until then this
|
|
59
|
+
* throws, loudly, on GNOME.
|
|
60
|
+
*/
|
|
61
|
+
function browserDialog<Name extends "alert" | "confirm" | "prompt">(
|
|
62
|
+
name: Name,
|
|
63
|
+
api: string,
|
|
64
|
+
): NonNullable<Window[Name]> {
|
|
65
|
+
const fn = typeof window === "undefined" ? undefined : window[name];
|
|
66
|
+
if (typeof fn !== "function") {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`@multiplatform.one/desktop: dialog.${api} needs window.${name}, which this platform does not have. ` +
|
|
69
|
+
`The dialog defaults are the browser's modal globals and are web-only; GNOME needs an Adw.AlertDialog ` +
|
|
70
|
+
`backend in dialog/index.gnome.ts.`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return fn.bind(window) as NonNullable<Window[Name]>;
|
|
74
|
+
}
|
|
75
|
+
|
|
43
76
|
export const dialog: DialogApi = {
|
|
44
77
|
async open(_options?: OpenDialogOptions): Promise<string | string[] | null> {
|
|
45
|
-
//
|
|
78
|
+
// No browser equivalent that returns a path; an <input type="file"> hands
|
|
79
|
+
// back a File, not a filename the fs API could then open.
|
|
46
80
|
return null;
|
|
47
81
|
},
|
|
48
82
|
async save(_options?: SaveDialogOptions): Promise<string | null> {
|
|
49
|
-
// Browser download could be used here, but returning null for simplicity
|
|
50
83
|
return null;
|
|
51
84
|
},
|
|
52
85
|
async message(message: string, options?: MessageDialogOptions): Promise<void> {
|
|
53
|
-
|
|
86
|
+
browserDialog("alert", "message")(options?.title ? `${options.title}\n\n${message}` : message);
|
|
54
87
|
},
|
|
55
88
|
async ask(message: string, options?: ConfirmDialogOptions): Promise<boolean> {
|
|
56
|
-
return
|
|
89
|
+
return browserDialog(
|
|
90
|
+
"confirm",
|
|
91
|
+
"ask",
|
|
92
|
+
)(options?.title ? `${options.title}\n\n${message}` : message);
|
|
57
93
|
},
|
|
58
94
|
async confirm(message: string, options?: ConfirmDialogOptions): Promise<boolean> {
|
|
59
|
-
return
|
|
95
|
+
return browserDialog(
|
|
96
|
+
"confirm",
|
|
97
|
+
"confirm",
|
|
98
|
+
)(options?.title ? `${options.title}\n\n${message}` : message);
|
|
60
99
|
},
|
|
61
100
|
async prompt(message: string, defaultValue = ""): Promise<string | null> {
|
|
62
|
-
return
|
|
101
|
+
return browserDialog("prompt", "prompt")(message, defaultValue);
|
|
63
102
|
},
|
|
64
103
|
};
|
package/src/updater/index.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-updater API for desktop applications
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* A platform-agnostic interface for checking and installing application
|
|
5
|
+
* updates. The Tauri target implements it via tauri-plugin-updater (the
|
|
6
|
+
* `.tauri` file next door). Everywhere else `isSupported()` returning false
|
|
7
|
+
* is the honest answer — web and mobile get updates from the service worker
|
|
8
|
+
* or the app store, and the GNOME target is installed by the system package
|
|
9
|
+
* manager.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
export interface UpdateInfo {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dialog/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IACrE,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1D,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3E,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACxE;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dialog/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IACrE,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1D,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3E,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACxE;AAoCD,eAAO,MAAM,MAAM,EAAE,SA2BpB,CAAC"}
|
package/types/updater/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-updater API for desktop applications
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* A platform-agnostic interface for checking and installing application
|
|
5
|
+
* updates. The Tauri target implements it via tauri-plugin-updater (the
|
|
6
|
+
* `.tauri` file next door). Everywhere else `isSupported()` returning false
|
|
7
|
+
* is the honest answer — web and mobile get updates from the service worker
|
|
8
|
+
* or the app store, and the GNOME target is installed by the system package
|
|
9
|
+
* manager.
|
|
8
10
|
*/
|
|
9
11
|
export interface UpdateInfo {
|
|
10
12
|
version: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/updater/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/updater/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,eAAe,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAE9C;;;;OAIG;IACH,kBAAkB,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9E;;OAEG;IACH,WAAW,IAAI,OAAO,CAAC;CACxB;AAGD,eAAO,MAAM,OAAO,EAAE,UAYrB,CAAC"}
|