From 1822a5543e9fb150f1853e5441f329412a3eacb7 Mon Sep 17 00:00:00 2001 From: rojasadrian012 Date: Tue, 7 Jan 2025 18:41:15 -0300 Subject: [PATCH 1/5] #586 Paste is done in a visibile place in the canvas --- src/core/providers/canvas/canvas.model.ts | 2 ++ src/core/providers/canvas/canvas.provider.tsx | 9 +++++- .../providers/canvas/use-clipboard.hook.tsx | 5 ++-- src/pods/canvas/canvas.pod.tsx | 4 +++ src/pods/canvas/clipboard.utils.ts | 30 +++++++++++++++---- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/core/providers/canvas/canvas.model.ts b/src/core/providers/canvas/canvas.model.ts index bc7656fd..606f5e82 100644 --- a/src/core/providers/canvas/canvas.model.ts +++ b/src/core/providers/canvas/canvas.model.ts @@ -122,6 +122,8 @@ export interface CanvasContextModel { setCanvasSize: (canvasDimensions: CanvasSize) => void; customColors: (string | null)[]; updateColorSlot: (color: string, index: number) => void; + dropRef: React.MutableRefObject; + setDropRef: (dropRef: React.MutableRefObject) => void; } export const APP_CONSTANTS = { diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index 105aee47..917f37db 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -183,11 +183,16 @@ export const CanvasProvider: React.FC = props => { }); }; + const [dropRef, setDropRef] = React.useState< + React.MutableRefObject + >(React.useRef(null)); + const { copyShapeToClipboard, pasteShapeFromClipboard, canCopy, canPaste } = useClipboard( pasteShapes, document.pages[document.activePageIndex].shapes, - selectionInfo + selectionInfo, + dropRef ); const createNewFullDocument = () => { @@ -362,6 +367,8 @@ export const CanvasProvider: React.FC = props => { setCanvasSize: setCanvasSize, customColors, updateColorSlot, + dropRef, + setDropRef, }} > {children} diff --git a/src/core/providers/canvas/use-clipboard.hook.tsx b/src/core/providers/canvas/use-clipboard.hook.tsx index 1b8c88a8..c7bfd2c9 100644 --- a/src/core/providers/canvas/use-clipboard.hook.tsx +++ b/src/core/providers/canvas/use-clipboard.hook.tsx @@ -10,7 +10,8 @@ import { export const useClipboard = ( pasteShapes: (shapes: ShapeModel[]) => void, shapes: ShapeModel[], - selectionInfo: { selectedShapesIds: string[] | null } + selectionInfo: { selectedShapesIds: string[] | null }, + dropRef: React.MutableRefObject ) => { const [clipboardShape, setClipboardShape] = useState( null @@ -34,7 +35,7 @@ export const useClipboard = ( if (clipboardShapesRef.current) { const newShapes: ShapeModel[] = cloneShapes(clipboardShapesRef.current); validateShapes(newShapes); - adjustShapesPosition(newShapes, copyCount.current); + adjustShapesPosition(newShapes, copyCount.current, dropRef); pasteShapes(newShapes); copyCount.current++; } diff --git a/src/pods/canvas/canvas.pod.tsx b/src/pods/canvas/canvas.pod.tsx index e2f06233..837bdb89 100644 --- a/src/pods/canvas/canvas.pod.tsx +++ b/src/pods/canvas/canvas.pod.tsx @@ -30,6 +30,7 @@ export const CanvasPod = () => { updateShapePosition, stageRef, canvasSize, + setDropRef, } = useCanvasContext(); const { @@ -52,6 +53,9 @@ export const CanvasPod = () => { const { isDraggedOver, dropRef } = useDropShape(); useMonitorShape(dropRef, addNewShapeAndSetSelected); + useEffect(() => { + if (dropRef.current) setDropRef(dropRef); + }, [dropRef, setDropRef]); const getSelectedShapeKonvaId = (): string[] => { let result: string[] = []; diff --git a/src/pods/canvas/clipboard.utils.ts b/src/pods/canvas/clipboard.utils.ts index f8d4fcdc..8cf07ab6 100644 --- a/src/pods/canvas/clipboard.utils.ts +++ b/src/pods/canvas/clipboard.utils.ts @@ -1,6 +1,12 @@ import cloneDeep from 'lodash.clonedeep'; import { ShapeModel } from '@/core/model'; import invariant from 'tiny-invariant'; +import { getScrollFromDiv } from './canvas.util'; + +interface PositionScroll { + x: number; + y: number; +} export const findShapesById = ( shapeIds: string[], @@ -17,12 +23,26 @@ export const cloneShape = (shape: ShapeModel): ShapeModel => { return cloneDeep(shape); }; -export const adjustShapesPosition = (shapes: ShapeModel[], copyCount: number) => - shapes.map(shape => adjustShapePosition(shape, copyCount)); +export const adjustShapesPosition = ( + shapes: ShapeModel[], + copyCount: number, + dropRef: React.MutableRefObject +) => { + const { scrollLeft, scrollTop } = getScrollFromDiv( + dropRef as unknown as React.MutableRefObject + ); + shapes.map(shape => + adjustShapePosition(shape, copyCount, { x: scrollLeft, y: scrollTop }) + ); +}; -export const adjustShapePosition = (shape: ShapeModel, copyCount: number) => { - shape.x += 20 * copyCount; - shape.y += 20 * copyCount; +export const adjustShapePosition = ( + shape: ShapeModel, + copyCount: number, + position: PositionScroll +) => { + shape.x += 20 * copyCount + position.x; + shape.y += 20 * copyCount + position.y; }; export const validateShapes = (shapes: ShapeModel[]) => { From 8a2011265c866fe2886b1e7641a4704f0bd61c8d Mon Sep 17 00:00:00 2001 From: rojasadrian012 Date: Tue, 7 Jan 2025 19:19:26 -0300 Subject: [PATCH 2/5] =?UTF-8?q?Removed=20increment=20in=20x=20and=20y,=20b?= =?UTF-8?q?ecause=20it=20causes=20disorder=20when=20the=20values=20?= =?UTF-8?q?=E2=80=8B=E2=80=8Baccumulate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pods/canvas/clipboard.utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pods/canvas/clipboard.utils.ts b/src/pods/canvas/clipboard.utils.ts index 8cf07ab6..93d2c4e7 100644 --- a/src/pods/canvas/clipboard.utils.ts +++ b/src/pods/canvas/clipboard.utils.ts @@ -41,8 +41,8 @@ export const adjustShapePosition = ( copyCount: number, position: PositionScroll ) => { - shape.x += 20 * copyCount + position.x; - shape.y += 20 * copyCount + position.y; + shape.x = 20 * copyCount + position.x; + shape.y = 20 * copyCount + position.y; }; export const validateShapes = (shapes: ShapeModel[]) => { From 010e9efbadfd792b7d92cb99b6d499c60cd0a546 Mon Sep 17 00:00:00 2001 From: rojasadrian012 Date: Mon, 13 Jan 2025 13:20:44 -0300 Subject: [PATCH 3/5] Ensure Paste is done in a visibile place in the canvas --- .../providers/canvas/use-clipboard.hook.tsx | 12 ++- src/pods/canvas/clipboard.utils.ts | 93 ++++++++++++++++--- 2 files changed, 91 insertions(+), 14 deletions(-) diff --git a/src/core/providers/canvas/use-clipboard.hook.tsx b/src/core/providers/canvas/use-clipboard.hook.tsx index c7bfd2c9..fa193712 100644 --- a/src/core/providers/canvas/use-clipboard.hook.tsx +++ b/src/core/providers/canvas/use-clipboard.hook.tsx @@ -31,11 +31,21 @@ export const useClipboard = ( } }; + const updateClipboardShapes = (shapes: ShapeModel[]) => { + copyCount.current = 0; + clipboardShapesRef.current = [...shapes]; + }; + const pasteShapeFromClipboard = () => { if (clipboardShapesRef.current) { const newShapes: ShapeModel[] = cloneShapes(clipboardShapesRef.current); validateShapes(newShapes); - adjustShapesPosition(newShapes, copyCount.current, dropRef); + adjustShapesPosition( + newShapes, + copyCount.current, + dropRef, + updateClipboardShapes + ); pasteShapes(newShapes); copyCount.current++; } diff --git a/src/pods/canvas/clipboard.utils.ts b/src/pods/canvas/clipboard.utils.ts index 93d2c4e7..c6879698 100644 --- a/src/pods/canvas/clipboard.utils.ts +++ b/src/pods/canvas/clipboard.utils.ts @@ -1,12 +1,16 @@ import cloneDeep from 'lodash.clonedeep'; import { ShapeModel } from '@/core/model'; import invariant from 'tiny-invariant'; -import { getScrollFromDiv } from './canvas.util'; - -interface PositionScroll { +interface Displacement { x: number; y: number; } +interface Viewport { + xMinVisible: number; + xMaxVisible: number; + yMinVisible: number; + yMaxVisible: number; +} export const findShapesById = ( shapeIds: string[], @@ -23,26 +27,89 @@ export const cloneShape = (shape: ShapeModel): ShapeModel => { return cloneDeep(shape); }; +function areAllShapesFullyVisible( + shapes: ShapeModel[], + viewport: Viewport, + copyCount: number +): boolean { + return shapes.every(shape => { + const offsetX = 20 * copyCount; + const offsetY = 20 * copyCount; + const newX = shape.x + offsetX; + const newY = shape.y + offsetY; + const left = newX; + const right = newX + shape.width; + const top = newY; + const bottom = newY + shape.height; + + return ( + left >= viewport.xMinVisible && + right <= viewport.xMaxVisible && + top >= viewport.yMinVisible && + bottom <= viewport.yMaxVisible + ); + }); +} + export const adjustShapesPosition = ( shapes: ShapeModel[], copyCount: number, - dropRef: React.MutableRefObject + dropRef: React.MutableRefObject, + updateClipboardShapes: (shapes: ShapeModel[]) => void ) => { - const { scrollLeft, scrollTop } = getScrollFromDiv( - dropRef as unknown as React.MutableRefObject - ); - shapes.map(shape => - adjustShapePosition(shape, copyCount, { x: scrollLeft, y: scrollTop }) - ); + const container = dropRef.current; + if (!container) return; + + const containerRect = container.getBoundingClientRect(); + const scrollLeft = container.scrollLeft; + const scrollTop = container.scrollTop; + + const viewPort: Viewport = { + xMinVisible: scrollLeft, + xMaxVisible: scrollLeft + containerRect.width, + yMinVisible: scrollTop, + yMaxVisible: scrollTop + containerRect.height, + }; + + const allVisible = areAllShapesFullyVisible(shapes, viewPort, copyCount); + + if (allVisible) { + shapes.forEach(shape => { + adjustShapePosition(shape, copyCount); + }); + } else { + const shape0 = shapes[0]; + const oldX0 = shape0.x + 20 * copyCount; + const oldY0 = shape0.y + 20 * copyCount; + + const centerX = scrollLeft + containerRect.width / 2; + const centerY = scrollTop + containerRect.height / 2; + + const targetX0 = centerX - shape0.width / 2; + const targetY0 = centerY - shape0.height / 2; + + const displacement: Displacement = { + x: targetX0 - oldX0, + y: targetY0 - oldY0, + }; + + shapes.forEach(shape => { + adjustShapePosition(shape, copyCount, displacement); + }); + } + + updateClipboardShapes(shapes); }; export const adjustShapePosition = ( shape: ShapeModel, copyCount: number, - position: PositionScroll + d: Displacement = { x: 0, y: 0 } ) => { - shape.x = 20 * copyCount + position.x; - shape.y = 20 * copyCount + position.y; + const originalX = shape.x + 20 * copyCount; + const originalY = shape.y + 20 * copyCount; + shape.x = originalX + d.x; + shape.y = originalY + d.y; }; export const validateShapes = (shapes: ShapeModel[]) => { From 7b3aa004e05547fa95dd321e32b1da20734d441e Mon Sep 17 00:00:00 2001 From: IonutGabi Date: Thu, 16 Jan 2025 11:19:22 +0100 Subject: [PATCH 4/5] Add 8 new icons of the set of icons --- public/icons/book.svg | 1 + public/icons/bookopen.svg | 1 + public/icons/books.svg | 1 + public/icons/chalkboard.svg | 1 + public/icons/clockuser.svg | 1 + public/icons/person.svg | 1 + public/icons/userfocus.svg | 1 + public/icons/userlist.svg | 1 + .../components/icon-selector/modal/icons.ts | 48 +++++++++++++++++++ 9 files changed, 56 insertions(+) create mode 100644 public/icons/book.svg create mode 100644 public/icons/bookopen.svg create mode 100644 public/icons/books.svg create mode 100644 public/icons/chalkboard.svg create mode 100644 public/icons/clockuser.svg create mode 100644 public/icons/person.svg create mode 100644 public/icons/userfocus.svg create mode 100644 public/icons/userlist.svg diff --git a/public/icons/book.svg b/public/icons/book.svg new file mode 100644 index 00000000..adab613e --- /dev/null +++ b/public/icons/book.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/bookopen.svg b/public/icons/bookopen.svg new file mode 100644 index 00000000..f4af8966 --- /dev/null +++ b/public/icons/bookopen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/books.svg b/public/icons/books.svg new file mode 100644 index 00000000..f7a9ab7f --- /dev/null +++ b/public/icons/books.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/chalkboard.svg b/public/icons/chalkboard.svg new file mode 100644 index 00000000..3b6ba032 --- /dev/null +++ b/public/icons/chalkboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/clockuser.svg b/public/icons/clockuser.svg new file mode 100644 index 00000000..a00a5289 --- /dev/null +++ b/public/icons/clockuser.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/person.svg b/public/icons/person.svg new file mode 100644 index 00000000..8a66b874 --- /dev/null +++ b/public/icons/person.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/userfocus.svg b/public/icons/userfocus.svg new file mode 100644 index 00000000..4d35eaec --- /dev/null +++ b/public/icons/userfocus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/userlist.svg b/public/icons/userlist.svg new file mode 100644 index 00000000..8eb0e2af --- /dev/null +++ b/public/icons/userlist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pods/properties/components/icon-selector/modal/icons.ts b/src/pods/properties/components/icon-selector/modal/icons.ts index d5ef5652..d736ce31 100644 --- a/src/pods/properties/components/icon-selector/modal/icons.ts +++ b/src/pods/properties/components/icon-selector/modal/icons.ts @@ -2302,4 +2302,52 @@ export const iconCollection: IconInfo[] = [ searchTerms: ['rocket', 'launch', 'space', 'fly'], categories: ['IT'], }, + { + name: 'Books', + filename: 'books.svg', + searchTerms: ['books', 'library', 'knowledge', 'read'], + categories: ['IT'], + }, + { + name: 'Chalkboard', + filename: 'chalkboard.svg', + searchTerms: ['chalkboard', 'blackboard', 'school', 'teach', 'learn'], + categories: ['IT'], + }, + { + name: 'Book', + filename: 'book.svg', + searchTerms: ['book', 'library', 'knowledge', 'read'], + categories: ['IT'], + }, + { + name: 'Book open', + filename: 'bookopen.svg', + searchTerms: ['book', 'open', 'library', 'knowledge', 'read'], + categories: ['IT'], + }, + { + name: 'User list', + filename: 'userlist.svg', + searchTerms: ['user', 'list', 'people', 'group', 'team'], + categories: ['IT'], + }, + { + name: 'Person', + filename: 'person.svg', + searchTerms: ['person', 'user', 'human', 'profile', 'individual'], + categories: ['IT'], + }, + { + name: 'User focus', + filename: 'userfocus.svg', + searchTerms: ['user', 'focus', 'human', 'profile', 'emphasis'], + categories: ['IT'], + }, + { + name: 'Clock user', + filename: 'clockuser.svg', + searchTerms: ['clock', 'user', 'human', 'timetable', 'schedule'], + categories: ['IT'], + }, ]; From 9fd9e627a00ecec436a022fab64e803549fe1c52 Mon Sep 17 00:00:00 2001 From: IonutGabi Date: Fri, 17 Jan 2025 14:07:05 +0100 Subject: [PATCH 5/5] Add 3 new icons of the set of icons --- public/icons/calendarplus.svg | 1 + public/icons/minuscircle.svg | 1 + public/icons/pluscircle.svg | 1 + .../components/icon-selector/modal/icons.ts | 18 ++++++++++++++++++ 4 files changed, 21 insertions(+) create mode 100644 public/icons/calendarplus.svg create mode 100644 public/icons/minuscircle.svg create mode 100644 public/icons/pluscircle.svg diff --git a/public/icons/calendarplus.svg b/public/icons/calendarplus.svg new file mode 100644 index 00000000..feb4033b --- /dev/null +++ b/public/icons/calendarplus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/minuscircle.svg b/public/icons/minuscircle.svg new file mode 100644 index 00000000..0d5fde54 --- /dev/null +++ b/public/icons/minuscircle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/pluscircle.svg b/public/icons/pluscircle.svg new file mode 100644 index 00000000..53122f79 --- /dev/null +++ b/public/icons/pluscircle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pods/properties/components/icon-selector/modal/icons.ts b/src/pods/properties/components/icon-selector/modal/icons.ts index d736ce31..4fee72bf 100644 --- a/src/pods/properties/components/icon-selector/modal/icons.ts +++ b/src/pods/properties/components/icon-selector/modal/icons.ts @@ -2350,4 +2350,22 @@ export const iconCollection: IconInfo[] = [ searchTerms: ['clock', 'user', 'human', 'timetable', 'schedule'], categories: ['IT'], }, + { + name: 'Plus circle', + filename: 'pluscircle.svg', + searchTerms: ['plus', 'circle', 'add', 'create', 'new', 'more'], + categories: ['IT'], + }, + { + name: 'Minus circle', + filename: 'minuscircle.svg', + searchTerms: ['minus', 'circle', 'remove', 'delete', 'less'], + categories: ['IT'], + }, + { + name: 'Calendar plus', + filename: 'calendarplus.svg', + searchTerms: ['calendar', 'plus', 'add', 'create', 'new'], + categories: ['IT'], + }, ];