@tastic/profile 0.16.1 → 0.17.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 +27 -15
- package/dist/index.d.mts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +78 -25
- package/dist/index.mjs +78 -25
- package/package.json +9 -8
- package/src/IdentityChip.tsx +4 -3
- package/src/OptionPicker.tsx +2 -0
- package/src/PickerMenu.tsx +53 -18
- package/src/ProfileColorContext.tsx +18 -9
- package/src/ProfilePicker.tsx +2 -1
- package/src/ProfilesManager.tsx +77 -51
- package/src/SwatchLayer.tsx +12 -6
- package/src/index.ts +1 -1
- package/src/sharedProfileStore.ts +35 -5
package/README.md
CHANGED
|
@@ -57,7 +57,10 @@ see its own doc comment for why that component is generic over your own `Profile
|
|
|
57
57
|
(`{ value, onChange, previewValue?, swatches?, label?, caption? }`) puts a Color row at the top of
|
|
58
58
|
the popover that opens a page of swatches (with a back row), and turns the trigger into one
|
|
59
59
|
button that leads with a chip in `color` with the selected profile's tag; `triggerStyle` styles
|
|
60
|
-
that button.
|
|
60
|
+
that button. A `flexShrink` in `triggerStyle` lets the button give way in a row too narrow for
|
|
61
|
+
it: its label is cut off with an ellipsis while the chip and chevron keep their size, and it never
|
|
62
|
+
gets narrower than those. With room it lays out as before. `OptionPicker` takes the same
|
|
63
|
+
`triggerStyle`. Also exports
|
|
61
64
|
`getProfilePickerContentSize(profileCount, hasManageRow?, colorSection?)` - the popover's
|
|
62
65
|
`{ width, height }`, for sizing your own placement hook.
|
|
63
66
|
- **`ProfilesManager`**: the roster screen itself: create/rename/recolor/retag/delete, editing
|
|
@@ -84,7 +87,10 @@ see its own doc comment for why that component is generic over your own `Profile
|
|
|
84
87
|
in that app's own local extension table yet - e.g. it was created on a different app in the
|
|
85
88
|
group). This split is deliberate, not a limitation to work around: it's this package's own
|
|
86
89
|
base-fields-only `Profile` boundary (see above), just applied to storage instead of just to the
|
|
87
|
-
TypeScript type.
|
|
90
|
+
TypeScript type. A read drops any stored entry that this version's `isValidProfile` rejects (a
|
|
91
|
+
profile saved by a newer app under a rule loosened since), and `saveSharedProfiles` writes those
|
|
92
|
+
entries back after your roster as they were, so an older app can't delete a newer app's profile
|
|
93
|
+
just by saving.
|
|
88
94
|
|
|
89
95
|
```ts
|
|
90
96
|
// Conceptual sketch of the pattern - see "Redux helpers for the shared roster" below for a
|
|
@@ -174,14 +180,16 @@ rect). Pass a placement **hook** as `useColorPickerAlign`:
|
|
|
174
180
|
<ProfilesScreen useColorPickerAlign={useFrameAutoAlign} /* ...the usual props */ />
|
|
175
181
|
```
|
|
176
182
|
|
|
177
|
-
It is called as `useColorPickerAlign(open, contentWidth, contentHeight)` - `open` is whether the swatch
|
|
178
|
-
popover is showing,
|
|
179
|
-
`getInlineColorPickerContentSize` for the default swatch list at the raw window width)
|
|
183
|
+
It is called as `useColorPickerAlign(open, contentWidth, contentHeight, column)` - `open` is whether the swatch
|
|
184
|
+
popover is showing, the size is what `InlineColorPicker` itself computes (`@tastic/hud`'s
|
|
185
|
+
`getInlineColorPickerContentSize` for the default swatch list at the raw window width), and `column` is a ref
|
|
186
|
+
to the roster's scroll view, which clips the popover at its edges: measure it to keep the popover inside a
|
|
187
|
+
roster narrower than the frame (a column capped in a wide window) - and must return a
|
|
180
188
|
`ProfilePickerAlignResult`, which is handed to `InlineColorPicker` as its `alignOverride` (its `triggerRef`
|
|
181
189
|
is the ref that gets measured). It is a hook, not a plain function: give it a stable identity for the life of
|
|
182
190
|
the mount (a module-level function), since switching it at runtime remounts the swatch. Omit it and behaviour is
|
|
183
|
-
identical to before
|
|
184
|
-
|
|
191
|
+
identical to before, and without a hook the popover is kept inside that column for you (`InlineColorPicker`'s
|
|
192
|
+
`bounds`, `@tastic/hud` 0.25.0+).
|
|
185
193
|
|
|
186
194
|
### Routed screens: `ProfilesScreen`
|
|
187
195
|
|
|
@@ -310,23 +318,27 @@ untouched. `useProfileColorPreview()` returns the provider's function (or `undef
|
|
|
310
318
|
|
|
311
319
|
`renderSwatch` (also optional, with or without `preview`) draws the fill of every round swatch this
|
|
312
320
|
package draws, in place of the flat color: `ProfileChip`, the pickers' chip trigger, the Color row's
|
|
313
|
-
swatch and the color page's swatches. It's called as `renderSwatch(color, size)` with the
|
|
314
|
-
color (after `preview`)
|
|
315
|
-
|
|
316
|
-
the
|
|
321
|
+
swatch and the color page's swatches. It's called as `renderSwatch(color, size, place)` with the
|
|
322
|
+
drawn color (after `preview`), the circle's diameter and where the swatch is (`ProfileSwatchPlace`:
|
|
323
|
+
`'chip'`, `'colorRow'` or `'colorPage'`, 0.16.2+), and its drawing is clipped to the circle, behind
|
|
324
|
+
the tag, icon or check. Return `null` (or `undefined`) to keep a swatch's flat fill there. The tag and
|
|
325
|
+
check still take their contrast from `color`, so treat it as the primary color of whatever you draw.
|
|
326
|
+
Hangman draws a pie of each color's triad where colors are chosen, and keeps its chips flat so their
|
|
327
|
+
tags stay legible:
|
|
317
328
|
|
|
318
329
|
```tsx
|
|
319
330
|
import { getTriadicPalette } from '@rific/auto-paper'
|
|
320
331
|
import { ProfileColorProvider, type ProfileSwatchRenderer } from '@tastic/profile'
|
|
321
332
|
|
|
322
|
-
const renderSwatch: ProfileSwatchRenderer = (color, size) => <TriadPie colors={getTriadicPalette(color)} size={size} />
|
|
333
|
+
const renderSwatch: ProfileSwatchRenderer = (color, size, place) => (place === 'chip' ? null : <TriadPie colors={getTriadicPalette(color)} size={size} />)
|
|
323
334
|
<ProfileColorProvider renderSwatch={renderSwatch}>{children}</ProfileColorProvider>
|
|
324
335
|
```
|
|
325
336
|
|
|
326
337
|
(`TriadPie` is your own component, e.g. three `react-native-svg` arcs.) Give it a stable identity
|
|
327
|
-
(module level, or `useCallback`).
|
|
328
|
-
|
|
329
|
-
|
|
338
|
+
(module level, or `useCallback`). `ProfilesManager`'s edit row hands it to `@tastic/hud`'s
|
|
339
|
+
`InlineColorPicker` too: its trigger (the profile's color with its tag on it) as a `'chip'`, its grid
|
|
340
|
+
as the `'colorPage'`. Rows that fill with a color (a selected picker row) stay flat.
|
|
341
|
+
`useProfileSwatchRenderer()` returns it (or `undefined`).
|
|
330
342
|
|
|
331
343
|
## Per-seat color persistence
|
|
332
344
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { RefObject, ReactNode, Ref } from 'react';
|
|
3
|
-
import { PopoverHost, PopoverRotation } from '@tastic/hud/chrome';
|
|
4
|
-
import {
|
|
3
|
+
import { ViewInstance, PopoverHost, PopoverRotation } from '@tastic/hud/chrome';
|
|
4
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
5
5
|
import { SeedColor } from '@rific/auto-paper';
|
|
6
6
|
|
|
7
7
|
interface ProfilePickerColorSection {
|
|
@@ -19,7 +19,7 @@ interface ProfilePickerAlignResult {
|
|
|
19
19
|
verticalAlign: 'below' | 'above';
|
|
20
20
|
maxHeight: number;
|
|
21
21
|
measured: boolean;
|
|
22
|
-
triggerRef: RefObject<
|
|
22
|
+
triggerRef: RefObject<ViewInstance | null>;
|
|
23
23
|
}
|
|
24
24
|
interface ProfilePickerContentSize {
|
|
25
25
|
width: number;
|
|
@@ -67,7 +67,8 @@ interface Props {
|
|
|
67
67
|
}
|
|
68
68
|
declare function ProfileChip({ profile, size, filled }: Props): react.JSX.Element;
|
|
69
69
|
|
|
70
|
-
type
|
|
70
|
+
type ProfileSwatchPlace = 'chip' | 'colorRow' | 'colorPage';
|
|
71
|
+
type ProfileSwatchRenderer = (color: string, size: number, place: ProfileSwatchPlace) => ReactNode;
|
|
71
72
|
interface ProfileColorProviderProps {
|
|
72
73
|
preview?: (hex: string) => string;
|
|
73
74
|
renderSwatch?: ProfileSwatchRenderer;
|
|
@@ -122,7 +123,7 @@ interface ProfilesManagerProps {
|
|
|
122
123
|
fgMuted?: string;
|
|
123
124
|
titleVariant?: MD3TextVariant$1;
|
|
124
125
|
colorPreview?: (hex: string) => string;
|
|
125
|
-
useColorPickerAlign?: (open: boolean, contentWidth: number, contentHeight: number) => ProfilePickerAlignResult;
|
|
126
|
+
useColorPickerAlign?: (open: boolean, contentWidth: number, contentHeight: number, column: RefObject<ViewInstance | null>) => ProfilePickerAlignResult;
|
|
126
127
|
}
|
|
127
128
|
declare function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride, titleVariant, colorPreview: colorPreviewProp, useColorPickerAlign }: ProfilesManagerProps): react.JSX.Element;
|
|
128
129
|
|
|
@@ -266,4 +267,4 @@ declare function loadSharedProfiles(groupId: string): Promise<Profile[]>;
|
|
|
266
267
|
declare function saveSharedProfiles(groupId: string, profiles: Profile[]): Promise<void>;
|
|
267
268
|
declare function subscribeSharedProfiles(groupId: string, listener: (profiles: Profile[]) => void): () => void;
|
|
268
269
|
|
|
269
|
-
export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, OptionPicker, type OptionPickerOption, type Profile, ProfileChip, type ProfileColorPick, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, type ProfileSwatchRenderer, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileColorPayload, type SetProfileExtensionPayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getOptionPickerContentSize, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profileSeatColor, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, subscribeSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useProfileSwatchRenderer, useSharedProfilesSync };
|
|
270
|
+
export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, OptionPicker, type OptionPickerOption, type Profile, ProfileChip, type ProfileColorPick, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, type ProfileSwatchPlace, type ProfileSwatchRenderer, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileColorPayload, type SetProfileExtensionPayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getOptionPickerContentSize, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profileSeatColor, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, subscribeSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useProfileSwatchRenderer, useSharedProfilesSync };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { RefObject, ReactNode, Ref } from 'react';
|
|
3
|
-
import { PopoverHost, PopoverRotation } from '@tastic/hud/chrome';
|
|
4
|
-
import {
|
|
3
|
+
import { ViewInstance, PopoverHost, PopoverRotation } from '@tastic/hud/chrome';
|
|
4
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
5
5
|
import { SeedColor } from '@rific/auto-paper';
|
|
6
6
|
|
|
7
7
|
interface ProfilePickerColorSection {
|
|
@@ -19,7 +19,7 @@ interface ProfilePickerAlignResult {
|
|
|
19
19
|
verticalAlign: 'below' | 'above';
|
|
20
20
|
maxHeight: number;
|
|
21
21
|
measured: boolean;
|
|
22
|
-
triggerRef: RefObject<
|
|
22
|
+
triggerRef: RefObject<ViewInstance | null>;
|
|
23
23
|
}
|
|
24
24
|
interface ProfilePickerContentSize {
|
|
25
25
|
width: number;
|
|
@@ -67,7 +67,8 @@ interface Props {
|
|
|
67
67
|
}
|
|
68
68
|
declare function ProfileChip({ profile, size, filled }: Props): react.JSX.Element;
|
|
69
69
|
|
|
70
|
-
type
|
|
70
|
+
type ProfileSwatchPlace = 'chip' | 'colorRow' | 'colorPage';
|
|
71
|
+
type ProfileSwatchRenderer = (color: string, size: number, place: ProfileSwatchPlace) => ReactNode;
|
|
71
72
|
interface ProfileColorProviderProps {
|
|
72
73
|
preview?: (hex: string) => string;
|
|
73
74
|
renderSwatch?: ProfileSwatchRenderer;
|
|
@@ -122,7 +123,7 @@ interface ProfilesManagerProps {
|
|
|
122
123
|
fgMuted?: string;
|
|
123
124
|
titleVariant?: MD3TextVariant$1;
|
|
124
125
|
colorPreview?: (hex: string) => string;
|
|
125
|
-
useColorPickerAlign?: (open: boolean, contentWidth: number, contentHeight: number) => ProfilePickerAlignResult;
|
|
126
|
+
useColorPickerAlign?: (open: boolean, contentWidth: number, contentHeight: number, column: RefObject<ViewInstance | null>) => ProfilePickerAlignResult;
|
|
126
127
|
}
|
|
127
128
|
declare function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride, titleVariant, colorPreview: colorPreviewProp, useColorPickerAlign }: ProfilesManagerProps): react.JSX.Element;
|
|
128
129
|
|
|
@@ -266,4 +267,4 @@ declare function loadSharedProfiles(groupId: string): Promise<Profile[]>;
|
|
|
266
267
|
declare function saveSharedProfiles(groupId: string, profiles: Profile[]): Promise<void>;
|
|
267
268
|
declare function subscribeSharedProfiles(groupId: string, listener: (profiles: Profile[]) => void): () => void;
|
|
268
269
|
|
|
269
|
-
export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, OptionPicker, type OptionPickerOption, type Profile, ProfileChip, type ProfileColorPick, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, type ProfileSwatchRenderer, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileColorPayload, type SetProfileExtensionPayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getOptionPickerContentSize, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profileSeatColor, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, subscribeSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useProfileSwatchRenderer, useSharedProfilesSync };
|
|
270
|
+
export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, OptionPicker, type OptionPickerOption, type Profile, ProfileChip, type ProfileColorPick, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, type ProfileSwatchPlace, type ProfileSwatchRenderer, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileColorPayload, type SetProfileExtensionPayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getOptionPickerContentSize, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profileSeatColor, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, subscribeSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useProfileSwatchRenderer, useSharedProfilesSync };
|
package/dist/index.js
CHANGED
|
@@ -88,8 +88,11 @@ function useProfileDisplayColor(hex) {
|
|
|
88
88
|
// src/SwatchLayer.tsx
|
|
89
89
|
var import_react_native = require("react-native");
|
|
90
90
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
91
|
-
function
|
|
92
|
-
return
|
|
91
|
+
function drawSwatch(render, color, size, place) {
|
|
92
|
+
return render ? render(color, size, place) ?? null : null;
|
|
93
|
+
}
|
|
94
|
+
function SwatchLayer({ size, children }) {
|
|
95
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native.View, { style: [styles.layer, { borderRadius: size / 2, height: size, width: size }], children });
|
|
93
96
|
}
|
|
94
97
|
var styles = import_react_native.StyleSheet.create({
|
|
95
98
|
layer: {
|
|
@@ -106,10 +109,11 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
|
106
109
|
var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
|
|
107
110
|
function IdentityChip({ color, tag, icon, size, filled = true }) {
|
|
108
111
|
const renderSwatch = useProfileSwatchRenderer();
|
|
112
|
+
const drawing = filled ? drawSwatch(renderSwatch, color, size, "chip") : null;
|
|
109
113
|
const contrastColor = (0, import_auto_paper.getContrastColor)(color);
|
|
110
114
|
const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : tag && Array.from(tag).length >= 3 ? 0.32 : 0.4);
|
|
111
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: [styles2.chip, { borderRadius: size / 2, height: size, width: size }, filled &&
|
|
112
|
-
|
|
115
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: [styles2.chip, { borderRadius: size / 2, height: size, width: size }, filled && drawing === null && { backgroundColor: color }], children: [
|
|
116
|
+
drawing !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SwatchLayer, { size, children: drawing }),
|
|
113
117
|
tag ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper.Text, { style: [styles2.tag, { color: contrastColor, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper.Icon, { source: icon, size: size * 0.6, color: contrastColor })
|
|
114
118
|
] });
|
|
115
119
|
}
|
|
@@ -134,6 +138,12 @@ var MENU_BORDER_WIDTH = 2;
|
|
|
134
138
|
var ROW_INSET = 12;
|
|
135
139
|
var CHIP_SIZE = 32;
|
|
136
140
|
var CHIP_TRIGGER_PADDING = 6;
|
|
141
|
+
var CHIP_TRIGGER_END_PADDING = CHIP_TRIGGER_PADDING + 8;
|
|
142
|
+
var CHIP_TRIGGER_GAP = 8;
|
|
143
|
+
var PLAIN_TRIGGER_GAP = 2;
|
|
144
|
+
var CHEVRON_SIZE = 16;
|
|
145
|
+
var CHIP_TRIGGER_FIXED_WIDTH = CHIP_TRIGGER_PADDING + CHIP_SIZE + 2 * CHIP_TRIGGER_GAP + CHEVRON_SIZE + CHIP_TRIGGER_END_PADDING;
|
|
146
|
+
var PLAIN_TRIGGER_FIXED_WIDTH = PLAIN_TRIGGER_GAP + CHEVRON_SIZE;
|
|
137
147
|
var SWATCH_SIZE = 28;
|
|
138
148
|
var SWATCH_MIN_GAP = 6;
|
|
139
149
|
var SWATCH_RING_WIDTH = 2;
|
|
@@ -178,11 +188,15 @@ function PickerMenu({ id, host, color, dark, label, chip, colorSection, homeColo
|
|
|
178
188
|
};
|
|
179
189
|
const triggerLabel = label.toUpperCase();
|
|
180
190
|
const showFooter = !!footer && !showColorPage;
|
|
181
|
-
|
|
182
|
-
|
|
191
|
+
const flatTriggerStyle = import_react_native3.StyleSheet.flatten(triggerStyle) ?? {};
|
|
192
|
+
const { flexShrink, ...pillStyle } = flatTriggerStyle;
|
|
193
|
+
const shrinks = typeof flexShrink === "number" && flexShrink > 0;
|
|
194
|
+
const anchorShrink = shrinks ? { flexShrink, minWidth: chipTrigger ? CHIP_TRIGGER_FIXED_WIDTH : PLAIN_TRIGGER_FIXED_WIDTH } : void 0;
|
|
195
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: [styles3.anchor, anchorShrink, open && styles3.anchorOpen], onLayout: handleAnchorLayout, children: [
|
|
196
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress: handleTriggerPress, borderless: chipTrigger, style: [styles3.trigger, chipTrigger && styles3.chipTrigger, shrinks && styles3.triggerShrinks, shrinks ? pillStyle : triggerStyle], accessibilityRole: "button", accessibilityLabel: triggerLabel, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { ref: triggerRef, collapsable: false, style: chipTrigger ? styles3.chipTriggerInner : styles3.triggerInner, children: [
|
|
183
197
|
chip && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IdentityChip, { color, tag: chip.tag, icon: chip.icon, size: CHIP_SIZE }),
|
|
184
198
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles3.triggerLabel, { color: chipTrigger ? fg : color }], numberOfLines: 1, children: triggerLabel }),
|
|
185
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "menu-down", size:
|
|
199
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "menu-down", size: CHEVRON_SIZE, color: chipTrigger ? fg : color })
|
|
186
200
|
] }) }),
|
|
187
201
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_chrome.PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: chipTrigger ? triggerWidth : TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
188
202
|
import_react_native3.View,
|
|
@@ -221,9 +235,10 @@ function ColorRow({ section, fg, dividerColor, onPress }) {
|
|
|
221
235
|
const { value, previewValue, label = "Color", caption } = section;
|
|
222
236
|
const renderSwatch = useProfileSwatchRenderer();
|
|
223
237
|
const drawn = previewValue ? previewValue(value) : value;
|
|
238
|
+
const drawing = drawSwatch(renderSwatch, drawn, COLOR_ROW_SWATCH_SIZE, "colorRow");
|
|
224
239
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
225
240
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress, style: [pickerMenuStyles.row, { height: colorRowHeight(caption) }], accessibilityRole: "button", accessibilityLabel: caption ? `${label}, ${caption}` : label, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: pickerMenuStyles.rowInner, children: [
|
|
226
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles3.colorRowSwatch,
|
|
241
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles3.colorRowSwatch, drawing === null && { backgroundColor: drawn }], children: drawing !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SwatchLayer, { size: COLOR_ROW_SWATCH_SIZE, children: drawing }) }),
|
|
227
242
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.colorRowText, children: [
|
|
228
243
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles3.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
|
|
229
244
|
caption ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles3.colorRowCaption, { color: fg }], numberOfLines: 1, children: caption }) : null
|
|
@@ -250,9 +265,11 @@ function ColorPage({ section, homeColor, fg, onBack }) {
|
|
|
250
265
|
const swatchDisplay = displayFor(swatch.value);
|
|
251
266
|
const contrast = (0, import_auto_paper2.getContrastColor)(swatchDisplay);
|
|
252
267
|
const accessibilityLabel = swappable ? `${swatch.label}, swap` : taken ? `${swatch.label}, taken` : isHomeColor ? `${swatch.label}, profile color` : swatch.label;
|
|
268
|
+
const drawnSize = selected ? SWATCH_SIZE - 2 * SWATCH_RING_WIDTH : SWATCH_SIZE;
|
|
269
|
+
const drawing = drawSwatch(renderSwatch, swatchDisplay, drawnSize, "colorPage");
|
|
253
270
|
const mark = selected ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "check", size: 16, color: contrast }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "swap-horizontal", size: 16, color: contrast }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "close", size: 16, color: contrast }) : isHomeColor ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles3.homeDot, { backgroundColor: contrast }] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, {});
|
|
254
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { disabled: taken && !swappable, onPress: () => onChange(swatch.value), borderless: true, style: [styles3.swatch,
|
|
255
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SwatchLayer, {
|
|
271
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { disabled: taken && !swappable, onPress: () => onChange(swatch.value), borderless: true, style: [styles3.swatch, drawing === null && { backgroundColor: swatchDisplay }, selected && styles3.swatchSelected, taken && !swappable && styles3.swatchTaken], accessibilityRole: "button", accessibilityLabel, accessibilityState: { selected, disabled: taken && !swappable }, children: drawing !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.swatchFace, children: [
|
|
272
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SwatchLayer, { size: drawnSize, children: drawing }),
|
|
256
273
|
mark
|
|
257
274
|
] }) : mark }, swatch.value);
|
|
258
275
|
}) })
|
|
@@ -297,10 +314,10 @@ var styles3 = import_react_native3.StyleSheet.create({
|
|
|
297
314
|
chipTriggerInner: {
|
|
298
315
|
alignItems: "center",
|
|
299
316
|
flexDirection: "row",
|
|
300
|
-
gap:
|
|
317
|
+
gap: CHIP_TRIGGER_GAP,
|
|
301
318
|
height: CHIP_SIZE + 2 * CHIP_TRIGGER_PADDING,
|
|
302
319
|
paddingLeft: CHIP_TRIGGER_PADDING,
|
|
303
|
-
paddingRight:
|
|
320
|
+
paddingRight: CHIP_TRIGGER_END_PADDING
|
|
304
321
|
},
|
|
305
322
|
colorRowCaption: {
|
|
306
323
|
fontSize: 12,
|
|
@@ -409,13 +426,22 @@ var styles3 = import_react_native3.StyleSheet.create({
|
|
|
409
426
|
triggerInner: {
|
|
410
427
|
alignItems: "center",
|
|
411
428
|
flexDirection: "row",
|
|
412
|
-
gap:
|
|
429
|
+
gap: PLAIN_TRIGGER_GAP,
|
|
413
430
|
height: TRIGGER_HEIGHT
|
|
414
431
|
},
|
|
432
|
+
// Lets the label, alone in its row, give way to a trigger narrower than its name (see `shrinks`).
|
|
433
|
+
// With room it keeps its natural width, so this changes nothing there.
|
|
415
434
|
triggerLabel: {
|
|
435
|
+
flexShrink: 1,
|
|
416
436
|
fontSize: 13,
|
|
417
437
|
fontWeight: "700",
|
|
418
|
-
letterSpacing: 1
|
|
438
|
+
letterSpacing: 1,
|
|
439
|
+
minWidth: 0
|
|
440
|
+
},
|
|
441
|
+
// A shrinking trigger (see `shrinks`): no wider than its anchor. Its row is then laid out at that
|
|
442
|
+
// width, which is what cuts the label off.
|
|
443
|
+
triggerShrinks: {
|
|
444
|
+
maxWidth: "100%"
|
|
419
445
|
}
|
|
420
446
|
});
|
|
421
447
|
|
|
@@ -564,6 +590,7 @@ function isValidProfile(value) {
|
|
|
564
590
|
// src/ProfilesManager.tsx
|
|
565
591
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
566
592
|
var CHIP_SIZE2 = 40;
|
|
593
|
+
var textInputRef = (ref) => ref;
|
|
567
594
|
function deriveTag(name) {
|
|
568
595
|
const trimmed = name.trim();
|
|
569
596
|
return trimmed ? trimmed[0].toUpperCase() : "";
|
|
@@ -574,6 +601,8 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
574
601
|
const { dark } = (0, import_auto_paper4.useAutoPaperTheme)();
|
|
575
602
|
const insets = (0, import_react_native_safe_area_context.useSafeAreaInsets)();
|
|
576
603
|
const { error: showErrorToast } = (0, import_toaster.useToast)();
|
|
604
|
+
const columnRef = (0, import_react4.useRef)(null);
|
|
605
|
+
const renderSwatch = useProfileSwatchRenderer();
|
|
577
606
|
const host = (0, import_chrome2.usePopoverHost)();
|
|
578
607
|
const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
|
|
579
608
|
const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
|
|
@@ -644,7 +673,7 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
644
673
|
headerLeft,
|
|
645
674
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Text, { variant: titleVariant, style: [styles5.title, { color: fg }], children: "Profiles" })
|
|
646
675
|
] }),
|
|
647
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.KeyboardAvoidingView, { behavior: import_react_native5.Platform.OS === "ios" ? "padding" : void 0, style: styles5.keyboardAvoiding, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper4.FadingScrollView, { style: styles5.scrollView, contentContainerStyle: [styles5.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
|
|
676
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.KeyboardAvoidingView, { behavior: import_react_native5.Platform.OS === "ios" ? "padding" : void 0, style: styles5.keyboardAvoiding, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.View, { ref: columnRef, collapsable: false, style: styles5.column, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper4.FadingScrollView, { style: styles5.scrollView, contentContainerStyle: [styles5.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
|
|
648
677
|
if (row.kind === "new") {
|
|
649
678
|
if (editingId !== "new") {
|
|
650
679
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => startEdit("new"), style: styles5.row, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native5.View, { style: styles5.rowInner, children: [
|
|
@@ -652,27 +681,28 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
652
681
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Text, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
|
|
653
682
|
] }) }, "new");
|
|
654
683
|
}
|
|
655
|
-
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign }, "new");
|
|
684
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign, column: columnRef, renderSwatch }, "new");
|
|
656
685
|
}
|
|
657
686
|
const { profile } = row;
|
|
658
687
|
if (editingId === profile.id) {
|
|
659
|
-
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign }, profile.id);
|
|
688
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign, column: columnRef, renderSwatch }, profile.id);
|
|
660
689
|
}
|
|
661
690
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => startEdit(profile), style: styles5.row, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native5.View, { style: styles5.rowInner, children: [
|
|
662
691
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ProfileChip, { profile, size: CHIP_SIZE2 }),
|
|
663
692
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Text, { variant: "bodyLarge", style: [styles5.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
|
|
664
693
|
] }) }, profile.id);
|
|
665
|
-
}) }) }),
|
|
694
|
+
}) }) }) }),
|
|
666
695
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_chrome2.ConfirmDialog, { visible: confirmDeleteProfile !== null, title: "Delete Profile?", message: confirmDeleteProfile ? `This permanently deletes ${confirmDeleteProfile.name}\u2019s profile. This cannot be undone.` : "", confirmLabel: "Delete", cancelLabel: "Cancel", icon: "alert-outline", destructive: true, onConfirm: handleConfirmDelete, onCancel: () => setConfirmDeleteId(null) })
|
|
667
696
|
] });
|
|
668
697
|
}
|
|
669
|
-
function AlignedColorPicker({ useAlign, ...props }) {
|
|
698
|
+
function AlignedColorPicker({ useAlign, column, ...props }) {
|
|
670
699
|
const { width: windowWidth } = (0, import_react_native5.useWindowDimensions)();
|
|
671
700
|
const { width, height } = (0, import_chrome2.getInlineColorPickerContentSize)(import_auto_paper4.defaultColors.length, windowWidth);
|
|
672
|
-
const alignOverride = useAlign(props.host.openId === props.id, width, height);
|
|
701
|
+
const alignOverride = useAlign(props.host.openId === props.id, width, height, column);
|
|
673
702
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_chrome2.InlineColorPicker, { ...props, alignOverride });
|
|
674
703
|
}
|
|
675
|
-
function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview, useColorPickerAlign }) {
|
|
704
|
+
function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview, useColorPickerAlign, column, renderSwatch }) {
|
|
705
|
+
const pickerSwatch = (0, import_react4.useMemo)(() => renderSwatch ? (color, size, place) => renderSwatch(color, size, place === "trigger" ? "chip" : "colorPage") : void 0, [renderSwatch]);
|
|
676
706
|
const register = (0, import_focus_chain.useFocusChain)();
|
|
677
707
|
const tag = register();
|
|
678
708
|
const name = register();
|
|
@@ -684,13 +714,17 @@ function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag,
|
|
|
684
714
|
if (host.openId === "color") tagFocusRef.current();
|
|
685
715
|
}, [host.openId]);
|
|
686
716
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native5.View, { style: styles5.editRow, children: [
|
|
687
|
-
useColorPickerAlign ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AlignedColorPicker, { useAlign: useColorPickerAlign, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_chrome2.InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
|
|
688
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles5.hiddenTagInput, pointerEvents: "none" }),
|
|
689
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.TextInput, { ref: name.ref, ...name.props, onSubmitEditing: onSubmit, mode: "outlined", dense: true, value: draftName, onChangeText: onNameChange, maxLength: MAX_PROFILE_NAME_LENGTH, placeholder: "Name", autoFocus, returnKeyType: "done", style: styles5.nameInput }),
|
|
717
|
+
useColorPickerAlign ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AlignedColorPicker, { useAlign: useColorPickerAlign, column, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark, renderSwatch: pickerSwatch }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_chrome2.InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark, renderSwatch: pickerSwatch, bounds: column }),
|
|
718
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.TextInput, { ref: textInputRef(tag.ref), ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles5.hiddenTagInput, pointerEvents: "none" }),
|
|
719
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.TextInput, { ref: textInputRef(name.ref), ...name.props, onSubmitEditing: onSubmit, mode: "outlined", dense: true, value: draftName, onChangeText: onNameChange, maxLength: MAX_PROFILE_NAME_LENGTH, placeholder: "Name", autoFocus, returnKeyType: "done", style: styles5.nameInput }),
|
|
690
720
|
onDelete && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press3.TouchableRipple, { onPress: onDelete, style: styles5.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Icon, { source: "trash-can-outline", size: 18, color: fgMuted }) })
|
|
691
721
|
] });
|
|
692
722
|
}
|
|
693
723
|
var styles5 = import_react_native5.StyleSheet.create({
|
|
724
|
+
// The roster's column, around the scroll view (see columnRef).
|
|
725
|
+
column: {
|
|
726
|
+
flex: 1
|
|
727
|
+
},
|
|
694
728
|
content: {
|
|
695
729
|
gap: 4,
|
|
696
730
|
paddingHorizontal: 20
|
|
@@ -964,10 +998,29 @@ async function readSharedProfiles(groupId) {
|
|
|
964
998
|
async function loadSharedProfiles(groupId) {
|
|
965
999
|
return await readSharedProfiles(groupId) ?? [];
|
|
966
1000
|
}
|
|
1001
|
+
var hasId = (entry) => typeof entry === "object" && entry !== null && typeof entry.id === "string" && entry.id !== "";
|
|
1002
|
+
async function unreadableEntries(groupId, profiles) {
|
|
1003
|
+
let json;
|
|
1004
|
+
try {
|
|
1005
|
+
json = web ? web.storage.getItem(webKey(groupId, PROFILES_KEY)) : await native?.getSharedJSON(groupId, PROFILES_KEY);
|
|
1006
|
+
} catch {
|
|
1007
|
+
return [];
|
|
1008
|
+
}
|
|
1009
|
+
if (!json) return [];
|
|
1010
|
+
try {
|
|
1011
|
+
const parsed = JSON.parse(json);
|
|
1012
|
+
if (!Array.isArray(parsed)) return [];
|
|
1013
|
+
const saved = new Set(profiles.map((profile) => profile.id));
|
|
1014
|
+
return parsed.filter((entry) => !isValidProfile(entry) && hasId(entry) && !saved.has(entry.id));
|
|
1015
|
+
} catch {
|
|
1016
|
+
return [];
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
967
1019
|
async function saveSharedProfiles(groupId, profiles) {
|
|
968
1020
|
if (!native) return;
|
|
969
1021
|
try {
|
|
970
|
-
await
|
|
1022
|
+
const kept = await unreadableEntries(groupId, profiles);
|
|
1023
|
+
await native.setSharedJSON(groupId, PROFILES_KEY, JSON.stringify([...profiles, ...kept]));
|
|
971
1024
|
} catch {
|
|
972
1025
|
}
|
|
973
1026
|
}
|