myphp-runtime 0.0.0-stage → 1.28.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 +59 -2
- package/package.json +16 -4
- package/src/i18n.test.ts +148 -0
- package/src/i18n.ts +738 -0
- package/src/index.test.ts +161 -0
- package/src/index.ts +268 -0
- package/src/types.ts +105 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { afterEach, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
Channel,
|
|
4
|
+
call,
|
|
5
|
+
currentWindow,
|
|
6
|
+
direction,
|
|
7
|
+
event,
|
|
8
|
+
isCallError,
|
|
9
|
+
isMyPHP,
|
|
10
|
+
locale,
|
|
11
|
+
on,
|
|
12
|
+
onDocumentReload,
|
|
13
|
+
onDocumentState,
|
|
14
|
+
onFileDrop,
|
|
15
|
+
onLocaleChange,
|
|
16
|
+
onPreferences,
|
|
17
|
+
preferences,
|
|
18
|
+
runtime,
|
|
19
|
+
type DocumentState,
|
|
20
|
+
type FileDrop,
|
|
21
|
+
type Runtime,
|
|
22
|
+
type ThemePreferences,
|
|
23
|
+
} from "./index";
|
|
24
|
+
|
|
25
|
+
const calls: [string, unknown[]][] = [];
|
|
26
|
+
const listeners = new Map<string, (payload: unknown) => void>();
|
|
27
|
+
|
|
28
|
+
const fake: Runtime = {
|
|
29
|
+
call: async <T>(method: string, ...args: unknown[]) => {
|
|
30
|
+
calls.push([method, args]);
|
|
31
|
+
if (method === "Svc.fail") {
|
|
32
|
+
const err = new Error("boom") as Error & { method: string };
|
|
33
|
+
err.name = "CallError";
|
|
34
|
+
err.method = method;
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
return method as T;
|
|
38
|
+
},
|
|
39
|
+
channel: <T>(onmessage?: (value: T) => void) =>
|
|
40
|
+
({ onmessage, onclose: null, closed: false, close() {}, [Symbol.asyncIterator]: () => ({}) }) as unknown as Channel<T>,
|
|
41
|
+
on: (name, listener) => {
|
|
42
|
+
listeners.set(name, listener as (payload: unknown) => void);
|
|
43
|
+
return () => listeners.delete(name);
|
|
44
|
+
},
|
|
45
|
+
once: (name, listener) => {
|
|
46
|
+
listeners.set(name, listener as (payload: unknown) => void);
|
|
47
|
+
return () => listeners.delete(name);
|
|
48
|
+
},
|
|
49
|
+
platform: "darwin",
|
|
50
|
+
windowId: 1,
|
|
51
|
+
version: "0.1.0",
|
|
52
|
+
preferences: { accent: null, reduceMotion: false, reduceTransparency: true, highContrast: false, textScale: 1 },
|
|
53
|
+
locale: "en",
|
|
54
|
+
dir: "ltr",
|
|
55
|
+
window: {
|
|
56
|
+
minimize: () => fake.call("myphp:window.Minimize"),
|
|
57
|
+
maximize: () => fake.call("myphp:window.Maximize"),
|
|
58
|
+
unmaximize: () => fake.call("myphp:window.Unmaximize"),
|
|
59
|
+
toggleMaximize: () => fake.call("myphp:window.ToggleMaximize"),
|
|
60
|
+
isMaximized: () => fake.call("myphp:window.IsMaximized"),
|
|
61
|
+
toggleFullScreen: () => fake.call("myphp:window.ToggleFullScreen"),
|
|
62
|
+
close: () => fake.call("myphp:window.Close"),
|
|
63
|
+
setTitle: (title) => fake.call("myphp:window.SetTitle", title),
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
delete (globalThis as { myphp?: Runtime }).myphp;
|
|
69
|
+
calls.length = 0;
|
|
70
|
+
listeners.clear();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("outside MyPHP", async () => {
|
|
74
|
+
expect(isMyPHP()).toBe(false);
|
|
75
|
+
expect(() => runtime()).toThrow("runtime not found");
|
|
76
|
+
await expect(call("Svc.method")).rejects.toThrow("runtime not found");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("delegates to window.myphp", async () => {
|
|
80
|
+
(globalThis as { myphp?: Runtime }).myphp = fake;
|
|
81
|
+
expect(isMyPHP()).toBe(true);
|
|
82
|
+
expect(await call<string>("Svc.method", 1, "a")).toBe("Svc.method");
|
|
83
|
+
expect(calls).toEqual([["Svc.method", [1, "a"]]]);
|
|
84
|
+
|
|
85
|
+
const got: unknown[] = [];
|
|
86
|
+
const off = event<{ n: number }>("tick").on((p) => got.push(p.n));
|
|
87
|
+
listeners.get("tick")?.({ n: 7 });
|
|
88
|
+
off();
|
|
89
|
+
expect(got).toEqual([7]);
|
|
90
|
+
expect(listeners.has("tick")).toBe(false);
|
|
91
|
+
|
|
92
|
+
on("other", () => {});
|
|
93
|
+
expect(listeners.has("other")).toBe(true);
|
|
94
|
+
|
|
95
|
+
await currentWindow.setTitle("Hi");
|
|
96
|
+
expect(calls.at(-1)).toEqual(["myphp:window.SetTitle", ["Hi"]]);
|
|
97
|
+
|
|
98
|
+
const err = await call("Svc.fail").catch((e: unknown) => e);
|
|
99
|
+
expect(isCallError(err)).toBe(true);
|
|
100
|
+
expect(isCallError(new Error("other"))).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("Channel", () => {
|
|
104
|
+
expect(() => new Channel()).toThrow("runtime not found");
|
|
105
|
+
(globalThis as { myphp?: Runtime }).myphp = fake;
|
|
106
|
+
const fn = (n: number) => n;
|
|
107
|
+
const ch = new Channel<number>(fn);
|
|
108
|
+
expect(ch.onmessage).toBe(fn);
|
|
109
|
+
expect(ch.closed).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("onFileDrop", () => {
|
|
113
|
+
(globalThis as { myphp?: Runtime }).myphp = fake;
|
|
114
|
+
const got: FileDrop[] = [];
|
|
115
|
+
const off = onFileDrop((d) => got.push(d));
|
|
116
|
+
listeners.get("myphp:file-drop")?.({ paths: ["/tmp/a.txt"], x: 10, y: 20 });
|
|
117
|
+
expect(got).toEqual([{ paths: ["/tmp/a.txt"], x: 10, y: 20 }]);
|
|
118
|
+
off();
|
|
119
|
+
expect(listeners.has("myphp:file-drop")).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("onDocumentState and onDocumentReload", () => {
|
|
123
|
+
(globalThis as { myphp?: Runtime }).myphp = fake;
|
|
124
|
+
const states: DocumentState[] = [];
|
|
125
|
+
let reloads = 0;
|
|
126
|
+
const off = onDocumentState((s) => states.push(s));
|
|
127
|
+
const offReload = onDocumentReload(() => reloads++);
|
|
128
|
+
const state = { name: "a.md", path: "/tmp/a.md", edited: true, readOnly: false, conflict: false, deleted: false };
|
|
129
|
+
listeners.get("myphp:document-state")?.(state);
|
|
130
|
+
listeners.get("myphp:document-reload")?.({});
|
|
131
|
+
expect(states).toEqual([state]);
|
|
132
|
+
expect(reloads).toBe(1);
|
|
133
|
+
off();
|
|
134
|
+
offReload();
|
|
135
|
+
expect(listeners.has("myphp:document-state")).toBe(false);
|
|
136
|
+
expect(listeners.has("myphp:document-reload")).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("preferences and onPreferences", () => {
|
|
140
|
+
(globalThis as { myphp?: Runtime }).myphp = fake;
|
|
141
|
+
expect(preferences()).toEqual(fake.preferences);
|
|
142
|
+
const got: ThemePreferences[] = [];
|
|
143
|
+
const off = onPreferences((p) => got.push(p));
|
|
144
|
+
const changed = { accent: "#ff8000", reduceMotion: true, reduceTransparency: false, highContrast: false, textScale: 1.25 };
|
|
145
|
+
listeners.get("myphp:preferences")?.(changed);
|
|
146
|
+
expect(got).toEqual([changed]);
|
|
147
|
+
off();
|
|
148
|
+
expect(listeners.has("myphp:preferences")).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("locale, direction and onLocaleChange", () => {
|
|
152
|
+
(globalThis as { myphp?: Runtime }).myphp = fake;
|
|
153
|
+
expect(locale()).toBe("en");
|
|
154
|
+
expect(direction()).toBe("ltr");
|
|
155
|
+
const got: [string, string][] = [];
|
|
156
|
+
const off = onLocaleChange((tag, dir) => got.push([tag, dir]));
|
|
157
|
+
listeners.get("myphp:locale")?.({ locale: "ar", dir: "rtl" });
|
|
158
|
+
expect(got).toEqual([["ar", "rtl"]]);
|
|
159
|
+
off();
|
|
160
|
+
expect(listeners.has("myphp:locale")).toBe(false);
|
|
161
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed access to the MyPHP runtime, which MyPHP injects into every page of
|
|
3
|
+
* an app as `window.myphp`. The client that `myphp generate` writes for your PHP
|
|
4
|
+
* services is built on it; use it directly for window controls and the
|
|
5
|
+
* platform:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { currentWindow, runtime } from "myphp-runtime";
|
|
9
|
+
*
|
|
10
|
+
* if (runtime().platform === "darwin") document.body.classList.add("mac");
|
|
11
|
+
* await currentWindow.toggleMaximize();
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import type {
|
|
17
|
+
Channel as ChannelType,
|
|
18
|
+
Direction,
|
|
19
|
+
LocaleChange,
|
|
20
|
+
Runtime,
|
|
21
|
+
ThemePreferences,
|
|
22
|
+
WindowControls,
|
|
23
|
+
} from "./types";
|
|
24
|
+
|
|
25
|
+
export type { Direction, LocaleChange, Platform, Runtime, ThemePreferences, WindowControls } from "./types";
|
|
26
|
+
|
|
27
|
+
declare global {
|
|
28
|
+
interface Window {
|
|
29
|
+
/** The MyPHP runtime; missing outside MyPHP windows. */
|
|
30
|
+
myphp?: Runtime;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Reports whether the page runs inside a MyPHP window. */
|
|
35
|
+
export function isMyPHP(): boolean {
|
|
36
|
+
return typeof globalThis === "object" && (globalThis as { myphp?: Runtime }).myphp !== undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Returns the MyPHP runtime. Throws when the page is not running inside a
|
|
41
|
+
* MyPHP window, e.g. when the dev server is opened in a regular browser.
|
|
42
|
+
*/
|
|
43
|
+
export function runtime(): Runtime {
|
|
44
|
+
const rt = (globalThis as { myphp?: Runtime }).myphp;
|
|
45
|
+
if (!rt) throw new Error("myphp: runtime not found; is this page running inside a MyPHP window?");
|
|
46
|
+
return rt;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Calls a bound PHP method by name, e.g. `call("Greeter.greet", "Ada")`.
|
|
51
|
+
* Prefer the typed client generated by `myphp generate`. A PHP exception rejects
|
|
52
|
+
* with a {@link CallError}.
|
|
53
|
+
*/
|
|
54
|
+
export function call<T = unknown>(method: string, ...args: unknown[]): Promise<T> {
|
|
55
|
+
try {
|
|
56
|
+
return runtime().call<T>(method, ...args);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return Promise.reject(err);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A stream of values from a PHP method that takes a `MyPHP\Channel`.
|
|
64
|
+
* Create one, pass it to the call in place of that parameter, and iterate
|
|
65
|
+
* it or handle its values with `onmessage`:
|
|
66
|
+
*
|
|
67
|
+
* ```ts
|
|
68
|
+
* const lines = new Channel<string>();
|
|
69
|
+
* const done = Shell.tail("ping -c 3 example.com", lines);
|
|
70
|
+
* for await (const line of lines) output.append(line + "\n");
|
|
71
|
+
* await done;
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* It closes when the method returns; `close()`, or breaking out of the
|
|
75
|
+
* loop, stops the method. A channel serves one call.
|
|
76
|
+
*/
|
|
77
|
+
export type Channel<T = unknown> = ChannelType<T>;
|
|
78
|
+
|
|
79
|
+
/** Creates a {@link Channel}, optionally with its `onmessage` handler. */
|
|
80
|
+
export const Channel = function <T>(onmessage?: (value: T) => void): Channel<T> {
|
|
81
|
+
return runtime().channel<T>(onmessage);
|
|
82
|
+
} as unknown as new <T = unknown>(onmessage?: (value: T) => void) => Channel<T>;
|
|
83
|
+
|
|
84
|
+
/** Subscribes to a PHP event by name. Returns a function that unsubscribes. */
|
|
85
|
+
export function on<T = unknown>(name: string, listener: (payload: T) => void): () => void {
|
|
86
|
+
return runtime().on<T>(name, listener);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Like {@link on}, but unsubscribes after the first event. */
|
|
90
|
+
export function once<T = unknown>(name: string, listener: (payload: T) => void): () => void {
|
|
91
|
+
return runtime().once<T>(name, listener);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The language the app shows, a BCP 47 tag such as "en" or "zh-Hans"
|
|
96
|
+
* (`App::locale()`): the page has it from document start, so its first paint
|
|
97
|
+
* can be in it.
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* document.documentElement.lang = locale();
|
|
101
|
+
* document.documentElement.dir = direction();
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function locale(): string {
|
|
105
|
+
return runtime().locale;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The direction the language the app shows is written in: "ltr" or "rtl". */
|
|
109
|
+
export function direction(): Direction {
|
|
110
|
+
return runtime().dir;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Subscribes to changes of the language the app shows, as `App::setLocale()`
|
|
115
|
+
* makes them, or the user's languages while the app follows them. The
|
|
116
|
+
* listener hears only of a language the page did not have: a page loaded
|
|
117
|
+
* after a change starts with the new one. Returns a function that
|
|
118
|
+
* unsubscribes.
|
|
119
|
+
*
|
|
120
|
+
* ```ts
|
|
121
|
+
* onLocaleChange((tag, dir) => {
|
|
122
|
+
* document.documentElement.lang = tag;
|
|
123
|
+
* document.documentElement.dir = dir;
|
|
124
|
+
* });
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export function onLocaleChange(listener: (locale: string, dir: Direction) => void): () => void {
|
|
128
|
+
return on<LocaleChange>("myphp:locale", (p) => listener(p.locale, p.dir));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** A typed event sent by the PHP side, declared with `new Event(...)`. */
|
|
132
|
+
export interface PhpEvent<T> {
|
|
133
|
+
readonly name: string;
|
|
134
|
+
/** Subscribes to the event and returns a function that unsubscribes. */
|
|
135
|
+
on(listener: (payload: T) => void): () => void;
|
|
136
|
+
/** Like on, but unsubscribes after the first event. */
|
|
137
|
+
once(listener: (payload: T) => void): () => void;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Returns the typed event `name`; the generated client declares them. */
|
|
141
|
+
export function event<T>(name: string): PhpEvent<T> {
|
|
142
|
+
return {
|
|
143
|
+
name,
|
|
144
|
+
on: (listener) => on<T>(name, listener),
|
|
145
|
+
once: (listener) => once<T>(name, listener),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The error of a call whose PHP method threw. */
|
|
150
|
+
export interface CallError extends Error {
|
|
151
|
+
readonly name: "CallError";
|
|
152
|
+
/** The method that failed, e.g. "Greeter.greet". */
|
|
153
|
+
readonly method: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Reports whether err comes from a PHP method that threw. */
|
|
157
|
+
export function isCallError(err: unknown): err is CallError {
|
|
158
|
+
return err instanceof Error && err.name === "CallError";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Files dropped on the window, e.g. from Finder or Explorer. */
|
|
162
|
+
export interface FileDrop {
|
|
163
|
+
/** Absolute paths of the dropped files and directories. */
|
|
164
|
+
paths: string[];
|
|
165
|
+
/** Where they were dropped, in CSS pixels, like `clientX`. */
|
|
166
|
+
x: number;
|
|
167
|
+
/** Where they were dropped, in CSS pixels, like `clientY`. */
|
|
168
|
+
y: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Subscribes to files dropped on the window, with their paths, which DOM
|
|
173
|
+
* drop events do not tell. Only the app's own pages get them. Returns a
|
|
174
|
+
* function that unsubscribes.
|
|
175
|
+
*
|
|
176
|
+
* ```ts
|
|
177
|
+
* onFileDrop(({ paths, x, y }) => {
|
|
178
|
+
* const target = document.elementFromPoint(x, y);
|
|
179
|
+
* });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export function onFileDrop(listener: (drop: FileDrop) => void): () => void {
|
|
183
|
+
return on<FileDrop>("myphp:file-drop", listener);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The document a window shows (`new Documents(...)`), as PHP describes it. */
|
|
187
|
+
export interface DocumentState {
|
|
188
|
+
/** The file name, or a name such as "Untitled 2" while untitled. */
|
|
189
|
+
name: string;
|
|
190
|
+
/** The file, "" while the document is untitled. */
|
|
191
|
+
path: string;
|
|
192
|
+
/** Whether the document has unsaved changes. */
|
|
193
|
+
edited: boolean;
|
|
194
|
+
/** Whether its file can't be written: saving asks for another. */
|
|
195
|
+
readOnly: boolean;
|
|
196
|
+
/** Whether another app changed its file while it had unsaved changes. */
|
|
197
|
+
conflict: boolean;
|
|
198
|
+
/** Whether its file was deleted: saving writes it again. */
|
|
199
|
+
deleted: boolean;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Subscribes to the state of the window's document: edited, saved,
|
|
204
|
+
* renamed, moved, changed by another app. Only the app's own pages get it.
|
|
205
|
+
* Returns a function that unsubscribes.
|
|
206
|
+
*
|
|
207
|
+
* ```ts
|
|
208
|
+
* onDocumentState(({ name, edited }) => {
|
|
209
|
+
* heading.textContent = edited ? `${name} (edited)` : name;
|
|
210
|
+
* });
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export function onDocumentState(listener: (state: DocumentState) => void): () => void {
|
|
214
|
+
return on<DocumentState>("myphp:document-state", listener);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Subscribes to the window's document being read from its file again:
|
|
219
|
+
* reverted, or changed by another app while it had no unsaved changes.
|
|
220
|
+
* Fetch its content again, with the service bound to
|
|
221
|
+
* `$documents->service()`. Returns a function that unsubscribes.
|
|
222
|
+
*/
|
|
223
|
+
export function onDocumentReload(listener: () => void): () => void {
|
|
224
|
+
return on<unknown>("myphp:document-reload", () => listener());
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Returns the accent color and the accessibility settings of the desktop
|
|
229
|
+
* (`Theme::preferences()` in PHP), current from document start on.
|
|
230
|
+
* Throws outside MyPHP, as {@link runtime} does.
|
|
231
|
+
*
|
|
232
|
+
* ```ts
|
|
233
|
+
* const { accent, reduceTransparency } = preferences();
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* CSS gets the accent and the text scale from the `--myphp-accent` and
|
|
237
|
+
* `--myphp-text-scale` variables of `:root`.
|
|
238
|
+
*/
|
|
239
|
+
export function preferences(): ThemePreferences {
|
|
240
|
+
return runtime().preferences;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Subscribes to changes of the desktop's preferences, which
|
|
245
|
+
* {@link preferences} returns; the listener gets the new ones. Returns a
|
|
246
|
+
* function that unsubscribes.
|
|
247
|
+
*
|
|
248
|
+
* ```ts
|
|
249
|
+
* const apply = (p: ThemePreferences) => root.classList.toggle("opaque", p.reduceTransparency);
|
|
250
|
+
* apply(preferences());
|
|
251
|
+
* onPreferences(apply);
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
export function onPreferences(listener: (preferences: ThemePreferences) => void): () => void {
|
|
255
|
+
return on<ThemePreferences>("myphp:preferences", listener);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** The window hosting the page, e.g. for a custom title bar. */
|
|
259
|
+
export const currentWindow: WindowControls = {
|
|
260
|
+
minimize: async () => runtime().window.minimize(),
|
|
261
|
+
maximize: async () => runtime().window.maximize(),
|
|
262
|
+
unmaximize: async () => runtime().window.unmaximize(),
|
|
263
|
+
toggleMaximize: async () => runtime().window.toggleMaximize(),
|
|
264
|
+
isMaximized: async () => runtime().window.isMaximized(),
|
|
265
|
+
toggleFullScreen: async () => runtime().window.toggleFullScreen(),
|
|
266
|
+
close: async () => runtime().window.close(),
|
|
267
|
+
setTitle: async (title) => runtime().window.setTitle(title),
|
|
268
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/** The operating system, using Node.js' `process.platform` names. */
|
|
2
|
+
export type Platform = "darwin" | "linux" | "win32";
|
|
3
|
+
|
|
4
|
+
/** The direction a language is written in: left to right, or right to left. */
|
|
5
|
+
export type Direction = "ltr" | "rtl";
|
|
6
|
+
|
|
7
|
+
/** The payload of the `myphp:locale` event. */
|
|
8
|
+
export interface LocaleChange {
|
|
9
|
+
/** The language the app shows now, e.g. "zh-Hans". */
|
|
10
|
+
locale: string;
|
|
11
|
+
/** The direction it is written in. */
|
|
12
|
+
dir: Direction;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Controls for the window hosting the page, handy for custom title bars. */
|
|
16
|
+
export interface WindowControls {
|
|
17
|
+
minimize(): Promise<void>;
|
|
18
|
+
maximize(): Promise<void>;
|
|
19
|
+
unmaximize(): Promise<void>;
|
|
20
|
+
toggleMaximize(): Promise<void>;
|
|
21
|
+
isMaximized(): Promise<boolean>;
|
|
22
|
+
toggleFullScreen(): Promise<void>;
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
setTitle(title: string): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A stream of values from a PHP method that takes a `MyPHP\Channel`,
|
|
29
|
+
* passed to the call in its place. Iterate it, or handle the values with
|
|
30
|
+
* `onmessage`: values that arrive before either wait for it. The channel
|
|
31
|
+
* closes when the method returns, or calls `Close`, once its values are
|
|
32
|
+
* taken; `close()` stops the method.
|
|
33
|
+
*/
|
|
34
|
+
export interface Channel<T = unknown> extends AsyncIterable<T> {
|
|
35
|
+
/** Called with each value, instead of iterating. */
|
|
36
|
+
onmessage: ((value: T) => void) | null;
|
|
37
|
+
/** Called when the channel closes. */
|
|
38
|
+
onclose: (() => void) | null;
|
|
39
|
+
/** Whether the channel is closed. */
|
|
40
|
+
readonly closed: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Closes the channel, dropping values not taken yet: the PHP method's
|
|
43
|
+
* `send()` fails and it stops. Breaking out of a
|
|
44
|
+
* `for await` loop over the channel closes it too.
|
|
45
|
+
*/
|
|
46
|
+
close(): void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The accent color and the accessibility settings of the desktop, as
|
|
51
|
+
* `MyPHP\ThemePreferences` encodes them.
|
|
52
|
+
*/
|
|
53
|
+
export interface ThemePreferences {
|
|
54
|
+
/** The accent color of the desktop's controls as "#rrggbb", or null where the desktop has none. */
|
|
55
|
+
accent: string | null;
|
|
56
|
+
/** Whether the desktop asks for less motion; `prefers-reduced-motion` tells it too. */
|
|
57
|
+
reduceMotion: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the desktop asks for opaque surfaces in place of translucent
|
|
60
|
+
* ones: macOS's Reduce Transparency, Windows's transparency effects
|
|
61
|
+
* turned off. Linux desktops have no such setting.
|
|
62
|
+
*/
|
|
63
|
+
reduceTransparency: boolean;
|
|
64
|
+
/** Whether the desktop asks for more contrast; `prefers-contrast` tells it too. */
|
|
65
|
+
highContrast: boolean;
|
|
66
|
+
/** How many times larger than usual text should be; 1 is usual. */
|
|
67
|
+
textScale: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The runtime MyPHP injects into every page as `window.myphp`. */
|
|
71
|
+
export interface Runtime {
|
|
72
|
+
/** Calls a bound PHP method, e.g. `call("Greeter.greet", "Ada")`. */
|
|
73
|
+
call<T = unknown>(method: string, ...args: unknown[]): Promise<T>;
|
|
74
|
+
/**
|
|
75
|
+
* Creates a channel to pass to a call of a PHP method that streams values
|
|
76
|
+
* through a `MyPHP\Channel` parameter. A channel serves one call.
|
|
77
|
+
*/
|
|
78
|
+
channel<T = unknown>(onmessage?: (value: T) => void): Channel<T>;
|
|
79
|
+
/** Subscribes to a PHP event. Returns a function that unsubscribes. */
|
|
80
|
+
on<T = unknown>(event: string, listener: (payload: T) => void): () => void;
|
|
81
|
+
/** Like `on`, but unsubscribes after the first event. */
|
|
82
|
+
once<T = unknown>(event: string, listener: (payload: T) => void): () => void;
|
|
83
|
+
/** The operating system. */
|
|
84
|
+
readonly platform: Platform;
|
|
85
|
+
/** The id of the `MyPHP\Window` hosting this page. */
|
|
86
|
+
readonly windowId: number;
|
|
87
|
+
/** MyPHP version. */
|
|
88
|
+
readonly version: string;
|
|
89
|
+
/**
|
|
90
|
+
* The desktop's preferences, current from document start on; the
|
|
91
|
+
* `myphp:preferences` event tells their changes.
|
|
92
|
+
*/
|
|
93
|
+
readonly preferences: ThemePreferences;
|
|
94
|
+
/**
|
|
95
|
+
* The language the app shows, a BCP 47 tag such as "en" or "zh-Hans"
|
|
96
|
+
* (`App::locale()`): of the languages the app is written in, the one the
|
|
97
|
+
* user reads best, or the one `App::setLocale()` chose. A page has it from
|
|
98
|
+
* document start, and the `myphp:locale` event tells of changes.
|
|
99
|
+
*/
|
|
100
|
+
readonly locale: string;
|
|
101
|
+
/** The direction `locale` is written in, for `<html dir>`. */
|
|
102
|
+
readonly dir: Direction;
|
|
103
|
+
/** The window hosting this page. */
|
|
104
|
+
readonly window: WindowControls;
|
|
105
|
+
}
|