mirror of
https://github.com/Qortal/qapp-core.git
synced 2025-10-22 16:22:39 +00:00
fix loading
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "qapp-core",
|
||||
"version": "1.0.49",
|
||||
"version": "1.0.51",
|
||||
"description": "Qortal's core React library with global state, UI components, and utilities",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
|
@@ -18,6 +18,7 @@ interface LoadingVideoProps {
|
||||
togglePlay: () => void;
|
||||
startPlay: boolean;
|
||||
downloadResource: () => void;
|
||||
isStatusWrong: boolean
|
||||
}
|
||||
export const LoadingVideo = ({
|
||||
status,
|
||||
@@ -27,6 +28,7 @@ export const LoadingVideo = ({
|
||||
togglePlay,
|
||||
startPlay,
|
||||
downloadResource,
|
||||
isStatusWrong
|
||||
}: LoadingVideoProps) => {
|
||||
const getDownloadProgress = (percentLoaded: number) => {
|
||||
const progress = percentLoaded;
|
||||
@@ -80,7 +82,7 @@ export const LoadingVideo = ({
|
||||
|
||||
<> Refetching in 10 seconds</>
|
||||
</>
|
||||
) : status === "DOWNLOADED" ? (
|
||||
) : (status === "DOWNLOADED" && !isStatusWrong) ? (
|
||||
<>Download Completed: building video...</>
|
||||
) : status === "FAILED_TO_DOWNLOAD" ? (
|
||||
<>Unable to fetch video chunks from peers</>
|
||||
|
@@ -196,6 +196,7 @@ export const VideoPlayer = ({
|
||||
seekTo,
|
||||
togglePictureInPicture,
|
||||
downloadResource,
|
||||
isStatusWrong
|
||||
} = useVideoPlayerController({
|
||||
autoPlay,
|
||||
playerRef,
|
||||
@@ -357,7 +358,7 @@ export const VideoPlayer = ({
|
||||
if (onPlayParent) {
|
||||
onPlayParent();
|
||||
}
|
||||
}, [setIsPlaying]);
|
||||
}, [setIsPlaying, onPlayParent]);
|
||||
|
||||
const onPause = useCallback(() => {
|
||||
setIsPlaying(false);
|
||||
@@ -762,6 +763,7 @@ export const VideoPlayer = ({
|
||||
isLoading={isLoading}
|
||||
startPlay={startPlay}
|
||||
downloadResource={downloadResource}
|
||||
isStatusWrong={isStatusWrong}
|
||||
/>
|
||||
<VideoElement
|
||||
ref={videoRef}
|
||||
|
@@ -43,7 +43,7 @@ export const useVideoPlayerController = (props: UseVideoControls) => {
|
||||
const startedFetchRef = useRef(false);
|
||||
const { playbackSettings, setPlaybackRate } = useVideoStore();
|
||||
|
||||
const { isReady, resourceUrl, status, percentLoaded, downloadResource } =
|
||||
const { isReady, resourceUrl, status, localChunkCount, totalChunkCount , percentLoaded, downloadResource } =
|
||||
useResourceStatus({
|
||||
resource: !startedFetch ? null : qortalVideoResource,
|
||||
retryAttempts,
|
||||
@@ -304,5 +304,6 @@ export const useVideoPlayerController = (props: UseVideoControls) => {
|
||||
seekTo,
|
||||
togglePictureInPicture,
|
||||
downloadResource,
|
||||
isStatusWrong: !isNaN(totalChunkCount) && !isNaN(localChunkCount) && totalChunkCount === 2 && (totalChunkCount < localChunkCount)
|
||||
};
|
||||
};
|
||||
|
@@ -71,77 +71,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
"lib-core": {
|
||||
"subtitle": {
|
||||
"subtitles": "Untertitel",
|
||||
"no_subtitles": "Keine Untertitel",
|
||||
"load_community_subs": "Community-Untertitel laden",
|
||||
"off": "Aus",
|
||||
"deleting_subtitle": "Untertitel wird gelöscht...",
|
||||
"deleted": "Untertitel gelöscht",
|
||||
"unable_delete": "Löschen nicht möglich",
|
||||
"publishing": "Untertitel werden veröffentlicht...",
|
||||
"published": "Untertitel veröffentlicht",
|
||||
"unable_publish": "Veröffentlichung nicht möglich",
|
||||
"my_subtitles": "Meine Untertitel",
|
||||
"new": "Neu",
|
||||
"existing": "Vorhanden",
|
||||
"import_subtitles": "Untertitel importieren"
|
||||
},
|
||||
"actions": {
|
||||
"remove": "entfernen",
|
||||
"publish": "veröffentlichen",
|
||||
"delete": "löschen",
|
||||
"publish_metadata": "Metadaten veröffentlichen",
|
||||
"publish_index": "Index veröffentlichen",
|
||||
"cancel": "abbrechen",
|
||||
"continue": "fortfahren",
|
||||
"close": "schließen",
|
||||
"retry": "wiederholen"
|
||||
},
|
||||
"video": {
|
||||
"playback_speed": "Wiedergabegeschwindigkeit",
|
||||
"toggle_fullscreen": "Vollbild umschalten (F)",
|
||||
"video_speed": "Video-Geschwindigkeit. Erhöhen (+ oder >), Verringern (- oder <)",
|
||||
"toggle_mute": "Stummschalten umschalten (M), Lauter (OBEN), Leiser (UNTEN)",
|
||||
"seek_video": "Video in 10%-Schritten vorspulen (0–9)",
|
||||
"reload_video": "Video neu laden (R)",
|
||||
"play_pause": "Wiedergabe/Pause (Leertaste)"
|
||||
},
|
||||
"index": {
|
||||
"title": "Index-Manager",
|
||||
"create_new_index": "Neuen Index erstellen",
|
||||
"add_metadata": "Metadaten hinzufügen",
|
||||
"publishing_metadata": "Metadaten werden veröffentlicht...",
|
||||
"published_metadata": "Metadaten erfolgreich veröffentlicht",
|
||||
"failed_metadata": "Veröffentlichung der Metadaten fehlgeschlagen",
|
||||
"example": "Beispiel, wie es aussehen könnte:",
|
||||
"metadata_title": "Titel",
|
||||
"metadata_description": "Beschreibung",
|
||||
"metadata_title_placeholder": "Titel für den Link hinzufügen",
|
||||
"metadata_description_placeholder": "Beschreibung für den Link hinzufügen",
|
||||
"characters": "Zeichen",
|
||||
"publishing_index": "Index wird veröffentlicht...",
|
||||
"published_index": "Index erfolgreich veröffentlicht",
|
||||
"failed_index": "Veröffentlichung des Index fehlgeschlagen",
|
||||
"recommended_indices": "Empfohlene Indizes",
|
||||
"add_search_term": "Suchbegriff hinzufügen",
|
||||
"search_terms": "Suchbegriffe",
|
||||
"recommendation_size": "Es wird empfohlen, die Zeichenanzahl pro Begriff unter {{recommendedSize}} Zeichen zu halten",
|
||||
"multiple_title": "Mehrere Indizes hinzufügen",
|
||||
"multiple_description": "Weitere Indizes senken die Veröffentlichungsgebühren, haben aber weniger Gewicht in zukünftigen Suchergebnissen."
|
||||
},
|
||||
"multi_publish": {
|
||||
"title": "Veröffentlichungsstatus",
|
||||
"publish_failed": "Veröffentlichung fehlgeschlagen",
|
||||
"file_chunk": "Dateifragment",
|
||||
"file_processing": "Datei wird verarbeitet",
|
||||
"success": "Erfolgreich veröffentlicht",
|
||||
"attempt_retry": "Veröffentlichung fehlgeschlagen. Erneuter Versuch..."
|
||||
}
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"lib-core": {
|
||||
"subtitle": {
|
||||
@@ -213,6 +142,77 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
"lib-core": {
|
||||
"subtitle": {
|
||||
"subtitles": "Untertitel",
|
||||
"no_subtitles": "Keine Untertitel",
|
||||
"load_community_subs": "Community-Untertitel laden",
|
||||
"off": "Aus",
|
||||
"deleting_subtitle": "Untertitel wird gelöscht...",
|
||||
"deleted": "Untertitel gelöscht",
|
||||
"unable_delete": "Löschen nicht möglich",
|
||||
"publishing": "Untertitel werden veröffentlicht...",
|
||||
"published": "Untertitel veröffentlicht",
|
||||
"unable_publish": "Veröffentlichung nicht möglich",
|
||||
"my_subtitles": "Meine Untertitel",
|
||||
"new": "Neu",
|
||||
"existing": "Vorhanden",
|
||||
"import_subtitles": "Untertitel importieren"
|
||||
},
|
||||
"actions": {
|
||||
"remove": "entfernen",
|
||||
"publish": "veröffentlichen",
|
||||
"delete": "löschen",
|
||||
"publish_metadata": "Metadaten veröffentlichen",
|
||||
"publish_index": "Index veröffentlichen",
|
||||
"cancel": "abbrechen",
|
||||
"continue": "fortfahren",
|
||||
"close": "schließen",
|
||||
"retry": "wiederholen"
|
||||
},
|
||||
"video": {
|
||||
"playback_speed": "Wiedergabegeschwindigkeit",
|
||||
"toggle_fullscreen": "Vollbild umschalten (F)",
|
||||
"video_speed": "Video-Geschwindigkeit. Erhöhen (+ oder >), Verringern (- oder <)",
|
||||
"toggle_mute": "Stummschalten umschalten (M), Lauter (OBEN), Leiser (UNTEN)",
|
||||
"seek_video": "Video in 10%-Schritten vorspulen (0–9)",
|
||||
"reload_video": "Video neu laden (R)",
|
||||
"play_pause": "Wiedergabe/Pause (Leertaste)"
|
||||
},
|
||||
"index": {
|
||||
"title": "Index-Manager",
|
||||
"create_new_index": "Neuen Index erstellen",
|
||||
"add_metadata": "Metadaten hinzufügen",
|
||||
"publishing_metadata": "Metadaten werden veröffentlicht...",
|
||||
"published_metadata": "Metadaten erfolgreich veröffentlicht",
|
||||
"failed_metadata": "Veröffentlichung der Metadaten fehlgeschlagen",
|
||||
"example": "Beispiel, wie es aussehen könnte:",
|
||||
"metadata_title": "Titel",
|
||||
"metadata_description": "Beschreibung",
|
||||
"metadata_title_placeholder": "Titel für den Link hinzufügen",
|
||||
"metadata_description_placeholder": "Beschreibung für den Link hinzufügen",
|
||||
"characters": "Zeichen",
|
||||
"publishing_index": "Index wird veröffentlicht...",
|
||||
"published_index": "Index erfolgreich veröffentlicht",
|
||||
"failed_index": "Veröffentlichung des Index fehlgeschlagen",
|
||||
"recommended_indices": "Empfohlene Indizes",
|
||||
"add_search_term": "Suchbegriff hinzufügen",
|
||||
"search_terms": "Suchbegriffe",
|
||||
"recommendation_size": "Es wird empfohlen, die Zeichenanzahl pro Begriff unter {{recommendedSize}} Zeichen zu halten",
|
||||
"multiple_title": "Mehrere Indizes hinzufügen",
|
||||
"multiple_description": "Weitere Indizes senken die Veröffentlichungsgebühren, haben aber weniger Gewicht in zukünftigen Suchergebnissen."
|
||||
},
|
||||
"multi_publish": {
|
||||
"title": "Veröffentlichungsstatus",
|
||||
"publish_failed": "Veröffentlichung fehlgeschlagen",
|
||||
"file_chunk": "Dateifragment",
|
||||
"file_processing": "Datei wird verarbeitet",
|
||||
"success": "Erfolgreich veröffentlicht",
|
||||
"attempt_retry": "Veröffentlichung fehlgeschlagen. Erneuter Versuch..."
|
||||
}
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"lib-core": {
|
||||
"subtitle": {
|
||||
@@ -642,8 +642,8 @@
|
||||
},
|
||||
"supportedLanguages": [
|
||||
"ar",
|
||||
"de",
|
||||
"en",
|
||||
"de",
|
||||
"es",
|
||||
"fr",
|
||||
"it",
|
||||
|
@@ -1,28 +1,31 @@
|
||||
import { create } from "zustand";
|
||||
import { QortalGetMetadata, QortalMetadata } from "../types/interfaces/resources";
|
||||
import {
|
||||
QortalGetMetadata,
|
||||
QortalMetadata,
|
||||
} from "../types/interfaces/resources";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
|
||||
interface SearchCache {
|
||||
[listName: string]: {
|
||||
searches: {
|
||||
[searchTerm: string]: QortalMetadata[]; // List of products for each search term
|
||||
};
|
||||
temporaryNewResources: QortalMetadata[],
|
||||
temporaryNewResources: QortalMetadata[];
|
||||
expiry: number; // Expiry timestamp for the whole list
|
||||
searchParamsStringified: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const mergeUniqueItems = (array1: QortalMetadata[], array2: QortalMetadata[]) => {
|
||||
export const mergeUniqueItems = (
|
||||
array1: QortalMetadata[],
|
||||
array2: QortalMetadata[]
|
||||
) => {
|
||||
const mergedArray = [...array1, ...array2];
|
||||
|
||||
// Use a Map to ensure uniqueness based on `identifier-name`
|
||||
const uniqueMap = new Map();
|
||||
|
||||
mergedArray.forEach(item => {
|
||||
mergedArray.forEach((item) => {
|
||||
if (item.identifier && item.name && item.service) {
|
||||
const key = `${item.service}-${item.name}-${item.identifier}`;
|
||||
uniqueMap.set(key, item);
|
||||
@@ -33,241 +36,278 @@ export const mergeUniqueItems = (array1: QortalMetadata[], array2: QortalMetadat
|
||||
};
|
||||
|
||||
export interface ListItem {
|
||||
data: any
|
||||
qortalMetadata: QortalMetadata
|
||||
data: any;
|
||||
qortalMetadata: QortalMetadata;
|
||||
}
|
||||
|
||||
|
||||
interface resourceCache {
|
||||
[id: string]: {
|
||||
data: ListItem | false | null; // Cached resource data
|
||||
expiry: number; // Expiry timestamp in milliseconds
|
||||
};
|
||||
}
|
||||
[id: string]: {
|
||||
data: ListItem | false | null; // Cached resource data
|
||||
expiry: number; // Expiry timestamp in milliseconds
|
||||
};
|
||||
}
|
||||
|
||||
interface DeletedResources {
|
||||
[key: string]: { deleted: true; expiry: number }; // ✅ Added expiry field
|
||||
}
|
||||
interface DeletedResources {
|
||||
[key: string]: { deleted: true; expiry: number }; // ✅ Added expiry field
|
||||
}
|
||||
|
||||
interface CacheState {
|
||||
resourceCache: resourceCache;
|
||||
|
||||
searchCache: SearchCache;
|
||||
// Search cache actions
|
||||
setResourceCache: (id: string, data: ListItem | false | null, customExpiry?: number) => void;
|
||||
setResourceCache: (
|
||||
id: string,
|
||||
data: ListItem | false | null,
|
||||
customExpiry?: number
|
||||
) => void;
|
||||
|
||||
setSearchCache: (listName: string, searchTerm: string, data: QortalMetadata[], searchParamsStringified: string | null, customExpiry?: number) => void;
|
||||
setSearchParamsForList: (ListName: string, searchParamsStringified: string)=> void;
|
||||
getSearchCache: (listName: string, searchTerm: string) => QortalMetadata[] | null;
|
||||
setSearchCache: (
|
||||
listName: string,
|
||||
searchTerm: string,
|
||||
data: QortalMetadata[],
|
||||
searchParamsStringified: string | null,
|
||||
customExpiry?: number
|
||||
) => void;
|
||||
setSearchParamsForList: (
|
||||
ListName: string,
|
||||
searchParamsStringified: string
|
||||
) => void;
|
||||
getSearchCache: (
|
||||
listName: string,
|
||||
searchTerm: string
|
||||
) => QortalMetadata[] | null;
|
||||
clearExpiredCache: () => void;
|
||||
getResourceCache: (id: string, ignoreExpire?: boolean) => ListItem | false | null;
|
||||
addTemporaryResource: (listName: string, newResources: QortalMetadata[], customExpiry?: number)=> void;
|
||||
getTemporaryResources:(listName: string)=> QortalMetadata[]
|
||||
getResourceCache: (
|
||||
id: string,
|
||||
ignoreExpire?: boolean
|
||||
) => ListItem | false | null;
|
||||
addTemporaryResource: (
|
||||
listName: string,
|
||||
newResources: QortalMetadata[],
|
||||
customExpiry?: number
|
||||
) => void;
|
||||
getTemporaryResources: (listName: string) => QortalMetadata[];
|
||||
deletedResources: DeletedResources;
|
||||
markResourceAsDeleted: (item: QortalMetadata | QortalGetMetadata) => void;
|
||||
filterOutDeletedResources: (items: QortalMetadata[]) => QortalMetadata[];
|
||||
isListExpired: (listName: string)=> boolean | string;
|
||||
isListExpired: (listName: string) => boolean | string;
|
||||
searchCacheExpiryDuration: number;
|
||||
resourceCacheExpiryDuration: number;
|
||||
setSearchCacheExpiryDuration: (duration: number) => void;
|
||||
setResourceCacheExpiryDuration: (duration: number)=> void;
|
||||
setResourceCacheExpiryDuration: (duration: number) => void;
|
||||
deleteSearchCache: (listName: string) => void;
|
||||
filterSearchCacheItemsByNames: (names: string[]) => void;
|
||||
|
||||
}
|
||||
|
||||
export const useCacheStore = create<CacheState>
|
||||
((set, get) => ({
|
||||
searchCacheExpiryDuration: 5 * 60 * 1000,
|
||||
resourceCacheExpiryDuration: 30 * 60 * 1000,
|
||||
resourceCache: {},
|
||||
searchCache: {},
|
||||
deletedResources: {},
|
||||
setSearchCacheExpiryDuration: (duration) => set({ searchCacheExpiryDuration: duration }),
|
||||
setResourceCacheExpiryDuration: (duration) => set({ resourceCacheExpiryDuration: duration }),
|
||||
getResourceCache: (id, ignoreExpire) => {
|
||||
const cache = get().resourceCache[id];
|
||||
if (cache) {
|
||||
if (cache.expiry > Date.now() || ignoreExpire) {
|
||||
return cache.data; // ✅ Return data if not expired
|
||||
} else {
|
||||
set((state) => {
|
||||
const updatedCache = { ...state.resourceCache };
|
||||
delete updatedCache[id]; // ✅ Remove expired entry
|
||||
return { resourceCache: updatedCache };
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
setResourceCache: (id, data, customExpiry) =>
|
||||
export const useCacheStore = create<CacheState>((set, get) => ({
|
||||
searchCacheExpiryDuration: 5 * 60 * 1000,
|
||||
resourceCacheExpiryDuration: 30 * 60 * 1000,
|
||||
resourceCache: {},
|
||||
searchCache: {},
|
||||
deletedResources: {},
|
||||
setSearchCacheExpiryDuration: (duration) =>
|
||||
set({ searchCacheExpiryDuration: duration }),
|
||||
setResourceCacheExpiryDuration: (duration) =>
|
||||
set({ resourceCacheExpiryDuration: duration }),
|
||||
getResourceCache: (id, ignoreExpire) => {
|
||||
const cache = get().resourceCache[id];
|
||||
if (cache) {
|
||||
if (cache.expiry > Date.now() || ignoreExpire) {
|
||||
return cache.data; // ✅ Return data if not expired
|
||||
} else {
|
||||
set((state) => {
|
||||
const expiry = Date.now() + (customExpiry || get().resourceCacheExpiryDuration);
|
||||
return {
|
||||
resourceCache: {
|
||||
...state.resourceCache,
|
||||
[id]: { data, expiry },
|
||||
},
|
||||
};
|
||||
}),
|
||||
const updatedCache = { ...state.resourceCache };
|
||||
delete updatedCache[id]; // ✅ Remove expired entry
|
||||
return { resourceCache: updatedCache };
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
setSearchCache: (listName, searchTerm, data, searchParamsStringified, customExpiry) =>
|
||||
set((state) => {
|
||||
const expiry = Date.now() + (customExpiry || get().searchCacheExpiryDuration);
|
||||
|
||||
return {
|
||||
searchCache: {
|
||||
...state.searchCache,
|
||||
[listName]: {
|
||||
searches: {
|
||||
...(state.searchCache[listName]?.searches || {}),
|
||||
[searchTerm]: data,
|
||||
},
|
||||
temporaryNewResources: state.searchCache[listName]?.temporaryNewResources || [],
|
||||
expiry,
|
||||
searchParamsStringified: searchParamsStringified === null ? state.searchCache[listName]?.searchParamsStringified : searchParamsStringified
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
deleteSearchCache: (listName) =>
|
||||
set((state) => {
|
||||
const updatedSearchCache = { ...state.searchCache };
|
||||
delete updatedSearchCache[listName];
|
||||
return { searchCache: updatedSearchCache };
|
||||
}),
|
||||
setSearchParamsForList: (listName, searchParamsStringified) =>
|
||||
set((state) => {
|
||||
const existingList = state.searchCache[listName] || {};
|
||||
|
||||
return {
|
||||
searchCache: {
|
||||
...state.searchCache,
|
||||
[listName]: {
|
||||
...existingList,
|
||||
searchParamsStringified,
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
getSearchCache: (listName, searchTerm) => {
|
||||
const cache = get().searchCache[listName];
|
||||
if (cache) {
|
||||
if (cache.expiry > Date.now()) {
|
||||
return cache.searches[searchTerm] || null; // ✅ Return if valid
|
||||
} else {
|
||||
set((state) => {
|
||||
const updatedCache = { ...state.searchCache };
|
||||
delete updatedCache[listName]; // ✅ Remove expired list
|
||||
return { searchCache: updatedCache };
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
setResourceCache: (id, data, customExpiry) =>
|
||||
set((state) => {
|
||||
const expiry =
|
||||
Date.now() + (customExpiry || get().resourceCacheExpiryDuration);
|
||||
return {
|
||||
resourceCache: {
|
||||
...state.resourceCache,
|
||||
[id]: { data, expiry },
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
addTemporaryResource: (listName, newResources, customExpiry) =>
|
||||
set((state) => {
|
||||
setSearchCache: (
|
||||
listName,
|
||||
searchTerm,
|
||||
data,
|
||||
searchParamsStringified,
|
||||
customExpiry
|
||||
) =>
|
||||
set((state) => {
|
||||
const expiry =
|
||||
Date.now() + (customExpiry || get().searchCacheExpiryDuration);
|
||||
|
||||
const expiry = Date.now() + (customExpiry || 5 * 60 * 1000);
|
||||
|
||||
const existingResources = state.searchCache[listName]?.temporaryNewResources || [];
|
||||
|
||||
// Merge & remove duplicates, keeping the latest by `created` timestamp
|
||||
const uniqueResourcesMap = new Map<string, QortalMetadata>();
|
||||
|
||||
[...existingResources, ...newResources].forEach((item) => {
|
||||
const key = `${item.service}-${item.name}-${item.identifier}`;
|
||||
const existingItem = uniqueResourcesMap.get(key);
|
||||
|
||||
if (!existingItem || item.created > existingItem.created) {
|
||||
uniqueResourcesMap.set(key, item);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
searchCache: {
|
||||
...state.searchCache,
|
||||
[listName]: {
|
||||
...state.searchCache[listName],
|
||||
temporaryNewResources: Array.from(uniqueResourcesMap.values()),
|
||||
expiry,
|
||||
},
|
||||
return {
|
||||
searchCache: {
|
||||
...state.searchCache,
|
||||
[listName]: {
|
||||
searches: {
|
||||
...(state.searchCache[listName]?.searches || {}),
|
||||
[searchTerm]: data,
|
||||
},
|
||||
};
|
||||
}),
|
||||
temporaryNewResources:
|
||||
state.searchCache[listName]?.temporaryNewResources || [],
|
||||
expiry,
|
||||
searchParamsStringified:
|
||||
searchParamsStringified === null
|
||||
? state.searchCache[listName]?.searchParamsStringified
|
||||
: searchParamsStringified,
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
deleteSearchCache: (listName) =>
|
||||
set((state) => {
|
||||
const updatedSearchCache = { ...state.searchCache };
|
||||
delete updatedSearchCache[listName];
|
||||
return { searchCache: updatedSearchCache };
|
||||
}),
|
||||
setSearchParamsForList: (listName, searchParamsStringified) =>
|
||||
set((state) => {
|
||||
const existingList = state.searchCache[listName] || {};
|
||||
|
||||
getTemporaryResources: (listName: string) => {
|
||||
const cache = get().searchCache[listName];
|
||||
if (cache && cache.expiry > Date.now()) {
|
||||
return cache.temporaryNewResources || [];
|
||||
return {
|
||||
searchCache: {
|
||||
...state.searchCache,
|
||||
[listName]: {
|
||||
...existingList,
|
||||
searchParamsStringified,
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
getSearchCache: (listName, searchTerm) => {
|
||||
const cache = get().searchCache[listName];
|
||||
if (cache) {
|
||||
if (cache.expiry > Date.now()) {
|
||||
return cache.searches[searchTerm] || null; // ✅ Return if valid
|
||||
} else {
|
||||
set((state) => {
|
||||
const updatedCache = { ...state.searchCache };
|
||||
delete updatedCache[listName]; // ✅ Remove expired list
|
||||
return { searchCache: updatedCache };
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
addTemporaryResource: (listName, newResources, customExpiry) =>
|
||||
set((state) => {
|
||||
const expiry = Date.now() + (customExpiry || 5 * 60 * 1000);
|
||||
|
||||
const existingResources =
|
||||
state.searchCache[listName]?.temporaryNewResources || [];
|
||||
|
||||
// Merge & remove duplicates, keeping the latest by `created` timestamp
|
||||
const uniqueResourcesMap = new Map<string, QortalMetadata>();
|
||||
|
||||
[...existingResources, ...newResources].forEach((item) => {
|
||||
const key = `${item.service}-${item.name}-${item.identifier}`;
|
||||
const existingItem = uniqueResourcesMap.get(key);
|
||||
|
||||
if (!existingItem || item.created > existingItem.created) {
|
||||
uniqueResourcesMap.set(key, item);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
});
|
||||
|
||||
markResourceAsDeleted: (item) =>
|
||||
set((state) => {
|
||||
const now = Date.now();
|
||||
const expiry = now + 5 * 60 * 1000; // ✅ Expires in 5 minutes
|
||||
|
||||
// ✅ Remove expired deletions before adding a new one
|
||||
const validDeletedResources = Object.fromEntries(
|
||||
Object.entries(state.deletedResources).filter(([_, value]) => value.expiry > now)
|
||||
return {
|
||||
searchCache: {
|
||||
...state.searchCache,
|
||||
[listName]: {
|
||||
...state.searchCache[listName],
|
||||
temporaryNewResources: Array.from(uniqueResourcesMap.values()),
|
||||
expiry,
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
getTemporaryResources: (listName: string) => {
|
||||
const cache = get().searchCache[listName];
|
||||
if (cache && cache.expiry > Date.now()) {
|
||||
const resources = cache.temporaryNewResources || [];
|
||||
return [...resources].sort((a, b) => b?.created - a?.created);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
markResourceAsDeleted: (item) =>
|
||||
set((state) => {
|
||||
const now = Date.now();
|
||||
const expiry = now + 5 * 60 * 1000; // ✅ Expires in 5 minutes
|
||||
|
||||
// ✅ Remove expired deletions before adding a new one
|
||||
const validDeletedResources = Object.fromEntries(
|
||||
Object.entries(state.deletedResources).filter(
|
||||
([_, value]) => value.expiry > now
|
||||
)
|
||||
);
|
||||
|
||||
const key = `${item.service}-${item.name}-${item.identifier}`;
|
||||
return {
|
||||
deletedResources: {
|
||||
...validDeletedResources, // ✅ Keep only non-expired ones
|
||||
[key]: { deleted: true, expiry },
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
filterOutDeletedResources: (items) => {
|
||||
const deletedResources = get().deletedResources; // ✅ Read without modifying store
|
||||
return items.filter(
|
||||
(item) =>
|
||||
!deletedResources[`${item.service}-${item.name}-${item.identifier}`]
|
||||
);
|
||||
},
|
||||
isListExpired: (listName: string): boolean | string => {
|
||||
const cache = get().searchCache[listName];
|
||||
const isExpired = cache ? cache.expiry <= Date.now() : true; // ✅ Expired if expiry timestamp is in the past
|
||||
return isExpired === true ? true : cache.searchParamsStringified;
|
||||
},
|
||||
|
||||
clearExpiredCache: () =>
|
||||
set((state) => {
|
||||
const now = Date.now();
|
||||
const validSearchCache = Object.fromEntries(
|
||||
Object.entries(state.searchCache).filter(
|
||||
([, value]) => value.expiry > now
|
||||
)
|
||||
);
|
||||
return { searchCache: validSearchCache };
|
||||
}),
|
||||
filterSearchCacheItemsByNames: (names) =>
|
||||
set((state) => {
|
||||
const updatedSearchCache: SearchCache = {};
|
||||
|
||||
for (const [listName, list] of Object.entries(state.searchCache)) {
|
||||
const updatedSearches: { [searchTerm: string]: QortalMetadata[] } = {};
|
||||
|
||||
for (const [term, items] of Object.entries(list.searches)) {
|
||||
updatedSearches[term] = items.filter(
|
||||
(item) => !names.includes(item.name)
|
||||
);
|
||||
|
||||
const key = `${item.service}-${item.name}-${item.identifier}`;
|
||||
return {
|
||||
deletedResources: {
|
||||
...validDeletedResources, // ✅ Keep only non-expired ones
|
||||
[key]: { deleted: true, expiry },
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
filterOutDeletedResources: (items) => {
|
||||
const deletedResources = get().deletedResources; // ✅ Read without modifying store
|
||||
return items.filter(
|
||||
(item) => !deletedResources[`${item.service}-${item.name}-${item.identifier}`]
|
||||
);
|
||||
},
|
||||
isListExpired: (listName: string): boolean | string => {
|
||||
const cache = get().searchCache[listName];
|
||||
const isExpired = cache ? cache.expiry <= Date.now() : true; // ✅ Expired if expiry timestamp is in the past
|
||||
return isExpired === true ? true : cache.searchParamsStringified
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
clearExpiredCache: () =>
|
||||
set((state) => {
|
||||
const now = Date.now();
|
||||
const validSearchCache = Object.fromEntries(
|
||||
Object.entries(state.searchCache).filter(([, value]) => value.expiry > now)
|
||||
);
|
||||
return { searchCache: validSearchCache };
|
||||
}),
|
||||
filterSearchCacheItemsByNames: (names) =>
|
||||
set((state) => {
|
||||
const updatedSearchCache: SearchCache = {};
|
||||
|
||||
for (const [listName, list] of Object.entries(state.searchCache)) {
|
||||
const updatedSearches: { [searchTerm: string]: QortalMetadata[] } = {};
|
||||
|
||||
for (const [term, items] of Object.entries(list.searches)) {
|
||||
updatedSearches[term] = items.filter(
|
||||
(item) => !names.includes(item.name)
|
||||
);
|
||||
updatedSearchCache[listName] = {
|
||||
...list,
|
||||
searches: updatedSearches,
|
||||
};
|
||||
}
|
||||
|
||||
updatedSearchCache[listName] = {
|
||||
...list,
|
||||
searches: updatedSearches,
|
||||
};
|
||||
}
|
||||
|
||||
return { searchCache: updatedSearchCache };
|
||||
}),
|
||||
return { searchCache: updatedSearchCache };
|
||||
}),
|
||||
|
||||
);
|
||||
}));
|
||||
|
Reference in New Issue
Block a user