Skip to content
Draft
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
82 changes: 25 additions & 57 deletions src-gui/src/store/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import { sortBy, sum, throttle } from "lodash";
import { sortBy, throttle } from "lodash";
import {
BobStateName,
GetSwapInfoResponseExt,
isBitcoinSyncProgress,
isPendingBackgroundProcess,
isPendingLockBitcoinApprovalEvent,
isPendingSeedSelectionApprovalEvent,
PendingApprovalRequest,
PendingLockBitcoinApprovalRequest,
PendingSelectMakerApprovalRequest,
isPendingSelectMakerApprovalEvent,
haveFundsBeenLocked,
PendingSeedSelectionApprovalRequest,
PendingSendMoneroApprovalRequest,
isPendingSendMoneroApprovalEvent,
PendingPasswordApprovalRequest,
isPendingPasswordApprovalEvent,
isContextFullyInitialized,
} from "models/tauriModelExt";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import {
shallowEqual,
TypedUseSelectorHook,
useDispatch,
useSelector,
} from "react-redux";
import type { AppDispatch, RootState } from "renderer/store/storeRenderer";
import { parseDateString } from "utils/parseUtils";
import { useEffect, useMemo, useState } from "react";
Expand All @@ -35,7 +33,15 @@ import { Alert } from "models/apiModel";
import { fnv1a } from "utils/hash";
import {
selectAllSwapInfos,
selectBitcoinSyncProgress,
selectConservativeBitcoinSyncProgress,
selectPendingBackgroundProcesses,
selectPendingApprovals,
selectPendingLockBitcoinApprovals,
selectPendingPasswordApprovals,
selectPendingSeedSelectionApprovals,
selectPendingSelectMakerApprovals,
selectPendingSendMoneroApprovals,
selectSwapInfoWithTimelock,
selectSwapInfosRaw,
} from "./selectors";
Expand Down Expand Up @@ -191,22 +197,19 @@ export function useAreSwapInfosLoaded(): boolean {
}

export function useSettings<T>(selector: (settings: SettingsState) => T): T {
const settings = useAppSelector((state) => state.settings);
return selector(settings);
return useAppSelector((state) => selector(state.settings), shallowEqual);
}

export function useNodes<T>(selector: (nodes: NodesSlice) => T): T {
const nodes = useAppSelector((state) => state.nodes);
return selector(nodes);
return useAppSelector((state) => selector(state.nodes), shallowEqual);
}

export function usePendingApprovals(): PendingApprovalRequest[] {
return useAppSelector(selectPendingApprovals) as PendingApprovalRequest[];
}

export function usePendingLockBitcoinApproval(): PendingLockBitcoinApprovalRequest[] {
const approvals = usePendingApprovals();
return approvals.filter((c) => isPendingLockBitcoinApprovalEvent(c));
return useAppSelector(selectPendingLockBitcoinApprovals);
}

