yaver-feedback-react-native 0.8.1 → 0.8.2
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 +2 -2
- package/dist/AuthOverlay.js +1 -1
- package/dist/FeedbackModal.js +416 -214
- package/dist/P2PClient.d.ts +9 -3
- package/dist/P2PClient.js +23 -2
- package/dist/QuickActionIcon.js +16 -4
- package/dist/YaverFeedback.d.ts +5 -0
- package/dist/YaverFeedback.js +22 -0
- package/dist/__tests__/P2PClient.test.js +30 -0
- package/dist/__tests__/YaverFeedback.test.js +39 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +3 -4
- package/dist/preferences.d.ts +11 -0
- package/dist/preferences.js +82 -0
- package/package.json +6 -4
- package/src/AuthOverlay.tsx +1 -0
- package/src/FeedbackModal.tsx +485 -257
- package/src/P2PClient.ts +36 -3
- package/src/QuickActionIcon.tsx +25 -5
- package/src/YaverFeedback.ts +29 -0
- package/src/__tests__/P2PClient.test.ts +40 -0
- package/src/__tests__/YaverFeedback.test.ts +42 -0
- package/src/index.ts +3 -4
- package/src/preferences.ts +96 -0
package/src/P2PClient.ts
CHANGED
|
@@ -7,6 +7,15 @@ export interface FeedbackEvent {
|
|
|
7
7
|
data: any;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export interface ReloadAck {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
mode: 'dev' | 'bundle';
|
|
13
|
+
acknowledged: boolean;
|
|
14
|
+
message: string;
|
|
15
|
+
nativeChangesDetected?: boolean;
|
|
16
|
+
changeClass?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
/**
|
|
11
20
|
* Try to resolve `{projectName, bundleId}` for the running app so the
|
|
12
21
|
* agent can map the reload request to a MobileProject in its scan
|
|
@@ -317,7 +326,7 @@ export class P2PClient {
|
|
|
317
326
|
async reloadApp(
|
|
318
327
|
mode: 'dev' | 'bundle' = 'bundle',
|
|
319
328
|
opts?: { projectName?: string; bundleId?: string; projectPath?: string },
|
|
320
|
-
): Promise<
|
|
329
|
+
): Promise<ReloadAck> {
|
|
321
330
|
// Default path: always rebuild a fresh Hermes bundle.
|
|
322
331
|
//
|
|
323
332
|
// Rationale: the SDK's common caller is a phone user who's not
|
|
@@ -338,7 +347,19 @@ export class P2PClient {
|
|
|
338
347
|
headers: { Authorization: `Bearer ${this.authToken}` },
|
|
339
348
|
});
|
|
340
349
|
if (primary.ok) {
|
|
341
|
-
|
|
350
|
+
const payload = await primary.json().catch(() => ({} as Record<string, unknown>));
|
|
351
|
+
const nativeChangesDetected = payload.nativeChangesDetected === true;
|
|
352
|
+
return {
|
|
353
|
+
ok: true,
|
|
354
|
+
mode: 'dev',
|
|
355
|
+
acknowledged: true,
|
|
356
|
+
nativeChangesDetected,
|
|
357
|
+
changeClass:
|
|
358
|
+
typeof payload.changeClass === 'string' ? payload.changeClass : undefined,
|
|
359
|
+
message: nativeChangesDetected
|
|
360
|
+
? 'Reload accepted, but native changes need a rebuild.'
|
|
361
|
+
: 'Hot reload request accepted.',
|
|
362
|
+
};
|
|
342
363
|
}
|
|
343
364
|
// Dev mode failed — fall through to bundle rebuild below rather
|
|
344
365
|
// than surfacing the raw error, so the user never has to know
|
|
@@ -370,7 +391,19 @@ export class P2PClient {
|
|
|
370
391
|
const text = await res.text().catch(() => '');
|
|
371
392
|
throw new Error(friendlyReloadError(res.status, text));
|
|
372
393
|
}
|
|
373
|
-
|
|
394
|
+
const payload = await res.json().catch(() => ({} as Record<string, unknown>));
|
|
395
|
+
return {
|
|
396
|
+
ok: true,
|
|
397
|
+
mode: 'bundle',
|
|
398
|
+
acknowledged: true,
|
|
399
|
+
changeClass:
|
|
400
|
+
typeof payload.changeClass === 'string' ? payload.changeClass : undefined,
|
|
401
|
+
nativeChangesDetected: payload.nativeChangesDetected === true,
|
|
402
|
+
message:
|
|
403
|
+
typeof payload.message === 'string' && payload.message.trim()
|
|
404
|
+
? payload.message
|
|
405
|
+
: 'Reload request acknowledged. Agent is rebuilding the bundle.',
|
|
406
|
+
};
|
|
374
407
|
}
|
|
375
408
|
|
|
376
409
|
/**
|
package/src/QuickActionIcon.tsx
CHANGED
|
@@ -12,7 +12,13 @@ import {
|
|
|
12
12
|
View,
|
|
13
13
|
} from 'react-native';
|
|
14
14
|
import { YaverFeedback } from './YaverFeedback';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
getQuickIconColorPreset,
|
|
17
|
+
getQuickIconDisabled,
|
|
18
|
+
QUICK_ICON_COLOR_PRESETS,
|
|
19
|
+
setQuickIconColorPreset,
|
|
20
|
+
setQuickIconDisabled,
|
|
21
|
+
} from './preferences';
|
|
16
22
|
|
|
17
23
|
// Mirror the suppression rule used by YaverFeedback + ShakeDetector:
|
|
18
24
|
// when loaded through Yaver's super-host Hermes bundle, the host owns
|
|
@@ -123,6 +129,7 @@ export const QuickActionIcon: React.FC<QuickActionIconProps> = ({
|
|
|
123
129
|
const didDrag = useRef(false);
|
|
124
130
|
|
|
125
131
|
const [userDisabled, setUserDisabled] = useState<boolean | null>(null);
|
|
132
|
+
const [colorPreset, setColorPreset] = useState<keyof typeof QUICK_ICON_COLOR_PRESETS | null>(null);
|
|
126
133
|
const [shakenThisSession, setShakenThisSession] = useState(false);
|
|
127
134
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
128
135
|
const [hostSuppressed] = useState<boolean>(() => isRunningInsideYaverHost());
|
|
@@ -135,6 +142,9 @@ export const QuickActionIcon: React.FC<QuickActionIconProps> = ({
|
|
|
135
142
|
getQuickIconDisabled().then((v) => {
|
|
136
143
|
if (alive) setUserDisabled(v);
|
|
137
144
|
});
|
|
145
|
+
getQuickIconColorPreset().then((v) => {
|
|
146
|
+
if (alive) setColorPreset(v);
|
|
147
|
+
});
|
|
138
148
|
return () => {
|
|
139
149
|
alive = false;
|
|
140
150
|
};
|
|
@@ -169,9 +179,18 @@ export const QuickActionIcon: React.FC<QuickActionIconProps> = ({
|
|
|
169
179
|
setMenuOpen(false);
|
|
170
180
|
},
|
|
171
181
|
);
|
|
182
|
+
const colorSub = DeviceEventEmitter.addListener(
|
|
183
|
+
'yaverFeedback:quickIconColorChange',
|
|
184
|
+
(next: { preset?: keyof typeof QUICK_ICON_COLOR_PRESETS | null }) => {
|
|
185
|
+
const preset = next?.preset ?? null;
|
|
186
|
+
setColorPreset(preset);
|
|
187
|
+
void setQuickIconColorPreset(preset);
|
|
188
|
+
},
|
|
189
|
+
);
|
|
172
190
|
return () => {
|
|
173
191
|
showSub.remove();
|
|
174
192
|
hideSub.remove();
|
|
193
|
+
colorSub.remove();
|
|
175
194
|
};
|
|
176
195
|
}, []);
|
|
177
196
|
|
|
@@ -229,6 +248,7 @@ export const QuickActionIcon: React.FC<QuickActionIconProps> = ({
|
|
|
229
248
|
if (mode === 'after-shake' && !shakenThisSession) return null;
|
|
230
249
|
if (!YaverFeedback.isEnabled()) return null;
|
|
231
250
|
|
|
251
|
+
const presetColors = colorPreset ? QUICK_ICON_COLOR_PRESETS[colorPreset] : null;
|
|
232
252
|
const visualSize = size;
|
|
233
253
|
const radius = visualSize / 2;
|
|
234
254
|
|
|
@@ -271,9 +291,9 @@ export const QuickActionIcon: React.FC<QuickActionIconProps> = ({
|
|
|
271
291
|
width: visualSize,
|
|
272
292
|
height: visualSize,
|
|
273
293
|
borderRadius: radius,
|
|
274
|
-
backgroundColor,
|
|
275
|
-
borderColor,
|
|
276
|
-
shadowColor,
|
|
294
|
+
backgroundColor: presetColors?.backgroundColor ?? backgroundColor,
|
|
295
|
+
borderColor: presetColors?.borderColor ?? borderColor,
|
|
296
|
+
shadowColor: presetColors?.shadowColor ?? shadowColor,
|
|
277
297
|
opacity: pressed ? 0.85 : 1,
|
|
278
298
|
},
|
|
279
299
|
]}
|
|
@@ -282,7 +302,7 @@ export const QuickActionIcon: React.FC<QuickActionIconProps> = ({
|
|
|
282
302
|
style={[
|
|
283
303
|
styles.iconLabel,
|
|
284
304
|
{
|
|
285
|
-
color: foregroundColor,
|
|
305
|
+
color: presetColors?.foregroundColor ?? foregroundColor,
|
|
286
306
|
fontSize: Math.round(visualSize * 0.5),
|
|
287
307
|
},
|
|
288
308
|
]}
|
package/src/YaverFeedback.ts
CHANGED
|
@@ -9,13 +9,17 @@ import {
|
|
|
9
9
|
setStrictNativeAuth,
|
|
10
10
|
getToken,
|
|
11
11
|
getSelectedDeviceId,
|
|
12
|
+
listReachableDevices,
|
|
12
13
|
clearToken,
|
|
13
14
|
clearSelectedDeviceId,
|
|
14
15
|
DEFAULT_CONVEX_SITE_URL,
|
|
15
16
|
} from './auth';
|
|
16
17
|
import {
|
|
17
18
|
getQuickIconDisabled,
|
|
19
|
+
getQuickIconColorPreset,
|
|
18
20
|
setQuickIconDisabled,
|
|
21
|
+
setQuickIconColorPreset,
|
|
22
|
+
QuickIconColorPreset,
|
|
19
23
|
} from './preferences';
|
|
20
24
|
|
|
21
25
|
// Is this JS runtime the Yaver mobile app's super-host bridge? The
|
|
@@ -362,6 +366,15 @@ export class YaverFeedback {
|
|
|
362
366
|
await YaverFeedback.discoverAgent();
|
|
363
367
|
}
|
|
364
368
|
|
|
369
|
+
/** Resolve the currently selected remote machine from the authenticated device list. */
|
|
370
|
+
static async getSelectedRemoteDevice() {
|
|
371
|
+
if (!config?.authToken || !config.preferredDeviceId) return null;
|
|
372
|
+
const preferredDeviceId = config.preferredDeviceId;
|
|
373
|
+
const devices = await listReachableDevices(config.authToken);
|
|
374
|
+
const all = [...devices.owned, ...devices.shared];
|
|
375
|
+
return all.find((device) => device.deviceId === preferredDeviceId) ?? null;
|
|
376
|
+
}
|
|
377
|
+
|
|
365
378
|
/**
|
|
366
379
|
* Sign out: clear cached token + device, tear down the P2P client. The
|
|
367
380
|
* SDK stays enabled; the next feedback trigger will re-prompt for login.
|
|
@@ -859,6 +872,22 @@ export class YaverFeedback {
|
|
|
859
872
|
return getQuickIconDisabled();
|
|
860
873
|
}
|
|
861
874
|
|
|
875
|
+
static async setQuickIconColorPreset(
|
|
876
|
+
preset: QuickIconColorPreset | null,
|
|
877
|
+
): Promise<void> {
|
|
878
|
+
await setQuickIconColorPreset(preset);
|
|
879
|
+
try {
|
|
880
|
+
const { DeviceEventEmitter } = require('react-native');
|
|
881
|
+
DeviceEventEmitter.emit('yaverFeedback:quickIconColorChange', { preset });
|
|
882
|
+
} catch {
|
|
883
|
+
// emitter unavailable — preference is still persisted
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
static async getQuickIconColorPreset(): Promise<QuickIconColorPreset | null> {
|
|
888
|
+
return getQuickIconColorPreset();
|
|
889
|
+
}
|
|
890
|
+
|
|
862
891
|
/** Clear the persisted "user hid the icon" flag. */
|
|
863
892
|
static async resetQuickIconPreference(): Promise<void> {
|
|
864
893
|
await YaverFeedback.setQuickIconVisible(true);
|
|
@@ -215,4 +215,44 @@ describe('P2PClient', () => {
|
|
|
215
215
|
expect(result).toEqual(builds);
|
|
216
216
|
});
|
|
217
217
|
});
|
|
218
|
+
|
|
219
|
+
describe('reloadApp()', () => {
|
|
220
|
+
it('returns an acknowledgement for dev reloads', async () => {
|
|
221
|
+
mockFetch.mockResolvedValue({
|
|
222
|
+
ok: true,
|
|
223
|
+
json: () => Promise.resolve({ ok: true, changeClass: 'js_only' }),
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const client = new P2PClient('http://localhost:18080', 'tok');
|
|
227
|
+
const result = await client.reloadApp('dev');
|
|
228
|
+
|
|
229
|
+
expect(result).toEqual(
|
|
230
|
+
expect.objectContaining({
|
|
231
|
+
ok: true,
|
|
232
|
+
mode: 'dev',
|
|
233
|
+
acknowledged: true,
|
|
234
|
+
message: 'Hot reload request accepted.',
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('returns an acknowledgement for bundle reloads', async () => {
|
|
240
|
+
mockFetch.mockResolvedValue({
|
|
241
|
+
ok: true,
|
|
242
|
+
json: () => Promise.resolve({ ok: true }),
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const client = new P2PClient('http://localhost:18080', 'tok');
|
|
246
|
+
const result = await client.reloadApp('bundle');
|
|
247
|
+
|
|
248
|
+
expect(result).toEqual(
|
|
249
|
+
expect.objectContaining({
|
|
250
|
+
ok: true,
|
|
251
|
+
mode: 'bundle',
|
|
252
|
+
acknowledged: true,
|
|
253
|
+
message: 'Reload request acknowledged. Agent is rebuilding the bundle.',
|
|
254
|
+
}),
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
218
258
|
});
|
|
@@ -17,6 +17,34 @@ jest.mock('../Discovery', () => ({
|
|
|
17
17
|
},
|
|
18
18
|
}));
|
|
19
19
|
|
|
20
|
+
jest.mock('../auth', () => ({
|
|
21
|
+
configureAuthEndpoints: jest.fn(),
|
|
22
|
+
setStrictNativeAuth: jest.fn(),
|
|
23
|
+
getToken: jest.fn(async () => null),
|
|
24
|
+
getSelectedDeviceId: jest.fn(async () => null),
|
|
25
|
+
clearToken: jest.fn(async () => {}),
|
|
26
|
+
clearSelectedDeviceId: jest.fn(async () => {}),
|
|
27
|
+
listReachableDevices: jest.fn(async () => ({
|
|
28
|
+
owned: [
|
|
29
|
+
{
|
|
30
|
+
deviceId: 'device-1',
|
|
31
|
+
name: 'Dev Mac',
|
|
32
|
+
platform: 'darwin',
|
|
33
|
+
isOnline: true,
|
|
34
|
+
needsAuth: false,
|
|
35
|
+
runnerDown: false,
|
|
36
|
+
lastHeartbeat: Date.now(),
|
|
37
|
+
isGuest: false,
|
|
38
|
+
accessScope: 'owner',
|
|
39
|
+
quicHost: '127.0.0.1',
|
|
40
|
+
quicPort: 18080,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
shared: [],
|
|
44
|
+
})),
|
|
45
|
+
DEFAULT_CONVEX_SITE_URL: 'https://example.convex.site',
|
|
46
|
+
}));
|
|
47
|
+
|
|
20
48
|
// Reset module-level state between tests by re-requiring
|
|
21
49
|
beforeEach(() => {
|
|
22
50
|
// YaverFeedback uses module-level variables (config, enabled, p2pClient).
|
|
@@ -126,6 +154,20 @@ describe('YaverFeedback', () => {
|
|
|
126
154
|
});
|
|
127
155
|
});
|
|
128
156
|
|
|
157
|
+
describe('getSelectedRemoteDevice()', () => {
|
|
158
|
+
it('returns the selected device from the reachable device list', async () => {
|
|
159
|
+
YaverFeedback.init({
|
|
160
|
+
authToken: 'tok',
|
|
161
|
+
preferredDeviceId: 'device-1',
|
|
162
|
+
enabled: true,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const device = await YaverFeedback.getSelectedRemoteDevice();
|
|
166
|
+
expect(device?.deviceId).toBe('device-1');
|
|
167
|
+
expect(device?.name).toBe('Dev Mac');
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
129
171
|
describe('startReport()', () => {
|
|
130
172
|
it('does nothing when not enabled', async () => {
|
|
131
173
|
YaverFeedback.init({ authToken: 'tok', enabled: false });
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* yaver-feedback-react-native — Visual feedback SDK for Yaver.
|
|
3
3
|
*
|
|
4
|
-
* Shake-to-report surface with
|
|
4
|
+
* Shake-to-report surface with three launch actions:
|
|
5
5
|
* 1. Hot Reload — instant JS reload
|
|
6
6
|
* 2. Vibing — open a vibing session on the agent
|
|
7
|
-
* 3. Screenshot
|
|
8
|
-
*
|
|
9
|
-
* 4. Screen Recording — start, then stop + upload
|
|
7
|
+
* 3. Screenshot & Fix — capture the current screen and trigger
|
|
8
|
+
* the fix loop
|
|
10
9
|
*
|
|
11
10
|
* The small quick-access icon stays hidden until the first shake by
|
|
12
11
|
* default on mobile, then remains available unless the user hides it.
|
package/src/preferences.ts
CHANGED
|
@@ -25,6 +25,69 @@ try {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const QUICK_ICON_DISABLED_KEY = 'yaver_feedback_quickicon_disabled';
|
|
28
|
+
const QUICK_ICON_COLOR_KEY = 'yaver_feedback_quickicon_color';
|
|
29
|
+
|
|
30
|
+
export type QuickIconColorPreset =
|
|
31
|
+
| 'orange'
|
|
32
|
+
| 'lime'
|
|
33
|
+
| 'cyan'
|
|
34
|
+
| 'pink'
|
|
35
|
+
| 'yellow'
|
|
36
|
+
| 'slate';
|
|
37
|
+
|
|
38
|
+
export const QUICK_ICON_COLOR_PRESETS: Record<
|
|
39
|
+
QuickIconColorPreset,
|
|
40
|
+
{
|
|
41
|
+
label: string;
|
|
42
|
+
backgroundColor: string;
|
|
43
|
+
foregroundColor: string;
|
|
44
|
+
borderColor: string;
|
|
45
|
+
shadowColor: string;
|
|
46
|
+
}
|
|
47
|
+
> = {
|
|
48
|
+
orange: {
|
|
49
|
+
label: 'Orange',
|
|
50
|
+
backgroundColor: '#ff6b2c',
|
|
51
|
+
foregroundColor: '#111111',
|
|
52
|
+
borderColor: 'rgba(255,255,255,0.92)',
|
|
53
|
+
shadowColor: '#000000',
|
|
54
|
+
},
|
|
55
|
+
lime: {
|
|
56
|
+
label: 'Lime',
|
|
57
|
+
backgroundColor: '#a3e635',
|
|
58
|
+
foregroundColor: '#111111',
|
|
59
|
+
borderColor: 'rgba(255,255,255,0.85)',
|
|
60
|
+
shadowColor: '#365314',
|
|
61
|
+
},
|
|
62
|
+
cyan: {
|
|
63
|
+
label: 'Cyan',
|
|
64
|
+
backgroundColor: '#22d3ee',
|
|
65
|
+
foregroundColor: '#082f49',
|
|
66
|
+
borderColor: 'rgba(255,255,255,0.82)',
|
|
67
|
+
shadowColor: '#083344',
|
|
68
|
+
},
|
|
69
|
+
pink: {
|
|
70
|
+
label: 'Pink',
|
|
71
|
+
backgroundColor: '#fb7185',
|
|
72
|
+
foregroundColor: '#fff1f2',
|
|
73
|
+
borderColor: 'rgba(255,255,255,0.78)',
|
|
74
|
+
shadowColor: '#4c0519',
|
|
75
|
+
},
|
|
76
|
+
yellow: {
|
|
77
|
+
label: 'Yellow',
|
|
78
|
+
backgroundColor: '#facc15',
|
|
79
|
+
foregroundColor: '#1c1917',
|
|
80
|
+
borderColor: 'rgba(255,255,255,0.88)',
|
|
81
|
+
shadowColor: '#713f12',
|
|
82
|
+
},
|
|
83
|
+
slate: {
|
|
84
|
+
label: 'Slate',
|
|
85
|
+
backgroundColor: '#475569',
|
|
86
|
+
foregroundColor: '#f8fafc',
|
|
87
|
+
borderColor: 'rgba(255,255,255,0.68)',
|
|
88
|
+
shadowColor: '#020617',
|
|
89
|
+
},
|
|
90
|
+
};
|
|
28
91
|
|
|
29
92
|
/** True if the user has long-pressed the icon and chosen "Hide". */
|
|
30
93
|
export async function getQuickIconDisabled(): Promise<boolean> {
|
|
@@ -53,3 +116,36 @@ export async function setQuickIconDisabled(disabled: boolean): Promise<void> {
|
|
|
53
116
|
export async function clearQuickIconDisabled(): Promise<void> {
|
|
54
117
|
await setQuickIconDisabled(false);
|
|
55
118
|
}
|
|
119
|
+
|
|
120
|
+
export async function getQuickIconColorPreset(): Promise<QuickIconColorPreset | null> {
|
|
121
|
+
if (!AsyncStorage) return null;
|
|
122
|
+
try {
|
|
123
|
+
const v = await AsyncStorage.getItem(QUICK_ICON_COLOR_KEY);
|
|
124
|
+
if (!v) return null;
|
|
125
|
+
if (Object.prototype.hasOwnProperty.call(QUICK_ICON_COLOR_PRESETS, v)) {
|
|
126
|
+
return v as QuickIconColorPreset;
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function setQuickIconColorPreset(
|
|
135
|
+
preset: QuickIconColorPreset | null,
|
|
136
|
+
): Promise<void> {
|
|
137
|
+
if (!AsyncStorage) return;
|
|
138
|
+
try {
|
|
139
|
+
if (!preset) {
|
|
140
|
+
await AsyncStorage.removeItem(QUICK_ICON_COLOR_KEY);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
await AsyncStorage.setItem(QUICK_ICON_COLOR_KEY, preset);
|
|
144
|
+
} catch {
|
|
145
|
+
// best-effort
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function clearQuickIconColorPreset(): Promise<void> {
|
|
150
|
+
await setQuickIconColorPreset(null);
|
|
151
|
+
}
|