feat(canvas): fix rotation, clarify frame membership and load icons on demand
This commit is contained in:
@@ -6,11 +6,16 @@ import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
||||
import type { CanvasNode } from "@/lib/canvas";
|
||||
import type { FrameLabelMode } from "@/lib/canvas-elements";
|
||||
import { NodeEditing, TransformableNode } from "./transformable-node";
|
||||
import { FrameFeedback } from "./frame-feedback";
|
||||
import styles from "./canvas.module.css";
|
||||
|
||||
export function FrameNode(props: NodeProps<CanvasNode>) {
|
||||
const { id, data, selected } = props;
|
||||
const editing = useContext(NodeEditing);
|
||||
const feedback = useContext(FrameFeedback);
|
||||
const incoming = feedback.drops.filter((drop) => drop.to === id && drop.from !== id).length;
|
||||
const outgoing = feedback.drops.filter((drop) => drop.from === id && drop.to !== id).length;
|
||||
const count = feedback.counts.get(id) || 0;
|
||||
const { zoom } = useViewport();
|
||||
const reduce = useReducedMotion();
|
||||
const [title, setTitle] = useState(data.title);
|
||||
@@ -26,16 +31,18 @@ export function FrameNode(props: NodeProps<CanvasNode>) {
|
||||
<NodeToolbar style={{ zIndex: 100, pointerEvents: "auto" }} isVisible={true} position={Position.Top} offset={45}>
|
||||
<AnimatePresence>{selected && <motion.div className={`${styles.styleToolbar} nodrag nopan nowheel`} role="toolbar" aria-label="分区设置"
|
||||
initial={{ opacity: 0, y: reduce ? 0 : 5 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: reduce ? 0 : 5 }} transition={{ duration: reduce ? 0 : 0.14 }}
|
||||
onPointerDown={(event) => event.stopPropagation()} onKeyDown={(event) => event.stopPropagation()}>
|
||||
onPointerDown={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()} onDoubleClick={(event) => event.stopPropagation()} onKeyDown={(event) => event.stopPropagation()}>
|
||||
<input className={styles.frameNameInput} aria-label="分区名称" value={title} maxLength={300} onChange={(event) => setTitle(event.target.value)} onBlur={() => { if (cancelBlur.current) cancelBlur.current = false; else saveTitle(); }}
|
||||
onKeyDown={(event) => { if (event.key === "Enter" && !event.nativeEvent.isComposing) { event.currentTarget.blur(); } if (event.key === "Escape") { cancelBlur.current = true; setTitle(data.title); event.currentTarget.blur(); } }} />
|
||||
<select aria-label="分区名称显示模式" value={mode} onChange={(event) => editing.frame(id, { labelMode: event.target.value as FrameLabelMode })}>
|
||||
<option value="hidden">隐藏名称</option><option value="fixed">固定字号</option><option value="scaled">随画布缩放</option>
|
||||
</select>
|
||||
<span className={styles.frameMemberCount}>{count} 项内容</span>
|
||||
</motion.div>}</AnimatePresence>
|
||||
</NodeToolbar>
|
||||
<TransformableNode {...props} hideStyleToolbar className={styles.frameNode}>
|
||||
{mode !== "hidden" && <div className={styles.frameLabel} style={{ scale: mode === "fixed" ? 1 / zoom : 1 }} title={data.title}>{data.title}</div>}
|
||||
<TransformableNode {...props} hideStyleToolbar className={`${styles.frameNode} ${incoming ? styles.frameDropTarget : ""} ${outgoing ? styles.frameDropExit : ""}`}>
|
||||
{mode !== "hidden" && <div className={styles.frameLabel} style={{ scale: mode === "fixed" ? 1 / zoom : 1 }} title={data.title}>{data.title}<span className={styles.frameMemberCount}> · {count} 项</span></div>}
|
||||
{(incoming > 0 || outgoing > 0) && <div className={styles.frameDropHint} style={{ scale: 1 / zoom }}>{incoming > 0 ? `松开加入 · ${incoming} 项` : `松开移出 · ${outgoing} 项`}</div>}
|
||||
{(["top", "right", "bottom", "left"] as const).map((side) => <div key={side} className={`${styles.frameBorderHit} ${styles["frameBorder_" + side]}`} title="拖动分区,已有成员一起移动" />)}
|
||||
</TransformableNode>
|
||||
</>;
|
||||
|
||||
Reference in New Issue
Block a user