mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-09 03:32:54 -05:00
Compare commits
2 Commits
v25.5.0
...
ts-useSpli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a767d99534 | ||
|
|
636153593e |
@@ -2094,7 +2094,7 @@ export const TransactionTable = forwardRef((props, ref) => {
|
||||
result = props.transactions.filter((t, idx) => {
|
||||
if (t.parent_id) {
|
||||
if (idx >= index) {
|
||||
return splitsExpanded.expanded(t.parent_id);
|
||||
return splitsExpanded.isExpanded(t.parent_id);
|
||||
} else if (prevSplitsExpanded.current) {
|
||||
return prevSplitsExpanded.current.expanded(t.parent_id);
|
||||
}
|
||||
@@ -2113,7 +2113,7 @@ export const TransactionTable = forwardRef((props, ref) => {
|
||||
|
||||
result = props.transactions.filter(t => {
|
||||
if (t.parent_id) {
|
||||
return splitsExpanded.expanded(t.parent_id);
|
||||
return splitsExpanded.isExpanded(t.parent_id);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -2584,7 +2584,7 @@ export const TransactionTable = forwardRef((props, ref) => {
|
||||
transactionsByParent={transactionsByParent}
|
||||
transferAccountsByTransaction={transferAccountsByTransaction}
|
||||
selectedItems={selectedItems}
|
||||
isExpanded={splitsExpanded.expanded}
|
||||
isExpanded={splitsExpanded.isExpanded}
|
||||
onSave={onSave}
|
||||
onDelete={onDelete}
|
||||
onDuplicate={onDuplicate}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useMemo,
|
||||
useEffect,
|
||||
useContext,
|
||||
useReducer,
|
||||
} from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
const SplitsExpandedContext = createContext(null);
|
||||
|
||||
export function useSplitsExpanded() {
|
||||
const data = useContext(SplitsExpandedContext);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
...data,
|
||||
expanded: id =>
|
||||
data.state.mode === 'collapse'
|
||||
? !data.state.ids.has(id)
|
||||
: data.state.ids.has(id),
|
||||
}),
|
||||
[data],
|
||||
);
|
||||
}
|
||||
|
||||
export function SplitsExpandedProvider({ children, initialMode = 'expand' }) {
|
||||
const cachedState = useSelector(state => state.app.lastSplitState);
|
||||
const reduxDispatch = useDispatch();
|
||||
|
||||
const [state, dispatch] = useReducer(
|
||||
(state, action) => {
|
||||
switch (action.type) {
|
||||
case 'toggle-split': {
|
||||
const ids = new Set([...state.ids]);
|
||||
const { id } = action;
|
||||
if (ids.has(id)) {
|
||||
ids.delete(id);
|
||||
} else {
|
||||
ids.add(id);
|
||||
}
|
||||
return { ...state, ids };
|
||||
}
|
||||
case 'open-split': {
|
||||
const ids = new Set([...state.ids]);
|
||||
const { id } = action;
|
||||
if (state.mode === 'collapse') {
|
||||
ids.delete(id);
|
||||
} else {
|
||||
ids.add(id);
|
||||
}
|
||||
return { ...state, ids };
|
||||
}
|
||||
case 'close-splits': {
|
||||
const ids = new Set([...state.ids]);
|
||||
action.ids.forEach(id => {
|
||||
if (state.mode === 'collapse') {
|
||||
ids.add(id);
|
||||
} else {
|
||||
ids.delete(id);
|
||||
}
|
||||
});
|
||||
return { ...state, ids };
|
||||
}
|
||||
case 'set-mode': {
|
||||
return {
|
||||
...state,
|
||||
mode: action.mode,
|
||||
ids: new Set(),
|
||||
transitionId: null,
|
||||
};
|
||||
}
|
||||
case 'switch-mode':
|
||||
if (state.transitionId != null) {
|
||||
// You can only transition once at a time
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
mode: state.mode === 'expand' ? 'collapse' : 'expand',
|
||||
transitionId: action.id,
|
||||
ids: new Set(),
|
||||
};
|
||||
case 'finish-switch-mode':
|
||||
return { ...state, transitionId: null };
|
||||
default:
|
||||
throw new Error('Unknown action type: ' + action.type);
|
||||
}
|
||||
},
|
||||
cachedState.current || { ids: new Set(), mode: initialMode },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.transitionId != null) {
|
||||
// This timeout allows animations to finish
|
||||
setTimeout(() => {
|
||||
dispatch({ type: 'finish-switch-mode' });
|
||||
}, 250);
|
||||
}
|
||||
}, [state.transitionId]);
|
||||
|
||||
useEffect(() => {
|
||||
// In a finished state, cache the state
|
||||
if (state.transitionId == null) {
|
||||
reduxDispatch({ type: 'SET_LAST_SPLIT_STATE', splitState: state });
|
||||
}
|
||||
}, [reduxDispatch, state]);
|
||||
|
||||
const value = useMemo(() => ({ state, dispatch }), [state, dispatch]);
|
||||
|
||||
return (
|
||||
<SplitsExpandedContext.Provider value={value}>
|
||||
{children}
|
||||
</SplitsExpandedContext.Provider>
|
||||
);
|
||||
}
|
||||
178
packages/desktop-client/src/hooks/useSplitsExpanded.tsx
Normal file
178
packages/desktop-client/src/hooks/useSplitsExpanded.tsx
Normal file
@@ -0,0 +1,178 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useMemo,
|
||||
useEffect,
|
||||
useContext,
|
||||
useReducer,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import { type SplitState } from 'loot-core/client/state-types/app';
|
||||
|
||||
type SplitsExpandedState = SplitState & {
|
||||
transitionId?: string;
|
||||
};
|
||||
|
||||
type SplitsExpandedAction =
|
||||
| {
|
||||
type: 'toggle-split';
|
||||
id: string;
|
||||
}
|
||||
| {
|
||||
type: 'open-split';
|
||||
id: string;
|
||||
}
|
||||
| {
|
||||
type: 'close-splits';
|
||||
ids: string[];
|
||||
}
|
||||
| {
|
||||
type: 'set-mode';
|
||||
mode: 'expand' | 'collapse';
|
||||
}
|
||||
| {
|
||||
type: 'switch-mode';
|
||||
id: string;
|
||||
}
|
||||
| {
|
||||
type: 'finish-switch-mode';
|
||||
};
|
||||
|
||||
type SplitsExpandedContextValue = {
|
||||
state: SplitsExpandedState;
|
||||
dispatch: (action: SplitsExpandedAction) => void;
|
||||
};
|
||||
const SplitsExpandedContext = createContext<
|
||||
SplitsExpandedContextValue | undefined
|
||||
>(undefined);
|
||||
|
||||
type UseSplitsExpandedResult = SplitsExpandedContextValue & {
|
||||
isExpanded: (id: string) => boolean;
|
||||
};
|
||||
|
||||
export function useSplitsExpanded(): UseSplitsExpandedResult {
|
||||
const data = useContext(SplitsExpandedContext);
|
||||
|
||||
if (!data) {
|
||||
throw new Error(
|
||||
'useSplitsExpanded must be used within a SplitsExpandedProvider',
|
||||
);
|
||||
}
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
...data,
|
||||
isExpanded: id =>
|
||||
data.state.mode === 'collapse'
|
||||
? !data.state.ids.has(id)
|
||||
: data.state.ids.has(id),
|
||||
}),
|
||||
[data],
|
||||
);
|
||||
}
|
||||
|
||||
function splitsExpandedReducer(
|
||||
state: SplitsExpandedState,
|
||||
action: SplitsExpandedAction,
|
||||
): SplitsExpandedState {
|
||||
switch (action.type) {
|
||||
case 'toggle-split': {
|
||||
const ids = new Set([...state.ids]);
|
||||
const { id } = action;
|
||||
if (ids.has(id)) {
|
||||
ids.delete(id);
|
||||
} else {
|
||||
ids.add(id);
|
||||
}
|
||||
return { ...state, ids };
|
||||
}
|
||||
case 'open-split': {
|
||||
const ids = new Set([...state.ids]);
|
||||
const { id } = action;
|
||||
if (state.mode === 'collapse') {
|
||||
ids.delete(id);
|
||||
} else {
|
||||
ids.add(id);
|
||||
}
|
||||
return { ...state, ids };
|
||||
}
|
||||
case 'close-splits': {
|
||||
const ids = new Set([...state.ids]);
|
||||
action.ids.forEach(id => {
|
||||
if (state.mode === 'collapse') {
|
||||
ids.add(id);
|
||||
} else {
|
||||
ids.delete(id);
|
||||
}
|
||||
});
|
||||
return { ...state, ids };
|
||||
}
|
||||
case 'set-mode': {
|
||||
return {
|
||||
...state,
|
||||
mode: action.mode,
|
||||
ids: new Set(),
|
||||
transitionId: undefined,
|
||||
};
|
||||
}
|
||||
case 'switch-mode':
|
||||
if (state.transitionId != null) {
|
||||
// You can only transition once at a time
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
mode: state.mode === 'expand' ? 'collapse' : 'expand',
|
||||
transitionId: action.id,
|
||||
ids: new Set(),
|
||||
};
|
||||
case 'finish-switch-mode':
|
||||
return { ...state, transitionId: undefined };
|
||||
default:
|
||||
throw new Error(`Unknown action: ${JSON.stringify(action)}`);
|
||||
}
|
||||
}
|
||||
|
||||
type SplitsExpandedProviderProps = {
|
||||
children: ReactNode;
|
||||
initialMode?: 'expand' | 'collapse';
|
||||
};
|
||||
|
||||
export function SplitsExpandedProvider({
|
||||
children,
|
||||
initialMode = 'expand',
|
||||
}: SplitsExpandedProviderProps) {
|
||||
const cachedState = useSelector(state => state.app.lastSplitState);
|
||||
const reduxDispatch = useDispatch();
|
||||
|
||||
const [state, dispatch] = useReducer<typeof splitsExpandedReducer>(
|
||||
splitsExpandedReducer,
|
||||
cachedState.current || { ids: new Set(), mode: initialMode },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.transitionId != null) {
|
||||
// This timeout allows animations to finish
|
||||
setTimeout(() => {
|
||||
dispatch({ type: 'finish-switch-mode' });
|
||||
}, 250);
|
||||
}
|
||||
}, [state.transitionId]);
|
||||
|
||||
useEffect(() => {
|
||||
// In a finished state, cache the state
|
||||
if (state.transitionId == null) {
|
||||
reduxDispatch({ type: 'SET_LAST_SPLIT_STATE', splitState: state });
|
||||
}
|
||||
}, [reduxDispatch, state]);
|
||||
|
||||
const value = useMemo(() => ({ state, dispatch }), [state, dispatch]);
|
||||
|
||||
return (
|
||||
<SplitsExpandedContext.Provider value={value}>
|
||||
{children}
|
||||
</SplitsExpandedContext.Provider>
|
||||
);
|
||||
}
|
||||
6
upcoming-release-notes/3894.md
Normal file
6
upcoming-release-notes/3894.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
Convert useSplitsExpanded.jsx to TypeScript
|
||||
Reference in New Issue
Block a user