fix: improve legacy media imports and editor context menu parity
This commit is contained in:
parent
5b708faf7b
commit
3e353c6322
@ -65,6 +65,23 @@ export function Canvas(props: CanvasProps) {
|
||||
const selectionAllLocked = selection.length > 0 && selection.every((n) => n.locked);
|
||||
const selectionAllHidden = selection.length > 0 && selection.every((n) => n.hidden);
|
||||
|
||||
const ctxMenuPos = useMemo(() => {
|
||||
if (!ctx) return null;
|
||||
|
||||
// Rough clamp so the menu stays inside the viewport.
|
||||
// (We don't measure actual size to keep this simple + stable.)
|
||||
const w = 220;
|
||||
const h = 320;
|
||||
|
||||
const vw = typeof window === 'undefined' ? 10_000 : window.innerWidth;
|
||||
const vh = typeof window === 'undefined' ? 10_000 : window.innerHeight;
|
||||
|
||||
return {
|
||||
x: Math.max(8, Math.min(ctx.clientX, vw - w - 8)),
|
||||
y: Math.max(8, Math.min(ctx.clientY, vh - h - 8)),
|
||||
};
|
||||
}, [ctx]);
|
||||
|
||||
const clientToCanvas = useCallback((clientX: number, clientY: number) => {
|
||||
const el = ref.current;
|
||||
if (!el) return null;
|
||||
@ -175,15 +192,22 @@ export function Canvas(props: CanvasProps) {
|
||||
const p = clientToWorld(e.clientX, e.clientY);
|
||||
if (!p) return;
|
||||
|
||||
if (targetId && !props.selectionIds.includes(targetId)) {
|
||||
const additive = (e as React.MouseEvent).ctrlKey || (e as React.MouseEvent).metaKey;
|
||||
|
||||
if (targetId) {
|
||||
if (!props.selectionIds.includes(targetId)) {
|
||||
// goView-ish: right click selects the item.
|
||||
// Ctrl/Cmd keeps multi-select parity (add to selection instead of replacing).
|
||||
if ((e as React.MouseEvent).ctrlKey || (e as React.MouseEvent).metaKey) {
|
||||
if (additive) {
|
||||
props.onToggleSelect(targetId);
|
||||
} else {
|
||||
props.onSelectSingle(targetId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Editor parity: right-click on empty space clears selection (unless additive).
|
||||
if (!additive) props.onSelectSingle(undefined);
|
||||
}
|
||||
|
||||
setCtx({ clientX: e.clientX, clientY: e.clientY, worldX: p.x, worldY: p.y, targetId });
|
||||
};
|
||||
@ -264,12 +288,12 @@ export function Canvas(props: CanvasProps) {
|
||||
cursor: props.keyboard.space ? 'grab' : 'default',
|
||||
}}
|
||||
>
|
||||
{ctx && (
|
||||
{ctx && ctxMenuPos && (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
left: ctx.clientX,
|
||||
top: ctx.clientY,
|
||||
left: ctxMenuPos.x,
|
||||
top: ctxMenuPos.y,
|
||||
zIndex: 10_000,
|
||||
background: '#111827',
|
||||
border: '1px solid rgba(255,255,255,0.12)',
|
||||
|
||||
@ -15,6 +15,9 @@ import { convertGoViewTextOptionToNodeProps, type GoViewTextOption } from '../wi
|
||||
export interface GoViewComponentLike {
|
||||
id?: string;
|
||||
|
||||
// some exports wrap the actual component under a nested field
|
||||
component?: GoViewComponentLike;
|
||||
|
||||
// component identity
|
||||
key?: string; // e.g. "TextCommon" (sometimes)
|
||||
componentKey?: string;
|
||||
@ -58,7 +61,21 @@ export interface GoViewProjectLike {
|
||||
editCanvasConfig?: GoViewEditCanvasConfigLike;
|
||||
}
|
||||
|
||||
function keyOf(c: GoViewComponentLike): string {
|
||||
function unwrapComponent(c: GoViewComponentLike): GoViewComponentLike {
|
||||
// Prefer the nested component shape but keep outer fields as fallback.
|
||||
// This handles exports like: { id, attr, component: { chartConfig, option } }
|
||||
const inner = c.component;
|
||||
if (!inner) return c;
|
||||
return {
|
||||
...inner,
|
||||
...c,
|
||||
// ensure the nested component doesn't get lost
|
||||
component: inner.component,
|
||||
};
|
||||
}
|
||||
|
||||
function keyOf(cIn: GoViewComponentLike): string {
|
||||
const c = unwrapComponent(cIn);
|
||||
return (
|
||||
c.chartConfig?.key ??
|
||||
c.chartConfig?.chartKey ??
|
||||
@ -106,7 +123,8 @@ function pick<T>(...values: Array<T | undefined | null>): T | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function optionOf(c: GoViewComponentLike): unknown {
|
||||
function optionOf(cIn: GoViewComponentLike): unknown {
|
||||
const c = unwrapComponent(cIn);
|
||||
const chartData = c.chartConfig?.data as Record<string, unknown> | undefined;
|
||||
return (
|
||||
c.option ??
|
||||
@ -178,7 +196,9 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
|
||||
|
||||
const nodes: Array<TextWidgetNode | ImageWidgetNode | IframeWidgetNode | VideoWidgetNode> = [];
|
||||
|
||||
for (const c of componentList) {
|
||||
for (const raw of componentList) {
|
||||
const c = unwrapComponent(raw);
|
||||
|
||||
const rect = c.attr
|
||||
? {
|
||||
x: toNumber((c.attr as unknown as Record<string, unknown>).x, 0),
|
||||
|
||||
@ -30,7 +30,27 @@ function pickUrlLikeInner(input: unknown, depth: number): string {
|
||||
const obj = input as Record<string, unknown>;
|
||||
|
||||
// Common direct keys.
|
||||
for (const key of ['value', 'url', 'src', 'href', 'link', 'path', 'iframeUrl', 'videoUrl', 'mp4']) {
|
||||
// Keep this list generous; imports come from many low-code editors.
|
||||
for (const key of [
|
||||
'value',
|
||||
'url',
|
||||
'src',
|
||||
'href',
|
||||
'link',
|
||||
'path',
|
||||
'source',
|
||||
'address',
|
||||
// iframe-ish
|
||||
'iframeUrl',
|
||||
'iframeSrc',
|
||||
'embedUrl',
|
||||
// video-ish
|
||||
'videoUrl',
|
||||
'videoSrc',
|
||||
'mp4',
|
||||
'm3u8',
|
||||
'flv',
|
||||
]) {
|
||||
const v = obj[key];
|
||||
if (typeof v === 'string' && v) return v;
|
||||
}
|
||||
@ -38,7 +58,7 @@ function pickUrlLikeInner(input: unknown, depth: number): string {
|
||||
if (depth <= 0) return '';
|
||||
|
||||
// Common nesting keys.
|
||||
for (const key of ['dataset', 'data', 'config', 'option', 'options', 'props']) {
|
||||
for (const key of ['dataset', 'data', 'config', 'option', 'options', 'props', 'source', 'media']) {
|
||||
const v = obj[key];
|
||||
const nested = pickUrlLikeInner(v, depth - 1);
|
||||
if (nested) return nested;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user