mirror of
https://github.com/Qortal/qapp-core.git
synced 2025-06-22 12:21:20 +00:00
started on mutli-publish status
This commit is contained in:
parent
e32f98e1b0
commit
979c6d4265
169
src/components/MultiPublish/MultiPublishDialog.tsx
Normal file
169
src/components/MultiPublish/MultiPublishDialog.tsx
Normal file
@ -0,0 +1,169 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
Typography,
|
||||
Box,
|
||||
LinearProgress,
|
||||
Stack
|
||||
} from '@mui/material';
|
||||
import { PublishStatus, useMultiplePublishStore, usePublishStatusStore } from "../../state/multiplePublish";
|
||||
import { ResourceToPublish } from "../../types/qortalRequests/types";
|
||||
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const { publishLocation, chunks, totalChunks } = data;
|
||||
|
||||
const key = `${publishLocation?.service}-${publishLocation?.name}-${publishLocation?.identifier}`;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleNavigation);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("message", handleNavigation);
|
||||
};
|
||||
}, []);
|
||||
if(!isPublishing) return null
|
||||
return (
|
||||
<Dialog open={isPublishing} fullWidth maxWidth="sm">
|
||||
<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>
|
||||
</DialogContent>
|
||||
</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 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])
|
||||
|
||||
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;
|
||||
|
||||
const totalMB = publishStatus.totalChunks * 5;
|
||||
const estimatedProcessingMs = (300_000 / 2048) * totalMB;
|
||||
|
||||
const elapsed = now - processingStart;
|
||||
if(!elapsed || elapsed < 0) return 0
|
||||
return Math.min((elapsed / estimatedProcessingMs) * 100, 100);
|
||||
}, [chunkDone, processingStart, now, publishStatus?.totalChunks]);
|
||||
|
||||
return (
|
||||
<Box p={1} border={1} borderColor="divider" borderRadius={2}>
|
||||
<Typography variant="subtitle1" fontWeight="bold">
|
||||
{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 Processing ({(!processingPercent || !processingStart) ? '0' : processingPercent.toFixed(0)}%)
|
||||
</Typography>
|
||||
<LinearProgress variant="determinate" value={processingPercent} />
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export const MultiPublishDialog = React.memo(MultiPublishDialogComponent);
|
@ -259,7 +259,7 @@ const SubtitleManagerComponent = ({
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
if(!open) return
|
||||
if(!open) return null
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
Ref,
|
||||
RefObject,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
@ -31,7 +32,7 @@ import convert from "srt-webvtt";
|
||||
import { TimelineActionsComponent } from "./TimelineActionsComponent";
|
||||
import { PlayBackMenu } from "./VideoControls";
|
||||
import { useGlobalPlayerStore } from "../../state/pip";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { LocationContext } from "../../context/GlobalProvider";
|
||||
|
||||
export async function srtBase64ToVttBlobUrl(
|
||||
base64Srt: string
|
||||
@ -212,7 +213,8 @@ export const VideoPlayer = ({
|
||||
const [isOpenSubtitleManage, setIsOpenSubtitleManage] = useState(false);
|
||||
const subtitleBtnRef = useRef(null);
|
||||
const [currentSubTrack, setCurrentSubTrack] = useState<null | string>(null)
|
||||
const location = useLocation();
|
||||
const location = useContext(LocationContext)
|
||||
|
||||
const locationRef = useRef<string | null>(null)
|
||||
const [isOpenPlaybackMenu, setIsOpenPlaybackmenu] = useState(false)
|
||||
const {
|
||||
|
@ -12,7 +12,6 @@ import { useProgressStore, useVideoStore } from "../../state/video";
|
||||
import { QortalGetMetadata } from "../../types/interfaces/resources";
|
||||
import { useResourceStatus } from "../../hooks/useResourceStatus";
|
||||
import useIdleTimeout from "../../common/useIdleTimeout";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useGlobalPlayerStore } from "../../state/pip";
|
||||
|
||||
const controlsHeight = "42px";
|
||||
@ -42,7 +41,6 @@ export const useVideoPlayerController = (props: UseVideoControls) => {
|
||||
const [startPlay, setStartPlay] = useState(false);
|
||||
const [startedFetch, setStartedFetch] = useState(false);
|
||||
const startedFetchRef = useRef(false);
|
||||
const navigate = useNavigate()
|
||||
const { playbackSettings, setPlaybackRate, setVolume } = useVideoStore();
|
||||
const { getProgress } = useProgressStore();
|
||||
|
||||
|
@ -15,6 +15,8 @@ import { IndexManager } from "../components/IndexManager/IndexManager";
|
||||
import { useIndexes } from "../hooks/useIndexes";
|
||||
import { useProgressStore } from "../state/video";
|
||||
import { GlobalPipPlayer } from "../hooks/useGlobalPipPlayer";
|
||||
import { Location, NavigateFunction } from "react-router-dom";
|
||||
import { MultiPublishDialog } from "../components/MultiPublish/MultiPublishDialog";
|
||||
|
||||
// ✅ Define Global Context Type
|
||||
interface GlobalContextType {
|
||||
@ -24,6 +26,7 @@ interface GlobalContextType {
|
||||
identifierOperations: ReturnType<typeof useIdentifiers>;
|
||||
persistentOperations: ReturnType<typeof usePersistentStore>;
|
||||
indexOperations: ReturnType<typeof useIndexes>;
|
||||
navigate: NavigateFunction
|
||||
}
|
||||
|
||||
// ✅ Define Config Type for Hook Options
|
||||
@ -35,17 +38,25 @@ interface GlobalProviderProps {
|
||||
appName: string;
|
||||
publicSalt: string;
|
||||
};
|
||||
navigate: NavigateFunction
|
||||
location: Location
|
||||
toastStyle?: CSSProperties;
|
||||
}
|
||||
|
||||
// ✅ Create Context with Proper Type
|
||||
const GlobalContext = createContext<GlobalContextType | null>(null);
|
||||
export const GlobalContext = createContext<GlobalContextType | null>(null);
|
||||
|
||||
export const LocationContext = createContext<Location | null>(null);
|
||||
|
||||
|
||||
|
||||
// 🔹 Global Provider (Handles Multiple Hooks)
|
||||
export const GlobalProvider = ({
|
||||
children,
|
||||
config,
|
||||
toastStyle = {},
|
||||
navigate,
|
||||
location
|
||||
}: GlobalProviderProps) => {
|
||||
// ✅ Call hooks and pass in options dynamically
|
||||
const auth = useAuth(config?.auth || {});
|
||||
@ -70,8 +81,9 @@ export const GlobalProvider = ({
|
||||
identifierOperations,
|
||||
persistentOperations,
|
||||
indexOperations,
|
||||
navigate
|
||||
}),
|
||||
[auth, lists, appInfo, identifierOperations, persistentOperations]
|
||||
[auth, lists, appInfo, identifierOperations, persistentOperations, navigate]
|
||||
);
|
||||
const { clearOldProgress } = useProgressStore();
|
||||
|
||||
@ -80,8 +92,11 @@ export const GlobalProvider = ({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<LocationContext.Provider value={location}>
|
||||
|
||||
<GlobalContext.Provider value={contextValue}>
|
||||
<GlobalPipPlayer />
|
||||
<MultiPublishDialog />
|
||||
<Toaster
|
||||
position="top-center"
|
||||
toastOptions={{
|
||||
@ -94,6 +109,8 @@ export const GlobalProvider = ({
|
||||
|
||||
{children}
|
||||
</GlobalContext.Provider>
|
||||
</LocationContext.Provider>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
// GlobalVideoPlayer.tsx
|
||||
import videojs from 'video.js';
|
||||
import { useGlobalPlayerStore } from '../state/pip';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { Box, IconButton } from '@mui/material';
|
||||
import { VideoContainer } from '../components/VideoPlayer/VideoPlayer-styles';
|
||||
import { Rnd } from "react-rnd";
|
||||
@ -10,13 +10,14 @@ import CloseIcon from '@mui/icons-material/Close';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import PauseIcon from '@mui/icons-material/Pause';
|
||||
import OpenInFullIcon from '@mui/icons-material/OpenInFull';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { GlobalContext } from '../context/GlobalProvider';
|
||||
export const GlobalPipPlayer = () => {
|
||||
const { videoSrc, reset, isPlaying, location, type, currentTime, mode, videoId } = useGlobalPlayerStore();
|
||||
const [playing , setPlaying] = useState(false)
|
||||
const [hasStarted, setHasStarted] = useState(false)
|
||||
const playerRef = useRef<any>(null);
|
||||
const navigate = useNavigate()
|
||||
const {navigate} = useContext(GlobalContext)
|
||||
|
||||
const videoNode = useRef<HTMLVideoElement>(null);
|
||||
const { setProgress } = useProgressStore();
|
||||
|
||||
@ -279,7 +280,11 @@ const margin = 50;
|
||||
zIndex: 2,
|
||||
opacity: 1,
|
||||
|
||||
}} onClick={()=> navigate(location)}><OpenInFullIcon /></IconButton>
|
||||
}} onClick={()=> {
|
||||
if(navigate){
|
||||
navigate(location)
|
||||
}
|
||||
}}><OpenInFullIcon /></IconButton>
|
||||
)}
|
||||
{playing && (
|
||||
<IconButton sx={{
|
||||
|
@ -5,6 +5,8 @@ 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 { ResourceToPublish } from "../types/qortalRequests/types";
|
||||
|
||||
interface StoredPublish {
|
||||
qortalMetadata: QortalMetadata;
|
||||
@ -29,6 +31,7 @@ interface StoredPublish {
|
||||
}>;
|
||||
updatePublish: (publish: QortalGetMetadata, data: any) => Promise<void>;
|
||||
deletePublish: (publish: QortalGetMetadata) => Promise<boolean | undefined>;
|
||||
publishMultipleResources: (resources: ResourceToPublish[])=> void
|
||||
};
|
||||
|
||||
type UsePublishWithoutMetadata = {
|
||||
@ -39,6 +42,8 @@ interface StoredPublish {
|
||||
}>;
|
||||
updatePublish: (publish: QortalGetMetadata, data: any) => Promise<void>;
|
||||
deletePublish: (publish: QortalGetMetadata) => Promise<boolean | undefined>;
|
||||
publishMultipleResources: (resources: ResourceToPublish[])=> void
|
||||
|
||||
};
|
||||
|
||||
export function usePublish(
|
||||
@ -73,6 +78,8 @@ interface StoredPublish {
|
||||
const setResourceCache = useCacheStore((s) => s.setResourceCache);
|
||||
const markResourceAsDeleted = useCacheStore((s) => s.markResourceAsDeleted);
|
||||
|
||||
const setPublishResources = useMultiplePublishStore((state) => state.setPublishResources);
|
||||
const resetPublishResources = useMultiplePublishStore((state) => state.reset);
|
||||
const [hasResource, setHasResource] = useState<boolean | null>(null);
|
||||
const fetchRawData = useCallback(async (item: QortalGetMetadata) => {
|
||||
const url = `/arbitrary/${item?.service}/${encodeURIComponent(item?.name)}/${encodeURIComponent(item?.identifier)}?encoding=base64`;
|
||||
@ -262,6 +269,20 @@ 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({
|
||||
action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
|
||||
resources
|
||||
}, lengthOfTimeout);
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}, [getStorageKey, setPublish]);
|
||||
|
||||
if (!metadata)
|
||||
@ -269,6 +290,7 @@ interface StoredPublish {
|
||||
fetchPublish,
|
||||
updatePublish,
|
||||
deletePublish: deleteResource,
|
||||
publishMultipleResources
|
||||
};
|
||||
|
||||
return useMemo(() => ({
|
||||
@ -280,6 +302,7 @@ interface StoredPublish {
|
||||
fetchPublish,
|
||||
updatePublish,
|
||||
deletePublish: deleteResource,
|
||||
publishMultipleResources
|
||||
}), [
|
||||
isLoading,
|
||||
error,
|
||||
@ -289,6 +312,7 @@ interface StoredPublish {
|
||||
fetchPublish,
|
||||
updatePublish,
|
||||
deleteResource,
|
||||
publishMultipleResources
|
||||
]);
|
||||
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ interface PropsUseResourceStatus {
|
||||
}
|
||||
export const useResourceStatus = ({
|
||||
resource,
|
||||
retryAttempts = 50,
|
||||
retryAttempts = 200,
|
||||
}: PropsUseResourceStatus) => {
|
||||
const resourceId = !resource ? null : `${resource.service}-${resource.name}-${resource.identifier}`;
|
||||
const status = usePublishStore((state)=> state.getResourceStatus(resourceId)) || null
|
||||
|
79
src/state/multiplePublish.ts
Normal file
79
src/state/multiplePublish.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { create } from 'zustand';
|
||||
import { ResourceToPublish } from '../types/qortalRequests/types';
|
||||
import { 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),
|
||||
|
||||
}));
|
||||
|
||||
export type PublishLocation = {
|
||||
name: string;
|
||||
identifier: string;
|
||||
service: Service;
|
||||
};
|
||||
|
||||
export type PublishStatus = {
|
||||
publishLocation: PublishLocation;
|
||||
chunks: number;
|
||||
totalChunks: number;
|
||||
processed: boolean;
|
||||
};
|
||||
|
||||
type PublishStatusStore = {
|
||||
publishStatus: Record<string, PublishStatus>;
|
||||
getPublishStatusByKey: (key: string) => PublishStatus | undefined;
|
||||
setPublishStatusByKey: (key: string, update: Partial<PublishStatus>) => void;
|
||||
};
|
||||
|
||||
|
||||
export const usePublishStatusStore = create<PublishStatusStore>((set, get) => ({
|
||||
publishStatus: {},
|
||||
|
||||
getPublishStatusByKey: (key) => get().publishStatus[key],
|
||||
|
||||
setPublishStatusByKey: (key, update) => {
|
||||
const current = get().publishStatus;
|
||||
|
||||
const prev: PublishStatus = current[key] ?? {
|
||||
publishLocation: {
|
||||
name: '',
|
||||
identifier: '',
|
||||
service: 'DOCUMENT',
|
||||
processed: false,
|
||||
},
|
||||
chunks: 0,
|
||||
totalChunks: 0,
|
||||
processed: false,
|
||||
};
|
||||
|
||||
const newStatus: PublishStatus = {
|
||||
...prev,
|
||||
...update,
|
||||
publishLocation: {
|
||||
...prev.publishLocation,
|
||||
...(update.publishLocation ?? {}),
|
||||
},
|
||||
};
|
||||
|
||||
set({
|
||||
publishStatus: {
|
||||
...current,
|
||||
[key]: newStatus,
|
||||
},
|
||||
});
|
||||
},
|
||||
}));
|
Loading…
x
Reference in New Issue
Block a user