diff --git a/src/components/VideoPlayer/LanguageSelect.tsx b/src/components/VideoPlayer/LanguageSelect.tsx index af1dbcd..c38fa7c 100644 --- a/src/components/VideoPlayer/LanguageSelect.tsx +++ b/src/components/VideoPlayer/LanguageSelect.tsx @@ -12,22 +12,47 @@ export default function LanguageSelector({ onChange: (value: string | null) => void; }) { return ( - `${option.name} (${option.code})`} - value={languageOptions.find((opt) => opt.code === value) || null} - onChange={(event, newValue) => onChange(newValue?.code || null)} - renderInput={(params) => } - isOptionEqualToValue={(option, val) => option.code === val.code} - sx={{ width: 300 }} - slotProps={{ + `${option.name} (${option.code})`} + value={languageOptions.find((opt) => opt.code === value) || null} + onChange={(event, newValue) => onChange(newValue?.code || null)} + renderInput={(params) => ( + + )} + isOptionEqualToValue={(option, val) => option.code === val.code} + 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 + }, }, }, }} +/> - /> ); } diff --git a/src/components/VideoPlayer/SubtitleManager.tsx b/src/components/VideoPlayer/SubtitleManager.tsx index 62f9c12..01720a3 100644 --- a/src/components/VideoPlayer/SubtitleManager.tsx +++ b/src/components/VideoPlayer/SubtitleManager.tsx @@ -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,93 +210,95 @@ const SubtitleManagerComponent = ({ }; const onSelectHandler = (sub: SubtitlePublishedData) => { - console.log('onSelectHandler') + console.log("onSelectHandler"); onSelect(sub); close(); }; return ( - + - - - - - - - Subtitles - - - - + + + + + Subtitles + + + setIsOpenPublish(true)} + > + + + + + {mode === 1 && ( + - - - - {mode === 1 && ( - - )} - {/* + )} + {/* {[ 'Ambient mode', 'Annotations', @@ -303,7 +322,15 @@ const SubtitleManagerComponent = ({ ))} */} - + + + + // 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); const [subtitles, setSubtitles] = useState([]); + 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 ( - <> + + My Subtitles + ({ + position: "absolute", + right: 8, + top: 8, + })} + > + + - + + + + + + + + + {value === 0 && ( + @@ -483,17 +577,81 @@ const PublishSubtitles = ({ publishHandler }: PublishSubtitlesProps) => { {subtitles?.map((sub, i) => { return ( - <> + + + {sub.filename} + + onChangeValue("language", val, i) } /> - + + + + + ); })} + )} + {value === 1 && ( + + + {mySubtitles?.map((sub, i) => { + return ( + + + + ); + })} + + )} - + ); }; 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 ( - onSelect(isSelected ? null : resource?.data)} sx={{ + onSelect(isSelected ? null : resource?.data)} + sx={{ px: 2, py: 1, "&:hover": { backgroundColor: "rgba(255, 255, 255, 0.1)", }, - width: '100%', - justifyContent: 'space-between' - }}> - - {resource?.data?.language} - - {isSelected ? ( - - ) : ( - - )} - + {resource?.data?.language} + {isSelected ? : } ); }; +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 ( + + + {resource?.data?.filename} + + + {resource?.data?.language} + + + + + + + ); +}; + export const SubtitleManager = React.memo(SubtitleManagerComponent); diff --git a/src/hooks/useListData.tsx b/src/hooks/useListData.tsx index 97502ec..96c73ce 100644 --- a/src/hooks/useListData.tsx +++ b/src/hooks/useListData.tsx @@ -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 } diff --git a/src/hooks/usePublish.tsx b/src/hooks/usePublish.tsx index 6aa6980..d7b43e3 100644 --- a/src/hooks/usePublish.tsx +++ b/src/hooks/usePublish.tsx @@ -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(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,8 +100,9 @@ interface StoredPublish { if (metadata) { setIsLoading(true); } - const hasCache = getPublish(metadataProp) - + + const hasCache = getPublish(metadataProp) + if(hasCache){ if(hasCache?.qortalMetadata.size === 32){ if(metadata){ @@ -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: { - ...publish, + 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]); diff --git a/src/hooks/useResources.tsx b/src/hooks/useResources.tsx index fd25be4..85e7e46 100644 --- a/src/hooks/useResources.tsx +++ b/src/hooks/useResources.tsx @@ -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(); @@ -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); }); }, []);