refactor: improve legacy media detection + clamp context menu
This commit is contained in:
parent
50bce304d0
commit
176a1af2ed
@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { Screen, WidgetNode } from '@astralview/sdk';
|
||||
import { assertNever } from '@astralview/sdk';
|
||||
import { Button, Space, Typography } from 'antd';
|
||||
@ -55,6 +55,8 @@ export function Canvas(props: CanvasProps) {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const [box, setBox] = useState<{ x1: number; y1: number; x2: number; y2: number } | null>(null);
|
||||
const [ctx, setCtx] = useState<ContextMenuState | null>(null);
|
||||
const ctxMenuRef = useRef<HTMLDivElement | null>(null);
|
||||
const [ctxMenuSize, setCtxMenuSize] = useState<{ w: number; h: number }>({ w: 220, h: 320 });
|
||||
|
||||
const bounds = useMemo(() => ({ w: props.screen.width, h: props.screen.height }), [props.screen.width, props.screen.height]);
|
||||
|
||||
@ -66,22 +68,27 @@ 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);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!ctx) return;
|
||||
const el = ctxMenuRef.current;
|
||||
if (!el) return;
|
||||
|
||||
// Measure after render so clamping matches the real menu size.
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (rect.width && rect.height) setCtxMenuSize({ w: rect.width, h: rect.height });
|
||||
}, [ctx]);
|
||||
|
||||
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)),
|
||||
x: Math.max(8, Math.min(ctx.clientX, vw - ctxMenuSize.w - 8)),
|
||||
y: Math.max(8, Math.min(ctx.clientY, vh - ctxMenuSize.h - 8)),
|
||||
};
|
||||
}, [ctx]);
|
||||
}, [ctx, ctxMenuSize.h, ctxMenuSize.w]);
|
||||
|
||||
const clientToCanvas = useCallback((clientX: number, clientY: number) => {
|
||||
const el = ref.current;
|
||||
@ -292,6 +299,7 @@ export function Canvas(props: CanvasProps) {
|
||||
>
|
||||
{ctx && ctxMenuPos && (
|
||||
<div
|
||||
ref={ctxMenuRef}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
left: ctxMenuPos.x,
|
||||
|
||||
@ -134,7 +134,11 @@ function isVideo(c: GoViewComponentLike): boolean {
|
||||
k.includes('player') ||
|
||||
k.includes('stream') ||
|
||||
k.includes('rtsp') ||
|
||||
k.includes('hls')
|
||||
k.includes('hls') ||
|
||||
// common low-code names for live streams
|
||||
k.includes('camera') ||
|
||||
k.includes('cctv') ||
|
||||
k.includes('monitor')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -53,6 +53,9 @@ function pickUrlLikeInner(input: unknown, depth: number): string {
|
||||
'frameSrc',
|
||||
'htmlUrl',
|
||||
'htmlSrc',
|
||||
// generic web-ish
|
||||
'webUrl',
|
||||
'webSrc',
|
||||
// video-ish
|
||||
'videoUrl',
|
||||
'videoSrc',
|
||||
@ -70,6 +73,9 @@ function pickUrlLikeInner(input: unknown, depth: number): string {
|
||||
'rtspUrl',
|
||||
'rtmp',
|
||||
'rtmpUrl',
|
||||
// camera-ish
|
||||
'cameraUrl',
|
||||
'cameraSrc',
|
||||
]) {
|
||||
const v = obj[key];
|
||||
if (typeof v === 'string' && v) return v;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user