@k2b/ui 0.14.0 → 0.16.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.
Files changed (72) hide show
  1. package/README.md +5 -0
  2. package/dist/base.css +1097 -0
  3. package/dist/browser/chat/ChatPrimitives.js +19 -16
  4. package/dist/browser/chat/ChatPrimitives.js.map +1 -1
  5. package/dist/browser/chat/ChatTimeline.js +110 -87
  6. package/dist/browser/chat/ChatTimeline.js.map +1 -1
  7. package/dist/browser/content/FileGrid.js +28 -8
  8. package/dist/browser/content/FileGrid.js.map +1 -1
  9. package/dist/browser/content/MarkdownView.js +11 -5
  10. package/dist/browser/content/MarkdownView.js.map +1 -1
  11. package/dist/browser/content/MessageRow.js +1250 -0
  12. package/dist/browser/content/MessageRow.js.map +1 -0
  13. package/dist/browser/content/StructuredDataPreview.js +4 -1
  14. package/dist/browser/content/StructuredDataPreview.js.map +1 -1
  15. package/dist/browser/content/VirtualFeed.js +1127 -0
  16. package/dist/browser/content/VirtualFeed.js.map +1 -0
  17. package/dist/browser/content/collection-selection.js +27 -11
  18. package/dist/browser/content/collection-selection.js.map +1 -1
  19. package/dist/browser/content/index.js +2 -0
  20. package/dist/browser/content/index.js.map +1 -1
  21. package/dist/browser/feedback/InstallGuide.js +199 -145
  22. package/dist/browser/feedback/InstallGuide.js.map +1 -1
  23. package/dist/browser/feedback/install.js +8 -4
  24. package/dist/browser/feedback/install.js.map +1 -1
  25. package/dist/browser/inputs/QrScanner.js +101 -21
  26. package/dist/browser/inputs/QrScanner.js.map +1 -1
  27. package/dist/browser/intl/messages.js +86 -0
  28. package/dist/browser/intl/messages.js.map +1 -1
  29. package/dist/browser/surfaces/Avatar.js +33 -17
  30. package/dist/browser/surfaces/Avatar.js.map +1 -1
  31. package/dist/ssr/chat/ChatPrimitives.js +2 -2
  32. package/dist/ssr/chat/ChatPrimitives.js.map +1 -1
  33. package/dist/ssr/chat/ChatTimeline.js +110 -87
  34. package/dist/ssr/chat/ChatTimeline.js.map +1 -1
  35. package/dist/ssr/content/FileGrid.js +17 -3
  36. package/dist/ssr/content/FileGrid.js.map +1 -1
  37. package/dist/ssr/content/MarkdownView.js +11 -5
  38. package/dist/ssr/content/MarkdownView.js.map +1 -1
  39. package/dist/ssr/content/MessageRow.js +796 -0
  40. package/dist/ssr/content/MessageRow.js.map +1 -0
  41. package/dist/ssr/content/StructuredDataPreview.js +1 -1
  42. package/dist/ssr/content/StructuredDataPreview.js.map +1 -1
  43. package/dist/ssr/content/VirtualFeed.js +989 -0
  44. package/dist/ssr/content/VirtualFeed.js.map +1 -0
  45. package/dist/ssr/content/collection-selection.js +27 -11
  46. package/dist/ssr/content/collection-selection.js.map +1 -1
  47. package/dist/ssr/content/index.js +2 -0
  48. package/dist/ssr/content/index.js.map +1 -1
  49. package/dist/ssr/feedback/InstallGuide.js +150 -98
  50. package/dist/ssr/feedback/InstallGuide.js.map +1 -1
  51. package/dist/ssr/feedback/install.js +8 -4
  52. package/dist/ssr/feedback/install.js.map +1 -1
  53. package/dist/ssr/inputs/QrScanner.js +101 -21
  54. package/dist/ssr/inputs/QrScanner.js.map +1 -1
  55. package/dist/ssr/intl/messages.js +86 -0
  56. package/dist/ssr/intl/messages.js.map +1 -1
  57. package/dist/ssr/surfaces/Avatar.js +14 -1
  58. package/dist/ssr/surfaces/Avatar.js.map +1 -1
  59. package/dist/styles.css +1 -1
  60. package/dist/types/chat/ChatPrimitives.d.ts +2 -0
  61. package/dist/types/chat/ChatTimeline.d.ts +2 -0
  62. package/dist/types/content/FileGrid.d.ts +6 -1
  63. package/dist/types/content/MarkdownView.d.ts +8 -0
  64. package/dist/types/content/MessageRow.d.ts +168 -0
  65. package/dist/types/content/VirtualFeed.d.ts +81 -0
  66. package/dist/types/content/collection-selection.d.ts +15 -1
  67. package/dist/types/content/index.d.ts +4 -0
  68. package/dist/types/feedback/InstallGuide.d.ts +5 -3
  69. package/dist/types/feedback/install.d.ts +4 -3
  70. package/dist/types/inputs/QrScanner.d.ts +10 -2
  71. package/dist/types/intl/messages.d.ts +43 -0
  72. package/package.json +3 -1
@@ -1,30 +1,48 @@
1
1
  import { template as _$template } from "solid-js/web";
2
- import { memo as _$memo } from "solid-js/web";
3
2
  import { setAttribute as _$setAttribute } from "solid-js/web";
3
+ import { getNextMarker as _$getNextMarker } from "solid-js/web";
4
4
  import { className as _$className } from "solid-js/web";
5
5
  import { effect as _$effect } from "solid-js/web";
6
- import { getNextMarker as _$getNextMarker } from "solid-js/web";
6
+ import { memo as _$memo } from "solid-js/web";
7
7
  import { createComponent as _$createComponent } from "solid-js/web";
8
8
  import { getNextElement as _$getNextElement } from "solid-js/web";
9
9
  import { insert as _$insert } from "solid-js/web";
10
10
  var _tmpl$ = /*#__PURE__*/_$template(`<p role=alert>`),
11
11
  _tmpl$2 = /*#__PURE__*/_$template(`<p role=status>`),