export function useMoneroMainAddress(): string | null {
Expand All @@ -218,23 +221,19 @@ export function useMoneroSubaddresses(): SubaddressSummary[] {
}

export function usePendingSendMoneroApproval(): PendingSendMoneroApprovalRequest[] {
const approvals = usePendingApprovals();
return approvals.filter((c) => isPendingSendMoneroApprovalEvent(c));
return useAppSelector(selectPendingSendMoneroApprovals);
}

export function usePendingSelectMakerApproval(): PendingSelectMakerApprovalRequest[] {
const approvals = usePendingApprovals();
return approvals.filter((c) => isPendingSelectMakerApprovalEvent(c));
return useAppSelector(selectPendingSelectMakerApprovals);
}

export function usePendingSeedSelectionApproval(): PendingSeedSelectionApprovalRequest[] {
const approvals = usePendingApprovals();
return approvals.filter((c) => isPendingSeedSelectionApprovalEvent(c));
return useAppSelector(selectPendingSeedSelectionApprovals);
}

export function usePendingPasswordApproval(): PendingPasswordApprovalRequest[] {
const approvals = usePendingApprovals();
return approvals.filter((c) => isPendingPasswordApprovalEvent(c));
return useAppSelector(selectPendingPasswordApprovals);
}

/// Returns all the pending background processes
Expand All @@ -243,22 +242,11 @@ export function usePendingBackgroundProcesses(): [
string,
TauriBackgroundProgress,
][] {
const background = useAppSelector((state) => state.rpc.state.background);
return Object.entries(background).filter(([_, c]) =>
isPendingBackgroundProcess(c),
);
return useAppSelector(selectPendingBackgroundProcesses);
}

export function useBitcoinSyncProgress(): TauriBitcoinSyncProgress[] {
const pendingProcesses = usePendingBackgroundProcesses();
const syncingProcesses = pendingProcesses
.map(([_, c]) => c)
.filter(isBitcoinSyncProgress);
return syncingProcesses
.map((c) => c.progress.content)
.filter(
(content): content is TauriBitcoinSyncProgress => content !== undefined,
);
return useAppSelector(selectBitcoinSyncProgress);
}

export function useIsSyncingBitcoin(): boolean {
Expand All @@ -271,27 +259,7 @@ export function useIsSyncingBitcoin(): boolean {
/// If at least one sync is known, it returns {type: "Known", content: {consumed, total}}
/// where consumed and total are the sum of all the consumed and total values of the syncs
export function useConservativeBitcoinSyncProgress(): TauriBitcoinSyncProgress | null {
const syncingProcesses = useBitcoinSyncProgress();
const progressValues = syncingProcesses.map((c) => c.content?.consumed ?? 0);
const totalValues = syncingProcesses.map((c) => c.content?.total ?? 0);

const progress = sum(progressValues);
const total = sum(totalValues);

// If either the progress or the total is 0, we consider the sync to be unknown
if (progress === 0 || total === 0) {
return {
type: "Unknown",
};
}

return {
type: "Known",
content: {
consumed: progress,
total: total,
},
};
return useAppSelector(selectConservativeBitcoinSyncProgress);
}

/**
Expand Down
86 changes: 85 additions & 1 deletion src-gui/src/store/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { createSelector } from "@reduxjs/toolkit";
import { RootState } from "renderer/store/storeRenderer";
import { GetSwapInfoResponseExt } from "models/tauriModelExt";
import {
GetSwapInfoResponseExt,
isBitcoinSyncProgress,
isPendingBackgroundProcess,
isPendingLockBitcoinApprovalEvent,
isPendingPasswordApprovalEvent,
isPendingSeedSelectionApprovalEvent,
isPendingSelectMakerApprovalEvent,
isPendingSendMoneroApprovalEvent,
} from "models/tauriModelExt";
import {
ConnectionStatus,
ExpiredTimelocks,
QuoteStatus,
TauriBitcoinSyncProgress,
} from "models/tauriModel";

const selectRpcState = (state: RootState) => state.rpc.state;
Expand Down Expand Up @@ -60,6 +70,80 @@ export const selectPendingApprovals = createSelector(
),
);

export const selectPendingLockBitcoinApprovals = createSelector(
[selectPendingApprovals],
(approvals) => approvals.filter(isPendingLockBitcoinApprovalEvent),
);

export const selectPendingSendMoneroApprovals = createSelector(
[selectPendingApprovals],
(approvals) => approvals.filter(isPendingSendMoneroApprovalEvent),
);

export const selectPendingSelectMakerApprovals = createSelector(
[selectPendingApprovals],
(approvals) => approvals.filter(isPendingSelectMakerApprovalEvent),
);

export const selectPendingSeedSelectionApprovals = createSelector(
[selectPendingApprovals],
(approvals) => approvals.filter(isPendingSeedSelectionApprovalEvent),
);

export const selectPendingPasswordApprovals = createSelector(
[selectPendingApprovals],
(approvals) => approvals.filter(isPendingPasswordApprovalEvent),
);

export const selectPendingBackgroundProcesses = createSelector(
[selectRpcState],
(rpcState) =>
Object.entries(rpcState.background).filter(([, progress]) =>
isPendingBackgroundProcess(progress),
),
);

export const selectBitcoinSyncProgress = createSelector(
[selectPendingBackgroundProcesses],
(pendingProcesses): TauriBitcoinSyncProgress[] =>
pendingProcesses
.map(([, progress]) => progress)
.filter(isBitcoinSyncProgress)
.map((progress) => progress.progress.content)
.filter(
(content): content is TauriBitcoinSyncProgress =>
content !== undefined,
),
);

export const selectConservativeBitcoinSyncProgress = createSelector(
[selectBitcoinSyncProgress],
(syncingProcesses): TauriBitcoinSyncProgress | null => {
const progress = syncingProcesses.reduce(
(total, current) => total + (current.content?.consumed ?? 0),
0,
);
const total = syncingProcesses.reduce(
(sum, current) => sum + (current.content?.total ?? 0),
0,
);

if (progress === 0 || total === 0) {
return {
type: "Unknown",
};
}

return {
type: "Known",
content: {
consumed: progress,
total,
},
};
},
);

// TODO: This should be split into multiple selectors/hooks to avoid excessive re-rendering
export const selectPeers = createSelector([selectP2pState], (p2p) => {
const peerIds = new Set([
Expand Down