fix update cache

This commit is contained in:
PhilReact 2025-06-15 19:18:42 +03:00
parent 864d87697c
commit 700d5374ed
5 changed files with 369 additions and 184 deletions

View File

@ -13,21 +13,46 @@ export default function LanguageSelector({
}) {
return (
<Autocomplete
size="small"
options={languageOptions}
getOptionLabel={(option) => `${option.name} (${option.code})`}
value={languageOptions.find((opt) => opt.code === value) || null}
onChange={(event, newValue) => onChange(newValue?.code || null)}
renderInput={(params) => <TextField {...params} label="Subtitle Language" />}
renderInput={(params) => (
<TextField
required
{...params}
label="Subtitle Language"
sx={{
fontSize: '1rem', // Input text size
'& .MuiInputBase-input': {
fontSize: '1rem', // Inner input
},
'& .MuiInputLabel-root': {
fontSize: '0.75rem', // Label text
},
}}
/>
)}
isOptionEqualToValue={(option, val) => option.code === val.code}
sx={{ width: 300 }}
sx={{
width: '100%',
fontSize: '1rem', // affects root font size
'& .MuiAutocomplete-input': {
fontSize: '1rem',
},
}}
slotProps={{
popper: {
sx: {
zIndex: 999991, // Must be higher than Dialog's default zIndex (1300)
zIndex: 999991,
'& .MuiAutocomplete-paper': {
fontSize: '1rem', // dropdown font size
},
},
},
}}
/>
);
}

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
QortalGetMetadata,
QortalMetadata,
@ -9,6 +9,7 @@ import {
Box,
Button,
ButtonBase,
Card,
Dialog,
DialogActions,
DialogContent,
@ -17,10 +18,12 @@ import {
Fade,
IconButton,
Popover,
Tab,
Tabs,
Typography,
} from "@mui/material";
import CheckIcon from '@mui/icons-material/Check';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import CheckIcon from "@mui/icons-material/Check";
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos";
import ModeEditIcon from "@mui/icons-material/ModeEdit";
import CloseIcon from "@mui/icons-material/Close";
@ -39,6 +42,7 @@ import { fileToBase64, objectToBase64 } from "../../utils/base64";
import { ResourceToPublish } from "../../types/qortalRequests/types";
import { useListReturn } from "../../hooks/useListData";
import { usePublish } from "../../hooks/usePublish";
import { Spacer } from "../../common/Spacer";
interface SubtitleManagerProps {
qortalMetadata: QortalGetMetadata;
close: () => void;
@ -67,6 +71,14 @@ export const languageOptions = ISO6391.getAllCodes().map((code) => ({
name: ISO6391.getName(code),
nativeName: ISO6391.getNativeName(code),
}));
function a11yProps(index: number) {
return {
id: `subtitle-tab-${index}`,
"aria-controls": `subtitle-tabpanel-${index}`,
};
}
const SubtitleManagerComponent = ({
qortalMetadata,
open,
@ -76,13 +88,18 @@ const SubtitleManagerComponent = ({
currentSubTrack,
}: SubtitleManagerProps) => {
const [mode, setMode] = useState(1);
const [isOpenPublish, setIsOpenPublish] = useState(false);
const { lists, identifierOperations, auth } = useGlobal();
const { fetchResources } = useResources();
// const [subtitles, setSubtitles] = useState([])
const subtitles = useListReturn(
`subs-${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`
);
console.log('subtitles222', subtitles)
const mySubtitles = useMemo(()=> {
if(!auth?.name)return []
return subtitles?.filter((sub)=> sub.name === auth?.name)
}, [subtitles, auth?.name])
console.log("subtitles222", subtitles);
const getPublishedSubtitles = useCallback(async () => {
try {
@ -193,12 +210,13 @@ const SubtitleManagerComponent = ({
};
const onSelectHandler = (sub: SubtitlePublishedData) => {
console.log('onSelectHandler')
console.log("onSelectHandler");
onSelect(sub);
close();
};
return (
<>
<Popover
open={!!open}
anchorEl={subtitleBtnRef.current}
@ -260,6 +278,7 @@ const SubtitleManagerComponent = ({
sx={{
marginLeft: "auto",
}}
onClick={() => setIsOpenPublish(true)}
>
<ModeEditIcon
sx={{
@ -304,6 +323,14 @@ const SubtitleManagerComponent = ({
))}
</Box> */}
</Popover>
<PublishSubtitles
isOpen={isOpenPublish}
setIsOpen={setIsOpenPublish}
publishHandler={publishHandler}
mySubtitles={mySubtitles}
/>
</>
// <Dialog
// open={!!open}
// fullWidth={true}
@ -374,7 +401,7 @@ interface PublisherSubtitlesProps {
setMode: (val: number) => void;
onSelect: (subtitle: any) => void;
onBack: () => void;
currentSubTrack: string | null
currentSubTrack: string | null;
}
const PublisherSubtitles = ({
@ -383,7 +410,7 @@ const PublisherSubtitles = ({
setMode,
onSelect,
onBack,
currentSubTrack
currentSubTrack,
}: PublisherSubtitlesProps) => {
return (
<>
@ -403,11 +430,20 @@ const PublisherSubtitles = ({
interface PublishSubtitlesProps {
publishHandler: (subs: Subtitle[]) => void;
isOpen: boolean;
setIsOpen: (val: boolean) => void;
mySubtitles: QortalGetMetadata[]
}
const PublishSubtitles = ({ publishHandler }: PublishSubtitlesProps) => {
const PublishSubtitles = ({
publishHandler,
isOpen,
setIsOpen,
mySubtitles
}: PublishSubtitlesProps) => {
const [language, setLanguage] = useState<null | string>(null);
const [subtitles, setSubtitles] = useState<Subtitle[]>([]);
const {lists} = useGlobal()
const onDrop = useCallback(async (acceptedFiles: File[]) => {
const newSubtitles: Subtitle[] = [];
for (const file of acceptedFiles) {
@ -457,16 +493,74 @@ const PublishSubtitles = ({ publishHandler }: PublishSubtitlesProps) => {
};
console.log("subtitles", subtitles);
const handleClose = () => {
setIsOpen(false);
};
const [value, setValue] = useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
const onDelete = useCallback(async (sub: QortalGetMetadata)=> {
try {
await lists.deleteResource([
sub
])
} catch (error) {
}
},[])
return (
<>
<Dialog
open={isOpen}
fullWidth={true}
maxWidth={"md"}
sx={{
zIndex: 999990,
}}
slotProps={{
paper: {
elevation: 0,
},
}}
>
<DialogTitle>My Subtitles</DialogTitle>
<IconButton
aria-label="close"
onClick={handleClose}
sx={(theme) => ({
position: "absolute",
right: 8,
top: 8,
})}
>
<CloseIcon />
</IconButton>
<DialogContent>
<Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={value}
onChange={handleChange}
aria-label="basic tabs example"
>
<Tab label="New" {...a11yProps(0)} />
<Tab label="Existing" {...a11yProps(1)} />
</Tabs>
</Box>
</Box>
<Spacer height="25px" />
{value === 0 && (
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "20px",
width: "100%",
alignItems: "flex-start",
alignItems: "center",
}}
>
<Box {...getRootProps()}>
@ -483,17 +577,81 @@ const PublishSubtitles = ({ publishHandler }: PublishSubtitlesProps) => {
</Box>
{subtitles?.map((sub, i) => {
return (
<>
<Card
sx={{
padding: "10px",
width: "500px",
maxWidth: "100%",
}}
>
<Typography
sx={{
fontSize: "1rem",
}}
>
{sub.filename}
</Typography>
<Spacer height="10px" />
<LanguageSelect
value={sub.language}
onChange={(val: string | null) =>
onChangeValue("language", val, i)
}
/>
</>
<Spacer height="10px" />
<Box
sx={{
justifyContent: "flex-end",
width: "100%",
display: "flex",
}}
>
<Button
onClick={() => {
setSubtitles((prev) => {
const newSubtitles = [...prev];
newSubtitles.splice(i, 1); // Remove 1 item at index i
return newSubtitles;
});
}}
variant="contained"
size="small"
color="secondary"
>
remove
</Button>
</Box>
</Card>
);
})}
</Box>
)}
{value === 1 && (
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "20px",
width: "100%",
alignItems: "center",
}}
>
{mySubtitles?.map((sub, i) => {
return (
<Card
sx={{
padding: "10px",
width: "500px",
maxWidth: "100%",
}}
>
<MySubtitle onDelete={onDelete} sub={sub} />
</Card>
);
})}
</Box>
)}
</DialogContent>
<DialogActions>
<Button
@ -501,46 +659,87 @@ const PublishSubtitles = ({ publishHandler }: PublishSubtitlesProps) => {
// disabled={disableButton}
variant="contained"
>
Publish index
Publish
</Button>
</DialogActions>
</>
</Dialog>
);
};
interface SubProps {
sub: QortalGetMetadata;
onSelect: (subtitle: Subtitle) => void;
currentSubtrack: null | string
currentSubtrack: null | string;
}
const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
const { resource, isLoading } = usePublish(2, "JSON", sub);
console.log("resource", resource);
const isSelected = currentSubtrack === resource?.data?.language
const isSelected = currentSubtrack === resource?.data?.language;
return (
<ButtonBase onClick={() => onSelect(isSelected ? null : resource?.data)} sx={{
<ButtonBase
onClick={() => onSelect(isSelected ? null : resource?.data)}
sx={{
px: 2,
py: 1,
"&:hover": {
backgroundColor: "rgba(255, 255, 255, 0.1)",
},
width: '100%',
justifyContent: 'space-between'
}}>
<Typography
width: "100%",
justifyContent: "space-between",
}}
>
{resource?.data?.language}
</Typography>
{isSelected ? (
<CheckIcon />
) : (
<ArrowForwardIosIcon />
)}
<Typography>{resource?.data?.language}</Typography>
{isSelected ? <CheckIcon /> : <ArrowForwardIosIcon />}
</ButtonBase>
);
};
interface MySubtitleProps {
sub: QortalGetMetadata;
onDelete: (subtitle: QortalGetMetadata) => void;
}
const MySubtitle = ({ sub, onDelete }: MySubtitleProps) => {
const { resource, isLoading } = usePublish(2, "JSON", sub);
console.log("resource", resource);
return (
<Card
sx={{
padding: "10px",
width: "500px",
maxWidth: "100%",
}}
>
<Typography
sx={{
fontSize: "1rem",
}}
>
{resource?.data?.filename}
</Typography>
<Spacer height="10px" />
<Typography sx={{
fontSize: '1rem'
}}>{resource?.data?.language}</Typography>
<Spacer height="10px" />
<Box
sx={{
justifyContent: "flex-end",
width: "100%",
display: "flex",
}}
>
<Button
onClick={() => onDelete(sub)}
variant="contained"
size="small"
color="secondary"
>
delete
</Button>
</Box>
</Card>
);
};
export const SubtitleManager = React.memo(SubtitleManagerComponent);

View File

@ -5,5 +5,12 @@ import { QortalGetMetadata } from "../types/interfaces/resources";
export function useListReturn(listName: string): QortalGetMetadata[] {
const list = useListStore((state) => state.lists[listName]?.items) || [];
return list
const filterOutDeletedResources = useCacheStore((s) => s.filterOutDeletedResources);
const deletedResources = useCacheStore((s) => s.deletedResources);
const temporaryResources = useCacheStore().getTemporaryResources(listName)
const listToDisplay = useMemo(()=> {
return filterOutDeletedResources([...temporaryResources, ...(list || [])])
}, [list, listName, deletedResources, temporaryResources])
return listToDisplay
}

View File

@ -4,8 +4,8 @@ import { QortalGetMetadata, QortalMetadata } from "../types/interfaces/resources
import { base64ToObject, retryTransaction } from "../utils/publish";
import { useGlobal } from "../context/GlobalProvider";
import { ReturnType } from "../components/ResourceList/ResourceListDisplay";
import { useCacheStore } from "../state/cache";
const STORAGE_EXPIRY_DURATION = 5 * 60 * 1000;
interface StoredPublish {
qortalMetadata: QortalMetadata;
data: any;
@ -68,6 +68,8 @@ interface StoredPublish {
const publish = usePublishStore().getPublish(metadata || null, true);
const setPublish = usePublishStore((state)=> state.setPublish)
const getPublish = usePublishStore(state=> state.getPublish)
const setResourceCache = useCacheStore((s) => s.setResourceCache);
const markResourceAsDeleted = useCacheStore((s) => s.markResourceAsDeleted);
const [hasResource, setHasResource] = useState<boolean | null>(null);
const fetchRawData = useCallback(async (item: QortalGetMetadata) => {
@ -85,30 +87,7 @@ interface StoredPublish {
return `qortal_publish_${username}_${appNameHashed}`;
}, [username, appNameHashed]);
useEffect(() => {
if (!username || !appNameHashed) return;
const storageKey = getStorageKey();
if (!storageKey) return;
const storedData: StoredPublish[] = JSON.parse(localStorage.getItem(storageKey) || "[]");
if (Array.isArray(storedData) && storedData.length > 0) {
const now = Date.now();
const validPublishes = storedData.filter((item) => now - item.timestamp < STORAGE_EXPIRY_DURATION);
// ✅ Re-populate the Zustand store only with recent publishes
validPublishes.forEach((publishData) => {
setPublish(publishData.qortalMetadata, {
qortalMetadata: publishData.qortalMetadata,
data: publishData.data
}, Date.now() - publishData.timestamp);
});
// ✅ Re-store only valid (non-expired) publishes
localStorage.setItem(storageKey, JSON.stringify(validPublishes));
}
}, [username, appNameHashed, getStorageKey, setPublish]);
const fetchPublish = useCallback(
async (
@ -121,6 +100,7 @@ interface StoredPublish {
if (metadata) {
setIsLoading(true);
}
const hasCache = getPublish(metadataProp)
if(hasCache){
@ -139,6 +119,7 @@ interface StoredPublish {
if(metadata){
setHasResource(true)
setError(null)
setPublish(metadataProp, hasCache);
}
return {
resource: hasCache,
@ -240,30 +221,11 @@ interface StoredPublish {
});
if (res?.signature) {
const storageKey = getStorageKey();
if (storageKey) {
const existingPublishes = JSON.parse(localStorage.getItem(storageKey) || "[]");
// Remove any previous entries for the same identifier
const updatedPublishes = existingPublishes.filter(
(item: StoredPublish) => item.qortalMetadata.identifier !== publish.identifier && item.qortalMetadata.service !== publish.service && item.qortalMetadata.name !== publish.name
);
// Add the new one with timestamp
updatedPublishes.push({ qortalMetadata: {
...publish,
created: Date.now(),
updated: Date.now(),
size: 32
}, data: "RA==", timestamp: Date.now() });
// Save back to storage
localStorage.setItem(storageKey, JSON.stringify(updatedPublishes));
}
setPublish(publish, null);
setError(null)
setIsLoading(false)
setHasResource(false)
markResourceAsDeleted(publish)
return true;
}
}, [getStorageKey]);
@ -279,27 +241,16 @@ interface StoredPublish {
updated: Date.now(),
size: 100
}, data});
const storageKey = getStorageKey();
if (storageKey) {
const existingPublishes = JSON.parse(localStorage.getItem(storageKey) || "[]");
// Remove any previous entries for the same identifier
const updatedPublishes = existingPublishes.filter(
(item: StoredPublish) => item.qortalMetadata.identifier !== publish.identifier && item.qortalMetadata.service !== publish.service && item.qortalMetadata.name !== publish.name
);
// Add the new one with timestamp
updatedPublishes.push({ qortalMetadata: {
setResourceCache(
`${publish?.service}-${publish?.name}-${publish?.identifier}`,
{qortalMetadata: {
...publish,
created: Date.now(),
updated: Date.now(),
size: 100
}, data, timestamp: Date.now() });
}, data}
);
// Save back to storage
localStorage.setItem(storageKey, JSON.stringify(updatedPublishes));
}
}, [getStorageKey, setPublish]);

View File

@ -10,6 +10,7 @@ import { base64ToUint8Array, uint8ArrayToObject } from "../utils/base64";
import { retryTransaction } from "../utils/publish";
import { ReturnType } from "../components/ResourceList/ResourceListDisplay";
import { useListStore } from "../state/lists";
import { usePublishStore } from "../state/publishes";
export const requestQueueProductPublishes = new RequestQueueWithPromise(20);
export const requestQueueProductPublishesBackup = new RequestQueueWithPromise(
@ -20,7 +21,7 @@ export interface Resource {
qortalMetadata: QortalMetadata;
data: any;
}
export const useResources = (retryAttempts: number = 2) => {
export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => {
const setSearchCache = useCacheStore((s) => s.setSearchCache);
const getSearchCache = useCacheStore((s) => s.getSearchCache);
const getResourceCache = useCacheStore((s) => s.getResourceCache);
@ -29,6 +30,7 @@ export const useResources = (retryAttempts: number = 2) => {
const markResourceAsDeleted = useCacheStore((s) => s.markResourceAsDeleted);
const setSearchParamsForList = useCacheStore((s) => s.setSearchParamsForList);
const addList = useListStore((s) => s.addList);
const setPublish = usePublishStore((state)=> state.setPublish)
const deleteList = useListStore(state => state.deleteList)
const requestControllers = new Map<string, AbortController>();
@ -240,7 +242,7 @@ export const useResources = (retryAttempts: number = 2) => {
}
responseData = response;
const validResults = responseData.filter((item) => item.size !== 32);
const validResults = responseData.filter((item) => item.size !== 32 && item.size < maxSize);
console.log('validResults', validResults)
filteredResults = [...filteredResults, ...validResults];
@ -258,7 +260,6 @@ export const useResources = (retryAttempts: number = 2) => {
delete copyParams.after
delete copyParams.before
delete copyParams.offset
console.log('listName2', listName, filteredResults)
setSearchCache(listName, cacheKey, filteredResults, cancelRequests ? JSON.stringify(copyParams) : null);
fetchDataFromResults(filteredResults, returnType);
@ -309,7 +310,7 @@ export const useResources = (retryAttempts: number = 2) => {
const addNewResources = useCallback(
(listName: string, resources: Resource[]) => {
console.log('resources1212', resources)
addTemporaryResource(
listName,
resources.map((item) => item.qortalMetadata)
@ -319,6 +320,7 @@ export const useResources = (retryAttempts: number = 2) => {
`${temporaryResource?.qortalMetadata?.service}-${temporaryResource?.qortalMetadata?.name}-${temporaryResource?.qortalMetadata?.identifier}`,
temporaryResource
);
setPublish(temporaryResource?.qortalMetadata, temporaryResource);
});
},
[]
@ -330,6 +332,7 @@ export const useResources = (retryAttempts: number = 2) => {
`${temporaryResource?.qortalMetadata?.service}-${temporaryResource?.qortalMetadata?.name}-${temporaryResource?.qortalMetadata?.identifier}`,
temporaryResource
);
setPublish(temporaryResource?.qortalMetadata, temporaryResource);
});
}, []);