Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction Table Resize #3309

Open
wants to merge 55 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a024264
test
lelemm Aug 23, 2024
a9057e1
fixes
lelemm Aug 26, 2024
67ec8d6
maxsize fix
lelemm Aug 26, 2024
d489f62
maxsize fix #2
lelemm Aug 26, 2024
6df0e6a
fixes
lelemm Aug 26, 2024
fb323f9
calc fix
lelemm Aug 26, 2024
d36a9eb
clean up
lelemm Aug 26, 2024
25acb93
testing for manual resize
lelemm Aug 27, 2024
4b0dd0f
big mess
lelemm Aug 30, 2024
10d3840
Merge branch 'master' into table_resize_test
lelemm Sep 2, 2024
6c2ec5d
fixes and cleanup
lelemm Sep 2, 2024
ed5ace2
fixes and visual regression
lelemm Sep 2, 2024
848727f
missing scroll provider and typechecks
lelemm Sep 2, 2024
3ba0899
changed debounce import
lelemm Sep 2, 2024
d4b6d67
small test fix
lelemm Sep 2, 2024
059ad96
linter
lelemm Sep 2, 2024
1f4a72d
Fixes
lelemm Sep 2, 2024
cac2ffb
Fixes
lelemm Sep 2, 2024
a8c8d85
add reset menu to resizer
lelemm Sep 2, 2024
d729e37
multiple fixes and refactories
lelemm Sep 3, 2024
0c5f98d
Merge branch 'master' into table_resize_test
lelemm Sep 3, 2024
263b091
adjust field selection query
lelemm Sep 4, 2024
6169f69
new vrt snaps
lelemm Sep 4, 2024
613d548
3309.md
lelemm Sep 4, 2024
2f2bd59
linter
lelemm Sep 4, 2024
622c470
testing e2e fixes
lelemm Sep 4, 2024
e315330
revert changes
lelemm Sep 4, 2024
edca1fa
vrt
lelemm Sep 4, 2024
532c400
fixes
lelemm Oct 23, 2024
3b4e6be
Merge remote-tracking branch 'org/master' into table_resize_test
lelemm Oct 23, 2024
a62b1a7
merge fix
lelemm Oct 23, 2024
9691e4b
yarnlock?
lelemm Oct 23, 2024
82a1178
fix test
lelemm Oct 23, 2024
a2d6953
e2e
lelemm Oct 23, 2024
021db10
fix tests
lelemm Oct 23, 2024
9092e50
merge fix
lelemm Oct 23, 2024
25c7c13
linter
lelemm Oct 23, 2024
50c4b65
Merge branch 'master' into table_resize_test
lelemm Oct 24, 2024
38f752d
changes on newTransaction to fit on a new section of the table named …
lelemm Oct 24, 2024
b9f4d19
Merge branch 'table_resize_test' of https://github.com/lelemm/actual …
lelemm Oct 24, 2024
a479c95
fixes and e2e
lelemm Oct 25, 2024
e1f65ad
linter
lelemm Oct 25, 2024
e33dad3
Merge branch 'master' into table_resize_test
lelemm Oct 25, 2024
cc1e804
Update packages/desktop-client/src/components/HorizontalFakeScrollbar…
lelemm Oct 25, 2024
fff2153
Update packages/desktop-client/src/components/HorizontalFakeScrollbar…
lelemm Oct 25, 2024
c8d49e0
changes proposed by coderabbit and fixes
lelemm Oct 25, 2024
eaed656
Update packages/desktop-client/src/components/Resizer.tsx
lelemm Oct 25, 2024
6e3fb4e
coderabbit review suggestion
lelemm Oct 25, 2024
3da670b
removed fake scrollbar
lelemm Nov 14, 2024
0fc422c
Update packages/desktop-client/src/components/table.tsx
lelemm Nov 14, 2024
6e2b747
Merge remote-tracking branch 'org/master' into table_resize_test
lelemm Nov 14, 2024
79e51a3
some minor fixes
lelemm Nov 14, 2024
6aac0b0
test fix
lelemm Nov 14, 2024
76838be
e2e update
lelemm Nov 14, 2024
6ac5a5c
Update VRT
github-actions[bot] Nov 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion packages/desktop-client/e2e/transactions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ test.describe('Transactions', () => {
await filterTooltip.applyButton.click();

// Assert that there are no transactions
await expect(accountPage.transactionTable).toHaveText('No transactions');
await expect(accountPage.transactionTable).toContainText(
'No transactions',
);
await expect(page).toMatchThemeScreenshots();
});

