glimpse-sdk 0.5.0 → 0.7.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 CHANGED
@@ -39,8 +39,8 @@ import {
39
39
  Button,
40
40
  Hero,
41
41
  Icon,
42
- Label,
43
42
  StatusItem,
43
+ Label,
44
44
  type TreeNode,
45
45
  } from "glimpse-sdk";
46
46
 
package/dist/src/app.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type CallbackEvent, type ChangeEvent, type ClickEvent, type InitEvent, type InputEvent, type ScrollEvent, type ToggleEvent } from "./events.js";
1
+ import { type CallbackEvent, type ChangeEvent, type ClickEvent, type InitEvent, type InputEvent, type PopoverEvent, type ScrollEvent, type ToggleEvent } from "./events.js";
2
2
  import { StatusItem } from "./protocol.js";
3
3
  import { type TreeNode } from "./widgets.js";
4
4
  type Handler<EventT> = (event: EventT) => void | Promise<void>;
@@ -7,16 +7,13 @@ interface OutgoingMessage {
7
7
  data: unknown;
8
8
  line: string;
9
9
  }
10
- interface ShowNotificationArgs {
11
- summary: string;
10
+ interface ShowNotificationOptions {
12
11
  body?: string;
13
- urgency?: string;
14
12
  }
15
- interface OpenUriArgs {
16
- uri: string;
17
- }
18
- interface CopyToClipboardArgs {
19
- text: string;
13
+ export interface CommandResult {
14
+ stdout: string;
15
+ stderr: string;
16
+ rc: number;
20
17
  }
21
18
  interface DismissNotificationArgs {
22
19
  id: number;
@@ -24,6 +21,7 @@ interface DismissNotificationArgs {
24
21
  export declare abstract class Applet<State extends object> {
25
22
  state: State;
26
23
  private readonly handlerMap;
24
+ private inlineHandlerMap;
27
25
  private readonly outgoing;
28
26
  private flushPromise;
29
27
  private renderQueued;
@@ -32,6 +30,7 @@ export declare abstract class Applet<State extends object> {
32
30
  private popoverOpen;
33
31
  protected constructor();
34
32
  protected abstract initialState(): State;
33
+ protected cssClass(): string | null;
35
34
  protected onStart(): Promise<void>;
36
35
  protected onInit(_event: InitEvent): Promise<void>;
37
36
  protected onCallback(_event: CallbackEvent): Promise<void>;
@@ -43,12 +42,16 @@ export declare abstract class Applet<State extends object> {
43
42
  onInput(id: string, handler: Handler<InputEvent>): void;
44
43
  onChange(id: string, handler: Handler<ChangeEvent>): void;
45
44
  onToggle(id: string, handler: Handler<ToggleEvent>): void;
45
+ onPopover(handler: Handler<PopoverEvent>): void;
46
46
  isPopoverOpen(): boolean;
47
- protected showNotification(args: ShowNotificationArgs): void;
48
- protected openUri(args: OpenUriArgs): void;
49
- protected copyToClipboard(args: CopyToClipboardArgs): void;
47
+ log(...args: unknown[]): void;
48
+ protected showNotification(summary: string, options?: ShowNotificationOptions): Promise<void>;
49
+ protected openUri(uri: string): Promise<void>;
50
+ protected copyToClipboard(text: string): Promise<void>;
51
+ protected runCommand(command: string[]): Promise<CommandResult>;
50
52
  protected dismissNotification(args: DismissNotificationArgs): void;
51
53
  protected closePopover(): void;
54
+ protected runDesktopCommand(command: string, args: string[], input?: string): Promise<void>;
52
55
  run(): Promise<void>;
53
56
  protected drainOutgoingForTest(): Promise<OutgoingMessage[]>;
54
57
  private register;
@@ -59,4 +62,5 @@ export declare abstract class Applet<State extends object> {
59
62
  private emit;
60
63
  private emitAction;
61
64
  }
65
+ export declare function runCommand(command: string[]): Promise<CommandResult>;
62
66
  export {};
package/dist/src/app.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import { createInterface } from "node:readline";
2
+ import { spawn } from "node:child_process";
2
3
  import { parseCallbackEvent, parseInitEvent, } from "./events.js";
4
+ import { InlineHandlerRegistry } from "./widgets.js";
3
5
  export class Applet {
4
6
  state;
5
7
  handlerMap = new Map();
8
+ inlineHandlerMap = new Map();
6
9
  outgoing = [];
7
10
  flushPromise = null;
8
11
  renderQueued = false;
@@ -12,6 +15,9 @@ export class Applet {
12
15
  constructor() {
13
16
  this.state = this.initialState();
14
17
  }
18
+ cssClass() {
19
+ return null;
20
+ }
15
21
  async onStart() { }
16
22
  async onInit(_event) { }
17
23
  async onCallback(_event) { }
@@ -40,17 +46,31 @@ export class Applet {
40
46
  onToggle(id, handler) {
41
47
  this.register("toggle", id, handler);
42
48
  }
49
+ onPopover(handler) {
50
+ this.register("open", "popover", handler);
51
+ this.register("close", "popover", handler);
52
+ }
43
53
  isPopoverOpen() {
44
54
  return this.popoverOpen;
45
55
  }
46
- showNotification(args) {
47
- this.emitAction("show_notification", args);
56
+ log(...args) {
57
+ process.stderr.write(args.map(String).join(" ") + "\n");
58
+ }
59
+ async showNotification(summary, options = {}) {
60
+ const args = [summary];
61
+ if (options.body !== undefined) {
62
+ args.push(options.body);
63
+ }
64
+ await this.runDesktopCommand("notify-send", args);
65
+ }
66
+ async openUri(uri) {
67
+ await this.runDesktopCommand("xdg-open", [uri]);
48
68
  }
49
- openUri(args) {
50
- this.emitAction("open_uri", args);
69
+ async copyToClipboard(text) {
70
+ await this.runDesktopCommand("wl-copy", [], text);
51
71
  }
52
- copyToClipboard(args) {
53
- this.emitAction("copy_to_clipboard", args);
72
+ async runCommand(command) {
73
+ return await runCommand(command);
54
74
  }
55
75
  dismissNotification(args) {
56
76
  this.emitAction("dismiss_notification", args);
@@ -58,6 +78,9 @@ export class Applet {
58
78
  closePopover() {
59
79
  this.emitAction("close_popover", {});
60
80
  }
81
+ async runDesktopCommand(command, args, input) {
82
+ await runDesktopCommand([command, ...args], input);
83
+ }
61
84
  async run() {
62
85
  process.stdout.on("error", (err) => {
63
86
  if (err.code === "EPIPE") {
@@ -65,6 +88,10 @@ export class Applet {
65
88
  }
66
89
  });
67
90
  await this.onStart();
91
+ const cssClass = this.cssClass();
92
+ if (cssClass !== null) {
93
+ process.stdout.write(`class ${cssClass}\n`);
94
+ }
68
95
  await this.scheduleRender();
69
96
  const rl = createInterface({
70
97
  input: process.stdin,
@@ -104,7 +131,13 @@ export class Applet {
104
131
  this.handlerMap.set(`${event}:${id}`, handler);
105
132
  }
106
133
  async dispatchCallback(event) {
107
- const handler = this.handlerMap.get(`${event.event}:${event.id}`);
134
+ const key = `${event.event}:${event.id}`;
135
+ const inlineHandler = this.inlineHandlerMap.get(key);
136
+ if (inlineHandler !== undefined) {
137
+ await inlineHandler(event);
138
+ return;
139
+ }
140
+ const handler = this.handlerMap.get(key);
108
141
  if (handler !== undefined) {
109
142
  await handler(event);
110
143
  return;
@@ -151,6 +184,14 @@ export class Applet {
151
184
  this.emit("status", { items: status });
152
185
  }
153
186
  const widget = await this.popover(this.state);
187
+ if (widget !== null) {
188
+ const registry = new InlineHandlerRegistry();
189
+ widget.bindHandlers?.(registry, []);
190
+ this.inlineHandlerMap = registry.handlers;
191
+ }
192
+ else {
193
+ this.inlineHandlerMap = new Map();
194
+ }
154
195
  const tree = { root: widget?.toProtocol() ?? null };
155
196
  if (!deepEqual(tree, this.lastTree)) {
156
197
  this.lastTree = tree;
@@ -194,3 +235,38 @@ function deepEqual(left, right) {
194
235
  function isPopoverEvent(event) {
195
236
  return event.event === "open" || event.event === "close";
196
237
  }
238
+ export async function runCommand(command) {
239
+ return await runCommandWithInput(command);
240
+ }
241
+ async function runDesktopCommand(command, input) {
242
+ const result = await runCommandWithInput(command, input);
243
+ if (result.rc !== 0) {
244
+ throw new Error(`${command[0]} exited with status ${result.rc}`);
245
+ }
246
+ }
247
+ async function runCommandWithInput(command, input) {
248
+ if (command.length === 0) {
249
+ throw new Error("command must not be empty");
250
+ }
251
+ return await new Promise((resolve, reject) => {
252
+ const child = spawn(command[0], command.slice(1), { stdio: ["pipe", "pipe", "pipe"] });
253
+ const stdout = [];
254
+ const stderr = [];
255
+ child.on("error", reject);
256
+ child.stdout.on("data", (chunk) => stdout.push(chunk));
257
+ child.stderr.on("data", (chunk) => stderr.push(chunk));
258
+ child.on("close", (code) => {
259
+ resolve({
260
+ stdout: Buffer.concat(stdout).toString("utf8"),
261
+ stderr: Buffer.concat(stderr).toString("utf8"),
262
+ rc: code ?? -1,
263
+ });
264
+ });
265
+ if (input !== undefined) {
266
+ child.stdin.end(input);
267
+ }
268
+ else {
269
+ child.stdin.end();
270
+ }
271
+ });
272
+ }
@@ -8,7 +8,7 @@ interface CallbackEventBase {
8
8
  }
9
9
  export interface ClickEvent extends CallbackEventBase {
10
10
  event: "click";
11
- button?: string;
11
+ button?: string | null;
12
12
  }
13
13
  export interface ScrollEvent extends CallbackEventBase {
14
14
  event: "scroll";
@@ -8,7 +8,7 @@ export function parseCallbackEvent(payload) {
8
8
  const event = String(payload.type ?? payload.event ?? "");
9
9
  const id = String(payload.id ?? "");
10
10
  if (event === "click") {
11
- return { id, event, button: payload.button === undefined ? undefined : String(payload.button) };
11
+ return { id, event, button: payload.button === undefined ? null : String(payload.button) };
12
12
  }
13
13
  if (event === "scroll") {
14
14
  return {
@@ -1,5 +1,5 @@
1
- export { Applet } from "./app.js";
1
+ export { Applet, runCommand } from "./app.js";
2
2
  export { type CallbackEvent, type ChangeEvent, type ClickEvent, type InitEvent, type InputEvent, type PopoverEvent, type ScrollEvent, type ToggleEvent, parseCallbackEvent, parseInitEvent, } from "./events.js";
3
- export { Icon, StatusItem } from "./protocol.js";
4
- export { type Event, IpcError, Subscriber, ipc } from "./ipc.js";
5
- export { ActionItem, type Align, Badge, Box, Button, type ButtonVariant, Card, Checkbox, Column, type ContentFit, Copyable, EmptyState, Expander, Grid, GridChild, Hero, Image, Item, Label, LevelBar, type LevelBarMode, LinkButton, ListBox, MenuButton, Meter, Overlay, type Orientation, type PagerAppearance, PagerItem, PagerStrip, Picture, Progress, type Properties, PropertyList, Row, Scroll, Section, Select, SelectOption, Separator, Slider, Spinner, StatusDot, Switch, ToggleButton, TreeExpander, type TreeNode, type Variant, } from "./widgets.js";
3
+ export { ipc, IpcError, Subscriber } from "./ipc.js";
4
+ export { StatusItem } from "./protocol.js";
5
+ export { Badge, type BadgeKind, BatteryHero, BoxedList, ButtonRow, Calendar, CameraIndicator, Choice, ChoiceList, ChoiceTile, CircleBox, Column, Container, DateHero, EmptyState, EventItem, Events, ExpanderTile, Header, Hero, KeyValueGrid, Label, LocationIndicator, Meter, MicIndicator, MutedIndicator, type PagerAppearance, PagerItem, PagerStrip, PanelIndicator, PopoverShell, type PopoverSize, Row, ScreenCastIndicator, Scroll, SegmentedTile, Separator, SliderTile, Spinner, StatusDot, type StatusDotStatus, SwitchTile, Tile, type TreeNode, WeatherForecastItem, WeatherForecastList, WeatherHourlyItem, WeatherHourlyStrip, WidgetBase, WorldClock, WorldClockRow, } from "./widgets.js";
package/dist/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { Applet } from "./app.js";
1
+ export { Applet, runCommand } from "./app.js";
2
2
  export { parseCallbackEvent, parseInitEvent, } from "./events.js";
3
- export { Icon, StatusItem } from "./protocol.js";
4
- export { IpcError, Subscriber, ipc } from "./ipc.js";
5
- export { ActionItem, Badge, Box, Button, Card, Checkbox, Column, Copyable, EmptyState, Expander, Grid, GridChild, Hero, Image, Item, Label, LevelBar, LinkButton, ListBox, MenuButton, Meter, Overlay, PagerItem, PagerStrip, Picture, Progress, PropertyList, Row, Scroll, Section, Select, SelectOption, Separator, Slider, Spinner, StatusDot, Switch, ToggleButton, TreeExpander, } from "./widgets.js";
3
+ export { ipc, IpcError, Subscriber } from "./ipc.js";
4
+ export { StatusItem } from "./protocol.js";
5
+ export { Badge, BatteryHero, BoxedList, ButtonRow, Calendar, CameraIndicator, Choice, ChoiceList, ChoiceTile, CircleBox, Column, Container, DateHero, EmptyState, EventItem, Events, ExpanderTile, Header, Hero, KeyValueGrid, Label, LocationIndicator, Meter, MicIndicator, MutedIndicator, PagerItem, PagerStrip, PanelIndicator, PopoverShell, Row, ScreenCastIndicator, Scroll, SegmentedTile, Separator, SliderTile, Spinner, StatusDot, SwitchTile, Tile, WeatherForecastItem, WeatherForecastList, WeatherHourlyItem, WeatherHourlyStrip, WidgetBase, WorldClock, WorldClockRow, } from "./widgets.js";
@@ -1,26 +1,13 @@
1
- export type IconKind = "name" | "path";
2
- export declare class Icon {
3
- readonly kind: IconKind;
4
- readonly value: string;
5
- constructor(kind: IconKind, value: string);
6
- static name(value: string): Icon;
7
- static path(value: string): Icon;
8
- toProtocol(): {
9
- name: string;
10
- } | {
11
- path: string;
12
- };
13
- }
14
1
  export declare class StatusItem {
15
2
  readonly options: {
16
3
  id?: string;
17
- icon?: Icon;
4
+ icon?: string;
18
5
  label?: string;
19
6
  tooltip?: string;
20
7
  };
21
8
  constructor(options?: {
22
9
  id?: string;
23
- icon?: Icon;
10
+ icon?: string;
24
11
  label?: string;
25
12
  tooltip?: string;
26
13
  });
@@ -1,20 +1,3 @@
1
- export class Icon {
2
- kind;
3
- value;
4
- constructor(kind, value) {
5
- this.kind = kind;
6
- this.value = value;
7
- }
8
- static name(value) {
9
- return new Icon("name", value);
10
- }
11
- static path(value) {
12
- return new Icon("path", value);
13
- }
14
- toProtocol() {
15
- return this.kind === "name" ? { name: this.value } : { path: this.value };
16
- }
17
- }
18
1
  export class StatusItem {
19
2
  options;
20
3
  constructor(options = {}) {
@@ -26,7 +9,7 @@ export class StatusItem {
26
9
  payload.id = this.options.id;
27
10
  }
28
11
  if (this.options.icon !== undefined) {
29
- payload.icon = this.options.icon.toProtocol();
12
+ payload.icon = this.options.icon;
30
13
  }
31
14
  if (this.options.label !== undefined) {
32
15
  payload.label = this.options.label;