@timmo001/groundwork-mobile 0.14.0 → 0.15.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/package.json +3 -3
- package/src/bridge.ts +2 -0
- package/src/config.ts +3 -0
- package/src/view.ts +55 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timmo001/groundwork-mobile",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "The page side of Groundwork's Android shell: the app config, the typed bridge to the shell, and a fallback to the PWA outside it",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"format:check": "prettier --check ."
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@timmo001/groundwork-pwa": "0.
|
|
37
|
-
"@timmo001/groundwork-tokens": "0.
|
|
36
|
+
"@timmo001/groundwork-pwa": "0.15.0",
|
|
37
|
+
"@timmo001/groundwork-tokens": "0.15.0",
|
|
38
38
|
"effect": "4.1.2"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
package/src/bridge.ts
CHANGED
|
@@ -181,6 +181,8 @@ export const mobileRequests = {
|
|
|
181
181
|
pinShortcut: Schema.Boolean,
|
|
182
182
|
/** Removes shortcuts, greying out pinned ones. */
|
|
183
183
|
disableShortcuts: Schema.Boolean,
|
|
184
|
+
/** Saves a file the page made to Downloads, from `{ name, type, data }` in base64. */
|
|
185
|
+
saveFile: Schema.Boolean,
|
|
184
186
|
} as const;
|
|
185
187
|
|
|
186
188
|
/** The shell's messages, each with the schema its data is read with. */
|
package/src/config.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface MobileLabels {
|
|
|
12
12
|
readonly ongoing?: string;
|
|
13
13
|
/** Defaults to `Download finished`. */
|
|
14
14
|
readonly downloadFinished?: string;
|
|
15
|
+
/** Shown once a file the page made is saved. Defaults to `Saved to Downloads`. */
|
|
16
|
+
readonly fileSaved?: string;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export type ResolvedMobileLabels = Required<MobileLabels>;
|
|
@@ -104,6 +106,7 @@ export const resolveConfig = (
|
|
|
104
106
|
notifications: "Notifications",
|
|
105
107
|
ongoing: "Ongoing",
|
|
106
108
|
downloadFinished: "Download finished",
|
|
109
|
+
fileSaved: "Saved to Downloads",
|
|
107
110
|
...config.labels,
|
|
108
111
|
},
|
|
109
112
|
});
|
package/src/view.ts
CHANGED
|
@@ -168,8 +168,53 @@ export interface Mobile {
|
|
|
168
168
|
ids: readonly string[],
|
|
169
169
|
message?: string,
|
|
170
170
|
) => Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* Saves a file the page made, such as an export: to the phone's Downloads
|
|
173
|
+
* in the shell, through the browser's download outside it.
|
|
174
|
+
*/
|
|
175
|
+
readonly saveFile: (file: SavedFile) => Promise<void>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** A file for `saveFile`: its name, its type and what's in it. */
|
|
179
|
+
export interface SavedFile {
|
|
180
|
+
readonly name: string;
|
|
181
|
+
readonly type: string;
|
|
182
|
+
readonly contents: string | Blob;
|
|
171
183
|
}
|
|
172
184
|
|
|
185
|
+
const blobOf = ({ type, contents }: SavedFile) =>
|
|
186
|
+
contents instanceof Blob ? contents : new Blob([contents], { type });
|
|
187
|
+
|
|
188
|
+
/** Saves a file through the browser's download. */
|
|
189
|
+
const downloadFile = (file: SavedFile) => {
|
|
190
|
+
const url = URL.createObjectURL(blobOf(file));
|
|
191
|
+
const link = document.createElement("a");
|
|
192
|
+
|
|
193
|
+
link.href = url;
|
|
194
|
+
link.download = file.name;
|
|
195
|
+
link.click();
|
|
196
|
+
URL.revokeObjectURL(url);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const decodeText = Schema.decodeUnknownOption(Schema.String);
|
|
200
|
+
|
|
201
|
+
/** A blob's bytes in base64, for the bridge's JSON. */
|
|
202
|
+
const base64Of = (blob: Blob) =>
|
|
203
|
+
new Promise<string>((resolve, reject) => {
|
|
204
|
+
const reader = new FileReader();
|
|
205
|
+
|
|
206
|
+
// readAsDataURL gives data:<type>;base64,<data>
|
|
207
|
+
reader.addEventListener("load", () => {
|
|
208
|
+
const url = Option.getOrElse(decodeText(reader.result), () => "");
|
|
209
|
+
|
|
210
|
+
resolve(url.split(",")[1] ?? "");
|
|
211
|
+
});
|
|
212
|
+
reader.addEventListener("error", () =>
|
|
213
|
+
reject(reader.error ?? new Error("Couldn't read the file")),
|
|
214
|
+
);
|
|
215
|
+
reader.readAsDataURL(blob);
|
|
216
|
+
});
|
|
217
|
+
|
|
173
218
|
interface Waiting {
|
|
174
219
|
readonly resolve: (value: Schema.Json) => void;
|
|
175
220
|
readonly reject: (error: Error) => void;
|
|
@@ -270,6 +315,7 @@ const connectFloor = (
|
|
|
270
315
|
setShortcuts: async () => 0,
|
|
271
316
|
pinShortcut: async () => false,
|
|
272
317
|
disableShortcuts: async () => {},
|
|
318
|
+
saveFile: async (file) => downloadFile(file),
|
|
273
319
|
};
|
|
274
320
|
};
|
|
275
321
|
|
|
@@ -438,5 +484,14 @@ export const connectMobile = async (
|
|
|
438
484
|
|
|
439
485
|
await call("disableShortcuts", mobileRequests.disableShortcuts, params);
|
|
440
486
|
},
|
|
487
|
+
saveFile: async (file) => {
|
|
488
|
+
const blob = blobOf(file);
|
|
489
|
+
|
|
490
|
+
await call("saveFile", mobileRequests.saveFile, {
|
|
491
|
+
name: file.name,
|
|
492
|
+
type: blob.type || file.type,
|
|
493
|
+
data: await base64Of(blob),
|
|
494
|
+
});
|
|
495
|
+
},
|
|
441
496
|
};
|
|
442
497
|
};
|