12
- _tmpl$3 = /*#__PURE__*/_$template(`<div><!$><!/><!$><!/>`),
13
- _tmpl$4 = /*#__PURE__*/_$template(`<p>`),
14
- _tmpl$5 = /*#__PURE__*/_$template(`<i aria-hidden=true>`),
15
- _tmpl$6 = /*#__PURE__*/_$template(`<code>`),
12
+ _tmpl$3 = /*#__PURE__*/_$template(`<p>`),
13
+ _tmpl$4 = /*#__PURE__*/_$template(`<i aria-hidden=true>`),
14
+ _tmpl$5 = /*#__PURE__*/_$template(`<code>`),
15
+ _tmpl$6 = /*#__PURE__*/_$template(`<div><!$><!/><!$><!/>`),
16
16
  _tmpl$7 = /*#__PURE__*/_$template(`<ol class=k2b-install-guide__steps>`),
17
- _tmpl$8 = /*#__PURE__*/_$template(`<li><span class=k2b-install-guide__step-icon><i aria-hidden=true></i></span><div><h3></h3><p>`);
17
+ _tmpl$8 = /*#__PURE__*/_$template(`<li><span class=k2b-install-guide__step-icon><i aria-hidden=true></i></span><div><h3></h3><p>`),
18
+ _tmpl$9 = /*#__PURE__*/_$template(`<i class="ti ti-brand-chrome"aria-hidden=true>`);
18
19
  import { clipboard } from "@k2b/stdlib/solid";
19
- import { createMemo, For, Show } from "solid-js";
20
- import { Button } from "../actions/Button";
20
+ import { createMemo, For, Match, Show, Switch } from "solid-js";
21
+ import { Button, ButtonLink } from "../actions/Button";
21
22
  import { useUiMessages } from "../intl/messages";
22
23
  import { NoticeCard } from "../surfaces/NoticeCard";
24
+ /**
25
+ * Opens `url` in Chrome on Android. Without Chrome, the browser loads `url` itself. Only an absolute http or https
26
+ * address has an intent; anything else returns `undefined`.
27
+ */
28
+ const chromeIntent = url => {
29
+ let target;
30
+ try {
31
+ target = new URL(url);
32
+ } catch {
33
+ return undefined;
34
+ }
35
+ if (target.protocol !== "https:" && target.protocol !== "http:") return undefined;
36
+ const scheme = target.protocol.slice(0, -1);
37
+ return `intent://${target.host}${target.pathname}${target.search}${target.hash}#Intent;scheme=${scheme};package=com.android.chrome;S.browser_fallback_url=${encodeURIComponent(url)};end`;
38
+ };
39
+
23
40
  /**
24
41
  * Explains how to install the current page as an app on this platform: the browser's own dialog where it offers
25
42
  * one, otherwise the steps for Safari on iPhone, iPad, and Mac, other browsers on iPhone and iPad, Android, and other
26
43
  * browsers. A browser inside another app cannot install, so it gets a warning and the link to copy into Safari or
27
- * Chrome instead. The host owns the surrounding surface, its heading, and dismissal.
44
+ * Chrome instead. Samsung Internet gets a link to Chrome even when it offers its own dialog, because Android may block
45
+ * the app package it builds. The host owns the surrounding surface, its heading, and dismissal.
28
46
  */
29
47
  export function InstallGuide(props) {
30
48
  const messages = useUiMessages();
@@ -82,11 +100,11 @@ export function InstallGuide(props) {
82
100
  }];
83
101
  });
