added qortal multi publish status

This commit is contained in:
PhilReact 2025-06-20 20:18:32 +03:00
parent 979c6d4265
commit 378c80cf3c
12 changed files with 393 additions and 260 deletions

View File

@ -1,6 +1,6 @@
{
"name": "qapp-core",
"version": "1.0.31",
"version": "1.0.32",
"description": "Qortal's core React library with global state, UI components, and utilities",
"main": "dist/index.js",
"module": "dist/index.mjs",
@ -49,7 +49,6 @@
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^7.0.1",
"@mui/material": "^7.0.1",
"mediainfo.js": "^0.3.5",
"react": "^19.0.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.2"

View File

@ -13,157 +13,307 @@ import {
Typography,
Box,
LinearProgress,
Stack
Stack,
DialogActions,
Button
} from '@mui/material';
import { PublishStatus, useMultiplePublishStore, usePublishStatusStore } from "../../state/multiplePublish";
import { ResourceToPublish } from "../../types/qortalRequests/types";
import { QortalGetMetadata } from "../../types/interfaces/resources";
import ErrorIcon from '@mui/icons-material/Error';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
const MultiPublishDialogComponent = () => {
const { resources, isPublishing } = useMultiplePublishStore((state) => ({
resources: state.resources,
isPublishing: state.isPublishing
}));
const { publishStatus, setPublishStatusByKey, getPublishStatusByKey } = usePublishStatusStore();
useEffect(() => {
function handleNavigation(event: any) {
if (event.data?.action === 'PUBLISH_STATUS') {
const data = event.data;
console.log('datadata', data)
// Validate required structure before continuing
if (
!data.publishLocation ||
typeof data.publishLocation.name !== 'string' ||
typeof data.publishLocation.identifier !== 'string' ||
typeof data.publishLocation.service !== 'string'
) {
console.warn('Invalid PUBLISH_STATUS data, skipping:', data);
return;
export interface MultiplePublishError {
error: {
unsuccessfulPublishes: any[]
}
}
const { publishLocation, chunks, totalChunks } = data;
const key = `${publishLocation?.service}-${publishLocation?.name}-${publishLocation?.identifier}`;
export const MultiPublishDialogComponent = () => {
const {
resources,
isPublishing,
failedResources,
reset,
reject,
error: publishError,
isLoading,
setIsLoading,
setError,
setFailedPublishResources,
complete
} = useMultiplePublishStore((state) => ({
resources: state.resources,
isPublishing: state.isPublishing,
failedResources: state.failedResources,
reset: state.reset,
reject: state.reject,
error: state.error,
isLoading: state.isLoading,
setIsLoading: state.setIsLoading,
setError: state.setError,
setFailedPublishResources: state.setFailedPublishResources,
complete: state.complete
}));
try {
const dataToBeSent: any = {};
if (chunks !== undefined && chunks !== null) {
dataToBeSent.chunks = chunks;
}
if (totalChunks !== undefined && totalChunks !== null) {
dataToBeSent.totalChunks = totalChunks;
}
setPublishStatusByKey(key, {
publishLocation,
...dataToBeSent,
processed: data?.processed || false
});
} catch (err) {
console.error('Failed to set publish status:', err);
}
}
const { publishStatus, setPublishStatusByKey } = usePublishStatusStore();
const resourcesToPublish = useMemo(() => {
return resources.filter((item) =>
failedResources.some((f) =>
f?.name === item?.name && f?.identifier === item?.identifier && f?.service === item?.service
)
);
}, [resources, failedResources]);
const publishMultipleResources = useCallback(async () => {
const timeout = resources.length * 1200000;
try {
resourcesToPublish.forEach((item) => {
const key = `${item?.service}-${item?.name}-${item?.identifier}`;
setPublishStatusByKey(key, {
error: undefined,
chunks: 0,
totalChunks: 0
});
});
setIsLoading(true);
setError(null);
setFailedPublishResources([]);
const result = await qortalRequestWithTimeout(
{ action: 'PUBLISH_MULTIPLE_QDN_RESOURCES', resources: resourcesToPublish },
timeout
);
complete(result);
} catch (error: any) {
const unPublished = error?.error?.unsuccessfulPublishes;
const failedPublishes: QortalGetMetadata[] = [];
if (unPublished && Array.isArray(unPublished)) {
unPublished.forEach((item) => {
const key = `${item?.service}-${item?.name}-${item?.identifier}`;
setPublishStatusByKey(key, {
error: { reason: item?.reason }
});
failedPublishes.push({
name: item?.name,
service: item?.service,
identifier: item?.identifier
});
});
setFailedPublishResources(failedPublishes);
} else {
setError(error instanceof Error ? error.message : 'Error during publish');
}
} finally {
setIsLoading(false);
}
}, [resourcesToPublish, resources, setPublishStatusByKey, setIsLoading, setError, setFailedPublishResources, complete]);
const handleNavigation = useCallback((event: any) => {
if (event.data?.action !== 'PUBLISH_STATUS') return;
const data = event.data;
if (
!data.publishLocation ||
typeof data.publishLocation?.name !== 'string' ||
typeof data.publishLocation?.service !== 'string'
) {
console.warn('Invalid PUBLISH_STATUS data, skipping:', data);
return;
}
window.addEventListener("message", handleNavigation);
const {
publishLocation,
chunks,
totalChunks,
retry,
filename,
processed
} = data;
return () => {
window.removeEventListener("message", handleNavigation);
const key = `${publishLocation?.service}-${publishLocation?.name}-${publishLocation?.identifier}`;
const update: any = {
publishLocation,
processed: processed || false
};
}, []);
if(!isPublishing) return null
if (chunks != null && chunks !== undefined) update.chunks = chunks;
if (totalChunks != null && totalChunks !== undefined) update.totalChunks = totalChunks;
if (retry != null && retry !== undefined) update.retry = retry;
if (filename != null && retry !== undefined) update.filename = filename;
try {
setPublishStatusByKey(key, update);
} catch (err) {
console.error('Failed to set publish status:', err);
}
}, [setPublishStatusByKey]);
useEffect(() => {
window.addEventListener("message", handleNavigation);
return () => window.removeEventListener("message", handleNavigation);
}, [handleNavigation]);
if (!isPublishing) return null;
return (
<Dialog open={isPublishing} fullWidth maxWidth="sm">
<Dialog
open={isPublishing}
fullWidth
maxWidth="sm"
sx={{ zIndex: 999990 }}
slotProps={{ paper: { elevation: 0 } }}
>
<DialogTitle>Publishing Status</DialogTitle>
<DialogContent>
<Stack spacing={3}>
{resources.map((publish: ResourceToPublish) => {
const key = `${publish?.service}-${publish?.name}-${publish?.identifier}`;
const individualPublishStatus = publishStatus[key] || null
return (
<IndividualResourceComponent key={key} publishKey={key} publish={publish} publishStatus={individualPublishStatus} />
);
})}
</Stack>
{publishError && (
<Stack spacing={3}>
<Box mt={2} sx={{ display: 'flex', gap: '5px', alignItems: 'center' }}>
<ErrorIcon color="error" />
<Typography variant="body2">{publishError}</Typography>
</Box>
</Stack>
)}
{!publishError && (
<Stack spacing={3}>
{resources.map((publish) => {
const key = `${publish?.service}-${publish?.name}-${publish?.identifier}`;
const individualPublishStatus = publishStatus[key] || null;
return (
<IndividualResourceComponent
key={key}
publishKey={key}
publish={publish}
publishStatus={individualPublishStatus}
/>
);
})}
</Stack>
)}
</DialogContent>
<DialogActions>
<Button disabled={isLoading} color="error" variant="contained" onClick={() => {
reject(new Error('Canceled Publish'));
reset();
}}>
Close
</Button>
{failedResources?.length > 0 && (
<Button
disabled={isLoading || resourcesToPublish.length === 0}
color="success"
variant="contained"
onClick={publishMultipleResources}
>
Retry
</Button>
)}
</DialogActions>
</Dialog>
);
};
interface IndividualResourceComponentProps {
publish: ResourceToPublish
publishStatus: PublishStatus
publishKey: string
}
const ESTIMATED_PROCESSING_MS = 5 * 60 * 1000; // 5 minutes
const IndividualResourceComponent = ({publish, publishKey, publishStatus}: IndividualResourceComponentProps)=> {
const [now, setNow] = useState(Date.now());
console.log('key500',publishKey, publishStatus)
const IndividualResourceComponent = ({ publish, publishKey, publishStatus }: IndividualResourceComponentProps) => {
const [now, setNow] = useState(Date.now());
const [processingStart, setProcessingStart] = useState<number | undefined>();
const chunkPercent = useMemo(()=> {
if(!publishStatus?.chunks || !publishStatus?.totalChunks) return 0
return (publishStatus?.chunks / publishStatus?.totalChunks) * 100
}, [publishStatus])
console.log('chunkPercent', chunkPercent)
const chunkDone = useMemo(()=> {
if(!publishStatus?.chunks || !publishStatus?.totalChunks) return false
return publishStatus?.chunks === publishStatus?.totalChunks
}, [publishStatus])
const chunkPercent = useMemo(() => {
if (!publishStatus?.chunks || !publishStatus?.totalChunks) return 0;
return (publishStatus.chunks / publishStatus.totalChunks) * 100;
}, [publishStatus?.chunks, publishStatus?.totalChunks]);
useEffect(() => {
if(!chunkDone) return
const interval = setInterval(() => {
setNow(Date.now());
}, 1000);
const chunkDone = useMemo(() => {
return (
publishStatus?.chunks > 0 &&
publishStatus?.totalChunks > 0 &&
publishStatus?.chunks === publishStatus?.totalChunks
);
}, [publishStatus?.chunks, publishStatus?.totalChunks]);
// Start processing timer once chunking completes
useEffect(() => {
if (chunkDone && !processingStart) {
setProcessingStart(Date.now());
}
}, [chunkDone, processingStart]);
// Keep time ticking for progress simulation
useEffect(() => {
if (!chunkDone) return;
const interval = setInterval(() => setNow(Date.now()), 1000);
return () => clearInterval(interval);
}, [chunkDone]);
const [processingStart, setProcessingStart] = useState<number | undefined>();
useEffect(() => {
if (chunkDone && !processingStart) {
setProcessingStart(Date.now());
}
}, [chunkDone, processingStart]);
const processingPercent = useMemo(() => {
if (!chunkDone || !processingStart || !publishStatus?.totalChunks || !now) return 0;
if (publishStatus?.error || !chunkDone || !processingStart || !publishStatus?.totalChunks || !now) return 0;
const totalMB = publishStatus.totalChunks * 5;
const estimatedProcessingMs = (300_000 / 2048) * totalMB;
const totalMB = publishStatus.totalChunks * 5; // assume 5MB per chunk
const estimatedProcessingMs = (300_000 / 2048) * totalMB; // 5min per 2GB scaled
const elapsed = now - processingStart;
if(!elapsed || elapsed < 0) return 0
return Math.min((elapsed / estimatedProcessingMs) * 100, 100);
}, [chunkDone, processingStart, now, publishStatus?.totalChunks]);
const elapsed = now - processingStart;
if (elapsed <= 0) return 0;
return Math.min((elapsed / estimatedProcessingMs) * 100, 100);
}, [chunkDone, processingStart, now, publishStatus?.totalChunks, publishStatus?.error]);
return (
<Box p={1} border={1} borderColor="divider" borderRadius={2}>
<Typography variant="subtitle1" fontWeight="bold">
{publishKey}
</Typography>
<Box p={1} border={1} borderColor="divider" borderRadius={2}>
<Typography variant="subtitle1" fontWeight="bold">
{publish?.filename || publishStatus?.filename || publishKey}
</Typography>
<Box mt={2}>
<Typography variant="body2" gutterBottom>
File Chunk { (!publishStatus?.chunks || !publishStatus?.totalChunks) ? '' : publishStatus?.chunks}/{publishStatus?.totalChunks} ({(chunkPercent).toFixed(0)}%)
</Typography>
<LinearProgress variant="determinate" value={chunkPercent} />
</Box>
<Box mt={2}>
<Typography variant="body2" gutterBottom>
File Chunk {publishStatus?.chunks || 0}/{publishStatus?.totalChunks || 0} ({chunkPercent.toFixed(0)}%)
</Typography>
<LinearProgress variant="determinate" value={chunkPercent} />
</Box>
<Box mt={2}>
<Typography variant="body2" gutterBottom>
File Processing ({processingStart ? processingPercent.toFixed(0) : '0'}%)
</Typography>
<LinearProgress variant="determinate" value={processingPercent} />
</Box>
{publishStatus?.processed && (
<Box mt={2} display="flex" gap={1} alignItems="center">
<CheckCircleIcon color="success" />
<Typography variant="body2">Published successfully</Typography>
</Box>
)}
{publishStatus?.retry && !publishStatus?.error && !publishStatus?.processed && (
<Box mt={2} display="flex" gap={1} alignItems="center">
<ErrorIcon color="error" />
<Typography variant="body2">Publish failed. Attempting retry...</Typography>
</Box>
)}
{publishStatus?.error && !publishStatus?.processed && (
<Box mt={2} display="flex" gap={1} alignItems="center">
<ErrorIcon color="error" />
<Typography variant="body2">
Publish failed. {publishStatus?.error?.reason || 'Unknown error'}
</Typography>
</Box>
)}
</Box>
);
};
<Box mt={2}>
<Typography variant="body2" gutterBottom>
File Processing ({(!processingPercent || !processingStart) ? '0' : processingPercent.toFixed(0)}%)
</Typography>
<LinearProgress variant="determinate" value={processingPercent} />
</Box>
</Box>
)
}
export const MultiPublishDialog = React.memo(MultiPublishDialogComponent);

View File

@ -119,17 +119,14 @@ const SubtitleManagerComponent = ({
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 {
setIsLoading(true);
const videoId = `${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`;
console.log("videoId", videoId);
const postIdSearch = await identifierOperations.buildLooseSearchPrefix(
ENTITY_SUBTITLE,
videoId
@ -149,7 +146,6 @@ const SubtitleManagerComponent = ({
searchParams
);
lists.addList(`subs-${videoId}`, res?.filter((item)=> !!item?.metadata?.title) || []);
console.log("resres2", res);
} catch (error) {
console.error(error);
} finally {
@ -174,7 +170,6 @@ const SubtitleManagerComponent = ({
]);
const ref = useRef<any>(null)
console.log('isOpen', open)
useEffect(()=> {
if(open){
@ -184,7 +179,6 @@ const SubtitleManagerComponent = ({
const handleBlur = (e: React.FocusEvent) => {
if (!e.currentTarget.contains(e.relatedTarget) && !isOpenPublish) {
console.log('handleBlur')
close();
setIsOpenPublish(false)
}
@ -235,7 +229,6 @@ const SubtitleManagerComponent = ({
data: data,
});
}
console.log("resources", resources);
await qortalRequest({
action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
@ -253,7 +246,6 @@ const SubtitleManagerComponent = ({
};
const onSelectHandler = (sub: SubtitlePublishedData) => {
console.log("onSelectHandler");
onSelect(sub);
close();
};
@ -608,7 +600,6 @@ const PublishSubtitles = ({
return copyPrev;
});
};
console.log("subtitles", subtitles);
const handleClose = () => {
setIsOpen(false);
@ -843,7 +834,6 @@ const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
const [selectedToDownload, setSelectedToDownload] = useState<null | QortalGetMetadata>(null)
const [isReady, setIsReady] = useState(false)
const { resource, isLoading, error, refetch } = usePublish(2, "JSON", sub, true);
console.log("resource", resource, isLoading);
const isSelected = currentSubtrack === resource?.data?.language;
const [isGettingStatus, setIsGettingStatus] = useState(true)
// useEffect(()=> {
@ -852,7 +842,6 @@ const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
// onSelect(resource?.data)
// }
// }, [isSelected, resource?.data])
console.log('isReady', resource?.data)
const getStatus = useCallback(async (service: Service, name: string, identifier: string)=> {
try {
if(subtitlesStatus[`${service}-${name}-${identifier}`]){
@ -890,7 +879,6 @@ const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
getStatus(sub?.service, sub?.name, sub?.identifier)
}
}, [sub?.identifier, sub?.name, sub?.service])
console.log('tester', isReady,isLoading,error,resource?.data)
return (
<ButtonBase

View File

@ -92,7 +92,6 @@ export const ProgressSlider = ({ progress, duration, playerRef }: any) => {
const x = e.clientX - rect.left;
const percent = x / rect.width;
const time = Math.min(Math.max(0, percent * duration), duration);
console.log("hello100");
setHoverX(e.clientX);
setShowDuration(time);
@ -129,7 +128,6 @@ export const ProgressSlider = ({ progress, duration, playerRef }: any) => {
console.log("thumbnailUrl", thumbnailUrl, hoverX);
}
console.log("duration", duration);
return (
<Box
@ -445,7 +443,6 @@ interface PlayBackMenuProps {
export const PlayBackMenu = ({close, onSelect, isOpen, playbackRate}: PlayBackMenuProps)=> {
const theme = useTheme()
const ref = useRef<any>(null)
console.log('isOpen', isOpen)
useEffect(()=> {
if(isOpen){

View File

@ -47,7 +47,6 @@ export async function srtBase64ToVttBlobUrl(
// Step 2: Create a Blob from the Uint8Array with correct MIME type
const srtBlob = new Blob([bytes], { type: "application/x-subrip" });
console.log("srtBlob", srtBlob);
// Step 3: Use convert() with the Blob
const vttBlobUrl: string = await convert(srtBlob);
return vttBlobUrl;
@ -93,14 +92,7 @@ const videoStyles = {
video: {},
};
async function loadMediaInfo(wasmPath = "/MediaInfoModule.wasm") {
const mediaInfoModule = await import("mediainfo.js");
return await mediaInfoModule.default({
format: "JSON",
full: true,
locateFile: () => wasmPath,
});
}
async function getVideoMimeTypeFromUrl(
qortalVideoResource: any
@ -114,71 +106,6 @@ async function getVideoMimeTypeFromUrl(
} catch (error) {
return null;
}
// const mediaInfo = await loadMediaInfo();
// const chunkCache = new Map<string, Uint8Array>();
// let fileSize = 0;
// try {
// const headResp = await fetch(videoUrl, { method: 'HEAD' });
// const lengthHeader = headResp.headers.get('Content-Length');
// if (!lengthHeader) throw new Error('Missing content length');
// fileSize = parseInt(lengthHeader, 10);
// } catch (err) {
// console.error('Error fetching content length:', err);
// return null;
// }
// try {
// const rawResult = await mediaInfo.analyzeData(
// () => fileSize,
// async (chunkSize: number, offset: number): Promise<Uint8Array> => {
// const key = `${offset}:${chunkSize}`;
// if (chunkCache.has(key)) return chunkCache.get(key)!;
// const end = Math.min(fileSize - 1, offset + chunkSize - 1);
// const resp = await fetch(videoUrl, {
// headers: { Range: `bytes=${offset}-${end}` },
// });
// if (!resp.ok || (resp.status !== 206 && fileSize > chunkSize)) {
// console.warn(`Range request failed: ${resp.status}`);
// return new Uint8Array();
// }
// const blob = await resp.blob();
// const buffer = new Uint8Array(await blob.arrayBuffer());
// chunkCache.set(key, buffer);
// return buffer;
// }
// );
// const result = JSON.parse(rawResult);
// const tracks = result?.media?.track;
// const videoTrack = tracks?.find((t: any) => t['@type'] === 'Video');
// const format = videoTrack?.Format?.toLowerCase();
// switch (format) {
// case 'avc':
// case 'h264':
// case 'mpeg-4':
// case 'mp4':
// return 'video/mp4';
// case 'vp8':
// case 'vp9':
// return 'video/webm';
// case 'hevc':
// case 'h265':
// return 'video/mp4'; // still usually wrapped in MP4
// case 'matroska':
// return 'video/webm';
// default:
// return 'video/mp4'; // fallback
// }
// } catch (err) {
// console.error('Error analyzing media info:', err);
// return null;
// }
}
export const VideoPlayer = ({
@ -259,7 +186,6 @@ export const VideoPlayer = ({
}
},[location])
console.log('isFullscreen', isFullscreen)
const { getProgress } = useProgressStore();
const enterFullscreen = useCallback(() => {
@ -276,7 +202,6 @@ export const VideoPlayer = ({
}, [isFullscreen]);
const toggleFullscreen = useCallback(() => {
console.log('togglefull', isFullscreen)
isFullscreen ? exitFullscreen() : enterFullscreen();
}, [isFullscreen]);
@ -531,7 +456,6 @@ const videoLocationRef = useRef< null | string>(null)
return
}
console.log("onSelectSubtitle", subtitle);
const player = playerRef.current;
if (!player || !subtitle.subtitleData || !subtitle.type) return;
@ -604,16 +528,13 @@ const videoLocationRef = useRef< null | string>(null)
}, 1000);
});
const tracksInfo = playerRef.current?.textTracks();
console.log("tracksInfo", tracksInfo);
if (!tracksInfo) return;
const tracks = Array.from(
{ length: (tracksInfo as any).length },
(_, i) => (tracksInfo as any)[i]
);
console.log("tracks", tracks);
for (const track of tracks) {
console.log("track", track);
if (track.kind === "subtitles") {
track.mode = "showing"; // force display
@ -706,7 +627,6 @@ savedVideoRef.current = video.current;
{ length: (tracksInfo as any).length },
(_, i) => (tracksInfo as any)[i]
);
console.log("tracks", tracks);
for (const track of tracks) {
if (track.kind === 'subtitles' || track.kind === 'captions') {
@ -718,10 +638,7 @@ savedVideoRef.current = video.current;
}
if (activeTrack) {
console.log("Subtitle active:", {
label: activeTrack.label,
srclang: activeTrack.language || activeTrack.srclang, // srclang for native, language for VTT
});
setCurrentSubTrack(activeTrack.language || activeTrack.srclang)
} else {
setCurrentSubTrack(null)
@ -748,12 +665,10 @@ savedVideoRef.current = video.current;
console.error("useEffect start player", error);
}
return () => {
console.log('hello1002')
const video = savedVideoRef as any
const videoEl = video?.current!;
const player = playerRef.current;
console.log('videohello', videoEl);
const isPlaying = !player?.paused();
if (videoEl && isPlaying && videoLocationRef.current) {

View File

@ -140,7 +140,6 @@ export const useVideoPlayerController = (props: UseVideoControls) => {
const toggleMute = useCallback(() => {
try {
console.log('toggleMute')
const ref = playerRef as any;
if (!ref.current) return;

View File

@ -16,8 +16,8 @@ export const GlobalPipPlayer = () => {
const [playing , setPlaying] = useState(false)
const [hasStarted, setHasStarted] = useState(false)
const playerRef = useRef<any>(null);
const {navigate} = useContext(GlobalContext)
const context = useContext(GlobalContext)
const navigate = context?.navigate
const videoNode = useRef<HTMLVideoElement>(null);
const { setProgress } = useProgressStore();
@ -26,7 +26,6 @@ export const GlobalPipPlayer = () => {
if (!player || typeof player?.currentTime !== "function") return;
const currentTime = player.currentTime();
console.log('videoId', videoId)
if (typeof currentTime === "number" && videoId && currentTime > 0.1) {
setProgress(videoId, currentTime);
}

View File

@ -5,8 +5,9 @@ import { base64ToObject, retryTransaction } from "../utils/publish";
import { useGlobal } from "../context/GlobalProvider";
import { ReturnType } from "../components/ResourceList/ResourceListDisplay";
import { useCacheStore } from "../state/cache";
import { useMultiplePublishStore } from "../state/multiplePublish";
import { useMultiplePublishStore, usePublishStatusStore } from "../state/multiplePublish";
import { ResourceToPublish } from "../types/qortalRequests/types";
import { MultiplePublishError } from "../components/MultiPublish/MultiPublishDialog";
interface StoredPublish {
qortalMetadata: QortalMetadata;
@ -31,7 +32,7 @@ interface StoredPublish {
}>;
updatePublish: (publish: QortalGetMetadata, data: any) => Promise<void>;
deletePublish: (publish: QortalGetMetadata) => Promise<boolean | undefined>;
publishMultipleResources: (resources: ResourceToPublish[])=> void
publishMultipleResources: (resources: ResourceToPublish[])=> Promise<Error | QortalGetMetadata[]>
};
type UsePublishWithoutMetadata = {
@ -42,7 +43,7 @@ interface StoredPublish {
}>;
updatePublish: (publish: QortalGetMetadata, data: any) => Promise<void>;
deletePublish: (publish: QortalGetMetadata) => Promise<boolean | undefined>;
publishMultipleResources: (resources: ResourceToPublish[])=> void
publishMultipleResources: (resources: ResourceToPublish[])=> Promise<Error | QortalGetMetadata[]>
};
@ -77,7 +78,7 @@ interface StoredPublish {
const getPublish = usePublishStore(state=> state.getPublish)
const setResourceCache = useCacheStore((s) => s.setResourceCache);
const markResourceAsDeleted = useCacheStore((s) => s.markResourceAsDeleted);
const setPublishStatusByKey = usePublishStatusStore((s)=> s.setPublishStatusByKey)
const setPublishResources = useMultiplePublishStore((state) => state.setPublishResources);
const resetPublishResources = useMultiplePublishStore((state) => state.reset);
const [hasResource, setHasResource] = useState<boolean | null>(null);
@ -271,19 +272,55 @@ interface StoredPublish {
}, [getStorageKey, setPublish]);
const publishMultipleResources = useCallback(async (resources: ResourceToPublish[]) => {
try {
setPublishResources(resources)
const lengthOfResources = resources?.length;
const lengthOfTimeout = lengthOfResources * 1200000; // Time out in QR, Seconds = 20 Minutes
return await qortalRequestWithTimeout({
const publishMultipleResources = useCallback(async (resources: ResourceToPublish[]): Promise<Error | QortalGetMetadata[]> => {
return new Promise(async (resolve, reject) => {
const store = useMultiplePublishStore.getState();
store.setPublishResources(resources);
store.setIsPublishing(true);
store.setCompletionResolver(resolve);
store.setRejectionResolver(reject);
try {
store.setIsLoading(true);
setPublishResources(resources);
store.setError(null)
store.setFailedPublishResources([])
const lengthOfResources = resources?.length;
const lengthOfTimeout = lengthOfResources * 1200000; // 20 minutes per resource
const result = await qortalRequestWithTimeout({
action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
resources
}, lengthOfTimeout);
} catch (error) {
}
}, [getStorageKey, setPublish]);
store.complete(result);
} catch (error: any) {
const unPublished = error?.error?.unsuccessfulPublishes;
const failedPublishes: QortalGetMetadata[] = []
if (unPublished && Array.isArray(unPublished)) {
unPublished.forEach((item) => {
const key = `${item?.service}-${item?.name}-${item?.identifier}`;
setPublishStatusByKey(key, {
error: {
reason: item.reason
}
});
failedPublishes.push({
name: item?.name,
service: item?.service,
identifier: item?.identifier
})
});
store.setFailedPublishResources(failedPublishes)
} else {
store.setError(error?.message || 'Error during publish')
}
} finally {
store.setIsLoading(false);
}
})
}, [setPublishResources]);
if (!metadata)
return {

View File

@ -207,10 +207,8 @@ export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => {
if (cancelRequests) {
cancelAllRequests();
}
console.log('listName', listName)
const cacheKey = generateCacheKey(params);
const searchCache = getSearchCache(listName, cacheKey);
console.log('searchCache', searchCache)
if (searchCache) {
const copyParams = {...params}
delete copyParams.after
@ -223,12 +221,10 @@ export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => {
let responseData: QortalMetadata[] = [];
let filteredResults: QortalMetadata[] = [];
let lastCreated = params.before || undefined;
console.log('lastCreated', lastCreated)
const targetLimit = params.limit ?? 20; // Use `params.limit` if provided, else default to 20
const isUnlimited = params.limit === 0;
while (isUnlimited || filteredResults.length < targetLimit) {
console.log('beforebefore')
const response = await qortalRequest({
action: "SEARCH_QDN_RESOURCES",
mode: "ALL",
@ -236,14 +232,12 @@ export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => {
limit: targetLimit - filteredResults.length, // Adjust limit dynamically
before: lastCreated,
});
console.log('responseresponse', response)
if (!response || response.length === 0) {
break; // No more data available
}
responseData = response;
const validResults = responseData.filter((item) => item.size !== 32 && item.size < maxSize);
console.log('validResults', validResults)
filteredResults = [...filteredResults, ...validResults];
if (filteredResults.length >= targetLimit && !isUnlimited) {
@ -312,7 +306,6 @@ export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => {
const addNewResources = useCallback(
(listName: string, resources: Resource[]) => {
console.log('resources1212', resources)
addTemporaryResource(
listName,
resources.map((item) => item.qortalMetadata)

View File

@ -1,23 +1,75 @@
import { create } from 'zustand';
import { ResourceToPublish } from '../types/qortalRequests/types';
import { Service } from '../types/interfaces/resources';
import { QortalGetMetadata, Service } from '../types/interfaces/resources';
interface MultiplePublishState {
resources: ResourceToPublish[];
setPublishResources: (resources: ResourceToPublish[])=> void
reset: ()=> void
isPublishing: boolean
}
const initialState = {
resources: [],
isPublishing: false
};
export const useMultiplePublishStore = create<MultiplePublishState>((set) => ({
...initialState,
setPublishResources: (resources: ResourceToPublish[]) => set(() => ({ resources, isPublishing: true })),
reset: () => set(initialState),
failedResources: QortalGetMetadata[];
isPublishing: boolean;
resolveCallback?: (result: QortalGetMetadata[]) => void;
rejectCallback?: (error: Error) => void;
setPublishResources: (resources: ResourceToPublish[]) => void;
setFailedPublishResources: (resources: QortalGetMetadata[]) => void;
setIsPublishing: (value: boolean) => void;
setCompletionResolver: (resolver: (result: QortalGetMetadata[]) => void) => void;
setRejectionResolver: (resolver: (reject: Error) => void) => void;
complete: (result: any) => void;
reject: (Error: Error) => void;
reset: () => void;
setError: (message: string | null)=> void
error: string | null
isLoading: boolean
setIsLoading: (val: boolean)=> void
}
const initialState = {
resources: [],
failedResources: [],
isPublishing: false,
resolveCallback: undefined,
rejectCallback: undefined,
error: "",
isLoading: false
};
export const useMultiplePublishStore = create<MultiplePublishState>((set, get) => ({
...initialState,
setPublishResources: (resources) => {
set({ resources, isPublishing: true });
},
setFailedPublishResources: (resources) => {
set({ failedResources: resources });
},
setIsPublishing: (value) => {
set({ isPublishing: value });
},
setIsLoading: (value) => {
set({ isLoading: value });
},
setCompletionResolver: (resolver) => {
set({ resolveCallback: resolver });
},
setRejectionResolver: (reject) => {
set({ rejectCallback: reject });
},
complete: (result) => {
const resolver = get().resolveCallback;
if (resolver) resolver(result);
set({ resolveCallback: undefined, isPublishing: false });
},
reject: (result) => {
const resolver = get().rejectCallback;
if (resolver) resolver(result);
set({ resolveCallback: undefined, isPublishing: false });
},
setError: (message) => {
set({ error: message });
},
reset: () => set(initialState),
}));
export type PublishLocation = {
@ -31,6 +83,11 @@ export type PublishStatus = {
chunks: number;
totalChunks: number;
processed: boolean;
error?: {
reason: string
}
retry: boolean
filename: string
};
type PublishStatusStore = {
@ -58,6 +115,7 @@ export const usePublishStatusStore = create<PublishStatusStore>((set, get) => ({
chunks: 0,
totalChunks: 0,
processed: false,
retry: false
};
const newStatus: PublishStatus = {

View File

@ -121,7 +121,6 @@ export function base64ToObject(base64: string){
}
export const base64ToBlobUrl = (base64: string, mimeType = 'text/vtt'): string => {
console.log('base64ToBlobUrl', base64, mimeType)
const cleanedBase64 = base64.length % 4 === 0 ? base64 : base64 + '='.repeat(4 - base64.length % 4);
try {

View File

@ -9,7 +9,6 @@ export default defineConfig({
'@mui/material',
'@mui/system',
'@emotion/react',
'@emotion/styled',
'mediainfo.js'
'@emotion/styled'
],
});