@buoy-gg/storage 7.0.41 → 7.0.42
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 +11 -90
- package/lib/commonjs/index.js +1 -353
- package/lib/commonjs/preset.js +1 -85
- package/lib/commonjs/storage/components/DiffViewer/TreeDiffViewer.js +1 -36
- package/lib/commonjs/storage/components/DiffViewer/modes/ThemedSplitView.js +1 -12
- package/lib/commonjs/storage/components/DiffViewer/themes/diffThemes.js +1 -24
- package/lib/commonjs/storage/components/GameUIStorageBrowser.js +3 -1259
- package/lib/commonjs/storage/components/MMKVInstanceInfoPanel.js +1 -257
- package/lib/commonjs/storage/components/MMKVInstanceSelector.js +1 -419
- package/lib/commonjs/storage/components/SelectionActionBar.js +1 -207
- package/lib/commonjs/storage/components/StorageActionButtons.js +1 -242
- package/lib/commonjs/storage/components/StorageBrowserMode.js +1 -61
- package/lib/commonjs/storage/components/StorageEventActionButton.js +1 -121
- package/lib/commonjs/storage/components/StorageEventCard.js +1 -118
- package/lib/commonjs/storage/components/StorageEventDetailContent.js +1 -494
- package/lib/commonjs/storage/components/StorageEventFilterView.js +1 -160
- package/lib/commonjs/storage/components/StorageEventsSection.js +1 -24
- package/lib/commonjs/storage/components/StorageFilterCards.js +1 -231
- package/lib/commonjs/storage/components/StorageKeyCard.js +1 -520
- package/lib/commonjs/storage/components/StorageKeyDetailScreen.js +1 -579
- package/lib/commonjs/storage/components/StorageKeyRow.js +1 -559
- package/lib/commonjs/storage/components/StorageKeySection.js +1 -160
- package/lib/commonjs/storage/components/StorageKeyStats.js +1 -345
- package/lib/commonjs/storage/components/StorageModalWithTabs.js +1 -954
- package/lib/commonjs/storage/components/StorageSaveNotice.js +1 -171
- package/lib/commonjs/storage/components/StorageSection.js +1 -43
- package/lib/commonjs/storage/components/StorageTypeFilterBar.js +1 -144
- package/lib/commonjs/storage/hooks/useAsyncStorageKeys.js +1 -141
- package/lib/commonjs/storage/hooks/useMMKVInstances.js +1 -210
- package/lib/commonjs/storage/hooks/useMMKVKeys.js +1 -376
- package/lib/commonjs/storage/hooks/useSecureStoreKeys.js +1 -202
- package/lib/commonjs/storage/hooks/useStorageEvents.js +1 -61
- package/lib/commonjs/storage/stores/storageEventStore.js +1 -312
- package/lib/commonjs/storage/sync/storageSyncAdapter.js +1 -492
- package/lib/commonjs/storage/types.js +1 -5
- package/lib/commonjs/storage/utils/AsyncStorageListener.js +1 -691
- package/lib/commonjs/storage/utils/AsyncStorageListener.web.js +1 -89
- package/lib/commonjs/storage/utils/MMKVInstanceRegistry.js +1 -203
- package/lib/commonjs/storage/utils/MMKVListener.js +1 -416
- package/lib/commonjs/storage/utils/SecureStoreRegistry.js +1 -200
- package/lib/commonjs/storage/utils/asyncStorageCompat.js +1 -89
- package/lib/commonjs/storage/utils/browserStorageLifecycle.js +1 -10
- package/lib/commonjs/storage/utils/browserStorageLifecycle.web.js +1 -70
- package/lib/commonjs/storage/utils/clearAllStorage.js +1 -48
- package/lib/commonjs/storage/utils/deleteStorageKeys.js +1 -82
- package/lib/commonjs/storage/utils/lineDiff.js +1 -30
- package/lib/commonjs/storage/utils/mmkvAvailability.js +1 -62
- package/lib/commonjs/storage/utils/mmkvAvailability.web.js +1 -21
- package/lib/commonjs/storage/utils/mmkvTypeDetection.js +1 -154
- package/lib/commonjs/storage/utils/requiredStorageKeyScope.js +1 -17
- package/lib/commonjs/storage/utils/setStorageValue.js +1 -218
- package/lib/commonjs/storage/utils/snapshotProvider.js +1 -382
- package/lib/commonjs/storage/utils/storageActionHelpers.js +1 -46
- package/lib/commonjs/storage/utils/storageBrowserRefresh.js +1 -49
- package/lib/commonjs/storage/utils/storageLabels.js +1 -13
- package/lib/commonjs/storage/utils/storageLabels.web.js +1 -13
- package/lib/commonjs/storage/utils/storageOps.js +1 -53
- package/lib/commonjs/storage/utils/storageQueryUtils.js +1 -35
- package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +1 -288
- package/lib/commonjs/storage/utils/valueType.js +1 -83
- package/lib/module/index.js +1 -114
- package/lib/module/preset.js +1 -82
- package/lib/module/storage/components/DiffViewer/TreeDiffViewer.js +1 -32
- package/lib/module/storage/components/DiffViewer/modes/ThemedSplitView.js +1 -9
- package/lib/module/storage/components/DiffViewer/themes/diffThemes.js +1 -10
- package/lib/module/storage/components/GameUIStorageBrowser.js +3 -1257
- package/lib/module/storage/components/MMKVInstanceInfoPanel.js +1 -253
- package/lib/module/storage/components/MMKVInstanceSelector.js +1 -415
- package/lib/module/storage/components/SelectionActionBar.js +1 -204
- package/lib/module/storage/components/StorageActionButtons.js +1 -238
- package/lib/module/storage/components/StorageBrowserMode.js +1 -57
- package/lib/module/storage/components/StorageEventActionButton.js +1 -118
- package/lib/module/storage/components/StorageEventCard.js +1 -113
- package/lib/module/storage/components/StorageEventDetailContent.js +1 -490
- package/lib/module/storage/components/StorageEventFilterView.js +1 -156
- package/lib/module/storage/components/StorageEventsSection.js +1 -20
- package/lib/module/storage/components/StorageFilterCards.js +1 -227
- package/lib/module/storage/components/StorageKeyCard.js +1 -517
- package/lib/module/storage/components/StorageKeyDetailScreen.js +1 -577
- package/lib/module/storage/components/StorageKeyRow.js +1 -554
- package/lib/module/storage/components/StorageKeySection.js +1 -155
- package/lib/module/storage/components/StorageKeyStats.js +1 -342
- package/lib/module/storage/components/StorageModalWithTabs.js +1 -949
- package/lib/module/storage/components/StorageSaveNotice.js +1 -168
- package/lib/module/storage/components/StorageSection.js +1 -40
- package/lib/module/storage/components/StorageTypeFilterBar.js +1 -139
- package/lib/module/storage/hooks/useAsyncStorageKeys.js +1 -136
- package/lib/module/storage/hooks/useMMKVInstances.js +1 -204
- package/lib/module/storage/hooks/useMMKVKeys.js +1 -371
- package/lib/module/storage/hooks/useSecureStoreKeys.js +1 -199
- package/lib/module/storage/hooks/useStorageEvents.js +1 -58
- package/lib/module/storage/stores/storageEventStore.js +1 -301
- package/lib/module/storage/sync/storageSyncAdapter.js +1 -486
- package/lib/module/storage/types.js +1 -3
- package/lib/module/storage/utils/AsyncStorageListener.js +1 -679
- package/lib/module/storage/utils/AsyncStorageListener.web.js +1 -76
- package/lib/module/storage/utils/MMKVInstanceRegistry.js +1 -197
- package/lib/module/storage/utils/MMKVListener.js +1 -403
- package/lib/module/storage/utils/SecureStoreRegistry.js +1 -194
- package/lib/module/storage/utils/asyncStorageCompat.js +1 -81
- package/lib/module/storage/utils/browserStorageLifecycle.js +1 -6
- package/lib/module/storage/utils/browserStorageLifecycle.web.js +1 -66
- package/lib/module/storage/utils/clearAllStorage.js +1 -43
- package/lib/module/storage/utils/deleteStorageKeys.js +1 -78
- package/lib/module/storage/utils/lineDiff.js +1 -12
- package/lib/module/storage/utils/mmkvAvailability.js +1 -56
- package/lib/module/storage/utils/mmkvAvailability.web.js +1 -15
- package/lib/module/storage/utils/mmkvTypeDetection.js +1 -148
- package/lib/module/storage/utils/requiredStorageKeyScope.js +1 -13
- package/lib/module/storage/utils/setStorageValue.js +1 -209
- package/lib/module/storage/utils/snapshotProvider.js +1 -380
- package/lib/module/storage/utils/storageActionHelpers.js +1 -42
- package/lib/module/storage/utils/storageBrowserRefresh.js +1 -44
- package/lib/module/storage/utils/storageLabels.js +1 -9
- package/lib/module/storage/utils/storageLabels.web.js +1 -9
- package/lib/module/storage/utils/storageOps.js +1 -46
- package/lib/module/storage/utils/storageQueryUtils.js +1 -30
- package/lib/module/storage/utils/storageTimeTravelUtils.js +1 -281
- package/lib/module/storage/utils/valueType.js +1 -76
- package/lib/web/index.mjs +2 -3
- package/package.json +5 -5
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts.map +0 -1
- package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts.map +0 -1
- package/lib/typescript/storage/components/DiffViewer/themes/diffThemes.d.ts.map +0 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +0 -1
- package/lib/typescript/storage/components/MMKVInstanceInfoPanel.d.ts.map +0 -1
- package/lib/typescript/storage/components/MMKVInstanceSelector.d.ts.map +0 -1
- package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageActionButtons.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageEventActionButton.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageEventCard.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageEventFilterView.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageEventsSection.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageFilterCards.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageKeyCard.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageKeyDetailScreen.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageKeySection.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageKeyStats.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageSaveNotice.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageSection.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts.map +0 -1
- package/lib/typescript/storage/hooks/useAsyncStorageKeys.d.ts.map +0 -1
- package/lib/typescript/storage/hooks/useMMKVInstances.d.ts.map +0 -1
- package/lib/typescript/storage/hooks/useMMKVKeys.d.ts.map +0 -1
- package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts.map +0 -1
- package/lib/typescript/storage/hooks/useStorageEvents.d.ts.map +0 -1
- package/lib/typescript/storage/stores/storageEventStore.d.ts.map +0 -1
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/storage/types.d.ts.map +0 -1
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +0 -1
- package/lib/typescript/storage/utils/AsyncStorageListener.web.d.ts.map +0 -1
- package/lib/typescript/storage/utils/MMKVInstanceRegistry.d.ts.map +0 -1
- package/lib/typescript/storage/utils/MMKVListener.d.ts.map +0 -1
- package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts.map +0 -1
- package/lib/typescript/storage/utils/asyncStorageCompat.d.ts.map +0 -1
- package/lib/typescript/storage/utils/browserStorageLifecycle.d.ts.map +0 -1
- package/lib/typescript/storage/utils/browserStorageLifecycle.web.d.ts.map +0 -1
- package/lib/typescript/storage/utils/clearAllStorage.d.ts.map +0 -1
- package/lib/typescript/storage/utils/deleteStorageKeys.d.ts.map +0 -1
- package/lib/typescript/storage/utils/lineDiff.d.ts.map +0 -1
- package/lib/typescript/storage/utils/mmkvAvailability.d.ts.map +0 -1
- package/lib/typescript/storage/utils/mmkvAvailability.web.d.ts.map +0 -1
- package/lib/typescript/storage/utils/mmkvTypeDetection.d.ts.map +0 -1
- package/lib/typescript/storage/utils/requiredStorageKeyScope.d.ts.map +0 -1
- package/lib/typescript/storage/utils/setStorageValue.d.ts.map +0 -1
- package/lib/typescript/storage/utils/snapshotProvider.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageActionHelpers.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageLabels.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageLabels.web.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageOps.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageQueryUtils.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +0 -1
- package/lib/typescript/storage/utils/valueType.d.ts.map +0 -1
|
@@ -1,1258 +1,4 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";import{storageLabels as wt}from"../utils/storageLabels";import{useMemo as S,useCallback as b,useState as N,useEffect as A,useRef as ie}from"react";import{StyleSheet as It,Text as m,View as f,TouchableOpacity as ae,ScrollView as Re,Alert as le}from"react-native";import{absoluteFill as Ae,useLimit as Ee,TIER_LIMITS as _t,monoFont as W}from"@buoy-gg/shared-ui";import{Database as Le,Zap as Mt,ProBadge as ze,ProUpgradeModal as Kt,ToolEmptyState as xt,resolveListEmptyState as Bt}from"@buoy-gg/shared-ui";import{isDevToolsStorageKey as p}from"@buoy-gg/shared-ui";import{StorageKeySection as Nt,storageRowId as De}from"./StorageKeySection";import{StorageKeyDetailScreen as qt}from"./StorageKeyDetailScreen";import{StorageFilterCards as Vt}from"./StorageFilterCards";import{useAsyncStorageKeys as Rt}from"../hooks/useAsyncStorageKeys";import{addListener as At}from"../utils/AsyncStorageListener";import{useMultiMMKVKeys as Et}from"../hooks/useMMKVKeys";import{useSecureStoreKeys as Lt}from"../hooks/useSecureStoreKeys";import{useMMKVInstances as zt}from"../hooks/useMMKVInstances";import{StorageSaveNotice as Dt}from"./StorageSaveNotice";import{StorageActionButtons as Ft}from"./StorageActionButtons";import{SelectionActionBar as Wt}from"./SelectionActionBar";import{addMMKVListener as Ot}from"../utils/MMKVListener";import{onStorageBrowserRefresh as Ht}from"../utils/storageBrowserRefresh";import{deleteStorageKeys as Pt}from"../utils/deleteStorageKeys";import{setStorageValue as $t,storageEditBlockedReason as Ut}from"../utils/setStorageValue";import{gameUIColors as E,macOSColors as J,night as s,HardDrive as Fe}from"@buoy-gg/shared-ui";import{useIsPro as jt}from"@buoy-gg/license";import{jsx as a,jsxs as d,Fragment as Gt}from"react/jsx-runtime";const We=[J.semantic.info,J.semantic.success,J.semantic.warning,J.semantic.debug,"#FF6B9D","#00D9FF"];function L(z){const ce=z.split("").reduce((O,de)=>O+de.charCodeAt(0),0);return We[ce%We.length]}export const FREE_TIER_KEY_LIMIT=_t["storage.keys"].free;export function GameUIStorageBrowser({requiredStorageKeys:z=[],showFilters:ce=false,ignoredPatterns:O=new Set(["@react_buoy"]),onTogglePattern:de,onAddPattern:ue,searchQuery:w="",storageDataRef:X,eventCountByKey:ge,lastWriteByKey:Yt,onViewHistory:Z,enabledStorageTypes:H,statusFilter:ye,onStatusFilterChange:me,onStatusCountsChange:fe,pinnedKeys:_,onTogglePin:he,storageTypeFilter:pe,onStorageTypeFilterChange:Se,applySafeAreaInset:Oe,detailKey:P=null,onOpenKey:He,onCloseDetail:be}){const M=jt();const $=Ee("storage.keys");const Ce=Ee("storage.mmkvInstances");const[Q,q]=N(false);A(()=>{if(Q&&M){q(false)}},[Q,M]);const[Pe,$e]=N("all");const ee=ye!=null&&me!=null;const T=ee?ye:Pe;const Ue=ee?me:$e;const[je,Ge]=N("all");const ve=pe!=null&&Se!=null;const C=ve?pe:je;const Ye=ve?Se:Ge;const[g,Te]=N(null);const Je=ie(null);const Xe=ie(0);const ke=ie(new Map);const Ze=b((e,t)=>{if(t)ke.current.set(e,t);else ke.current.delete(e)},[]);const[we,Ie]=N(0);const[te,re]=N(false);const[U,V]=N(new Set);const{instances:y}=zt(false);const{storageKeys:_e,isLoading:Qe,error:et,refresh:j}=Rt(z);const tt=S(()=>y.map(e=>({instance:e.instance,id:e.id})),[y]);const{storageKeys:oe,isLoading:rt,error:ot,refresh:G}=Et(tt,z);const Me=S(()=>{if(!g){return oe}return oe.filter(e=>e.instanceId===g)},[oe,g]);const{storageKeys:Ke,isLoading:nt,error:st,refresh:xe}=Lt(z);const c=S(()=>{return[..._e,...Me,...Ke]},[_e,Me,Ke]);const K=S(()=>{if(!P)return null;const e=De(P);return c.find(t=>De(t)===e)??P},[P,c]);const it=Qe||rt||nt;const Be=et||ot||st;const k=b(()=>{j();G();xe()},[j,G,xe]);const R=b(e=>{return e.instanceId?`${e.storageType}-${e.instanceId}-${e.key}`:`${e.storageType}-${e.key}`},[]);const at=b(()=>{re(e=>{if(e){V(new Set)}return!e})},[]);const lt=b((e,t)=>{const r=R(e);V(h=>{const i=new Set(h);if(t){i.add(r)}else{i.delete(r)}return i})},[R]);const ct=b(()=>{V(new Set);re(false);k()},[k]);const ne=b(e=>{e.forEach(t=>ue?.(t.key));V(new Set);re(false)},[ue]);const se=b(async(e,t)=>{if(!M){q(true);throw new Error("Editing storage values is a Pro feature.")}await $t(e,t,y);k();Ie(r=>r+1)},[M,y,k]);const dt=b(e=>{void se(e,JSON.stringify(!e.value)).catch(t=>{le.alert("Couldn't save",t instanceof Error?t.message:String(t))})},[se]);const Ne=b((e,t)=>{if(!M){q(true);return}le.alert("Delete key",`Delete "${e.key}"? This cannot be undone.`,[{text:"Cancel",style:"cancel"},{text:"Delete",style:"destructive",onPress:async()=>{try{await Pt([e],y.map(r=>({id:r.id,instance:r.instance})));k();t?.()}catch(r){le.alert("Error",`Failed to delete key: ${r}`)}}}])},[M,y,k]);const qe=b(e=>Ut(e,y),[y]);const ut=S(()=>{const e=c;const t={valid:e.filter(l=>l.status==="required_present"||l.status==="optional_present").length,missing:e.filter(l=>l.status==="required_missing").length,issues:e.filter(l=>l.status==="required_wrong_value"||l.status==="required_wrong_type").length,total:e.length};const r=e.filter(l=>l.storageType==="async");const h=e.filter(l=>l.storageType==="mmkv");const i=e.filter(l=>l.storageType==="secure");return{summary:{valid:t.valid,missing:t.missing,issues:t.issues,total:t.total,timestamp:new Date().toISOString()},asyncStorage:r.reduce((l,u)=>{l[u.key]=u.value;return l},{}),mmkv:h.reduce((l,u)=>{const I=u.instanceId||"default";if(!l[I])l[I]={};l[I][u.key]=u.value;return l},{}),secure:i.reduce((l,u)=>{l[u.key]=u.value;return l},{})}},[c]);A(()=>{if(X){X.current=c}},[c,X]);A(()=>{let e;const t=At(r=>{clearTimeout(e);e=setTimeout(()=>{j()},100)});return()=>{clearTimeout(e);t()}},[j]);A(()=>{let e;const t=Ht(()=>{clearTimeout(e);e=setTimeout(k,100)});return()=>{clearTimeout(e);t()}},[k]);A(()=>{let e;const t=Ot(r=>{const h=g===null||r.instanceId===g;if(h){clearTimeout(e);e=setTimeout(()=>{G()},100)}});return()=>{clearTimeout(e);t()}},[G,g]);const D=S(()=>{const e=c;const t=e.filter(i=>!p(i.key));const r=e.filter(i=>p(i.key));const h={totalCount:e.length,requiredCount:t.filter(i=>i.category==="required").length,missingCount:t.filter(i=>i.status==="required_missing").length,wrongValueCount:t.filter(i=>i.status==="required_wrong_value").length,wrongTypeCount:t.filter(i=>i.status==="required_wrong_type").length,presentRequiredCount:t.filter(i=>i.status==="required_present").length,optionalCount:t.filter(i=>i.category==="optional").length,mmkvCount:e.filter(i=>i.storageType==="mmkv").length,asyncCount:e.filter(i=>i.storageType==="async").length,secureCount:e.filter(i=>i.storageType==="secure").length,devToolsCount:r.length};return h},[c]);const x=S(()=>{let e=c.filter(o=>!p(o.key));if(C!=="all"){e=e.filter(o=>o.storageType===C)}const t=e.filter(o=>o.status==="required_present"||o.status==="optional_present").length;const r=e.filter(o=>o.status==="required_missing").length;const h=e.filter(o=>o.status==="required_wrong_type"||o.status==="required_wrong_value").length;let i=0;let l=0;let u=0;let I=0;if(T==="all"){i=c.filter(o=>!p(o.key)&&o.storageType==="async"&&(o.status==="required_present"||o.status==="optional_present")).length;l=c.filter(o=>!p(o.key)&&o.storageType==="mmkv"&&(o.status==="required_present"||o.status==="optional_present")).length;u=c.filter(o=>!p(o.key)&&o.storageType==="secure"&&(o.status==="required_present"||o.status==="optional_present")).length;I=i+l+u}else if(T==="missing"){i=c.filter(o=>!p(o.key)&&o.storageType==="async"&&o.status==="required_missing").length;l=c.filter(o=>!p(o.key)&&o.storageType==="mmkv"&&o.status==="required_missing").length;u=c.filter(o=>!p(o.key)&&o.storageType==="secure"&&o.status==="required_missing").length;I=i+l+u}else if(T==="issues"){i=c.filter(o=>!p(o.key)&&o.storageType==="async"&&(o.status==="required_wrong_type"||o.status==="required_wrong_value")).length;l=c.filter(o=>!p(o.key)&&o.storageType==="mmkv"&&(o.status==="required_wrong_type"||o.status==="required_wrong_value")).length;u=c.filter(o=>!p(o.key)&&o.storageType==="secure"&&(o.status==="required_wrong_type"||o.status==="required_wrong_value")).length;I=i+l+u}return{validCount:t,missingCount:r,issuesCount:h,asyncCount:i,mmkvCount:l,secureCount:u,totalCount:I}},[c,T,C]);A(()=>{fe?.({validCount:x.validCount,missingCount:x.missingCount,issuesCount:x.issuesCount})},[fe,x.validCount,x.missingCount,x.issuesCount]);const Ve=S(()=>{return[...c].sort((e,t)=>{const r={required_missing:1,required_wrong_type:2,required_wrong_value:3,required_present:4,optional_present:5};return(r[e.status]||999)-(r[t.status]||999)})},[c]);const Jt=S(()=>{return Array.from(new Set(c.map(e=>e.key))).sort()},[c]);const v=S(()=>{let e=Ve;if(H&&H.size>0){e=e.filter(t=>H.has(t.storageType))}e=e.filter(t=>{const r=Array.from(O).some(h=>t.key.includes(h));return!r});if(w){const t=w.toLowerCase();e=e.filter(r=>r.key.toLowerCase().includes(t))}switch(T){case"all":e=e.filter(t=>t.status==="required_present"||t.status==="optional_present");break;case"missing":e=e.filter(t=>t.status==="required_missing");break;case"issues":e=e.filter(t=>t.status==="required_wrong_type"||t.status==="required_wrong_value");break}if(C!=="all"){e=e.filter(t=>t.storageType===C)}if(_&&_.size>0){const t=e.filter(h=>_.has(h.key));const r=e.filter(h=>!_.has(h.key));e=[...t,...r]}return e},[Ve,T,C,O,w,g,H,_]);const B=S(()=>v.slice(0,$),[v,$]);const Y=S(()=>{return Math.max(0,v.length-$)},[v.length,$]);const gt=Y>0;const Xt=S(()=>{const e=v.filter(r=>!p(r.key));const t={totalCount:v.length,requiredCount:e.filter(r=>r.category==="required").length,missingCount:e.filter(r=>r.status==="required_missing").length,wrongValueCount:e.filter(r=>r.status==="required_wrong_value").length,wrongTypeCount:e.filter(r=>r.status==="required_wrong_type").length,presentRequiredCount:e.filter(r=>r.status==="required_present").length,optionalCount:e.filter(r=>r.category==="optional").length,mmkvCount:v.filter(r=>r.storageType==="mmkv").length,asyncCount:v.filter(r=>r.storageType==="async").length,secureCount:v.filter(r=>r.storageType==="secure").length,devToolsCount:v.filter(r=>p(r.key)).length};return t},[v]);const yt=S(()=>{return B.filter(e=>U.has(R(e)))},[B,U,R]);const mt=b(()=>{const e=new Set(B.map(R));V(e)},[B,R]);const ft=b(()=>{V(new Set)},[]);const F=D.requiredCount>0?Math.round(D.presentRequiredCount/D.requiredCount*100):D.totalCount>0?100:0;const ht=F>=90?"OPTIMAL":F>=70?"WARNING":"CRITICAL";const pt=F>=90?E.success:F>=70?E.warning:E.error;if(K){return a(f,{style:n.root,children:a(qt,{storageKey:K,editBlockedReason:qe(K),eventCount:ge?.[K.key],onSave:e=>se(K,e),onViewHistory:Z?()=>Z(K.key):void 0,onDeleteKey:e=>Ne(e,()=>be?.()),onHideKey:e=>{ne([e]);be?.()},isPinned:_?.has(K.key)??false,onTogglePin:he,applySafeAreaInset:Oe})})}if(it&&c.length===0){return d(f,{style:n.emptyState,children:[a(Le,{size:48,color:s.color.textTertiary}),a(m,{style:n.emptyTitle,children:"Loading storage keys..."})]})}if(Be){return d(f,{style:n.emptyState,children:[a(Le,{size:48,color:E.error}),a(m,{style:[n.emptyTitle,{color:E.error}],children:"Error loading storage"}),a(m,{style:n.emptySubtitle,children:Be.message})]})}const St=d(Gt,{children:[C==="mmkv"&&y.length===0&&d(f,{style:n.emptyMMKVState,children:[a(m,{style:n.emptyMMKVTitle,children:"No MMKV Instances Detected"}),a(m,{style:n.emptyMMKVSubtitle,children:"MMKV instances must be registered with registerMMKVInstance()"}),a(m,{style:n.emptyMMKVCode,children:`import { registerMMKVInstance } from '@buoy-gg/storage';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { StyleSheet, Text, View, TouchableOpacity, ScrollView, Alert } from "react-native";
|
|
6
|
-
import { absoluteFill, useLimit, TIER_LIMITS, monoFont } from "@buoy-gg/shared-ui";
|
|
7
|
-
import { Database, Zap, ProBadge, ProUpgradeModal, ToolEmptyState, resolveListEmptyState } from "@buoy-gg/shared-ui";
|
|
8
|
-
import { isDevToolsStorageKey } from "@buoy-gg/shared-ui";
|
|
9
|
-
import { StorageKeySection, storageRowId } from "./StorageKeySection";
|
|
10
|
-
import { StorageKeyDetailScreen } from "./StorageKeyDetailScreen";
|
|
11
|
-
import { StorageFilterCards } from "./StorageFilterCards";
|
|
12
|
-
import { useAsyncStorageKeys } from "../hooks/useAsyncStorageKeys";
|
|
13
|
-
import { addListener } from "../utils/AsyncStorageListener";
|
|
14
|
-
import { useMultiMMKVKeys } from "../hooks/useMMKVKeys";
|
|
15
|
-
import { useSecureStoreKeys } from "../hooks/useSecureStoreKeys";
|
|
16
|
-
import { useMMKVInstances } from "../hooks/useMMKVInstances";
|
|
17
|
-
import { StorageSaveNotice } from "./StorageSaveNotice";
|
|
18
|
-
import { StorageActionButtons } from "./StorageActionButtons";
|
|
19
|
-
import { SelectionActionBar } from "./SelectionActionBar";
|
|
20
|
-
// Pure JS (no react-native-mmkv dependency) — works with any registered
|
|
21
|
-
// instance, including remote-backed wrappers on the desktop dashboard.
|
|
22
|
-
import { addMMKVListener } from "../utils/MMKVListener";
|
|
23
|
-
import { onStorageBrowserRefresh } from "../utils/storageBrowserRefresh";
|
|
24
|
-
import { deleteStorageKeys } from "../utils/deleteStorageKeys";
|
|
25
|
-
import { setStorageValue, storageEditBlockedReason } from "../utils/setStorageValue";
|
|
26
|
-
|
|
27
|
-
// Import shared Game UI components
|
|
28
|
-
import { gameUIColors, macOSColors, night, HardDrive } from "@buoy-gg/shared-ui";
|
|
29
|
-
import { useIsPro } from "@buoy-gg/license";
|
|
30
|
-
|
|
31
|
-
// MMKV Instance color palette - consistent colors per instance
|
|
32
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
33
|
-
const INSTANCE_COLORS = [macOSColors.semantic.info,
|
|
34
|
-
// Blue
|
|
35
|
-
macOSColors.semantic.success,
|
|
36
|
-
// Green
|
|
37
|
-
macOSColors.semantic.warning,
|
|
38
|
-
// Orange
|
|
39
|
-
macOSColors.semantic.debug,
|
|
40
|
-
// Purple
|
|
41
|
-
'#FF6B9D',
|
|
42
|
-
// Pink
|
|
43
|
-
'#00D9FF' // Cyan
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Get consistent color for an MMKV instance based on its ID
|
|
48
|
-
* Uses simple hash to ensure same instance always gets same color
|
|
49
|
-
*/
|
|
50
|
-
function getInstanceColor(instanceId) {
|
|
51
|
-
const hash = instanceId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
52
|
-
return INSTANCE_COLORS[hash % INSTANCE_COLORS.length];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @deprecated Read the live limit with `useLimit("storage.keys")` — this only
|
|
57
|
-
* reports the FREE tier's value and is blind to the anonymous tier. Kept
|
|
58
|
-
* because it is part of the package's public exports.
|
|
59
|
-
*/
|
|
60
|
-
export const FREE_TIER_KEY_LIMIT = TIER_LIMITS["storage.keys"].free;
|
|
61
|
-
export function GameUIStorageBrowser({
|
|
62
|
-
requiredStorageKeys = [],
|
|
63
|
-
showFilters = false,
|
|
64
|
-
ignoredPatterns = new Set(["@react_buoy"]),
|
|
65
|
-
onTogglePattern,
|
|
66
|
-
onAddPattern,
|
|
67
|
-
searchQuery = "",
|
|
68
|
-
storageDataRef,
|
|
69
|
-
eventCountByKey,
|
|
70
|
-
lastWriteByKey,
|
|
71
|
-
onViewHistory,
|
|
72
|
-
enabledStorageTypes,
|
|
73
|
-
statusFilter,
|
|
74
|
-
onStatusFilterChange,
|
|
75
|
-
onStatusCountsChange,
|
|
76
|
-
pinnedKeys,
|
|
77
|
-
onTogglePin,
|
|
78
|
-
storageTypeFilter,
|
|
79
|
-
onStorageTypeFilterChange,
|
|
80
|
-
applySafeAreaInset,
|
|
81
|
-
detailKey = null,
|
|
82
|
-
onOpenKey,
|
|
83
|
-
onCloseDetail
|
|
84
|
-
}) {
|
|
85
|
-
const isPro = useIsPro();
|
|
86
|
-
const keyLimit = useLimit("storage.keys");
|
|
87
|
-
const mmkvInstanceLimit = useLimit("storage.mmkvInstances");
|
|
88
|
-
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
|
|
89
|
-
|
|
90
|
-
// Auto-close upgrade modal when user becomes Pro
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
if (showUpgradeModal && isPro) {
|
|
93
|
-
setShowUpgradeModal(false);
|
|
94
|
-
}
|
|
95
|
-
}, [showUpgradeModal, isPro]);
|
|
96
|
-
const [internalFilter, setInternalFilter] = useState("all");
|
|
97
|
-
// Controlled when the host supplies both halves; uncontrolled otherwise.
|
|
98
|
-
const isFilterControlled = statusFilter != null && onStatusFilterChange != null;
|
|
99
|
-
const activeFilter = isFilterControlled ? statusFilter : internalFilter;
|
|
100
|
-
const setActiveFilter = isFilterControlled ? onStatusFilterChange : setInternalFilter;
|
|
101
|
-
const [internalStorageType, setInternalStorageType] = useState("all");
|
|
102
|
-
// Controlled when the host supplies both halves — the modal does, so the
|
|
103
|
-
// Events tab shows the same backend you picked here.
|
|
104
|
-
const isStorageTypeControlled = storageTypeFilter != null && onStorageTypeFilterChange != null;
|
|
105
|
-
const activeStorageType = isStorageTypeControlled ? storageTypeFilter : internalStorageType;
|
|
106
|
-
const setActiveStorageType = isStorageTypeControlled ? onStorageTypeFilterChange : setInternalStorageType;
|
|
107
|
-
const [selectedMMKVInstance, setSelectedMMKVInstance] = useState(null);
|
|
108
|
-
const scrollRef = useRef(null);
|
|
109
|
-
const scrollOffsetRef = useRef(0);
|
|
110
|
-
const rowNodesRef = useRef(new Map());
|
|
111
|
-
const handleRowRef = useCallback((id, node) => {
|
|
112
|
-
if (node) rowNodesRef.current.set(id, node);else rowNodesRef.current.delete(id);
|
|
113
|
-
}, []);
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Shown after every successful write, with the same sentence every time.
|
|
117
|
-
*
|
|
118
|
-
* An earlier version looked up which live store owned the key and offered to
|
|
119
|
-
* make that store re-read it. That required knowing what is linked to what,
|
|
120
|
-
* and every route to that knowledge is a guess — key names collide across
|
|
121
|
-
* backends, only zustand stores register at all, and a store can register
|
|
122
|
-
* after the lookup ran. A message that is right most of the time is the
|
|
123
|
-
* problem this tool has, not the fix for it.
|
|
124
|
-
*
|
|
125
|
-
* There is also no general mechanism to find: the app owns its cache, and
|
|
126
|
-
* only the app can invalidate it. So the tool promises the one thing that is
|
|
127
|
-
* true for every key, every backend and every persistence library — this is
|
|
128
|
-
* what loads next launch — and "make it live now" lives in the state tools,
|
|
129
|
-
* where the store you mean is the one you are looking at.
|
|
130
|
-
*/
|
|
131
|
-
/**
|
|
132
|
-
* Bumped on every save rather than flipped to `true`.
|
|
133
|
-
*
|
|
134
|
-
* The toast unmounts itself when it fades, and a boolean already `true` would
|
|
135
|
-
* not change on the next save — so a second edit would show nothing. The
|
|
136
|
-
* counter is the toast's `key`, so each save mounts a fresh one and the
|
|
137
|
-
* animation restarts from the beginning.
|
|
138
|
-
*/
|
|
139
|
-
const [saveToastId, setSaveToastId] = useState(0);
|
|
140
|
-
|
|
141
|
-
// Selection mode state
|
|
142
|
-
const [isSelectMode, setIsSelectMode] = useState(false);
|
|
143
|
-
const [selectedKeyIds, setSelectedKeyIds] = useState(new Set());
|
|
144
|
-
|
|
145
|
-
// Get all MMKV instances
|
|
146
|
-
const {
|
|
147
|
-
instances: mmkvInstances
|
|
148
|
-
} = useMMKVInstances(false);
|
|
149
|
-
|
|
150
|
-
// REMOVED: Auto-selection is now handled by the instance navbar
|
|
151
|
-
// Users can manually select an instance from the navbar if they want to filter by instance
|
|
152
|
-
// By default, selectedMMKVInstance is null, which shows ALL MMKV keys
|
|
153
|
-
|
|
154
|
-
// Use AsyncStorage hook
|
|
155
|
-
const {
|
|
156
|
-
storageKeys: asyncStorageKeys,
|
|
157
|
-
isLoading: isLoadingAsync,
|
|
158
|
-
error: asyncError,
|
|
159
|
-
refresh: refreshAsync
|
|
160
|
-
} = useAsyncStorageKeys(requiredStorageKeys);
|
|
161
|
-
|
|
162
|
-
// Memoize the instances array to prevent infinite re-renders
|
|
163
|
-
const mmkvInstancesForHook = useMemo(() => mmkvInstances.map(inst => ({
|
|
164
|
-
instance: inst.instance,
|
|
165
|
-
id: inst.id
|
|
166
|
-
})), [mmkvInstances]);
|
|
167
|
-
|
|
168
|
-
// Use MMKV hook - always fetch from ALL instances, then filter by selected instance
|
|
169
|
-
const {
|
|
170
|
-
storageKeys: allMMKVKeys,
|
|
171
|
-
isLoading: isLoadingMMKV,
|
|
172
|
-
error: mmkvError,
|
|
173
|
-
refresh: refreshMMKV
|
|
174
|
-
} = useMultiMMKVKeys(mmkvInstancesForHook, requiredStorageKeys);
|
|
175
|
-
|
|
176
|
-
// Filter MMKV keys by selected instance (if one is selected)
|
|
177
|
-
const mmkvStorageKeys = useMemo(() => {
|
|
178
|
-
if (!selectedMMKVInstance) {
|
|
179
|
-
return allMMKVKeys; // Show all MMKV keys
|
|
180
|
-
}
|
|
181
|
-
return allMMKVKeys.filter(k => k.instanceId === selectedMMKVInstance);
|
|
182
|
-
}, [allMMKVKeys, selectedMMKVInstance]);
|
|
183
|
-
|
|
184
|
-
// Use SecureStore hook — only surfaces keys declared via registerSecureStoreKeys()
|
|
185
|
-
const {
|
|
186
|
-
storageKeys: secureStorageKeys,
|
|
187
|
-
isLoading: isLoadingSecure,
|
|
188
|
-
error: secureError,
|
|
189
|
-
refresh: refreshSecure
|
|
190
|
-
} = useSecureStoreKeys(requiredStorageKeys);
|
|
191
|
-
|
|
192
|
-
// Merge storage keys from AsyncStorage, MMKV, and SecureStore
|
|
193
|
-
const allStorageKeys = useMemo(() => {
|
|
194
|
-
return [...asyncStorageKeys, ...mmkvStorageKeys, ...secureStorageKeys];
|
|
195
|
-
}, [asyncStorageKeys, mmkvStorageKeys, secureStorageKeys]);
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* The CURRENT snapshot of the key the detail screen is open on. `detailKey`
|
|
199
|
-
* is only an identity — resolving it fresh each pass is what makes the
|
|
200
|
-
* detail screen live: the app writes, the snapshot refreshes, the screen
|
|
201
|
-
* follows. Falls back to the captured info because the backend hooks
|
|
202
|
-
* transiently EMPTY their lists mid-refresh — treating "missing from this
|
|
203
|
-
* render's list" as "deleted" closed the screen the moment it opened, so
|
|
204
|
-
* absence here is never acted on (deletion closes explicitly, from the
|
|
205
|
-
* delete flow itself).
|
|
206
|
-
*/
|
|
207
|
-
const detailInfo = useMemo(() => {
|
|
208
|
-
if (!detailKey) return null;
|
|
209
|
-
const id = storageRowId(detailKey);
|
|
210
|
-
return allStorageKeys.find(k => storageRowId(k) === id) ?? detailKey;
|
|
211
|
-
}, [detailKey, allStorageKeys]);
|
|
212
|
-
|
|
213
|
-
// Determine loading and error states
|
|
214
|
-
const isLoading = isLoadingAsync || isLoadingMMKV || isLoadingSecure;
|
|
215
|
-
const error = asyncError || mmkvError || secureError;
|
|
216
|
-
|
|
217
|
-
// Combined refresh function
|
|
218
|
-
const refresh = useCallback(() => {
|
|
219
|
-
refreshAsync();
|
|
220
|
-
refreshMMKV();
|
|
221
|
-
refreshSecure();
|
|
222
|
-
}, [refreshAsync, refreshMMKV, refreshSecure]);
|
|
223
|
-
|
|
224
|
-
// Generate unique identifier for a storage key
|
|
225
|
-
const getKeyIdentifier = useCallback(storageKey => {
|
|
226
|
-
return storageKey.instanceId ? `${storageKey.storageType}-${storageKey.instanceId}-${storageKey.key}` : `${storageKey.storageType}-${storageKey.key}`;
|
|
227
|
-
}, []);
|
|
228
|
-
|
|
229
|
-
// Toggle select mode
|
|
230
|
-
const handleToggleSelectMode = useCallback(() => {
|
|
231
|
-
setIsSelectMode(prev => {
|
|
232
|
-
if (prev) {
|
|
233
|
-
// When exiting select mode, clear selection
|
|
234
|
-
setSelectedKeyIds(new Set());
|
|
235
|
-
}
|
|
236
|
-
return !prev;
|
|
237
|
-
});
|
|
238
|
-
}, []);
|
|
239
|
-
|
|
240
|
-
// Handle selection change for a single key
|
|
241
|
-
const handleSelectionChange = useCallback((storageKey, selected) => {
|
|
242
|
-
const keyId = getKeyIdentifier(storageKey);
|
|
243
|
-
setSelectedKeyIds(prev => {
|
|
244
|
-
const newSet = new Set(prev);
|
|
245
|
-
if (selected) {
|
|
246
|
-
newSet.add(keyId);
|
|
247
|
-
} else {
|
|
248
|
-
newSet.delete(keyId);
|
|
249
|
-
}
|
|
250
|
-
return newSet;
|
|
251
|
-
});
|
|
252
|
-
}, [getKeyIdentifier]);
|
|
253
|
-
|
|
254
|
-
// Clear selection and exit select mode after deletion
|
|
255
|
-
const handleDeleteComplete = useCallback(() => {
|
|
256
|
-
setSelectedKeyIds(new Set());
|
|
257
|
-
setIsSelectMode(false);
|
|
258
|
-
refresh();
|
|
259
|
-
}, [refresh]);
|
|
260
|
-
|
|
261
|
-
// Hide the selected keys by adding each to the ignored-pattern filter store,
|
|
262
|
-
// then exit selection mode. Non-destructive — keys can be unhidden from the
|
|
263
|
-
// filters UI.
|
|
264
|
-
const handleHideKeys = useCallback(keys => {
|
|
265
|
-
keys.forEach(k => onAddPattern?.(k.key));
|
|
266
|
-
setSelectedKeyIds(new Set());
|
|
267
|
-
setIsSelectMode(false);
|
|
268
|
-
}, [onAddPattern]);
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Write an edited value back to the device.
|
|
272
|
-
*
|
|
273
|
-
* Pro is checked here rather than on the Edit button because the technical
|
|
274
|
-
* blocks (`storageEditBlockedReason`) and the licence are different kinds of
|
|
275
|
-
* "no": the former means this key can never be edited and the row says so
|
|
276
|
-
* permanently, the latter is an upsell. Throwing lets the editor keep the
|
|
277
|
-
* draft on screen instead of discarding what was typed.
|
|
278
|
-
*/
|
|
279
|
-
const handleSaveValue = useCallback(async (storageKey, raw) => {
|
|
280
|
-
if (!isPro) {
|
|
281
|
-
setShowUpgradeModal(true);
|
|
282
|
-
throw new Error("Editing storage values is a Pro feature.");
|
|
283
|
-
}
|
|
284
|
-
await setStorageValue(storageKey, raw, mmkvInstances);
|
|
285
|
-
refresh();
|
|
286
|
-
setSaveToastId(id => id + 1);
|
|
287
|
-
}, [isPro, mmkvInstances, refresh]);
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Flip a boolean key straight from its card.
|
|
291
|
-
*
|
|
292
|
-
* Goes through `handleSaveValue` like every other write, so the Pro gate and
|
|
293
|
-
* the type preservation are the same ones the editor uses — this is only a
|
|
294
|
-
* shortcut past the UI, not past the rules.
|
|
295
|
-
*/
|
|
296
|
-
const handleToggleKey = useCallback(storageKey => {
|
|
297
|
-
void handleSaveValue(storageKey, JSON.stringify(!storageKey.value)).catch(e => {
|
|
298
|
-
Alert.alert("Couldn't save", e instanceof Error ? e.message : String(e));
|
|
299
|
-
});
|
|
300
|
-
}, [handleSaveValue]);
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Delete ONE key, from its own card.
|
|
304
|
-
*
|
|
305
|
-
* Routed through `deleteStorageKeys` exactly like the selection bar, so the
|
|
306
|
-
* per-backend fan-out keeps having a single implementation — the last time
|
|
307
|
-
* that fan-out was wrong (an MMKV method renamed upstream) it was wrong in
|
|
308
|
-
* silence, and a second copy is how one backend quietly stops being deleted.
|
|
309
|
-
*
|
|
310
|
-
* Pro is checked here rather than on the button because it's an upsell, not
|
|
311
|
-
* a capability: the row shows the action and explains the wall on press.
|
|
312
|
-
*/
|
|
313
|
-
const handleDeleteKey = useCallback((storageKey, onDeleted) => {
|
|
314
|
-
if (!isPro) {
|
|
315
|
-
setShowUpgradeModal(true);
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
Alert.alert("Delete key", `Delete "${storageKey.key}"? This cannot be undone.`, [{
|
|
319
|
-
text: "Cancel",
|
|
320
|
-
style: "cancel"
|
|
321
|
-
}, {
|
|
322
|
-
text: "Delete",
|
|
323
|
-
style: "destructive",
|
|
324
|
-
onPress: async () => {
|
|
325
|
-
try {
|
|
326
|
-
await deleteStorageKeys([storageKey], mmkvInstances.map(inst => ({
|
|
327
|
-
id: inst.id,
|
|
328
|
-
instance: inst.instance
|
|
329
|
-
})));
|
|
330
|
-
refresh();
|
|
331
|
-
// Only after the delete actually landed — a cancelled alert
|
|
332
|
-
// or a failed delete leaves the caller's screen alone.
|
|
333
|
-
onDeleted?.();
|
|
334
|
-
} catch (error) {
|
|
335
|
-
Alert.alert("Error", `Failed to delete key: ${error}`);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}]);
|
|
339
|
-
}, [isPro, mmkvInstances, refresh]);
|
|
340
|
-
|
|
341
|
-
/** Permanent, per-key reasons a value can't be edited (never the licence). */
|
|
342
|
-
const editBlockedFor = useCallback(storageKey => storageEditBlockedReason(storageKey, mmkvInstances), [mmkvInstances]);
|
|
343
|
-
|
|
344
|
-
// Memoized export data for copy functionality
|
|
345
|
-
const copyExportData = useMemo(() => {
|
|
346
|
-
const allKeys = allStorageKeys;
|
|
347
|
-
|
|
348
|
-
// Calculate stats
|
|
349
|
-
const stats = {
|
|
350
|
-
valid: allKeys.filter(k => k.status === 'required_present' || k.status === 'optional_present').length,
|
|
351
|
-
missing: allKeys.filter(k => k.status === 'required_missing').length,
|
|
352
|
-
issues: allKeys.filter(k => k.status === 'required_wrong_value' || k.status === 'required_wrong_type').length,
|
|
353
|
-
total: allKeys.length
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
// Group by storage type
|
|
357
|
-
const asyncKeys = allKeys.filter(k => k.storageType === 'async');
|
|
358
|
-
const mmkvKeys = allKeys.filter(k => k.storageType === 'mmkv');
|
|
359
|
-
const secureKeys = allKeys.filter(k => k.storageType === 'secure');
|
|
360
|
-
|
|
361
|
-
// Build structured export
|
|
362
|
-
return {
|
|
363
|
-
summary: {
|
|
364
|
-
valid: stats.valid,
|
|
365
|
-
missing: stats.missing,
|
|
366
|
-
issues: stats.issues,
|
|
367
|
-
total: stats.total,
|
|
368
|
-
timestamp: new Date().toISOString()
|
|
369
|
-
},
|
|
370
|
-
asyncStorage: asyncKeys.reduce((acc, k) => {
|
|
371
|
-
acc[k.key] = k.value;
|
|
372
|
-
return acc;
|
|
373
|
-
}, {}),
|
|
374
|
-
mmkv: mmkvKeys.reduce((acc, k) => {
|
|
375
|
-
const instanceId = k.instanceId || 'default';
|
|
376
|
-
if (!acc[instanceId]) acc[instanceId] = {};
|
|
377
|
-
acc[instanceId][k.key] = k.value;
|
|
378
|
-
return acc;
|
|
379
|
-
}, {}),
|
|
380
|
-
secure: secureKeys.reduce((acc, k) => {
|
|
381
|
-
acc[k.key] = k.value;
|
|
382
|
-
return acc;
|
|
383
|
-
}, {})
|
|
384
|
-
};
|
|
385
|
-
}, [allStorageKeys]);
|
|
386
|
-
|
|
387
|
-
// Update storage data ref for copy functionality
|
|
388
|
-
useEffect(() => {
|
|
389
|
-
if (storageDataRef) {
|
|
390
|
-
storageDataRef.current = allStorageKeys;
|
|
391
|
-
}
|
|
392
|
-
}, [allStorageKeys, storageDataRef]);
|
|
393
|
-
|
|
394
|
-
// Auto-refresh on storage events (AsyncStorage)
|
|
395
|
-
useEffect(() => {
|
|
396
|
-
let timeoutId;
|
|
397
|
-
const unsubscribe = addListener(event => {
|
|
398
|
-
clearTimeout(timeoutId);
|
|
399
|
-
timeoutId = setTimeout(() => {
|
|
400
|
-
refreshAsync(); // Auto-refresh AsyncStorage when it changes
|
|
401
|
-
}, 100); // 100ms debounce
|
|
402
|
-
});
|
|
403
|
-
return () => {
|
|
404
|
-
clearTimeout(timeoutId);
|
|
405
|
-
unsubscribe();
|
|
406
|
-
};
|
|
407
|
-
}, [refreshAsync]);
|
|
408
|
-
|
|
409
|
-
// Auto-refresh when something outside this component knows the underlying
|
|
410
|
-
// storage changed. On device the listeners above cover it; in remote-mirror
|
|
411
|
-
// mode (desktop dashboard) local capture is suppressed and they never fire,
|
|
412
|
-
// so the dashboard signals each incoming device snapshot this way — without
|
|
413
|
-
// it the browse tab is frozen at whatever it read on mount.
|
|
414
|
-
useEffect(() => {
|
|
415
|
-
let timeoutId;
|
|
416
|
-
const unsubscribe = onStorageBrowserRefresh(() => {
|
|
417
|
-
clearTimeout(timeoutId);
|
|
418
|
-
timeoutId = setTimeout(refresh, 100);
|
|
419
|
-
});
|
|
420
|
-
return () => {
|
|
421
|
-
clearTimeout(timeoutId);
|
|
422
|
-
unsubscribe();
|
|
423
|
-
};
|
|
424
|
-
}, [refresh]);
|
|
425
|
-
|
|
426
|
-
// Auto-refresh on MMKV storage events
|
|
427
|
-
useEffect(() => {
|
|
428
|
-
let timeoutId;
|
|
429
|
-
const unsubscribe = addMMKVListener(event => {
|
|
430
|
-
// Refresh when the changed instance is one we're actually showing.
|
|
431
|
-
// `selectedMMKVInstance` is null by default ("All instances"), so
|
|
432
|
-
// comparing it to event.instanceId directly was never true for the view
|
|
433
|
-
// every user starts on — MMKV values sat stale in the browse list until
|
|
434
|
-
// it remounted, even though the write was recorded in Events.
|
|
435
|
-
const affectsView = selectedMMKVInstance === null || event.instanceId === selectedMMKVInstance;
|
|
436
|
-
if (affectsView) {
|
|
437
|
-
clearTimeout(timeoutId);
|
|
438
|
-
timeoutId = setTimeout(() => {
|
|
439
|
-
refreshMMKV(); // Auto-refresh MMKV when it changes
|
|
440
|
-
}, 100); // 100ms debounce
|
|
441
|
-
}
|
|
442
|
-
});
|
|
443
|
-
return () => {
|
|
444
|
-
clearTimeout(timeoutId);
|
|
445
|
-
unsubscribe();
|
|
446
|
-
};
|
|
447
|
-
}, [refreshMMKV, selectedMMKVInstance]);
|
|
448
|
-
|
|
449
|
-
// Calculate stats from ALL keys (not filtered) - base stats for display
|
|
450
|
-
const stats = useMemo(() => {
|
|
451
|
-
const allKeys = allStorageKeys;
|
|
452
|
-
const appKeys = allKeys.filter(k => !isDevToolsStorageKey(k.key));
|
|
453
|
-
const devKeys = allKeys.filter(k => isDevToolsStorageKey(k.key));
|
|
454
|
-
const storageStats = {
|
|
455
|
-
totalCount: allKeys.length,
|
|
456
|
-
requiredCount: appKeys.filter(k => k.category === "required").length,
|
|
457
|
-
missingCount: appKeys.filter(k => k.status === "required_missing").length,
|
|
458
|
-
wrongValueCount: appKeys.filter(k => k.status === "required_wrong_value").length,
|
|
459
|
-
wrongTypeCount: appKeys.filter(k => k.status === "required_wrong_type").length,
|
|
460
|
-
presentRequiredCount: appKeys.filter(k => k.status === "required_present").length,
|
|
461
|
-
optionalCount: appKeys.filter(k => k.category === "optional").length,
|
|
462
|
-
// Count keys by their actual storageType property
|
|
463
|
-
mmkvCount: allKeys.filter(k => k.storageType === "mmkv").length,
|
|
464
|
-
asyncCount: allKeys.filter(k => k.storageType === "async").length,
|
|
465
|
-
secureCount: allKeys.filter(k => k.storageType === "secure").length,
|
|
466
|
-
devToolsCount: devKeys.length
|
|
467
|
-
};
|
|
468
|
-
return storageStats;
|
|
469
|
-
}, [allStorageKeys]);
|
|
470
|
-
|
|
471
|
-
// Calculate stats for tab badges based on current filters
|
|
472
|
-
const tabStats = useMemo(() => {
|
|
473
|
-
// Start with all keys, excluding devtools keys
|
|
474
|
-
let keysForStats = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key));
|
|
475
|
-
|
|
476
|
-
// If a specific storage type is selected, filter by that type first
|
|
477
|
-
if (activeStorageType !== "all") {
|
|
478
|
-
keysForStats = keysForStats.filter(k => k.storageType === activeStorageType);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Calculate status counts from filtered keys
|
|
482
|
-
const validCount = keysForStats.filter(k => k.status === "required_present" || k.status === "optional_present").length;
|
|
483
|
-
const missingCount = keysForStats.filter(k => k.status === "required_missing").length;
|
|
484
|
-
const issuesCount = keysForStats.filter(k => k.status === "required_wrong_type" || k.status === "required_wrong_value").length;
|
|
485
|
-
|
|
486
|
-
// Calculate storage type counts based on active status filter
|
|
487
|
-
let asyncCount = 0;
|
|
488
|
-
let mmkvCount = 0;
|
|
489
|
-
let secureCount = 0;
|
|
490
|
-
let totalCount = 0;
|
|
491
|
-
if (activeFilter === "all") {
|
|
492
|
-
// Valid: only keys with valid status
|
|
493
|
-
asyncCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "async" && (k.status === "required_present" || k.status === "optional_present")).length;
|
|
494
|
-
mmkvCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "mmkv" && (k.status === "required_present" || k.status === "optional_present")).length;
|
|
495
|
-
secureCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "secure" && (k.status === "required_present" || k.status === "optional_present")).length;
|
|
496
|
-
totalCount = asyncCount + mmkvCount + secureCount;
|
|
497
|
-
} else if (activeFilter === "missing") {
|
|
498
|
-
// Missing: only keys with missing status
|
|
499
|
-
asyncCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "async" && k.status === "required_missing").length;
|
|
500
|
-
mmkvCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "mmkv" && k.status === "required_missing").length;
|
|
501
|
-
secureCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "secure" && k.status === "required_missing").length;
|
|
502
|
-
totalCount = asyncCount + mmkvCount + secureCount;
|
|
503
|
-
} else if (activeFilter === "issues") {
|
|
504
|
-
// Issues: only keys with issue status
|
|
505
|
-
asyncCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "async" && (k.status === "required_wrong_type" || k.status === "required_wrong_value")).length;
|
|
506
|
-
mmkvCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "mmkv" && (k.status === "required_wrong_type" || k.status === "required_wrong_value")).length;
|
|
507
|
-
secureCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "secure" && (k.status === "required_wrong_type" || k.status === "required_wrong_value")).length;
|
|
508
|
-
totalCount = asyncCount + mmkvCount + secureCount;
|
|
509
|
-
}
|
|
510
|
-
return {
|
|
511
|
-
validCount,
|
|
512
|
-
missingCount,
|
|
513
|
-
issuesCount,
|
|
514
|
-
asyncCount,
|
|
515
|
-
mmkvCount,
|
|
516
|
-
secureCount,
|
|
517
|
-
totalCount
|
|
518
|
-
};
|
|
519
|
-
}, [allStorageKeys, activeFilter, activeStorageType]);
|
|
520
|
-
|
|
521
|
-
// Push the status counts to a host toolbar that renders them (desktop).
|
|
522
|
-
useEffect(() => {
|
|
523
|
-
onStatusCountsChange?.({
|
|
524
|
-
validCount: tabStats.validCount,
|
|
525
|
-
missingCount: tabStats.missingCount,
|
|
526
|
-
issuesCount: tabStats.issuesCount
|
|
527
|
-
});
|
|
528
|
-
}, [onStatusCountsChange, tabStats.validCount, tabStats.missingCount, tabStats.issuesCount]);
|
|
529
|
-
|
|
530
|
-
// Sort all keys by priority (issues first)
|
|
531
|
-
const sortedKeys = useMemo(() => {
|
|
532
|
-
// Sort by status priority: errors first, then warnings, then valid
|
|
533
|
-
return [...allStorageKeys].sort((a, b) => {
|
|
534
|
-
const priorityMap = {
|
|
535
|
-
required_missing: 1,
|
|
536
|
-
required_wrong_type: 2,
|
|
537
|
-
required_wrong_value: 3,
|
|
538
|
-
required_present: 4,
|
|
539
|
-
optional_present: 5
|
|
540
|
-
};
|
|
541
|
-
return (priorityMap[a.status] || 999) - (priorityMap[b.status] || 999);
|
|
542
|
-
});
|
|
543
|
-
}, [allStorageKeys]);
|
|
544
|
-
|
|
545
|
-
// Get all unique keys for filter suggestions
|
|
546
|
-
const allUniqueKeys = useMemo(() => {
|
|
547
|
-
return Array.from(new Set(allStorageKeys.map(k => k.key))).sort();
|
|
548
|
-
}, [allStorageKeys]);
|
|
549
|
-
|
|
550
|
-
// Filter keys based on active filter, storage type, ignored patterns, and search
|
|
551
|
-
const filteredKeys = useMemo(() => {
|
|
552
|
-
let keys = sortedKeys;
|
|
553
|
-
|
|
554
|
-
// Apply enabled storage types filter (from parent unified filter)
|
|
555
|
-
if (enabledStorageTypes && enabledStorageTypes.size > 0) {
|
|
556
|
-
keys = keys.filter(k => enabledStorageTypes.has(k.storageType));
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Apply ignored pattern filter
|
|
560
|
-
keys = keys.filter(k => {
|
|
561
|
-
// Check if key matches any ignored pattern
|
|
562
|
-
const shouldIgnore = Array.from(ignoredPatterns).some(pattern => k.key.includes(pattern));
|
|
563
|
-
return !shouldIgnore;
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
// Apply search filter
|
|
567
|
-
if (searchQuery) {
|
|
568
|
-
const query = searchQuery.toLowerCase();
|
|
569
|
-
keys = keys.filter(k => k.key.toLowerCase().includes(query));
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// Apply status filter
|
|
573
|
-
switch (activeFilter) {
|
|
574
|
-
case "all":
|
|
575
|
-
// "All" (Valid) shows only keys without issues
|
|
576
|
-
keys = keys.filter(k => k.status === "required_present" || k.status === "optional_present");
|
|
577
|
-
break;
|
|
578
|
-
case "missing":
|
|
579
|
-
keys = keys.filter(k => k.status === "required_missing");
|
|
580
|
-
break;
|
|
581
|
-
case "issues":
|
|
582
|
-
keys = keys.filter(k => k.status === "required_wrong_type" || k.status === "required_wrong_value");
|
|
583
|
-
break;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// Apply storage type filter
|
|
587
|
-
if (activeStorageType !== "all") {
|
|
588
|
-
keys = keys.filter(k => k.storageType === activeStorageType);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// NOTE: MMKV instance filtering is now handled in mmkvStorageKeys memo (line 128)
|
|
592
|
-
// No need to filter again here
|
|
593
|
-
|
|
594
|
-
// Float pinned keys to the top (stable — preserves existing order otherwise)
|
|
595
|
-
if (pinnedKeys && pinnedKeys.size > 0) {
|
|
596
|
-
const pinned = keys.filter(k => pinnedKeys.has(k.key));
|
|
597
|
-
const rest = keys.filter(k => !pinnedKeys.has(k.key));
|
|
598
|
-
keys = [...pinned, ...rest];
|
|
599
|
-
}
|
|
600
|
-
return keys;
|
|
601
|
-
}, [sortedKeys, activeFilter, activeStorageType, ignoredPatterns, searchQuery, selectedMMKVInstance, enabledStorageTypes, pinnedKeys]);
|
|
602
|
-
|
|
603
|
-
// Pro is Infinity here, so slice() returns everything.
|
|
604
|
-
const visibleKeys = useMemo(() => filteredKeys.slice(0, keyLimit), [filteredKeys, keyLimit]);
|
|
605
|
-
|
|
606
|
-
// Calculate how many keys are locked (only those matching current filter)
|
|
607
|
-
const lockedKeyCount = useMemo(() => {
|
|
608
|
-
return Math.max(0, filteredKeys.length - keyLimit);
|
|
609
|
-
}, [filteredKeys.length, keyLimit]);
|
|
610
|
-
const hasLockedKeys = lockedKeyCount > 0;
|
|
611
|
-
|
|
612
|
-
// Calculate stats from FILTERED keys to show actual visible counts
|
|
613
|
-
const filteredStats = useMemo(() => {
|
|
614
|
-
const appKeys = filteredKeys.filter(k => !isDevToolsStorageKey(k.key));
|
|
615
|
-
const storageStats = {
|
|
616
|
-
totalCount: filteredKeys.length,
|
|
617
|
-
requiredCount: appKeys.filter(k => k.category === "required").length,
|
|
618
|
-
missingCount: appKeys.filter(k => k.status === "required_missing").length,
|
|
619
|
-
wrongValueCount: appKeys.filter(k => k.status === "required_wrong_value").length,
|
|
620
|
-
wrongTypeCount: appKeys.filter(k => k.status === "required_wrong_type").length,
|
|
621
|
-
presentRequiredCount: appKeys.filter(k => k.status === "required_present").length,
|
|
622
|
-
optionalCount: appKeys.filter(k => k.category === "optional").length,
|
|
623
|
-
mmkvCount: filteredKeys.filter(k => k.storageType === "mmkv").length,
|
|
624
|
-
asyncCount: filteredKeys.filter(k => k.storageType === "async").length,
|
|
625
|
-
secureCount: filteredKeys.filter(k => k.storageType === "secure").length,
|
|
626
|
-
devToolsCount: filteredKeys.filter(k => isDevToolsStorageKey(k.key)).length
|
|
627
|
-
};
|
|
628
|
-
return storageStats;
|
|
629
|
-
}, [filteredKeys]);
|
|
630
|
-
|
|
631
|
-
// Get selected keys as StorageKeyInfo objects (from visible keys only)
|
|
632
|
-
const selectedKeysInfo = useMemo(() => {
|
|
633
|
-
return visibleKeys.filter(key => selectedKeyIds.has(getKeyIdentifier(key)));
|
|
634
|
-
}, [visibleKeys, selectedKeyIds, getKeyIdentifier]);
|
|
635
|
-
|
|
636
|
-
// Select all visible keys (only unlocked keys can be selected)
|
|
637
|
-
const handleSelectAll = useCallback(() => {
|
|
638
|
-
const allIds = new Set(visibleKeys.map(getKeyIdentifier));
|
|
639
|
-
setSelectedKeyIds(allIds);
|
|
640
|
-
}, [visibleKeys, getKeyIdentifier]);
|
|
641
|
-
|
|
642
|
-
// Clear all selections
|
|
643
|
-
const handleClearSelection = useCallback(() => {
|
|
644
|
-
setSelectedKeyIds(new Set());
|
|
645
|
-
}, []);
|
|
646
|
-
|
|
647
|
-
/**
|
|
648
|
-
* Serialized size of everything in storage, for the table's footer summary.
|
|
649
|
-
* Keyed on `allStorageKeys` rather than the filtered set: it answers "how much
|
|
650
|
-
* is in here", which a filter should not change.
|
|
651
|
-
*/
|
|
652
|
-
// Calculate health percentage
|
|
653
|
-
const healthPercentage = stats.requiredCount > 0 ? Math.round(stats.presentRequiredCount / stats.requiredCount * 100) : stats.totalCount > 0 ? 100 : 0;
|
|
654
|
-
const healthStatus = healthPercentage >= 90 ? "OPTIMAL" : healthPercentage >= 70 ? "WARNING" : "CRITICAL";
|
|
655
|
-
const healthColor = healthPercentage >= 90 ? gameUIColors.success : healthPercentage >= 70 ? gameUIColors.warning : gameUIColors.error;
|
|
656
|
-
|
|
657
|
-
// The detail screen REPLACES the list as the modal's whole body — the host
|
|
658
|
-
// modal's header carries back + the key's name, the React Query
|
|
659
|
-
// interaction (and the Zustand tool's). All hooks above have run; this
|
|
660
|
-
// return only swaps what's painted.
|
|
661
|
-
if (detailInfo) {
|
|
662
|
-
return /*#__PURE__*/_jsx(View, {
|
|
663
|
-
style: styles.root,
|
|
664
|
-
children: /*#__PURE__*/_jsx(StorageKeyDetailScreen, {
|
|
665
|
-
storageKey: detailInfo,
|
|
666
|
-
editBlockedReason: editBlockedFor(detailInfo),
|
|
667
|
-
eventCount: eventCountByKey?.[detailInfo.key],
|
|
668
|
-
onSave: raw => handleSaveValue(detailInfo, raw),
|
|
669
|
-
onViewHistory: onViewHistory ? () => onViewHistory(detailInfo.key) : undefined,
|
|
670
|
-
onDeleteKey: k => handleDeleteKey(k, () => onCloseDetail?.()),
|
|
671
|
-
onHideKey: k => {
|
|
672
|
-
handleHideKeys([k]);
|
|
673
|
-
onCloseDetail?.();
|
|
674
|
-
},
|
|
675
|
-
isPinned: pinnedKeys?.has(detailInfo.key) ?? false,
|
|
676
|
-
onTogglePin: onTogglePin,
|
|
677
|
-
applySafeAreaInset: applySafeAreaInset
|
|
678
|
-
})
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// Loading state
|
|
683
|
-
if (isLoading && allStorageKeys.length === 0) {
|
|
684
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
685
|
-
style: styles.emptyState,
|
|
686
|
-
children: [/*#__PURE__*/_jsx(Database, {
|
|
687
|
-
size: 48,
|
|
688
|
-
color: night.color.textTertiary
|
|
689
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
690
|
-
style: styles.emptyTitle,
|
|
691
|
-
children: "Loading storage keys..."
|
|
692
|
-
})]
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// Error state
|
|
697
|
-
if (error) {
|
|
698
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
699
|
-
style: styles.emptyState,
|
|
700
|
-
children: [/*#__PURE__*/_jsx(Database, {
|
|
701
|
-
size: 48,
|
|
702
|
-
color: gameUIColors.error
|
|
703
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
704
|
-
style: [styles.emptyTitle, {
|
|
705
|
-
color: gameUIColors.error
|
|
706
|
-
}],
|
|
707
|
-
children: "Error loading storage"
|
|
708
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
709
|
-
style: styles.emptySubtitle,
|
|
710
|
-
children: error.message
|
|
711
|
-
})]
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
/* ---------------------------------------------------------------- *
|
|
716
|
-
* Subtrees the card list composes. Extracted rather than inlined so Pro
|
|
717
|
-
* gating and MMKV
|
|
718
|
-
* instance selection or the empty-state copy.
|
|
719
|
-
* ---------------------------------------------------------------- */
|
|
720
|
-
|
|
721
|
-
const mmkvSection = /*#__PURE__*/_jsxs(_Fragment, {
|
|
722
|
-
children: [activeStorageType === "mmkv" && mmkvInstances.length === 0 && /*#__PURE__*/_jsxs(View, {
|
|
723
|
-
style: styles.emptyMMKVState,
|
|
724
|
-
children: [/*#__PURE__*/_jsx(Text, {
|
|
725
|
-
style: styles.emptyMMKVTitle,
|
|
726
|
-
children: "No MMKV Instances Detected"
|
|
727
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
728
|
-
style: styles.emptyMMKVSubtitle,
|
|
729
|
-
children: "MMKV instances must be registered with registerMMKVInstance()"
|
|
730
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
731
|
-
style: styles.emptyMMKVCode,
|
|
732
|
-
children: `import { registerMMKVInstance } from '@buoy-gg/storage';\n\nconst storage = createMMKV();\nregisterMMKVInstance('mmkv.default', storage);`
|
|
733
|
-
})]
|
|
734
|
-
}), activeStorageType === "mmkv" && mmkvInstances.length > 0 && /*#__PURE__*/_jsxs(View, {
|
|
735
|
-
style: styles.instanceNavbar,
|
|
736
|
-
children: [/*#__PURE__*/_jsxs(View, {
|
|
737
|
-
style: styles.instanceNavbarHeader,
|
|
738
|
-
children: [/*#__PURE__*/_jsx(Text, {
|
|
739
|
-
style: styles.instanceNavbarLabel,
|
|
740
|
-
children: "INSTANCES"
|
|
741
|
-
}), mmkvInstances.length > mmkvInstanceLimit && /*#__PURE__*/_jsx(ProBadge, {
|
|
742
|
-
style: {
|
|
743
|
-
transform: [{
|
|
744
|
-
scale: 0.85
|
|
745
|
-
}]
|
|
746
|
-
}
|
|
747
|
-
})]
|
|
748
|
-
}), /*#__PURE__*/_jsxs(ScrollView, {
|
|
749
|
-
horizontal: true,
|
|
750
|
-
showsHorizontalScrollIndicator: false,
|
|
751
|
-
contentContainerStyle: styles.instanceNavbarScroll,
|
|
752
|
-
children: [(isPro || mmkvInstances.length === 1) && /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
753
|
-
onPress: () => setSelectedMMKVInstance(null),
|
|
754
|
-
testID: "buoy-storage-instance-all",
|
|
755
|
-
accessibilityRole: "button",
|
|
756
|
-
accessibilityLabel: `All ${storageLabels.mmkv} instances`,
|
|
757
|
-
style: [styles.instanceNavbarButton, selectedMMKVInstance === null && styles.instanceNavbarButtonActive],
|
|
758
|
-
children: [/*#__PURE__*/_jsx(HardDrive, {
|
|
759
|
-
size: 12,
|
|
760
|
-
color: selectedMMKVInstance === null ? night.color.text : night.color.textSecondary
|
|
761
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
762
|
-
style: [styles.instanceNavbarButtonText, selectedMMKVInstance === null && styles.instanceNavbarButtonTextActive],
|
|
763
|
-
children: "All"
|
|
764
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
765
|
-
style: styles.instanceNavbarBadge,
|
|
766
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
767
|
-
style: styles.instanceNavbarBadgeText,
|
|
768
|
-
children: mmkvInstances.reduce((sum, inst) => sum + inst.keyCount, 0)
|
|
769
|
-
})
|
|
770
|
-
})]
|
|
771
|
-
}), mmkvInstances.map((inst, index) => {
|
|
772
|
-
const isLocked = index >= mmkvInstanceLimit;
|
|
773
|
-
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
774
|
-
testID: `buoy-storage-instance-${inst.id}`,
|
|
775
|
-
accessibilityRole: "button",
|
|
776
|
-
accessibilityLabel: `MMKV instance ${inst.id}`,
|
|
777
|
-
onPress: () => {
|
|
778
|
-
if (isLocked) {
|
|
779
|
-
setShowUpgradeModal(true);
|
|
780
|
-
} else {
|
|
781
|
-
setSelectedMMKVInstance(inst.id);
|
|
782
|
-
}
|
|
783
|
-
},
|
|
784
|
-
style: [styles.instanceNavbarButton, inst.id === selectedMMKVInstance && styles.instanceNavbarButtonActive, {
|
|
785
|
-
borderColor: isLocked ? night.color.border : getInstanceColor(inst.id) + '40',
|
|
786
|
-
backgroundColor: inst.id === selectedMMKVInstance ? getInstanceColor(inst.id) + '20' : night.color.surfaceElevated,
|
|
787
|
-
opacity: isLocked ? 0.5 : 1
|
|
788
|
-
}],
|
|
789
|
-
children: [/*#__PURE__*/_jsx(HardDrive, {
|
|
790
|
-
size: 12,
|
|
791
|
-
color: inst.id === selectedMMKVInstance ? getInstanceColor(inst.id) : night.color.textSecondary
|
|
792
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
793
|
-
style: [styles.instanceNavbarButtonText, inst.id === selectedMMKVInstance && {
|
|
794
|
-
color: getInstanceColor(inst.id),
|
|
795
|
-
fontWeight: '700'
|
|
796
|
-
}],
|
|
797
|
-
children: inst.id
|
|
798
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
799
|
-
style: [styles.instanceNavbarBadge, inst.id === selectedMMKVInstance && {
|
|
800
|
-
backgroundColor: getInstanceColor(inst.id) + '30'
|
|
801
|
-
}],
|
|
802
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
803
|
-
style: [styles.instanceNavbarBadgeText, inst.id === selectedMMKVInstance && {
|
|
804
|
-
color: getInstanceColor(inst.id)
|
|
805
|
-
}],
|
|
806
|
-
children: inst.keyCount
|
|
807
|
-
})
|
|
808
|
-
})]
|
|
809
|
-
}, inst.id);
|
|
810
|
-
})]
|
|
811
|
-
})]
|
|
812
|
-
})]
|
|
813
|
-
});
|
|
814
|
-
const actionButtonsNode = /*#__PURE__*/_jsx(StorageActionButtons, {
|
|
815
|
-
copyValue: copyExportData,
|
|
816
|
-
mmkvInstances: mmkvInstances.map(inst => ({
|
|
817
|
-
id: inst.id,
|
|
818
|
-
instance: inst.instance
|
|
819
|
-
})),
|
|
820
|
-
activeStorageType: activeStorageType,
|
|
821
|
-
onClearComplete: refresh,
|
|
822
|
-
isSelectMode: isSelectMode,
|
|
823
|
-
onToggleSelectMode: handleToggleSelectMode,
|
|
824
|
-
selectedCount: selectedKeyIds.size
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
/* Selection Action Bar - Only show in select mode with selections */
|
|
828
|
-
const selectionBarNode = isSelectMode ? /*#__PURE__*/_jsx(SelectionActionBar, {
|
|
829
|
-
selectedKeys: selectedKeysInfo,
|
|
830
|
-
mmkvInstances: mmkvInstances.map(inst => ({
|
|
831
|
-
id: inst.id,
|
|
832
|
-
instance: inst.instance
|
|
833
|
-
})),
|
|
834
|
-
onDeleteComplete: handleDeleteComplete,
|
|
835
|
-
onHideKeys: handleHideKeys,
|
|
836
|
-
onSelectAll: handleSelectAll,
|
|
837
|
-
onClearSelection: handleClearSelection,
|
|
838
|
-
totalVisibleKeys: visibleKeys.length
|
|
839
|
-
}) : null;
|
|
840
|
-
const lockedBannerNode = hasLockedKeys ? /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
841
|
-
style: styles.limitBanner,
|
|
842
|
-
onPress: () => setShowUpgradeModal(true),
|
|
843
|
-
activeOpacity: 0.8,
|
|
844
|
-
testID: "buoy-storage-locked-banner",
|
|
845
|
-
accessibilityRole: "button",
|
|
846
|
-
children: [/*#__PURE__*/_jsxs(View, {
|
|
847
|
-
style: styles.limitBannerContent,
|
|
848
|
-
children: [/*#__PURE__*/_jsxs(View, {
|
|
849
|
-
style: styles.limitBannerLeft,
|
|
850
|
-
children: [/*#__PURE__*/_jsx(Zap, {
|
|
851
|
-
size: 16,
|
|
852
|
-
color: night.color.accent
|
|
853
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
854
|
-
style: styles.limitBannerText,
|
|
855
|
-
children: searchQuery ? `${lockedKeyCount} matching ${lockedKeyCount === 1 ? 'key' : 'keys'} in locked storage` : `${lockedKeyCount} keys locked`
|
|
856
|
-
})]
|
|
857
|
-
}), /*#__PURE__*/_jsx(ProBadge, {})]
|
|
858
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
859
|
-
style: styles.limitBannerSubtext,
|
|
860
|
-
children: searchQuery ? "Upgrade to Pro to search your full storage" : "Upgrade to Pro to unlock full storage access"
|
|
861
|
-
})]
|
|
862
|
-
}) : null;
|
|
863
|
-
const emptyStateNode = (() => {
|
|
864
|
-
// Short-circuits on the first real key, so this costs nothing.
|
|
865
|
-
const hasAnyKeys = allStorageKeys.some(k => !isDevToolsStorageKey(k.key));
|
|
866
|
-
|
|
867
|
-
// Three causes here are unlike any other tool's: an empty MISSING or
|
|
868
|
-
// ISSUES list is a PASS, not a failure, and an empty VALID list means
|
|
869
|
-
// everything is broken. Those skip the shared resolver and say so.
|
|
870
|
-
let content;
|
|
871
|
-
if (searchQuery || !hasAnyKeys) {
|
|
872
|
-
content = resolveListEmptyState({
|
|
873
|
-
subject: "keys",
|
|
874
|
-
verb: "registered",
|
|
875
|
-
search: searchQuery,
|
|
876
|
-
total: hasAnyKeys ? 1 : 0,
|
|
877
|
-
waiting: {
|
|
878
|
-
// Previously this fell into the "all" branch and claimed every key
|
|
879
|
-
// had an issue — of the zero keys that exist.
|
|
880
|
-
otto: "empty",
|
|
881
|
-
title: "No storage keys",
|
|
882
|
-
message: "Nothing is registered for this tool to read."
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
if (searchQuery && hasAnyKeys) {
|
|
886
|
-
// No count: the pre-search total would mean re-running the whole key
|
|
887
|
-
// pipeline a second time for a number the sentence does not need.
|
|
888
|
-
content = {
|
|
889
|
-
...content,
|
|
890
|
-
message: "No keys match. Clear the search to see them."
|
|
891
|
-
};
|
|
892
|
-
}
|
|
893
|
-
} else if (activeFilter === "missing") {
|
|
894
|
-
content = {
|
|
895
|
-
otto: "thumbsup",
|
|
896
|
-
title: "No missing keys",
|
|
897
|
-
message: "All required keys are present"
|
|
898
|
-
};
|
|
899
|
-
} else if (activeFilter === "issues") {
|
|
900
|
-
content = {
|
|
901
|
-
otto: "cool",
|
|
902
|
-
title: "No issues found",
|
|
903
|
-
message: "All storage keys are correctly configured"
|
|
904
|
-
};
|
|
905
|
-
} else {
|
|
906
|
-
content = {
|
|
907
|
-
otto: "broken",
|
|
908
|
-
title: "No valid keys found",
|
|
909
|
-
message: "All keys have issues or are missing"
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
return /*#__PURE__*/_jsx(ToolEmptyState, {
|
|
913
|
-
...content,
|
|
914
|
-
testID: "buoy-storage-keys-empty"
|
|
915
|
-
});
|
|
916
|
-
})();
|
|
917
|
-
const upgradeModalNode = /*#__PURE__*/_jsx(ProUpgradeModal, {
|
|
918
|
-
visible: showUpgradeModal,
|
|
919
|
-
onClose: () => setShowUpgradeModal(false),
|
|
920
|
-
featureName: "Storage Keys"
|
|
921
|
-
});
|
|
922
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
923
|
-
style: styles.root,
|
|
924
|
-
children: [saveToastId > 0 ? /*#__PURE__*/_jsx(StorageSaveNotice, {
|
|
925
|
-
onDone: () => setSaveToastId(0)
|
|
926
|
-
}, saveToastId) : null, /*#__PURE__*/_jsxs(ScrollView
|
|
927
|
-
// Named because `by.type("RCTScrollView")` doesn't match on Fabric
|
|
928
|
-
// (renamed class) — automation scrolls below-the-fold rows by this id.
|
|
929
|
-
, {
|
|
930
|
-
testID: "buoy-storage-scroll",
|
|
931
|
-
ref: scrollRef,
|
|
932
|
-
onScroll: e => {
|
|
933
|
-
scrollOffsetRef.current = e.nativeEvent.contentOffset.y;
|
|
934
|
-
},
|
|
935
|
-
scrollEventThrottle: 32,
|
|
936
|
-
style: styles.scrollContainer,
|
|
937
|
-
contentContainerStyle: styles.container,
|
|
938
|
-
showsVerticalScrollIndicator: false,
|
|
939
|
-
children: [/*#__PURE__*/_jsx(View, {
|
|
940
|
-
style: styles.backgroundGrid
|
|
941
|
-
}), /*#__PURE__*/_jsx(StorageFilterCards, {
|
|
942
|
-
stats: stats,
|
|
943
|
-
tabStats: tabStats,
|
|
944
|
-
healthPercentage: healthPercentage,
|
|
945
|
-
healthStatus: healthStatus,
|
|
946
|
-
healthColor: healthColor,
|
|
947
|
-
activeFilter: activeFilter,
|
|
948
|
-
onFilterChange: setActiveFilter,
|
|
949
|
-
activeStorageType: activeStorageType,
|
|
950
|
-
onStorageTypeChange: setActiveStorageType
|
|
951
|
-
// Same rule the wide bar follows: when a host toolbar owns the status
|
|
952
|
-
// filter it also renders the counters, so ours would be a second copy.
|
|
953
|
-
,
|
|
954
|
-
showStatus: !isFilterControlled
|
|
955
|
-
}), mmkvSection, filteredKeys.length > 0 ? /*#__PURE__*/_jsxs(View, {
|
|
956
|
-
style: styles.keysSection,
|
|
957
|
-
children: [/*#__PURE__*/_jsxs(View, {
|
|
958
|
-
style: styles.sectionHeader,
|
|
959
|
-
children: [/*#__PURE__*/_jsxs(View, {
|
|
960
|
-
style: styles.sectionHeaderLeft,
|
|
961
|
-
children: [/*#__PURE__*/_jsxs(Text, {
|
|
962
|
-
style: styles.sectionTitle,
|
|
963
|
-
children: [activeFilter === "all" ? "KEYS" : activeFilter === "missing" ? "MISSING KEYS" : "ISSUES TO FIX", activeStorageType !== "all" && ` (${activeStorageType.toUpperCase()})`]
|
|
964
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
965
|
-
style: styles.countBadge,
|
|
966
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
967
|
-
style: styles.countText,
|
|
968
|
-
children: visibleKeys.length
|
|
969
|
-
})
|
|
970
|
-
})]
|
|
971
|
-
}), actionButtonsNode]
|
|
972
|
-
}), selectionBarNode, /*#__PURE__*/_jsx(StorageKeySection, {
|
|
973
|
-
title: "",
|
|
974
|
-
count: -1,
|
|
975
|
-
keys: visibleKeys,
|
|
976
|
-
emptyMessage: "",
|
|
977
|
-
isSelectMode: isSelectMode,
|
|
978
|
-
selectedKeys: selectedKeyIds,
|
|
979
|
-
onSelectionChange: handleSelectionChange,
|
|
980
|
-
eventCountByKey: eventCountByKey,
|
|
981
|
-
onViewHistory: onViewHistory,
|
|
982
|
-
onHideKey: key => handleHideKeys([key]),
|
|
983
|
-
onToggleKey: handleToggleKey,
|
|
984
|
-
onDeleteKey: handleDeleteKey,
|
|
985
|
-
editBlockedFor: editBlockedFor,
|
|
986
|
-
pinnedKeys: pinnedKeys,
|
|
987
|
-
onTogglePin: onTogglePin,
|
|
988
|
-
onRowRef: handleRowRef,
|
|
989
|
-
onOpenKey: onOpenKey
|
|
990
|
-
}), lockedBannerNode]
|
|
991
|
-
}) : emptyStateNode, /*#__PURE__*/_jsx(Text, {
|
|
992
|
-
style: styles.techFooter,
|
|
993
|
-
children: "ASYNC STORAGE | MMKV | SECURE STORAGE BACKENDS"
|
|
994
|
-
}), upgradeModalNode]
|
|
995
|
-
})]
|
|
996
|
-
});
|
|
997
|
-
}
|
|
998
|
-
const styles = StyleSheet.create({
|
|
999
|
-
/** Measures the space the columns actually get; `flex: 1` so it fills the
|
|
1000
|
-
* parent exactly as the bare ScrollView it wraps used to. */
|
|
1001
|
-
root: {
|
|
1002
|
-
flex: 1,
|
|
1003
|
-
minHeight: 0
|
|
1004
|
-
},
|
|
1005
|
-
/**
|
|
1006
|
-
* No background of its own: the tree editor paints an opaque screen, while a
|
|
1007
|
-
* scalar key shows only a modal and wants the list visible behind its scrim.
|
|
1008
|
-
*/
|
|
1009
|
-
editorOverlay: {
|
|
1010
|
-
...absoluteFill,
|
|
1011
|
-
zIndex: 10
|
|
1012
|
-
},
|
|
1013
|
-
scrollContainer: {
|
|
1014
|
-
flex: 1,
|
|
1015
|
-
// Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
|
|
1016
|
-
backgroundColor: "transparent"
|
|
1017
|
-
},
|
|
1018
|
-
container: {
|
|
1019
|
-
padding: 12,
|
|
1020
|
-
// Generous so a key card expanded at the bottom of the list can scroll
|
|
1021
|
-
// its full detail block above the modal edge.
|
|
1022
|
-
paddingBottom: 120
|
|
1023
|
-
},
|
|
1024
|
-
backgroundGrid: {
|
|
1025
|
-
...absoluteFill,
|
|
1026
|
-
opacity: 0.006,
|
|
1027
|
-
backgroundColor: gameUIColors.info
|
|
1028
|
-
},
|
|
1029
|
-
techFooter: {
|
|
1030
|
-
fontSize: 8,
|
|
1031
|
-
color: night.color.textTertiary,
|
|
1032
|
-
fontFamily: monoFont,
|
|
1033
|
-
textAlign: "center",
|
|
1034
|
-
marginTop: 20,
|
|
1035
|
-
letterSpacing: 1,
|
|
1036
|
-
opacity: 0.5
|
|
1037
|
-
},
|
|
1038
|
-
// Keys section
|
|
1039
|
-
keysSection: {
|
|
1040
|
-
marginTop: 8,
|
|
1041
|
-
backgroundColor: night.color.bg,
|
|
1042
|
-
borderRadius: 12,
|
|
1043
|
-
padding: 12,
|
|
1044
|
-
borderWidth: 1,
|
|
1045
|
-
borderColor: night.color.border
|
|
1046
|
-
},
|
|
1047
|
-
sectionHeader: {
|
|
1048
|
-
flexDirection: "row",
|
|
1049
|
-
justifyContent: "space-between",
|
|
1050
|
-
alignItems: "center",
|
|
1051
|
-
marginBottom: 10,
|
|
1052
|
-
paddingBottom: 6,
|
|
1053
|
-
borderBottomWidth: 1,
|
|
1054
|
-
borderBottomColor: night.color.separator
|
|
1055
|
-
},
|
|
1056
|
-
sectionHeaderLeft: {
|
|
1057
|
-
flexDirection: "row",
|
|
1058
|
-
alignItems: "center",
|
|
1059
|
-
gap: 8
|
|
1060
|
-
},
|
|
1061
|
-
sectionTitle: {
|
|
1062
|
-
fontSize: 12,
|
|
1063
|
-
fontWeight: "600",
|
|
1064
|
-
color: night.color.textSecondary,
|
|
1065
|
-
letterSpacing: 0.4,
|
|
1066
|
-
textTransform: "uppercase"
|
|
1067
|
-
},
|
|
1068
|
-
countBadge: {
|
|
1069
|
-
backgroundColor: night.color.surfaceElevated,
|
|
1070
|
-
paddingHorizontal: 8,
|
|
1071
|
-
paddingVertical: 3,
|
|
1072
|
-
borderRadius: 6,
|
|
1073
|
-
borderWidth: 1,
|
|
1074
|
-
borderColor: night.color.border
|
|
1075
|
-
},
|
|
1076
|
-
countText: {
|
|
1077
|
-
fontSize: 11,
|
|
1078
|
-
fontWeight: "600",
|
|
1079
|
-
color: night.color.text,
|
|
1080
|
-
fontFamily: monoFont
|
|
1081
|
-
},
|
|
1082
|
-
// Empty state
|
|
1083
|
-
emptyState: {
|
|
1084
|
-
alignItems: "center",
|
|
1085
|
-
justifyContent: "center",
|
|
1086
|
-
paddingVertical: 48
|
|
1087
|
-
},
|
|
1088
|
-
emptyTitle: {
|
|
1089
|
-
fontSize: 16,
|
|
1090
|
-
fontWeight: "600",
|
|
1091
|
-
color: night.color.text,
|
|
1092
|
-
marginTop: 12,
|
|
1093
|
-
marginBottom: 8
|
|
1094
|
-
},
|
|
1095
|
-
emptySubtitle: {
|
|
1096
|
-
fontSize: 13,
|
|
1097
|
-
color: night.color.textSecondary,
|
|
1098
|
-
textAlign: "center"
|
|
1099
|
-
},
|
|
1100
|
-
// Empty MMKV state
|
|
1101
|
-
emptyMMKVState: {
|
|
1102
|
-
backgroundColor: night.color.surface,
|
|
1103
|
-
borderRadius: 12,
|
|
1104
|
-
padding: 20,
|
|
1105
|
-
borderWidth: 1,
|
|
1106
|
-
borderColor: night.color.border,
|
|
1107
|
-
marginTop: 8,
|
|
1108
|
-
alignItems: "center"
|
|
1109
|
-
},
|
|
1110
|
-
emptyMMKVTitle: {
|
|
1111
|
-
fontSize: 14,
|
|
1112
|
-
fontWeight: "600",
|
|
1113
|
-
color: night.color.text,
|
|
1114
|
-
marginBottom: 6
|
|
1115
|
-
},
|
|
1116
|
-
emptyMMKVSubtitle: {
|
|
1117
|
-
fontSize: 12,
|
|
1118
|
-
color: night.color.textSecondary,
|
|
1119
|
-
textAlign: "center",
|
|
1120
|
-
marginBottom: 12
|
|
1121
|
-
},
|
|
1122
|
-
emptyMMKVCode: {
|
|
1123
|
-
fontSize: 10,
|
|
1124
|
-
fontFamily: monoFont,
|
|
1125
|
-
color: night.color.accent,
|
|
1126
|
-
backgroundColor: night.color.bg,
|
|
1127
|
-
padding: 12,
|
|
1128
|
-
borderRadius: 8,
|
|
1129
|
-
borderWidth: 1,
|
|
1130
|
-
borderColor: night.color.border,
|
|
1131
|
-
alignSelf: "stretch"
|
|
1132
|
-
},
|
|
1133
|
-
// Instance Navbar styles
|
|
1134
|
-
instanceNavbar: {
|
|
1135
|
-
marginTop: 12,
|
|
1136
|
-
marginBottom: 8,
|
|
1137
|
-
backgroundColor: night.color.surface,
|
|
1138
|
-
borderRadius: 10,
|
|
1139
|
-
borderWidth: 1,
|
|
1140
|
-
borderColor: night.color.border,
|
|
1141
|
-
padding: 10,
|
|
1142
|
-
shadowColor: '#000',
|
|
1143
|
-
shadowOpacity: 0.08,
|
|
1144
|
-
shadowRadius: 6,
|
|
1145
|
-
shadowOffset: {
|
|
1146
|
-
width: 0,
|
|
1147
|
-
height: 2
|
|
1148
|
-
}
|
|
1149
|
-
},
|
|
1150
|
-
instanceNavbarHeader: {
|
|
1151
|
-
flexDirection: 'row',
|
|
1152
|
-
alignItems: 'center',
|
|
1153
|
-
justifyContent: 'space-between',
|
|
1154
|
-
marginBottom: 8,
|
|
1155
|
-
paddingLeft: 2
|
|
1156
|
-
},
|
|
1157
|
-
instanceNavbarLabel: {
|
|
1158
|
-
fontSize: 9,
|
|
1159
|
-
fontWeight: '700',
|
|
1160
|
-
color: night.color.textTertiary,
|
|
1161
|
-
textTransform: "uppercase",
|
|
1162
|
-
letterSpacing: 1
|
|
1163
|
-
},
|
|
1164
|
-
instanceNavbarScroll: {
|
|
1165
|
-
gap: 8,
|
|
1166
|
-
paddingRight: 8
|
|
1167
|
-
},
|
|
1168
|
-
instanceNavbarButton: {
|
|
1169
|
-
flexDirection: 'row',
|
|
1170
|
-
alignItems: 'center',
|
|
1171
|
-
gap: 6,
|
|
1172
|
-
paddingHorizontal: 12,
|
|
1173
|
-
paddingVertical: 8,
|
|
1174
|
-
backgroundColor: night.color.surfaceElevated,
|
|
1175
|
-
borderRadius: 8,
|
|
1176
|
-
borderWidth: 1,
|
|
1177
|
-
borderColor: night.color.border,
|
|
1178
|
-
minWidth: 90
|
|
1179
|
-
},
|
|
1180
|
-
instanceNavbarButtonActive: {
|
|
1181
|
-
borderWidth: 1.5,
|
|
1182
|
-
shadowColor: '#000',
|
|
1183
|
-
shadowOpacity: 0.1,
|
|
1184
|
-
shadowRadius: 4,
|
|
1185
|
-
shadowOffset: {
|
|
1186
|
-
width: 0,
|
|
1187
|
-
height: 1
|
|
1188
|
-
}
|
|
1189
|
-
},
|
|
1190
|
-
instanceNavbarButtonText: {
|
|
1191
|
-
fontSize: 11,
|
|
1192
|
-
fontWeight: '600',
|
|
1193
|
-
color: night.color.textSecondary,
|
|
1194
|
-
fontFamily: monoFont,
|
|
1195
|
-
flex: 1
|
|
1196
|
-
},
|
|
1197
|
-
instanceNavbarButtonTextActive: {
|
|
1198
|
-
fontWeight: '700',
|
|
1199
|
-
color: night.color.text
|
|
1200
|
-
},
|
|
1201
|
-
instanceNavbarBadge: {
|
|
1202
|
-
backgroundColor: night.color.bg,
|
|
1203
|
-
paddingHorizontal: 6,
|
|
1204
|
-
paddingVertical: 2,
|
|
1205
|
-
borderRadius: 6,
|
|
1206
|
-
minWidth: 24,
|
|
1207
|
-
alignItems: 'center'
|
|
1208
|
-
},
|
|
1209
|
-
instanceNavbarBadgeText: {
|
|
1210
|
-
fontSize: 10,
|
|
1211
|
-
fontWeight: '700',
|
|
1212
|
-
color: night.color.textSecondary,
|
|
1213
|
-
fontFamily: monoFont
|
|
1214
|
-
},
|
|
1215
|
-
clearSearchButton: {
|
|
1216
|
-
marginTop: 16,
|
|
1217
|
-
paddingHorizontal: 16,
|
|
1218
|
-
paddingVertical: 8,
|
|
1219
|
-
backgroundColor: night.color.surfaceElevated,
|
|
1220
|
-
borderRadius: 6,
|
|
1221
|
-
borderWidth: 1,
|
|
1222
|
-
borderColor: night.color.border
|
|
1223
|
-
},
|
|
1224
|
-
clearSearchText: {
|
|
1225
|
-
fontSize: 13,
|
|
1226
|
-
color: night.color.text,
|
|
1227
|
-
fontWeight: "500"
|
|
1228
|
-
},
|
|
1229
|
-
// Free tier limit banner
|
|
1230
|
-
limitBanner: {
|
|
1231
|
-
backgroundColor: night.color.surface,
|
|
1232
|
-
borderRadius: 10,
|
|
1233
|
-
borderWidth: 1,
|
|
1234
|
-
borderColor: night.color.accentBorderStrong,
|
|
1235
|
-
padding: 12,
|
|
1236
|
-
marginTop: 12
|
|
1237
|
-
},
|
|
1238
|
-
limitBannerContent: {
|
|
1239
|
-
flexDirection: "row",
|
|
1240
|
-
alignItems: "center",
|
|
1241
|
-
justifyContent: "space-between",
|
|
1242
|
-
marginBottom: 4
|
|
1243
|
-
},
|
|
1244
|
-
limitBannerLeft: {
|
|
1245
|
-
flexDirection: "row",
|
|
1246
|
-
alignItems: "center",
|
|
1247
|
-
gap: 8
|
|
1248
|
-
},
|
|
1249
|
-
limitBannerText: {
|
|
1250
|
-
color: night.color.text,
|
|
1251
|
-
fontSize: 13,
|
|
1252
|
-
fontWeight: "600"
|
|
1253
|
-
},
|
|
1254
|
-
limitBannerSubtext: {
|
|
1255
|
-
color: night.color.textTertiary,
|
|
1256
|
-
fontSize: 11
|
|
1257
|
-
}
|
|
1258
|
-
});
|
|
3
|
+
const storage = createMMKV();
|
|
4
|
+
registerMMKVInstance('mmkv.default', storage);`})]}),C==="mmkv"&&y.length>0&&d(f,{style:n.instanceNavbar,children:[d(f,{style:n.instanceNavbarHeader,children:[a(m,{style:n.instanceNavbarLabel,children:"INSTANCES"}),y.length>Ce&&a(ze,{style:{transform:[{scale:.85}]}})]}),d(Re,{horizontal:true,showsHorizontalScrollIndicator:false,contentContainerStyle:n.instanceNavbarScroll,children:[(M||y.length===1)&&d(ae,{onPress:()=>Te(null),testID:"buoy-storage-instance-all",accessibilityRole:"button",accessibilityLabel:`All ${wt.mmkv} instances`,style:[n.instanceNavbarButton,g===null&&n.instanceNavbarButtonActive],children:[a(Fe,{size:12,color:g===null?s.color.text:s.color.textSecondary}),a(m,{style:[n.instanceNavbarButtonText,g===null&&n.instanceNavbarButtonTextActive],children:"All"}),a(f,{style:n.instanceNavbarBadge,children:a(m,{style:n.instanceNavbarBadgeText,children:y.reduce((e,t)=>e+t.keyCount,0)})})]}),y.map((e,t)=>{const r=t>=Ce;return d(ae,{testID:`buoy-storage-instance-${e.id}`,accessibilityRole:"button",accessibilityLabel:`MMKV instance ${e.id}`,onPress:()=>{if(r){q(true)}else{Te(e.id)}},style:[n.instanceNavbarButton,e.id===g&&n.instanceNavbarButtonActive,{borderColor:r?s.color.border:L(e.id)+"40",backgroundColor:e.id===g?L(e.id)+"20":s.color.surfaceElevated,opacity:r?.5:1}],children:[a(Fe,{size:12,color:e.id===g?L(e.id):s.color.textSecondary}),a(m,{style:[n.instanceNavbarButtonText,e.id===g&&{color:L(e.id),fontWeight:"700"}],children:e.id}),a(f,{style:[n.instanceNavbarBadge,e.id===g&&{backgroundColor:L(e.id)+"30"}],children:a(m,{style:[n.instanceNavbarBadgeText,e.id===g&&{color:L(e.id)}],children:e.keyCount})})]},e.id)})]})]})]});const bt=a(Ft,{copyValue:ut,mmkvInstances:y.map(e=>({id:e.id,instance:e.instance})),activeStorageType:C,onClearComplete:k,isSelectMode:te,onToggleSelectMode:at,selectedCount:U.size});const Ct=te?a(Wt,{selectedKeys:yt,mmkvInstances:y.map(e=>({id:e.id,instance:e.instance})),onDeleteComplete:ct,onHideKeys:ne,onSelectAll:mt,onClearSelection:ft,totalVisibleKeys:B.length}):null;const vt=gt?d(ae,{style:n.limitBanner,onPress:()=>q(true),activeOpacity:.8,testID:"buoy-storage-locked-banner",accessibilityRole:"button",children:[d(f,{style:n.limitBannerContent,children:[d(f,{style:n.limitBannerLeft,children:[a(Mt,{size:16,color:s.color.accent}),a(m,{style:n.limitBannerText,children:w?`${Y} matching ${Y===1?"key":"keys"} in locked storage`:`${Y} keys locked`})]}),a(ze,{})]}),a(m,{style:n.limitBannerSubtext,children:w?"Upgrade to Pro to search your full storage":"Upgrade to Pro to unlock full storage access"})]}):null;const Tt=(()=>{const e=c.some(r=>!p(r.key));let t;if(w||!e){t=Bt({subject:"keys",verb:"registered",search:w,total:e?1:0,waiting:{otto:"empty",title:"No storage keys",message:"Nothing is registered for this tool to read."}});if(w&&e){t={...t,message:"No keys match. Clear the search to see them."}}}else if(T==="missing"){t={otto:"thumbsup",title:"No missing keys",message:"All required keys are present"}}else if(T==="issues"){t={otto:"cool",title:"No issues found",message:"All storage keys are correctly configured"}}else{t={otto:"broken",title:"No valid keys found",message:"All keys have issues or are missing"}}return a(xt,{...t,testID:"buoy-storage-keys-empty"})})();const kt=a(Kt,{visible:Q,onClose:()=>q(false),featureName:"Storage Keys"});return d(f,{style:n.root,children:[we>0?a(Dt,{onDone:()=>Ie(0)},we):null,d(Re,{testID:"buoy-storage-scroll",ref:Je,onScroll:e=>{Xe.current=e.nativeEvent.contentOffset.y},scrollEventThrottle:32,style:n.scrollContainer,contentContainerStyle:n.container,showsVerticalScrollIndicator:false,children:[a(f,{style:n.backgroundGrid}),a(Vt,{stats:D,tabStats:x,healthPercentage:F,healthStatus:ht,healthColor:pt,activeFilter:T,onFilterChange:Ue,activeStorageType:C,onStorageTypeChange:Ye,showStatus:!ee}),St,v.length>0?d(f,{style:n.keysSection,children:[d(f,{style:n.sectionHeader,children:[d(f,{style:n.sectionHeaderLeft,children:[d(m,{style:n.sectionTitle,children:[T==="all"?"KEYS":T==="missing"?"MISSING KEYS":"ISSUES TO FIX",C!=="all"&&` (${C.toUpperCase()})`]}),a(f,{style:n.countBadge,children:a(m,{style:n.countText,children:B.length})})]}),bt]}),Ct,a(Nt,{title:"",count:-1,keys:B,emptyMessage:"",isSelectMode:te,selectedKeys:U,onSelectionChange:lt,eventCountByKey:ge,onViewHistory:Z,onHideKey:e=>ne([e]),onToggleKey:dt,onDeleteKey:Ne,editBlockedFor:qe,pinnedKeys:_,onTogglePin:he,onRowRef:Ze,onOpenKey:He}),vt]}):Tt,a(m,{style:n.techFooter,children:"ASYNC STORAGE | MMKV | SECURE STORAGE BACKENDS"}),kt]})]})}const n=It.create({root:{flex:1,minHeight:0},editorOverlay:{...Ae,zIndex:10},scrollContainer:{flex:1,backgroundColor:"transparent"},container:{padding:12,paddingBottom:120},backgroundGrid:{...Ae,opacity:.006,backgroundColor:E.info},techFooter:{fontSize:8,color:s.color.textTertiary,fontFamily:W,textAlign:"center",marginTop:20,letterSpacing:1,opacity:.5},keysSection:{marginTop:8,backgroundColor:s.color.bg,borderRadius:12,padding:12,borderWidth:1,borderColor:s.color.border},sectionHeader:{flexDirection:"row",justifyContent:"space-between",alignItems:"center",marginBottom:10,paddingBottom:6,borderBottomWidth:1,borderBottomColor:s.color.separator},sectionHeaderLeft:{flexDirection:"row",alignItems:"center",gap:8},sectionTitle:{fontSize:12,fontWeight:"600",color:s.color.textSecondary,letterSpacing:.4,textTransform:"uppercase"},countBadge:{backgroundColor:s.color.surfaceElevated,paddingHorizontal:8,paddingVertical:3,borderRadius:6,borderWidth:1,borderColor:s.color.border},countText:{fontSize:11,fontWeight:"600",color:s.color.text,fontFamily:W},emptyState:{alignItems:"center",justifyContent:"center",paddingVertical:48},emptyTitle:{fontSize:16,fontWeight:"600",color:s.color.text,marginTop:12,marginBottom:8},emptySubtitle:{fontSize:13,color:s.color.textSecondary,textAlign:"center"},emptyMMKVState:{backgroundColor:s.color.surface,borderRadius:12,padding:20,borderWidth:1,borderColor:s.color.border,marginTop:8,alignItems:"center"},emptyMMKVTitle:{fontSize:14,fontWeight:"600",color:s.color.text,marginBottom:6},emptyMMKVSubtitle:{fontSize:12,color:s.color.textSecondary,textAlign:"center",marginBottom:12},emptyMMKVCode:{fontSize:10,fontFamily:W,color:s.color.accent,backgroundColor:s.color.bg,padding:12,borderRadius:8,borderWidth:1,borderColor:s.color.border,alignSelf:"stretch"},instanceNavbar:{marginTop:12,marginBottom:8,backgroundColor:s.color.surface,borderRadius:10,borderWidth:1,borderColor:s.color.border,padding:10,shadowColor:"#000",shadowOpacity:.08,shadowRadius:6,shadowOffset:{width:0,height:2}},instanceNavbarHeader:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",marginBottom:8,paddingLeft:2},instanceNavbarLabel:{fontSize:9,fontWeight:"700",color:s.color.textTertiary,textTransform:"uppercase",letterSpacing:1},instanceNavbarScroll:{gap:8,paddingRight:8},instanceNavbarButton:{flexDirection:"row",alignItems:"center",gap:6,paddingHorizontal:12,paddingVertical:8,backgroundColor:s.color.surfaceElevated,borderRadius:8,borderWidth:1,borderColor:s.color.border,minWidth:90},instanceNavbarButtonActive:{borderWidth:1.5,shadowColor:"#000",shadowOpacity:.1,shadowRadius:4,shadowOffset:{width:0,height:1}},instanceNavbarButtonText:{fontSize:11,fontWeight:"600",color:s.color.textSecondary,fontFamily:W,flex:1},instanceNavbarButtonTextActive:{fontWeight:"700",color:s.color.text},instanceNavbarBadge:{backgroundColor:s.color.bg,paddingHorizontal:6,paddingVertical:2,borderRadius:6,minWidth:24,alignItems:"center"},instanceNavbarBadgeText:{fontSize:10,fontWeight:"700",color:s.color.textSecondary,fontFamily:W},clearSearchButton:{marginTop:16,paddingHorizontal:16,paddingVertical:8,backgroundColor:s.color.surfaceElevated,borderRadius:6,borderWidth:1,borderColor:s.color.border},clearSearchText:{fontSize:13,color:s.color.text,fontWeight:"500"},limitBanner:{backgroundColor:s.color.surface,borderRadius:10,borderWidth:1,borderColor:s.color.accentBorderStrong,padding:12,marginTop:12},limitBannerContent:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",marginBottom:4},limitBannerLeft:{flexDirection:"row",alignItems:"center",gap:8},limitBannerText:{color:s.color.text,fontSize:13,fontWeight:"600"},limitBannerSubtext:{color:s.color.textTertiary,fontSize:11}});
|