84
102
  return (() => {
85
- var _el$ = _$getNextElement(_tmpl$3),
86
- _el$4 = _el$.firstChild,
87
- [_el$5, _co$] = _$getNextMarker(_el$4.nextSibling),
88
- _el$6 = _el$5.nextSibling,
89
- [_el$7, _co$2] = _$getNextMarker(_el$6.nextSibling);
103
+ var _el$ = _$getNextElement(_tmpl$6),
104
+ _el$0 = _el$.firstChild,
105
+ [_el$1, _co$] = _$getNextMarker(_el$0.nextSibling),
106
+ _el$10 = _el$1.nextSibling,
107
+ [_el$11, _co$2] = _$getNextMarker(_el$10.nextSibling);
90
108
  _$insert(_el$, _$createComponent(Show, {
91
109
  get when() {
92
110
  return state().failed();
@@ -96,142 +114,130 @@ export function InstallGuide(props) {
96
114
  _$insert(_el$2, () => messages().installFailed);
97
115
  return _el$2;
98
116
  }
99
- }), _el$5, _co$);
100
- _$insert(_el$, _$createComponent(Show, {
101
- get when() {
102
- return state().requested();
103
- },
117
+ }), _el$1, _co$);
118
+ _$insert(_el$, _$createComponent(Switch, {
104
119
  get fallback() {
105
- return _$createComponent(Show, {
120
+ return [_$createComponent(Show, {
106
121
  get when() {
107
- return state().canPrompt() || state().busy();
122
+ return hint();
123
+ },
124
+ children: hint => _$createComponent(NoticeCard, {
125
+ tone: "info",
126
+ get title() {
127
+ return hint().title;
128
+ },
129
+ get detail() {
130
+ return hint().detail;
131
+ }
132
+ })
133
+ }), (() => {
134
+ var _el$12 = _$getNextElement(_tmpl$7);
135
+ _$insert(_el$12, _$createComponent(For, {
136
+ get each() {
137
+ return steps();
138
+ },
139
+ children: step => (() => {
140
+ var _el$16 = _$getNextElement(_tmpl$8),
141
+ _el$17 = _el$16.firstChild,
142
+ _el$18 = _el$17.firstChild,
143
+ _el$19 = _el$17.nextSibling,
144
+ _el$20 = _el$19.firstChild,
145
+ _el$21 = _el$20.nextSibling;
146
+ _$insert(_el$20, () => step.title);
147
+ _$insert(_el$21, () => step.detail);
148
+ _$effect(() => _$className(_el$18, step.icon));
149
+ return _el$16;
150
+ })()
151
+ }));
152
+ return _el$12;
153
+ })(), _$createComponent(Show, {
154
+ get when() {
155
+ return state().platform === "apple-mobile";
156
+ },
157
+ get children() {
158
+ var _el$13 = _$getNextElement(_tmpl$3);
159
+ _$insert(_el$13, () => messages().installSafariFallback);
160
+ return _el$13;
161
+ }
162
+ }), _$createComponent(Show, {
163
+ get when() {
164
+ return props.note;
165
+ },
166
+ get children() {
167
+ var _el$14 = _$getNextElement(_tmpl$3);
168
+ _$insert(_el$14, () => props.note);
169
+ return _el$14;
170
+ }
171
+ }), _$createComponent(Show, {
172
+ get when() {
173
+ return state().platform === "generic";
174
+ },
175
+ get children() {
176
+ var _el$15 = _$getNextElement(_tmpl$3);
177
+ _$insert(_el$15, () => messages().installUnavailable);
178
+ return _el$15;
179
+ }
180
+ })];
181
+ },
182
+ get children() {
183
+ return [_$createComponent(Match, {
184
+ get when() {
185
+ return state().requested();
186
+ },
187
+ get children() {
188
+ var _el$3 = _$getNextElement(_tmpl$2);
189
+ _$insert(_el$3, () => messages().installRequested);
190
+ return _el$3;
191
+ }
192
+ }), _$createComponent(Match, {
193
+ get when() {
194
+ return state().platform === "android-samsung";
108
195
  },
109
- get fallback() {
110
- return _$createComponent(Show, {
196
+ get children() {
197
+ return [_$createComponent(NoticeCard, {
198
+ tone: "info",
199
+ get title() {
200
+ return messages().installSamsungTitle;
201
+ },
202
+ get detail() {
203
+ return messages().installSamsungDetail({
204
+ appName: props.appName
205
+ });
206
+ }
207
+ }), _$createComponent(Show, {
111
208
  get when() {
112
- return embedded();
209
+ return chromeIntent(props.url);
113
210
  },
114
- get fallback() {
115
- return [_$createComponent(Show, {
116
- get when() {
117
- return hint();
118
- },
119
- children: hint => _$createComponent(NoticeCard, {
120
- tone: "info",
121
- get title() {
122
- return hint().title;
123
- },
124
- get detail() {
125
- return hint().detail;
126
- }
127
- })
128
- }), (() => {
129
- var _el$11 = _$getNextElement(_tmpl$7);
130
- _$insert(_el$11, _$createComponent(For, {
131
- get each() {
132
- return steps();
133
- },
134
- children: step => (() => {
135
- var _el$15 = _$getNextElement(_tmpl$8),
136
- _el$16 = _el$15.firstChild,
137
- _el$17 = _el$16.firstChild,
138
- _el$18 = _el$16.nextSibling,
139
- _el$19 = _el$18.firstChild,
140
- _el$20 = _el$19.nextSibling;
141
- _$insert(_el$19, () => step.title);
142
- _$insert(_el$20, () => step.detail);
143
- _$effect(() => _$className(_el$17, step.icon));
144
- return _el$15;
145
- })()
146
- }));
147
- return _el$11;
148
- })(), _$createComponent(Show, {
149
- get when() {
150
- return state().platform === "apple-mobile";
151
- },
152
- get children() {
153
- var _el$12 = _$getNextElement(_tmpl$4);
154
- _$insert(_el$12, () => messages().installSafariFallback);
155
- return _el$12;
156
- }
157
- }), _$createComponent(Show, {
158
- get when() {
159
- return props.note;
160
- },
161
- get children() {
162
- var _el$13 = _$getNextElement(_tmpl$4);
163
- _$insert(_el$13, () => props.note);
164
- return _el$13;
165
- }
166
- }), _$createComponent(Show, {
167
- get when() {
168
- return state().platform === "generic";
169
- },
170
- get children() {
171
- var _el$14 = _$getNextElement(_tmpl$4);
172
- _$insert(_el$14, () => messages().installUnavailable);
173
- return _el$14;
174
- }
175
- })];
211
+ children: href => _$createComponent(ButtonLink, {
212
+ get href() {
213
+ return href();
214
+ },
215
+ get children() {
216
+ return [_$getNextElement(_tmpl$9), _$memo(() => messages().installOpenChrome)];
217
+ }
218
+ })
219
+ }), _$createComponent(Show, {
220
+ get when() {
221
+ return props.note;
176
222
  },
177
223
  get children() {
178
- return [_$createComponent(NoticeCard, {
179
- tone: "warning",
180
- get title() {
181
- return _$memo(() => state().platform === "apple-in-app")() ? messages().installAppleInAppTitle : messages().installInAppTitle;
182
- },
183
- get detail() {
184
- return _$memo(() => state().platform === "apple-in-app")() ? messages().installAppleInAppDetail({
185
- appName: props.appName
186
- }) : messages().installInAppDetail({
187
- appName: props.appName
188
- });
189
- }
190
- }), _$createComponent(Button, {
191
- variant: "secondary",
192
- onClick: () => void copy.copy(props.url),
193
- get children() {
194
- return [(() => {
195
- var _el$9 = _$getNextElement(_tmpl$5);
196
- _$effect(() => _$className(_el$9, copy.wasCopied() ? "ti ti-check" : "ti ti-copy"));
197
- return _el$9;
198
- })(), _$memo(() => _$memo(() => !!copy.wasCopied())() ? messages().installLinkCopied : messages().installCopyLink)];
199
- }
200
- }), _$createComponent(Show, {
201
- get when() {
202
- return copy.error();
203
- },
204
- get children() {
205
- return [(() => {
206
- var _el$0 = _$getNextElement(_tmpl$);
207
- _$insert(_el$0, () => messages().installCopyFailed);
208
- return _el$0;
209
- })(), (() => {
210
- var _el$1 = _$getNextElement(_tmpl$6);
211
- _$insert(_el$1, () => props.url);
212
- return _el$1;
213
- })()];
214
- }
215
- }), _$createComponent(Show, {
216
- get when() {
217
- return props.note;
218
- },
219
- get children() {
220
- var _el$10 = _$getNextElement(_tmpl$4);
221
- _$insert(_el$10, () => props.note);
222
- return _el$10;
223
- }
224
- })];
224
+ var _el$4 = _$getNextElement(_tmpl$3);
225
+ _$insert(_el$4, () => props.note);
226
+ return _el$4;
225
227
  }
226
- });
228
+ })];
229
+ }
230
+ }), _$createComponent(Match, {
231
+ get when() {
232
+ return state().canPrompt() || state().busy();
227
233
  },
228
234
  get children() {
229
235
  return [(() => {
230
- var _el$8 = _$getNextElement(_tmpl$4);
231
- _$insert(_el$8, () => messages().installNative({
236
+ var _el$5 = _$getNextElement(_tmpl$3);
237
+ _$insert(_el$5, () => messages().installNative({
232
238
  appName: props.appName
233
239
  }));
234
- return _el$8;
240
+ return _el$5;
235
241
  })(), _$createComponent(Button, {
236
242
  get loading() {
237
243
  return state().busy();
@@ -242,14 +248,62 @@ export function InstallGuide(props) {
242
248
  }
243
249
  })];
244
250
  }
245
- });
246
- },
247
- get children() {
248
- var _el$3 = _$getNextElement(_tmpl$2);
249
- _$insert(_el$3, () => messages().installRequested);
250
- return _el$3;
251
+ }), _$createComponent(Match, {
252
+ get when() {
253
+ return embedded();
254
+ },
255
+ get children() {
256
+ return [_$createComponent(NoticeCard, {
257
+ tone: "warning",
258
+ get title() {
259
+ return _$memo(() => state().platform === "apple-in-app")() ? messages().installAppleInAppTitle : messages().installInAppTitle;
260
+ },
261
+ get detail() {
262
+ return _$memo(() => state().platform === "apple-in-app")() ? messages().installAppleInAppDetail({
263
+ appName: props.appName
264
+ }) : messages().installInAppDetail({
265
+ appName: props.appName
266
+ });
267
+ }
268
+ }), _$createComponent(Button, {
269
+ variant: "secondary",
270
+ onClick: () => void copy.copy(props.url),
271
+ get children() {
272
+ return [(() => {
273
+ var _el$6 = _$getNextElement(_tmpl$4);
274
+ _$effect(() => _$className(_el$6, copy.wasCopied() ? "ti ti-check" : "ti ti-copy"));
275
+ return _el$6;
276
+ })(), _$memo(() => _$memo(() => !!copy.wasCopied())() ? messages().installLinkCopied : messages().installCopyLink)];
277
+ }
278
+ }), _$createComponent(Show, {
279
+ get when() {
280
+ return copy.error();
281
+ },
282
+ get children() {
283
+ return [(() => {
284
+ var _el$7 = _$getNextElement(_tmpl$);
285
+ _$insert(_el$7, () => messages().installCopyFailed);
286
+ return _el$7;
287
+ })(), (() => {
288
+ var _el$8 = _$getNextElement(_tmpl$5);
289
+ _$insert(_el$8, () => props.url);
290
+ return _el$8;
291
+ })()];
292
+ }
293
+ }), _$createComponent(Show, {
294
+ get when() {
295
+ return props.note;
296
+ },
297
+ get children() {
298
+ var _el$9 = _$getNextElement(_tmpl$3);
299
+ _$insert(_el$9, () => props.note);
300
+ return _el$9;
301
+ }
302
+ })];
303
+ }
304
+ })];
251
305
  }
252
- }), _el$7, _co$2);
306
+ }), _el$11, _co$2);
253
307
  _$effect(_p$ => {
254
308
  var _v$ = props.class ? `k2b-install-guide ${props.class}` : "k2b-install-guide",
255
309
  _v$2 = state().platform;
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["feedback/InstallGuide.tsx"],"sourcesContent":["import { clipboard } from \"@k2b/stdlib/solid\";\nimport { createMemo, For, type JSX, Show } from \"solid-js\";\nimport { Button } from \"../actions/Button\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { NoticeCard } from \"../surfaces/NoticeCard\";\nimport type { InstallPrompt } from \"./install\";\n\nexport type InstallGuideProps = {\n /** The name people see on their Home Screen, used in the guidance text. */\n appName: string;\n install: InstallPrompt;\n /**\n * The address a browser inside another app copies, to open it in Safari or Chrome. Shown as text when copying\n * fails, so it can be copied by hand.\n */\n url: string;\n /**\n * An application note under the installation steps or the embedded browser's link, for example what else the\n * installed app enables.\n */\n note?: string;\n class?: string;\n};\n\n/**\n * Explains how to install the current page as an app on this platform: the browser's own dialog where it offers\n * one, otherwise the steps for Safari on iPhone, iPad, and Mac, other browsers on iPhone and iPad, Android, and other\n * browsers. A browser inside another app cannot install, so it gets a warning and the link to copy into Safari or\n * Chrome instead. The host owns the surrounding surface, its heading, and dismissal.\n */\nexport function InstallGuide(props: InstallGuideProps): JSX.Element {\n const messages = useUiMessages();\n // The host may hand over another state after mount, such as the browser's own in place of the server's view.\n const state = () => props.install;\n const copy = clipboard.createWriter({ write: (url: string) => navigator.clipboard.writeText(url) });\n const embedded = () => state().platform === \"in-app\" || state().platform === \"apple-in-app\";\n /** A browser that installs, but less reliably than the platform's own. */\n const hint = createMemo(() => {\n const t = messages();\n if (state().platform === \"apple-browser\") return { title: t.installAppleBrowserTitle, detail: t.installAppleBrowserDetail };\n if (state().platform === \"android-browser\") return { title: t.installAndroidBrowserTitle, detail: t.installAndroidBrowserDetail };\n return undefined;\n });\n const steps = createMemo(() => {\n const t = messages();\n if (state().platform === \"apple-mobile\" || state().platform === \"apple-browser\")\n return [\n {\n icon: \"ti ti-share-2\",\n title: t.installOpenShare,\n detail: state().platform === \"apple-browser\" ? t.installOpenShareBrowserDetail : t.installOpenShareDetail,\n },\n { icon: \"ti ti-square-plus\", title: t.installHomeScreen, detail: t.installHomeScreenDetail },\n { icon: \"ti ti-check\", title: t.installConfirmAdd, detail: t.installConfirmAddDetail },\n ];\n if (state().platform === \"apple-desktop\")\n return [\n { icon: \"ti ti-share-2\", title: t.installSafariMenu, detail: t.installSafariMenuDetail },\n { icon: \"ti ti-app-window\", title: t.installAddDock, detail: t.installAddDockDetail },\n ];\n return [\n { icon: \"ti ti-dots\", title: t.installBrowserMenu, detail: t.installBrowserMenuDetail },\n {\n icon: \"ti ti-square-plus\",\n title: t.installApp,\n detail: state().platform === \"android\" || state().platform === \"android-browser\" ? t.installAndroidDetail : t.installBrowserDetail,\n },\n ];\n });\n return (\n <div class={props.class ? `k2b-install-guide ${props.class}` : \"k2b-install-guide\"} data-platform={state().platform}>\n <Show when={state().failed()}>\n <p role=\"alert\">{messages().installFailed}</p>\n </Show>\n <Show\n when={state().requested()}\n fallback={\n <Show\n when={state().canPrompt() || state().busy()}\n fallback={\n <Show\n when={embedded()}\n fallback={\n <>\n <Show when={hint()}>{(hint) => <NoticeCard tone=\"info\" title={hint().title} detail={hint().detail} />}</Show>\n <ol class=\"k2b-install-guide__steps\">\n <For each={steps()}>\n {(step) => (\n <li>\n <span class=\"k2b-install-guide__step-icon\">\n <i class={step.icon} aria-hidden=\"true\" />\n </span>\n <div>\n <h3>{step.title}</h3>\n <p>{step.detail}</p>\n </div>\n </li>\n )}\n </For>\n </ol>\n <Show when={state().platform === \"apple-mobile\"}>\n <p>{messages().installSafariFallback}</p>\n </Show>\n <Show when={props.note}>\n <p>{props.note}</p>\n </Show>\n <Show when={state().platform === \"generic\"}>\n <p>{messages().installUnavailable}</p>\n </Show>\n </>\n }\n >\n <NoticeCard\n tone=\"warning\"\n title={state().platform === \"apple-in-app\" ? messages().installAppleInAppTitle : messages().installInAppTitle}\n detail={\n state().platform === \"apple-in-app\"\n ? messages().installAppleInAppDetail({ appName: props.appName })\n : messages().installInAppDetail({ appName: props.appName })\n }\n />\n <Button variant=\"secondary\" onClick={() => void copy.copy(props.url)}>\n <i class={copy.wasCopied() ? \"ti ti-check\" : \"ti ti-copy\"} aria-hidden=\"true\" />\n {copy.wasCopied() ? messages().installLinkCopied : messages().installCopyLink}\n </Button>\n <Show when={copy.error()}>\n <p role=\"alert\">{messages().installCopyFailed}</p>\n <code>{props.url}</code>\n </Show>\n <Show when={props.note}>\n <p>{props.note}</p>\n </Show>\n </Show>\n }\n >\n <p>{messages().installNative({ appName: props.appName })}</p>\n <Button loading={state().busy()} onClick={() => void state().install()}>\n {messages().installApp}\n </Button>\n </Show>\n }\n >\n <p role=\"status\">{messages().installRequested}</p>\n </Show>\n </div>\n );\n}\n\nexport default InstallGuide;\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["feedback/InstallGuide.tsx"],"sourcesContent":["import { clipboard } from \"@k2b/stdlib/solid\";\nimport { createMemo, For, type JSX, Match, Show, Switch } from \"solid-js\";\nimport { Button, ButtonLink } from \"../actions/Button\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { NoticeCard } from \"../surfaces/NoticeCard\";\nimport type { InstallPrompt } from \"./install\";\n\nexport type InstallGuideProps = {\n /** The name people see on their Home Screen, used in the guidance text. */\n appName: string;\n install: InstallPrompt;\n /**\n * The absolute address of the page to install. A browser inside another app copies it, to open it in Safari or\n * Chrome, and shows it as text when copying fails, so it can be copied by hand. Samsung Internet opens an http or\n * https address in Chrome.\n */\n url: string;\n /**\n * An application note under the installation steps or the embedded browser's link, for example what else the\n * installed app enables.\n */\n note?: string;\n class?: string;\n};\n\n/**\n * Opens `url` in Chrome on Android. Without Chrome, the browser loads `url` itself. Only an absolute http or https\n * address has an intent; anything else returns `undefined`.\n */\nconst chromeIntent = (url: string) => {\n let target: URL;\n try {\n target = new URL(url);\n } catch {\n return undefined;\n }\n if (target.protocol !== \"https:\" && target.protocol !== \"http:\") return undefined;\n const scheme = target.protocol.slice(0, -1);\n return `intent://${target.host}${target.pathname}${target.search}${target.hash}#Intent;scheme=${scheme};package=com.android.chrome;S.browser_fallback_url=${encodeURIComponent(url)};end`;\n};\n\n/**\n * Explains how to install the current page as an app on this platform: the browser's own dialog where it offers\n * one, otherwise the steps for Safari on iPhone, iPad, and Mac, other browsers on iPhone and iPad, Android, and other\n * browsers. A browser inside another app cannot install, so it gets a warning and the link to copy into Safari or\n * Chrome instead. Samsung Internet gets a link to Chrome even when it offers its own dialog, because Android may block\n * the app package it builds. The host owns the surrounding surface, its heading, and dismissal.\n */\nexport function InstallGuide(props: InstallGuideProps): JSX.Element {\n const messages = useUiMessages();\n // The host may hand over another state after mount, such as the browser's own in place of the server's view.\n const state = () => props.install;\n const copy = clipboard.createWriter({ write: (url: string) => navigator.clipboard.writeText(url) });\n const embedded = () => state().platform === \"in-app\" || state().platform === \"apple-in-app\";\n /** A browser that installs, but less reliably than the platform's own. */\n const hint = createMemo(() => {\n const t = messages();\n if (state().platform === \"apple-browser\") return { title: t.installAppleBrowserTitle, detail: t.installAppleBrowserDetail };\n if (state().platform === \"android-browser\") return { title: t.installAndroidBrowserTitle, detail: t.installAndroidBrowserDetail };\n return undefined;\n });\n const steps = createMemo(() => {\n const t = messages();\n if (state().platform === \"apple-mobile\" || state().platform === \"apple-browser\")\n return [\n {\n icon: \"ti ti-share-2\",\n title: t.installOpenShare,\n detail: state().platform === \"apple-browser\" ? t.installOpenShareBrowserDetail : t.installOpenShareDetail,\n },\n { icon: \"ti ti-square-plus\", title: t.installHomeScreen, detail: t.installHomeScreenDetail },\n { icon: \"ti ti-check\", title: t.installConfirmAdd, detail: t.installConfirmAddDetail },\n ];\n if (state().platform === \"apple-desktop\")\n return [\n { icon: \"ti ti-share-2\", title: t.installSafariMenu, detail: t.installSafariMenuDetail },\n { icon: \"ti ti-app-window\", title: t.installAddDock, detail: t.installAddDockDetail },\n ];\n return [\n { icon: \"ti ti-dots\", title: t.installBrowserMenu, detail: t.installBrowserMenuDetail },\n {\n icon: \"ti ti-square-plus\",\n title: t.installApp,\n detail: state().platform === \"android\" || state().platform === \"android-browser\" ? t.installAndroidDetail : t.installBrowserDetail,\n },\n ];\n });\n return (\n <div class={props.class ? `k2b-install-guide ${props.class}` : \"k2b-install-guide\"} data-platform={state().platform}>\n <Show when={state().failed()}>\n <p role=\"alert\">{messages().installFailed}</p>\n </Show>\n <Switch\n fallback={\n <>\n <Show when={hint()}>{(hint) => <NoticeCard tone=\"info\" title={hint().title} detail={hint().detail} />}</Show>\n <ol class=\"k2b-install-guide__steps\">\n <For each={steps()}>\n {(step) => (\n <li>\n <span class=\"k2b-install-guide__step-icon\">\n <i class={step.icon} aria-hidden=\"true\" />\n </span>\n <div>\n <h3>{step.title}</h3>\n <p>{step.detail}</p>\n </div>\n </li>\n )}\n </For>\n </ol>\n <Show when={state().platform === \"apple-mobile\"}>\n <p>{messages().installSafariFallback}</p>\n </Show>\n <Show when={props.note}>\n <p>{props.note}</p>\n </Show>\n <Show when={state().platform === \"generic\"}>\n <p>{messages().installUnavailable}</p>\n </Show>\n </>\n }\n >\n <Match when={state().requested()}>\n <p role=\"status\">{messages().installRequested}</p>\n </Match>\n <Match when={state().platform === \"android-samsung\"}>\n <NoticeCard\n tone=\"info\"\n title={messages().installSamsungTitle}\n detail={messages().installSamsungDetail({ appName: props.appName })}\n />\n <Show when={chromeIntent(props.url)}>\n {(href) => (\n <ButtonLink href={href()}>\n <i class=\"ti ti-brand-chrome\" aria-hidden=\"true\" />\n {messages().installOpenChrome}\n </ButtonLink>\n )}\n </Show>\n <Show when={props.note}>\n <p>{props.note}</p>\n </Show>\n </Match>\n <Match when={state().canPrompt() || state().busy()}>\n <p>{messages().installNative({ appName: props.appName })}</p>\n <Button loading={state().busy()} onClick={() => void state().install()}>\n {messages().installApp}\n </Button>\n </Match>\n <Match when={embedded()}>\n <NoticeCard\n tone=\"warning\"\n title={state().platform === \"apple-in-app\" ? messages().installAppleInAppTitle : messages().installInAppTitle}\n detail={\n state().platform === \"apple-in-app\"\n ? messages().installAppleInAppDetail({ appName: props.appName })\n : messages().installInAppDetail({ appName: props.appName })\n }\n />\n <Button variant=\"secondary\" onClick={() => void copy.copy(props.url)}>\n <i class={copy.wasCopied() ? \"ti ti-check\" : \"ti ti-copy\"} aria-hidden=\"true\" />\n {copy.wasCopied() ? messages().installLinkCopied : messages().installCopyLink}\n </Button>\n <Show when={copy.error()}>\n <p role=\"alert\">{messages().installCopyFailed}</p>\n <code>{props.url}</code>\n </Show>\n <Show when={props.note}>\n <p>{props.note}</p>\n </Show>\n </Match>\n </Switch>\n </div>\n );\n}\n\nexport default InstallGuide;\n"],"mappings":"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","ignoreList":[]}
@@ -3,8 +3,9 @@ import { createSignal, onCleanup } from "solid-js";
3
3
  /**
4
4
  * Which installation steps a browser needs. `apple-in-app` and `in-app` are browsers embedded in another app, on
5
5
  * iPhone or iPad and elsewhere, which cannot install at all. `apple-browser` is Chrome, Firefox, Edge, or another
6
- * browser on iPhone or iPad, which installs through its Share menu but less reliably than Safari. `android-browser`
7
- * is an Android browser other than Chrome.
6
+ * browser on iPhone or iPad, which installs through its Share menu but less reliably than Safari. `android-samsung` is
7
+ * Samsung Internet on Android, whose installed apps Android may block as built for an older Android version.
8
+ * `android-browser` is another Android browser, such as Firefox or Edge.
8
9
  */
9
10
 
10
11
  /** Tokens of apps that open links in their own browser view. Case-sensitive, so `Line/` matches only LINE. */
@@ -12,7 +13,7 @@ const EMBEDDED = /FBAN|FBAV|FB_IAB|Instagram|LinkedInApp|Twitter|Snapchat|musica
12
13
  /** Browsers on iPhone and iPad other than Safari. */
13
14
  const APPLE_BROWSER = /CriOS\/|FxiOS\/|EdgiOS\/|OPiOS\/|OPT\/|Ddg\/|DuckDuckGo\/|YaBrowser\//;
14
15
  /** Android browsers whose user agent also names Chrome. */
15
- const ANDROID_OTHER = /SamsungBrowser|EdgA\/|OPR\/|YaBrowser|Firefox\//;
16
+ const ANDROID_OTHER = /EdgA\/|OPR\/|YaBrowser|Firefox\//;
16
17
 
17
18
  /**
18
19
  * Classifies the browser from its user agent; `platform` and `touchPoints` only tell an iPad that reports a Mac apart.
@@ -29,7 +30,10 @@ export function installationPlatform(userAgent, platform, touchPoints) {
29
30
  }
30
31
  if (EMBEDDED.test(userAgent)) return "in-app";
31
32
  if (/Macintosh/.test(userAgent) && /Safari/.test(userAgent) && !/Chrome|Chromium|Edg\//.test(userAgent)) return "apple-desktop";
32
- if (/Android/.test(userAgent)) return /Chrome\//.test(userAgent) && !ANDROID_OTHER.test(userAgent) ? "android" : "android-browser";
33
+ if (/Android/.test(userAgent)) {
34
+ if (/SamsungBrowser\//.test(userAgent)) return "android-samsung";
35
+ return /Chrome\//.test(userAgent) && !ANDROID_OTHER.test(userAgent) ? "android" : "android-browser";
36
+ }
33
37
  return "generic";
34
38
  }
35
39
  function isInstallPrompt(event) {
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["feedback/install.ts"],"sourcesContent":["import { createSignal, onCleanup } from \"solid-js\";\n\n/**\n * Which installation steps a browser needs. `apple-in-app` and `in-app` are browsers embedded in another app, on\n * iPhone or iPad and elsewhere, which cannot install at all. `apple-browser` is Chrome, Firefox, Edge, or another\n * browser on iPhone or iPad, which installs through its Share menu but less reliably than Safari. `android-browser`\n * is an Android browser other than Chrome.\n */\nexport type InstallationPlatform =\n | \"in-app\"\n | \"apple-in-app\"\n | \"apple-browser\"\n | \"apple-mobile\"\n | \"apple-desktop\"\n | \"android-browser\"\n | \"android\"\n | \"generic\";\n\n/** Tokens of apps that open links in their own browser view. Case-sensitive, so `Line/` matches only LINE. */\nconst EMBEDDED =\n /FBAN|FBAV|FB_IAB|Instagram|LinkedInApp|Twitter|Snapchat|musical_ly|BytedanceWebview|Pinterest|MicroMessenger|\\bLine\\/|GSA\\/|; wv\\)/;\n/** Browsers on iPhone and iPad other than Safari. */\nconst APPLE_BROWSER = /CriOS\\/|FxiOS\\/|EdgiOS\\/|OPiOS\\/|OPT\\/|Ddg\\/|DuckDuckGo\\/|YaBrowser\\//;\n/** Android browsers whose user agent also names Chrome. */\nconst ANDROID_OTHER = /SamsungBrowser|EdgA\\/|OPR\\/|YaBrowser|Firefox\\//;\n\n/**\n * Classifies the browser from its user agent; `platform` and `touchPoints` only tell an iPad that reports a Mac apart.\n * A pure function, so the server and the browser compute the same value from the same user agent. Not a device\n * check: an installed app on iPhone may itself look like an embedded browser.\n */\nexport function installationPlatform(userAgent: string, platform: string, touchPoints: number): InstallationPlatform {\n if (/iPad|iPhone|iPod/.test(userAgent) || (platform === \"MacIntel\" && touchPoints > 1)) {\n if (EMBEDDED.test(userAgent)) return \"apple-in-app\";\n if (APPLE_BROWSER.test(userAgent)) return \"apple-browser\";\n // Safari and every real browser send `Safari/`; a bare web view inside an app, such as a QR scanner, does not.\n if (!/Safari\\//.test(userAgent)) return \"apple-in-app\";\n return \"apple-mobile\";\n }\n if (EMBEDDED.test(userAgent)) return \"in-app\";\n if (/Macintosh/.test(userAgent) && /Safari/.test(userAgent) && !/Chrome|Chromium|Edg\\//.test(userAgent)) return \"apple-desktop\";\n if (/Android/.test(userAgent)) return /Chrome\\//.test(userAgent) && !ANDROID_OTHER.test(userAgent) ? \"android\" : \"android-browser\";\n return \"generic\";\n}\n\ninterface BeforeInstallPromptEvent extends Event {\n prompt(): Promise<{ outcome: \"accepted\" | \"dismissed\" }>;\n}\n\nfunction isInstallPrompt(event: Event): event is BeforeInstallPromptEvent {\n return \"prompt\" in event && typeof event.prompt === \"function\";\n}\n\nexport type InstallPrompt = {\n platform: InstallationPlatform;\n /** Running from the Home Screen or as an installed app, or installed during this visit. */\n installed: () => boolean;\n /** The browser offered its own installation dialog, which `install()` opens. */\n canPrompt: () => boolean;\n busy: () => boolean;\n /** The person accepted the browser's dialog; the browser finishes the installation. */\n requested: () => boolean;\n /** The browser's dialog could not be opened. */\n failed: () => boolean;\n /** Opens the browser's dialog once. Call it directly from a click. */\n install: () => Promise<void>;\n};\n\n/**\n * Tracks whether the page runs installed and captures the browser's installation prompt. Browser-only; call it once,\n * when the page loads, under an owner that lives as long as the page: Chrome offers its prompt only once, early.\n */\nexport function createInstallPrompt(): InstallPrompt {\n const displayMode = matchMedia(\"(display-mode: standalone)\");\n const isStandalone = () => displayMode.matches || (\"standalone\" in navigator && navigator.standalone === true);\n const [installed, setInstalled] = createSignal(isStandalone());\n const [prompt, setPrompt] = createSignal<BeforeInstallPromptEvent>();\n const [busy, setBusy] = createSignal(false);\n const [requested, setRequested] = createSignal(false);\n const [failed, setFailed] = createSignal(false);\n const capture = (event: Event) => {\n if (!isInstallPrompt(event)) return;\n event.preventDefault();\n if (installed()) return;\n setPrompt(event);\n setRequested(false);\n };\n const complete = () => {\n setInstalled(true);\n setPrompt(undefined);\n };\n const displayChanged = () => {\n if (isStandalone()) complete();\n };\n window.addEventListener(\"beforeinstallprompt\", capture);\n window.addEventListener(\"appinstalled\", complete);\n displayMode.addEventListener(\"change\", displayChanged);\n onCleanup(() => {\n window.removeEventListener(\"beforeinstallprompt\", capture);\n window.removeEventListener(\"appinstalled\", complete);\n displayMode.removeEventListener(\"change\", displayChanged);\n });\n const install = async () => {\n if (busy() || installed()) return;\n const event = prompt();\n setFailed(false);\n if (!event) return;\n // Consume once, and invoke synchronously within the user's click.\n setPrompt(undefined);\n setBusy(true);\n try {\n const result = await event.prompt();\n if (!installed()) setRequested(result.outcome === \"accepted\");\n } catch {\n setFailed(true);\n } finally {\n setBusy(false);\n }\n };\n return {\n platform: installationPlatform(navigator.userAgent, navigator.platform, navigator.maxTouchPoints),\n installed,\n canPrompt: () => Boolean(prompt()),\n busy,\n requested,\n failed,\n install,\n };\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["feedback/install.ts"],"sourcesContent":["import { createSignal, onCleanup } from \"solid-js\";\n\n/**\n * Which installation steps a browser needs. `apple-in-app` and `in-app` are browsers embedded in another app, on\n * iPhone or iPad and elsewhere, which cannot install at all. `apple-browser` is Chrome, Firefox, Edge, or another\n * browser on iPhone or iPad, which installs through its Share menu but less reliably than Safari. `android-samsung` is\n * Samsung Internet on Android, whose installed apps Android may block as built for an older Android version.\n * `android-browser` is another Android browser, such as Firefox or Edge.\n */\nexport type InstallationPlatform =\n | \"in-app\"\n | \"apple-in-app\"\n | \"apple-browser\"\n | \"apple-mobile\"\n | \"apple-desktop\"\n | \"android-samsung\"\n | \"android-browser\"\n | \"android\"\n | \"generic\";\n\n/** Tokens of apps that open links in their own browser view. Case-sensitive, so `Line/` matches only LINE. */\nconst EMBEDDED =\n /FBAN|FBAV|FB_IAB|Instagram|LinkedInApp|Twitter|Snapchat|musical_ly|BytedanceWebview|Pinterest|MicroMessenger|\\bLine\\/|GSA\\/|; wv\\)/;\n/** Browsers on iPhone and iPad other than Safari. */\nconst APPLE_BROWSER = /CriOS\\/|FxiOS\\/|EdgiOS\\/|OPiOS\\/|OPT\\/|Ddg\\/|DuckDuckGo\\/|YaBrowser\\//;\n/** Android browsers whose user agent also names Chrome. */\nconst ANDROID_OTHER = /EdgA\\/|OPR\\/|YaBrowser|Firefox\\//;\n\n/**\n * Classifies the browser from its user agent; `platform` and `touchPoints` only tell an iPad that reports a Mac apart.\n * A pure function, so the server and the browser compute the same value from the same user agent. Not a device\n * check: an installed app on iPhone may itself look like an embedded browser.\n */\nexport function installationPlatform(userAgent: string, platform: string, touchPoints: number): InstallationPlatform {\n if (/iPad|iPhone|iPod/.test(userAgent) || (platform === \"MacIntel\" && touchPoints > 1)) {\n if (EMBEDDED.test(userAgent)) return \"apple-in-app\";\n if (APPLE_BROWSER.test(userAgent)) return \"apple-browser\";\n // Safari and every real browser send `Safari/`; a bare web view inside an app, such as a QR scanner, does not.\n if (!/Safari\\//.test(userAgent)) return \"apple-in-app\";\n return \"apple-mobile\";\n }\n if (EMBEDDED.test(userAgent)) return \"in-app\";\n if (/Macintosh/.test(userAgent) && /Safari/.test(userAgent) && !/Chrome|Chromium|Edg\\//.test(userAgent)) return \"apple-desktop\";\n if (/Android/.test(userAgent)) {\n if (/SamsungBrowser\\//.test(userAgent)) return \"android-samsung\";\n return /Chrome\\//.test(userAgent) && !ANDROID_OTHER.test(userAgent) ? \"android\" : \"android-browser\";\n }\n return \"generic\";\n}\n\ninterface BeforeInstallPromptEvent extends Event {\n prompt(): Promise<{ outcome: \"accepted\" | \"dismissed\" }>;\n}\n\nfunction isInstallPrompt(event: Event): event is BeforeInstallPromptEvent {\n return \"prompt\" in event && typeof event.prompt === \"function\";\n}\n\nexport type InstallPrompt = {\n platform: InstallationPlatform;\n /** Running from the Home Screen or as an installed app, or installed during this visit. */\n installed: () => boolean;\n /** The browser offered its own installation dialog, which `install()` opens. */\n canPrompt: () => boolean;\n busy: () => boolean;\n /** The person accepted the browser's dialog; the browser finishes the installation. */\n requested: () => boolean;\n /** The browser's dialog could not be opened. */\n failed: () => boolean;\n /** Opens the browser's dialog once. Call it directly from a click. */\n install: () => Promise<void>;\n};\n\n/**\n * Tracks whether the page runs installed and captures the browser's installation prompt. Browser-only; call it once,\n * when the page loads, under an owner that lives as long as the page: Chrome offers its prompt only once, early.\n */\nexport function createInstallPrompt(): InstallPrompt {\n const displayMode = matchMedia(\"(display-mode: standalone)\");\n const isStandalone = () => displayMode.matches || (\"standalone\" in navigator && navigator.standalone === true);\n const [installed, setInstalled] = createSignal(isStandalone());\n const [prompt, setPrompt] = createSignal<BeforeInstallPromptEvent>();\n const [busy, setBusy] = createSignal(false);\n const [requested, setRequested] = createSignal(false);\n const [failed, setFailed] = createSignal(false);\n const capture = (event: Event) => {\n if (!isInstallPrompt(event)) return;\n event.preventDefault();\n if (installed()) return;\n setPrompt(event);\n setRequested(false);\n };\n const complete = () => {\n setInstalled(true);\n setPrompt(undefined);\n };\n const displayChanged = () => {\n if (isStandalone()) complete();\n };\n window.addEventListener(\"beforeinstallprompt\", capture);\n window.addEventListener(\"appinstalled\", complete);\n displayMode.addEventListener(\"change\", displayChanged);\n onCleanup(() => {\n window.removeEventListener(\"beforeinstallprompt\", capture);\n window.removeEventListener(\"appinstalled\", complete);\n displayMode.removeEventListener(\"change\", displayChanged);\n });\n const install = async () => {\n if (busy() || installed()) return;\n const event = prompt();\n setFailed(false);\n if (!event) return;\n // Consume once, and invoke synchronously within the user's click.\n setPrompt(undefined);\n setBusy(true);\n try {\n const result = await event.prompt();\n if (!installed()) setRequested(result.outcome === \"accepted\");\n } catch {\n setFailed(true);\n } finally {\n setBusy(false);\n }\n };\n return {\n platform: installationPlatform(navigator.userAgent, navigator.platform, navigator.maxTouchPoints),\n installed,\n canPrompt: () => Boolean(prompt()),\n busy,\n requested,\n failed,\n install,\n };\n}\n"],"mappings":"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","ignoreList":[]}