refactor(editor): improve selection parity and exhaustiveness
This commit is contained in:
parent
534516d17e
commit
2d032fe050
@ -1,5 +1,6 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import type { Screen, WidgetNode } from '@astralview/sdk';
|
import type { Screen, WidgetNode } from '@astralview/sdk';
|
||||||
|
import { assertNever } from '@astralview/sdk';
|
||||||
import { Button, Space, Typography } from 'antd';
|
import { Button, Space, Typography } from 'antd';
|
||||||
import type { ResizeHandle } from './types';
|
import type { ResizeHandle } from './types';
|
||||||
import { rectFromPoints } from './geometry';
|
import { rectFromPoints } from './geometry';
|
||||||
@ -232,7 +233,7 @@ 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;
|
||||||
|
|
||||||
const additive = e.ctrlKey || e.metaKey;
|
const additive = e.ctrlKey || e.metaKey || e.shiftKey;
|
||||||
if (!additive) props.onSelectSingle(undefined);
|
if (!additive) props.onSelectSingle(undefined);
|
||||||
props.onBeginBoxSelect(e, p.x, p.y, additive);
|
props.onBeginBoxSelect(e, p.x, p.y, additive);
|
||||||
setBox(rectFromPoints({ x: p.x, y: p.y }, { x: p.x, y: p.y }));
|
setBox(rectFromPoints({ x: p.x, y: p.y }, { x: p.x, y: p.y }));
|
||||||
@ -555,120 +556,137 @@ function NodeView(props: {
|
|||||||
borderStyle: node.hidden ? 'dashed' : 'solid',
|
borderStyle: node.hidden ? 'dashed' : 'solid',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{node.type === 'text' ? (
|
{(() => {
|
||||||
<div
|
switch (node.type) {
|
||||||
style={{
|
case 'text':
|
||||||
width: '100%',
|
return (
|
||||||
height: '100%',
|
<div
|
||||||
display: 'flex',
|
style={{
|
||||||
alignItems: 'center',
|
width: '100%',
|
||||||
justifyContent:
|
height: '100%',
|
||||||
node.props.textAlign === 'left'
|
display: 'flex',
|
||||||
? 'flex-start'
|
alignItems: 'center',
|
||||||
: node.props.textAlign === 'right'
|
justifyContent:
|
||||||
? 'flex-end'
|
node.props.textAlign === 'left'
|
||||||
: 'center',
|
? 'flex-start'
|
||||||
backgroundColor: node.props.backgroundColor ?? 'transparent',
|
: node.props.textAlign === 'right'
|
||||||
borderStyle: 'solid',
|
? 'flex-end'
|
||||||
borderWidth: `${node.props.borderWidth ?? 0}px`,
|
: 'center',
|
||||||
borderColor: node.props.borderColor ?? 'transparent',
|
backgroundColor: node.props.backgroundColor ?? 'transparent',
|
||||||
borderRadius: `${node.props.borderRadius ?? 0}px`,
|
borderStyle: 'solid',
|
||||||
boxSizing: 'border-box',
|
borderWidth: `${node.props.borderWidth ?? 0}px`,
|
||||||
padding: `${node.props.paddingY ?? 0}px ${node.props.paddingX ?? 0}px`,
|
borderColor: node.props.borderColor ?? 'transparent',
|
||||||
overflow: 'hidden',
|
borderRadius: `${node.props.borderRadius ?? 0}px`,
|
||||||
}}
|
boxSizing: 'border-box',
|
||||||
>
|
padding: `${node.props.paddingY ?? 0}px ${node.props.paddingX ?? 0}px`,
|
||||||
<span
|
overflow: 'hidden',
|
||||||
style={{
|
}}
|
||||||
whiteSpace: 'pre-wrap',
|
>
|
||||||
fontSize: node.props.fontSize ?? 24,
|
<span
|
||||||
color: node.props.color ?? '#fff',
|
style={{
|
||||||
fontWeight: node.props.fontWeight ?? 400,
|
whiteSpace: 'pre-wrap',
|
||||||
letterSpacing: `${node.props.letterSpacing ?? 0}px`,
|
fontSize: node.props.fontSize ?? 24,
|
||||||
writingMode: node.props.writingMode as unknown as React.CSSProperties['writingMode'],
|
color: node.props.color ?? '#fff',
|
||||||
cursor: node.props.link ? 'pointer' : 'default',
|
fontWeight: node.props.fontWeight ?? 400,
|
||||||
}}
|
letterSpacing: `${node.props.letterSpacing ?? 0}px`,
|
||||||
onClick={() => {
|
writingMode: node.props.writingMode as unknown as React.CSSProperties['writingMode'],
|
||||||
if (!node.props.link) return;
|
cursor: node.props.link ? 'pointer' : 'default',
|
||||||
const head = node.props.linkHead ?? 'http://';
|
}}
|
||||||
window.open(`${head}${node.props.link}`);
|
onClick={() => {
|
||||||
}}
|
if (!node.props.link) return;
|
||||||
>
|
const head = node.props.linkHead ?? 'http://';
|
||||||
{node.props.text}
|
window.open(`${head}${node.props.link}`);
|
||||||
</span>
|
}}
|
||||||
</div>
|
>
|
||||||
) : node.type === 'image' ? (
|
{node.props.text}
|
||||||
<div
|
</span>
|
||||||
style={{
|
</div>
|
||||||
width: '100%',
|
);
|
||||||
height: '100%',
|
|
||||||
overflow: 'hidden',
|
case 'image':
|
||||||
borderRadius: `${node.props.borderRadius ?? 0}px`,
|
return (
|
||||||
}}
|
<div
|
||||||
>
|
style={{
|
||||||
<img
|
width: '100%',
|
||||||
src={node.props.src}
|
height: '100%',
|
||||||
alt=""
|
overflow: 'hidden',
|
||||||
style={{
|
borderRadius: `${node.props.borderRadius ?? 0}px`,
|
||||||
width: '100%',
|
}}
|
||||||
height: '100%',
|
>
|
||||||
objectFit: node.props.fit ?? 'contain',
|
<img
|
||||||
display: 'block',
|
src={node.props.src}
|
||||||
// Editor parity: allow selecting/dragging the widget even when clicking the media.
|
alt=""
|
||||||
pointerEvents: 'none',
|
style={{
|
||||||
}}
|
width: '100%',
|
||||||
/>
|
height: '100%',
|
||||||
</div>
|
objectFit: node.props.fit ?? 'contain',
|
||||||
) : node.type === 'iframe' ? (
|
display: 'block',
|
||||||
<div
|
// Editor parity: allow selecting/dragging the widget even when clicking the media.
|
||||||
style={{
|
pointerEvents: 'none',
|
||||||
width: '100%',
|
}}
|
||||||
height: '100%',
|
/>
|
||||||
overflow: 'hidden',
|
</div>
|
||||||
borderRadius: `${node.props.borderRadius ?? 0}px`,
|
);
|
||||||
}}
|
|
||||||
>
|
case 'iframe':
|
||||||
<iframe
|
return (
|
||||||
src={node.props.src}
|
<div
|
||||||
width={rect.w}
|
style={{
|
||||||
height={rect.h}
|
width: '100%',
|
||||||
style={{
|
height: '100%',
|
||||||
border: 0,
|
overflow: 'hidden',
|
||||||
display: 'block',
|
borderRadius: `${node.props.borderRadius ?? 0}px`,
|
||||||
// Editor parity: iframes steal pointer events; disable so selection/context menu works.
|
}}
|
||||||
pointerEvents: 'none',
|
>
|
||||||
}}
|
<iframe
|
||||||
title={node.id}
|
src={node.props.src}
|
||||||
/>
|
width={rect.w}
|
||||||
</div>
|
height={rect.h}
|
||||||
) : node.type === 'video' ? (
|
style={{
|
||||||
<div
|
border: 0,
|
||||||
style={{
|
display: 'block',
|
||||||
width: '100%',
|
// Editor parity: iframes steal pointer events; disable so selection/context menu works.
|
||||||
height: '100%',
|
pointerEvents: 'none',
|
||||||
overflow: 'hidden',
|
}}
|
||||||
borderRadius: `${node.props.borderRadius ?? 0}px`,
|
title={node.id}
|
||||||
}}
|
/>
|
||||||
>
|
</div>
|
||||||
<video
|
);
|
||||||
src={node.props.src}
|
|
||||||
width={rect.w}
|
case 'video':
|
||||||
height={rect.h}
|
return (
|
||||||
autoPlay
|
<div
|
||||||
playsInline
|
style={{
|
||||||
loop={node.props.loop ?? false}
|
width: '100%',
|
||||||
muted={node.props.muted ?? false}
|
height: '100%',
|
||||||
style={{
|
overflow: 'hidden',
|
||||||
display: 'block',
|
borderRadius: `${node.props.borderRadius ?? 0}px`,
|
||||||
width: '100%',
|
}}
|
||||||
height: '100%',
|
>
|
||||||
objectFit: node.props.fit ?? 'contain',
|
<video
|
||||||
// Editor parity: allow selecting/dragging even when clicking the video surface.
|
src={node.props.src}
|
||||||
pointerEvents: 'none',
|
width={rect.w}
|
||||||
}}
|
height={rect.h}
|
||||||
/>
|
autoPlay
|
||||||
</div>
|
playsInline
|
||||||
) : null}
|
loop={node.props.loop ?? false}
|
||||||
|
muted={node.props.muted ?? false}
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
objectFit: node.props.fit ?? 'contain',
|
||||||
|
// Editor parity: allow selecting/dragging even when clicking the video surface.
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return assertNever(node);
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
|
||||||
{props.selected && !node.locked && !node.hidden && <ResizeHandles onPointerDown={props.onResizePointerDown} />}
|
{props.selected && !node.locked && !node.hidden && <ResizeHandles onPointerDown={props.onResizePointerDown} />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user