@echoform/echo-ui 0.1.0 → 0.2.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 +57 -10
- package/dist/components/EchoChangeProfileNameModal.d.ts +59 -0
- package/dist/components/EchoChangeProfileNameModal.d.ts.map +1 -0
- package/dist/components/EchoChangeProfileNameModal.js +317 -0
- package/dist/components/EchoChangeProfileNameModal.js.map +1 -0
- package/dist/components/EchoColorPicker.d.ts +34 -0
- package/dist/components/EchoColorPicker.d.ts.map +1 -0
- package/dist/components/EchoColorPicker.js +206 -0
- package/dist/components/EchoColorPicker.js.map +1 -0
- package/dist/components/EchoConnectedAppsModal.d.ts +40 -0
- package/dist/components/EchoConnectedAppsModal.d.ts.map +1 -0
- package/dist/components/EchoConnectedAppsModal.js +220 -0
- package/dist/components/EchoConnectedAppsModal.js.map +1 -0
- package/dist/components/EchoDeleteProfilePanel.d.ts +63 -0
- package/dist/components/EchoDeleteProfilePanel.d.ts.map +1 -0
- package/dist/components/EchoDeleteProfilePanel.js +286 -0
- package/dist/components/EchoDeleteProfilePanel.js.map +1 -0
- package/dist/components/EchoDeletionIntercept.d.ts +39 -0
- package/dist/components/EchoDeletionIntercept.d.ts.map +1 -0
- package/dist/components/EchoDeletionIntercept.js +212 -0
- package/dist/components/EchoDeletionIntercept.js.map +1 -0
- package/dist/components/EchoDeletionStatusCard.d.ts +35 -0
- package/dist/components/EchoDeletionStatusCard.d.ts.map +1 -0
- package/dist/components/EchoDeletionStatusCard.js +156 -0
- package/dist/components/EchoDeletionStatusCard.js.map +1 -0
- package/dist/components/EchoEmblem.d.ts +93 -0
- package/dist/components/EchoEmblem.d.ts.map +1 -0
- package/dist/components/EchoEmblem.js +308 -0
- package/dist/components/EchoEmblem.js.map +1 -0
- package/dist/components/EchoEmblemPicker.d.ts +71 -0
- package/dist/components/EchoEmblemPicker.d.ts.map +1 -0
- package/dist/components/EchoEmblemPicker.js +490 -0
- package/dist/components/EchoEmblemPicker.js.map +1 -0
- package/dist/components/EchoErrorBanner.d.ts +17 -0
- package/dist/components/EchoErrorBanner.d.ts.map +1 -0
- package/dist/components/EchoErrorBanner.js +48 -0
- package/dist/components/EchoErrorBanner.js.map +1 -0
- package/dist/components/EchoFormField.d.ts +27 -0
- package/dist/components/EchoFormField.d.ts.map +1 -0
- package/dist/components/EchoFormField.js +49 -0
- package/dist/components/EchoFormField.js.map +1 -0
- package/dist/components/EchoLinkButton.d.ts +16 -0
- package/dist/components/EchoLinkButton.d.ts.map +1 -0
- package/dist/components/EchoLinkButton.js +37 -0
- package/dist/components/EchoLinkButton.js.map +1 -0
- package/dist/components/EchoMoreScreen.d.ts +41 -0
- package/dist/components/EchoMoreScreen.d.ts.map +1 -0
- package/dist/components/EchoMoreScreen.js +94 -0
- package/dist/components/EchoMoreScreen.js.map +1 -0
- package/dist/components/EchoPasswordField.d.ts +28 -0
- package/dist/components/EchoPasswordField.d.ts.map +1 -0
- package/dist/components/EchoPasswordField.js +67 -0
- package/dist/components/EchoPasswordField.js.map +1 -0
- package/dist/components/EchoPrimaryButton.d.ts +23 -0
- package/dist/components/EchoPrimaryButton.d.ts.map +1 -0
- package/dist/components/EchoPrimaryButton.js +54 -0
- package/dist/components/EchoPrimaryButton.js.map +1 -0
- package/dist/components/EchoProfileCard.d.ts +35 -0
- package/dist/components/EchoProfileCard.d.ts.map +1 -0
- package/dist/components/EchoProfileCard.js +206 -0
- package/dist/components/EchoProfileCard.js.map +1 -0
- package/dist/components/EchoProfileDivider.d.ts +14 -0
- package/dist/components/EchoProfileDivider.d.ts.map +1 -0
- package/dist/components/EchoProfileDivider.js +31 -0
- package/dist/components/EchoProfileDivider.js.map +1 -0
- package/dist/components/EchoProfileHero.d.ts +48 -0
- package/dist/components/EchoProfileHero.d.ts.map +1 -0
- package/dist/components/EchoProfileHero.js +215 -0
- package/dist/components/EchoProfileHero.js.map +1 -0
- package/dist/components/EchoProfileOverview.d.ts +18 -0
- package/dist/components/EchoProfileOverview.d.ts.map +1 -0
- package/dist/components/EchoProfileOverview.js +36 -0
- package/dist/components/EchoProfileOverview.js.map +1 -0
- package/dist/components/EchoProfileRow.d.ts +60 -0
- package/dist/components/EchoProfileRow.d.ts.map +1 -0
- package/dist/components/EchoProfileRow.js +113 -0
- package/dist/components/EchoProfileRow.js.map +1 -0
- package/dist/components/EchoProfileSection.d.ts +29 -0
- package/dist/components/EchoProfileSection.d.ts.map +1 -0
- package/dist/components/EchoProfileSection.js +58 -0
- package/dist/components/EchoProfileSection.js.map +1 -0
- package/dist/components/EchoRewardNotificationBanner.d.ts +61 -0
- package/dist/components/EchoRewardNotificationBanner.d.ts.map +1 -0
- package/dist/components/EchoRewardNotificationBanner.js +272 -0
- package/dist/components/EchoRewardNotificationBanner.js.map +1 -0
- package/dist/components/EchoSettingsRow.d.ts +24 -0
- package/dist/components/EchoSettingsRow.d.ts.map +1 -0
- package/dist/components/EchoSettingsRow.js +84 -0
- package/dist/components/EchoSettingsRow.js.map +1 -0
- package/dist/components/EchoSettingsSection.d.ts +16 -0
- package/dist/components/EchoSettingsSection.d.ts.map +1 -0
- package/dist/components/EchoSettingsSection.js +48 -0
- package/dist/components/EchoSettingsSection.js.map +1 -0
- package/dist/components/EchoSignOutButton.d.ts +28 -0
- package/dist/components/EchoSignOutButton.d.ts.map +1 -0
- package/dist/components/EchoSignOutButton.js +67 -0
- package/dist/components/EchoSignOutButton.js.map +1 -0
- package/dist/components/EchoSuccessBanner.d.ts +17 -0
- package/dist/components/EchoSuccessBanner.d.ts.map +1 -0
- package/dist/components/EchoSuccessBanner.js +48 -0
- package/dist/components/EchoSuccessBanner.js.map +1 -0
- package/dist/components/EchoSyncPanel.d.ts +165 -0
- package/dist/components/EchoSyncPanel.d.ts.map +1 -0
- package/dist/components/EchoSyncPanel.js +913 -0
- package/dist/components/EchoSyncPanel.js.map +1 -0
- package/dist/components/EchoSyncPromptModal.d.ts +87 -0
- package/dist/components/EchoSyncPromptModal.d.ts.map +1 -0
- package/dist/components/EchoSyncPromptModal.js +351 -0
- package/dist/components/EchoSyncPromptModal.js.map +1 -0
- package/dist/components/EchoTitlePicker.d.ts +72 -0
- package/dist/components/EchoTitlePicker.d.ts.map +1 -0
- package/dist/components/EchoTitlePicker.js +487 -0
- package/dist/components/EchoTitlePicker.js.map +1 -0
- package/dist/components/EchoVerificationCodeInput.d.ts +40 -0
- package/dist/components/EchoVerificationCodeInput.d.ts.map +1 -0
- package/dist/components/EchoVerificationCodeInput.js +140 -0
- package/dist/components/EchoVerificationCodeInput.js.map +1 -0
- package/dist/components/EchoWelcome.d.ts +44 -0
- package/dist/components/EchoWelcome.d.ts.map +1 -0
- package/dist/components/EchoWelcome.js +289 -0
- package/dist/components/EchoWelcome.js.map +1 -0
- package/dist/components/EchoWhyBecomeAnEchoModal.d.ts +34 -0
- package/dist/components/EchoWhyBecomeAnEchoModal.d.ts.map +1 -0
- package/dist/components/EchoWhyBecomeAnEchoModal.js +321 -0
- package/dist/components/EchoWhyBecomeAnEchoModal.js.map +1 -0
- package/dist/components/EchoformInfoButton.d.ts +27 -0
- package/dist/components/EchoformInfoButton.d.ts.map +1 -0
- package/dist/components/EchoformInfoButton.js +60 -0
- package/dist/components/EchoformInfoButton.js.map +1 -0
- package/dist/components/EchoformInfoModal.d.ts +63 -0
- package/dist/components/EchoformInfoModal.d.ts.map +1 -0
- package/dist/components/EchoformInfoModal.js +347 -0
- package/dist/components/EchoformInfoModal.js.map +1 -0
- package/dist/echoColors.d.ts +58 -0
- package/dist/echoColors.d.ts.map +1 -0
- package/dist/echoColors.js +94 -0
- package/dist/echoColors.js.map +1 -0
- package/dist/index.d.ts +71 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +86 -4
- package/dist/index.js.map +1 -1
- package/dist/theme.d.ts +48 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +51 -0
- package/dist/theme.js.map +1 -0
- package/dist/titleRarity.d.ts +19 -0
- package/dist/titleRarity.d.ts.map +1 -0
- package/dist/titleRarity.js +46 -0
- package/dist/titleRarity.js.map +1 -0
- package/package.json +6 -5
- package/dist/components/EchoUITest.d.ts +0 -14
- package/dist/components/EchoUITest.d.ts.map +0 -1
- package/dist/components/EchoUITest.js +0 -53
- package/dist/components/EchoUITest.js.map +0 -1
|
@@ -0,0 +1,913 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* EchoSyncPanel
|
|
4
|
+
*
|
|
5
|
+
* Presentation-only Echo Profile Sync panel.
|
|
6
|
+
* Extracted from components/feature/SyncPanel.tsx in the Echo UI Reference.
|
|
7
|
+
*
|
|
8
|
+
* ARCHITECTURE
|
|
9
|
+
* ────────────
|
|
10
|
+
* This component is PURE PRESENTATION. It owns:
|
|
11
|
+
* • Rendering all sync states (signed-out, loading, active, result)
|
|
12
|
+
* • Deriving button/badge display state from supplied props
|
|
13
|
+
* • Accessibility labels, roles, live regions
|
|
14
|
+
*
|
|
15
|
+
* It does NOT own:
|
|
16
|
+
* • Authentication
|
|
17
|
+
* • useFocusEffect
|
|
18
|
+
* • Server/status fetching
|
|
19
|
+
* • Upload / download operations
|
|
20
|
+
* • Reconciliation planning
|
|
21
|
+
* • Guest-partition detection or cleanup
|
|
22
|
+
* • Sync queue operations
|
|
23
|
+
* • Storage operations
|
|
24
|
+
* • Supabase / EchoBrain RPC calls
|
|
25
|
+
*
|
|
26
|
+
* All host-owned operations are supplied via callback props.
|
|
27
|
+
*
|
|
28
|
+
* DATA LABEL
|
|
29
|
+
* ──────────
|
|
30
|
+
* The `dataLabel` prop names the records being synchronized in ALL visible
|
|
31
|
+
* and accessibility wording. Examples: "Demo App Data", "Rig Vault Data".
|
|
32
|
+
* There is no hard-coded "Demo App", "Rig Vault", or "Guest Vault" in this
|
|
33
|
+
* package component.
|
|
34
|
+
*
|
|
35
|
+
* GUEST ADOPTION
|
|
36
|
+
* ──────────────
|
|
37
|
+
* All wording for the guest-adoption card is supplied through the optional
|
|
38
|
+
* `guestAdoption` prop. When neither `guestAdoption` nor `onAdoptGuest` is
|
|
39
|
+
* supplied the section is simply not rendered.
|
|
40
|
+
*
|
|
41
|
+
* DEVELOPMENT CONTROLS
|
|
42
|
+
* ────────────────────
|
|
43
|
+
* `showDevelopmentControls` replaces `__DEV__` so the host decides when
|
|
44
|
+
* dev-only UI (Force Sync, raw conflict JSON) is visible.
|
|
45
|
+
*
|
|
46
|
+
* SIGNED-OUT STATE
|
|
47
|
+
* ────────────────
|
|
48
|
+
* Deliberately names EchoBrain in the signed-out message:
|
|
49
|
+
* "Echo Profile Sync requires an Echo Profile.
|
|
50
|
+
* Sign in to enable upload to EchoBrain."
|
|
51
|
+
* This is an intentional product-level decision; EchoBrain is the public
|
|
52
|
+
* cloud sync service name.
|
|
53
|
+
*
|
|
54
|
+
* No Expo Router, no auth context, no services, no AsyncStorage, no Supabase,
|
|
55
|
+
* no @/ aliases.
|
|
56
|
+
* Canonical shared component for @echoform/echo-ui.
|
|
57
|
+
*/
|
|
58
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
59
|
+
if (k2 === undefined) k2 = k;
|
|
60
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
61
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
62
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
63
|
+
}
|
|
64
|
+
Object.defineProperty(o, k2, desc);
|
|
65
|
+
}) : (function(o, m, k, k2) {
|
|
66
|
+
if (k2 === undefined) k2 = k;
|
|
67
|
+
o[k2] = m[k];
|
|
68
|
+
}));
|
|
69
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
70
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
71
|
+
}) : function(o, v) {
|
|
72
|
+
o["default"] = v;
|
|
73
|
+
});
|
|
74
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
75
|
+
var ownKeys = function(o) {
|
|
76
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
77
|
+
var ar = [];
|
|
78
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
79
|
+
return ar;
|
|
80
|
+
};
|
|
81
|
+
return ownKeys(o);
|
|
82
|
+
};
|
|
83
|
+
return function (mod) {
|
|
84
|
+
if (mod && mod.__esModule) return mod;
|
|
85
|
+
var result = {};
|
|
86
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
87
|
+
__setModuleDefault(result, mod);
|
|
88
|
+
return result;
|
|
89
|
+
};
|
|
90
|
+
})();
|
|
91
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
92
|
+
exports.EchoSyncPanel = EchoSyncPanel;
|
|
93
|
+
const react_1 = __importStar(require("react"));
|
|
94
|
+
const react_native_1 = require("react-native");
|
|
95
|
+
const vector_icons_1 = require("@expo/vector-icons");
|
|
96
|
+
const theme_1 = require("../theme");
|
|
97
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
98
|
+
// Internal sub-components
|
|
99
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
100
|
+
function StatusBadge({ count, label, color, }) {
|
|
101
|
+
return (<react_native_1.View style={[
|
|
102
|
+
styles.badge,
|
|
103
|
+
{ borderColor: color + '44', backgroundColor: color + '15' },
|
|
104
|
+
]} accessible accessibilityLabel={`${count} ${label}`}>
|
|
105
|
+
<react_native_1.Text style={[styles.badgeCount, { color }]}>{count}</react_native_1.Text>
|
|
106
|
+
<react_native_1.Text style={[styles.badgeLabel, { color: color + 'CC' }]}>{label}</react_native_1.Text>
|
|
107
|
+
</react_native_1.View>);
|
|
108
|
+
}
|
|
109
|
+
function ConflictDetailRow({ conflict, showRaw, }) {
|
|
110
|
+
var _a, _b;
|
|
111
|
+
const [rawExpanded, setRawExpanded] = (0, react_1.useState)(false);
|
|
112
|
+
return (<react_native_1.View style={styles.conflictBlock}>
|
|
113
|
+
<react_native_1.View style={styles.detailRow}>
|
|
114
|
+
<vector_icons_1.MaterialIcons name="warning-amber" size={13} color={theme_1.COLORS.warning}/>
|
|
115
|
+
<react_native_1.Text style={[styles.detailText, { color: theme_1.COLORS.warning, fontWeight: '700' }]} selectable>
|
|
116
|
+
CONFLICT: {conflict.reason}
|
|
117
|
+
</react_native_1.Text>
|
|
118
|
+
</react_native_1.View>
|
|
119
|
+
<react_native_1.View style={styles.conflictField}>
|
|
120
|
+
<react_native_1.Text style={styles.conflictLabel}>record_id</react_native_1.Text>
|
|
121
|
+
<react_native_1.Text style={styles.conflictValue} selectable>
|
|
122
|
+
{conflict.id}
|
|
123
|
+
</react_native_1.Text>
|
|
124
|
+
</react_native_1.View>
|
|
125
|
+
{conflict.presetId ? (<react_native_1.View style={styles.conflictField}>
|
|
126
|
+
<react_native_1.Text style={styles.conflictLabel}>preset_id</react_native_1.Text>
|
|
127
|
+
<react_native_1.Text style={styles.conflictValue} selectable>
|
|
128
|
+
{conflict.presetId}
|
|
129
|
+
</react_native_1.Text>
|
|
130
|
+
</react_native_1.View>) : null}
|
|
131
|
+
{conflict.controlId ? (<react_native_1.View style={styles.conflictField}>
|
|
132
|
+
<react_native_1.Text style={styles.conflictLabel}>control_id</react_native_1.Text>
|
|
133
|
+
<react_native_1.Text style={styles.conflictValue} selectable>
|
|
134
|
+
{conflict.controlId}
|
|
135
|
+
</react_native_1.Text>
|
|
136
|
+
</react_native_1.View>) : null}
|
|
137
|
+
{conflict.serverRevision !== undefined ||
|
|
138
|
+
conflict.submittedBaseRevision !== undefined ? (<react_native_1.View style={styles.conflictField}>
|
|
139
|
+
<react_native_1.Text style={styles.conflictLabel}>revisions</react_native_1.Text>
|
|
140
|
+
<react_native_1.Text style={styles.conflictValue} selectable>
|
|
141
|
+
{`local sent: ${(_a = conflict.submittedBaseRevision) !== null && _a !== void 0 ? _a : '?'} · server has: ${(_b = conflict.serverRevision) !== null && _b !== void 0 ? _b : '?'}`}
|
|
142
|
+
</react_native_1.Text>
|
|
143
|
+
</react_native_1.View>) : null}
|
|
144
|
+
{showRaw && conflict.raw ? (<react_native_1.Pressable onPress={() => setRawExpanded(v => !v)} style={styles.rawToggle} accessibilityRole="button" accessibilityLabel={rawExpanded ? 'Collapse raw conflict JSON' : 'Expand raw conflict JSON'} accessibilityState={{ expanded: rawExpanded }} hitSlop={6}>
|
|
145
|
+
<vector_icons_1.MaterialIcons name={rawExpanded ? 'expand-less' : 'expand-more'} size={12} color={theme_1.COLORS.textMuted}/>
|
|
146
|
+
<react_native_1.Text style={styles.rawToggleLabel}>raw JSON</react_native_1.Text>
|
|
147
|
+
</react_native_1.Pressable>) : null}
|
|
148
|
+
{showRaw && rawExpanded && conflict.raw ? (<react_native_1.Text style={styles.rawJson} selectable>
|
|
149
|
+
{conflict.raw}
|
|
150
|
+
</react_native_1.Text>) : null}
|
|
151
|
+
</react_native_1.View>);
|
|
152
|
+
}
|
|
153
|
+
function EntityRow({ entity, uploaded, skipped, conflicts, failed, errors, conflictDetails, firstError, showDevelopmentControls, }) {
|
|
154
|
+
const [expanded, setExpanded] = (0, react_1.useState)(false);
|
|
155
|
+
const hasDetails = Object.keys(errors).length > 0 ||
|
|
156
|
+
conflictDetails.length > 0 ||
|
|
157
|
+
Boolean(firstError);
|
|
158
|
+
const total = uploaded + skipped + conflicts + failed;
|
|
159
|
+
if (total === 0 && !hasDetails)
|
|
160
|
+
return null;
|
|
161
|
+
const label = entity.replace(/_/g, ' ');
|
|
162
|
+
return (<react_native_1.View style={styles.entityRow}>
|
|
163
|
+
<react_native_1.Pressable onPress={hasDetails ? () => setExpanded(v => !v) : undefined} style={({ pressed }) => [
|
|
164
|
+
styles.entityRowHeader,
|
|
165
|
+
pressed && hasDetails && { opacity: 0.75 },
|
|
166
|
+
]} accessibilityLabel={`${label}: ${uploaded} uploaded, ${skipped} skipped, ${conflicts} conflicts, ${failed} failed`} accessibilityRole={hasDetails ? 'button' : 'text'} accessibilityHint={hasDetails
|
|
167
|
+
? `Double tap to ${expanded ? 'collapse' : 'expand'} error details`
|
|
168
|
+
: undefined} accessibilityState={hasDetails ? { expanded } : undefined}>
|
|
169
|
+
<react_native_1.Text style={styles.entityName}>{label}</react_native_1.Text>
|
|
170
|
+
<react_native_1.View style={styles.entityCounts}>
|
|
171
|
+
{uploaded > 0 ? (<react_native_1.Text style={[styles.entityCount, { color: theme_1.COLORS.neonGreen }]}>
|
|
172
|
+
↑{uploaded}
|
|
173
|
+
</react_native_1.Text>) : null}
|
|
174
|
+
{skipped > 0 ? (<react_native_1.Text style={[styles.entityCount, { color: theme_1.COLORS.textMuted }]}>
|
|
175
|
+
={skipped}
|
|
176
|
+
</react_native_1.Text>) : null}
|
|
177
|
+
{conflicts > 0 ? (<react_native_1.Text style={[styles.entityCount, { color: theme_1.COLORS.warning }]}>
|
|
178
|
+
⚡{conflicts}
|
|
179
|
+
</react_native_1.Text>) : null}
|
|
180
|
+
{failed > 0 ? (<react_native_1.Text style={[styles.entityCount, { color: theme_1.COLORS.error }]}>
|
|
181
|
+
✗{failed}
|
|
182
|
+
</react_native_1.Text>) : null}
|
|
183
|
+
</react_native_1.View>
|
|
184
|
+
{hasDetails ? (<vector_icons_1.MaterialIcons name={expanded ? 'expand-less' : 'expand-more'} size={18} color={theme_1.COLORS.textMuted}/>) : null}
|
|
185
|
+
</react_native_1.Pressable>
|
|
186
|
+
|
|
187
|
+
{expanded && hasDetails ? (<react_native_1.View style={styles.entityDetails} accessibilityLiveRegion="polite">
|
|
188
|
+
{firstError ? (<react_native_1.View style={styles.detailRow}>
|
|
189
|
+
<vector_icons_1.MaterialIcons name="bug-report" size={13} color={theme_1.COLORS.error}/>
|
|
190
|
+
<react_native_1.Text style={[styles.detailText, { color: theme_1.COLORS.error }]} selectable>
|
|
191
|
+
RPC error: {firstError}
|
|
192
|
+
</react_native_1.Text>
|
|
193
|
+
</react_native_1.View>) : null}
|
|
194
|
+
{Object.entries(errors).map(([id, msg]) => (<react_native_1.View key={id} style={styles.detailRow}>
|
|
195
|
+
<vector_icons_1.MaterialIcons name="error-outline" size={13} color={theme_1.COLORS.error}/>
|
|
196
|
+
<react_native_1.Text style={styles.detailText} selectable>
|
|
197
|
+
<react_native_1.Text style={styles.detailId}>{id.slice(0, 8)}…</react_native_1.Text>: {msg}
|
|
198
|
+
</react_native_1.Text>
|
|
199
|
+
</react_native_1.View>))}
|
|
200
|
+
{conflictDetails.map(c => (<ConflictDetailRow key={c.id} conflict={c} showRaw={showDevelopmentControls}/>))}
|
|
201
|
+
</react_native_1.View>) : null}
|
|
202
|
+
</react_native_1.View>);
|
|
203
|
+
}
|
|
204
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
205
|
+
// Result card helpers
|
|
206
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
207
|
+
function resultCardStyle(outcome) {
|
|
208
|
+
switch (outcome) {
|
|
209
|
+
case 'adoption_complete':
|
|
210
|
+
case 'complete':
|
|
211
|
+
case 'empty':
|
|
212
|
+
return styles.resultCardSuccess;
|
|
213
|
+
case 'partial':
|
|
214
|
+
case 'unable_to_verify':
|
|
215
|
+
return styles.resultCardWarning;
|
|
216
|
+
case 'failed':
|
|
217
|
+
case 'fatal':
|
|
218
|
+
return styles.resultCardError;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function resultIconName(outcome) {
|
|
222
|
+
switch (outcome) {
|
|
223
|
+
case 'adoption_complete':
|
|
224
|
+
case 'complete':
|
|
225
|
+
case 'empty':
|
|
226
|
+
return 'check-circle-outline';
|
|
227
|
+
case 'partial':
|
|
228
|
+
case 'unable_to_verify':
|
|
229
|
+
return 'warning-amber';
|
|
230
|
+
case 'failed':
|
|
231
|
+
case 'fatal':
|
|
232
|
+
return 'error-outline';
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function resultIconColor(outcome) {
|
|
236
|
+
switch (outcome) {
|
|
237
|
+
case 'adoption_complete':
|
|
238
|
+
case 'complete':
|
|
239
|
+
case 'empty':
|
|
240
|
+
return theme_1.COLORS.neonGreen;
|
|
241
|
+
case 'partial':
|
|
242
|
+
case 'unable_to_verify':
|
|
243
|
+
return theme_1.COLORS.warning;
|
|
244
|
+
case 'failed':
|
|
245
|
+
case 'fatal':
|
|
246
|
+
return theme_1.COLORS.error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function buildResultHeadline(result, dataLabel) {
|
|
250
|
+
switch (result.outcome) {
|
|
251
|
+
case 'adoption_complete':
|
|
252
|
+
return `${dataLabel} added to Echo Profile Sync.`;
|
|
253
|
+
case 'complete':
|
|
254
|
+
return `${dataLabel} Sync Complete`;
|
|
255
|
+
case 'empty':
|
|
256
|
+
return `${dataLabel} Up to Date`;
|
|
257
|
+
case 'partial':
|
|
258
|
+
return `${dataLabel} Sync Partially Completed`;
|
|
259
|
+
case 'failed':
|
|
260
|
+
return `${dataLabel} Sync Failed`;
|
|
261
|
+
case 'fatal':
|
|
262
|
+
return result.fatalError
|
|
263
|
+
? `${dataLabel} Sync Failed: ${result.fatalError}`
|
|
264
|
+
: `${dataLabel} Sync Failed`;
|
|
265
|
+
case 'unable_to_verify':
|
|
266
|
+
return `Unable to Verify ${dataLabel} Sync`;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function buildResultA11yLabel(result, dataLabel) {
|
|
270
|
+
var _a, _b;
|
|
271
|
+
const remaining = result.remainingUnsynced && result.remainingUnsynced > 0
|
|
272
|
+
? ` ${result.remainingUnsynced} records still need synchronization.`
|
|
273
|
+
: '';
|
|
274
|
+
switch (result.outcome) {
|
|
275
|
+
case 'adoption_complete':
|
|
276
|
+
return `${dataLabel} successfully added to Echo Profile Sync.${remaining}`;
|
|
277
|
+
case 'complete':
|
|
278
|
+
return `${dataLabel} sync complete. ${result.totalUploaded} uploaded, ${result.totalSkipped} already synced.${remaining}`;
|
|
279
|
+
case 'empty':
|
|
280
|
+
return `${dataLabel} is already up to date.${remaining}`;
|
|
281
|
+
case 'partial':
|
|
282
|
+
return `${dataLabel} sync partially completed. ${result.totalUploaded} uploaded, ${result.totalFailed} failed.${remaining}`;
|
|
283
|
+
case 'failed':
|
|
284
|
+
return `${dataLabel} sync failed. ${(_a = result.firstError) !== null && _a !== void 0 ? _a : 'See error details below.'}${remaining}`;
|
|
285
|
+
case 'fatal':
|
|
286
|
+
return `${dataLabel} sync failed. ${(_b = result.fatalError) !== null && _b !== void 0 ? _b : 'See error details below.'}${remaining}`;
|
|
287
|
+
case 'unable_to_verify':
|
|
288
|
+
return `Unable to verify ${dataLabel} sync.${remaining}`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
292
|
+
// Main component
|
|
293
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
294
|
+
function EchoSyncPanel({ hasEchoProfile, dataLabel, status, statusLoading = false, syncing = false, guestAdoption, result, showDevelopmentControls = false, onRefresh, onSync, onForceSync, onAdoptGuest, onDismissResult, }) {
|
|
295
|
+
var _a, _b, _c, _d;
|
|
296
|
+
// ── Signed-out state ────────────────────────────────────────────────────
|
|
297
|
+
if (!hasEchoProfile) {
|
|
298
|
+
return (<react_native_1.View style={styles.guestNotice} accessible accessibilityRole="text" accessibilityLabel="Echo Profile Sync requires an Echo Profile. Sign in or create an Echo Profile to enable upload to EchoBrain.">
|
|
299
|
+
<vector_icons_1.MaterialIcons name="cloud-off" size={20} color={theme_1.COLORS.textMuted}/>
|
|
300
|
+
<react_native_1.Text style={styles.guestNoticeText}>
|
|
301
|
+
{'Echo Profile Sync requires an Echo Profile.\n'}
|
|
302
|
+
{'Sign in to enable upload to EchoBrain.'}
|
|
303
|
+
</react_native_1.Text>
|
|
304
|
+
</react_native_1.View>);
|
|
305
|
+
}
|
|
306
|
+
// ── Derived display state ───────────────────────────────────────────────
|
|
307
|
+
const unsyncedCount = (_a = status === null || status === void 0 ? void 0 : status.unsynced) !== null && _a !== void 0 ? _a : null;
|
|
308
|
+
const needsSync = unsyncedCount !== null && unsyncedCount > 0;
|
|
309
|
+
// canVerify: only true when we have a confirmed, reachable server response.
|
|
310
|
+
// status === null (no fetch yet) is treated as unable to verify, not reachable.
|
|
311
|
+
const canVerify = status !== null && status.serverReachable;
|
|
312
|
+
const syncDisabled = syncing || (canVerify && !needsSync);
|
|
313
|
+
// ── Refresh handler with accessibility announcement ─────────────────────
|
|
314
|
+
const handleRefresh = async () => {
|
|
315
|
+
if (statusLoading)
|
|
316
|
+
return;
|
|
317
|
+
await onRefresh();
|
|
318
|
+
react_native_1.AccessibilityInfo.announceForAccessibility(`${dataLabel} sync status refreshed.`);
|
|
319
|
+
};
|
|
320
|
+
// ── Render ───────────────────────────────────────────────────────────────
|
|
321
|
+
return (<react_native_1.View style={styles.root}>
|
|
322
|
+
|
|
323
|
+
{/* ── Scope note ──────────────────────────────────────────────────── */}
|
|
324
|
+
<react_native_1.Text style={styles.scopeNote} accessible accessibilityRole="text" accessibilityLabel={`This section shows and syncs ${dataLabel} only.`}>
|
|
325
|
+
{`This section shows and syncs ${dataLabel} only.`}
|
|
326
|
+
</react_native_1.Text>
|
|
327
|
+
|
|
328
|
+
{/* ── Status summary row ──────────────────────────────────────────── */}
|
|
329
|
+
<react_native_1.View style={styles.statusRow} accessible accessibilityLabel={status
|
|
330
|
+
? `${dataLabel} sync status: ${status.unsynced} unsynced, ${status.synced} synced` +
|
|
331
|
+
(!status.serverReachable ? ', Echo Profile Sync unavailable' : '')
|
|
332
|
+
: `Loading ${dataLabel} sync status`}>
|
|
333
|
+
{statusLoading ? (<react_native_1.ActivityIndicator size="small" color={theme_1.COLORS.neonCyan} style={{ marginRight: theme_1.SPACING.sm }}/>) : status ? (<>
|
|
334
|
+
<StatusBadge count={status.unsynced} label="unsynced" color={status.unsynced > 0 ? theme_1.COLORS.neonCyan : theme_1.COLORS.textMuted}/>
|
|
335
|
+
<StatusBadge count={status.synced} label="synced" color={theme_1.COLORS.neonGreen}/>
|
|
336
|
+
{!status.serverReachable ? (<react_native_1.View style={styles.offlineTag} accessible accessibilityLabel={`Echo Profile Sync unavailable — ${dataLabel} sync status unverified`}>
|
|
337
|
+
<vector_icons_1.MaterialIcons name="cloud-off" size={11} color={theme_1.COLORS.warning}/>
|
|
338
|
+
<react_native_1.Text style={[styles.offlineTagText, { color: theme_1.COLORS.warning }]}>
|
|
339
|
+
offline
|
|
340
|
+
</react_native_1.Text>
|
|
341
|
+
</react_native_1.View>) : null}
|
|
342
|
+
</>) : null}
|
|
343
|
+
|
|
344
|
+
<react_native_1.Pressable onPress={handleRefresh} disabled={statusLoading} style={({ pressed }) => [
|
|
345
|
+
styles.refreshBtn,
|
|
346
|
+
pressed && !statusLoading && { opacity: 0.7 },
|
|
347
|
+
]} accessibilityLabel={`Refresh ${dataLabel} Sync Status`} accessibilityRole="button" accessibilityHint={`Checks for changes and updates the ${dataLabel} sync status.`} accessibilityState={{ busy: statusLoading, disabled: statusLoading }} hitSlop={12}>
|
|
348
|
+
{statusLoading ? (<react_native_1.ActivityIndicator size="small" color={theme_1.COLORS.neonCyan}/>) : (<vector_icons_1.MaterialIcons name="refresh" size={20} color={theme_1.COLORS.neonCyan} importantForAccessibility="no" accessibilityElementsHidden/>)}
|
|
349
|
+
</react_native_1.Pressable>
|
|
350
|
+
</react_native_1.View>
|
|
351
|
+
|
|
352
|
+
{/* ── Main sync button ─────────────────────────────────────────────── */}
|
|
353
|
+
<react_native_1.Pressable onPress={() => onSync()} disabled={syncDisabled} style={({ pressed }) => [
|
|
354
|
+
styles.uploadBtn,
|
|
355
|
+
pressed && { opacity: 0.8 },
|
|
356
|
+
needsSync || !canVerify ? styles.uploadBtnActive : styles.uploadBtnDisabled,
|
|
357
|
+
!canVerify && !syncing ? styles.uploadBtnWarning : null,
|
|
358
|
+
]} accessibilityLabel={syncing
|
|
359
|
+
? `Syncing ${dataLabel}, please wait`
|
|
360
|
+
: !canVerify
|
|
361
|
+
? `Unable to verify ${dataLabel} sync — tap to retry`
|
|
362
|
+
: !needsSync
|
|
363
|
+
? `${dataLabel} is up to date`
|
|
364
|
+
: `Sync ${dataLabel} — ${unsyncedCount} record${unsyncedCount !== 1 ? 's' : ''} need synchronization`} accessibilityRole="button" accessibilityHint={!canVerify
|
|
365
|
+
? `Echo Profile Sync could not be reached to verify ${dataLabel} sync state. Tap to retry.`
|
|
366
|
+
: needsSync
|
|
367
|
+
? `Uploads pending ${dataLabel} changes and downloads newer ${dataLabel} cloud records through Echo Profile Sync`
|
|
368
|
+
: `No ${dataLabel} changes need to be synchronized right now`} accessibilityState={{ busy: syncing, disabled: syncDisabled }}>
|
|
369
|
+
{syncing ? (<react_native_1.ActivityIndicator size="small" color={theme_1.COLORS.neonGreen}/>) : (<vector_icons_1.MaterialIcons name={!canVerify ? 'cloud-off' : needsSync ? 'sync' : 'cloud-done'} size={20} color={!canVerify
|
|
370
|
+
? theme_1.COLORS.warning
|
|
371
|
+
: needsSync
|
|
372
|
+
? theme_1.COLORS.neonGreen
|
|
373
|
+
: theme_1.COLORS.textMuted}/>)}
|
|
374
|
+
<react_native_1.Text style={[
|
|
375
|
+
styles.uploadBtnText,
|
|
376
|
+
canVerify && !needsSync ? styles.uploadBtnTextDisabled : null,
|
|
377
|
+
!canVerify && !syncing ? styles.uploadBtnTextWarning : null,
|
|
378
|
+
]}>
|
|
379
|
+
{syncing
|
|
380
|
+
? `Syncing ${dataLabel}\u2026`
|
|
381
|
+
: !canVerify
|
|
382
|
+
? `Unable to Verify ${dataLabel} Sync`
|
|
383
|
+
: needsSync
|
|
384
|
+
? `Sync ${dataLabel} (${unsyncedCount})`
|
|
385
|
+
: `${dataLabel} Up to Date`}
|
|
386
|
+
</react_native_1.Text>
|
|
387
|
+
</react_native_1.Pressable>
|
|
388
|
+
|
|
389
|
+
{/* ── Force Sync (dev only, host-controlled) ─────────────────────── */}
|
|
390
|
+
{showDevelopmentControls && onForceSync ? (<react_native_1.Pressable onPress={() => onForceSync()} disabled={syncing} style={({ pressed }) => [styles.forceBtn, pressed && { opacity: 0.7 }]} accessibilityLabel="Force full re-sync — upload all then download" accessibilityRole="button" accessibilityHint="Development only: re-uploads all local records regardless of sync status, then downloads" accessibilityState={{ busy: syncing }}>
|
|
391
|
+
<vector_icons_1.MaterialIcons name="refresh" size={14} color={theme_1.COLORS.textMuted}/>
|
|
392
|
+
<react_native_1.Text style={styles.forceBtnText}>Force full re-sync (dev)</react_native_1.Text>
|
|
393
|
+
</react_native_1.Pressable>) : null}
|
|
394
|
+
|
|
395
|
+
{/* ── Guest-data adoption ─────────────────────────────────────────── */}
|
|
396
|
+
{guestAdoption && onAdoptGuest ? (<react_native_1.View style={styles.guestAdoptSection}>
|
|
397
|
+
<react_native_1.View style={styles.dividerRow} accessible={false}>
|
|
398
|
+
<react_native_1.View style={styles.dividerLine}/>
|
|
399
|
+
<react_native_1.Text style={styles.dividerLabel}>OR</react_native_1.Text>
|
|
400
|
+
<react_native_1.View style={styles.dividerLine}/>
|
|
401
|
+
</react_native_1.View>
|
|
402
|
+
|
|
403
|
+
<react_native_1.View style={styles.guestAdoptCard}>
|
|
404
|
+
<react_native_1.View style={styles.guestAdoptHeader}>
|
|
405
|
+
<vector_icons_1.MaterialIcons name="account-circle" size={15} color={theme_1.COLORS.neonCyan}/>
|
|
406
|
+
<react_native_1.Text style={styles.guestAdoptTitle} accessibilityRole="header">
|
|
407
|
+
{guestAdoption.title}
|
|
408
|
+
</react_native_1.Text>
|
|
409
|
+
</react_native_1.View>
|
|
410
|
+
|
|
411
|
+
<react_native_1.Text style={styles.guestAdoptSubtitle}>
|
|
412
|
+
{guestAdoption.description}
|
|
413
|
+
</react_native_1.Text>
|
|
414
|
+
|
|
415
|
+
<react_native_1.Pressable onPress={() => onAdoptGuest()} disabled={syncing} style={({ pressed }) => [
|
|
416
|
+
styles.guestAdoptBtn,
|
|
417
|
+
pressed && !syncing && { opacity: 0.8 },
|
|
418
|
+
syncing && styles.uploadBtnDisabled,
|
|
419
|
+
]} accessibilityLabel={syncing
|
|
420
|
+
? `Adding ${guestAdoption.title}, please wait`
|
|
421
|
+
: guestAdoption.accessibilityLabel} accessibilityRole="button" accessibilityHint={guestAdoption.accessibilityHint} accessibilityState={{ busy: syncing, disabled: syncing }}>
|
|
422
|
+
{syncing ? (<react_native_1.ActivityIndicator size="small" color={theme_1.COLORS.neonCyan}/>) : (<vector_icons_1.MaterialIcons name="cloud-upload" size={20} color={theme_1.COLORS.neonCyan}/>)}
|
|
423
|
+
<react_native_1.Text style={styles.guestAdoptBtnText}>
|
|
424
|
+
{syncing
|
|
425
|
+
? ((_b = guestAdoption.loadingLabel) !== null && _b !== void 0 ? _b : `${guestAdoption.buttonLabel}\u2026`)
|
|
426
|
+
: guestAdoption.buttonLabel}
|
|
427
|
+
</react_native_1.Text>
|
|
428
|
+
</react_native_1.Pressable>
|
|
429
|
+
|
|
430
|
+
<react_native_1.View style={styles.guestAdoptNote} accessible accessibilityRole="text" accessibilityLabel={guestAdoption.note}>
|
|
431
|
+
<vector_icons_1.MaterialIcons name="info-outline" size={13} color={theme_1.COLORS.neonBlue}/>
|
|
432
|
+
<react_native_1.Text style={styles.guestAdoptNoteText}>{guestAdoption.note}</react_native_1.Text>
|
|
433
|
+
</react_native_1.View>
|
|
434
|
+
</react_native_1.View>
|
|
435
|
+
</react_native_1.View>) : null}
|
|
436
|
+
|
|
437
|
+
{/* ── Sync result card ─────────────────────────────────────────────── */}
|
|
438
|
+
{result ? (<react_native_1.View style={[styles.resultCard, resultCardStyle(result.outcome)]} accessible accessibilityRole="text" accessibilityLiveRegion="polite" accessibilityLabel={buildResultA11yLabel(result, dataLabel)}>
|
|
439
|
+
{/* Headline */}
|
|
440
|
+
<react_native_1.View style={styles.resultHeader}>
|
|
441
|
+
<vector_icons_1.MaterialIcons name={resultIconName(result.outcome)} size={18} color={resultIconColor(result.outcome)}/>
|
|
442
|
+
<react_native_1.Text style={[styles.resultTitle, { color: resultIconColor(result.outcome) }]}>
|
|
443
|
+
{buildResultHeadline(result, dataLabel)}
|
|
444
|
+
</react_native_1.Text>
|
|
445
|
+
</react_native_1.View>
|
|
446
|
+
|
|
447
|
+
{/* First RPC error */}
|
|
448
|
+
{(result.outcome === 'failed' || result.outcome === 'partial') &&
|
|
449
|
+
result.firstError ? (<react_native_1.View style={styles.rpcErrorBox}>
|
|
450
|
+
<vector_icons_1.MaterialIcons name="bug-report" size={13} color={theme_1.COLORS.error}/>
|
|
451
|
+
<react_native_1.Text style={styles.rpcErrorText} selectable>
|
|
452
|
+
{result.firstError}
|
|
453
|
+
</react_native_1.Text>
|
|
454
|
+
</react_native_1.View>) : null}
|
|
455
|
+
|
|
456
|
+
{/* Counts summary */}
|
|
457
|
+
{result.outcome !== 'fatal' ? (<react_native_1.Text style={styles.resultLegend}>
|
|
458
|
+
{`↑${result.totalUploaded} uploaded`}
|
|
459
|
+
{result.totalSkipped > 0
|
|
460
|
+
? ` =${result.totalSkipped} already synced`
|
|
461
|
+
: ''}
|
|
462
|
+
{result.totalConflicts > 0
|
|
463
|
+
? ` ⚡${result.totalConflicts} conflict`
|
|
464
|
+
: ''}
|
|
465
|
+
{result.totalFailed > 0 ? ` ✗${result.totalFailed} failed` : ''}
|
|
466
|
+
</react_native_1.Text>) : null}
|
|
467
|
+
|
|
468
|
+
{/* Source partition (dev diagnostic) */}
|
|
469
|
+
{showDevelopmentControls && result.sourcePartition ? (<react_native_1.Text style={[styles.resultLegend, { color: theme_1.COLORS.neonCyan + '99' }]}>
|
|
470
|
+
Source partition: {result.sourcePartition}
|
|
471
|
+
</react_native_1.Text>) : null}
|
|
472
|
+
|
|
473
|
+
{/* Remaining unsynced */}
|
|
474
|
+
{result.remainingUnsynced && result.remainingUnsynced > 0 ? (<react_native_1.Text style={[styles.resultLegend, { color: theme_1.COLORS.warning }]}>
|
|
475
|
+
{`${result.remainingUnsynced} record${result.remainingUnsynced !== 1 ? 's' : ''} still need synchronization`}
|
|
476
|
+
</react_native_1.Text>) : null}
|
|
477
|
+
|
|
478
|
+
{/* Image sync summary */}
|
|
479
|
+
{result.imageSummary && result.imageSummary.attempted > 0 ? (<react_native_1.View style={styles.imageSyncRow}>
|
|
480
|
+
<vector_icons_1.MaterialIcons name={result.imageSummary.failed > 0 ? 'broken-image' : 'photo-camera'} size={12} color={result.imageSummary.failed > 0
|
|
481
|
+
? theme_1.COLORS.error
|
|
482
|
+
: theme_1.COLORS.neonGreen}/>
|
|
483
|
+
<react_native_1.Text style={[styles.resultLegend, { flex: 1 }]}>
|
|
484
|
+
{`Photos: ${result.imageSummary.succeeded}/${result.imageSummary.attempted} uploaded`}
|
|
485
|
+
{result.imageSummary.failed > 0
|
|
486
|
+
? ` ✗${result.imageSummary.failed} failed`
|
|
487
|
+
: ''}
|
|
488
|
+
</react_native_1.Text>
|
|
489
|
+
</react_native_1.View>) : result.imageSummary && result.imageSummary.attempted === 0 ? (<react_native_1.View style={styles.imageSyncRow}>
|
|
490
|
+
<vector_icons_1.MaterialIcons name="photo-camera" size={12} color={theme_1.COLORS.textMuted}/>
|
|
491
|
+
<react_native_1.Text style={[styles.resultLegend, { flex: 1 }]}>
|
|
492
|
+
{'Photos: 0 eligible (no local photos found)'}
|
|
493
|
+
</react_native_1.Text>
|
|
494
|
+
</react_native_1.View>) : null}
|
|
495
|
+
|
|
496
|
+
{/* First image error */}
|
|
497
|
+
{((_c = result.imageSummary) === null || _c === void 0 ? void 0 : _c.failed) ? (<react_native_1.View style={styles.rpcErrorBox}>
|
|
498
|
+
<vector_icons_1.MaterialIcons name="broken-image" size={13} color={theme_1.COLORS.error}/>
|
|
499
|
+
<react_native_1.Text style={styles.rpcErrorText} selectable>
|
|
500
|
+
{`Photo error: ${(_d = result.imageSummary.firstError) !== null && _d !== void 0 ? _d : 'unknown'}${result.imageSummary.timeoutFailures
|
|
501
|
+
? ` (${result.imageSummary.timeoutFailures} timed out)`
|
|
502
|
+
: ''}${result.imageSummary.missingSourceFiles
|
|
503
|
+
? ` (${result.imageSummary.missingSourceFiles} missing local files)`
|
|
504
|
+
: ''}${result.imageSummary.storageFailures
|
|
505
|
+
? ` (${result.imageSummary.storageFailures} storage failures)`
|
|
506
|
+
: ''}${result.imageSummary.metaFailures
|
|
507
|
+
? ` (${result.imageSummary.metaFailures} metadata failures)`
|
|
508
|
+
: ''}`}
|
|
509
|
+
</react_native_1.Text>
|
|
510
|
+
</react_native_1.View>) : null}
|
|
511
|
+
|
|
512
|
+
{/* Per-entity breakdown */}
|
|
513
|
+
<react_native_1.View style={styles.entityList}>
|
|
514
|
+
{result.entities.map(e => (<EntityRow key={e.entity} entity={e.entity} uploaded={e.uploaded} skipped={e.skipped} conflicts={e.conflicts} failed={e.failed} errors={e.errors} conflictDetails={e.conflictDetails} firstError={e.firstError} showDevelopmentControls={showDevelopmentControls}/>))}
|
|
515
|
+
</react_native_1.View>
|
|
516
|
+
|
|
517
|
+
{/* Timestamp */}
|
|
518
|
+
<react_native_1.Text style={styles.resultTimestamp}>
|
|
519
|
+
{new Date(result.completedAt).toLocaleTimeString()}
|
|
520
|
+
</react_native_1.Text>
|
|
521
|
+
|
|
522
|
+
{/* Dismiss */}
|
|
523
|
+
<react_native_1.Pressable onPress={onDismissResult} accessibilityRole="button" accessibilityLabel="Dismiss sync result" accessibilityHint="Hides this sync result" style={({ pressed }) => [
|
|
524
|
+
styles.dismissResultButton,
|
|
525
|
+
pressed ? { opacity: 0.7 } : null,
|
|
526
|
+
]}>
|
|
527
|
+
<vector_icons_1.MaterialIcons name="close" size={18} color={theme_1.COLORS.textMuted}/>
|
|
528
|
+
<react_native_1.Text style={styles.dismissResultText}>Dismiss</react_native_1.Text>
|
|
529
|
+
</react_native_1.Pressable>
|
|
530
|
+
</react_native_1.View>) : null}
|
|
531
|
+
</react_native_1.View>);
|
|
532
|
+
}
|
|
533
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
534
|
+
// Styles
|
|
535
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
536
|
+
const styles = react_native_1.StyleSheet.create({
|
|
537
|
+
root: { gap: theme_1.SPACING.sm },
|
|
538
|
+
// Scope note
|
|
539
|
+
scopeNote: {
|
|
540
|
+
fontSize: theme_1.FONTS.xs,
|
|
541
|
+
color: theme_1.COLORS.textMuted,
|
|
542
|
+
lineHeight: 18,
|
|
543
|
+
marginBottom: theme_1.SPACING.xs,
|
|
544
|
+
},
|
|
545
|
+
// Guest notice (signed-out)
|
|
546
|
+
guestNotice: {
|
|
547
|
+
flexDirection: 'row',
|
|
548
|
+
alignItems: 'flex-start',
|
|
549
|
+
gap: theme_1.SPACING.sm,
|
|
550
|
+
backgroundColor: theme_1.COLORS.surfaceHigh,
|
|
551
|
+
borderRadius: theme_1.RADIUS.md,
|
|
552
|
+
borderWidth: 1,
|
|
553
|
+
borderColor: theme_1.COLORS.border,
|
|
554
|
+
padding: theme_1.SPACING.md,
|
|
555
|
+
},
|
|
556
|
+
guestNoticeText: {
|
|
557
|
+
flex: 1,
|
|
558
|
+
fontSize: theme_1.FONTS.sm,
|
|
559
|
+
color: theme_1.COLORS.textMuted,
|
|
560
|
+
lineHeight: 20,
|
|
561
|
+
},
|
|
562
|
+
// Status row
|
|
563
|
+
statusRow: {
|
|
564
|
+
flexDirection: 'row',
|
|
565
|
+
alignItems: 'center',
|
|
566
|
+
gap: theme_1.SPACING.sm,
|
|
567
|
+
flexWrap: 'wrap',
|
|
568
|
+
},
|
|
569
|
+
badge: {
|
|
570
|
+
flexDirection: 'row',
|
|
571
|
+
alignItems: 'center',
|
|
572
|
+
gap: 4,
|
|
573
|
+
borderRadius: 100,
|
|
574
|
+
borderWidth: 1,
|
|
575
|
+
paddingHorizontal: theme_1.SPACING.sm,
|
|
576
|
+
paddingVertical: 4,
|
|
577
|
+
},
|
|
578
|
+
badgeCount: {
|
|
579
|
+
fontSize: theme_1.FONTS.sm,
|
|
580
|
+
fontWeight: '700',
|
|
581
|
+
lineHeight: 18,
|
|
582
|
+
},
|
|
583
|
+
badgeLabel: {
|
|
584
|
+
fontSize: theme_1.FONTS.xs,
|
|
585
|
+
fontWeight: '500',
|
|
586
|
+
},
|
|
587
|
+
offlineTag: {
|
|
588
|
+
flexDirection: 'row',
|
|
589
|
+
alignItems: 'center',
|
|
590
|
+
gap: 3,
|
|
591
|
+
borderRadius: 100,
|
|
592
|
+
borderWidth: 1,
|
|
593
|
+
borderColor: theme_1.COLORS.warning + '55',
|
|
594
|
+
backgroundColor: theme_1.COLORS.warning + '12',
|
|
595
|
+
paddingHorizontal: theme_1.SPACING.sm,
|
|
596
|
+
paddingVertical: 4,
|
|
597
|
+
},
|
|
598
|
+
offlineTagText: {
|
|
599
|
+
fontSize: theme_1.FONTS.xs,
|
|
600
|
+
color: theme_1.COLORS.textMuted,
|
|
601
|
+
fontWeight: '500',
|
|
602
|
+
},
|
|
603
|
+
refreshBtn: {
|
|
604
|
+
width: 44,
|
|
605
|
+
height: 44,
|
|
606
|
+
alignItems: 'center',
|
|
607
|
+
justifyContent: 'center',
|
|
608
|
+
marginLeft: 'auto',
|
|
609
|
+
borderRadius: theme_1.RADIUS.sm,
|
|
610
|
+
},
|
|
611
|
+
// Main sync button
|
|
612
|
+
uploadBtn: {
|
|
613
|
+
flexDirection: 'row',
|
|
614
|
+
alignItems: 'center',
|
|
615
|
+
justifyContent: 'center',
|
|
616
|
+
gap: theme_1.SPACING.sm,
|
|
617
|
+
backgroundColor: theme_1.COLORS.neonGreen + '18',
|
|
618
|
+
borderRadius: theme_1.RADIUS.lg,
|
|
619
|
+
borderWidth: 1.5,
|
|
620
|
+
borderColor: theme_1.COLORS.neonGreen + '55',
|
|
621
|
+
paddingVertical: theme_1.SPACING.md,
|
|
622
|
+
minHeight: 52,
|
|
623
|
+
},
|
|
624
|
+
uploadBtnActive: {
|
|
625
|
+
opacity: 1,
|
|
626
|
+
},
|
|
627
|
+
uploadBtnDisabled: {
|
|
628
|
+
opacity: 0.45,
|
|
629
|
+
},
|
|
630
|
+
uploadBtnWarning: {
|
|
631
|
+
backgroundColor: theme_1.COLORS.warning + '12',
|
|
632
|
+
borderColor: theme_1.COLORS.warning + '55',
|
|
633
|
+
},
|
|
634
|
+
uploadBtnText: {
|
|
635
|
+
fontSize: theme_1.FONTS.md,
|
|
636
|
+
fontWeight: '700',
|
|
637
|
+
color: theme_1.COLORS.neonGreen,
|
|
638
|
+
letterSpacing: 0.2,
|
|
639
|
+
},
|
|
640
|
+
uploadBtnTextDisabled: {
|
|
641
|
+
color: theme_1.COLORS.textMuted,
|
|
642
|
+
},
|
|
643
|
+
uploadBtnTextWarning: {
|
|
644
|
+
color: theme_1.COLORS.warning,
|
|
645
|
+
},
|
|
646
|
+
// Force sync (dev)
|
|
647
|
+
forceBtn: {
|
|
648
|
+
flexDirection: 'row',
|
|
649
|
+
alignItems: 'center',
|
|
650
|
+
justifyContent: 'center',
|
|
651
|
+
gap: theme_1.SPACING.xs,
|
|
652
|
+
paddingVertical: theme_1.SPACING.xs,
|
|
653
|
+
minHeight: 36,
|
|
654
|
+
},
|
|
655
|
+
forceBtnText: {
|
|
656
|
+
fontSize: theme_1.FONTS.xs,
|
|
657
|
+
color: theme_1.COLORS.textMuted,
|
|
658
|
+
fontStyle: 'italic',
|
|
659
|
+
},
|
|
660
|
+
// Guest adoption
|
|
661
|
+
guestAdoptSection: {
|
|
662
|
+
gap: theme_1.SPACING.sm,
|
|
663
|
+
},
|
|
664
|
+
dividerRow: {
|
|
665
|
+
flexDirection: 'row',
|
|
666
|
+
alignItems: 'center',
|
|
667
|
+
gap: theme_1.SPACING.sm,
|
|
668
|
+
marginVertical: theme_1.SPACING.xs,
|
|
669
|
+
},
|
|
670
|
+
dividerLine: {
|
|
671
|
+
flex: 1,
|
|
672
|
+
height: 1,
|
|
673
|
+
backgroundColor: theme_1.COLORS.border,
|
|
674
|
+
},
|
|
675
|
+
dividerLabel: {
|
|
676
|
+
fontSize: theme_1.FONTS.xs,
|
|
677
|
+
fontWeight: '700',
|
|
678
|
+
color: theme_1.COLORS.textMuted,
|
|
679
|
+
letterSpacing: 1.2,
|
|
680
|
+
},
|
|
681
|
+
guestAdoptCard: {
|
|
682
|
+
backgroundColor: theme_1.COLORS.neonCyan + '0A',
|
|
683
|
+
borderRadius: theme_1.RADIUS.lg,
|
|
684
|
+
borderWidth: 1,
|
|
685
|
+
borderColor: theme_1.COLORS.neonCyan + '33',
|
|
686
|
+
padding: theme_1.SPACING.md,
|
|
687
|
+
gap: theme_1.SPACING.sm,
|
|
688
|
+
},
|
|
689
|
+
guestAdoptHeader: {
|
|
690
|
+
flexDirection: 'row',
|
|
691
|
+
alignItems: 'center',
|
|
692
|
+
gap: theme_1.SPACING.xs,
|
|
693
|
+
},
|
|
694
|
+
guestAdoptTitle: {
|
|
695
|
+
fontSize: theme_1.FONTS.sm,
|
|
696
|
+
fontWeight: '700',
|
|
697
|
+
color: theme_1.COLORS.neonCyan,
|
|
698
|
+
letterSpacing: 0.3,
|
|
699
|
+
},
|
|
700
|
+
guestAdoptSubtitle: {
|
|
701
|
+
fontSize: theme_1.FONTS.xs,
|
|
702
|
+
color: theme_1.COLORS.textSecondary,
|
|
703
|
+
lineHeight: 18,
|
|
704
|
+
},
|
|
705
|
+
guestAdoptBtn: {
|
|
706
|
+
flexDirection: 'row',
|
|
707
|
+
alignItems: 'center',
|
|
708
|
+
justifyContent: 'center',
|
|
709
|
+
gap: theme_1.SPACING.sm,
|
|
710
|
+
backgroundColor: theme_1.COLORS.neonCyan + '18',
|
|
711
|
+
borderRadius: theme_1.RADIUS.lg,
|
|
712
|
+
borderWidth: 1.5,
|
|
713
|
+
borderColor: theme_1.COLORS.neonCyan + '55',
|
|
714
|
+
paddingVertical: theme_1.SPACING.md,
|
|
715
|
+
minHeight: 52,
|
|
716
|
+
},
|
|
717
|
+
guestAdoptBtnText: {
|
|
718
|
+
fontSize: theme_1.FONTS.md,
|
|
719
|
+
fontWeight: '700',
|
|
720
|
+
color: theme_1.COLORS.neonCyan,
|
|
721
|
+
letterSpacing: 0.2,
|
|
722
|
+
},
|
|
723
|
+
guestAdoptNote: {
|
|
724
|
+
flexDirection: 'row',
|
|
725
|
+
alignItems: 'flex-start',
|
|
726
|
+
gap: theme_1.SPACING.xs,
|
|
727
|
+
},
|
|
728
|
+
guestAdoptNoteText: {
|
|
729
|
+
flex: 1,
|
|
730
|
+
fontSize: theme_1.FONTS.xs,
|
|
731
|
+
color: theme_1.COLORS.textMuted,
|
|
732
|
+
lineHeight: 18,
|
|
733
|
+
},
|
|
734
|
+
// Result card
|
|
735
|
+
resultCard: {
|
|
736
|
+
borderRadius: theme_1.RADIUS.lg,
|
|
737
|
+
borderWidth: 1,
|
|
738
|
+
padding: theme_1.SPACING.md,
|
|
739
|
+
gap: theme_1.SPACING.xs,
|
|
740
|
+
},
|
|
741
|
+
resultCardSuccess: {
|
|
742
|
+
backgroundColor: theme_1.COLORS.neonGreen + '0C',
|
|
743
|
+
borderColor: theme_1.COLORS.neonGreen + '44',
|
|
744
|
+
},
|
|
745
|
+
resultCardWarning: {
|
|
746
|
+
backgroundColor: theme_1.COLORS.warning + '0C',
|
|
747
|
+
borderColor: theme_1.COLORS.warning + '44',
|
|
748
|
+
},
|
|
749
|
+
resultCardError: {
|
|
750
|
+
backgroundColor: theme_1.COLORS.error + '0C',
|
|
751
|
+
borderColor: theme_1.COLORS.error + '44',
|
|
752
|
+
},
|
|
753
|
+
resultHeader: {
|
|
754
|
+
flexDirection: 'row',
|
|
755
|
+
alignItems: 'flex-start',
|
|
756
|
+
gap: theme_1.SPACING.sm,
|
|
757
|
+
},
|
|
758
|
+
resultTitle: {
|
|
759
|
+
flex: 1,
|
|
760
|
+
fontSize: theme_1.FONTS.sm,
|
|
761
|
+
fontWeight: '700',
|
|
762
|
+
lineHeight: 20,
|
|
763
|
+
},
|
|
764
|
+
rpcErrorBox: {
|
|
765
|
+
flexDirection: 'row',
|
|
766
|
+
alignItems: 'flex-start',
|
|
767
|
+
gap: theme_1.SPACING.xs,
|
|
768
|
+
backgroundColor: theme_1.COLORS.error + '12',
|
|
769
|
+
borderRadius: theme_1.RADIUS.sm,
|
|
770
|
+
padding: theme_1.SPACING.sm,
|
|
771
|
+
marginTop: 2,
|
|
772
|
+
},
|
|
773
|
+
rpcErrorText: {
|
|
774
|
+
flex: 1,
|
|
775
|
+
fontSize: theme_1.FONTS.xs,
|
|
776
|
+
color: theme_1.COLORS.error,
|
|
777
|
+
lineHeight: 18,
|
|
778
|
+
fontFamily: 'monospace',
|
|
779
|
+
},
|
|
780
|
+
resultLegend: {
|
|
781
|
+
fontSize: theme_1.FONTS.xs,
|
|
782
|
+
color: theme_1.COLORS.textMuted,
|
|
783
|
+
lineHeight: 18,
|
|
784
|
+
},
|
|
785
|
+
entityList: {
|
|
786
|
+
gap: 2,
|
|
787
|
+
marginTop: theme_1.SPACING.xs,
|
|
788
|
+
},
|
|
789
|
+
entityRow: {
|
|
790
|
+
borderRadius: theme_1.RADIUS.sm,
|
|
791
|
+
overflow: 'hidden',
|
|
792
|
+
},
|
|
793
|
+
entityRowHeader: {
|
|
794
|
+
flexDirection: 'row',
|
|
795
|
+
alignItems: 'center',
|
|
796
|
+
gap: theme_1.SPACING.sm,
|
|
797
|
+
paddingVertical: 4,
|
|
798
|
+
paddingHorizontal: theme_1.SPACING.xs,
|
|
799
|
+
},
|
|
800
|
+
entityName: {
|
|
801
|
+
flex: 1,
|
|
802
|
+
fontSize: theme_1.FONTS.xs,
|
|
803
|
+
color: theme_1.COLORS.textSecondary,
|
|
804
|
+
fontWeight: '500',
|
|
805
|
+
textTransform: 'capitalize',
|
|
806
|
+
},
|
|
807
|
+
entityCounts: {
|
|
808
|
+
flexDirection: 'row',
|
|
809
|
+
gap: theme_1.SPACING.xs,
|
|
810
|
+
},
|
|
811
|
+
entityCount: {
|
|
812
|
+
fontSize: theme_1.FONTS.xs,
|
|
813
|
+
fontWeight: '700',
|
|
814
|
+
},
|
|
815
|
+
entityDetails: {
|
|
816
|
+
backgroundColor: theme_1.COLORS.surfaceHigh,
|
|
817
|
+
borderRadius: theme_1.RADIUS.sm,
|
|
818
|
+
padding: theme_1.SPACING.sm,
|
|
819
|
+
gap: 4,
|
|
820
|
+
marginBottom: theme_1.SPACING.xs,
|
|
821
|
+
},
|
|
822
|
+
detailRow: {
|
|
823
|
+
flexDirection: 'row',
|
|
824
|
+
alignItems: 'flex-start',
|
|
825
|
+
gap: theme_1.SPACING.xs,
|
|
826
|
+
},
|
|
827
|
+
detailText: {
|
|
828
|
+
flex: 1,
|
|
829
|
+
fontSize: theme_1.FONTS.xs,
|
|
830
|
+
color: theme_1.COLORS.textSecondary,
|
|
831
|
+
lineHeight: 18,
|
|
832
|
+
},
|
|
833
|
+
detailId: {
|
|
834
|
+
fontWeight: '700',
|
|
835
|
+
color: theme_1.COLORS.textMuted,
|
|
836
|
+
},
|
|
837
|
+
conflictBlock: {
|
|
838
|
+
backgroundColor: theme_1.COLORS.warning + '0A',
|
|
839
|
+
borderLeftWidth: 2,
|
|
840
|
+
borderLeftColor: theme_1.COLORS.warning + '88',
|
|
841
|
+
borderRadius: theme_1.RADIUS.sm,
|
|
842
|
+
paddingLeft: theme_1.SPACING.sm,
|
|
843
|
+
paddingVertical: theme_1.SPACING.xs,
|
|
844
|
+
gap: 3,
|
|
845
|
+
marginTop: 2,
|
|
846
|
+
marginBottom: 2,
|
|
847
|
+
},
|
|
848
|
+
conflictField: {
|
|
849
|
+
flexDirection: 'row',
|
|
850
|
+
gap: theme_1.SPACING.sm,
|
|
851
|
+
paddingLeft: 2,
|
|
852
|
+
},
|
|
853
|
+
conflictLabel: {
|
|
854
|
+
fontSize: theme_1.FONTS.xs,
|
|
855
|
+
color: theme_1.COLORS.textMuted,
|
|
856
|
+
fontFamily: 'monospace',
|
|
857
|
+
fontWeight: '600',
|
|
858
|
+
minWidth: 76,
|
|
859
|
+
lineHeight: 18,
|
|
860
|
+
},
|
|
861
|
+
conflictValue: {
|
|
862
|
+
flex: 1,
|
|
863
|
+
fontSize: theme_1.FONTS.xs,
|
|
864
|
+
color: theme_1.COLORS.textSecondary,
|
|
865
|
+
fontFamily: 'monospace',
|
|
866
|
+
lineHeight: 18,
|
|
867
|
+
},
|
|
868
|
+
rawToggle: {
|
|
869
|
+
flexDirection: 'row',
|
|
870
|
+
alignItems: 'center',
|
|
871
|
+
gap: 3,
|
|
872
|
+
paddingLeft: 2,
|
|
873
|
+
marginTop: 2,
|
|
874
|
+
},
|
|
875
|
+
rawToggleLabel: {
|
|
876
|
+
fontSize: theme_1.FONTS.xs,
|
|
877
|
+
color: theme_1.COLORS.textMuted,
|
|
878
|
+
fontStyle: 'italic',
|
|
879
|
+
},
|
|
880
|
+
rawJson: {
|
|
881
|
+
fontSize: 10,
|
|
882
|
+
color: theme_1.COLORS.textMuted,
|
|
883
|
+
fontFamily: 'monospace',
|
|
884
|
+
lineHeight: 16,
|
|
885
|
+
marginTop: 2,
|
|
886
|
+
},
|
|
887
|
+
imageSyncRow: {
|
|
888
|
+
flexDirection: 'row',
|
|
889
|
+
alignItems: 'center',
|
|
890
|
+
gap: 5,
|
|
891
|
+
marginTop: 1,
|
|
892
|
+
},
|
|
893
|
+
resultTimestamp: {
|
|
894
|
+
fontSize: theme_1.FONTS.xs,
|
|
895
|
+
color: theme_1.COLORS.textMuted,
|
|
896
|
+
textAlign: 'right',
|
|
897
|
+
marginTop: 2,
|
|
898
|
+
},
|
|
899
|
+
dismissResultButton: {
|
|
900
|
+
flexDirection: 'row',
|
|
901
|
+
alignItems: 'center',
|
|
902
|
+
justifyContent: 'center',
|
|
903
|
+
gap: theme_1.SPACING.xs,
|
|
904
|
+
paddingVertical: theme_1.SPACING.sm,
|
|
905
|
+
marginTop: theme_1.SPACING.sm,
|
|
906
|
+
},
|
|
907
|
+
dismissResultText: {
|
|
908
|
+
color: theme_1.COLORS.textMuted,
|
|
909
|
+
fontSize: 14,
|
|
910
|
+
fontWeight: '600',
|
|
911
|
+
},
|
|
912
|
+
});
|
|
913
|
+
//# sourceMappingURL=EchoSyncPanel.js.map
|