diff --git a/app/src/__tests__/components/history/HistoryPage.spec.tsx b/app/src/__tests__/components/history/HistoryPage.spec.tsx
index ad994de74..6f16d11d1 100644
--- a/app/src/__tests__/components/history/HistoryPage.spec.tsx
+++ b/app/src/__tests__/components/history/HistoryPage.spec.tsx
@@ -8,8 +8,9 @@ import HistoryPage from 'components/history/HistoryPage';
describe('HistoryPage', () => {
let store: Store;
- beforeEach(() => {
+ beforeEach(async () => {
store = createStore();
+ await store.swapStore.fetchSwaps();
});
const render = () => {
@@ -35,9 +36,35 @@ describe('HistoryPage', () => {
expect(getByText('Updated')).toBeInTheDocument();
});
- it('should export channels', () => {
+ it('should export loop history', () => {
const { getByText } = render();
fireEvent.click(getByText('download.svg'));
expect(saveAs).toBeCalledWith(expect.any(Blob), 'swaps.csv');
});
+
+ it('should sort the history list', () => {
+ const { getByText, store } = render();
+ expect(store.settingsStore.historySort.field).toBe('lastUpdateTime');
+ expect(store.settingsStore.historySort.descending).toBe(true);
+
+ fireEvent.click(getByText('Status'));
+ expect(store.settingsStore.historySort.field).toBe('stateLabel');
+
+ fireEvent.click(getByText('Type'));
+ expect(store.settingsStore.historySort.field).toBe('typeName');
+
+ fireEvent.click(getByText('Amount'));
+ expect(store.settingsStore.historySort.field).toBe('amount');
+
+ fireEvent.click(getByText('Created'));
+ expect(store.settingsStore.historySort.field).toBe('initiationTime');
+
+ fireEvent.click(getByText('Updated'));
+ expect(store.settingsStore.historySort.field).toBe('lastUpdateTime');
+ expect(store.settingsStore.historySort.descending).toBe(false);
+
+ fireEvent.click(getByText('Updated'));
+ expect(store.settingsStore.historySort.field).toBe('lastUpdateTime');
+ expect(store.settingsStore.historySort.descending).toBe(true);
+ });
});
diff --git a/app/src/__tests__/components/loop/LoopPage.spec.tsx b/app/src/__tests__/components/loop/LoopPage.spec.tsx
index c928f5da6..40a3c12b4 100644
--- a/app/src/__tests__/components/loop/LoopPage.spec.tsx
+++ b/app/src/__tests__/components/loop/LoopPage.spec.tsx
@@ -173,5 +173,40 @@ describe('LoopPage component', () => {
expect(getByText('Review Loop amount and fee')).toBeInTheDocument();
expect(store.buildSwapStore.processingTimeout).toBeUndefined();
});
+
+ it('should sort the channel list', () => {
+ const { getByText, store } = render();
+ expect(getByText('Capacity')).toBeInTheDocument();
+ expect(store.settingsStore.channelSort.field).toBeUndefined();
+ expect(store.settingsStore.channelSort.descending).toBe(true);
+
+ fireEvent.click(getByText('Can Receive'));
+ expect(store.settingsStore.channelSort.field).toBe('remoteBalance');
+
+ fireEvent.click(getByText('Can Send'));
+ expect(store.settingsStore.channelSort.field).toBe('localBalance');
+
+ fireEvent.click(getByText('In Fee %'));
+ expect(store.settingsStore.channelSort.field).toBe('remoteFeeRate');
+
+ fireEvent.click(getByText('Uptime %'));
+ expect(store.settingsStore.channelSort.field).toBe('uptimePercent');
+
+ fireEvent.click(getByText('Peer/Alias'));
+ expect(store.settingsStore.channelSort.field).toBe('aliasLabel');
+
+ fireEvent.click(getByText('Capacity'));
+ expect(store.settingsStore.channelSort.field).toBe('capacity');
+ expect(store.settingsStore.channelSort.descending).toBe(false);
+
+ fireEvent.click(getByText('Capacity'));
+ expect(store.settingsStore.channelSort.field).toBe('capacity');
+ expect(store.settingsStore.channelSort.descending).toBe(true);
+
+ expect(getByText('slash.svg')).toBeInTheDocument();
+ fireEvent.click(getByText('slash.svg'));
+ expect(store.settingsStore.channelSort.field).toBeUndefined();
+ expect(store.settingsStore.channelSort.descending).toBe(true);
+ });
});
});
diff --git a/app/src/assets/icons/arrow-down.svg b/app/src/assets/icons/arrow-down.svg
new file mode 100644
index 000000000..42d174218
--- /dev/null
+++ b/app/src/assets/icons/arrow-down.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/src/assets/icons/arrow-up.svg b/app/src/assets/icons/arrow-up.svg
new file mode 100644
index 000000000..37d9b5507
--- /dev/null
+++ b/app/src/assets/icons/arrow-up.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/src/assets/icons/slash.svg b/app/src/assets/icons/slash.svg
new file mode 100644
index 000000000..13cef0c4a
--- /dev/null
+++ b/app/src/assets/icons/slash.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/src/components/base/icons.tsx b/app/src/components/base/icons.tsx
index 6208c738a..2b8f18051 100644
--- a/app/src/components/base/icons.tsx
+++ b/app/src/components/base/icons.tsx
@@ -1,5 +1,7 @@
+import { ReactComponent as ArrowDownIcon } from 'assets/icons/arrow-down.svg';
import { ReactComponent as ArrowLeftIcon } from 'assets/icons/arrow-left.svg';
import { ReactComponent as ArrowRightIcon } from 'assets/icons/arrow-right.svg';
+import { ReactComponent as ArrowUpIcon } from 'assets/icons/arrow-up.svg';
import { ReactComponent as BitcoinIcon } from 'assets/icons/bitcoin.svg';
import { ReactComponent as BoltIcon } from 'assets/icons/bolt.svg';
import { ReactComponent as CheckIcon } from 'assets/icons/check.svg';
@@ -16,10 +18,11 @@ import { ReactComponent as MaximizeIcon } from 'assets/icons/maximize.svg';
import { ReactComponent as MenuIcon } from 'assets/icons/menu.svg';
import { ReactComponent as MinimizeIcon } from 'assets/icons/minimize.svg';
import { ReactComponent as RefreshIcon } from 'assets/icons/refresh-cw.svg';
+import { ReactComponent as CancelIcon } from 'assets/icons/slash.svg';
import { styled } from 'components/theme';
interface IconProps {
- size?: 'small' | 'medium' | 'large';
+ size?: 'x-small' | 'small' | 'medium' | 'large';
onClick?: () => void;
}
@@ -39,6 +42,13 @@ const Icon = styled.span`
}
`}
+ ${props =>
+ props.size === 'x-small' &&
+ `
+ width: 16px;
+ height: 16px;
+ `}
+
${props =>
props.size === 'small' &&
`
@@ -61,10 +71,13 @@ const Icon = styled.span`
`}
`;
+export const ArrowLeft = Icon.withComponent(ArrowLeftIcon);
export const ArrowRight = Icon.withComponent(ArrowRightIcon);
+export const ArrowUp = Icon.withComponent(ArrowUpIcon);
+export const ArrowDown = Icon.withComponent(ArrowDownIcon);
+export const Cancel = Icon.withComponent(CancelIcon);
export const Clock = Icon.withComponent(ClockIcon);
export const Download = Icon.withComponent(DownloadIcon);
-export const ArrowLeft = Icon.withComponent(ArrowLeftIcon);
export const Bolt = Icon.withComponent(BoltIcon);
export const Bitcoin = Icon.withComponent(BitcoinIcon);
export const Check = Icon.withComponent(CheckIcon);
diff --git a/app/src/components/common/SortableHeader.tsx b/app/src/components/common/SortableHeader.tsx
new file mode 100644
index 000000000..20694f255
--- /dev/null
+++ b/app/src/components/common/SortableHeader.tsx
@@ -0,0 +1,62 @@
+import React, { useCallback } from 'react';
+import { SortParams } from 'types/state';
+import { ArrowDown, ArrowUp, HeaderFour } from 'components/base';
+import { styled } from 'components/theme';
+
+const Styled = {
+ HeaderFour: styled(HeaderFour)<{ selected: boolean }>`
+ ${props =>
+ props.selected &&
+ `
+ color: ${props.theme.colors.white};
+ `}
+
+ &:hover {
+ cursor: pointer;
+ color: ${props => props.theme.colors.white};
+ }
+ `,
+ Icon: styled.span`
+ display: inline-block;
+ margin-left: 6px;
+
+ svg {
+ padding: 0;
+ }
+ `,
+};
+
+interface Props {
+ field: keyof T;
+ sort: SortParams;
+ onSort: (field: SortParams['field'], descending: boolean) => void;
+}
+
+const SortableHeader = ({
+ field,
+ sort,
+ onSort,
+ children,
+}: React.PropsWithChildren>) => {
+ const selected = field === sort.field;
+ const SortIcon = sort.descending ? ArrowDown : ArrowUp;
+
+ const handleSortClick = useCallback(() => {
+ const descending = selected ? !sort.descending : false;
+ onSort(field, descending);
+ }, [selected, sort.descending, field, onSort]);
+
+ const { HeaderFour, Icon } = Styled;
+ return (
+
+ {children}
+ {selected && (
+
+
+
+ )}
+
+ );
+};
+
+export default SortableHeader;
diff --git a/app/src/components/history/HistoryRow.tsx b/app/src/components/history/HistoryRow.tsx
index ddd864d21..369f0b054 100644
--- a/app/src/components/history/HistoryRow.tsx
+++ b/app/src/components/history/HistoryRow.tsx
@@ -1,8 +1,10 @@
import React, { CSSProperties } from 'react';
import { observer } from 'mobx-react-lite';
import { usePrefixedTranslation } from 'hooks';
+import { useStore } from 'store';
import { Swap } from 'store/models';
-import { Column, HeaderFour, Row } from 'components/base';
+import { Column, Row } from 'components/base';
+import SortableHeader from 'components/common/SortableHeader';
import Unit from 'components/common/Unit';
import SwapDot from 'components/loop/SwapDot';
import { styled } from 'components/theme';
@@ -43,31 +45,65 @@ const Styled = {
`,
};
-export const HistoryRowHeader: React.FC = () => {
+const RowHeader: React.FC = () => {
const { l } = usePrefixedTranslation('cmps.history.HistoryRowHeader');
+ const { settingsStore } = useStore();
+
const { HeaderRow, ActionColumn, HeaderColumn } = Styled;
return (
- {l('status')}
+
+ field="stateLabel"
+ sort={settingsStore.historySort}
+ onSort={settingsStore.setHistorySort}
+ >
+ {l('status')}
+
- {l('type')}
+
+ field="typeName"
+ sort={settingsStore.historySort}
+ onSort={settingsStore.setHistorySort}
+ >
+ {l('type')}
+
- {l('amount')}
+
+ field="amount"
+ sort={settingsStore.historySort}
+ onSort={settingsStore.setHistorySort}
+ >
+ {l('amount')}
+
- {l('created')}
+
+ field="initiationTime"
+ sort={settingsStore.historySort}
+ onSort={settingsStore.setHistorySort}
+ >
+ {l('created')}
+
- {l('updated')}
+
+ field="lastUpdateTime"
+ sort={settingsStore.historySort}
+ onSort={settingsStore.setHistorySort}
+ >
+ {l('updated')}
+
);
};
+export const HistoryRowHeader = observer(RowHeader);
+
interface Props {
swap: Swap;
style?: CSSProperties;
diff --git a/app/src/components/loop/ChannelRow.tsx b/app/src/components/loop/ChannelRow.tsx
index 96e6d086e..afd531b9f 100644
--- a/app/src/components/loop/ChannelRow.tsx
+++ b/app/src/components/loop/ChannelRow.tsx
@@ -3,8 +3,9 @@ import { observer } from 'mobx-react-lite';
import { usePrefixedTranslation } from 'hooks';
import { useStore } from 'store';
import { Channel } from 'store/models';
-import { Column, HeaderFour, Row } from 'components/base';
+import { Cancel, Column, HeaderFour, Row } from 'components/base';
import Checkbox from 'components/common/Checkbox';
+import SortableHeader from 'components/common/SortableHeader';
import Tip from 'components/common/Tip';
import Unit from 'components/common/Unit';
import { styled } from 'components/theme';
@@ -57,6 +58,10 @@ const Styled = {
max-width: 20%;
}
`,
+ ClearSortIcon: styled(Cancel)`
+ padding: 2px;
+ margin-left: 4px;
+ `,
StatusIcon: styled.span`
color: ${props => props.theme.colors.pink};
`,
@@ -81,52 +86,100 @@ const ChannelAliasTip: React.FC<{ channel: Channel }> = ({ channel }) => {
);
};
-export const ChannelRowHeader: React.FC = () => {
+const RowHeader: React.FC = () => {
const { l } = usePrefixedTranslation('cmps.loop.ChannelRowHeader');
- const { Column, ActionColumn, WideColumn } = Styled;
+ const { settingsStore } = useStore();
+
+ const { Column, ActionColumn, WideColumn, ClearSortIcon } = Styled;
return (
-
+
+ {settingsStore.channelSort.field && (
+
+
+
+
+
+ )}
+
- {l('canReceive')}
+
+ field="remoteBalance"
+ sort={settingsStore.channelSort}
+ onSort={settingsStore.setChannelSort}
+ >
+ {l('canReceive')}
+
-
+
- {l('canSend')}
+
+ field="localBalance"
+ sort={settingsStore.channelSort}
+ onSort={settingsStore.setChannelSort}
+ >
+ {l('canSend')}
+
-
-
- {l('feeRate')}
-
+
+
+ field="remoteFeeRate"
+ sort={settingsStore.channelSort}
+ onSort={settingsStore.setChannelSort}
+ >
+
+ {l('feeRate')}
+
+
-
- {l('upTime')}
+
+
+ field="uptimePercent"
+ sort={settingsStore.channelSort}
+ onSort={settingsStore.setChannelSort}
+ >
+ {l('upTime')}
+
- {l('peer')}
+
+ field="aliasLabel"
+ sort={settingsStore.channelSort}
+ onSort={settingsStore.setChannelSort}
+ >
+ {l('peer')}
+
- {l('capacity')}
+
+ field="capacity"
+ sort={settingsStore.channelSort}
+ onSort={settingsStore.setChannelSort}
+ >
+ {l('capacity')}
+
);
};
+export const ChannelRowHeader = observer(RowHeader);
+
interface Props {
channel: Channel;
style?: CSSProperties;
}
const ChannelRow: React.FC = ({ channel, style }) => {
- const store = useStore();
+ const { buildSwapStore } = useStore();
- const editable = store.buildSwapStore.listEditable;
- const disabled = store.buildSwapStore.showWizard;
- const checked = store.buildSwapStore.selectedChanIds.includes(channel.chanId);
+ const editable = buildSwapStore.listEditable;
+ const disabled = buildSwapStore.showWizard;
+ const checked = buildSwapStore.selectedChanIds.includes(channel.chanId);
const dimmed = editable && disabled && !checked;
const handleRowChecked = () => {
- store.buildSwapStore.toggleSelectedChannel(channel.chanId);
+ buildSwapStore.toggleSelectedChannel(channel.chanId);
};
const { Row, Column, ActionColumn, WideColumn, StatusIcon, Check, Balance } = Styled;
@@ -155,12 +208,12 @@ const ChannelRow: React.FC = ({ channel, style }) => {
-
+
{channel.remoteFeePct}
- {channel.uptimePercent}
+ {channel.uptimePercent}
}
diff --git a/app/src/i18n/locales/en-US.json b/app/src/i18n/locales/en-US.json
index 47f2f18a4..c06584cd5 100644
--- a/app/src/i18n/locales/en-US.json
+++ b/app/src/i18n/locales/en-US.json
@@ -23,6 +23,7 @@
"cmps.loop.ChannelIcon.processing.in": "Loop In currently in progress",
"cmps.loop.ChannelIcon.processing.out": "Loop Out currently in progress",
"cmps.loop.ChannelIcon.processing.both": "Loop In and Loop Out currently in progress",
+ "cmps.loop.ChannelRowHeader.resetSort": "Reset Sorting",
"cmps.loop.ChannelRowHeader.canReceive": "Can Receive",
"cmps.loop.ChannelRowHeader.canSend": "Can Send",
"cmps.loop.ChannelRowHeader.feeRate": "In Fee %",
diff --git a/app/src/store/models/channel.ts b/app/src/store/models/channel.ts
index 87353576a..0c1e0c122 100644
--- a/app/src/store/models/channel.ts
+++ b/app/src/store/models/channel.ts
@@ -1,6 +1,7 @@
import { action, computed, observable } from 'mobx';
import * as LND from 'types/generated/lnd_pb';
import * as LOOP from 'types/generated/loop_pb';
+import { SortParams } from 'types/state';
import Big from 'big.js';
import { getBalanceStatus } from 'util/balances';
import { percentage } from 'util/bigmath';
@@ -84,7 +85,7 @@ export default class Channel {
/**
* The order to sort this channel based on the current mode
*/
- @computed get sortOrder() {
+ @computed get balanceModeOrder() {
const mode = this._store.settingsStore.balanceMode;
switch (mode) {
case BalanceMode.routing:
@@ -154,6 +155,44 @@ export default class Channel {
this.lifetime = Big(lndChannel.lifetime);
}
+ /**
+ * Compares a specific field of two channels for sorting
+ * @param a the first channel to compare
+ * @param b the second channel to compare
+ * @param sortBy the field and direction to sort the two channels by
+ * @returns a positive number if `a`'s field is greater than `b`'s,
+ * a negative number if `a`'s field is less than `b`'s, or zero otherwise
+ */
+ static compare(a: Channel, b: Channel, field: SortParams['field']): number {
+ let order = 0;
+ switch (field) {
+ case 'remoteBalance':
+ order = +a.remoteBalance.sub(b.remoteBalance);
+ break;
+ case 'localBalance':
+ order = +a.localBalance.sub(b.localBalance);
+ break;
+ case 'remoteFeeRate':
+ order = a.remoteFeeRate - b.remoteFeeRate;
+ break;
+ case 'uptimePercent':
+ order = a.uptimePercent - b.uptimePercent;
+ break;
+ case 'aliasLabel':
+ order = a.aliasLabel.toLowerCase() > b.aliasLabel.toLowerCase() ? 1 : -1;
+ break;
+ case 'capacity':
+ order = +a.capacity.sub(b.capacity);
+ break;
+ case 'balanceModeOrder':
+ default:
+ order = a.balanceModeOrder - b.balanceModeOrder;
+ break;
+ }
+
+ return order;
+ }
+
/**
* Specifies which properties of this class should be exported to CSV
* @param key must match the name of a property on this class
diff --git a/app/src/store/models/swap.ts b/app/src/store/models/swap.ts
index d947a7866..04bd9a108 100644
--- a/app/src/store/models/swap.ts
+++ b/app/src/store/models/swap.ts
@@ -1,6 +1,7 @@
import { action, computed, observable } from 'mobx';
import { now } from 'mobx-utils';
import * as LOOP from 'types/generated/loop_pb';
+import { SortParams } from 'types/state';
import Big from 'big.js';
import formatDate from 'date-fns/format';
import { CsvColumns } from 'util/csv';
@@ -112,6 +113,30 @@ export default class Swap {
this.state = loopSwap.state;
}
+ /**
+ * Compares a specific field of two swaps for sorting
+ * @param a the first swap to compare
+ * @param b the second swap to compare
+ * @param sortBy the field and direction to sort the two swaps by
+ * @returns a positive number if `a`'s field is greater than `b`'s,
+ * a negative number if `a`'s field is less than `b`'s, or zero otherwise
+ */
+ static compare(a: Swap, b: Swap, field: SortParams['field']): number {
+ switch (field) {
+ case 'stateLabel':
+ return a.stateLabel.toLowerCase() > b.stateLabel.toLowerCase() ? 1 : -1;
+ case 'typeName':
+ return a.typeName.toLowerCase() > b.typeName.toLowerCase() ? 1 : -1;
+ case 'amount':
+ return +a.amount.sub(b.amount);
+ case 'initiationTime':
+ return a.initiationTime - b.initiationTime;
+ case 'lastUpdateTime':
+ default:
+ return a.lastUpdateTime - b.lastUpdateTime;
+ }
+ }
+
/**
* Specifies which properties of this class should be exported to CSV
* @param key must match the name of a property on this class
diff --git a/app/src/store/stores/channelStore.ts b/app/src/store/stores/channelStore.ts
index 6c2bb22d3..aef502aac 100644
--- a/app/src/store/stores/channelStore.ts
+++ b/app/src/store/stores/channelStore.ts
@@ -34,9 +34,11 @@ export default class ChannelStore {
* an array of channels sorted by balance percent descending
*/
@computed get sortedChannels() {
- return values(this.channels)
+ const { field, descending } = this._store.settingsStore.channelSort;
+ const channels = values(this.channels)
.slice()
- .sort((a, b) => b.sortOrder - a.sortOrder);
+ .sort((a, b) => Channel.compare(a, b, field));
+ return descending ? channels.reverse() : channels;
}
/**
diff --git a/app/src/store/stores/settingsStore.ts b/app/src/store/stores/settingsStore.ts
index 24ac8fa7a..9dc4b33f1 100644
--- a/app/src/store/stores/settingsStore.ts
+++ b/app/src/store/stores/settingsStore.ts
@@ -1,12 +1,16 @@
import { action, autorun, observable, toJS } from 'mobx';
+import { SortParams } from 'types/state';
import { BalanceMode, Unit } from 'util/constants';
import { Store } from 'store';
+import { Channel, Swap } from 'store/models';
export interface PersistentSettings {
sidebarVisible: boolean;
unit: Unit;
balanceMode: BalanceMode;
tourAutoShown: boolean;
+ channelSort: SortParams;
+ historySort: SortParams;
}
export default class SettingsStore {
@@ -27,6 +31,18 @@ export default class SettingsStore {
/** specifies the mode to use to determine channel balance status */
@observable balanceMode: BalanceMode = BalanceMode.receive;
+ /** specifies the sorting field and direction for the channel list */
+ @observable channelSort: SortParams = {
+ field: undefined,
+ descending: true,
+ };
+
+ /** specifies the sorting field and direction for the channel list */
+ @observable historySort: SortParams = {
+ field: 'lastUpdateTime',
+ descending: true,
+ };
+
/** the chosen language */
@observable lang = 'en-US';
@@ -66,6 +82,40 @@ export default class SettingsStore {
this.balanceMode = mode;
}
+ /**
+ * Sets the sort field and direction that the channel list should use
+ * @param field the channel field to sort by
+ * @param descending true of the order should be descending, otherwise false
+ */
+ @action.bound
+ setChannelSort(field: SortParams['field'], descending: boolean) {
+ this.channelSort = { field, descending };
+ this._store.log.info('updated channel list sort order', toJS(this.channelSort));
+ }
+
+ /**
+ * Resets the channel list sort order
+ */
+ @action.bound
+ resetChannelSort() {
+ this.channelSort = {
+ field: undefined,
+ descending: true,
+ };
+ this._store.log.info('reset channel list sort order', toJS(this.channelSort));
+ }
+
+ /**
+ * Sets the sort field and direction that the swap history list should use
+ * @param field the swap field to sort by
+ * @param descending true of the order should be descending, otherwise false
+ */
+ @action.bound
+ setHistorySort(field: SortParams['field'], descending: boolean) {
+ this.historySort = { field, descending };
+ this._store.log.info('updated history list sort order', toJS(this.historySort));
+ }
+
/**
* initialized the settings and auto-save when a setting is changed
*/
@@ -79,6 +129,8 @@ export default class SettingsStore {
unit: this.unit,
balanceMode: this.balanceMode,
tourAutoShown: this.tourAutoShown,
+ channelSort: toJS(this.channelSort),
+ historySort: toJS(this.historySort),
};
this._store.storage.set('settings', settings);
this._store.log.info('saved settings to localStorage', settings);
@@ -99,6 +151,8 @@ export default class SettingsStore {
this.unit = settings.unit;
this.balanceMode = settings.balanceMode;
this.tourAutoShown = settings.tourAutoShown;
+ if (settings.channelSort) this.channelSort = settings.channelSort;
+ if (settings.historySort) this.historySort = settings.historySort;
this._store.log.info('loaded settings', settings);
}
diff --git a/app/src/store/stores/swapStore.ts b/app/src/store/stores/swapStore.ts
index e83a43dba..754a56fa5 100644
--- a/app/src/store/stores/swapStore.ts
+++ b/app/src/store/stores/swapStore.ts
@@ -37,9 +37,12 @@ export default class SwapStore {
/** swaps sorted by created date descending */
@computed get sortedSwaps() {
- return values(this.swaps)
+ const { field, descending } = this._store.settingsStore.historySort;
+ const swaps = values(this.swaps)
.slice()
- .sort((a, b) => b.initiationTime - a.initiationTime);
+ .sort((a, b) => Swap.compare(a, b, field));
+
+ return descending ? swaps.reverse() : swaps;
}
/** the last two swaps */
diff --git a/app/src/types/state.ts b/app/src/types/state.ts
index 5df5730a3..6b377a81c 100644
--- a/app/src/types/state.ts
+++ b/app/src/types/state.ts
@@ -38,3 +38,8 @@ export interface Alert {
/** the number of milliseconds before the toast closes automatically */
ms?: number;
}
+
+export interface SortParams {
+ field?: keyof T;
+ descending: boolean;
+}