@buoy-gg/redux 7.0.38 → 7.0.39
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/lib/commonjs/index.js +256 -1
- package/lib/commonjs/preset.js +90 -1
- package/lib/commonjs/redux/components/ReduxActionButton.js +131 -1
- package/lib/commonjs/redux/components/ReduxActionDetailContent.js +437 -1
- package/lib/commonjs/redux/components/ReduxActionDetailView.js +401 -1
- package/lib/commonjs/redux/components/ReduxActionInfoView.js +836 -1
- package/lib/commonjs/redux/components/ReduxActionItem.js +334 -1
- package/lib/commonjs/redux/components/ReduxAutoInstrument.js +20 -1
- package/lib/commonjs/redux/components/ReduxDetailViewToggle.js +50 -1
- package/lib/commonjs/redux/components/ReduxIcon.js +18 -1
- package/lib/commonjs/redux/components/ReduxModal.js +703 -1
- package/lib/commonjs/redux/hooks/useAutoInstrumentRedux.js +187 -1
- package/lib/commonjs/redux/hooks/useReduxActions.js +58 -1
- package/lib/commonjs/redux/sync/reduxSyncAdapter.js +191 -1
- package/lib/commonjs/redux/utils/activeStoreRegistry.js +104 -1
- package/lib/commonjs/redux/utils/autoInstrument.js +231 -1
- package/lib/commonjs/redux/utils/buoyReduxMiddleware.js +202 -1
- package/lib/commonjs/redux/utils/createReduxHistoryAdapter.js +146 -1
- package/lib/commonjs/redux/utils/devtoolsHook.js +152 -1
- package/lib/commonjs/redux/utils/reduxActionStore.js +473 -1
- package/lib/commonjs/redux/utils/snapshotProvider.js +158 -1
- package/lib/commonjs/redux/utils/testIDs.js +75 -1
- package/lib/module/index.js +125 -1
- package/lib/module/preset.js +87 -1
- package/lib/module/redux/components/ReduxActionButton.js +128 -1
- package/lib/module/redux/components/ReduxActionDetailContent.js +433 -1
- package/lib/module/redux/components/ReduxActionDetailView.js +397 -1
- package/lib/module/redux/components/ReduxActionInfoView.js +831 -1
- package/lib/module/redux/components/ReduxActionItem.js +330 -1
- package/lib/module/redux/components/ReduxAutoInstrument.js +17 -1
- package/lib/module/redux/components/ReduxDetailViewToggle.js +45 -1
- package/lib/module/redux/components/ReduxIcon.js +8 -1
- package/lib/module/redux/components/ReduxModal.js +698 -1
- package/lib/module/redux/hooks/useAutoInstrumentRedux.js +183 -1
- package/lib/module/redux/hooks/useReduxActions.js +54 -1
- package/lib/module/redux/sync/reduxSyncAdapter.js +188 -1
- package/lib/module/redux/utils/activeStoreRegistry.js +92 -1
- package/lib/module/redux/utils/autoInstrument.js +201 -1
- package/lib/module/redux/utils/buoyReduxMiddleware.js +188 -1
- package/lib/module/redux/utils/createReduxHistoryAdapter.js +142 -1
- package/lib/module/redux/utils/devtoolsHook.js +145 -1
- package/lib/module/redux/utils/reduxActionStore.js +465 -1
- package/lib/module/redux/utils/snapshotProvider.js +157 -1
- package/lib/module/redux/utils/testIDs.js +68 -1
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts.map +1 -0
- package/lib/typescript/redux/components/ReduxActionButton.d.ts.map +1 -0
- package/lib/typescript/redux/components/ReduxActionDetailContent.d.ts.map +1 -0
- package/lib/typescript/redux/components/ReduxActionDetailView.d.ts.map +1 -0
- package/lib/typescript/redux/components/ReduxActionInfoView.d.ts.map +1 -0
- package/lib/typescript/redux/components/ReduxActionItem.d.ts.map +1 -0
- package/lib/typescript/redux/components/ReduxAutoInstrument.d.ts.map +1 -0
- package/lib/typescript/redux/components/ReduxDetailViewToggle.d.ts.map +1 -0
- package/lib/typescript/redux/components/ReduxIcon.d.ts.map +1 -0
- package/lib/typescript/redux/components/ReduxModal.d.ts.map +1 -0
- package/lib/typescript/redux/hooks/useAutoInstrumentRedux.d.ts.map +1 -0
- package/lib/typescript/redux/hooks/useReduxActions.d.ts.map +1 -0
- package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts.map +1 -0
- package/lib/typescript/redux/types/index.d.ts.map +1 -0
- package/lib/typescript/redux/utils/activeStoreRegistry.d.ts.map +1 -0
- package/lib/typescript/redux/utils/autoInstrument.d.ts.map +1 -0
- package/lib/typescript/redux/utils/buoyReduxMiddleware.d.ts.map +1 -0
- package/lib/typescript/redux/utils/createReduxHistoryAdapter.d.ts.map +1 -0
- package/lib/typescript/redux/utils/devtoolsHook.d.ts.map +1 -0
- package/lib/typescript/redux/utils/reduxActionStore.d.ts.map +1 -0
- package/lib/typescript/redux/utils/snapshotProvider.d.ts.map +1 -0
- package/lib/typescript/redux/utils/testIDs.d.ts.map +1 -0
- package/package.json +4 -4
|
@@ -1 +1,703 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.FREE_TIER_ACTION_LIMIT=void 0,exports.ReduxModal=ReduxModal,exports.ReduxToolView=ReduxToolView;var _react=require("react"),_reactNative=require("react-native"),_sharedUi=require("@buoy-gg/shared-ui"),_license=require("@buoy-gg/license"),_useReduxActions=require("../hooks/useReduxActions"),_reduxActionStore=require("../utils/reduxActionStore"),_useAutoInstrumentRedux=require("../hooks/useAutoInstrumentRedux"),_ReduxActionItem=require("./ReduxActionItem"),_ReduxActionDetailContent=require("./ReduxActionDetailContent"),_testIDs=require("../utils/testIDs"),_jsxRuntime=require("react/jsx-runtime");const FREE_TIER_ACTION_LIMIT=exports.FREE_TIER_ACTION_LIMIT=_sharedUi.TIER_LIMITS["redux.actions"].free;function EmptyState({isEnabled:e,query:t,totalActions:i}){const s=(0,_sharedUi.resolveListEmptyState)({subject:"actions",search:t,total:i,capturing:e,waiting:{otto:"waiting",title:"No Redux actions",message:"Actions will appear here as they are dispatched"},paused:{title:"No Redux actions",message:"Enable capture to start recording actions"}});return(0,_jsxRuntime.jsx)(_sharedUi.ToolEmptyState,{...s,testID:`${_testIDs.REDUX_TEST_ID}-empty`})}function ReduxModal({visible:e,onClose:t,onBack:i,onMinimize:s,enableSharedModalDimensions:r=!1,inline:a=!1,onActionSelect:o,initialSelectedActionId:n,initialDetailView:l}){const c=(0,_license.useIsPro)(),d=(0,_sharedUi.useLimit)("redux.actions"),[u,h]=(0,_react.useState)(!1),{isInstrumented:_,isLoading:x,error:g,isReactReduxAvailable:y,captureMechanism:R}=(0,_useAutoInstrumentRedux.useAutoInstrumentRedux)(),{filteredActions:m,filter:p,setFilter:b,stats:j,clearActions:I,isEnabled:T,toggleCapture:D}=(0,_useReduxActions.useReduxActions)(),C=(0,_react.useMemo)(()=>m.slice(0,d),[m,d]),S=(0,_react.useMemo)(()=>Math.max(0,m.length-d),[m.length,d]),U=S>0,[E,A]=(0,_react.useState)(null),f=(0,_react.useMemo)(()=>{if(null===E)return null;const e=C.findIndex(e=>e.id===E);return e>=0?e:null},[E,C]),v=null!==f?C[f]:null,w=(0,_react.useRef)(null);(0,_react.useEffect)(()=>{if(!o)return;const e=v?.id??null;null!==e&&e!==w.current&&(w.current=e,o(v))},[v,o]);const k=(0,_react.useRef)(null);(0,_react.useEffect)(()=>{n&&k.current!==n&&(C.findIndex(e=>e.id===n)<0||(k.current=n,A(n)))},[n,C]);const[N,B]=(0,_react.useState)(""),[M,$]=(0,_react.useState)(!1),z=((0,_react.useRef)(null),(0,_react.useRef)(null)),L=(0,_react.useCallback)(e=>{},[]),V=e=>{B(e),b(t=>({...t,searchText:e}))},P=(0,_react.useCallback)(()=>{B(""),b(e=>({...e,searchText:""})),$(!1)},[b]),X=(0,_react.useCallback)(e=>{A(e.id)},[]),O=(0,_react.useCallback)(()=>{h(!0)},[]),W=(0,_react.useCallback)(()=>{A(null)},[]),H=(0,_react.useCallback)(e=>{const t=C[e];t&&A(t.id)},[C]),q=async()=>{if(!c)return void h(!0);const e=m.map(e=>({id:e.id,type:e.type,...void 0!==e.payload&&{payload:e.payload},timestamp:e.timestamp,prevState:e.prevState,nextState:e.nextState,hasStateChange:e.hasStateChange}));await(0,_sharedUi.copyToClipboard)(e)},F=(0,_react.useCallback)(e=>e.id,[]),K=(0,_react.useMemo)(()=>(0,_testIDs.buildActionRowTestIDs)(C),[C]),G=(0,_react.useMemo)(()=>{const e=new Map;for(const t of _reduxActionStore.reduxActionStore.getActions()){const i=t.meta?.requestId;i&&e.set(i,(e.get(i)??0)+1)}return e},[m]),J=(0,_react.useCallback)(({item:e})=>{const t=e.meta?.requestId;return(0,_jsxRuntime.jsx)(_ReduxActionItem.ReduxActionItem,{action:e,onPress:X,linkedCount:t?G.get(t)??0:0,testID:K.get(e.id)})},[X,K,G]),Q=()=>v?(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader,{children:[(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation,{onBack:W}),(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Content,{title:v.type,centered:!0,testID:`${_testIDs.REDUX_TEST_ID}-detail-title`})]}):(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader,{children:[i&&(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation,{onBack:i}),(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Content,{title:"",children:M?(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Search,{value:N,onChangeText:V,onCancel:P,placeholder:"Search action types...",accessibilityLabel:"Search action types",iconSize:14,testID:`${_testIDs.REDUX_TEST_ID}-search-field`,containerTestID:`${_testIDs.REDUX_TEST_ID}-search`,clearTestID:`${_testIDs.REDUX_TEST_ID}-search-clear`,onBlur:()=>{0===N.length&&$(!1)}}):(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.headerChipRow,children:[(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{style:[styles.headerChip,!p.onlyWithChanges&&styles.headerChipActive],onPress:()=>b(e=>({...e,onlyWithChanges:!1})),testID:`${_testIDs.REDUX_TEST_ID}-chip-all`,accessibilityRole:"button",accessibilityLabel:`Show all actions, ${j.totalActions} captured`,accessibilityState:{selected:!p.onlyWithChanges},children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.headerChipLabel,children:"ALL"}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.headerChipValue,{color:_sharedUi.night.color.text}],testID:`${_testIDs.REDUX_TEST_ID}-chip-all-value`,children:j.totalActions})]}),(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{style:[styles.headerChip,p.onlyWithChanges&&styles.headerChipActive],onPress:()=>b(e=>({...e,onlyWithChanges:!0})),testID:`${_testIDs.REDUX_TEST_ID}-chip-changed`,accessibilityRole:"button",accessibilityLabel:`Show only actions that changed state, ${j.actionsWithChanges} of them`,accessibilityState:{selected:!!p.onlyWithChanges},children:[(0,_jsxRuntime.jsx)(_sharedUi.Zap,{size:12,color:_sharedUi.macOSColors.semantic.warning}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.headerChipValue,{color:_sharedUi.macOSColors.semantic.warning}],testID:`${_testIDs.REDUX_TEST_ID}-chip-changed-value`,children:j.actionsWithChanges})]}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.headerChip,testID:`${_testIDs.REDUX_TEST_ID}-chip-types`,accessibilityLabel:`${j.uniqueActionTypes} distinct action types`,children:[(0,_jsxRuntime.jsx)(_sharedUi.CheckCircle,{size:12,color:_sharedUi.macOSColors.semantic.success}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.headerChipValue,{color:_sharedUi.macOSColors.semantic.success}],testID:`${_testIDs.REDUX_TEST_ID}-chip-types-value`,children:j.uniqueActionTypes})]})]})}),(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader.Actions,{children:[!M&&(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:()=>$(!0),style:[styles.headerActionButton,N.length>0&&styles.headerActionButtonActive],testID:`${_testIDs.REDUX_TEST_ID}-search-open`,accessibilityRole:"button",accessibilityLabel:"Search action types",children:(0,_jsxRuntime.jsx)(_sharedUi.Search,{size:14,color:N.length>0?_sharedUi.night.color.accent:_sharedUi.night.color.textSecondary})}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:q,style:[styles.headerActionButton,0===m.length&&styles.headerActionButtonDisabled],disabled:0===m.length,testID:`${_testIDs.REDUX_TEST_ID}-copy`,accessibilityRole:"button",accessibilityLabel:"Copy all actions",accessibilityState:{disabled:0===m.length},children:(0,_jsxRuntime.jsx)(_sharedUi.Copy,{size:14,color:m.length>0?_sharedUi.night.color.textSecondary:_sharedUi.night.color.textTertiary})}),(0,_jsxRuntime.jsx)(_sharedUi.PowerToggleButton,{isEnabled:T,onToggle:D,accessibilityLabel:"Toggle Redux action capture",testID:`${_testIDs.REDUX_TEST_ID}-power`}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:I,style:[styles.headerActionButton,0===m.length&&styles.headerActionButtonDisabled],disabled:0===m.length,testID:`${_testIDs.REDUX_TEST_ID}-clear`,accessibilityRole:"button",accessibilityLabel:"Clear actions",accessibilityState:{disabled:0===m.length},children:(0,_jsxRuntime.jsx)(_sharedUi.Trash2,{size:14,color:(m.length,_sharedUi.night.color.textTertiary)})})]})]}),Z=r?_sharedUi.devToolsStorageKeys.modal.root():"buoy-redux-modal";if(!e)return null;const Y=v&&null!==f?(0,_jsxRuntime.jsx)(_ReduxActionDetailContent.ReduxActionDetailFooter,{action:v,actions:C,selectedIndex:f,onIndexChange:H}):null,ee=v&&C.length>1?68:0,te=null==v,ie=(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.container,children:v&&null!==f?(0,_jsxRuntime.jsx)(_ReduxActionDetailContent.ReduxActionDetailContent,{action:v,actions:C,selectedIndex:f,onIndexChange:H,disableInternalFooter:!0,initialView:l}):(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[g&&!_&&!_reduxActionStore.reduxActionStore.isCaptureSuppressed()&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.errorBanner,testID:`${_testIDs.REDUX_TEST_ID}-banner-error`,children:[(0,_jsxRuntime.jsx)(_sharedUi.AlertTriangle,{size:14,color:_sharedUi.night.color.danger}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.errorText,children:y?"Not inside a Redux Provider. Wrap your app with <Provider store={store}>.":"react-redux not installed. Run: npm install react-redux"})]}),!T&&_&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.disabledBanner,testID:`${_testIDs.REDUX_TEST_ID}-banner-disabled`,children:[(0,_jsxRuntime.jsx)(_sharedUi.Power,{size:14,color:_sharedUi.night.color.warning}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.disabledText,children:"Action capture is disabled"})]}),"patch"===R&&!_reduxActionStore.reduxActionStore.isCaptureSuppressed()&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.disabledBanner,testID:`${_testIDs.REDUX_TEST_ID}-banner-patch`,children:[(0,_jsxRuntime.jsx)(_sharedUi.AlertTriangle,{size:14,color:_sharedUi.night.color.warning}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.disabledText,children:"Top-level capture only — thunk/RTK Query internals aren't visible. Put import '@buoy-gg/redux' first in your app entry (or add buoyReduxMiddleware) for full capture."})]}),U&&(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{style:styles.lockedBanner,onPress:O,activeOpacity:.8,testID:`${_testIDs.REDUX_TEST_ID}-locked`,accessibilityRole:"button",accessibilityLabel:`${S} older ${1===S?"action":"actions"} locked. Upgrade to see them.`,children:[(0,_jsxRuntime.jsx)(_sharedUi.Lock,{size:14,color:_sharedUi.night.color.accent}),(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.lockedText,testID:`${_testIDs.REDUX_TEST_ID}-locked-text`,children:[S," older ",1===S?"action":"actions"," locked"]}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.upgradeBadge,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.upgradeBadgeText,children:"UPGRADE"})})]}),C.length>0?(0,_jsxRuntime.jsx)(_reactNative.FlatList,{ref:z,testID:`${_testIDs.REDUX_TEST_ID}-list`,data:C,renderItem:J,keyExtractor:F,contentContainerStyle:styles.listContent,showsVerticalScrollIndicator:!0,..._sharedUi.EVENT_LIST_PRESET}):(0,_jsxRuntime.jsx)(EmptyState,{isEnabled:T,query:N,totalActions:j.totalActions})]})}),se=(0,_jsxRuntime.jsx)(_sharedUi.ProUpgradeModal,{visible:u,onClose:()=>h(!1),featureName:"Full Action History"});return a?(0,_jsxRuntime.jsxs)(_sharedUi.TickProvider,{children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.inlineRoot,children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.inlineHeader,..._sharedUi.windowDragProps,children:Q()}),ie,Y]}),se]}):(0,_jsxRuntime.jsxs)(_sharedUi.TickProvider,{children:[(0,_jsxRuntime.jsx)(_sharedUi.JsModal,{visible:e,onClose:t,onMinimize:s,persistenceKey:Z,header:{showToggleButton:!0,customContent:Q()},onModeChange:L,enablePersistence:!0,initialMode:"bottomSheet",enableGlitchEffects:!0,disableScrollWrapper:te,styles:{},footer:Y,footerHeight:ee,children:ie}),se]})}function ReduxToolView(e={}){return(0,_jsxRuntime.jsx)(ReduxModal,{...e,inline:!0,visible:!0,onClose:()=>{}})}const styles=_reactNative.StyleSheet.create({inlineRoot:{flex:1,minHeight:0,backgroundColor:"transparent"},inlineHeader:{paddingHorizontal:12,paddingVertical:8,borderBottomWidth:1,borderBottomColor:_sharedUi.macOSColors.border.default,backgroundColor:"transparent"},container:{flex:1,backgroundColor:"transparent"},headerChipRow:{flexDirection:"row",alignItems:"center",gap:8},headerChip:{flexDirection:"row",alignItems:"center",gap:4,backgroundColor:_sharedUi.night.color.surfaceElevated,paddingHorizontal:10,paddingVertical:5,borderRadius:12,borderWidth:1,borderColor:_sharedUi.night.color.border},headerChipActive:{backgroundColor:_sharedUi.night.color.accentSoft,borderColor:_sharedUi.night.color.accentBorderStrong},headerChipLabel:{fontSize:10,fontWeight:"700",color:_sharedUi.night.color.textTertiary},headerChipValue:{fontSize:12,fontWeight:"600",fontFamily:_sharedUi.monoFont},headerActionButton:{width:32,height:32,borderRadius:8,backgroundColor:_sharedUi.night.color.surfaceElevated,borderWidth:1,borderColor:_sharedUi.night.color.border,alignItems:"center",justifyContent:"center"},headerActionButtonDisabled:{opacity:.55},headerActionButtonActive:{backgroundColor:_sharedUi.night.color.accentSoft,borderColor:_sharedUi.night.color.accentBorderStrong},startButton:{backgroundColor:_sharedUi.macOSColors.semantic.successBackground,borderColor:_sharedUi.macOSColors.semantic.success+"40"},stopButton:{backgroundColor:_sharedUi.macOSColors.semantic.errorBackground,borderColor:_sharedUi.macOSColors.semantic.error+"40"},errorBanner:{flexDirection:"row",alignItems:"center",gap:8,padding:10,marginHorizontal:12,marginTop:8,backgroundColor:_sharedUi.night.color.dangerSoft,borderRadius:8,borderWidth:1,borderColor:_sharedUi.night.color.danger+"20"},errorText:{color:_sharedUi.night.color.danger,fontSize:11,flex:1},disabledBanner:{flexDirection:"row",alignItems:"center",gap:8,padding:10,marginHorizontal:12,marginTop:8,backgroundColor:_sharedUi.night.color.warningSoft,borderRadius:8,borderWidth:1,borderColor:_sharedUi.night.color.warning+"20"},disabledText:{color:_sharedUi.night.color.warning,fontSize:11,flex:1},listContent:{paddingTop:8,paddingBottom:20},lockedBanner:{flexDirection:"row",alignItems:"center",gap:8,padding:10,marginHorizontal:12,marginTop:8,backgroundColor:_sharedUi.night.color.accentSoft,borderRadius:8,borderWidth:1,borderColor:_sharedUi.night.color.accentBorder},lockedText:{color:_sharedUi.night.color.accent,fontSize:11,fontWeight:"500",flex:1},upgradeBadge:{backgroundColor:_sharedUi.night.color.accent,paddingHorizontal:8,paddingVertical:3,borderRadius:4},upgradeBadgeText:{color:_sharedUi.night.color.onAccent,fontSize:9,fontWeight:"700",letterSpacing:.5}});
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.FREE_TIER_ACTION_LIMIT = void 0;
|
|
7
|
+
exports.ReduxModal = ReduxModal;
|
|
8
|
+
exports.ReduxToolView = ReduxToolView;
|
|
9
|
+
var _react = require("react");
|
|
10
|
+
var _reactNative = require("react-native");
|
|
11
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
12
|
+
var _license = require("@buoy-gg/license");
|
|
13
|
+
var _useReduxActions = require("../hooks/useReduxActions");
|
|
14
|
+
var _reduxActionStore = require("../utils/reduxActionStore");
|
|
15
|
+
var _useAutoInstrumentRedux = require("../hooks/useAutoInstrumentRedux");
|
|
16
|
+
var _ReduxActionItem = require("./ReduxActionItem");
|
|
17
|
+
var _ReduxActionDetailContent = require("./ReduxActionDetailContent");
|
|
18
|
+
var _testIDs = require("../utils/testIDs");
|
|
19
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
20
|
+
/**
|
|
21
|
+
* Main Redux DevTools modal
|
|
22
|
+
*
|
|
23
|
+
* This component auto-instruments the Redux store when it mounts,
|
|
24
|
+
* providing zero-config action capture for the DevTools.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated Read the live limit with `useLimit("redux.actions")` — this
|
|
29
|
+
* constant only ever reports the FREE tier's value and is blind to the
|
|
30
|
+
* anonymous tier. Kept because it is part of the package's public exports.
|
|
31
|
+
*/
|
|
32
|
+
const FREE_TIER_ACTION_LIMIT = exports.FREE_TIER_ACTION_LIMIT = _sharedUi.TIER_LIMITS["redux.actions"].free;
|
|
33
|
+
/**
|
|
34
|
+
* Why this takes the query as well as the enabled flag: "nothing was captured"
|
|
35
|
+
* and "nothing matched what you typed" are different problems, and an empty
|
|
36
|
+
* list under a live search reads as a dead tool. Naming the query back is what
|
|
37
|
+
* makes the difference legible without clearing the field to find out.
|
|
38
|
+
*/
|
|
39
|
+
function EmptyState({
|
|
40
|
+
isEnabled,
|
|
41
|
+
query,
|
|
42
|
+
totalActions
|
|
43
|
+
}) {
|
|
44
|
+
const content = (0, _sharedUi.resolveListEmptyState)({
|
|
45
|
+
subject: "actions",
|
|
46
|
+
search: query,
|
|
47
|
+
total: totalActions,
|
|
48
|
+
capturing: isEnabled,
|
|
49
|
+
waiting: {
|
|
50
|
+
otto: "waiting",
|
|
51
|
+
title: "No Redux actions",
|
|
52
|
+
message: "Actions will appear here as they are dispatched"
|
|
53
|
+
},
|
|
54
|
+
paused: {
|
|
55
|
+
title: "No Redux actions",
|
|
56
|
+
message: "Enable capture to start recording actions"
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
|
|
60
|
+
...content,
|
|
61
|
+
testID: `${_testIDs.REDUX_TEST_ID}-empty`
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function ReduxModal({
|
|
65
|
+
visible,
|
|
66
|
+
onClose,
|
|
67
|
+
onBack,
|
|
68
|
+
onMinimize,
|
|
69
|
+
enableSharedModalDimensions = false,
|
|
70
|
+
inline = false,
|
|
71
|
+
onActionSelect,
|
|
72
|
+
initialSelectedActionId,
|
|
73
|
+
initialDetailView
|
|
74
|
+
}) {
|
|
75
|
+
const isPro = (0, _license.useIsPro)();
|
|
76
|
+
const actionLimit = (0, _sharedUi.useLimit)("redux.actions");
|
|
77
|
+
const [showProModal, setShowProModal] = (0, _react.useState)(false);
|
|
78
|
+
|
|
79
|
+
// Auto-instrument the Redux store (zero-config)
|
|
80
|
+
const {
|
|
81
|
+
isInstrumented,
|
|
82
|
+
isLoading: isInstrumentLoading,
|
|
83
|
+
error: instrumentError,
|
|
84
|
+
isReactReduxAvailable,
|
|
85
|
+
captureMechanism
|
|
86
|
+
} = (0, _useAutoInstrumentRedux.useAutoInstrumentRedux)();
|
|
87
|
+
const {
|
|
88
|
+
filteredActions,
|
|
89
|
+
filter,
|
|
90
|
+
setFilter,
|
|
91
|
+
stats,
|
|
92
|
+
clearActions,
|
|
93
|
+
isEnabled,
|
|
94
|
+
toggleCapture
|
|
95
|
+
} = (0, _useReduxActions.useReduxActions)();
|
|
96
|
+
|
|
97
|
+
// Pro is Infinity here, so slice() returns everything and the locked count
|
|
98
|
+
// floors at 0 — no tier branching needed at the call site.
|
|
99
|
+
const visibleActions = (0, _react.useMemo)(() => filteredActions.slice(0, actionLimit), [filteredActions, actionLimit]);
|
|
100
|
+
const lockedActionCount = (0, _react.useMemo)(() => Math.max(0, filteredActions.length - actionLimit), [filteredActions.length, actionLimit]);
|
|
101
|
+
const hasLockedActions = lockedActionCount > 0;
|
|
102
|
+
|
|
103
|
+
// Selection state, held as an action ID rather than as a list position.
|
|
104
|
+
//
|
|
105
|
+
// This list grows at the HEAD: every dispatch prepends a row and pushes
|
|
106
|
+
// everything below it down one. An index therefore names a different action
|
|
107
|
+
// a moment after it is taken — so a detail page opened while the app was
|
|
108
|
+
// still dispatching silently became a detail page for some OTHER action,
|
|
109
|
+
// with no transition and nothing to say it had happened. A devtool that
|
|
110
|
+
// quietly swaps what it is showing you is worse than one that shows nothing.
|
|
111
|
+
//
|
|
112
|
+
// The index is still what the stepper and the diff's "previous action" are
|
|
113
|
+
// expressed in — both are genuinely about position — so it is derived here
|
|
114
|
+
// and re-derived whenever the list moves.
|
|
115
|
+
const [selectedActionId, setSelectedActionId] = (0, _react.useState)(null);
|
|
116
|
+
const selectedActionIndex = (0, _react.useMemo)(() => {
|
|
117
|
+
if (selectedActionId === null) return null;
|
|
118
|
+
const index = visibleActions.findIndex(a => a.id === selectedActionId);
|
|
119
|
+
// Below zero means the action is gone — cleared, or aged out past the tier
|
|
120
|
+
// cap. Falling back to the list is the honest outcome; the alternative is
|
|
121
|
+
// showing whatever moved into its place.
|
|
122
|
+
return index >= 0 ? index : null;
|
|
123
|
+
}, [selectedActionId, visibleActions]);
|
|
124
|
+
const selectedAction = selectedActionIndex !== null ? visibleActions[selectedActionIndex] : null;
|
|
125
|
+
|
|
126
|
+
// Notify the host on selection (keyed by action id so re-renders with the
|
|
127
|
+
// same selection don't re-fire; remote mirrors fetch state trees here).
|
|
128
|
+
const lastNotifiedSelectionRef = (0, _react.useRef)(null);
|
|
129
|
+
(0, _react.useEffect)(() => {
|
|
130
|
+
if (!onActionSelect) return;
|
|
131
|
+
const id = selectedAction?.id ?? null;
|
|
132
|
+
if (id === null || id === lastNotifiedSelectionRef.current) return;
|
|
133
|
+
lastNotifiedSelectionRef.current = id;
|
|
134
|
+
onActionSelect(selectedAction);
|
|
135
|
+
}, [selectedAction, onActionSelect]);
|
|
136
|
+
|
|
137
|
+
// Deep link into an action's detail (see initialSelectedActionId). Applied
|
|
138
|
+
// at most once per id, so the list re-rendering — or the user pressing Back
|
|
139
|
+
// — never re-opens it.
|
|
140
|
+
const appliedInitialSelectionRef = (0, _react.useRef)(null);
|
|
141
|
+
(0, _react.useEffect)(() => {
|
|
142
|
+
if (!initialSelectedActionId) return;
|
|
143
|
+
if (appliedInitialSelectionRef.current === initialSelectedActionId) return;
|
|
144
|
+
const index = visibleActions.findIndex(a => a.id === initialSelectedActionId);
|
|
145
|
+
if (index < 0) return;
|
|
146
|
+
appliedInitialSelectionRef.current = initialSelectedActionId;
|
|
147
|
+
setSelectedActionId(initialSelectedActionId);
|
|
148
|
+
}, [initialSelectedActionId, visibleActions]);
|
|
149
|
+
const [searchText, setSearchText] = (0, _react.useState)("");
|
|
150
|
+
const [isSearchActive, setIsSearchActive] = (0, _react.useState)(false);
|
|
151
|
+
const searchInputRef = (0, _react.useRef)(null);
|
|
152
|
+
const flatListRef = (0, _react.useRef)(null);
|
|
153
|
+
const handleModeChange = (0, _react.useCallback)(_mode => {
|
|
154
|
+
// Mode changes handled by JsModal
|
|
155
|
+
}, []);
|
|
156
|
+
const handleSearch = text => {
|
|
157
|
+
setSearchText(text);
|
|
158
|
+
setFilter(prev => ({
|
|
159
|
+
...prev,
|
|
160
|
+
searchText: text
|
|
161
|
+
}));
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/** Drop the query AND put the header back — the ✕ means "stop filtering". */
|
|
165
|
+
const closeSearch = (0, _react.useCallback)(() => {
|
|
166
|
+
setSearchText("");
|
|
167
|
+
setFilter(prev => ({
|
|
168
|
+
...prev,
|
|
169
|
+
searchText: ""
|
|
170
|
+
}));
|
|
171
|
+
setIsSearchActive(false);
|
|
172
|
+
}, [setFilter]);
|
|
173
|
+
const handleActionPress = (0, _react.useCallback)(action => {
|
|
174
|
+
setSelectedActionId(action.id);
|
|
175
|
+
}, []);
|
|
176
|
+
const handleUpgradePress = (0, _react.useCallback)(() => {
|
|
177
|
+
setShowProModal(true);
|
|
178
|
+
}, []);
|
|
179
|
+
const handleBack = (0, _react.useCallback)(() => {
|
|
180
|
+
setSelectedActionId(null);
|
|
181
|
+
}, []);
|
|
182
|
+
|
|
183
|
+
// The stepper and the async timeline both navigate by POSITION; the
|
|
184
|
+
// selection is kept by identity, so the position is resolved to an id here,
|
|
185
|
+
// once, at the moment of the tap.
|
|
186
|
+
const handleIndexChange = (0, _react.useCallback)(newIndex => {
|
|
187
|
+
const next = visibleActions[newIndex];
|
|
188
|
+
if (next) setSelectedActionId(next.id);
|
|
189
|
+
}, [visibleActions]);
|
|
190
|
+
const handleCopyAll = async () => {
|
|
191
|
+
if (!isPro) {
|
|
192
|
+
setShowProModal(true);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const actionsData = filteredActions.map(action => ({
|
|
196
|
+
id: action.id,
|
|
197
|
+
type: action.type,
|
|
198
|
+
...(action.payload !== undefined && {
|
|
199
|
+
payload: action.payload
|
|
200
|
+
}),
|
|
201
|
+
timestamp: action.timestamp,
|
|
202
|
+
prevState: action.prevState,
|
|
203
|
+
nextState: action.nextState,
|
|
204
|
+
hasStateChange: action.hasStateChange
|
|
205
|
+
}));
|
|
206
|
+
await (0, _sharedUi.copyToClipboard)(actionsData);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// FlatList optimization
|
|
210
|
+
const keyExtractor = (0, _react.useCallback)(item => item.id, []);
|
|
211
|
+
|
|
212
|
+
// An action row's handle carries an ordinal among rows of the SAME type, and
|
|
213
|
+
// an ordinal is a fact about the list rather than about the row — see
|
|
214
|
+
// ../utils/testIDs.ts for why a type alone cannot address a redux row.
|
|
215
|
+
const rowTestIDs = (0, _react.useMemo)(() => (0, _testIDs.buildActionRowTestIDs)(visibleActions), [visibleActions]);
|
|
216
|
+
|
|
217
|
+
// How many captured actions share each requestId, i.e. how far along each
|
|
218
|
+
// async operation is.
|
|
219
|
+
//
|
|
220
|
+
// Computed HERE rather than in the row, and passed down, because a row's
|
|
221
|
+
// async badge is a fact about OTHER rows. `pending` is captured and drawn
|
|
222
|
+
// while it is still alone in the world; `fulfilled` arrives a moment later
|
|
223
|
+
// and makes the earlier row's badge wrong. `ReduxActionItem` is memoized on
|
|
224
|
+
// its props and the action object it holds never changes identity, so
|
|
225
|
+
// nothing about that second arrival reached the first row — the pending half
|
|
226
|
+
// of every settled thunk permanently showed no flow badge, which is exactly
|
|
227
|
+
// backwards from what the badge is for.
|
|
228
|
+
//
|
|
229
|
+
// One pass over the store instead of a per-row scan: the previous shape was
|
|
230
|
+
// O(rows x actions) at 200 actions, on a list that re-renders on every
|
|
231
|
+
// dispatch.
|
|
232
|
+
const linkedCounts = (0, _react.useMemo)(() => {
|
|
233
|
+
const counts = new Map();
|
|
234
|
+
for (const action of _reduxActionStore.reduxActionStore.getActions()) {
|
|
235
|
+
const requestId = action.meta?.requestId;
|
|
236
|
+
if (!requestId) continue;
|
|
237
|
+
counts.set(requestId, (counts.get(requestId) ?? 0) + 1);
|
|
238
|
+
}
|
|
239
|
+
return counts;
|
|
240
|
+
// `filteredActions` takes a new identity whenever the store notifies, which
|
|
241
|
+
// is the only moment these counts can change.
|
|
242
|
+
}, [filteredActions]);
|
|
243
|
+
const renderItem = (0, _react.useCallback)(({
|
|
244
|
+
item
|
|
245
|
+
}) => {
|
|
246
|
+
const requestId = item.meta?.requestId;
|
|
247
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_ReduxActionItem.ReduxActionItem, {
|
|
248
|
+
action: item,
|
|
249
|
+
onPress: handleActionPress,
|
|
250
|
+
linkedCount: requestId ? linkedCounts.get(requestId) ?? 0 : 0,
|
|
251
|
+
testID: rowTestIDs.get(item.id)
|
|
252
|
+
});
|
|
253
|
+
}, [handleActionPress, rowTestIDs, linkedCounts]);
|
|
254
|
+
const renderHeaderContent = () => {
|
|
255
|
+
// Detail view header - show action type
|
|
256
|
+
if (selectedAction) {
|
|
257
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
|
|
258
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
|
|
259
|
+
onBack: handleBack
|
|
260
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
|
|
261
|
+
title: selectedAction.type,
|
|
262
|
+
centered: true,
|
|
263
|
+
testID: `${_testIDs.REDUX_TEST_ID}-detail-title`
|
|
264
|
+
})]
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Main list view header
|
|
269
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
|
|
270
|
+
children: [onBack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
|
|
271
|
+
onBack: onBack
|
|
272
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
|
|
273
|
+
title: "",
|
|
274
|
+
children: isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Search, {
|
|
275
|
+
value: searchText,
|
|
276
|
+
onChangeText: handleSearch,
|
|
277
|
+
onCancel: closeSearch,
|
|
278
|
+
placeholder: "Search action types...",
|
|
279
|
+
accessibilityLabel: "Search action types",
|
|
280
|
+
iconSize: 14,
|
|
281
|
+
testID: `${_testIDs.REDUX_TEST_ID}-search-field`,
|
|
282
|
+
containerTestID: `${_testIDs.REDUX_TEST_ID}-search`,
|
|
283
|
+
clearTestID: `${_testIDs.REDUX_TEST_ID}-search-clear`
|
|
284
|
+
// Stay open while the query still filters the list.
|
|
285
|
+
//
|
|
286
|
+
// Collapsing it on blur hid the reason rows were missing: the
|
|
287
|
+
// header went back to its chips — which count the WHOLE
|
|
288
|
+
// capture, not the filtered list — while the list stayed
|
|
289
|
+
// narrowed, so the tool showed "4 actions" above a single row
|
|
290
|
+
// and offered nothing to explain the gap. The only way back
|
|
291
|
+
// was to reopen a field the user had no reason to think was
|
|
292
|
+
// still armed.
|
|
293
|
+
,
|
|
294
|
+
onBlur: () => {
|
|
295
|
+
if (searchText.length === 0) setIsSearchActive(false);
|
|
296
|
+
}
|
|
297
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
298
|
+
style: styles.headerChipRow,
|
|
299
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
300
|
+
style: [styles.headerChip, !filter.onlyWithChanges && styles.headerChipActive],
|
|
301
|
+
onPress: () => setFilter(prev => ({
|
|
302
|
+
...prev,
|
|
303
|
+
onlyWithChanges: false
|
|
304
|
+
})),
|
|
305
|
+
testID: `${_testIDs.REDUX_TEST_ID}-chip-all`,
|
|
306
|
+
accessibilityRole: "button",
|
|
307
|
+
accessibilityLabel: `Show all actions, ${stats.totalActions} captured`,
|
|
308
|
+
accessibilityState: {
|
|
309
|
+
selected: !filter.onlyWithChanges
|
|
310
|
+
},
|
|
311
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
312
|
+
style: styles.headerChipLabel,
|
|
313
|
+
children: "ALL"
|
|
314
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
315
|
+
style: [styles.headerChipValue, {
|
|
316
|
+
color: _sharedUi.night.color.text
|
|
317
|
+
}],
|
|
318
|
+
testID: `${_testIDs.REDUX_TEST_ID}-chip-all-value`,
|
|
319
|
+
children: stats.totalActions
|
|
320
|
+
})]
|
|
321
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
322
|
+
style: [styles.headerChip, filter.onlyWithChanges && styles.headerChipActive],
|
|
323
|
+
onPress: () => setFilter(prev => ({
|
|
324
|
+
...prev,
|
|
325
|
+
onlyWithChanges: true
|
|
326
|
+
})),
|
|
327
|
+
testID: `${_testIDs.REDUX_TEST_ID}-chip-changed`,
|
|
328
|
+
accessibilityRole: "button",
|
|
329
|
+
accessibilityLabel: `Show only actions that changed state, ${stats.actionsWithChanges} of them`,
|
|
330
|
+
accessibilityState: {
|
|
331
|
+
selected: !!filter.onlyWithChanges
|
|
332
|
+
},
|
|
333
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Zap, {
|
|
334
|
+
size: 12,
|
|
335
|
+
color: _sharedUi.macOSColors.semantic.warning
|
|
336
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
337
|
+
style: [styles.headerChipValue, {
|
|
338
|
+
color: _sharedUi.macOSColors.semantic.warning
|
|
339
|
+
}],
|
|
340
|
+
testID: `${_testIDs.REDUX_TEST_ID}-chip-changed-value`,
|
|
341
|
+
children: stats.actionsWithChanges
|
|
342
|
+
})]
|
|
343
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
344
|
+
style: styles.headerChip,
|
|
345
|
+
testID: `${_testIDs.REDUX_TEST_ID}-chip-types`,
|
|
346
|
+
accessibilityLabel: `${stats.uniqueActionTypes} distinct action types`,
|
|
347
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle, {
|
|
348
|
+
size: 12,
|
|
349
|
+
color: _sharedUi.macOSColors.semantic.success
|
|
350
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
351
|
+
style: [styles.headerChipValue, {
|
|
352
|
+
color: _sharedUi.macOSColors.semantic.success
|
|
353
|
+
}],
|
|
354
|
+
testID: `${_testIDs.REDUX_TEST_ID}-chip-types-value`,
|
|
355
|
+
children: stats.uniqueActionTypes
|
|
356
|
+
})]
|
|
357
|
+
})]
|
|
358
|
+
})
|
|
359
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader.Actions, {
|
|
360
|
+
children: [!isSearchActive && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
361
|
+
onPress: () => setIsSearchActive(true),
|
|
362
|
+
style: [styles.headerActionButton, searchText.length > 0 && styles.headerActionButtonActive],
|
|
363
|
+
testID: `${_testIDs.REDUX_TEST_ID}-search-open`,
|
|
364
|
+
accessibilityRole: "button",
|
|
365
|
+
accessibilityLabel: "Search action types",
|
|
366
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
|
|
367
|
+
size: 14,
|
|
368
|
+
color: searchText.length > 0 ? _sharedUi.night.color.accent : _sharedUi.night.color.textSecondary
|
|
369
|
+
})
|
|
370
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
371
|
+
onPress: handleCopyAll,
|
|
372
|
+
style: [styles.headerActionButton, filteredActions.length === 0 && styles.headerActionButtonDisabled],
|
|
373
|
+
disabled: filteredActions.length === 0,
|
|
374
|
+
testID: `${_testIDs.REDUX_TEST_ID}-copy`,
|
|
375
|
+
accessibilityRole: "button",
|
|
376
|
+
accessibilityLabel: "Copy all actions",
|
|
377
|
+
accessibilityState: {
|
|
378
|
+
disabled: filteredActions.length === 0
|
|
379
|
+
},
|
|
380
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Copy, {
|
|
381
|
+
size: 14,
|
|
382
|
+
color: filteredActions.length > 0 ? _sharedUi.night.color.textSecondary : _sharedUi.night.color.textTertiary
|
|
383
|
+
})
|
|
384
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PowerToggleButton, {
|
|
385
|
+
isEnabled: isEnabled,
|
|
386
|
+
onToggle: toggleCapture,
|
|
387
|
+
accessibilityLabel: "Toggle Redux action capture",
|
|
388
|
+
testID: `${_testIDs.REDUX_TEST_ID}-power`
|
|
389
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
390
|
+
onPress: clearActions,
|
|
391
|
+
style: [styles.headerActionButton, filteredActions.length === 0 && styles.headerActionButtonDisabled],
|
|
392
|
+
disabled: filteredActions.length === 0,
|
|
393
|
+
testID: `${_testIDs.REDUX_TEST_ID}-clear`,
|
|
394
|
+
accessibilityRole: "button",
|
|
395
|
+
accessibilityLabel: "Clear actions",
|
|
396
|
+
accessibilityState: {
|
|
397
|
+
disabled: filteredActions.length === 0
|
|
398
|
+
},
|
|
399
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
|
|
400
|
+
size: 14,
|
|
401
|
+
color: filteredActions.length > 0 ? _sharedUi.night.color.textTertiary : _sharedUi.night.color.textTertiary
|
|
402
|
+
})
|
|
403
|
+
})]
|
|
404
|
+
})]
|
|
405
|
+
});
|
|
406
|
+
};
|
|
407
|
+
const persistenceKey = enableSharedModalDimensions ? _sharedUi.devToolsStorageKeys.modal.root() : "buoy-redux-modal";
|
|
408
|
+
if (!visible) return null;
|
|
409
|
+
|
|
410
|
+
// Footer for detail view navigation
|
|
411
|
+
const footerNode = selectedAction && selectedActionIndex !== null ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_ReduxActionDetailContent.ReduxActionDetailFooter, {
|
|
412
|
+
action: selectedAction,
|
|
413
|
+
actions: visibleActions,
|
|
414
|
+
selectedIndex: selectedActionIndex,
|
|
415
|
+
onIndexChange: handleIndexChange
|
|
416
|
+
}) : null;
|
|
417
|
+
const footerHeight = selectedAction && visibleActions.length > 1 ? 68 : 0;
|
|
418
|
+
|
|
419
|
+
// The action list owns its scrolling so FlatList virtualization works
|
|
420
|
+
// (see ZustandModal — same pattern, same tool-bench evidence).
|
|
421
|
+
const listOwnsScroll = selectedAction == null;
|
|
422
|
+
const body = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
423
|
+
style: styles.container,
|
|
424
|
+
children: selectedAction && selectedActionIndex !== null ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_ReduxActionDetailContent.ReduxActionDetailContent, {
|
|
425
|
+
action: selectedAction,
|
|
426
|
+
actions: visibleActions,
|
|
427
|
+
selectedIndex: selectedActionIndex,
|
|
428
|
+
onIndexChange: handleIndexChange,
|
|
429
|
+
disableInternalFooter: true,
|
|
430
|
+
initialView: initialDetailView
|
|
431
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
432
|
+
children: [instrumentError && !isInstrumented && !_reduxActionStore.reduxActionStore.isCaptureSuppressed() && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
433
|
+
style: styles.errorBanner,
|
|
434
|
+
testID: `${_testIDs.REDUX_TEST_ID}-banner-error`,
|
|
435
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
|
|
436
|
+
size: 14,
|
|
437
|
+
color: _sharedUi.night.color.danger
|
|
438
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
439
|
+
style: styles.errorText,
|
|
440
|
+
children: isReactReduxAvailable ? "Not inside a Redux Provider. Wrap your app with <Provider store={store}>." : "react-redux not installed. Run: npm install react-redux"
|
|
441
|
+
})]
|
|
442
|
+
}), !isEnabled && isInstrumented && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
443
|
+
style: styles.disabledBanner,
|
|
444
|
+
testID: `${_testIDs.REDUX_TEST_ID}-banner-disabled`,
|
|
445
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Power, {
|
|
446
|
+
size: 14,
|
|
447
|
+
color: _sharedUi.night.color.warning
|
|
448
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
449
|
+
style: styles.disabledText,
|
|
450
|
+
children: "Action capture is disabled"
|
|
451
|
+
})]
|
|
452
|
+
}), captureMechanism === "patch" && !_reduxActionStore.reduxActionStore.isCaptureSuppressed() && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
453
|
+
style: styles.disabledBanner,
|
|
454
|
+
testID: `${_testIDs.REDUX_TEST_ID}-banner-patch`,
|
|
455
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
|
|
456
|
+
size: 14,
|
|
457
|
+
color: _sharedUi.night.color.warning
|
|
458
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
459
|
+
style: styles.disabledText,
|
|
460
|
+
children: "Top-level capture only \u2014 thunk/RTK Query internals aren't visible. Put import '@buoy-gg/redux' first in your app entry (or add buoyReduxMiddleware) for full capture."
|
|
461
|
+
})]
|
|
462
|
+
}), hasLockedActions && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
463
|
+
style: styles.lockedBanner,
|
|
464
|
+
onPress: handleUpgradePress,
|
|
465
|
+
activeOpacity: 0.8,
|
|
466
|
+
testID: `${_testIDs.REDUX_TEST_ID}-locked`,
|
|
467
|
+
accessibilityRole: "button",
|
|
468
|
+
accessibilityLabel: `${lockedActionCount} older ${lockedActionCount === 1 ? "action" : "actions"} locked. Upgrade to see them.`,
|
|
469
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Lock, {
|
|
470
|
+
size: 14,
|
|
471
|
+
color: _sharedUi.night.color.accent
|
|
472
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
473
|
+
style: styles.lockedText,
|
|
474
|
+
testID: `${_testIDs.REDUX_TEST_ID}-locked-text`,
|
|
475
|
+
children: [lockedActionCount, " older ", lockedActionCount === 1 ? "action" : "actions", " locked"]
|
|
476
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
477
|
+
style: styles.upgradeBadge,
|
|
478
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
479
|
+
style: styles.upgradeBadgeText,
|
|
480
|
+
children: "UPGRADE"
|
|
481
|
+
})
|
|
482
|
+
})]
|
|
483
|
+
}), visibleActions.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
|
|
484
|
+
ref: flatListRef,
|
|
485
|
+
testID: `${_testIDs.REDUX_TEST_ID}-list`,
|
|
486
|
+
data: visibleActions,
|
|
487
|
+
renderItem: renderItem,
|
|
488
|
+
keyExtractor: keyExtractor,
|
|
489
|
+
contentContainerStyle: styles.listContent,
|
|
490
|
+
showsVerticalScrollIndicator: true,
|
|
491
|
+
..._sharedUi.EVENT_LIST_PRESET
|
|
492
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(EmptyState, {
|
|
493
|
+
isEnabled: isEnabled,
|
|
494
|
+
query: searchText,
|
|
495
|
+
totalActions: stats.totalActions
|
|
496
|
+
})]
|
|
497
|
+
})
|
|
498
|
+
});
|
|
499
|
+
const upgradeModal = /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProUpgradeModal, {
|
|
500
|
+
visible: showProModal,
|
|
501
|
+
onClose: () => setShowProModal(false),
|
|
502
|
+
featureName: "Full Action History"
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// The ONLY difference between the phone and the desktop dashboard is which
|
|
506
|
+
// shell the header, body and footer go in. Everything above this is shared.
|
|
507
|
+
if (inline) {
|
|
508
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.TickProvider, {
|
|
509
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
510
|
+
style: styles.inlineRoot,
|
|
511
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
512
|
+
style: styles.inlineHeader,
|
|
513
|
+
..._sharedUi.windowDragProps,
|
|
514
|
+
children: renderHeaderContent()
|
|
515
|
+
}), body, footerNode]
|
|
516
|
+
}), upgradeModal]
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.TickProvider, {
|
|
520
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.JsModal, {
|
|
521
|
+
visible: visible,
|
|
522
|
+
onClose: onClose,
|
|
523
|
+
onMinimize: onMinimize,
|
|
524
|
+
persistenceKey: persistenceKey,
|
|
525
|
+
header: {
|
|
526
|
+
showToggleButton: true,
|
|
527
|
+
customContent: renderHeaderContent()
|
|
528
|
+
},
|
|
529
|
+
onModeChange: handleModeChange,
|
|
530
|
+
enablePersistence: true,
|
|
531
|
+
initialMode: "bottomSheet",
|
|
532
|
+
enableGlitchEffects: true,
|
|
533
|
+
disableScrollWrapper: listOwnsScroll,
|
|
534
|
+
styles: {},
|
|
535
|
+
footer: footerNode,
|
|
536
|
+
footerHeight: footerHeight,
|
|
537
|
+
children: body
|
|
538
|
+
}), upgradeModal]
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* The same Redux tool as `ReduxModal`, rendered INLINE for hosts that place
|
|
544
|
+
* tools in a panel rather than a modal. Exists so there is exactly one
|
|
545
|
+
* implementation — the desktop dashboard used to ship its own copy of this
|
|
546
|
+
* component's header, action list and detail router.
|
|
547
|
+
*/
|
|
548
|
+
function ReduxToolView(props = {}) {
|
|
549
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(ReduxModal, {
|
|
550
|
+
...props,
|
|
551
|
+
inline: true,
|
|
552
|
+
visible: true,
|
|
553
|
+
onClose: () => {}
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
const styles = _reactNative.StyleSheet.create({
|
|
557
|
+
inlineRoot: {
|
|
558
|
+
flex: 1,
|
|
559
|
+
minHeight: 0,
|
|
560
|
+
// Transparent so the desktop workbench's hoisted sky (one field spanning
|
|
561
|
+
// sidebar + panel) shows through — the shell paints its base beneath it.
|
|
562
|
+
backgroundColor: "transparent"
|
|
563
|
+
},
|
|
564
|
+
inlineHeader: {
|
|
565
|
+
paddingHorizontal: 12,
|
|
566
|
+
paddingVertical: 8,
|
|
567
|
+
borderBottomWidth: 1,
|
|
568
|
+
borderBottomColor: _sharedUi.macOSColors.border.default,
|
|
569
|
+
// Clear, like Bench — the host's hoisted sky shows through the header.
|
|
570
|
+
backgroundColor: "transparent"
|
|
571
|
+
},
|
|
572
|
+
container: {
|
|
573
|
+
flex: 1,
|
|
574
|
+
// Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
|
|
575
|
+
backgroundColor: "transparent"
|
|
576
|
+
},
|
|
577
|
+
headerChipRow: {
|
|
578
|
+
flexDirection: "row",
|
|
579
|
+
alignItems: "center",
|
|
580
|
+
gap: 8
|
|
581
|
+
},
|
|
582
|
+
headerChip: {
|
|
583
|
+
flexDirection: "row",
|
|
584
|
+
alignItems: "center",
|
|
585
|
+
gap: 4,
|
|
586
|
+
backgroundColor: _sharedUi.night.color.surfaceElevated,
|
|
587
|
+
paddingHorizontal: 10,
|
|
588
|
+
paddingVertical: 5,
|
|
589
|
+
borderRadius: 12,
|
|
590
|
+
borderWidth: 1,
|
|
591
|
+
borderColor: _sharedUi.night.color.border
|
|
592
|
+
},
|
|
593
|
+
headerChipActive: {
|
|
594
|
+
// Active chip chrome takes the night accent, not info-cyan.
|
|
595
|
+
backgroundColor: _sharedUi.night.color.accentSoft,
|
|
596
|
+
borderColor: _sharedUi.night.color.accentBorderStrong
|
|
597
|
+
},
|
|
598
|
+
headerChipLabel: {
|
|
599
|
+
fontSize: 10,
|
|
600
|
+
fontWeight: "700",
|
|
601
|
+
color: _sharedUi.night.color.textTertiary
|
|
602
|
+
},
|
|
603
|
+
headerChipValue: {
|
|
604
|
+
fontSize: 12,
|
|
605
|
+
fontWeight: "600",
|
|
606
|
+
fontFamily: _sharedUi.monoFont
|
|
607
|
+
},
|
|
608
|
+
headerActionButton: {
|
|
609
|
+
width: 32,
|
|
610
|
+
height: 32,
|
|
611
|
+
borderRadius: 8,
|
|
612
|
+
backgroundColor: _sharedUi.night.color.surfaceElevated,
|
|
613
|
+
borderWidth: 1,
|
|
614
|
+
borderColor: _sharedUi.night.color.border,
|
|
615
|
+
alignItems: "center",
|
|
616
|
+
justifyContent: "center"
|
|
617
|
+
},
|
|
618
|
+
headerActionButtonDisabled: {
|
|
619
|
+
opacity: 0.55
|
|
620
|
+
},
|
|
621
|
+
headerActionButtonActive: {
|
|
622
|
+
backgroundColor: _sharedUi.night.color.accentSoft,
|
|
623
|
+
borderColor: _sharedUi.night.color.accentBorderStrong
|
|
624
|
+
},
|
|
625
|
+
startButton: {
|
|
626
|
+
backgroundColor: _sharedUi.macOSColors.semantic.successBackground,
|
|
627
|
+
borderColor: _sharedUi.macOSColors.semantic.success + "40"
|
|
628
|
+
},
|
|
629
|
+
stopButton: {
|
|
630
|
+
backgroundColor: _sharedUi.macOSColors.semantic.errorBackground,
|
|
631
|
+
borderColor: _sharedUi.macOSColors.semantic.error + "40"
|
|
632
|
+
},
|
|
633
|
+
errorBanner: {
|
|
634
|
+
flexDirection: "row",
|
|
635
|
+
alignItems: "center",
|
|
636
|
+
gap: 8,
|
|
637
|
+
padding: 10,
|
|
638
|
+
marginHorizontal: 12,
|
|
639
|
+
marginTop: 8,
|
|
640
|
+
backgroundColor: _sharedUi.night.color.dangerSoft,
|
|
641
|
+
borderRadius: 8,
|
|
642
|
+
borderWidth: 1,
|
|
643
|
+
borderColor: _sharedUi.night.color.danger + "20"
|
|
644
|
+
},
|
|
645
|
+
errorText: {
|
|
646
|
+
color: _sharedUi.night.color.danger,
|
|
647
|
+
fontSize: 11,
|
|
648
|
+
flex: 1
|
|
649
|
+
},
|
|
650
|
+
disabledBanner: {
|
|
651
|
+
flexDirection: "row",
|
|
652
|
+
alignItems: "center",
|
|
653
|
+
gap: 8,
|
|
654
|
+
padding: 10,
|
|
655
|
+
marginHorizontal: 12,
|
|
656
|
+
marginTop: 8,
|
|
657
|
+
backgroundColor: _sharedUi.night.color.warningSoft,
|
|
658
|
+
borderRadius: 8,
|
|
659
|
+
borderWidth: 1,
|
|
660
|
+
borderColor: _sharedUi.night.color.warning + "20"
|
|
661
|
+
},
|
|
662
|
+
disabledText: {
|
|
663
|
+
color: _sharedUi.night.color.warning,
|
|
664
|
+
fontSize: 11,
|
|
665
|
+
flex: 1
|
|
666
|
+
},
|
|
667
|
+
listContent: {
|
|
668
|
+
paddingTop: 8,
|
|
669
|
+
paddingBottom: 20
|
|
670
|
+
},
|
|
671
|
+
// Locked actions banner styles
|
|
672
|
+
lockedBanner: {
|
|
673
|
+
flexDirection: "row",
|
|
674
|
+
alignItems: "center",
|
|
675
|
+
gap: 8,
|
|
676
|
+
padding: 10,
|
|
677
|
+
marginHorizontal: 12,
|
|
678
|
+
marginTop: 8,
|
|
679
|
+
backgroundColor: _sharedUi.night.color.accentSoft,
|
|
680
|
+
borderRadius: 8,
|
|
681
|
+
borderWidth: 1,
|
|
682
|
+
borderColor: _sharedUi.night.color.accentBorder
|
|
683
|
+
},
|
|
684
|
+
lockedText: {
|
|
685
|
+
color: _sharedUi.night.color.accent,
|
|
686
|
+
fontSize: 11,
|
|
687
|
+
fontWeight: "500",
|
|
688
|
+
flex: 1
|
|
689
|
+
},
|
|
690
|
+
upgradeBadge: {
|
|
691
|
+
backgroundColor: _sharedUi.night.color.accent,
|
|
692
|
+
paddingHorizontal: 8,
|
|
693
|
+
paddingVertical: 3,
|
|
694
|
+
borderRadius: 4
|
|
695
|
+
},
|
|
696
|
+
upgradeBadgeText: {
|
|
697
|
+
// The night accent takes dark ink.
|
|
698
|
+
color: _sharedUi.night.color.onAccent,
|
|
699
|
+
fontSize: 9,
|
|
700
|
+
fontWeight: "700",
|
|
701
|
+
letterSpacing: 0.5
|
|
702
|
+
}
|
|
703
|
+
});
|