sanity-plugin-mux-input
Version:
An input component that integrates Sanity Studio with Mux video encoding/hosting service.
1,208 lines (1,207 loc) • 152 kB
JavaScript
import { useClient as useClient$1, createHookFromObservableFactory, useDocumentStore, collate, useDocumentValues, truncateString, useFormattedDuration, SanityDefaultPreview, useTimeAgo, TextWithTone, isRecord, getPreviewStateObservable, getPreviewValueWithFallback, DocumentPreviewPresence, useDocumentPreviewStore, useSchema, useDocumentPresence, PreviewCard, isReference, useProjectId, useDataset, useCurrentUser, PatchEvent, unset, setIfMissing, set, LinearProgress, FormField as FormField$2, definePlugin } from "sanity";
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
import { ErrorOutlineIcon, RetryIcon, CheckmarkCircleIcon, RetrieveIcon, SortIcon, WarningOutlineIcon, EditIcon, PublishIcon, DocumentIcon, TrashIcon, RevertIcon, SearchIcon, ClockIcon, CropIcon, CalendarIcon, TagIcon, CheckmarkIcon, LockIcon, PlayIcon, PlugIcon, EllipsisHorizontalIcon, UploadIcon, ImageIcon, ResetIcon, TranslateIcon, WarningFilledIcon, DocumentVideoIcon } from "@sanity/icons";
import { Card, Box, Spinner, Stack, Text, Checkbox, Button, Dialog, Flex, Heading, Code, MenuButton, Menu, MenuItem, TextInput, Tooltip, Inline, useToast, TabList, Tab, TabPanel, Label as Label$1, Grid, useTheme_v2, useClickOutsideEvent, Popover, MenuDivider, Autocomplete, Radio, rem } from "@sanity/ui";
import React, { useState, useMemo, useEffect, useRef, useId, memo, createContext, useContext, isValidElement, useCallback, useReducer, PureComponent, createElement, forwardRef, Suspense } from "react";
import compact from "lodash/compact.js";
import toLower from "lodash/toLower.js";
import trim from "lodash/trim.js";
import uniq from "lodash/uniq.js";
import words from "lodash/words.js";
import { styled, css } from "styled-components";
import { uuid } from "@sanity/uuid";
import { defer, timer, of, Observable, concat, throwError, from, Subject } from "rxjs";
import { expand, concatMap, tap, switchMap, mergeMap, catchError, mergeMapTo, takeUntil } from "rxjs/operators";
import { suspend, clear, preload } from "suspend-react";
import MuxPlayer from "@mux/mux-player-react";
import { IntentLink } from "sanity/router";
import isNumber from "lodash/isNumber.js";
import isString from "lodash/isString.js";
import { useObservable } from "react-rx";
import useSWR from "swr";
import scrollIntoView from "scroll-into-view-if-needed";
import { UpChunk } from "@mux/upchunk";
import { isValidElementType } from "react-is";
import LanguagesList from "iso-639-1";
const ToolIcon = () => /* @__PURE__ */ jsx(
"svg",
{
stroke: "currentColor",
fill: "currentColor",
strokeWidth: "0",
viewBox: "0 0 24 24",
height: "1em",
width: "1em",
xmlns: "http://www.w3.org/2000/svg",
children: /* @__PURE__ */ jsx("path", { d: "M21 3H3c-1.11 0-2 .89-2 2v12c0 1.1.89 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.11-.9-2-2-2zm0 14H3V5h18v12zm-5-6l-7 4V7z" })
}
), SANITY_API_VERSION = "2024-03-05";
function useClient() {
return useClient$1({ apiVersion: SANITY_API_VERSION });
}
const SPECIAL_CHARS = /([^!@#$%^&*(),\\/?";:{}|[\]+<>\s-])+/g, STRIP_EDGE_CHARS = /(^[.]+)|([.]+$)/;
function tokenize(string) {
return (string.match(SPECIAL_CHARS) || []).map((token) => token.replace(STRIP_EDGE_CHARS, ""));
}
function toGroqParams(terms) {
const params = {};
return terms.reduce((acc, term, i) => (acc[`t${i}`] = `*${term}*`, acc), params);
}
function extractTermsFromQuery(query) {
const quotedQueries = [], unquotedQuery = query.replace(/("[^"]*")/g, (match) => words(match).length > 1 ? (quotedQueries.push(match), "") : match), quotedTerms = quotedQueries.map((str) => trim(toLower(str))), remainingTerms = uniq(compact(tokenize(toLower(unquotedQuery))));
return [...quotedTerms, ...remainingTerms];
}
function createConstraints(terms, includeAssetId) {
const searchPaths = includeAssetId ? ["filename", "assetId"] : ["filename"];
return terms.map((_term, i) => searchPaths.map((joinedPath) => `${joinedPath} match $t${i}`)).filter((constraint) => constraint.length > 0).map((constraint) => `(${constraint.join(" || ")})`);
}
function createSearchFilter(query) {
const terms = extractTermsFromQuery(query);
return {
filter: createConstraints(terms, query.length >= 8),
// if the search is big enough, include the assetId (mux id) in the results
params: {
...toGroqParams(terms)
}
};
}
const ASSET_SORT_OPTIONS = {
createdDesc: { groq: "_createdAt desc", label: "Newest first" },
createdAsc: { groq: "_createdAt asc", label: "First created (oldest)" },
filenameAsc: { groq: "filename asc", label: "By filename (A-Z)" },
filenameDesc: { groq: "filename desc", label: "By filename (Z-A)" }
}, useAssetDocuments = createHookFromObservableFactory(({ documentStore, sort, searchQuery }) => {
const search = createSearchFilter(searchQuery), filter = ['_type == "mux.videoAsset"', ...search.filter].filter(Boolean).join(" && "), sortFragment = ASSET_SORT_OPTIONS[sort].groq;
return documentStore.listenQuery(
/* groq */
`*[${filter}] | order(${sortFragment})`,
search.params,
{
apiVersion: SANITY_API_VERSION
}
);
});
function useAssets() {
const documentStore = useDocumentStore(), [sort, setSort] = useState("createdDesc"), [searchQuery, setSearchQuery] = useState(""), [assetDocuments = [], isLoading] = useAssetDocuments(
useMemo(() => ({ documentStore, sort, searchQuery }), [documentStore, sort, searchQuery])
);
return {
assets: useMemo(
() => (
// Avoid displaying both drafts & published assets by collating them together and giving preference to drafts
collate(assetDocuments).map(
(collated) => ({
...collated.draft || collated.published || {},
_id: collated.id
})
)
),
[assetDocuments]
),
isLoading,
sort,
searchQuery,
setSort,
setSearchQuery
};
}
function parseMuxDate(date) {
return new Date(Number(date) * 1e3);
}
const FIRST_PAGE = 1, ASSETS_PER_PAGE = 100;
async function fetchMuxAssetsPage({ secretKey, token }, pageNum) {
try {
const json = await (await fetch(
`https://api.mux.com/video/v1/assets?limit=${ASSETS_PER_PAGE}&page=${pageNum}`,
{
headers: {
Authorization: `Basic ${btoa(`${token}:${secretKey}`)}`
}
}
)).json();
return json.error ? {
pageNum,
error: {
_tag: "MuxError",
error: json.error
}
} : {
pageNum,
data: json.data
};
} catch {
return {
pageNum,
error: { _tag: "FetchError" }
};
}
}
function accumulateIntermediateState(currentState, pageResult) {
const currentData = "data" in currentState && currentState.data || [];
return {
...currentState,
data: [
...currentData,
...("data" in pageResult && pageResult.data || []).filter(
// De-duplicate assets for safety
(asset) => !currentData.some((a2) => a2.id === asset.id)
)
],
error: "error" in pageResult ? pageResult.error : (
// Reset error if current page is successful
void 0
),
pageNum: pageResult.pageNum,
loading: !0
};
}
function hasMorePages(pageResult) {
return typeof pageResult == "object" && "data" in pageResult && Array.isArray(pageResult.data) && pageResult.data.length > 0;
}
function useMuxAssets({ secrets, enabled }) {
const [state, setState] = useState({ loading: !0, pageNum: FIRST_PAGE });
return useEffect(() => {
if (!enabled) return;
const subscription = defer(
() => fetchMuxAssetsPage(
secrets,
// When we've already successfully loaded before (fully or partially), we start from the following page to avoid re-fetching
"data" in state && state.data && state.data.length > 0 && !state.error ? state.pageNum + 1 : state.pageNum
)
).pipe(
// Here we replace "concatMap" with "expand" to recursively fetch next pages
expand((pageResult) => hasMorePages(pageResult) ? timer(2e3).pipe(
// eslint-disable-next-line max-nested-callbacks
concatMap(() => defer(() => fetchMuxAssetsPage(secrets, pageResult.pageNum + 1)))
) : of()),
// On each iteration, persist intermediate states to give feedback to users
tap(
(pageResult) => setState((prevState) => accumulateIntermediateState(prevState, pageResult))
)
).subscribe({
// Once done, let the user know we've stopped loading
complete: () => {
setState((prev) => ({
...prev,
loading: !1
}));
}
});
return () => subscription.unsubscribe();
}, [enabled]), state;
}
const name = "mux-input", cacheNs = "sanity-plugin-mux-input", muxSecretsDocumentId = "secrets.mux", DIALOGS_Z_INDEX = 6e4, THUMBNAIL_ASPECT_RATIO = 1.7777777777777777, MIN_ASPECT_RATIO = 5 / 4, AUDIO_ASPECT_RATIO = 5 / 1, path$1 = ["token", "secretKey", "enableSignedUrls", "signingKeyId", "signingKeyPrivate"], useSecretsDocumentValues = () => {
const { error, isLoading, value } = useDocumentValues(
muxSecretsDocumentId,
path$1
), cache = useMemo(() => {
const exists = !!value, secrets = {
token: value?.token || null,
secretKey: value?.secretKey || null,
enableSignedUrls: value?.enableSignedUrls || !1,
signingKeyId: value?.signingKeyId || null,
signingKeyPrivate: value?.signingKeyPrivate || null
};
return {
isInitialSetup: !exists,
needsSetup: !secrets?.token || !secrets?.secretKey,
secrets
};
}, [value]);
return { error, isLoading, value: cache };
};
function useImportMuxAssets() {
const documentStore = useDocumentStore(), client = useClient$1({
apiVersion: SANITY_API_VERSION
}), [assetsInSanity, assetsInSanityLoading] = useAssetsInSanity(documentStore), secretDocumentValues = useSecretsDocumentValues(), hasSecrets = !!secretDocumentValues.value.secrets?.secretKey, [importError, setImportError] = useState(), [importState, setImportState] = useState("closed"), dialogOpen = importState !== "closed", muxAssets = useMuxAssets({
secrets: secretDocumentValues.value.secrets,
enabled: hasSecrets && dialogOpen
}), missingAssets = useMemo(() => assetsInSanity && muxAssets.data ? muxAssets.data.filter((a2) => !assetExistsInSanity(a2, assetsInSanity)) : void 0, [assetsInSanity, muxAssets.data]), [selectedAssets, setSelectedAssets] = useState([]), closeDialog = () => {
importState !== "importing" && setImportState("closed");
}, openDialog = () => {
importState === "closed" && setImportState("idle");
};
async function importAssets() {
setImportState("importing");
const documents = selectedAssets.flatMap((asset) => muxAssetToSanityDocument(asset) || []), tx = client.transaction();
documents.forEach((doc) => tx.create(doc));
try {
await tx.commit({ returnDocuments: !1 }), setSelectedAssets([]), setImportState("done");
} catch (error) {
setImportState("error"), setImportError(error);
}
}
return {
assetsInSanityLoading,
closeDialog,
dialogOpen,
importState,
importError,
hasSecrets,
importAssets,
missingAssets,
muxAssets,
openDialog,
selectedAssets,
setSelectedAssets
};
}
function muxAssetToSanityDocument(asset) {
const playbackId = (asset.playback_ids || []).find((p) => p.id)?.id;
if (playbackId)
return {
_id: uuid(),
_type: "mux.videoAsset",
_updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
_createdAt: parseMuxDate(asset.created_at).toISOString(),
assetId: asset.id,
playbackId,
filename: asset.meta?.title ?? `Asset #${truncateString(asset.id, 15)}`,
status: asset.status,
data: asset
};
}
const useAssetsInSanity = createHookFromObservableFactory(
(documentStore) => documentStore.listenQuery(
/* groq */
`*[_type == "mux.videoAsset"] {
"uploadId": coalesce(uploadId, data.upload_id),
"assetId": coalesce(assetId, data.id),
}`,
{},
{
apiVersion: SANITY_API_VERSION
}
)
);
function assetExistsInSanity(asset, existingAssets) {
return asset.status !== "ready" ? !1 : existingAssets.some(
(existing) => existing.assetId === asset.id || existing.uploadId === asset.upload_id
);
}
function useInView(ref, options = {}) {
const [inView, setInView] = useState(!1);
return useEffect(() => {
if (!ref.current) return;
const observer = new IntersectionObserver(([entry], obs) => {
const nowInView = entry.isIntersecting && obs.thresholds.some((threshold) => entry.intersectionRatio >= threshold);
setInView(nowInView), options?.onChange?.(nowInView);
}, options), toObserve = ref.current;
return observer.observe(toObserve), () => {
toObserve && observer.unobserve(toObserve);
};
}, [options, ref]), inView;
}
const _id = "secrets.mux";
function readSecrets(client) {
const { projectId, dataset } = client.config();
return suspend(async () => {
const data = await client.fetch(
/* groq */
`*[_id == $_id][0]{
token,
secretKey,
enableSignedUrls,
signingKeyId,
signingKeyPrivate
}`,
{ _id }
);
return {
token: data?.token || null,
secretKey: data?.secretKey || null,
enableSignedUrls: !!data?.enableSignedUrls || !1,
signingKeyId: data?.signingKeyId || null,
signingKeyPrivate: data?.signingKeyPrivate || null
};
}, [cacheNs, _id, projectId, dataset]);
}
function generateJwt(client, playbackId, aud, payload) {
const { signingKeyId, signingKeyPrivate } = readSecrets(client);
if (!signingKeyId)
throw new TypeError("Missing `signingKeyId`.\n Check your plugin's configuration");
if (!signingKeyPrivate)
throw new TypeError("Missing `signingKeyPrivate`.\n Check your plugin's configuration");
const { default: sign } = suspend(() => import("jsonwebtoken-esm/sign"), ["jsonwebtoken-esm/sign"]);
return sign(
payload ? JSON.parse(JSON.stringify(payload, (_, v) => v ?? void 0)) : {},
atob(signingKeyPrivate),
{
algorithm: "RS256",
keyid: signingKeyId,
audience: aud,
subject: playbackId,
noTimestamp: !0,
expiresIn: "12h"
}
);
}
function getPlaybackId(asset) {
if (!asset?.playbackId)
throw console.error("Asset is missing a playbackId", { asset }), new TypeError("Missing playbackId");
return asset.playbackId;
}
function getPlaybackPolicy(asset) {
return asset.data?.playback_ids?.find((playbackId) => asset.playbackId === playbackId.id)?.policy ?? "public";
}
function createUrlParamsObject(client, asset, params, audience) {
const playbackId = getPlaybackId(asset);
let searchParams = new URLSearchParams(
JSON.parse(JSON.stringify(params, (_, v) => v ?? void 0))
);
if (getPlaybackPolicy(asset) === "signed") {
const token = generateJwt(client, playbackId, audience, params);
searchParams = new URLSearchParams({ token });
}
return { playbackId, searchParams };
}
function getAnimatedPosterSrc({
asset,
client,
height,
width,
start = asset.thumbTime ? Math.max(0, asset.thumbTime - 2.5) : 0,
end = start + 5,
fps = 15
}) {
const params = { height, width, start, end, fps }, { playbackId, searchParams } = createUrlParamsObject(client, asset, params, "g");
return `https://image.mux.com/${playbackId}/animated.gif?${searchParams}`;
}
function getPosterSrc({
asset,
client,
fit_mode,
height,
time = asset.thumbTime ?? void 0,
width
}) {
const params = { fit_mode, height, width };
time && (params.time = time);
const { playbackId, searchParams } = createUrlParamsObject(client, asset, params, "t");
return `https://image.mux.com/${playbackId}/thumbnail.png?${searchParams}`;
}
const Image = styled.img`
transition: opacity 0.175s ease-out 0s;
display: block;
width: 100%;
height: 100%;
object-fit: contain;
object-position: center center;
`, STATUS_TO_TONE = {
loading: "transparent",
error: "critical",
loaded: "default"
};
function VideoThumbnail({
asset,
width,
staticImage = !1
}) {
const ref = useRef(null), inView = useInView(ref), posterWidth = width || 250, [status, setStatus] = useState("loading"), client = useClient(), src = useMemo(() => {
try {
let thumbnail;
return staticImage ? thumbnail = getPosterSrc({ asset, client, width: posterWidth }) : thumbnail = getAnimatedPosterSrc({ asset, client, width: posterWidth }), thumbnail;
} catch {
status !== "error" && setStatus("error");
return;
}
}, [asset, client, posterWidth, status, staticImage]);
function handleLoad() {
setStatus("loaded");
}
function handleError() {
setStatus("error");
}
return /* @__PURE__ */ jsx(
Card,
{
style: {
aspectRatio: THUMBNAIL_ASPECT_RATIO,
position: "relative",
maxWidth: width ? `${width}px` : void 0,
width: "100%",
flex: 1
},
border: !0,
radius: 2,
ref,
tone: STATUS_TO_TONE[status],
children: inView ? /* @__PURE__ */ jsxs(Fragment, { children: [
status === "loading" && /* @__PURE__ */ jsx(
Box,
{
style: {
position: "absolute",
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)"
},
children: /* @__PURE__ */ jsx(Spinner, {})
}
),
status === "error" && /* @__PURE__ */ jsxs(
Stack,
{
space: 4,
style: {
position: "absolute",
width: "100%",
left: 0,
top: "50%",
transform: "translateY(-50%)",
justifyItems: "center"
},
children: [
/* @__PURE__ */ jsx(Text, { size: 4, muted: !0, children: /* @__PURE__ */ jsx(ErrorOutlineIcon, { style: { fontSize: "1.75em" } }) }),
/* @__PURE__ */ jsx(Text, { muted: !0, align: "center", children: "Failed loading thumbnail" })
]
}
),
/* @__PURE__ */ jsx(
Image,
{
src,
alt: `Preview for ${staticImage ? "image" : "video"} ${asset.filename || asset.assetId}`,
onLoad: handleLoad,
onError: handleError,
style: { opacity: status === "loaded" ? 1 : 0 }
}
)
] }) : null
}
);
}
const MissingAssetCheckbox = styled(Checkbox)`
position: static !important;
input::after {
content: '';
position: absolute;
inset: 0;
display: block;
cursor: pointer;
z-index: 1000;
}
`;
function MissingAsset({
asset,
selectAsset,
selected
}) {
const duration = useFormattedDuration(asset.duration * 1e3);
return /* @__PURE__ */ jsx(
Card,
{
tone: selected ? "positive" : void 0,
border: !0,
paddingX: 2,
paddingY: 3,
style: { position: "relative" },
radius: 1,
children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
/* @__PURE__ */ jsx(
MissingAssetCheckbox,
{
checked: selected,
onChange: (e) => {
selectAsset(e.currentTarget.checked);
},
"aria-label": selected ? `Import video ${asset.id}` : `Skip import of video ${asset.id}`
}
),
/* @__PURE__ */ jsx(
VideoThumbnail,
{
asset: {
assetId: asset.id,
data: asset,
filename: asset.id,
playbackId: asset.playback_ids.find((p) => p.id)?.id
},
width: 150
}
),
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 1, children: [
/* @__PURE__ */ jsx(Code, { size: 2, children: truncateString(asset.id, 15) }),
" ",
/* @__PURE__ */ jsxs(Text, { muted: !0, size: 2, children: [
"(",
duration.formatted,
")"
] })
] }),
/* @__PURE__ */ jsxs(Text, { size: 1, children: [
"Uploaded at",
" ",
new Date(Number(asset.created_at) * 1e3).toLocaleDateString("en", {
year: "numeric",
day: "2-digit",
month: "2-digit"
})
] })
] })
] })
},
asset.id
);
}
function ImportVideosDialog(props) {
const { importState } = props, canTriggerImport = (importState === "idle" || importState === "error") && props.selectedAssets.length > 0, isImporting = importState === "importing", noAssetsToImport = props.missingAssets?.length === 0 && !props.muxAssets.loading && !props.assetsInSanityLoading;
return /* @__PURE__ */ jsx(
Dialog,
{
animate: !0,
header: "Import videos from Mux",
zOffset: DIALOGS_Z_INDEX,
id: "video-details-dialog",
onClose: props.closeDialog,
onClickOutside: props.closeDialog,
width: 1,
position: "fixed",
footer: importState !== "done" && !noAssetsToImport && /* @__PURE__ */ jsx(Card, { padding: 3, children: /* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", children: [
/* @__PURE__ */ jsx(
Button,
{
fontSize: 2,
padding: 3,
mode: "bleed",
text: "Cancel",
tone: "critical",
onClick: props.closeDialog,
disabled: isImporting
}
),
props.missingAssets && /* @__PURE__ */ jsx(
Button,
{
icon: RetrieveIcon,
fontSize: 2,
padding: 3,
mode: "ghost",
text: props.selectedAssets?.length > 0 ? `Import ${props.selectedAssets.length} video(s)` : "No video(s) selected",
tone: "positive",
onClick: props.importAssets,
iconRight: isImporting && Spinner,
disabled: !canTriggerImport
}
)
] }) }),
children: /* @__PURE__ */ jsxs(Box, { padding: 3, children: [
(props.muxAssets.loading || props.assetsInSanityLoading) && /* @__PURE__ */ jsx(Card, { tone: "primary", marginBottom: 5, padding: 3, border: !0, children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 4, children: [
/* @__PURE__ */ jsx(Spinner, { muted: !0, size: 4 }),
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
/* @__PURE__ */ jsx(Text, { size: 2, weight: "semibold", children: "Loading assets from Mux" }),
/* @__PURE__ */ jsxs(Text, { size: 1, children: [
"This may take a while.",
props.missingAssets && props.missingAssets.length > 0 && ` There are at least ${props.missingAssets.length} video${props.missingAssets.length > 1 ? "s" : ""} currently not in Sanity...`
] })
] })
] }) }),
props.muxAssets.error && /* @__PURE__ */ jsx(Card, { tone: "critical", marginBottom: 5, padding: 3, border: !0, children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
/* @__PURE__ */ jsx(ErrorOutlineIcon, { fontSize: 36 }),
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
/* @__PURE__ */ jsx(Text, { size: 2, weight: "semibold", children: "There was an error getting all data from Mux" }),
/* @__PURE__ */ jsx(Text, { size: 1, children: props.missingAssets ? `But we've found ${props.missingAssets.length} video${props.missingAssets.length > 1 ? "s" : ""} not in Sanity, which you can start importing now.` : "Please try again or contact a developer for help." })
] })
] }) }),
importState === "importing" && /* @__PURE__ */ jsx(Card, { tone: "primary", marginBottom: 5, padding: 3, border: !0, children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 4, children: [
/* @__PURE__ */ jsx(Spinner, { muted: !0, size: 4 }),
/* @__PURE__ */ jsx(Stack, { space: 2, children: /* @__PURE__ */ jsxs(Text, { size: 2, weight: "semibold", children: [
"Importing ",
props.selectedAssets.length,
" video",
props.selectedAssets.length > 1 && "s",
" from Mux"
] }) })
] }) }),
importState === "error" && /* @__PURE__ */ jsx(Card, { tone: "critical", marginBottom: 5, padding: 3, border: !0, children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
/* @__PURE__ */ jsx(ErrorOutlineIcon, { fontSize: 36 }),
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
/* @__PURE__ */ jsx(Text, { size: 2, weight: "semibold", children: "There was an error importing videos" }),
/* @__PURE__ */ jsx(Text, { size: 1, children: props.importError ? `Error: ${props.importError}` : "Please try again or contact a developer for help." }),
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
Button,
{
icon: RetryIcon,
text: "Retry",
tone: "primary",
onClick: props.importAssets
}
) })
] })
] }) }),
(noAssetsToImport || importState === "done") && /* @__PURE__ */ jsxs(Stack, { paddingY: 5, marginBottom: 4, space: 3, style: { textAlign: "center" }, children: [
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(CheckmarkCircleIcon, { fontSize: 48 }) }),
/* @__PURE__ */ jsx(Heading, { size: 2, children: importState === "done" ? "Videos imported successfully" : "There are no Mux videos to import" }),
/* @__PURE__ */ jsx(Text, { size: 2, children: importState === "done" ? "You can now use them in your Sanity content." : "They're all in Sanity and ready to be used in your content." })
] }),
props.missingAssets && props.missingAssets.length > 0 && (importState === "idle" || importState === "error") && /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
/* @__PURE__ */ jsxs(Heading, { size: 1, children: [
"There are ",
props.missingAssets.length,
props.muxAssets.loading && "+",
" Mux video",
props.missingAssets.length > 1 && "s",
" ",
"not in Sanity"
] }),
!props.muxAssets.loading && /* @__PURE__ */ jsxs(Flex, { align: "center", paddingX: 2, children: [
/* @__PURE__ */ jsx(
Checkbox,
{
id: "import-all",
style: { display: "block" },
onClick: (e) => {
e.currentTarget.checked ? props.missingAssets && props.setSelectedAssets(props.missingAssets) : props.setSelectedAssets([]);
},
checked: props.selectedAssets.length === props.missingAssets.length
}
),
/* @__PURE__ */ jsx(Box, { flex: 1, paddingLeft: 3, as: "label", htmlFor: "import-all", children: /* @__PURE__ */ jsx(Text, { children: "Import all" }) })
] }),
props.missingAssets.map((asset) => /* @__PURE__ */ jsx(
MissingAsset,
{
asset,
selectAsset: (selected) => {
selected ? props.setSelectedAssets([...props.selectedAssets, asset]) : props.setSelectedAssets(props.selectedAssets.filter((a2) => a2.id !== asset.id));
},
selected: props.selectedAssets.some((a2) => a2.id === asset.id)
},
asset.id
))
] })
] })
}
);
}
function ImportVideosFromMux() {
const importAssets = useImportMuxAssets();
if (importAssets.hasSecrets)
return importAssets.dialogOpen ? /* @__PURE__ */ jsx(ImportVideosDialog, { ...importAssets }) : /* @__PURE__ */ jsx(Button, { mode: "bleed", text: "Import from Mux", onClick: importAssets.openDialog });
}
const CONTEXT_MENU_POPOVER_PROPS = {
constrainSize: !0,
placement: "bottom",
portal: !0,
width: 0
};
function SelectSortOptions(props) {
const id = useId();
return /* @__PURE__ */ jsx(
MenuButton,
{
button: /* @__PURE__ */ jsx(Button, { text: "Sort", icon: SortIcon, mode: "bleed", padding: 3, style: { cursor: "pointer" } }),
id,
menu: /* @__PURE__ */ jsx(Menu, { children: Object.entries(ASSET_SORT_OPTIONS).map(([type, { label }]) => /* @__PURE__ */ jsx(
MenuItem,
{
"data-as": "button",
onClick: () => props.setSort(type),
padding: 3,
tone: "default",
text: label,
pressed: type === props.sort
},
type
)) }),
popover: CONTEXT_MENU_POPOVER_PROPS
}
);
}
const SpinnerBox = () => /* @__PURE__ */ jsx(
Box,
{
style: {
display: "flex",
alignItems: "center",
justifyContent: "center",
minHeight: "150px"
},
children: /* @__PURE__ */ jsx(Spinner, {})
}
);
function FormField(props) {
const { children, title, description, inputId } = props;
return /* @__PURE__ */ jsxs(Stack, { space: 1, children: [
/* @__PURE__ */ jsx(Flex, { align: "flex-end", children: /* @__PURE__ */ jsx(Box, { flex: 1, paddingY: 2, children: /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: inputId, weight: "semibold", size: 1, children: title || /* @__PURE__ */ jsx("em", { children: "Untitled" }) }),
description && /* @__PURE__ */ jsx(Text, { muted: !0, size: 1, children: description })
] }) }) }),
/* @__PURE__ */ jsx("div", { children })
] });
}
var FormField$1 = memo(FormField);
const IconInfo = (props) => {
const Icon = props.icon;
return /* @__PURE__ */ jsxs(Flex, { gap: 2, align: "center", padding: 1, children: [
/* @__PURE__ */ jsx(Text, { size: (props.size || 1) + 1, muted: !0, children: /* @__PURE__ */ jsx(Icon, {}) }),
/* @__PURE__ */ jsx(Text, { size: props.size || 1, muted: props.muted, children: props.text })
] });
};
function ResolutionIcon(props) {
return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1em", height: "1em", viewBox: "0 0 24 24", ...props, children: /* @__PURE__ */ jsx(
"path",
{
fill: "currentColor",
d: "M20 9V6h-3V4h5v5h-2ZM2 9V4h5v2H4v3H2Zm15 11v-2h3v-3h2v5h-5ZM2 20v-5h2v3h3v2H2Zm4-4V8h12v8H6Zm2-2h8v-4H8v4Zm0 0v-4v4Z"
}
) });
}
function StopWatchIcon(props) {
return /* @__PURE__ */ jsxs(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "1em",
height: "1em",
viewBox: "0 0 512 512",
...props,
children: [
/* @__PURE__ */ jsx("path", { d: "M232 306.667h48V176h-48v130.667z", fill: "currentColor" }),
/* @__PURE__ */ jsx(
"path",
{
d: "M407.67 170.271l30.786-30.786-33.942-33.941-30.785 30.786C341.217 111.057 300.369 96 256 96 149.961 96 64 181.961 64 288s85.961 192 192 192 192-85.961 192-192c0-44.369-15.057-85.217-40.33-117.729zm-45.604 223.795C333.734 422.398 296.066 438 256 438s-77.735-15.602-106.066-43.934C121.602 365.735 106 328.066 106 288s15.602-77.735 43.934-106.066C178.265 153.602 215.934 138 256 138s77.734 15.602 106.066 43.934C390.398 210.265 406 247.934 406 288s-15.602 77.735-43.934 106.066z",
fill: "currentColor"
}
),
/* @__PURE__ */ jsx("path", { d: "M192 32h128v48H192z", fill: "currentColor" })
]
}
);
}
const DialogStateContext = createContext({
dialogState: !1,
setDialogState: () => null
}), DialogStateProvider = ({
dialogState,
setDialogState,
children
}) => /* @__PURE__ */ jsx(DialogStateContext.Provider, { value: { dialogState, setDialogState }, children }), useDialogStateContext = () => useContext(DialogStateContext);
function getVideoSrc({ asset, client }) {
const playbackId = getPlaybackId(asset), searchParams = new URLSearchParams();
if (getPlaybackPolicy(asset) === "signed") {
const token = generateJwt(client, playbackId, "v");
searchParams.set("token", token);
}
return `https://stream.mux.com/${playbackId}.m3u8?${searchParams}`;
}
function getDevicePixelRatio(options) {
const {
defaultDpr = 1,
maxDpr = 3,
round = !0
} = options || {}, dpr = typeof window < "u" && typeof window.devicePixelRatio == "number" ? window.devicePixelRatio : defaultDpr;
return Math.min(Math.max(1, round ? Math.floor(dpr) : dpr), maxDpr);
}
function formatSeconds(seconds) {
if (typeof seconds != "number" || Number.isNaN(seconds))
return "";
const hrs = ~~(seconds / 3600), mins = ~~(seconds % 3600 / 60), secs = ~~seconds % 60;
let ret = "";
return hrs > 0 && (ret += "" + hrs + ":" + (mins < 10 ? "0" : "")), ret += "" + mins + ":" + (secs < 10 ? "0" : ""), ret += "" + secs, ret;
}
function formatSecondsToHHMMSS(seconds) {
const hrs = Math.floor(seconds / 3600).toString().padStart(2, "0"), mins = Math.floor(seconds % 3600 / 60).toString().padStart(2, "0"), secs = Math.floor(seconds % 60).toString().padStart(2, "0");
return `${hrs}:${mins}:${secs}`;
}
function isValidTimeFormat(time) {
return /^([0-1]?[0-9]|2[0-3]):([0-5]?[0-9]):([0-5]?[0-9])$/.test(time) || time === "";
}
function getSecondsFromTimeFormat(time) {
const [hh = 0, mm = 0, ss = 0] = time.split(":").map(Number);
return hh * 3600 + mm * 60 + ss;
}
function EditThumbnailDialog({ asset, currentTime = 0 }) {
const client = useClient(), { setDialogState } = useDialogStateContext(), dialogId = `EditThumbnailDialog${useId()}`, [timeFormatted, setTimeFormatted] = useState(
() => formatSecondsToHHMMSS(currentTime)
), [nextTime, setNextTime] = useState(currentTime), [inputError, setInputError] = useState(""), assetWithNewThumbnail = useMemo(() => ({ ...asset, thumbTime: nextTime }), [asset, nextTime]), [saving, setSaving] = useState(!1), [saveThumbnailError, setSaveThumbnailError] = useState(null), handleSave = () => {
setSaving(!0), client.patch(asset._id).set({ thumbTime: nextTime }).commit({ returnDocuments: !1 }).then(() => void setDialogState(!1)).catch(setSaveThumbnailError).finally(() => void setSaving(!1));
}, width = 300 * getDevicePixelRatio({ maxDpr: 2 });
if (saveThumbnailError)
throw saveThumbnailError;
return /* @__PURE__ */ jsx(
Dialog,
{
id: dialogId,
header: "Edit thumbnail",
onClose: () => setDialogState(!1),
footer: /* @__PURE__ */ jsx(Stack, { padding: 3, children: /* @__PURE__ */ jsx(
Button,
{
disabled: inputError !== "",
mode: "ghost",
tone: "primary",
loading: saving,
onClick: handleSave,
text: "Set new thumbnail"
},
"thumbnail"
) }),
children: /* @__PURE__ */ jsxs(Stack, { space: 3, padding: 3, children: [
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
/* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: "Current:" }),
/* @__PURE__ */ jsx(VideoThumbnail, { asset, width, staticImage: !0 })
] }),
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
/* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: "New:" }),
/* @__PURE__ */ jsx(VideoThumbnail, { asset: assetWithNewThumbnail, width, staticImage: !0 })
] }),
/* @__PURE__ */ jsx(Stack, { space: 2, children: /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", children: /* @__PURE__ */ jsx(Text, { size: 5, weight: "semibold", children: "Or" }) }) }),
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
/* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: "Selected time for thumbnail (hh:mm:ss):" }),
/* @__PURE__ */ jsx(
TextInput,
{
size: 1,
value: timeFormatted,
placeholder: "hh:mm:ss",
onChange: (event) => {
const value = event.currentTarget.value;
if (setTimeFormatted(value), isValidTimeFormat(value)) {
setInputError("");
const totalSeconds = getSecondsFromTimeFormat(value);
setNextTime(totalSeconds);
} else
setInputError("Invalid time format");
},
customValidity: inputError
}
)
] })
] })
}
);
}
function VideoPlayer({
asset,
thumbnailWidth = 250,
children,
...props
}) {
const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = useRef(null), thumbnail = getPosterSrc({ asset, client, width: thumbnailWidth }), { src: videoSrc, error } = useMemo(() => {
try {
const src = asset?.playbackId && getVideoSrc({ client, asset });
return src ? { src } : { error: new TypeError("Asset has no playback ID") };
} catch (error2) {
return { error: error2 };
}
}, [asset, client]), signedToken = useMemo(() => {
try {
return new URL(videoSrc).searchParams.get("token");
} catch {
return !1;
}
}, [videoSrc]), [width, height] = (asset?.data?.aspect_ratio ?? "16:9").split(":").map(Number), targetAspectRatio = props.forceAspectRatio || (Number.isNaN(width) ? 16 / 9 : width / height);
let aspectRatio = Math.max(MIN_ASPECT_RATIO, targetAspectRatio);
return isAudio && (aspectRatio = props.forceAspectRatio ? (
// Make it wider when forcing aspect ratio to balance with videos' rendering height (audio players overflow a bit)
props.forceAspectRatio * 1.2
) : AUDIO_ASPECT_RATIO), /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsxs(Card, { tone: "transparent", style: { aspectRatio, position: "relative" }, children: [
videoSrc && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
MuxPlayer,
{
poster: thumbnail,
ref: muxPlayer,
...props,
playsInline: !0,
playbackId: asset.playbackId,
tokens: signedToken ? { playback: signedToken, thumbnail: signedToken, storyboard: signedToken } : void 0,
preload: "metadata",
crossOrigin: "anonymous",
metadata: {
player_name: "Sanity Admin Dashboard",
player_version: "2.9.0",
page_type: "Preview Player"
},
audio: isAudio,
style: {
height: "100%",
width: "100%",
display: "block",
objectFit: "contain"
}
}
),
children
] }),
error ? /* @__PURE__ */ jsx(
"div",
{
style: {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)"
},
children: /* @__PURE__ */ jsxs(Text, { muted: !0, children: [
/* @__PURE__ */ jsx(ErrorOutlineIcon, { style: { marginRight: "0.15em" } }),
typeof error == "object" && "message" in error && typeof error.message == "string" ? error.message : "Error loading video"
] })
}
) : null,
children
] }),
dialogState === "edit-thumbnail" && /* @__PURE__ */ jsx(EditThumbnailDialog, { asset, currentTime: muxPlayer?.current?.currentTime })
] });
}
function assetIsAudio(asset) {
return asset.data?.max_stored_resolution === "Audio only";
}
function deleteAssetOnMux(client, assetId) {
const { dataset } = client.config();
return client.request({
url: `/addons/mux/assets/${dataset}/${assetId}`,
withCredentials: !0,
method: "DELETE"
});
}
async function deleteAsset({
client,
asset,
deleteOnMux
}) {
if (!asset?._id) return !0;
try {
await client.delete(asset._id);
} catch {
return "failed-sanity";
}
if (deleteOnMux && asset?.assetId)
try {
await deleteAssetOnMux(client, asset.assetId);
} catch {
return "failed-mux";
}
return !0;
}
function getAsset(client, assetId) {
const { dataset } = client.config();
return client.request({
url: `/addons/mux/assets/${dataset}/data/${assetId}`,
withCredentials: !0,
method: "GET"
});
}
const getUnknownTypeFallback = (id, typeName) => ({
title: /* @__PURE__ */ jsxs("em", { children: [
"No schema found for type ",
/* @__PURE__ */ jsx("code", { children: typeName })
] }),
subtitle: /* @__PURE__ */ jsxs("em", { children: [
"Document: ",
/* @__PURE__ */ jsx("code", { children: id })
] }),
media: () => /* @__PURE__ */ jsx(WarningOutlineIcon, {})
});
function MissingSchemaType(props) {
const { layout, value } = props;
return /* @__PURE__ */ jsx(SanityDefaultPreview, { ...getUnknownTypeFallback(value._id, value._type), layout });
}
function TimeAgo({ time }) {
const timeAgo = useTimeAgo(time);
return /* @__PURE__ */ jsxs("span", { title: timeAgo, children: [
timeAgo,
" ago"
] });
}
function DraftStatus(props) {
const { document: document2 } = props, updatedAt = document2 && "_updatedAt" in document2 && document2._updatedAt;
return /* @__PURE__ */ jsx(
Tooltip,
{
animate: !0,
portal: !0,
content: /* @__PURE__ */ jsx(Box, { padding: 2, children: /* @__PURE__ */ jsx(Text, { size: 1, children: document2 ? /* @__PURE__ */ jsxs(Fragment, { children: [
"Edited ",
updatedAt && /* @__PURE__ */ jsx(TimeAgo, { time: updatedAt })
] }) : /* @__PURE__ */ jsx(Fragment, { children: "No unpublished edits" }) }) }),
children: /* @__PURE__ */ jsx(TextWithTone, { tone: "caution", dimmed: !document2, muted: !document2, size: 1, children: /* @__PURE__ */ jsx(EditIcon, {}) })
}
);
}
function PublishedStatus(props) {
const { document: document2 } = props, updatedAt = document2 && "_updatedAt" in document2 && document2._updatedAt;
return /* @__PURE__ */ jsx(
Tooltip,
{
animate: !0,
portal: !0,
content: /* @__PURE__ */ jsx(Box, { padding: 2, children: /* @__PURE__ */ jsx(Text, { size: 1, children: document2 ? /* @__PURE__ */ jsxs(Fragment, { children: [
"Published ",
updatedAt && /* @__PURE__ */ jsx(TimeAgo, { time: updatedAt })
] }) : /* @__PURE__ */ jsx(Fragment, { children: "Not published" }) }) }),
children: /* @__PURE__ */ jsx(TextWithTone, { tone: "positive", dimmed: !document2, muted: !document2, size: 1, children: /* @__PURE__ */ jsx(PublishIcon, {}) })
}
);
}
function PaneItemPreview(props) {
const { icon, layout, presence, schemaType, value } = props, title = isRecord(value.title) && isValidElement(value.title) || isString(value.title) || isNumber(value.title) ? value.title : null, observable = useMemo(
() => getPreviewStateObservable(props.documentPreviewStore, schemaType, value._id, title),
[props.documentPreviewStore, schemaType, title, value._id]
), { draft, published, isLoading } = useObservable(observable, {
draft: null,
published: null,
isLoading: !0
}), status = isLoading ? null : /* @__PURE__ */ jsxs(Inline, { space: 4, children: [
presence && presence.length > 0 && /* @__PURE__ */ jsx(DocumentPreviewPresence, { presence }),
/* @__PURE__ */ jsx(PublishedStatus, { document: published }),
/* @__PURE__ */ jsx(DraftStatus, { document: draft })
] });
return /* @__PURE__ */ jsx(
SanityDefaultPreview,
{
...getPreviewValueWithFallback({ value, draft, published }),
isPlaceholder: isLoading,
icon,
layout,
status
}
);
}
function getIconWithFallback(icon, schemaType, defaultIcon) {
return icon === !1 ? !1 : icon || schemaType && schemaType.icon || defaultIcon || !1;
}
function DocumentPreviewLink(props) {
return (linkProps) => /* @__PURE__ */ jsx(IntentLink, { intent: "edit", params: { id: props.documentPair.id }, children: linkProps.children });
}
function DocumentPreview(props) {
const { schemaType, documentPair } = props, doc = documentPair?.draft || documentPair?.published, id = documentPair.id || "", documentPreviewStore = useDocumentPreviewStore(), schema = useSchema(), documentPresence = useDocumentPresence(id), hasSchemaType = !!(schemaType && schemaType.name && schema.get(schemaType.name)), PreviewComponent = useMemo(() => doc ? !schemaType || !hasSchemaType ? /* @__PURE__ */ jsx(MissingSchemaType, { value: doc }) : /* @__PURE__ */ jsx(
PaneItemPreview,
{
documentPreviewStore,
icon: getIconWithFallback(void 0, schemaType, DocumentIcon),
schemaType,
layout: "default",
value: doc,
presence: documentPresence
}
) : null, [hasSchemaType, schemaType, documentPresence, doc, documentPreviewStore]);
return /* @__PURE__ */ jsx(
PreviewCard,
{
__unstable_focusRing: !0,
as: DocumentPreviewLink(props),
"data-as": "a",
"data-ui": "PaneItem",
padding: 2,
radius: 2,
tone: "inherit",
children: PreviewComponent
}
);
}
const Container = styled(Box)`
* {
color: ${(props) => props.theme.sanity.color.base.fg};
}
a {
text-decoration: none;
}
h2 {
font-size: ${(props) => props.theme.sanity.fonts.text.sizes[1]};
}
`, VideoReferences = (props) => {
const schema = useSchema();
if (!props.isLoaded)
return /* @__PURE__ */ jsx(SpinnerBox, {});
if (!props.references?.length)
return /* @__PURE__ */ jsx(Card, { border: !0, radius: 3, padding: 3, children: /* @__PURE__ */ jsx(Text, { size: 2, children: "No documents are using this video" }) });
const documentPairs = collate(props.references || []);
return /* @__PURE__ */ jsx(Container, { children: documentPairs?.map((documentPair) => {
const schemaType = schema.get(documentPair.type);
return /* @__PURE__ */ jsx(
Card,
{
marginBottom: 2,
padding: 2,
radius: 2,
shadow: 1,
style: { overflow: "hidden" },
children: /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(DocumentPreview, { documentPair, schemaType }) })
},
documentPair.id
);
}) });
};
function DeleteDialog({
asset,
references,
referencesLoading,
cancelDelete,
succeededDeleting
}) {
const client = useClient(), [state, setState] = useState("checkingReferences"), [deleteOnMux, setDeleteOnMux] = useState(!0), toast = useToast();
useEffect(() => {
state !== "checkingReferences" || referencesLoading || setState(references?.length ? "cantDelete" : "confirm");
}, [state, references, referencesLoading]);
async function confirmDelete() {
if (state !== "confirm") return;
setState("processing_deletion");
const worked = await deleteAsset({ client, asset, deleteOnMux });
worked === !0 ? (toast.push({ title: "Successfully deleted video", status: "success" }), succeededDeleting()) : worked === "failed-mux" ? (toast.push({
title: "Deleted video in Sanity",
description: "But it wasn't deleted in Mux",
status: "warning"
}), succeededDeleting()) : (toast.push({ title: "Failed deleting video", status: "error" }), setState("error_deleting"));
}
return /* @__PURE__ */ jsx(
Dialog,
{
animate: !0,
header: "Delete video",
zOffset: DIALOGS_Z_INDEX,
id: "deleting-video-details-dialog",
onClose: cancelDelete,
onClickOutside: cancelDelete,
width: 1,
position: "fixed",
children: /* @__PURE__ */ jsx(
Card,
{
padding: 3,
style: {
minHeight: "150px",
display: "flex",
alignItems: "center",
justifyContent: "center"
},
children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
state === "checkingReferences" && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(Heading, { size: 2, children: "Checking if video can be deleted" }),
/* @__PURE__ */ jsx(SpinnerBox, {})
] }),
state === "cantDelete" && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(Heading, { size: 2, children: "Video can't be deleted" }),
/* @__PURE__ */ jsxs(Text, { size: 2, style: { marginBottom: "2rem" }, children: [
"There are ",
references?.length,
" document",
references && references.length > 0 && "s",
" ",
"pointing to this video. Remove their references to this file or delete them before proceeding."
] }),
/* @__PURE__ */ jsx(VideoReferences, { references, isLoaded: !referencesLoading })
] }),
state === "confirm" && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(Heading, { size: 2, children: "Are you sure you want to delete this video?" }),
/* @__PURE__ */ jsx(Text, { size: 2, children: "This action is irreversible" }),
/* @__PURE__ */ jsxs(Stack, { space: 4, marginY: 4, children: [
/* @__PURE__ */ jsxs(Flex, { align: "center", as: "label", children: [
/* @__PURE__ */ jsx(
Checkbox,
{
checked: deleteOnMux,
onChange: () => setDeleteOnMux((prev) => !prev)
}
),
/* @__PURE__ */ jsx(Text, { style: { margin: "0 10px" }, children: "Delete asset on Mux" })
] }),
/* @__PURE__ */ jsxs(Flex, { align: "center", as: "label", children: [
/* @__PURE__ */ jsx(Checkbox, { disabled: !0, checked: !0 }),
/* @__PURE__ */ jsx(Text, { style: { margin: "0 10px" }, children: "Delete video from dataset" })
] }),
/* @__PURE__ */ jsx(Box, { children: /