Expand Down
198 changes: 198 additions & 0 deletions packages/desktop-client/src/components/ColumnWidthContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import React, {
createContext,
useContext,
useState,
useCallback,
useEffect,
useRef,
useMemo,
} from 'react';

import { AUTO_SIZE } from 'loot-core/client/constants';

import { useSyncedPref } from '../hooks/useSyncedPref';

const ColumnWidthContext = createContext();

export const ColumnWidthProvider = ({ children, prefName }) => {
const [columnSizePrefs, setColumnSizePrefs] = useSyncedPref(prefName);
const [columnWidths, setColumnWidths] = useState({});
const [fixedSizedColumns, setFixedSizedColumns] = useState({});
const positionAccumulatorRef = useRef(0);
const [clientWidth, setClientWidth] = useState(0);
const [editMode, setEditMode] = useState(false);

useEffect(() => {
if (columnSizePrefs) {
setColumnWidths(JSON.parse(columnSizePrefs));
}
}, [columnSizePrefs]);

const currentTotalWidth = useMemo(() => {
return Object.values(columnWidths).reduce((acc, width) => acc + width, 0);
}, [columnWidths]);

const otherColumnsWidth = useMemo(() => {
return Object.entries(fixedSizedColumns)
.filter(([key]) => !(key in columnWidths))
.reduce((acc, [, width]) => acc + width + 10, 0);
}, [fixedSizedColumns, columnWidths]);

const totalWidth = useCallback(() => {
const widthSum = otherColumnsWidth + currentTotalWidth + 10;
return widthSum > clientWidth ? widthSum : clientWidth;
}, [currentTotalWidth, otherColumnsWidth, clientWidth]);

const savePrefs = useCallback(
value => {
setColumnSizePrefs(JSON.stringify(value ?? columnWidths));
},
[columnWidths, setColumnSizePrefs],
);

const removeColumn = columnName => {
const { [columnName]: _, ...newObj } = columnWidths;
setColumnWidths(newObj);
savePrefs(newObj);
};

const resetAllColumns = () => {
setColumnWidths({});
savePrefs({});
};

const handleDoubleClick = columnName => {
updateColumnWidth(columnName, -1);

setTimeout(() => {
let maximum = -1;
document
.querySelectorAll(`[data-resizeable-column=${columnName}]`)
.forEach(row => {
const rect = row.getBoundingClientRect();
const styles = getComputedStyle(row);
const localValue = Math.max(
rect.width +
parseFloat(styles.marginLeft) +
parseFloat(styles.marginRight),
110,
);

maximum = Math.max(localValue, maximum);
});

const newObj = updateColumnWidth(columnName, maximum);

savePrefs(newObj);
}, 100);
};

const getViewportWidth = useCallback(() => clientWidth, [clientWidth]);

const updateColumnWidth = useCallback(
(columnName, accumulatedDelta) => {
const existingColumnWidth = columnWidths[columnName] || 0;
const newWidth = accumulatedDelta;

if (newWidth === AUTO_SIZE) {
const newObj = {
...columnWidths,
[columnName]: newWidth,
};
setColumnWidths(newObj);
return newObj;
}

const currentTotalWidth = Object.values(columnWidths).reduce(
(acc, width) => acc + width,
0,
);
const otherColumnsWidth = Object.values(fixedSizedColumns).reduce(
(acc, width) => acc + width + 10,
0,
);

const proposedTotalWidth =
currentTotalWidth - existingColumnWidth + newWidth + otherColumnsWidth;

const viewportWidth = getViewportWidth();
const adjustedWidth =
proposedTotalWidth > viewportWidth
? viewportWidth - otherColumnsWidth
: newWidth;

const newObj = {
...columnWidths,
[columnName]: Math.max(adjustedWidth, 110),
};

setColumnWidths(newObj);

return newObj;
},
[columnWidths, fixedSizedColumns, getViewportWidth],
);
lelemm marked this conversation as resolved.
Show resolved Hide resolved

const handleMoveProps = (columnName, ref, resizerRef) => {
const updatePosition = deltaX => {
positionAccumulatorRef.current += deltaX;
updateColumnWidth(columnName, positionAccumulatorRef.current);
};

return {
onMoveStart() {
if (resizerRef.current) {
resizerRef.current.classList.add('dragging');
let columnWidth = columnWidths[columnName];
if (!columnWidth) {
const rect = ref.getBoundingClientRect();
columnWidth = rect.width;
}
const startWidth = columnWidth;
positionAccumulatorRef.current = startWidth;
}
},
onMove(e) {
if (resizerRef.current) {
updatePosition(e.deltaX);
}
},
onMoveEnd() {
savePrefs();
if (resizerRef.current) {
resizerRef.current.classList.remove('dragging');
}
positionAccumulatorRef.current = 0;
},
};
};

return (
<ColumnWidthContext.Provider
value={{
columnWidths,
handleMoveProps,
setFixedSizedColumns,
handleDoubleClick,
totalWidth,
removeColumn,
clientWidth,
setClientWidth,
resetAllColumns,
editMode,
setEditMode,
fixedSizedColumns,
}}
>
{children}
</ColumnWidthContext.Provider>
);
};

