fix(tokens): импорт неигровых токенов из zip и плавные пути на macOS

Полный импорт/открытие проекта подхватывает app-tokens. Движение по путям пишется в DOM без ре-рендера кадра, у пульта и презентации выключен background throttling.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-09-07 16:16:30 +08:00
parent 586166cacd
commit fdccc0deb5
16 changed files with 285 additions and 111 deletions
@@ -1,9 +1,11 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import type { AppToken, SceneToken, SceneTokensSessionState } from '../../../shared/types';
import { clampSceneTokenSizeN } from '../../../shared/types/appTokens';
import { tokenPathKey } from '../../../shared/types/tokenPathSession';
import { useLiveResizeBroadcast } from '../playerToken/useLiveDragBroadcast';
import { registerPathTokenNode, writeTokenPathTransform } from './tokenPathLiveDom';
import { useTokenImageUrl } from './useTokenImageUrl';
import styles from './SceneTokensOverlay.module.css';
@@ -106,18 +108,29 @@ function TokenSprite({
};
}, []);
const elRef = useRef<HTMLDivElement>(null);
const posNx = localPos?.nx ?? nx;
const posNy = localPos?.ny ?? ny;
const shownSizeN = localSize ?? sizeN;
const pathKey = tokenPathKey('token', String(placement.id));
const minDim = Math.min(viewport.w, viewport.h);
const sizePx = Math.max(16, shownSizeN * minDim);
const left = viewport.x + posNx * viewport.w;
const top = viewport.y + posNy * viewport.h;
const canDrag = editable && dragEnabled && Boolean(onMove);
const canResize = editable && dragEnabled && Boolean(onResize);
const interactive = canDrag || canResize || Boolean(onContextMenu);
useLayoutEffect(() => {
registerPathTokenNode(pathKey, elRef.current, viewport, true);
return () => registerPathTokenNode(pathKey, null, viewport, true);
}, [pathKey, viewport]);
useLayoutEffect(() => {
const el = elRef.current;
if (!el || el.dataset.pathLive === '1') return;
writeTokenPathTransform(el, posNx, posNy, rotationDeg, viewport, true);
}, [posNx, posNy, rotationDeg, viewport]);
const hostToNorm = (clientX: number, clientY: number, host: HTMLElement) => {
const r = host.getBoundingClientRect();
const w = Math.max(1e-6, viewport.w);
@@ -160,6 +173,7 @@ function TokenSprite({
return (
<div
ref={elRef}
className={[
styles.token,
canDrag ? styles.tokenEditable : '',
@@ -168,11 +182,8 @@ function TokenSprite({
.filter(Boolean)
.join(' ')}
style={{
left,
top,
width: sizePx,
height: sizePx,
transform: `translate(-50%, -50%) rotate(${String(rotationDeg)}deg)`,
}}
onContextMenu={
onContextMenu