fix: improve goView media imports and editor context menu parity
This commit is contained in:
parent
1bab5905b8
commit
2a5372ecb3
@ -65,6 +65,23 @@ export function Canvas(props: CanvasProps) {
|
|||||||
const selectionAllLocked = selection.length > 0 && selection.every((n) => n.locked);
|
const selectionAllLocked = selection.length > 0 && selection.every((n) => n.locked);
|
||||||
const selectionAllHidden = selection.length > 0 && selection.every((n) => n.hidden);
|
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 clientToCanvas = useCallback((clientX: number, clientY: number) => {
|
||||||
const el = ref.current;
|
const el = ref.current;
|
||||||
if (!el) return null;
|
if (!el) return null;
|
||||||
@ -175,15 +192,22 @@ export function Canvas(props: CanvasProps) {
|
|||||||
const p = clientToWorld(e.clientX, e.clientY);
|
const p = clientToWorld(e.clientX, e.clientY);
|
||||||
if (!p) return;
|
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.
|
// goView-ish: right click selects the item.
|
||||||
// Ctrl/Cmd keeps multi-select parity (add to selection instead of replacing).
|
// 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);
|
props.onToggleSelect(targetId);
|
||||||
} else {
|
} else {
|
||||||
props.onSelectSingle(targetId);
|
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 });
|
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',
|
cursor: props.keyboard.space ? 'grab' : 'default',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ctx && (
|
{ctx && ctxMenuPos && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
left: ctx.clientX,
|
left: ctxMenuPos.x,
|
||||||
top: ctx.clientY,
|
top: ctxMenuPos.y,
|
||||||
zIndex: 10_000,
|
zIndex: 10_000,
|
||||||
background: '#111827',
|
background: '#111827',
|
||||||
border: '1px solid rgba(255,255,255,0.12)',
|
border: '1px solid rgba(255,255,255,0.12)',
|
||||||
|
|||||||
@ -15,6 +15,9 @@ import { convertGoViewTextOptionToNodeProps, type GoViewTextOption } from '../wi
|
|||||||
export interface GoViewComponentLike {
|
export interface GoViewComponentLike {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|
||||||
|
// some exports wrap the actual component under a nested field
|
||||||
|
component?: GoViewComponentLike;
|
||||||
|
|
||||||
// component identity
|
// component identity
|
||||||
key?: string; // e.g. "TextCommon" (sometimes)
|
key?: string; // e.g. "TextCommon" (sometimes)
|
||||||
componentKey?: string;
|
componentKey?: string;
|
||||||
@ -58,7 +61,21 @@ export interface GoViewProjectLike {
|
|||||||
editCanvasConfig?: GoViewEditCanvasConfigLike;
|
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 (
|
return (
|
||||||
c.chartConfig?.key ??
|
c.chartConfig?.key ??
|
||||||
c.chartConfig?.chartKey ??
|
c.chartConfig?.chartKey ??
|
||||||
@ -106,7 +123,8 @@ function pick<T>(...values: Array<T | undefined | null>): T | undefined {
|
|||||||
return 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;
|
const chartData = c.chartConfig?.data as Record<string, unknown> | undefined;
|
||||||
return (
|
return (
|
||||||
c.option ??
|
c.option ??
|
||||||
@ -178,7 +196,9 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
|
|||||||
|
|
||||||
const nodes: Array<TextWidgetNode | ImageWidgetNode | IframeWidgetNode | VideoWidgetNode> = [];
|
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
|
const rect = c.attr
|
||||||
? {
|
? {
|
||||||
x: toNumber((c.attr as unknown as Record<string, unknown>).x, 0),
|
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>;
|
const obj = input as Record<string, unknown>;
|
||||||
|
|
||||||
// Common direct keys.
|
// 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];
|
const v = obj[key];
|
||||||
if (typeof v === 'string' && v) return v;
|
if (typeof v === 'string' && v) return v;
|
||||||
}
|
}
|
||||||
@ -38,7 +58,7 @@ function pickUrlLikeInner(input: unknown, depth: number): string {
|
|||||||
if (depth <= 0) return '';
|
if (depth <= 0) return '';
|
||||||
|
|
||||||
// Common nesting keys.
|
// 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 v = obj[key];
|
||||||
const nested = pickUrlLikeInner(v, depth - 1);
|
const nested = pickUrlLikeInner(v, depth - 1);
|
||||||
if (nested) return nested;
|
if (nested) return nested;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user