export const useColumnWidth = () => {
const context = useContext(ColumnWidthContext);
if (!context) {
throw new Error('useColumnWidth must be used within a ColumnWidthProvider');
}
return context;
};
55 changes: 44 additions & 11 deletions packages/desktop-client/src/components/FixedSizeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,23 @@ type FixedSizeListProps = {
scrollDirection: FixedSizeListState['scrollDirection'];
scrollOffset: FixedSizeListState['scrollOffset'];
scrollUpdateWasRequested: FixedSizeListState['scrollUpdateWasRequested'];
scrollLeftOffset: FixedSizeListState['scrollLeftOffset'];
}) => void;
indexForKey?: (key: string | number) => number;
height?: number;
width?: number;
header?: ReactNode;
innerRef?: Ref<HTMLDivElement>;
itemKey?: (index: number) => string | number;
totalWidth?: number;
lelemm marked this conversation as resolved.
Show resolved Hide resolved
};

type FixedSizeListState = {
isScrolling: boolean;
scrollDirection: 'forward' | 'backward';
scrollOffset: number;
scrollUpdateWasRequested: boolean;
scrollLeftOffset: number;
};

export class FixedSizeList extends PureComponent<
Expand Down Expand Up @@ -103,6 +106,7 @@ export class FixedSizeList extends PureComponent<
? this.props.initialScrollOffset
: 0,
scrollUpdateWasRequested: false,
scrollLeftOffset: 0,
};
}

Expand Down Expand Up @@ -203,6 +207,7 @@ export class FixedSizeList extends PureComponent<
itemKey = defaultItemKey,
useIsScrolling,
width,
totalWidth,
lelemm marked this conversation as resolved.
Show resolved Hide resolved
} = this.props;
const { isScrolling } = this.state;

