mirror of
https://github.com/suitenumerique/drive.git
synced 2026-08-17 20:15:40 +02:00
♻️(frontend) generalize duplicating poller to transient items
The same polling loop fits any transient upload state. Rename the duplicating poller to a transient one driven by TRANSIENT_UPLOAD_STATES so converting items are picked up without copying the hook, and the explorer refreshes automatically when background conversion finishes.
This commit is contained in:
@@ -13,6 +13,7 @@ and this project adheres to
|
||||
- ✨(backend) manage reconciliation requests for user accounts
|
||||
- ✨(backend) add recursive folder export as ZIP archive
|
||||
- ✨(frontend) add folder export action
|
||||
- ✨(backend) background conversion of legacy Office files
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
+2
-2
@@ -45,7 +45,7 @@ import {
|
||||
} from "../../types/columns";
|
||||
import { ColumnHeader } from "./headers/ColumnHeader";
|
||||
import { CustomizableColumnHeader } from "./headers/CustomizableColumnHeader";
|
||||
import { useDuplicatingItemsPoller } from "../../hooks/useDuplicatingItemsPoller";
|
||||
import { useTransientItemsPoller } from "../../hooks/useTransientItemsPoller";
|
||||
import { EmbeddedExplorerGridRow } from "./EmbeddedExplorerGridRow";
|
||||
import posthog from "posthog-js";
|
||||
|
||||
@@ -128,7 +128,7 @@ export const EmbeddedExplorerGrid = (props: EmbeddedExplorerGridProps) => {
|
||||
});
|
||||
const contextMenu = useContextMenuContext();
|
||||
|
||||
useDuplicatingItemsPoller(props.items ?? EMPTY_ARRAY);
|
||||
useTransientItemsPoller(props.items ?? EMPTY_ARRAY);
|
||||
|
||||
const selectionStore = useSelectionStore();
|
||||
// TODO: This hook makes use of the ExplorerContext to manage the overred items. So, this component is not really standalone as it should be.
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { useMemo, useEffect, useRef } from "react";
|
||||
import { useQueries } from "@tanstack/react-query";
|
||||
import { getDriver } from "@/features/config/Config";
|
||||
import { Item, ItemUploadState } from "@/features/drivers/types";
|
||||
import { useRefreshItemCache } from "./useRefreshItems";
|
||||
|
||||
const POLL_INTERVAL = 3000;
|
||||
const POLL_TIMEOUT = 10 * 60 * 1000;
|
||||
|
||||
export const useDuplicatingItemsPoller = (items: Item[]) => {
|
||||
const refreshItemCache = useRefreshItemCache();
|
||||
const startTimesRef = useRef<Map<string, number>>(new Map());
|
||||
|
||||
const duplicatingItems = useMemo(
|
||||
() => items.filter((i) => i.upload_state === ItemUploadState.DUPLICATING),
|
||||
[items],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
for (const item of duplicatingItems) {
|
||||
if (!startTimesRef.current.has(item.id)) {
|
||||
startTimesRef.current.set(item.id, Date.now());
|
||||
}
|
||||
}
|
||||
const duplicatingIds = new Set(duplicatingItems.map((i) => i.id));
|
||||
for (const id of startTimesRef.current.keys()) {
|
||||
if (!duplicatingIds.has(id)) {
|
||||
startTimesRef.current.delete(id);
|
||||
}
|
||||
}
|
||||
}, [duplicatingItems]);
|
||||
|
||||
useQueries({
|
||||
queries: duplicatingItems.map((item) => ({
|
||||
queryKey: ["items", item.id, "duplicate-poll"],
|
||||
queryFn: async () => {
|
||||
const updatedItem = await getDriver().getItem(item.id);
|
||||
if (updatedItem.upload_state !== ItemUploadState.DUPLICATING) {
|
||||
await refreshItemCache(item.id, updatedItem);
|
||||
}
|
||||
return updatedItem;
|
||||
},
|
||||
refetchInterval: (query: { state: { data: Item | undefined } }) => {
|
||||
const data = query.state.data;
|
||||
if (data && data.upload_state !== ItemUploadState.DUPLICATING) {
|
||||
return false;
|
||||
}
|
||||
const startTime = startTimesRef.current.get(item.id) ?? Date.now();
|
||||
if (Date.now() - startTime > POLL_TIMEOUT) {
|
||||
return false;
|
||||
}
|
||||
return POLL_INTERVAL;
|
||||
},
|
||||
})),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import { useMemo, useEffect, useRef } from "react";
|
||||
import { useQueries, useQueryClient } from "@tanstack/react-query";
|
||||
import { APIError } from "@/features/api/APIError";
|
||||
import { getDriver } from "@/features/config/Config";
|
||||
import { Item, TRANSIENT_UPLOAD_STATES } from "@/features/drivers/types";
|
||||
import { useRefreshItemCache } from "./useRefreshItems";
|
||||
import { useRemoveItemsFromPaginatedList } from "./useOptimisticPagination";
|
||||
import {
|
||||
addToast,
|
||||
ToasterItem,
|
||||
} from "@/features/ui/components/toaster/Toaster";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const POLL_INTERVAL = 3000;
|
||||
const POLL_TIMEOUT = 10 * 60 * 1000;
|
||||
|
||||
export const useTransientItemsPoller = (items: Item[]) => {
|
||||
const refreshItemCache = useRefreshItemCache();
|
||||
const removeItems = useRemoveItemsFromPaginatedList();
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const startTimesRef = useRef<Map<string, number>>(new Map());
|
||||
const failedToastShownRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const transientItems = useMemo(
|
||||
() =>
|
||||
items.filter((i) => TRANSIENT_UPLOAD_STATES.includes(i.upload_state)),
|
||||
[items],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
for (const item of transientItems) {
|
||||
if (!startTimesRef.current.has(item.id)) {
|
||||
startTimesRef.current.set(item.id, Date.now());
|
||||
}
|
||||
}
|
||||
const transientIds = new Set(transientItems.map((i) => i.id));
|
||||
for (const id of startTimesRef.current.keys()) {
|
||||
if (!transientIds.has(id)) {
|
||||
startTimesRef.current.delete(id);
|
||||
}
|
||||
}
|
||||
}, [transientItems]);
|
||||
|
||||
useQueries({
|
||||
queries: transientItems.map((item) => ({
|
||||
queryKey: ["items", item.id, "transient-poll"],
|
||||
queryFn: async (): Promise<Item | null> => {
|
||||
try {
|
||||
const updatedItem = await getDriver().getItem(item.id);
|
||||
if (!TRANSIENT_UPLOAD_STATES.includes(updatedItem.upload_state)) {
|
||||
await refreshItemCache(item.id, updatedItem);
|
||||
}
|
||||
return updatedItem;
|
||||
} catch (error) {
|
||||
if (error instanceof APIError && error.code === 404) {
|
||||
removeItems(["items"], [item.id]);
|
||||
queryClient.removeQueries({ queryKey: ["items", item.id] });
|
||||
if (!failedToastShownRef.current.has(item.id)) {
|
||||
failedToastShownRef.current.add(item.id);
|
||||
addToast(
|
||||
<ToasterItem type="error">
|
||||
{t("explorer.actions.convert.modal.error")}
|
||||
</ToasterItem>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
refetchInterval: (query: {
|
||||
state: { data: Item | null | undefined };
|
||||
}) => {
|
||||
const data = query.state.data;
|
||||
if (data === null) {
|
||||
return false;
|
||||
}
|
||||
if (data && !TRANSIENT_UPLOAD_STATES.includes(data.upload_state)) {
|
||||
return false;
|
||||
}
|
||||
const startTime = startTimesRef.current.get(item.id) ?? Date.now();
|
||||
if (Date.now() - startTime > POLL_TIMEOUT) {
|
||||
return false;
|
||||
}
|
||||
return POLL_INTERVAL;
|
||||
},
|
||||
})),
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user