import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import {
QortalGetMetadata,
QortalMetadata,
Service,
} from "../../types/interfaces/resources";
import {
alpha,
Box,
Button,
ButtonBase,
Card,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Divider,
Fade,
IconButton,
Popover,
Skeleton,
Tab,
Tabs,
Typography,
useTheme,
} from "@mui/material";
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";
import { useListStore } from "../../state/lists";
import { Resource, useResources } from "../../hooks/useResources";
import { useGlobal } from "../../context/GlobalProvider";
import { ENTITY_SUBTITLE, SERVICE_SUBTITLE } from "./video-player-constants";
import ISO6391, { LanguageCode } from "iso-639-1";
import LanguageSelect from "./LanguageSelect";
import {
useDropzone,
DropzoneRootProps,
DropzoneInputProps,
} from "react-dropzone";
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";
import {
dismissToast,
showError,
showLoading,
showSuccess,
} from "../../utils/toast";
interface SubtitleManagerProps {
qortalMetadata: QortalGetMetadata;
close: () => void;
open: boolean;
onSelect: (subtitle: SubtitlePublishedData) => void;
subtitleBtnRef: any;
currentSubTrack: null | string;
}
export interface Subtitle {
language: string | null;
base64: string;
type: string;
filename: string;
size: number;
}
export interface SubtitlePublishedData {
language: string | null;
subtitleData: string;
type: string;
filename: string;
size: number;
}
export const languageOptions = ISO6391.getAllCodes().map((code) => ({
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,
close,
onSelect,
subtitleBtnRef,
currentSubTrack,
}: SubtitleManagerProps) => {
const [mode, setMode] = useState(1);
const [isOpenPublish, setIsOpenPublish] = useState(false);
const { lists, identifierOperations, auth } = useGlobal();
const [isLoading, setIsLoading] = useState(false);
const [showAll, setShowAll] = useState(false);
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 {
setIsLoading(true);
const videoId = `${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`;
console.log("videoId", videoId);
const postIdSearch = await identifierOperations.buildSearchPrefix(
ENTITY_SUBTITLE,
videoId
);
let name: string | undefined = qortalMetadata?.name;
if (showAll) {
name = undefined;
}
const searchParams = {
service: SERVICE_SUBTITLE,
identifier: postIdSearch,
name,
limit: 0,
};
const res = await lists.fetchResources(
searchParams,
`subs-${videoId}`,
"BASE64"
);
lists.addList(`subs-${videoId}`, res || []);
console.log("resres2", res);
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}
}, [showAll]);
useEffect(() => {
if (
!qortalMetadata?.identifier ||
!qortalMetadata?.name ||
!qortalMetadata?.service
)
return;
getPublishedSubtitles();
}, [
qortalMetadata?.identifier,
qortalMetadata?.service,
qortalMetadata?.name,
getPublishedSubtitles,
]);
const handleClose = () => {
close();
setMode(1);
// setTitle("");
// setDescription("");
// setHasMetadata(false);
};
const publishHandler = async (subtitles: Subtitle[]) => {
try {
const videoId = `${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`;
const identifier = await identifierOperations.buildIdentifier(
ENTITY_SUBTITLE,
videoId
);
const name = auth?.name;
console.log("identifier2", identifier);
if (!name) return;
const resources: ResourceToPublish[] = [];
const tempResources: { qortalMetadata: QortalMetadata; data: any }[] = [];
for (const sub of subtitles) {
const data = {
subtitleData: sub.base64,
language: sub.language,
filename: sub.filename,
type: sub.type,
};
const base64Data = await objectToBase64(data);
const resource = {
name,
identifier,
service: SERVICE_SUBTITLE,
base64: base64Data,
filename: sub.filename,
title: sub.language || undefined,
};
resources.push(resource);
tempResources.push({
qortalMetadata: {
identifier,
service: SERVICE_SUBTITLE,
name,
size: 100,
created: Date.now(),
},
data: data,
});
}
console.log("resources", resources);
await qortalRequest({
action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
resources,
});
lists.addNewResources(
`subs-${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`,
tempResources
);
} catch (error) {}
};
const onBack = () => {
if (mode === 1) close();
};
const onSelectHandler = (sub: SubtitlePublishedData) => {
console.log("onSelectHandler");
onSelect(sub);
close();
};
const theme = useTheme();
return (
<>
Subtitles
setIsOpenPublish(true)}
>
{isLoading && }
{!isLoading && subtitles?.length === 0 && (
No subtitles
)}
{mode === 1 && !isLoading && subtitles?.length > 0 && (
)}
{/*
{[
'Ambient mode',
'Annotations',
'Subtitles/CC',
'Sleep timer',
'Playback speed',
'Quality',
].map((label) => (
{label}
))}
*/}
>
//
);
};
interface PublisherSubtitlesProps {
publisherName: string;
subtitles: any[];
setMode: (val: number) => void;
onSelect: (subtitle: any) => void;
onBack: () => void;
currentSubTrack: string | null;
}
const PublisherSubtitles = ({
publisherName,
subtitles,
setMode,
onSelect,
onBack,
currentSubTrack,
}: PublisherSubtitlesProps) => {
return (
<>
onSelect(null)}
sx={{
px: 2,
py: 1,
"&:hover": {
backgroundColor: "rgba(255, 255, 255, 0.1)",
},
width: "100%",
justifyContent: "space-between",
}}
>
Off
{!currentSubTrack ? : }
{subtitles?.map((sub) => {
return (
);
})}
>
);
};
interface PublishSubtitlesProps {
publishHandler: (subs: Subtitle[]) => Promise;
isOpen: boolean;
setIsOpen: (val: boolean) => void;
mySubtitles: QortalGetMetadata[];
}
const PublishSubtitles = ({
publishHandler,
isOpen,
setIsOpen,
mySubtitles,
}: PublishSubtitlesProps) => {
const [language, setLanguage] = useState(null);
const [subtitles, setSubtitles] = useState([]);
const [isPublishing, setIsPublishing] = useState(false);
const { lists } = useGlobal();
const theme = useTheme();
const onDrop = useCallback(async (acceptedFiles: File[]) => {
const newSubtitles: Subtitle[] = [];
for (const file of acceptedFiles) {
try {
const newSubtitle = {
base64: await fileToBase64(file),
language: null,
type: file.type,
filename: file.name,
size: file.size,
};
newSubtitles.push(newSubtitle);
} catch (error) {
console.error("Failed to parse audio file:", error);
}
}
setSubtitles((prev) => [...newSubtitles, ...prev]);
}, []);
const {
getRootProps,
getInputProps,
}: {
getRootProps: () => DropzoneRootProps;
getInputProps: () => DropzoneInputProps;
isDragActive: boolean;
} = useDropzone({
onDrop,
accept: {
"application/x-subrip": [".srt"], // SRT subtitles
"text/vtt": [".vtt"], // WebVTT subtitles
},
multiple: true,
maxSize: 2 * 1024 * 1024, // 2MB
});
const onChangeValue = (field: string, data: any, index: number) => {
const sub = subtitles[index];
if (!sub) return;
const copySub = { ...sub, [field]: data };
setSubtitles((prev) => {
const copyPrev = [...prev];
copyPrev[index] = copySub;
return copyPrev;
});
};
console.log("subtitles", subtitles);
const handleClose = () => {
setIsOpen(false);
setSubtitles([]);
};
const [value, setValue] = useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
const onDelete = useCallback(async (sub: QortalGetMetadata) => {
let loadId;
try {
setIsPublishing(true);
loadId = showLoading("Deleting subtitle...");
await lists.deleteResource([sub]);
showSuccess("Deleted subtitle");
} catch (error) {
showError(error instanceof Error ? error.message : "Unable to delete");
} finally {
setIsPublishing(false);
dismissToast(loadId);
}
}, []);
const publishHandlerLocal = async (subtitles: Subtitle[]) => {
let loadId;
try {
setIsPublishing(true);
loadId = showLoading("Publishing subtitles...");
await publishHandler(subtitles);
showSuccess("Subtitles published");
setSubtitles([]);
} catch (error) {
showError(error instanceof Error ? error.message : "Unable to publish");
} finally {
dismissToast(loadId);
setIsPublishing(false);
}
};
const disableButton =
!!subtitles.find((sub) => !sub?.language) || isPublishing;
return (
);
};
interface SubProps {
sub: QortalGetMetadata;
onSelect: (subtitle: Subtitle) => void;
currentSubtrack: null | string;
}
const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
const { resource, isLoading, error } = usePublish(2, "JSON", sub);
console.log("resource", resource);
const isSelected = currentSubtrack === resource?.data?.language;
return (
onSelect(isSelected ? null : resource?.data)}
sx={{
px: 2,
py: 1,
"&:hover": {
backgroundColor: "rgba(255, 255, 255, 0.1)",
},
width: "100%",
justifyContent: "space-between",
}}
>
{isLoading && }
{!isLoading && !error && (
<>
{resource?.data?.language}
{isSelected ? : }
>
)}
);
};
interface MySubtitleProps {
sub: QortalGetMetadata;
onDelete: (subtitle: QortalGetMetadata) => void;
}
const MySubtitle = ({ sub, onDelete }: MySubtitleProps) => {
const { resource, isLoading, error } = usePublish(2, "JSON", sub);
console.log("resource", resource);
return (
{resource?.data?.filename}
{resource?.data?.language}
);
};
export const SubtitleManager = React.memo(SubtitleManagerComponent);