mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-10 04:02:38 -05:00
Compare commits
2 Commits
ts-runQuer
...
ts-useSpli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a767d99534 | ||
|
|
636153593e |
@@ -2094,7 +2094,7 @@ export const TransactionTable = forwardRef((props, ref) => {
|
|||||||
result = props.transactions.filter((t, idx) => {
|
result = props.transactions.filter((t, idx) => {
|
||||||
if (t.parent_id) {
|
if (t.parent_id) {
|
||||||
if (idx >= index) {
|
if (idx >= index) {
|
||||||
return splitsExpanded.expanded(t.parent_id);
|
return splitsExpanded.isExpanded(t.parent_id);
|
||||||
} else if (prevSplitsExpanded.current) {
|
} else if (prevSplitsExpanded.current) {
|
||||||
return prevSplitsExpanded.current.expanded(t.parent_id);
|
return prevSplitsExpanded.current.expanded(t.parent_id);
|
||||||
}
|
}
|
||||||
@@ -2113,7 +2113,7 @@ export const TransactionTable = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
result = props.transactions.filter(t => {
|
result = props.transactions.filter(t => {
|
||||||
if (t.parent_id) {
|
if (t.parent_id) {
|
||||||
return splitsExpanded.expanded(t.parent_id);
|
return splitsExpanded.isExpanded(t.parent_id);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -2584,7 +2584,7 @@ export const TransactionTable = forwardRef((props, ref) => {
|
|||||||
transactionsByParent={transactionsByParent}
|
transactionsByParent={transactionsByParent}
|
||||||
transferAccountsByTransaction={transferAccountsByTransaction}
|
transferAccountsByTransaction={transferAccountsByTransaction}
|
||||||
selectedItems={selectedItems}
|
selectedItems={selectedItems}
|
||||||
isExpanded={splitsExpanded.expanded}
|
isExpanded={splitsExpanded.isExpanded}
|
||||||
onSave={onSave}
|
onSave={onSave}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
onDuplicate={onDuplicate}
|
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