Expand Down Expand Up @@ -256,7 +261,7 @@ export class FixedSizeList extends PureComponent<
style={{
height,
width,
overflow: 'hidden auto',
overflow: 'visible auto',
lelemm marked this conversation as resolved.
Show resolved Hide resolved
}}
>
<View>{header}</View>
Expand All @@ -265,7 +270,7 @@ export class FixedSizeList extends PureComponent<
style={{
position: 'relative',
height: estimatedTotalSize,
width: '100%',
width: `${Math.max(totalWidth || 0, width || 0) - 16}px`,
pointerEvents: isScrolling ? 'none' : undefined,
}}
>
Expand Down Expand Up @@ -412,11 +417,17 @@ export class FixedSizeList extends PureComponent<
);

_callOnScroll = memoizeOne(
(scrollDirection, scrollOffset, scrollUpdateWasRequested) =>
(
scrollDirection,
scrollOffset,
scrollUpdateWasRequested,
scrollLeftOffset,
) =>
this.props.onScroll({
scrollDirection,
scrollOffset,
scrollUpdateWasRequested,
scrollLeftOffset,
}),
);

Expand All @@ -440,12 +451,17 @@ export class FixedSizeList extends PureComponent<
}

if (typeof this.props.onScroll === 'function') {
const { scrollDirection, scrollOffset, scrollUpdateWasRequested } =
this.state;
const {
scrollDirection,
scrollOffset,
scrollUpdateWasRequested,
scrollLeftOffset,
} = this.state;
this._callOnScroll(
scrollDirection,
scrollOffset,
scrollUpdateWasRequested,
scrollLeftOffset,
);
}
}
Expand Down Expand Up @@ -522,24 +538,41 @@ export class FixedSizeList extends PureComponent<
}

_onScrollVertical = (event: UIEvent<HTMLDivElement>) => {
const { scrollTop } = event.currentTarget;
const { scrollTop, scrollLeft } = event.currentTarget;

this.setState(prevState => {
if (prevState.scrollOffset === scrollTop) {
if (
prevState.scrollOffset === scrollTop &&
prevState.scrollLeftOffset === scrollLeft
) {
// Scroll position may have been updated by cDM/cDU,
// In which case we don't need to trigger another render,
// And we don't want to update state.isScrolling.
return null;
}
const newState = {
isScrolling: scrollTop !== prevState.scrollOffset,
scrollDirection:
prevState.scrollOffset < scrollTop ? 'forward' : 'backward',
scrollOffset: scrollTop,
scrollUpdateWasRequested: false,
scrollLeftOffset: scrollLeft,
};

const scrollOffset = scrollTop;
this._callOnScroll(
newState.scrollDirection,
newState.scrollOffset,
newState.scrollUpdateWasRequested,
newState.scrollLeftOffset,
);

return {
isScrolling: true,
isScrolling: scrollTop !== prevState.scrollOffset,
scrollDirection:
prevState.scrollOffset < scrollOffset ? 'forward' : 'backward',
scrollOffset,
prevState.scrollOffset < scrollTop ? 'forward' : 'backward',
scrollOffset: scrollTop,
scrollUpdateWasRequested: false,
scrollLeftOffset: scrollLeft,
lelemm marked this conversation as resolved.
Show resolved Hide resolved
};
}, this._resetIsScrollingDebounced);
};
Expand Down
32 changes: 32 additions & 0 deletions packages/desktop-client/src/components/Resizer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.resizer-container {
position: relative;
width: 10px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 2px;
margin-bottom: 2px;
z-index: 1500;
}

.resizer-container-enabled {
cursor: col-resize;
}

.resizer-container-enabled::before {
content: '';
display: block;
width: 2px;
height: 80%;
background-color: inherit;
border-radius: 2px;
box-shadow: inset 0.5px 0.5px 1px rgba(0, 0, 0, 0.7),
inset -0.5px -0.5px 1px rgba(255, 255, 255, 0.7);
transition: box-shadow 0.2s ease, background-color 0.2s ease;
}

.resizer-container-enabled:hover::before {
box-shadow: 0.5px 0.5px 1px rgba(0, 0, 0, 0.1),
-0.5px -0.5px 1px rgba(255, 255, 255, 0.1);
background-color: rgba(0, 0, 0, 0.05);
}
Loading