refactor: improve goView video/iframe import + editor parity
This commit is contained in:
parent
e111184acc
commit
54f3cab08f
@ -502,15 +502,18 @@ function NodeView(props: {
|
|||||||
>
|
>
|
||||||
<iframe
|
<iframe
|
||||||
src={node.props.src}
|
src={node.props.src}
|
||||||
|
allow={node.props.allow}
|
||||||
|
sandbox={node.props.sandbox}
|
||||||
width={rect.w}
|
width={rect.w}
|
||||||
height={rect.h}
|
height={rect.h}
|
||||||
style={{
|
style={{
|
||||||
border: 0,
|
border: 0,
|
||||||
display: 'block',
|
display: 'block',
|
||||||
|
objectFit: node.props.fit,
|
||||||
// Editor parity: iframes steal pointer events; disable so selection/context menu works.
|
// Editor parity: iframes steal pointer events; disable so selection/context menu works.
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
}}
|
}}
|
||||||
title={node.id}
|
title={node.props.title ?? node.id}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -530,9 +533,11 @@ function NodeView(props: {
|
|||||||
width={rect.w}
|
width={rect.w}
|
||||||
height={rect.h}
|
height={rect.h}
|
||||||
autoPlay={node.props.autoplay ?? false}
|
autoPlay={node.props.autoplay ?? false}
|
||||||
|
controls={node.props.controls ?? false}
|
||||||
playsInline
|
playsInline
|
||||||
loop={node.props.loop ?? false}
|
loop={node.props.loop ?? false}
|
||||||
muted={node.props.muted ?? false}
|
muted={node.props.muted ?? false}
|
||||||
|
poster={node.props.poster}
|
||||||
style={{
|
style={{
|
||||||
display: 'block',
|
display: 'block',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
|||||||
@ -93,6 +93,19 @@ export function EditorApp() {
|
|||||||
|
|
||||||
const ctxMenuSyncedRef = useRef(false);
|
const ctxMenuSyncedRef = useRef(false);
|
||||||
|
|
||||||
|
const dispatchWithMenuSelection = useCallback(
|
||||||
|
(action: Parameters<typeof dispatch>[0]) => {
|
||||||
|
if (ctxMenu) {
|
||||||
|
const currentKey = selectionKeyOf(state.selection.ids);
|
||||||
|
if (currentKey !== ctxMenu.selectionKey) {
|
||||||
|
dispatch({ type: 'setSelection', ids: ctxMenu.selectionIds });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dispatch(action);
|
||||||
|
},
|
||||||
|
[ctxMenu, dispatch, selectionKeyOf, state.selection.ids],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Reset the sync gate whenever a new context menu opens/closes.
|
// Reset the sync gate whenever a new context menu opens/closes.
|
||||||
ctxMenuSyncedRef.current = false;
|
ctxMenuSyncedRef.current = false;
|
||||||
@ -509,7 +522,7 @@ export function EditorApp() {
|
|||||||
: additive
|
: additive
|
||||||
? [...state.selection.ids, node.id]
|
? [...state.selection.ids, node.id]
|
||||||
: [node.id];
|
: [node.id];
|
||||||
const selectionKey = nextSelectionIds.join('|');
|
const selectionKey = selectionKeyOf(nextSelectionIds);
|
||||||
if (!state.selection.ids.includes(node.id)) {
|
if (!state.selection.ids.includes(node.id)) {
|
||||||
if (additive) {
|
if (additive) {
|
||||||
dispatch({ type: 'toggleSelect', id: node.id });
|
dispatch({ type: 'toggleSelect', id: node.id });
|
||||||
@ -566,12 +579,12 @@ export function EditorApp() {
|
|||||||
onAddTextAt={(x, y) => dispatch({ type: 'addTextAt', x, y })}
|
onAddTextAt={(x, y) => dispatch({ type: 'addTextAt', x, y })}
|
||||||
onSelectSingle={(id) => dispatch({ type: 'selectSingle', id })}
|
onSelectSingle={(id) => dispatch({ type: 'selectSingle', id })}
|
||||||
onSelectAll={() => dispatch({ type: 'selectAll' })}
|
onSelectAll={() => dispatch({ type: 'selectAll' })}
|
||||||
onDuplicateSelected={() => dispatch({ type: 'duplicateSelected' })}
|
onDuplicateSelected={() => dispatchWithMenuSelection({ type: 'duplicateSelected' })}
|
||||||
onToggleLockSelected={() => dispatch({ type: 'toggleLockSelected' })}
|
onToggleLockSelected={() => dispatchWithMenuSelection({ type: 'toggleLockSelected' })}
|
||||||
onToggleHideSelected={() => dispatch({ type: 'toggleHideSelected' })}
|
onToggleHideSelected={() => dispatchWithMenuSelection({ type: 'toggleHideSelected' })}
|
||||||
onBringToFrontSelected={() => dispatch({ type: 'bringToFrontSelected' })}
|
onBringToFrontSelected={() => dispatchWithMenuSelection({ type: 'bringToFrontSelected' })}
|
||||||
onSendToBackSelected={() => dispatch({ type: 'sendToBackSelected' })}
|
onSendToBackSelected={() => dispatchWithMenuSelection({ type: 'sendToBackSelected' })}
|
||||||
onDeleteSelected={() => dispatch({ type: 'deleteSelected' })}
|
onDeleteSelected={() => dispatchWithMenuSelection({ type: 'deleteSelected' })}
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -28,6 +28,7 @@ export type EditorAction =
|
|||||||
| { type: 'updatePan'; current: { screenX: number; screenY: number } }
|
| { type: 'updatePan'; current: { screenX: number; screenY: number } }
|
||||||
| { type: 'endPan' }
|
| { type: 'endPan' }
|
||||||
| { type: 'selectSingle'; id?: string }
|
| { type: 'selectSingle'; id?: string }
|
||||||
|
| { type: 'setSelection'; ids: string[] }
|
||||||
| { type: 'selectAll' }
|
| { type: 'selectAll' }
|
||||||
| { type: 'toggleSelect'; id: string }
|
| { type: 'toggleSelect'; id: string }
|
||||||
| {
|
| {
|
||||||
@ -494,6 +495,11 @@ export function editorReducer(state: EditorRuntimeState, action: EditorAction):
|
|||||||
case 'selectSingle':
|
case 'selectSingle':
|
||||||
return { ...state, selection: { ids: action.id ? [action.id] : [] } };
|
return { ...state, selection: { ids: action.id ? [action.id] : [] } };
|
||||||
|
|
||||||
|
case 'setSelection': {
|
||||||
|
const ids = Array.from(new Set(action.ids));
|
||||||
|
return { ...state, selection: { ids } };
|
||||||
|
}
|
||||||
|
|
||||||
case 'selectAll':
|
case 'selectAll':
|
||||||
return { ...state, selection: { ids: state.doc.screen.nodes.map((n) => n.id) } };
|
return { ...state, selection: { ids: state.doc.screen.nodes.map((n) => n.id) } };
|
||||||
|
|
||||||
|
|||||||
@ -284,6 +284,44 @@ function hasAnyKeyDeep(input: unknown, keys: string[], depth = 2): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function containsVideoHtmlDeep(input: unknown, depth = 2): boolean {
|
||||||
|
if (!input || typeof input !== 'object') return false;
|
||||||
|
const obj = input as Record<string, unknown>;
|
||||||
|
|
||||||
|
for (const key of [
|
||||||
|
'srcdoc',
|
||||||
|
'srcDoc',
|
||||||
|
'html',
|
||||||
|
'htmlContent',
|
||||||
|
'htmlString',
|
||||||
|
'iframeHtml',
|
||||||
|
'embedHtml',
|
||||||
|
'embedCode',
|
||||||
|
'iframeCode',
|
||||||
|
'iframeEmbed',
|
||||||
|
'embed',
|
||||||
|
'code',
|
||||||
|
'content',
|
||||||
|
'template',
|
||||||
|
]) {
|
||||||
|
const v = obj[key];
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
const s = v.trim();
|
||||||
|
if (s.startsWith('<') && s.includes('>') && (/\bvideo\b/i.test(s) || /\bsource\b/i.test(s))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depth <= 0) return false;
|
||||||
|
|
||||||
|
for (const nestKey of ['style', 'config', 'option', 'options', 'props', 'dataset', 'data']) {
|
||||||
|
if (containsVideoHtmlDeep(obj[nestKey], depth - 1)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function looksLikeIframeOption(option: unknown): boolean {
|
function looksLikeIframeOption(option: unknown): boolean {
|
||||||
if (!option) return false;
|
if (!option) return false;
|
||||||
|
|
||||||
@ -302,6 +340,10 @@ function looksLikeIframeOption(option: unknown): boolean {
|
|||||||
|
|
||||||
// Prefer explicit iframe-ish keys when option is an object (including nested shapes).
|
// Prefer explicit iframe-ish keys when option is an object (including nested shapes).
|
||||||
if (typeof option === 'object') {
|
if (typeof option === 'object') {
|
||||||
|
// If the embed/html content is clearly a <video> snippet, don't classify as iframe.
|
||||||
|
// (Some exporters put a <video> tag under fields like `html` / `code`.)
|
||||||
|
if (containsVideoHtmlDeep(option, 2)) return false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
hasAnyKeyDeep(option, ['iframeUrl', 'iframeSrc', 'embedUrl', 'frameUrl', 'frameSrc'], 2) ||
|
hasAnyKeyDeep(option, ['iframeUrl', 'iframeSrc', 'embedUrl', 'frameUrl', 'frameSrc'], 2) ||
|
||||||
// Some exports store raw HTML instead of a URL.
|
// Some exports store raw HTML instead of a URL.
|
||||||
@ -406,6 +448,9 @@ function looksLikeVideoOption(option: unknown): boolean {
|
|||||||
'autoplay',
|
'autoplay',
|
||||||
'autoPlay',
|
'autoPlay',
|
||||||
'isAutoPlay',
|
'isAutoPlay',
|
||||||
|
'controls',
|
||||||
|
'showControls',
|
||||||
|
'showControl',
|
||||||
// common UI fields
|
// common UI fields
|
||||||
'poster',
|
'poster',
|
||||||
'posterUrl',
|
'posterUrl',
|
||||||
@ -531,6 +576,22 @@ function pickSizeLike(option: unknown): { w?: number; h?: number } {
|
|||||||
return { w, h };
|
return { w, h };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pickAspectRatioLike(option: unknown): number | undefined {
|
||||||
|
const ratio = pickNumberLike(option, ['aspectRatio', 'aspect', 'ratio', 'aspect_ratio']);
|
||||||
|
if (ratio === undefined) return undefined;
|
||||||
|
return ratio > 0 ? ratio : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAspectRatioToSize(
|
||||||
|
size: { w?: number; h?: number },
|
||||||
|
aspectRatio?: number,
|
||||||
|
): { w?: number; h?: number } {
|
||||||
|
if (!aspectRatio || aspectRatio <= 0) return size;
|
||||||
|
if (size.w && !size.h) return { ...size, h: Math.round(size.w / aspectRatio) };
|
||||||
|
if (size.h && !size.w) return { ...size, w: Math.round(size.h * aspectRatio) };
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeRect(
|
function normalizeRect(
|
||||||
rect: { x: number; y: number; w: number; h: number },
|
rect: { x: number; y: number; w: number; h: number },
|
||||||
fallback: { w: number; h: number },
|
fallback: { w: number; h: number },
|
||||||
@ -654,7 +715,7 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
|
|||||||
? { w: 480, h: 270 }
|
? { w: 480, h: 270 }
|
||||||
: { w: 320, h: 60 };
|
: { w: 320, h: 60 };
|
||||||
|
|
||||||
const optSize = pickSizeLike(option);
|
const optSize = applyAspectRatioToSize(pickSizeLike(option), pickAspectRatioLike(option));
|
||||||
|
|
||||||
const attr = c.attr as unknown as Record<string, unknown> | undefined;
|
const attr = c.attr as unknown as Record<string, unknown> | undefined;
|
||||||
const rawRect = attr
|
const rawRect = attr
|
||||||
|
|||||||
@ -64,6 +64,11 @@ export interface IframeWidgetNode extends WidgetNodeBase {
|
|||||||
type: 'iframe';
|
type: 'iframe';
|
||||||
props: {
|
props: {
|
||||||
src: string;
|
src: string;
|
||||||
|
allow?: string;
|
||||||
|
sandbox?: string;
|
||||||
|
title?: string;
|
||||||
|
fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
||||||
|
aspectRatio?: number;
|
||||||
borderRadius?: number;
|
borderRadius?: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -73,9 +78,12 @@ export interface VideoWidgetNode extends WidgetNodeBase {
|
|||||||
props: {
|
props: {
|
||||||
src: string;
|
src: string;
|
||||||
autoplay?: boolean;
|
autoplay?: boolean;
|
||||||
|
controls?: boolean;
|
||||||
loop?: boolean;
|
loop?: boolean;
|
||||||
muted?: boolean;
|
muted?: boolean;
|
||||||
|
poster?: string;
|
||||||
fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
||||||
|
aspectRatio?: number;
|
||||||
borderRadius?: number;
|
borderRadius?: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,16 @@ export interface GoViewIframeOption {
|
|||||||
sourceList?: unknown;
|
sourceList?: unknown;
|
||||||
urlList?: unknown;
|
urlList?: unknown;
|
||||||
|
|
||||||
|
allow?: unknown;
|
||||||
|
sandbox?: unknown;
|
||||||
|
title?: unknown;
|
||||||
|
|
||||||
|
fit?: unknown;
|
||||||
|
objectFit?: unknown;
|
||||||
|
|
||||||
|
aspectRatio?: unknown;
|
||||||
|
ratio?: unknown;
|
||||||
|
|
||||||
borderRadius?: number;
|
borderRadius?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +74,20 @@ function toMaybeNumber(v: unknown): number | undefined {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toMaybeString(v: unknown): string | undefined {
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
const s = v.trim();
|
||||||
|
return s ? s : undefined;
|
||||||
|
}
|
||||||
|
if (!v) return undefined;
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
const parts = v.filter((item): item is string => typeof item === 'string' && item.trim().length > 0);
|
||||||
|
if (!parts.length) return undefined;
|
||||||
|
return parts.join('; ');
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function pickFromNested<T>(
|
function pickFromNested<T>(
|
||||||
input: unknown,
|
input: unknown,
|
||||||
picker: (obj: Record<string, unknown>) => T | undefined,
|
picker: (obj: Record<string, unknown>) => T | undefined,
|
||||||
@ -84,6 +108,12 @@ function pickFromNested<T>(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function asString(v: unknown): string {
|
||||||
|
if (typeof v === 'string') return v;
|
||||||
|
if (!v) return '';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function pickFirstUrlFromList(input: unknown): string {
|
function pickFirstUrlFromList(input: unknown): string {
|
||||||
if (!input) return '';
|
if (!input) return '';
|
||||||
if (typeof input === 'string') return input;
|
if (typeof input === 'string') return input;
|
||||||
@ -161,6 +191,44 @@ function pickSrc(option: GoViewIframeOption): string {
|
|||||||
return listUrl;
|
return listUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pickFit(option: GoViewIframeOption): IframeWidgetNode['props']['fit'] | undefined {
|
||||||
|
const raw = asString(option.fit) || asString(option.objectFit);
|
||||||
|
if (!raw) return undefined;
|
||||||
|
const v = raw.toLowerCase();
|
||||||
|
if (v === 'contain' || v === 'cover' || v === 'fill' || v === 'none' || v === 'scale-down') {
|
||||||
|
return v as IframeWidgetNode['props']['fit'];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickAspectRatio(option: GoViewIframeOption): number | undefined {
|
||||||
|
const direct = toMaybeNumber(option.aspectRatio ?? option.ratio);
|
||||||
|
if (direct !== undefined && direct > 0) return direct;
|
||||||
|
const nested = pickFromNested(option, (obj) => toMaybeNumber(obj.aspectRatio ?? obj.aspect ?? obj.ratio), 2);
|
||||||
|
if (nested !== undefined && nested > 0) return nested;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickStringLike(option: GoViewIframeOption, keys: string[]): string | undefined {
|
||||||
|
const direct = toMaybeString(
|
||||||
|
keys
|
||||||
|
.map((key) => (option as Record<string, unknown>)[key])
|
||||||
|
.find((v) => toMaybeString(v) !== undefined),
|
||||||
|
);
|
||||||
|
if (direct) return direct;
|
||||||
|
return pickFromNested(
|
||||||
|
option,
|
||||||
|
(obj) => {
|
||||||
|
for (const key of keys) {
|
||||||
|
const v = toMaybeString(obj[key]);
|
||||||
|
if (v) return v;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function pickBorderRadius(option: GoViewIframeOption): number | undefined {
|
function pickBorderRadius(option: GoViewIframeOption): number | undefined {
|
||||||
const direct = toMaybeNumber(option.borderRadius);
|
const direct = toMaybeNumber(option.borderRadius);
|
||||||
if (direct !== undefined) return direct;
|
if (direct !== undefined) return direct;
|
||||||
@ -170,6 +238,11 @@ function pickBorderRadius(option: GoViewIframeOption): number | undefined {
|
|||||||
export function convertGoViewIframeOptionToNodeProps(option: GoViewIframeOption): IframeWidgetNode['props'] {
|
export function convertGoViewIframeOptionToNodeProps(option: GoViewIframeOption): IframeWidgetNode['props'] {
|
||||||
return {
|
return {
|
||||||
src: pickSrc(option),
|
src: pickSrc(option),
|
||||||
|
allow: pickStringLike(option, ['allow', 'allowList', 'permissions', 'permission']),
|
||||||
|
sandbox: pickStringLike(option, ['sandbox', 'sandboxList']),
|
||||||
|
title: pickStringLike(option, ['title', 'name', 'label']),
|
||||||
|
fit: pickFit(option),
|
||||||
|
aspectRatio: pickAspectRatio(option),
|
||||||
borderRadius: pickBorderRadius(option),
|
borderRadius: pickBorderRadius(option),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,12 +22,24 @@ export interface GoViewVideoOption {
|
|||||||
autoPlay?: boolean;
|
autoPlay?: boolean;
|
||||||
isAutoPlay?: boolean;
|
isAutoPlay?: boolean;
|
||||||
|
|
||||||
|
controls?: unknown;
|
||||||
|
|
||||||
loop?: boolean;
|
loop?: boolean;
|
||||||
muted?: boolean;
|
muted?: boolean;
|
||||||
|
|
||||||
|
poster?: unknown;
|
||||||
|
posterUrl?: unknown;
|
||||||
|
cover?: unknown;
|
||||||
|
coverUrl?: unknown;
|
||||||
|
thumbnail?: unknown;
|
||||||
|
thumbnailUrl?: unknown;
|
||||||
|
|
||||||
fit?: unknown;
|
fit?: unknown;
|
||||||
objectFit?: unknown;
|
objectFit?: unknown;
|
||||||
|
|
||||||
|
aspectRatio?: unknown;
|
||||||
|
ratio?: unknown;
|
||||||
|
|
||||||
borderRadius?: number;
|
borderRadius?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,6 +268,18 @@ function pickAutoplay(option: GoViewVideoOption): boolean | undefined {
|
|||||||
return pickBooleanLike(option, ['autoplay', 'autoPlay', 'auto_play', 'isAutoPlay']);
|
return pickBooleanLike(option, ['autoplay', 'autoPlay', 'auto_play', 'isAutoPlay']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pickControls(option: GoViewVideoOption): boolean | undefined {
|
||||||
|
return pickBooleanLike(option, [
|
||||||
|
'controls',
|
||||||
|
'showControls',
|
||||||
|
'showControl',
|
||||||
|
'controlsVisible',
|
||||||
|
'showControlBar',
|
||||||
|
'controlBar',
|
||||||
|
'showBar',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
function pickFitFromNested(option: GoViewVideoOption): string {
|
function pickFitFromNested(option: GoViewVideoOption): string {
|
||||||
const direct = asString(option.fit) || asString(option.objectFit);
|
const direct = asString(option.fit) || asString(option.objectFit);
|
||||||
if (direct) return direct;
|
if (direct) return direct;
|
||||||
@ -277,13 +301,66 @@ function pickFit(option: GoViewVideoOption): VideoWidgetNode['props']['fit'] | u
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pickPoster(option: GoViewVideoOption): string | undefined {
|
||||||
|
const direct =
|
||||||
|
pickUrlLike({
|
||||||
|
poster: option.poster,
|
||||||
|
posterUrl: option.posterUrl,
|
||||||
|
cover: option.cover,
|
||||||
|
coverUrl: option.coverUrl,
|
||||||
|
thumbnail: option.thumbnail,
|
||||||
|
thumbnailUrl: option.thumbnailUrl,
|
||||||
|
}) ?? '';
|
||||||
|
if (direct) return direct;
|
||||||
|
|
||||||
|
return pickFromNested(
|
||||||
|
option,
|
||||||
|
(obj) => {
|
||||||
|
for (const key of [
|
||||||
|
'poster',
|
||||||
|
'posterUrl',
|
||||||
|
'posterURL',
|
||||||
|
'cover',
|
||||||
|
'coverUrl',
|
||||||
|
'coverURL',
|
||||||
|
'thumbnail',
|
||||||
|
'thumbnailUrl',
|
||||||
|
'preview',
|
||||||
|
'previewImage',
|
||||||
|
'previewUrl',
|
||||||
|
'posterImage',
|
||||||
|
]) {
|
||||||
|
const v = obj[key];
|
||||||
|
if (typeof v === 'string' && v) return v;
|
||||||
|
if (typeof v === 'object') {
|
||||||
|
const url = pickUrlLike(v, 1);
|
||||||
|
if (url) return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickAspectRatio(option: GoViewVideoOption): number | undefined {
|
||||||
|
const direct = toMaybeNumber(option.aspectRatio ?? option.ratio);
|
||||||
|
if (direct !== undefined && direct > 0) return direct;
|
||||||
|
const nested = pickFromNested(option, (obj) => toMaybeNumber(obj.aspectRatio ?? obj.aspect ?? obj.ratio), 2);
|
||||||
|
if (nested !== undefined && nested > 0) return nested;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function convertGoViewVideoOptionToNodeProps(option: GoViewVideoOption): VideoWidgetNode['props'] {
|
export function convertGoViewVideoOptionToNodeProps(option: GoViewVideoOption): VideoWidgetNode['props'] {
|
||||||
return {
|
return {
|
||||||
src: pickSrc(option),
|
src: pickSrc(option),
|
||||||
autoplay: pickAutoplay(option),
|
autoplay: pickAutoplay(option),
|
||||||
|
controls: pickControls(option),
|
||||||
loop: pickBooleanLike(option, ['loop', 'isLoop']),
|
loop: pickBooleanLike(option, ['loop', 'isLoop']),
|
||||||
muted: pickBooleanLike(option, ['muted', 'isMuted', 'mute']),
|
muted: pickBooleanLike(option, ['muted', 'isMuted', 'mute']),
|
||||||
|
poster: pickPoster(option),
|
||||||
fit: pickFit(option),
|
fit: pickFit(option),
|
||||||
|
aspectRatio: pickAspectRatio(option),
|
||||||
borderRadius: pickBorderRadius(option),
|
borderRadius: pickBorderRadius(option),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user