import {Op, html, watch} from "@benev/slate" import {VideoEffect} from "../../../../context/types.js" import {Tooltip} from "../../../../views/tooltip/view.js" import xMarkSvg from "../../../../icons/gravity-ui/x-mark.svg.js" import {tooltipStyles} from "../../../../views/tooltip/styles.js" import exportSvg from "../../../../icons/gravity-ui/export.svg.js" import {StateHandler} from "../../../../views/state-handler/view.js" import {collaboration, shadow_view} from "../../../../context/context.js" import circleInfoSvg from "../../../../icons/gravity-ui/circle-info.svg.js" import {confirmModalStyles, exportOverlayStyles, styles} from "./styles.js" export const Export = shadow_view(use => () => { use.styles([styles, tooltipStyles]) use.watch(() => use.context.state) const state = use.context.state const video_export = use.context.controllers.video_export const [logs, setLogs, getLogs] = use.state([]) use.mount(() => { const dispose = collaboration.onChange(() => use.rerender()) return () => dispose() }) const isClient = collaboration.client use.mount(() => { const dispose = watch.track(() => use.context.state.log, (log) => { if(getLogs().length === 20) { const new_arr = getLogs().slice(1) setLogs(new_arr) } setLogs([...getLogs(), log]) }) return () => dispose() }) const renderAspectRatio = () => { const aspectRatio = state.settings.aspectRatio return html`

Aspect Ratio

${aspectRatio === "21/9" ? "Ultra-Wide" : aspectRatio === "9/16" ? "Vertical" : aspectRatio === "16/9" ? "Wide" : aspectRatio === "1/1" ? "Square" : aspectRatio === "4/3" ? "Standard" : aspectRatio === "3/2" ? "Balanced" : null}
${aspectRatio}
` } return StateHandler(Op.all( use.context.helpers.ffmpeg.is_loading.value, use.context.is_webcodecs_supported.value), () => html`

Export

${circleInfoSvg} Your video will export with the following settings:

${renderAspectRatio()}

Resolution

${state.settings.width}x${state.settings.height} (${state.settings.standard})

Timebase

${state.timebase} fps

Bitrate

${state.settings.bitrate} kbps
${Tooltip( html` `, html`${isClient ? "Only host can export" : null}` )}
`) }) export const ExportInProgressOverlay = shadow_view((use) => () => { use.styles([exportOverlayStyles]) use.watch(() => use.context.state) const state = use.context.state const videoExport = use.context.controllers.video_export // Format progress to 2 decimal places const formattedProgress = state.export_status === "complete" || state.export_status === "flushing" ? 100 : Number.parseFloat(state.export_progress.toFixed(2)) // Get status text for display const getStatusText = () => { switch (state.export_status) { // case "preparing": // return "Preparing export..." case "composing": return "Exporting video..." case "flushing": return "Finalizing export..." case "complete": return "Export complete!" case "error": return "Export failed" default: return "Processing..." } } // Get variant for progress bar const getProgressVariant = () => { if (state.export_status === "error") return "danger" if (state.export_status === "complete") return "success" return "primary" } const handleSaveButtonMouseOver = () => { if (state.export_status === "complete") { use.rerender() } } const handleSaveButtonMouseOut = () => { use.rerender() } // Update visibility class and body scroll if (state.is_exporting) { use.element.classList.add("visible") document.body.style.overflow = "hidden" } else { use.element.classList.remove("visible") document.body.style.overflow = "" } return html`
${ state.is_exporting ? html`
${use.context.controllers.compositor.app.view}
` : "" }

${ state.export_status === "complete" ? html` Export Complete ` : state.export_status === "error" ? html` Export Failed ` : html` Exporting Video ` }

${formattedProgress}% ${getStatusText()}
{ videoExport.resetExporter(use.context.state) }} > ${state.export_status === "complete" ? "Continue Editing" : "Cancel Export"} videoExport.save_file()} class="save-button" @mouseover=${handleSaveButtonMouseOver} @mouseout=${handleSaveButtonMouseOut} > Save Video
` }) export const ExportConfirmModal = shadow_view(use => (showModal: boolean, setShowModal: (v: boolean) => void) => { use.watch(() => use.context.state) use.styles([confirmModalStyles]) const mediaController = use.context.controllers.media const [_, setFilesProgess, getFileProgress] = use.state>(new Map()) // get proxy videos on timeline const getProxies = () => { const files = [...mediaController.values()] return files.filter(file => file.kind === "video" && file.proxy && use.context.state.effects.some(e => e.kind === "video" && e.file_hash === file.hash)) } use.mount(() => { const dispose = mediaController.on_media_change(() => use.rerender()) const dispose1 = collaboration.onFileProgress(({hash, progress}) => { setFilesProgess(new Map(getFileProgress()).set(hash, progress)) }) return () => {dispose(); dispose1()} }) const dialog = use.defer(() => use.shadow.querySelector("dialog")) if(showModal) { dialog?.showModal() } else { dialog?.close() } const getProxyPreview = (hash: string) => { return (use.context.state.effects.find(e => e.kind === "video" && e.file_hash === hash) as VideoEffect).thumbnail } const getFileMetadata = (hash: string) => { return collaboration.filesMetada.find(([fileHash]) => hash === fileHash)?.[1] } const renderProxyFilesInProgressModal = () => { return html`

Export Unavailable

We're still transferring the following files to you.
Please wait until all transfers are complete before exporting the project.

Pending files:
${getProxyFilesInProgress.map(([hash, file]) => html`
${getFileMetadata(hash)?.name} Progress: ${((file.received/file.total) * 100).toFixed()}%
`)}
` } const renderProjectReadyModal = () => { return html`

Your project is ready

All files have been transferred without proxies.
You can now export your project in the highest quality.

` } const renderRequestOriginalFilesModal = () => { return html`

Proxy Videos Detected

To ensure faster transfers within collaborative environment, some of your videos are proxies (same resolution, lower bitrate):

Export options:

  1. Export with proxies
  2. Request original videos:
${getProxies().map(proxy => html`
${proxy?.file.name}
`)}
` } const getProxyFilesInProgress = collaboration.filesInProgress.filter(([_, file]) => file.proxy === true) return html`
${getProxyFilesInProgress.length !== 0 ? renderProxyFilesInProgressModal() : getProxies().length !== 0 ? renderRequestOriginalFilesModal() : renderProjectReadyModal() }
` })