Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions src/components/canvas-context-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
getUniqueName,
} from "@/lib/form-element-model";
import { formatShortcut } from "@/lib/shortcuts";
import { useEditorStore } from "@/stores/editor-store";
import { getDisplayElements, useEditorStore } from "@/stores/editor-store";
import {
AlignLeft,
ChevronDown,
Expand Down Expand Up @@ -119,7 +119,7 @@ export function CanvasContextMenu({
label: t("contextMenu.selectAll"),
shortcut: formatShortcut("selectAll"),
action: () => {
const pageElements = store.elements.filter(
const pageElements = getDisplayElements(store).filter(
(el) => el.pageNumber === context.pageNumber,
);
store.selectElements(new Set(pageElements.map((el) => el.id)));
Expand All @@ -129,7 +129,7 @@ export function CanvasContextMenu({
}

if (context.type === "canvas") {
const elements = store.elements;
const elements = getDisplayElements(store);
const result: MenuEntry[] = [];

if (clipboard.length > 0) {
Expand Down Expand Up @@ -228,7 +228,10 @@ export function CanvasContextMenu({
x: context.pdfX,
y: context.pdfY,
pageNumber: context.pageNumber,
name: getUniqueName(`optionlist_${elements.length + 1}`, elements),
name: getUniqueName(
`optionlist_${elements.length + 1}`,
elements,
),
});
store.addElement(newEl);
store.selectElements(new Set([newEl.id]));
Expand All @@ -255,7 +258,7 @@ export function CanvasContextMenu({
label: t("contextMenu.selectAll"),
shortcut: formatShortcut("selectAll"),
action: () => {
const pageElements = store.elements.filter(
const pageElements = getDisplayElements(store).filter(
(el) => el.pageNumber === context.pageNumber,
);
store.selectElements(new Set(pageElements.map((el) => el.id)));
Expand Down Expand Up @@ -386,7 +389,13 @@ export function CanvasContextMenu({
>
{entries.map((entry, i) => {
if ("separator" in entry) {
return <div key={`sep-${i}`} role="separator" className="-mx-1 my-1 h-px bg-border" />;
return (
<div
key={`sep-${i}`}
role="separator"
className="-mx-1 my-1 h-px bg-border"
/>
);
}

const Icon = entry.icon;
Expand Down
100 changes: 76 additions & 24 deletions src/components/canvas-overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ import {
type PageLayout,
} from "@/lib/page-layout";
import type { SnapContext } from "@/lib/snap-engine";
import { useEditorStore } from "@/stores/editor-store";
import {
getDisplayElements,
getDisplayGuides,
useEditorStore,
} from "@/stores/editor-store";
import {
memo,
useCallback,
Expand All @@ -57,19 +61,43 @@ interface ElementOverlayListProps {
visiblePages: Set<number>;
layouts: Map<number, PageLayout>;
selectedIds: Set<string>;
dragLivePositions: Map<string, { x: number; y: number; width: number; height: number }> | null;
dragLivePositions: Map<
string,
{ x: number; y: number; width: number; height: number }
> | null;
snapTargetIds: Set<string>;
zoom: number;
dragOffset: { dx: number; dy: number } | null;
dragDraggingIdRef: RefObject<string | null>;
dragSnapCorrection: { dx: number; dy: number } | null;
resizeResizingIdRef: RefObject<string | null>;
resizeSnapCorrection: { dx: number; dy: number; dw: number; dh: number } | null;
resizeSnapCorrection: {
dx: number;
dy: number;
dw: number;
dh: number;
} | null;
multiResizeActiveRef: RefObject<boolean>;
onDragStart: (el: FormElement, e: React.MouseEvent) => void;
onDrag: (el: FormElement, screen: { x: number; y: number }, d: { x: number; y: number }, me: MouseEvent) => void;
onDragStop: (el: FormElement, screen: { x: number; y: number }, d: { x: number; y: number }, me: MouseEvent) => void;
onResize: (el: FormElement, dir: string, ref: HTMLElement, position: { x: number; y: number }, me: MouseEvent) => void;
onDrag: (
el: FormElement,
screen: { x: number; y: number },
d: { x: number; y: number },
me: MouseEvent,
) => void;
onDragStop: (
el: FormElement,
screen: { x: number; y: number },
d: { x: number; y: number },
me: MouseEvent,
) => void;
onResize: (
el: FormElement,
dir: string,
ref: HTMLElement,
position: { x: number; y: number },
me: MouseEvent,
) => void;
onResizeStop: (el: FormElement) => void;
onResetResize: () => void;
}
Expand Down Expand Up @@ -103,7 +131,9 @@ const ElementOverlayList = memo(function ElementOverlayList({
if (!layout) return null;

const isSelected = selectedIds.has(el.id);
const livePos = isSelected ? dragLivePositions?.get(el.id) ?? null : null;
const livePos = isSelected
? (dragLivePositions?.get(el.id) ?? null)
: null;
const isMultiResize =
multiResizeActiveRef.current &&
!!livePos &&
Expand All @@ -124,9 +154,7 @@ const ElementOverlayList = memo(function ElementOverlayList({
isMultiResize={isMultiResize}
isSnapTarget={snapTargetIds.has(el.id)}
effectiveDragOffset={
!isMultiResize && isSelected && !isDragging
? dragOffset
: null
!isMultiResize && isSelected && !isDragging ? dragOffset : null
}
isDragging={isDragging}
isResizing={isResizing}
Expand All @@ -148,14 +176,14 @@ const ElementOverlayList = memo(function ElementOverlayList({
export function CanvasOverlay() {
const { t } = useTranslation();
const scrollRef = useScrollContainerRef();
const elements = useEditorStore((s) => s.elements);
const elements = useEditorStore(getDisplayElements);
const activeTool = useEditorStore((s) => s.activeTool);
const zoom = useEditorStore((s) => s.zoom);
const pages = useEditorStore((s) => s.pages);
const pdfBytes = useEditorStore((s) => s.pdfBytes);
const selectedIds = useEditorStore((s) => s.selectedIds);
const gridSize = useEditorStore((s) => s.gridSize);
const guides = useEditorStore((s) => s.guides);
const guides = useEditorStore(getDisplayGuides);
const previewGuide = useEditorStore((s) => s.previewGuide);
const visiblePages = useVisiblePages();

Expand Down Expand Up @@ -522,7 +550,12 @@ export function CanvasOverlay() {
};

if (drawing.drawStartRef.current) {
drawing.updateDraw(currentX, currentY, modifiers, HORIZONTAL_DRAW_TOOLS.has(activeTool));
drawing.updateDraw(
currentX,
currentY,
modifiers,
HORIZONTAL_DRAW_TOOLS.has(activeTool),
);
return;
}

Expand Down Expand Up @@ -582,9 +615,9 @@ export function CanvasOverlay() {
const handleKeyDown = (e: KeyboardEvent) => {
if (
e.target instanceof HTMLElement &&
(e.target.closest(
e.target.closest(
"input, textarea, select, [role='menu'], [role='menuitem']",
) !== null)
) !== null
) {
return;
}
Expand All @@ -596,7 +629,7 @@ export function CanvasOverlay() {
const state = useEditorStore.getState();
const page = getPageAtViewportCenter(scrollEl, state.pages, zoom);
if (page === undefined) return;
const pageIds = state.elements
const pageIds = getDisplayElements(state)
.filter((el) => el.pageNumber === page)
.map((el) => el.id);
selectElements(new Set(pageIds));
Expand All @@ -616,7 +649,7 @@ export function CanvasOverlay() {

e.preventDefault();
const updates: Array<{ id: string; x: number; y: number }> = [];
for (const el of store.elements) {
for (const el of getDisplayElements(store)) {
if (store.selectedIds.has(el.id)) {
updates.push({ id: el.id, x: el.x + dx, y: el.y + dy });
}
Expand All @@ -642,7 +675,9 @@ export function CanvasOverlay() {
if (!store.selectedIds.has(elementId)) {
selectElements(new Set([elementId]));
}
const el = store.elements.find((el) => el.id === elementId);
const el = getDisplayElements(store).find(
(element) => element.id === elementId,
);
const rect = e.currentTarget.getBoundingClientRect();
const screenX = e.clientX - rect.left;
const screenY = e.clientY - rect.top;
Expand Down Expand Up @@ -706,8 +741,7 @@ export function CanvasOverlay() {
);

const totalContentHeight = useMemo(
() =>
getTotalContentHeight(pages, zoom, scrollViewportHeight || undefined),
() => getTotalContentHeight(pages, zoom, scrollViewportHeight || undefined),
[pages, zoom, scrollViewportHeight],
);

Expand All @@ -726,7 +760,12 @@ export function CanvasOverlay() {
if (multiResize.isActive.current && multiResize.currentBbox.current) {
return multiResize.currentBbox.current;
}
const items: Array<{ x: number; y: number; width: number; height: number }> = [];
const items: Array<{
x: number;
y: number;
width: number;
height: number;
}> = [];
for (const el of elements) {
if (!selectedIds.has(el.id)) continue;
const layout = layouts.get(el.pageNumber);
Expand All @@ -736,19 +775,32 @@ export function CanvasOverlay() {
const py = live?.y ?? el.y;
const pw = live?.width ?? el.width;
const ph = live?.height ?? el.height;
const tl = pdfToScreen({ x: px, y: py }, { zoom, pageX: layout.xOffset, pageY: layout.yOffset });
const tl = pdfToScreen(
{ x: px, y: py },
{ zoom, pageX: layout.xOffset, pageY: layout.yOffset },
);
items.push({ x: tl.x, y: tl.y, width: pw * zoom, height: ph * zoom });
}
if (items.length < 2) return null;
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
let minX = Infinity,
minY = Infinity,
maxX = -Infinity,
maxY = -Infinity;
for (const r of items) {
if (r.x < minX) minX = r.x;
if (r.y < minY) minY = r.y;
if (r.x + r.width > maxX) maxX = r.x + r.width;
if (r.y + r.height > maxY) maxY = r.y + r.height;
}
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
}, [selectedIds, elements, layouts, zoom, dragLivePositions, multiResize.snapCorrection]);
}, [
selectedIds,
elements,
layouts,
zoom,
dragLivePositions,
multiResize.snapCorrection,
]);

const anyHeightLocked = useMemo(() => {
if (selectedIds.size < 2) return false;
Expand Down
Loading