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

Switch to dnd-kit for drag and drop #2239

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 4 additions & 2 deletions packages/desktop-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
"build"
],
"devDependencies": {
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@juggle/resize-observer": "^3.1.2",
"@playwright/test": "^1.41.1",
"@reach/listbox": "^0.18.0",
Expand Down Expand Up @@ -45,8 +49,6 @@
"memoize-one": "^6.0.0",
"pikaday": "1.8.2",
"react": "18.2.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.2.0",
"react-error-boundary": "^4.0.11",
"react-markdown": "^8.0.7",
Expand Down
6 changes: 1 addition & 5 deletions packages/desktop-client/src/components/FinancesApp.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// @ts-strict-ignore
import React, { type ReactElement, useEffect, useMemo } from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend as Backend } from 'react-dnd-html5-backend';
import {
Route,
Routes,
Expand Down Expand Up @@ -267,9 +265,7 @@ export function FinancesApp() {
<BudgetMonthCountProvider>
<PayeesProvider>
<AccountsProvider>
<DndProvider backend={Backend}>
<ScrollProvider>{app}</ScrollProvider>
</DndProvider>
<ScrollProvider>{app}</ScrollProvider>
</AccountsProvider>
</PayeesProvider>
</BudgetMonthCountProvider>
Expand Down
158 changes: 135 additions & 23 deletions packages/desktop-client/src/components/accounts/MobileAccounts.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';

import {
DndContext,
MouseSensor,
TouchSensor,
closestCenter,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
restrictToParentElement,
restrictToVerticalAxis,
} from '@dnd-kit/modifiers';
import {
SortableContext,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

import * as queries from 'loot-core/src/client/queries';
import { send } from 'loot-core/src/platform/client/fetch';

import { useActions } from '../../hooks/useActions';
import { useCategories } from '../../hooks/useCategories';
Expand All @@ -16,6 +36,7 @@ import { View } from '../common/View';
import { Page } from '../Page';
import { PullToRefresh } from '../responsive/PullToRefresh';
import { CellValue } from '../spreadsheet/CellValue';
import { findSortDown, getDropPosition } from '../util/sort';

function AccountHeader({ name, amount, style = {} }) {
return (
Expand Down Expand Up @@ -52,8 +73,26 @@ function AccountHeader({ name, amount, style = {} }) {
}

function AccountCard({ account, updated, getBalanceQuery, onSelect }) {
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: account.id });

const dndStyle = {
opacity: isDragging ? 0.5 : undefined,
transform: CSS.Transform.toString(transform),
transition,
};

return (
<View
innerRef={setNodeRef}
{...attributes}
{...listeners}
style={{
flex: 1,
flexDirection: 'row',
Expand All @@ -63,20 +102,18 @@ function AccountCard({ account, updated, getBalanceQuery, onSelect }) {
marginTop: 10,
marginRight: 10,
width: '100%',
...dndStyle,
}}
data-testid="account"
>
<Button
onMouseDown={() => onSelect(account.id)}
onClick={() => onSelect(account.id)}
style={{
flexDirection: 'row',
border: '1px solid ' + theme.pillBorder,
flex: 1,
alignItems: 'center',
borderRadius: 6,
'&:active': {
opacity: 0.1,
},
}}
>
<View
Expand Down Expand Up @@ -149,6 +186,7 @@ function AccountList({
onAddAccount,
onSelectAccount,
onSync,
onReorder,
}) {
const budgetedAccounts = accounts.filter(account => account.offbudget === 0);
const offbudgetAccounts = accounts.filter(account => account.offbudget === 1);
Expand All @@ -157,6 +195,41 @@ function AccountList({
color: 'white',
};

const sensors = useSensors(
useSensor(TouchSensor, {
activationConstraint: {
delay: 250,
tolerance: 5,
},
}),
useSensor(MouseSensor, {
activationConstraint: {
distance: 10,
},
}),
);

const [isDragging, setIsDragging] = useState(false);

const onDragStart = () => {
setIsDragging(true);
};

const onDragEnd = e => {
const { active, over } = e;

if (active.id !== over.id) {
const dropPos = getDropPosition(
active.rect.current.translated,
active.rect.current.initial,
);

onReorder(active.id, dropPos, over.id);
}

setIsDragging(false);
};

return (
<Page
title="Accounts"
Expand All @@ -178,20 +251,35 @@ function AccountList({
style={{ flex: 1, backgroundColor: theme.mobilePageBackground }}
>
{accounts.length === 0 && <EmptyMessage />}
<PullToRefresh onRefresh={onSync}>
<PullToRefresh isPullable={!isDragging} onRefresh={onSync}>
<View style={{ margin: 10 }}>
{budgetedAccounts.length > 0 && (
<AccountHeader name="For Budget" amount={getOnBudgetBalance()} />
)}
{budgetedAccounts.map(acct => (
<AccountCard
account={acct}
key={acct.id}
updated={updatedAccounts.includes(acct.id)}
getBalanceQuery={getBalanceQuery}
onSelect={onSelectAccount}
/>
))}
<View>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
modifiers={[restrictToVerticalAxis, restrictToParentElement]}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
>
<SortableContext
items={budgetedAccounts}
strategy={verticalListSortingStrategy}
>
{budgetedAccounts.map(acct => (
<AccountCard
account={acct}
key={acct.id}
updated={updatedAccounts.includes(acct.id)}
getBalanceQuery={getBalanceQuery}
onSelect={onSelectAccount}
/>
))}
</SortableContext>
</DndContext>
</View>

{offbudgetAccounts.length > 0 && (
<AccountHeader
Expand All @@ -200,15 +288,30 @@ function AccountList({
style={{ marginTop: 30 }}
/>
)}
{offbudgetAccounts.map(acct => (
<AccountCard
account={acct}
key={acct.id}
updated={updatedAccounts.includes(acct.id)}
getBalanceQuery={getBalanceQuery}
onSelect={onSelectAccount}
/>
))}
<View>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
modifiers={[restrictToVerticalAxis, restrictToParentElement]}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
>
<SortableContext
items={offbudgetAccounts}
strategy={verticalListSortingStrategy}
>
{offbudgetAccounts.map(acct => (
<AccountCard
account={acct}
key={acct.id}
updated={updatedAccounts.includes(acct.id)}
getBalanceQuery={getBalanceQuery}
onSelect={onSelectAccount}
/>
))}
</SortableContext>
</DndContext>
</View>
</View>
</PullToRefresh>
</Page>
Expand Down Expand Up @@ -244,6 +347,14 @@ export function Accounts() {
navigate(`/transaction/${transaction}`);
};

const onReorder = async (id, dropPos, targetId) => {
await send('account-move', {
id,
...findSortDown(accounts, dropPos, targetId),
});
await getAccounts();
};

useSetThemeColor(theme.mobileViewTheme);

return (
Expand All @@ -264,6 +375,7 @@ export function Accounts() {
onSelectAccount={onSelectAccount}
onSelectTransaction={onSelectTransaction}
onSync={syncAndDownload}
onReorder={onReorder}
/>
</View>
);
Expand Down
Loading
Loading