mirror of
https://github.com/documenso/documenso.git
synced 2026-09-29 16:24:30 +10:00
fix: auto show and hide editor action bar
This commit is contained in:
@@ -37,12 +37,6 @@ export type EnvelopeCanvasSelectionKind = 'field' | 'content';
|
||||
export type EnvelopeCanvasSelection = {
|
||||
kind: EnvelopeCanvasSelectionKind;
|
||||
groups: Konva.Group[];
|
||||
|
||||
/**
|
||||
* Whether the selection was made programmatically (e.g. on creation) rather
|
||||
* than by the user.
|
||||
*/
|
||||
isAuto: boolean;
|
||||
} | null;
|
||||
|
||||
/**
|
||||
|
||||
+2
-8
@@ -65,13 +65,8 @@ type SyncEditorSelectionToCanvasOptions = {
|
||||
*/
|
||||
selectedGroups: Konva.Group[];
|
||||
|
||||
select: (kind: EnvelopeCanvasSelectionKind, nodes: Konva.Node[], options?: { isAuto?: boolean }) => void;
|
||||
select: (kind: EnvelopeCanvasSelectionKind, nodes: Konva.Node[]) => void;
|
||||
clear: () => void;
|
||||
|
||||
/**
|
||||
* Whether a selection made by this sync counts as automatic.
|
||||
*/
|
||||
isAuto: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -92,7 +87,6 @@ export const syncEditorSelectionToCanvas = ({
|
||||
selectedGroups,
|
||||
select,
|
||||
clear,
|
||||
isAuto,
|
||||
}: SyncEditorSelectionToCanvasOptions) => {
|
||||
const isSingleSelection = selectedGroups.length === 1;
|
||||
|
||||
@@ -106,7 +100,7 @@ export const syncEditorSelectionToCanvas = ({
|
||||
const groupToSelect = layer.findOne(`#${editorFormId}`);
|
||||
|
||||
if (groupToSelect instanceof Konva.Group) {
|
||||
select(kind, [groupToSelect], { isAuto });
|
||||
select(kind, [groupToSelect]);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
-1
@@ -345,7 +345,6 @@ export const useEnvelopeCanvasContentsLayer = ({
|
||||
selectedGroups,
|
||||
select: selection.select,
|
||||
clear: selection.clear,
|
||||
isAuto: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
-3
@@ -376,8 +376,6 @@ export const useEnvelopeCanvasFieldsLayer = ({
|
||||
selection.select('field', liveSelectedGroups);
|
||||
}
|
||||
|
||||
// Newly created fields are auto selected, which suppresses the action bar
|
||||
// so it can't intercept the next placement click.
|
||||
syncEditorSelectionToCanvas({
|
||||
layer,
|
||||
kind: 'field',
|
||||
@@ -386,7 +384,6 @@ export const useEnvelopeCanvasFieldsLayer = ({
|
||||
selectedGroups,
|
||||
select: selection.select,
|
||||
clear: selection.clear,
|
||||
isAuto: true,
|
||||
});
|
||||
|
||||
applyPageItemsVisibility();
|
||||
|
||||
+3
-7
@@ -21,10 +21,6 @@ const EMPTY_GROUPS: Konva.Group[] = [];
|
||||
/** How far past a resize handle you can still grab it, in screen pixels. */
|
||||
const TRANSFORMER_ANCHOR_HIT_STROKE_PX = 24;
|
||||
|
||||
type SelectOptions = {
|
||||
isAuto?: boolean;
|
||||
};
|
||||
|
||||
type UseEnvelopeCanvasSelectionOptions = {
|
||||
/**
|
||||
* Resolve the transformer configuration for a selection.
|
||||
@@ -106,7 +102,7 @@ export const useEnvelopeCanvasSelection = ({ getTransformerConfig, onChange }: U
|
||||
}, []);
|
||||
|
||||
const select = useCallback(
|
||||
(kind: EnvelopeCanvasSelectionKind, nodes: Konva.Node[], options?: SelectOptions) => {
|
||||
(kind: EnvelopeCanvasSelectionKind, nodes: Konva.Node[]) => {
|
||||
const groupName = ENVELOPE_CANVAS_GROUP_NAMES[kind];
|
||||
|
||||
const groups = nodes.filter(
|
||||
@@ -117,7 +113,7 @@ export const useEnvelopeCanvasSelection = ({ getTransformerConfig, onChange }: U
|
||||
Boolean(node.getParent()),
|
||||
);
|
||||
|
||||
applySelection(groups.length > 0 ? { kind, groups, isAuto: Boolean(options?.isAuto) } : null);
|
||||
applySelection(groups.length > 0 ? { kind, groups } : null);
|
||||
},
|
||||
[applySelection],
|
||||
);
|
||||
@@ -169,7 +165,7 @@ export const useEnvelopeCanvasSelection = ({ getTransformerConfig, onChange }: U
|
||||
.find(`.${ENVELOPE_CANVAS_GROUP_NAMES[currentSelection.kind]}`)
|
||||
.filter((group) => selectedIds.has(group.id()));
|
||||
|
||||
select(currentSelection.kind, groups, { isAuto: currentSelection.isAuto });
|
||||
select(currentSelection.kind, groups);
|
||||
},
|
||||
[select],
|
||||
);
|
||||
|
||||
+11
-1
@@ -83,7 +83,7 @@ type EnvelopeEditorContentDragDropProps = {
|
||||
};
|
||||
|
||||
export const EnvelopeEditorContentDragDrop = ({ selectedEnvelopeItemId }: EnvelopeEditorContentDragDropProps) => {
|
||||
const { envelope, editorContents } = useCurrentEnvelopeEditor();
|
||||
const { envelope, editorContents, setIsPlacingItem } = useCurrentEnvelopeEditor();
|
||||
|
||||
const organisation = useCurrentOrganisation();
|
||||
|
||||
@@ -96,6 +96,16 @@ export const EnvelopeEditorContentDragDrop = ({ selectedEnvelopeItemId }: Envelo
|
||||
|
||||
const [selectedContent, setSelectedContent] = useState<ContentDragDropItem | null>(null);
|
||||
|
||||
// Let the canvas know a content is being placed, so it can get its selection
|
||||
// out of the way of the placement click.
|
||||
useEffect(() => {
|
||||
setIsPlacingItem(selectedContent !== null);
|
||||
|
||||
return () => {
|
||||
setIsPlacingItem(false);
|
||||
};
|
||||
}, [selectedContent, setIsPlacingItem]);
|
||||
|
||||
const { isWithinPageBounds, getPage } = useDocumentElement();
|
||||
|
||||
const [isContentWithinBounds, setIsContentWithinBounds] = useState(false);
|
||||
|
||||
+11
-1
@@ -94,12 +94,22 @@ export const EnvelopeEditorFieldDragDrop = ({
|
||||
selectedRecipientId,
|
||||
selectedEnvelopeItemId,
|
||||
}: EnvelopeEditorFieldDragDropProps) => {
|
||||
const { envelope, editorFields, isTemplate, getRecipientColorKey } = useCurrentEnvelopeEditor();
|
||||
const { envelope, editorFields, isTemplate, getRecipientColorKey, setIsPlacingItem } = useCurrentEnvelopeEditor();
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
const [selectedField, setSelectedField] = useState<FieldType | null>(null);
|
||||
|
||||
// Let the canvas know a field is being placed, so it can get its selection
|
||||
// out of the way of the placement click.
|
||||
useEffect(() => {
|
||||
setIsPlacingItem(selectedField !== null);
|
||||
|
||||
return () => {
|
||||
setIsPlacingItem(false);
|
||||
};
|
||||
}, [selectedField, setIsPlacingItem]);
|
||||
|
||||
const { isWithinPageBounds, getPage } = useDocumentElement();
|
||||
|
||||
const isFieldsDisabled = useMemo(() => {
|
||||
|
||||
+25
-2
@@ -14,6 +14,7 @@ import { useLingui } from '@lingui/react/macro';
|
||||
import type Konva from 'konva';
|
||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
import { CopyPlusIcon, TrashIcon } from 'lucide-react';
|
||||
import { useEffect } from 'react';
|
||||
import { match } from 'ts-pattern';
|
||||
import {
|
||||
EnvelopeCanvasActionBar,
|
||||
@@ -54,7 +55,7 @@ const getNodeSelectionKind = (node: Konva.Node): EnvelopeCanvasSelectionKind | n
|
||||
|
||||
export const EnvelopeEditorFieldsPageRenderer = ({ pageData }: { pageData: PageRenderData }) => {
|
||||
const { t } = useLingui();
|
||||
const { envelope, editorFields, editorContents, selectedEditorTab } = useCurrentEnvelopeEditor();
|
||||
const { envelope, editorFields, editorContents, selectedEditorTab, isPlacingItem } = useCurrentEnvelopeEditor();
|
||||
const { currentEnvelopeItem } = useCurrentEnvelopeRender();
|
||||
const organisation = useCurrentOrganisation();
|
||||
|
||||
@@ -136,6 +137,28 @@ export const EnvelopeEditorFieldsPageRenderer = ({ pageData }: { pageData: PageR
|
||||
onEmptyClick: selection.clear,
|
||||
});
|
||||
|
||||
/**
|
||||
* Picking a field or content from the palette drops the selection and any
|
||||
* pending creation, so nothing floating over the page sits under the
|
||||
* placement click.
|
||||
*
|
||||
* The action bar and pending menus are DOM elements above the canvas, so a
|
||||
* placement click landing on one would press its button as well as placing
|
||||
* the item, and by then the button acts on the item which was just placed
|
||||
* (e.g. removing it).
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!isPlacingItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
fields.clearPending();
|
||||
contents.clearPending();
|
||||
selection.clear();
|
||||
|
||||
pageLayer.current?.batchDraw();
|
||||
}, [isPlacingItem]);
|
||||
|
||||
/**
|
||||
* Initialize the Konva page canvas and all fields, contents and interactions.
|
||||
*/
|
||||
@@ -234,7 +257,7 @@ export const EnvelopeEditorFieldsPageRenderer = ({ pageData }: { pageData: PageR
|
||||
return (
|
||||
<>
|
||||
{currentSelection?.kind === 'field' && (
|
||||
<EnvelopeCanvasActionBar nodes={currentSelection.groups} hidden={isTransforming || currentSelection.isAuto}>
|
||||
<EnvelopeCanvasActionBar nodes={currentSelection.groups} hidden={isTransforming}>
|
||||
<EnvelopeCanvasFieldActionButtons
|
||||
selectedFieldFormIds={currentSelection.groups.map((group) => group.id())}
|
||||
onDuplicate={fields.duplicateSelected}
|
||||
|
||||
@@ -49,10 +49,7 @@ test('a selected field survives zooming', async ({ page }) => {
|
||||
await root.getByRole('button', { name: 'Signature', exact: true }).click();
|
||||
await getPageCanvas(root).click({ position: { x: 160, y: 160 } });
|
||||
|
||||
// A freshly placed field is auto selected, which deliberately suppresses
|
||||
// the action bar, so click it to make it a real selection.
|
||||
await selectContentOnCanvas(root, { x: 160, y: 160 });
|
||||
|
||||
// A freshly placed field is selected, with its action bar showing.
|
||||
await expect.poll(() => getKonvaTransformerNodeCountForPage(root, 1)).toBe(1);
|
||||
await expect(root.getByTestId('envelope-canvas-action-bar')).toBeVisible();
|
||||
|
||||
|
||||
@@ -69,6 +69,17 @@ type EnvelopeEditorProviderValue = {
|
||||
selectedEditorTab: EnvelopeEditorTab;
|
||||
setSelectedEditorTab: (tab: EnvelopeEditorTab) => void;
|
||||
|
||||
/**
|
||||
* Whether a field or content has been picked from the palette and is
|
||||
* waiting to be placed on the page.
|
||||
*
|
||||
* The canvas drops its selection while this is set, so nothing floating
|
||||
* over the page (the transformer, the action bar) sits under the
|
||||
* placement click.
|
||||
*/
|
||||
isPlacingItem: boolean;
|
||||
setIsPlacingItem: (isPlacingItem: boolean) => void;
|
||||
|
||||
isAutosaving: boolean;
|
||||
flushAutosave: () => Promise<TEditorEnvelope>;
|
||||
autosaveError: boolean;
|
||||
@@ -159,6 +170,8 @@ export const EnvelopeEditorProvider = ({
|
||||
|
||||
const [selectedEditorTab, setSelectedEditorTab] = useState<EnvelopeEditorTab>('fields');
|
||||
|
||||
const [isPlacingItem, setIsPlacingItem] = useState(false);
|
||||
|
||||
const isCscMode = IS_INSTANCE_CSC_MODE();
|
||||
|
||||
/**
|
||||
@@ -655,6 +668,8 @@ export const EnvelopeEditorProvider = ({
|
||||
editorRecipients,
|
||||
selectedEditorTab,
|
||||
setSelectedEditorTab,
|
||||
isPlacingItem,
|
||||
setIsPlacingItem,
|
||||
autosaveError,
|
||||
flushAutosave,
|
||||
isAutosaving,
|
||||
|
||||
Reference in New Issue
Block a user