diff --git a/src/pages/names/NamesTable.tsx b/src/pages/names/NamesTable.tsx index 9e22047..6a5c52c 100644 --- a/src/pages/names/NamesTable.tsx +++ b/src/pages/names/NamesTable.tsx @@ -14,6 +14,7 @@ import { useWallets } from "@wallets"; import { NameActions } from "./mgmt/NameActions"; +import { useNameTableLock } from "./tableLock"; import { KristNameLink } from "@comp/names/KristNameLink"; import { ContextualAddress } from "@comp/addresses/ContextualAddress"; @@ -56,8 +57,16 @@ // Used to change the actions depending on whether or not we own the name const { walletAddressMap } = useWallets(); + // Used to pause the table lookups when performing a bulk name edit + const locked = useNameTableLock(); + // Fetch the names from the API, mapping the table options useEffect(() => { + if (locked) { + debug("skipping name lookup; table locked"); + return; + } + debug("looking up names for %s", addresses ? addresses.join(",") : "network"); setLoading(true); @@ -65,7 +74,7 @@ .then(setRes) .catch(setError) .finally(() => setLoading(false)); - }, [refreshingID, addresses, setError, options]); + }, [locked, refreshingID, addresses, setError, options]); debug("results? %b res.names.length: %d res.count: %d res.total: %d", !!res, res?.names?.length, res?.count, res?.total); diff --git a/src/pages/names/mgmt/NameEditModal.tsx b/src/pages/names/mgmt/NameEditModal.tsx index aca997e..36bb76c 100644 --- a/src/pages/names/mgmt/NameEditModal.tsx +++ b/src/pages/names/mgmt/NameEditModal.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under GPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { useState, Dispatch, SetStateAction } from "react"; +import { useState, useRef, Dispatch, SetStateAction } from "react"; import { Modal, notification } from "antd"; import { useTFns } from "@utils/i18n"; @@ -18,6 +18,7 @@ import { NameOption, fetchNames, buildLUT } from "./lookupNames"; import { handleError } from "./handleErrors"; +import { lockNameTable, NameTableLock } from "../tableLock"; import { useNameEditForm } from "./NameEditForm"; import { useEditProgress } from "./EditProgress"; @@ -49,6 +50,8 @@ const { t, tKey, tStr, tErr } = tFns; const [submitting, setSubmitting] = useState(false); + // Pause updates of the name table if it's visible when submitting + const tableLock = useRef(); // Confirmation modal used for when transferring multiple names. // This is created here to provide a translation context for the modal. @@ -109,6 +112,10 @@ const finalAddresses = decryptResults as ValidDecryptedAddresses; const finalNames = names.map(n => ({ name: n.key, owner: n.owner })); + // Lock the name table if present + tableLock?.current?.release(); + tableLock.current = lockNameTable(); + if (mode === "transfer") { // Transfer the names await transferNames(finalAddresses, finalNames, recipient!, onProgress); @@ -129,6 +136,7 @@ }); setSubmitting(false); + tableLock?.current?.release(); closeModal(); } @@ -178,7 +186,10 @@ initProgress(count); handleSubmit(filteredNames, recipient, aRecord) .catch(onError) - .finally(() => setSubmitting(false)); + .finally(() => { + setSubmitting(false); + tableLock?.current?.release(); + }); }; if (mode === "transfer" && count > 1) { @@ -200,6 +211,7 @@ setVisible(false); resetFields(); resetProgress(); + tableLock?.current?.release(); } return <> diff --git a/src/pages/names/tableLock.ts b/src/pages/names/tableLock.ts new file mode 100644 index 0000000..38a6ea9 --- /dev/null +++ b/src/pages/names/tableLock.ts @@ -0,0 +1,44 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { useSelector } from "react-redux"; +import { RootState } from "@store"; +import { store } from "@app"; +import { + incrementNameTableLock, decrementNameTableLock +} from "@actions/MiscActions"; + +import Debug from "debug"; +const debug = Debug("kristweb:name-table-lock"); + +export function useNameTableLock(): boolean { + const nameTableLock = useSelector((s: RootState) => s.misc.nameTableLock); + return nameTableLock > 0; +} + +export interface NameTableLock { + release: () => void; +} + +export function lockNameTable(timeoutMs = 20000): NameTableLock { + let _timeout: ReturnType | undefined = setTimeout(() => { + debug("timeout reached, releasing name table lock"); + release(); + }, timeoutMs); + + function release() { + if (_timeout !== undefined) { + debug("name table lock being released"); + store.dispatch(decrementNameTableLock()); + + debug("clearing name table lock timeout %o", _timeout); + clearTimeout(_timeout); + _timeout = undefined; + } + } + + debug("name table being locked"); + store.dispatch(incrementNameTableLock()); + + return { release }; +} diff --git a/src/store/actions/MiscActions.ts b/src/store/actions/MiscActions.ts new file mode 100644 index 0000000..fa00bb4 --- /dev/null +++ b/src/store/actions/MiscActions.ts @@ -0,0 +1,8 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { createAction } from "typesafe-actions"; +import * as constants from "../constants"; + +export const incrementNameTableLock = createAction(constants.INCR_NAME_TABLE_LOCK)(); +export const decrementNameTableLock = createAction(constants.DECR_NAME_TABLE_LOCK)(); diff --git a/src/store/actions/index.ts b/src/store/actions/index.ts index 64b5807..2ac00af 100644 --- a/src/store/actions/index.ts +++ b/src/store/actions/index.ts @@ -6,12 +6,14 @@ import * as settingsActions from "./SettingsActions"; import * as websocketActions from "./WebsocketActions"; import * as nodeActions from "./NodeActions"; +import * as miscActions from "./MiscActions"; const RootAction = { masterPassword: masterPasswordActions, wallets: walletsActions, settings: settingsActions, websocket: websocketActions, - node: nodeActions + node: nodeActions, + misc: miscActions }; export default RootAction; diff --git a/src/store/constants.ts b/src/store/constants.ts index 03dc9ae..8a02720 100644 --- a/src/store/constants.ts +++ b/src/store/constants.ts @@ -49,3 +49,8 @@ export const CURRENCY = "CURRENCY"; export const CONSTANTS = "CONSTANTS"; export const MOTD = "MOTD"; + +// Miscellaneous +// --- +export const INCR_NAME_TABLE_LOCK = "INCR_NAME_TABLE_LOCK"; +export const DECR_NAME_TABLE_LOCK = "DECR_NAME_TABLE_LOCK"; diff --git a/src/store/reducers/MiscReducer.ts b/src/store/reducers/MiscReducer.ts new file mode 100644 index 0000000..14f4c62 --- /dev/null +++ b/src/store/reducers/MiscReducer.ts @@ -0,0 +1,25 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { createReducer } from "typesafe-actions"; +import { + incrementNameTableLock, decrementNameTableLock +} from "@actions/MiscActions"; + +export interface State { + readonly nameTableLock: number; +} + +const initialState: State = { + nameTableLock: 0 +}; + +export const MiscReducer = createReducer(initialState) + .handleAction(incrementNameTableLock, (state, _) => ({ + ...state, + nameTableLock: state.nameTableLock + 1 + })) + .handleAction(decrementNameTableLock, (state, _) => ({ + ...state, + nameTableLock: state.nameTableLock - 1 + })); diff --git a/src/store/reducers/RootReducer.ts b/src/store/reducers/RootReducer.ts index 516fc35..1bb3411 100644 --- a/src/store/reducers/RootReducer.ts +++ b/src/store/reducers/RootReducer.ts @@ -8,11 +8,13 @@ import { SettingsReducer } from "./SettingsReducer"; import { WebsocketReducer } from "./WebsocketReducer"; import { NodeReducer } from "./NodeReducer"; +import { MiscReducer } from "./MiscReducer"; export default combineReducers({ masterPassword: MasterPasswordReducer, wallets: WalletsReducer, settings: SettingsReducer, websocket: WebsocketReducer, - node: NodeReducer + node: NodeReducer, + misc: MiscReducer });