@remotion/studio
Version:
APIs for interacting with the Remotion Studio
424 lines (423 loc) • 18.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RenderButton = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const player_1 = require("@remotion/player");
const react_1 = require("react");
const react_dom_1 = __importDefault(require("react-dom"));
const remotion_1 = require("remotion");
const client_id_1 = require("../helpers/client-id");
const colors_1 = require("../helpers/colors");
const show_browser_rendering_1 = require("../helpers/show-browser-rendering");
const use_keybinding_1 = require("../helpers/use-keybinding");
const caret_1 = require("../icons/caret");
const render_1 = require("../icons/render");
const in_out_1 = require("../state/in-out");
const modals_1 = require("../state/modals");
const z_index_1 = require("../state/z-index");
const layout_1 = require("./layout");
const is_menu_item_1 = require("./Menu/is-menu-item");
const portals_1 = require("./Menu/portals");
const styles_1 = require("./Menu/styles");
const MenuContent_1 = require("./NewComposition/MenuContent");
const splitButtonContainer = {
display: 'inline-flex',
flexDirection: 'row',
alignItems: 'stretch',
borderRadius: 4,
border: `1px solid ${colors_1.INPUT_BORDER_COLOR_UNHOVERED}`,
backgroundColor: colors_1.INPUT_BACKGROUND,
overflow: 'hidden',
};
const mainButtonStyle = {
paddingLeft: 7,
paddingRight: 7,
paddingTop: 7,
paddingBottom: 7,
background: 'transparent',
border: 'none',
color: 'white',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
fontSize: 14,
fontFamily: 'inherit',
};
const dividerStyle = {
width: 1,
backgroundColor: colors_1.INPUT_BORDER_COLOR_UNHOVERED,
alignSelf: 'stretch',
};
const dropdownTriggerStyle = {
paddingLeft: 6,
paddingRight: 6,
paddingTop: 7,
paddingBottom: 7,
background: 'transparent',
border: 'none',
color: 'white',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
};
const mainButtonContent = {
paddingLeft: 4,
paddingRight: 6,
};
const label = {
fontSize: 14,
};
const RENDER_TYPE_STORAGE_KEY = 'remotion.renderType';
const getInitialRenderType = (readOnlyStudio) => {
if (!show_browser_rendering_1.SHOW_BROWSER_RENDERING) {
return readOnlyStudio ? 'render-command' : 'server-render';
}
if (readOnlyStudio) {
return 'client-render';
}
try {
const stored = localStorage.getItem(RENDER_TYPE_STORAGE_KEY);
if (stored === 'server-render' || stored === 'client-render') {
return stored;
}
}
catch (_a) {
// localStorage might not be available
}
return 'server-render';
};
const RenderButton = ({ readOnlyStudio, }) => {
const { inFrame, outFrame } = (0, in_out_1.useTimelineInOutFramePosition)();
const { setSelectedModal } = (0, react_1.useContext)(modals_1.ModalsContext);
const [preferredRenderType, setPreferredRenderType] = (0, react_1.useState)(() => getInitialRenderType(readOnlyStudio));
const [dropdownOpened, setDropdownOpened] = (0, react_1.useState)(false);
const dropdownRef = (0, react_1.useRef)(null);
const containerRef = (0, react_1.useRef)(null);
const { currentZIndex } = (0, z_index_1.useZIndex)();
const size = player_1.PlayerInternals.useElementSize(dropdownRef, {
triggerOnWindowResize: true,
shouldApplyCssTransforms: true,
});
const refresh = size === null || size === void 0 ? void 0 : size.refresh;
const onPointerDown = (0, react_1.useCallback)(() => {
setDropdownOpened((o) => {
if (!o) {
refresh === null || refresh === void 0 ? void 0 : refresh();
}
return !o;
});
}, [refresh]);
const onClickDropdown = (0, react_1.useCallback)((e) => {
e.stopPropagation();
const isKeyboardInitiated = e.detail === 0;
if (!isKeyboardInitiated) {
return;
}
setDropdownOpened((o) => {
if (!o) {
refresh === null || refresh === void 0 ? void 0 : refresh();
window.addEventListener('pointerup', (evt) => {
if (!(0, is_menu_item_1.isMenuItem)(evt.target)) {
setDropdownOpened(false);
}
}, {
once: true,
});
}
return !o;
});
}, [refresh]);
const connectionStatus = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx)
.previewServerState.type;
const canServerRender = connectionStatus === 'connected';
const canRender = canServerRender || show_browser_rendering_1.SHOW_BROWSER_RENDERING || readOnlyStudio;
const renderType = (0, react_1.useMemo)(() => {
if (readOnlyStudio) {
if (!show_browser_rendering_1.SHOW_BROWSER_RENDERING) {
return 'render-command';
}
return preferredRenderType === 'render-command'
? 'render-command'
: 'client-render';
}
if (connectionStatus === 'disconnected' && show_browser_rendering_1.SHOW_BROWSER_RENDERING) {
return 'client-render';
}
if (!show_browser_rendering_1.SHOW_BROWSER_RENDERING) {
return 'server-render';
}
return preferredRenderType;
}, [connectionStatus, preferredRenderType, readOnlyStudio]);
const shortcut = (0, use_keybinding_1.areKeyboardShortcutsDisabled)() ? '' : '(R)';
const tooltip = renderType === 'render-command'
? 'Copy a CLI command to render this composition ' + shortcut
: canRender
? 'Export the current composition ' + shortcut
: 'Connect to the Studio server to render';
const iconStyle = (0, react_1.useMemo)(() => {
return {
style: {
height: 16,
color: 'currentColor',
},
};
}, []);
const video = remotion_1.Internals.useVideo();
const getCurrentFrame = player_1.PlayerInternals.useFrameImperative();
const { props } = (0, react_1.useContext)(remotion_1.Internals.EditorPropsContext);
const openServerRenderModal = (0, react_1.useCallback)((copyCommandOnly) => {
var _a;
var _b, _c;
if (!video) {
return null;
}
const defaults = window.remotion_renderDefaults;
if (!defaults) {
throw new TypeError('Expected defaults');
}
setSelectedModal({
type: 'server-render',
readOnlyStudio: copyCommandOnly,
compositionId: video.id,
initialFrame: getCurrentFrame(),
initialStillImageFormat: defaults.stillImageFormat,
initialVideoImageFormat: null,
initialJpegQuality: defaults.jpegQuality,
initialScale: (_b = (_a = window.remotion_renderDefaults) === null || _a === void 0 ? void 0 : _a.scale) !== null && _b !== void 0 ? _b : 1,
initialLogLevel: defaults.logLevel,
initialConcurrency: defaults.concurrency,
maxConcurrency: defaults.maxConcurrency,
minConcurrency: defaults.minConcurrency,
initialMuted: defaults.muted,
initialEnforceAudioTrack: defaults.enforceAudioTrack,
initialProResProfile: defaults.proResProfile,
initialx264Preset: defaults.x264Preset,
initialPixelFormat: null,
initialAudioBitrate: defaults.audioBitrate,
initialVideoBitrate: defaults.videoBitrate,
initialEveryNthFrame: defaults.everyNthFrame,
initialNumberOfGifLoops: defaults.numberOfGifLoops,
initialDelayRenderTimeout: defaults.delayRenderTimeout,
defaultConfigurationAudioCodec: defaults.audioCodec,
initialEnvVariables: window.process.env,
initialDisableWebSecurity: defaults.disableWebSecurity,
initialDarkMode: defaults.darkMode,
initialOpenGlRenderer: defaults.openGlRenderer,
initialHeadless: defaults.headless,
initialIgnoreCertificateErrors: defaults.ignoreCertificateErrors,
initialOffthreadVideoCacheSizeInBytes: defaults.offthreadVideoCacheSizeInBytes,
initialOffthreadVideoThreads: defaults.offthreadVideoThreads,
defaultProps: (_c = props[video.id]) !== null && _c !== void 0 ? _c : video.defaultProps,
inFrameMark: inFrame,
outFrameMark: outFrame,
initialColorSpace: defaults.colorSpace,
initialMultiProcessOnLinux: defaults.multiProcessOnLinux,
defaultConfigurationVideoCodec: defaults.codec,
initialEncodingBufferSize: defaults.encodingBufferSize,
initialEncodingMaxRate: defaults.encodingMaxRate,
initialUserAgent: defaults.userAgent,
initialBeep: defaults.beepOnFinish,
initialRepro: defaults.repro,
initialForSeamlessAacConcatenation: defaults.forSeamlessAacConcatenation,
renderTypeOfLastRender: null,
defaulMetadata: defaults.metadata,
initialHardwareAcceleration: defaults.hardwareAcceleration,
initialChromeMode: defaults.chromeMode,
initialMediaCacheSizeInBytes: defaults.mediaCacheSizeInBytes,
renderDefaults: defaults,
});
}, [video, setSelectedModal, getCurrentFrame, props, inFrame, outFrame]);
const openClientRenderModal = (0, react_1.useCallback)(() => {
var _a;
if (!video) {
return null;
}
const defaults = window.remotion_renderDefaults;
if (!defaults) {
throw new TypeError('Expected defaults');
}
setSelectedModal({
type: 'web-render',
compositionId: video.id,
initialFrame: getCurrentFrame(),
defaultProps: (_a = props[video.id]) !== null && _a !== void 0 ? _a : video.defaultProps,
inFrameMark: inFrame,
outFrameMark: outFrame,
initialLogLevel: defaults.logLevel,
initialLicenseKey: defaults.publicLicenseKey,
initialStillImageFormat: defaults.stillImageFormat,
initialScale: defaults.scale,
initialDelayRenderTimeout: defaults.delayRenderTimeout,
initialDefaultOutName: null,
initialContainer: null,
initialVideoCodec: null,
initialAudioCodec: null,
initialAudioBitrate: null,
initialVideoBitrate: null,
initialHardwareAcceleration: null,
initialKeyframeIntervalInSeconds: null,
initialTransparent: null,
initialMuted: null,
initialMediaCacheSizeInBytes: defaults.mediaCacheSizeInBytes,
});
}, [video, setSelectedModal, getCurrentFrame, props, inFrame, outFrame]);
const onClick = (0, react_1.useCallback)(() => {
if (renderType === 'render-command') {
openServerRenderModal(true);
return;
}
if (!show_browser_rendering_1.SHOW_BROWSER_RENDERING || renderType === 'server-render') {
openServerRenderModal(false);
}
else {
openClientRenderModal();
}
}, [renderType, openServerRenderModal, openClientRenderModal]);
const onHideDropdown = (0, react_1.useCallback)(() => {
setDropdownOpened(false);
}, []);
const handleRenderTypeChange = (0, react_1.useCallback)((newType) => {
setPreferredRenderType(newType);
try {
localStorage.setItem(RENDER_TYPE_STORAGE_KEY, newType);
}
catch (_a) {
// localStorage might not be available
}
setDropdownOpened(false);
if (newType === 'server-render') {
openServerRenderModal(false);
}
else if (newType === 'render-command') {
openServerRenderModal(true);
}
else {
openClientRenderModal();
}
}, [openClientRenderModal, openServerRenderModal]);
const dropdownValues = (0, react_1.useMemo)(() => {
if (readOnlyStudio) {
return [
{
type: 'item',
id: 'client-render',
label: 'Render on web',
value: 'client-render',
onClick: () => handleRenderTypeChange('client-render'),
keyHint: null,
leftItem: null,
subMenu: null,
quickSwitcherLabel: null,
},
{
type: 'item',
id: 'render-command',
label: 'Render via CLI',
value: 'render-command',
onClick: () => handleRenderTypeChange('render-command'),
keyHint: null,
leftItem: null,
subMenu: null,
quickSwitcherLabel: null,
},
];
}
return [
{
type: 'item',
id: 'server-render',
label: 'Server-side render',
value: 'server-render',
onClick: () => handleRenderTypeChange('server-render'),
keyHint: null,
leftItem: null,
subMenu: null,
quickSwitcherLabel: null,
},
{
type: 'item',
id: 'client-render',
label: 'Client-side render',
value: 'client-render',
onClick: () => handleRenderTypeChange('client-render'),
keyHint: null,
leftItem: null,
subMenu: null,
quickSwitcherLabel: null,
},
];
}, [handleRenderTypeChange, readOnlyStudio]);
const spaceToBottom = (0, react_1.useMemo)(() => {
const margin = 10;
if (size && dropdownOpened) {
return size.windowSize.height - (size.top + size.height) - margin;
}
return 0;
}, [dropdownOpened, size]);
const spaceToTop = (0, react_1.useMemo)(() => {
const margin = 10;
if (size && dropdownOpened) {
return size.top - margin;
}
return 0;
}, [dropdownOpened, size]);
const derivedMaxHeight = (0, react_1.useMemo)(() => {
return spaceToTop > spaceToBottom ? spaceToTop : spaceToBottom;
}, [spaceToBottom, spaceToTop]);
const portalStyle = (0, react_1.useMemo)(() => {
if (!dropdownOpened || !size) {
return null;
}
const verticalLayout = spaceToTop > spaceToBottom ? 'bottom' : 'top';
return {
...(verticalLayout === 'top'
? {
...styles_1.menuContainerTowardsBottom,
top: size.top + size.height,
}
: {
...styles_1.menuContainerTowardsTop,
bottom: size.windowSize.height - size.top,
}),
right: size.windowSize.width - size.left - size.width,
};
}, [dropdownOpened, size, spaceToBottom, spaceToTop]);
const containerStyle = (0, react_1.useMemo)(() => {
return {
...splitButtonContainer,
borderColor: colors_1.INPUT_BORDER_COLOR_UNHOVERED,
opacity: canRender ? 1 : 0.7,
cursor: canRender ? 'pointer' : 'inherit',
};
}, [canRender]);
const renderLabel = renderType === 'server-render'
? 'Render'
: renderType === 'render-command'
? 'Render via CLI'
: 'Render on web';
const shouldShowDropdown = (0, react_1.useMemo)(() => {
if (readOnlyStudio) {
return show_browser_rendering_1.SHOW_BROWSER_RENDERING;
}
if (!show_browser_rendering_1.SHOW_BROWSER_RENDERING) {
return false;
}
return true;
}, [readOnlyStudio]);
if (!video) {
return null;
}
return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
jsx_runtime_1.jsx("button", { style: { display: 'none' }, id: "render-modal-button-server", disabled: !canServerRender, onClick: () => openServerRenderModal(false), type: "button" }), ' ', jsx_runtime_1.jsx("button", { style: { display: 'none' }, id: "render-modal-button-client", onClick: openClientRenderModal, type: "button" }), jsx_runtime_1.jsxs("div", { ref: containerRef, style: containerStyle, title: tooltip, children: [
jsx_runtime_1.jsx("button", { type: "button", style: mainButtonStyle, onClick: onClick, id: "render-modal-button", disabled: !canRender, children: jsx_runtime_1.jsxs(layout_1.Row, { align: "center", style: mainButtonContent, children: [
jsx_runtime_1.jsx(render_1.ThinRenderIcon, { fill: "currentcolor", svgProps: iconStyle }), jsx_runtime_1.jsx(layout_1.Spacing, { x: 1 }), jsx_runtime_1.jsx("span", { style: label, children: renderLabel })
] }) }), shouldShowDropdown ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
jsx_runtime_1.jsx("div", { style: dividerStyle }), jsx_runtime_1.jsx("button", { ref: dropdownRef, type: "button", style: dropdownTriggerStyle, disabled: !readOnlyStudio && connectionStatus !== 'connected', className: is_menu_item_1.MENU_INITIATOR_CLASSNAME, onPointerDown: onPointerDown, onClick: onClickDropdown, children: jsx_runtime_1.jsx(caret_1.CaretDown, {}) })
] })) : null] }), portalStyle
? react_dom_1.default.createPortal(jsx_runtime_1.jsx("div", { style: styles_1.fullScreenOverlay, children: jsx_runtime_1.jsx("div", { style: styles_1.outerPortal, className: "css-reset", children: jsx_runtime_1.jsx(z_index_1.HigherZIndex, { onOutsideClick: onHideDropdown, onEscape: onHideDropdown, children: jsx_runtime_1.jsx("div", { style: portalStyle, children: jsx_runtime_1.jsx(MenuContent_1.MenuContent, { onNextMenu: () => { }, onPreviousMenu: () => { }, values: dropdownValues, onHide: onHideDropdown, leaveLeftSpace: false, preselectIndex: dropdownValues.findIndex((v) => v.id === renderType), topItemCanBeUnselected: false, fixedHeight: derivedMaxHeight }) }) }) }) }), (0, portals_1.getPortal)(currentZIndex))
: null] }));
};
exports.RenderButton = RenderButton;