@domicile-desktop/sdk 0.0.0-alpha-bf4214ef2f5b → 0.0.0-alpha-dfd80a336b89

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/dist/portal.d.ts CHANGED
@@ -2,8 +2,12 @@ import type { DomicileHost } from "./domicile-host";
2
2
  export declare enum PortalKind {
3
3
  /** A yes/no question, such as whether an application may use the camera. */
4
4
  Access = 0,
5
+ /** Pick an application to open a file or URI with. */
6
+ AppChooser = 1,
7
+ /** Files to open, or where to save. */
8
+ FileChooser = 2,
5
9
  /** A kind this SDK cannot parse. Answer it with {@link PortalAnswer.Refused}. */
6
- Unknown = 1
10
+ Unknown = 3
7
11
  }
8
12
  /** What every request carries. */
9
13
  export type PortalRequestBase = {
@@ -22,6 +26,79 @@ export type AccessBody = {
22
26
  grantLabel: string | undefined;
23
27
  denyLabel: string | undefined;
24
28
  };
29
+ /** What an app chooser opens, and the applications it offers. */
30
+ export type AppChooserBody = {
31
+ /** Desktop file IDs without `.desktop`. May change while the dialog is up. */
32
+ choices: readonly string[];
33
+ /** The application chosen last time for this content type. */
34
+ lastChoice: string | undefined;
35
+ /** The MIME type being opened. */
36
+ contentType: string | undefined;
37
+ uri: string | undefined;
38
+ /** The file's name, without its directory. */
39
+ filename: string | undefined;
40
+ };
41
+ /** Which `FileChooser` method asked. */
42
+ export declare enum FileChooserMode {
43
+ /** Existing files, or a folder when `directory` is set. */
44
+ Open = 0,
45
+ /** One new path. */
46
+ Save = 1,
47
+ /** A folder to save `files` into. */
48
+ SaveFiles = 2
49
+ }
50
+ /** A named group of accepted extensions, such as "Images". */
51
+ export type FileChooserFilter = {
52
+ /** Lowercase and without the dot; empty accepts any file. */
53
+ extensions: readonly string[];
54
+ name: string;
55
+ };
56
+ /**
57
+ * A question asked beside the files. No options means a checkbox, answered
58
+ * `"true"` or `"false"`.
59
+ */
60
+ export type FileChoice = {
61
+ id: string;
62
+ /** The option id, or `"true"`/`"false"`, selected at first. */
63
+ initial: string;
64
+ label: string;
65
+ options: readonly {
66
+ id: string;
67
+ label: string;
68
+ }[];
69
+ };
70
+ /** A file chooser's request. Paths are absolute. */
71
+ export type FileChooserBody = {
72
+ /** The confirm button's label; the shell's own when absent. */
73
+ acceptLabel: string | undefined;
74
+ choices: readonly FileChoice[];
75
+ /** The index in `filters` to start with. */
76
+ currentFilter: number | undefined;
77
+ /** The folder to start in; the home when absent. */
78
+ currentFolder: string | undefined;
79
+ /** The suggested name for {@link FileChooserMode.Save}. */
80
+ currentName: string | undefined;
81
+ /** Open a folder rather than a file. */
82
+ directory: boolean;
83
+ /** The names {@link FileChooserMode.SaveFiles} saves into the folder. */
84
+ files: readonly string[];
85
+ /** Empty accepts any file. */
86
+ filters: readonly FileChooserFilter[];
87
+ /** The user's home directory, where the picker's places are. */
88
+ home: string;
89
+ mode: FileChooserMode;
90
+ multiple: boolean;
91
+ title: string;
92
+ };
93
+ /** What the user chose in a file chooser. */
94
+ export type FileChosen = {
95
+ /** Each {@link FileChoice}'s answer, by id. */
96
+ choices: ReadonlyMap<string, string>;
97
+ /** The index in the request's `filters` in use. */
98
+ currentFilter: number | undefined;
99
+ /** Absolute paths: the files, or the folder for `SaveFiles`. */
100
+ paths: readonly string[];
101
+ };
25
102
  export declare const PortalRequest: {
26
103
  Access: (base: PortalRequestBase, body: AccessBody) => {
27
104
  /** The id {@link answerPortalRequest} takes. */
@@ -33,6 +110,26 @@ export declare const PortalRequest: {
33
110
  body: AccessBody;
34
111
  kind: PortalKind.Access;
35
112
  };
113
+ AppChooser: (base: PortalRequestBase, body: AppChooserBody) => {
114
+ /** The id {@link answerPortalRequest} takes. */
115
+ id: number;
116
+ /** The asking application's desktop file id. Empty when unknown. */
117
+ appId: string;
118
+ /** The `<app>` to show the dialog over, or the focused screen when absent. */
119
+ parentAppId: string | undefined;
120
+ body: AppChooserBody;
121
+ kind: PortalKind.AppChooser;
122
+ };
123
+ FileChooser: (base: PortalRequestBase, body: FileChooserBody) => {
124
+ /** The id {@link answerPortalRequest} takes. */
125
+ id: number;
126
+ /** The asking application's desktop file id. Empty when unknown. */
127
+ appId: string;
128
+ /** The `<app>` to show the dialog over, or the focused screen when absent. */
129
+ parentAppId: string | undefined;
130
+ body: FileChooserBody;
131
+ kind: PortalKind.FileChooser;
132
+ };
36
133
  Unknown: (base: PortalRequestBase, wireKind: string) => {
37
134
  /** The id {@link answerPortalRequest} takes. */
38
135
  id: number;
@@ -48,18 +145,31 @@ export type PortalRequest = ReturnType<(typeof PortalRequest)[keyof typeof Porta
48
145
  export declare enum PortalAnswerKind {
49
146
  /** The user allowed an {@link PortalKind.Access} request. */
50
147
  Access = 0,
148
+ /** The application the user picked in an {@link PortalKind.AppChooser}. */
149
+ AppChooser = 1,
150
+ /** The user chose in a {@link PortalKind.FileChooser}. */
151
+ FileChooser = 2,
51
152
  /** The user dismissed or denied the dialog. */
52
- Canceled = 1,
153
+ Canceled = 3,
53
154
  /** The shell has no dialog for this kind. */
54
- Refused = 2
155
+ Refused = 4
55
156
  }
56
157
  export declare const PortalAnswer: {
57
158
  Access: () => {
58
159
  kind: PortalAnswerKind.Access;
59
160
  };
161
+ /** `choice` must be one of the request's `choices`. */
162
+ AppChooser: (choice: string) => {
163
+ choice: string;
164
+ kind: PortalAnswerKind.AppChooser;
165
+ };
60
166
  Canceled: () => {
61
167
  kind: PortalAnswerKind.Canceled;
62
168
  };
169
+ FileChooser: (chosen: FileChosen) => {
170
+ chosen: FileChosen;
171
+ kind: PortalAnswerKind.FileChooser;
172
+ };
63
173
  Refused: () => {
64
174
  kind: PortalAnswerKind.Refused;
65
175
  };
package/dist/portal.js CHANGED
@@ -9,15 +9,39 @@ export var PortalKind;
9
9
  (function (PortalKind) {
10
10
  /** A yes/no question, such as whether an application may use the camera. */
11
11
  PortalKind[PortalKind["Access"] = 0] = "Access";
12
+ /** Pick an application to open a file or URI with. */
13
+ PortalKind[PortalKind["AppChooser"] = 1] = "AppChooser";
14
+ /** Files to open, or where to save. */
15
+ PortalKind[PortalKind["FileChooser"] = 2] = "FileChooser";
12
16
  /** A kind this SDK cannot parse. Answer it with {@link PortalAnswer.Refused}. */
13
- PortalKind[PortalKind["Unknown"] = 1] = "Unknown";
17
+ PortalKind[PortalKind["Unknown"] = 3] = "Unknown";
14
18
  })(PortalKind || (PortalKind = {}));
19
+ /** Which `FileChooser` method asked. */
20
+ export var FileChooserMode;
21
+ (function (FileChooserMode) {
22
+ /** Existing files, or a folder when `directory` is set. */
23
+ FileChooserMode[FileChooserMode["Open"] = 0] = "Open";
24
+ /** One new path. */
25
+ FileChooserMode[FileChooserMode["Save"] = 1] = "Save";
26
+ /** A folder to save `files` into. */
27
+ FileChooserMode[FileChooserMode["SaveFiles"] = 2] = "SaveFiles";
28
+ })(FileChooserMode || (FileChooserMode = {}));
15
29
  export const PortalRequest = {
16
30
  Access: (base, body) => ({
17
31
  ...base,
18
32
  body,
19
33
  kind: PortalKind.Access,
20
34
  }),
35
+ AppChooser: (base, body) => ({
36
+ ...base,
37
+ body,
38
+ kind: PortalKind.AppChooser,
39
+ }),
40
+ FileChooser: (base, body) => ({
41
+ ...base,
42
+ body,
43
+ kind: PortalKind.FileChooser,
44
+ }),
21
45
  Unknown: (base, wireKind) => ({
22
46
  ...base,
23
47
  kind: PortalKind.Unknown,
@@ -28,14 +52,27 @@ export var PortalAnswerKind;
28
52
  (function (PortalAnswerKind) {
29
53
  /** The user allowed an {@link PortalKind.Access} request. */
30
54
  PortalAnswerKind[PortalAnswerKind["Access"] = 0] = "Access";
55
+ /** The application the user picked in an {@link PortalKind.AppChooser}. */
56
+ PortalAnswerKind[PortalAnswerKind["AppChooser"] = 1] = "AppChooser";
57
+ /** The user chose in a {@link PortalKind.FileChooser}. */
58
+ PortalAnswerKind[PortalAnswerKind["FileChooser"] = 2] = "FileChooser";
31
59
  /** The user dismissed or denied the dialog. */
32
- PortalAnswerKind[PortalAnswerKind["Canceled"] = 1] = "Canceled";
60
+ PortalAnswerKind[PortalAnswerKind["Canceled"] = 3] = "Canceled";
33
61
  /** The shell has no dialog for this kind. */
34
- PortalAnswerKind[PortalAnswerKind["Refused"] = 2] = "Refused";
62
+ PortalAnswerKind[PortalAnswerKind["Refused"] = 4] = "Refused";
35
63
  })(PortalAnswerKind || (PortalAnswerKind = {}));
36
64
  export const PortalAnswer = {
37
65
  Access: () => ({ kind: PortalAnswerKind.Access }),
66
+ /** `choice` must be one of the request's `choices`. */
67
+ AppChooser: (choice) => ({
68
+ choice,
69
+ kind: PortalAnswerKind.AppChooser,
70
+ }),
38
71
  Canceled: () => ({ kind: PortalAnswerKind.Canceled }),
72
+ FileChooser: (chosen) => ({
73
+ chosen,
74
+ kind: PortalAnswerKind.FileChooser,
75
+ }),
39
76
  Refused: () => ({ kind: PortalAnswerKind.Refused }),
40
77
  };
41
78
  /**
@@ -56,7 +93,7 @@ export const watchPortalRequests = (host, listener) => {
56
93
  };
57
94
  /** Answer request `id`. The first answer to reach the compositor wins. */
58
95
  export const answerPortalRequest = (host, id, answer) => {
59
- host.answerPortalRequest(id, JSON.stringify({ kind: wireAnswer(answer) }));
96
+ host.answerPortalRequest(id, JSON.stringify(wireAnswer(answer)));
60
97
  };
61
98
  const accessSchema = z
62
99
  .object({
@@ -73,12 +110,69 @@ const accessSchema = z
73
110
  subtitle: body.subtitle,
74
111
  title: body.title,
75
112
  }));
113
+ const appChooserSchema = z
114
+ .object({
115
+ choices: z.array(z.string()),
116
+ content_type: z.string().optional(),
117
+ filename: z.string().optional(),
118
+ last_choice: z.string().optional(),
119
+ uri: z.string().optional(),
120
+ })
121
+ .transform((body) => ({
122
+ choices: body.choices,
123
+ contentType: body.content_type,
124
+ filename: body.filename,
125
+ lastChoice: body.last_choice,
126
+ uri: body.uri,
127
+ }));
128
+ const fileChooserSchema = z
129
+ .object({
130
+ accept_label: z.string().optional(),
131
+ choices: z.array(z.object({
132
+ id: z.string(),
133
+ initial: z.string(),
134
+ label: z.string(),
135
+ options: z.array(z.object({ id: z.string(), label: z.string() })),
136
+ })),
137
+ current_filter: z.number().optional(),
138
+ current_folder: z.string().optional(),
139
+ current_name: z.string().optional(),
140
+ directory: z.boolean(),
141
+ files: z.array(z.string()),
142
+ filters: z.array(z.object({ extensions: z.array(z.string()), name: z.string() })),
143
+ home: z.string(),
144
+ mode: z.enum(["open", "save", "save_files"]),
145
+ multiple: z.boolean(),
146
+ title: z.string(),
147
+ })
148
+ .transform((body) => ({
149
+ acceptLabel: body.accept_label,
150
+ choices: body.choices,
151
+ currentFilter: body.current_filter,
152
+ currentFolder: body.current_folder,
153
+ currentName: body.current_name,
154
+ directory: body.directory,
155
+ files: body.files,
156
+ filters: body.filters,
157
+ home: body.home,
158
+ mode: chooserMode(body.mode),
159
+ multiple: body.multiple,
160
+ title: body.title,
161
+ }));
76
162
  /** Each kind's wire name, and how to read its body into a request. */
77
163
  const KINDS = new Map([
78
164
  [
79
165
  "access",
80
166
  (base, body) => PortalRequest.Access(base, accessSchema.parse(body)),
81
167
  ],
168
+ [
169
+ "app_chooser",
170
+ (base, body) => PortalRequest.AppChooser(base, appChooserSchema.parse(body)),
171
+ ],
172
+ [
173
+ "file_chooser",
174
+ (base, body) => PortalRequest.FileChooser(base, fileChooserSchema.parse(body)),
175
+ ],
82
176
  ]);
83
177
  const itemSchema = z.object({
84
178
  app_id: z.string(),
@@ -102,13 +196,33 @@ const parseRequest = (item) => {
102
196
  ? PortalRequest.Unknown(base, item.kind)
103
197
  : parse(base, item.body);
104
198
  };
199
+ const chooserMode = (mode) => {
200
+ switch (mode) {
201
+ case "open":
202
+ return FileChooserMode.Open;
203
+ case "save":
204
+ return FileChooserMode.Save;
205
+ case "save_files":
206
+ return FileChooserMode.SaveFiles;
207
+ }
208
+ };
209
+ /** `answer` as the compositor reads it. */
105
210
  const wireAnswer = (answer) => {
106
211
  switch (answer.kind) {
107
212
  case PortalAnswerKind.Access:
108
- return "access";
213
+ return { kind: "access" };
214
+ case PortalAnswerKind.AppChooser:
215
+ return { choice: answer.choice, kind: "app_chooser" };
109
216
  case PortalAnswerKind.Canceled:
110
- return "canceled";
217
+ return { kind: "canceled" };
218
+ case PortalAnswerKind.FileChooser:
219
+ return {
220
+ choices: Object.fromEntries(answer.chosen.choices),
221
+ current_filter: answer.chosen.currentFilter,
222
+ kind: "file_chooser",
223
+ paths: answer.chosen.paths,
224
+ };
111
225
  case PortalAnswerKind.Refused:
112
- return "refused";
226
+ return { kind: "refused" };
113
227
  }
114
228
  };
package/package.json CHANGED
@@ -95,7 +95,7 @@
95
95
  "test:unit": "bun test"
96
96
  },
97
97
  "type": "module",
98
- "version": "0.0.0-alpha-bf4214ef2f5b",
98
+ "version": "0.0.0-alpha-dfd80a336b89",
99
99
  "repository": {
100
100
  "type": "git",
101
101
  "url": "git+https://github.com/cprussin/domicile.git",