diff --git a/inventree_template_editor/frontend/package-lock.json b/inventree_template_editor/frontend/package-lock.json index 02fbdaf..b6808e2 100644 --- a/inventree_template_editor/frontend/package-lock.json +++ b/inventree_template_editor/frontend/package-lock.json @@ -14,14 +14,13 @@ "@mantine/core": "^7.12.2", "@tabler/icons-react": "^3.17.0", "@uiw/react-split": "^5.9.3", - "fabric": "^5.4.0", + "fabric": "^6.4.2", "preact": "^10.23.1", "qrcode-svg": "^1.1.0", "zustand": "^4.5.5" }, "devDependencies": { "@preact/preset-vite": "^2.9.0", - "@types/fabric": "^5.3.9", "@types/node": "^22.5.5", "@types/qrcode-svg": "^1.1.5", "babel-plugin-macros": "^3.1.0", @@ -1557,13 +1556,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/fabric": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/@types/fabric/-/fabric-5.3.9.tgz", - "integrity": "sha512-2UEVcGUG/taX5MRqW7mjCedHFdh6VZLseyMWw3DiILH0h/fdwW0mwijgZGTqAvl+uhfdv793wU3XcQxMVbuXZA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1667,35 +1659,25 @@ } }, "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", "license": "MIT", "optional": true, "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "license": "MIT", - "optional": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" } }, "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "license": "MIT", "optional": true, + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } @@ -1836,13 +1818,6 @@ "concat-map": "0.0.1" } }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "license": "BSD-2-Clause", - "optional": true - }, "node_modules/browserslist": { "version": "4.23.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", @@ -2124,20 +2099,6 @@ "node": ">=12" } }, - "node_modules/data-urls/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -2300,7 +2261,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -2450,16 +2411,16 @@ } }, "node_modules/fabric": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/fabric/-/fabric-5.4.0.tgz", - "integrity": "sha512-jI2W6GBt6iUp9oZBswYfYPqDGiT/Xg8uw0Wr9+9zx5cyXTB5Xz1C600LFTi9pfHPwuD10+ChkYMI9pXQN/HkTA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/fabric/-/fabric-6.4.2.tgz", + "integrity": "sha512-wsXxy45eHQ66t66RSjti9lM6Z6iC+EBhrxvWWkZlcrhsI8Lxgn2mii/o2UqRpUr4IwzmggTZq/SAGjtZ6mHTtQ==", "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=16.20.0" }, "optionalDependencies": { - "canvas": "^2.8.0", - "jsdom": "^19.0.0" + "canvas": "^2.11.2", + "jsdom": "^20.0.1" } }, "node_modules/form-data": { @@ -2919,42 +2880,41 @@ } }, "node_modules/jsdom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", - "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==", + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", "license": "MIT", "optional": true, "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.5.0", - "acorn-globals": "^6.0.0", + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", - "data-urls": "^3.0.1", - "decimal.js": "^10.3.1", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", "html-encoding-sniffer": "^3.0.0", "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^3.0.0", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^10.0.0", - "ws": "^8.2.3", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" }, "peerDependencies": { "canvas": "^2.5.0" @@ -3386,11 +3346,17 @@ } }, "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "license": "MIT", - "optional": true + "optional": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } }, "node_modules/path-is-absolute": { "version": "1.0.1", @@ -3800,16 +3766,16 @@ "optional": true }, "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "license": "ISC", "optional": true, "dependencies": { "xmlchars": "^2.2.0" }, "engines": { - "node": ">=10" + "node": ">=v12.22.7" } }, "node_modules/scheduler": { @@ -4302,28 +4268,17 @@ } } }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", - "license": "MIT", - "optional": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, "node_modules/w3c-xmlserializer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", - "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", "license": "MIT", "optional": true, "dependencies": { "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/webidl-conversions": { @@ -4360,9 +4315,9 @@ } }, "node_modules/whatwg-url": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz", - "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "license": "MIT", "optional": true, "dependencies": { diff --git a/inventree_template_editor/frontend/package.json b/inventree_template_editor/frontend/package.json index 37f29de..8ee3b19 100644 --- a/inventree_template_editor/frontend/package.json +++ b/inventree_template_editor/frontend/package.json @@ -15,14 +15,13 @@ "@mantine/core": "^7.12.2", "@tabler/icons-react": "^3.17.0", "@uiw/react-split": "^5.9.3", - "fabric": "^5.4.0", + "fabric": "^6.4.2", "preact": "^10.23.1", "qrcode-svg": "^1.1.0", "zustand": "^4.5.5" }, "devDependencies": { "@preact/preset-vite": "^2.9.0", - "@types/fabric": "^5.3.9", "@types/node": "^22.5.5", "@types/qrcode-svg": "^1.1.5", "babel-plugin-macros": "^3.1.0", diff --git a/inventree_template_editor/frontend/src/components/LabelEditor/EditorArea.tsx b/inventree_template_editor/frontend/src/components/LabelEditor/EditorArea.tsx index fb5de73..7b121c9 100644 --- a/inventree_template_editor/frontend/src/components/LabelEditor/EditorArea.tsx +++ b/inventree_template_editor/frontend/src/components/LabelEditor/EditorArea.tsx @@ -1,4 +1,4 @@ -import { fabric } from 'fabric'; +import { Point as FabricPoint, Rect as FabricRect, FabricObject, util, Canvas as FabricCanvas, TEvent, TPointerEvent } from 'fabric'; import { FabricJSCanvas, useFabricJSEditor } from '../fabricjs-react'; import { useCallback, useEffect, useRef } from 'react'; @@ -19,8 +19,8 @@ export const EditorArea = () => { isDragging: false, lastPosX: 0, lastPosY: 0, - pageElement: null as fabric.Object | null, - ignoreObjects: new Set() + pageElement: null as FabricObject | null, + ignoreObjects: new Set() }); const pageWidth = useLabelEditorState((s) => s.pageWidth); @@ -87,7 +87,7 @@ export const EditorArea = () => { height / (pageHeight + 10) ); - editor.canvas.zoomToPoint({ x: width / 2, y: height / 2 }, minZoom); + editor.canvas.zoomToPoint(new FabricPoint(width / 2, height / 2), minZoom); }, [editor]); useEffect(() => { @@ -118,7 +118,7 @@ export const EditorArea = () => { if (new_zoom > 20) new_zoom = 20; if (new_zoom < minZoom) new_zoom = minZoom; editor.canvas.zoomToPoint( - { x: event.e.offsetX, y: event.e.offsetY }, + new FabricPoint(event.e.offsetX, event.e.offsetY), new_zoom ); event.e.preventDefault(); @@ -127,9 +127,12 @@ export const EditorArea = () => { }); on('mouse:down', (event) => { + // @ts-expect-error - missing type if (event.e.altKey === true || event.e.button === 1) { editorState.current.isDragging = true; + // @ts-expect-error - missing type editorState.current.lastPosX = event.e.clientX; + // @ts-expect-error - missing type editorState.current.lastPosY = event.e.clientY; editor.canvas.selection = false; } @@ -137,9 +140,12 @@ export const EditorArea = () => { on('mouse:move', (event) => { if (editorState.current.isDragging) { + // @ts-expect-error - missing type handleDrag(event.e.clientX, event.e.clientY); editor.canvas.requestRenderAll(); + // @ts-expect-error - missing type editorState.current.lastPosX = event.e.clientX; + // @ts-expect-error - missing type editorState.current.lastPosY = event.e.clientY; } }); @@ -154,10 +160,10 @@ export const EditorArea = () => { }); on('object:added', (e) => { - if (editorState.current.ignoreObjects.has(e.target as fabric.Object)) + if (editorState.current.ignoreObjects.has(e.target as FabricObject)) return; labelEditorStore.setState((s) => ({ - objects: [...s.objects, e.target as fabric.Object] + objects: [...s.objects, e.target as FabricObject] })); }); @@ -168,7 +174,7 @@ export const EditorArea = () => { }); // handle selections - const autoSwitchPanel = (e: fabric.IEvent) => { + const autoSwitchPanel = (e: Partial>) => { // check if selection was not set by the user if (e.e === undefined) return; const { setRightPanel, selectedObjects } = labelEditorStore.getState(); @@ -195,14 +201,14 @@ export const EditorArea = () => { on('selection:created', (e) => { labelEditorStore.setState({ - selectedObjects: e.selected as fabric.Object[] + selectedObjects: e.selected as FabricObject[] }); autoSwitchPanel(e); }); on('selection:updated', (e) => { labelEditorStore.setState({ - selectedObjects: e.selected as fabric.Object[] + selectedObjects: e.selected as FabricObject[] }); autoSwitchPanel(e); }); @@ -210,7 +216,7 @@ export const EditorArea = () => { // change width and height instead of scaling the object on('object:scaling', (e) => { const settings = labelEditorStore.getState().pageSettings; - const obj = e.target as fabric.Object; + const obj = e.target as FabricObject; const corner = e.transform?.corner; if ( @@ -279,7 +285,7 @@ export const EditorArea = () => { on('object:moving', (e) => { const settings = labelEditorStore.getState().pageSettings; if (!settings.snap['grid.enable']) return; - const obj = e.target as fabric.Object; + const obj = e.target as FabricObject; const gridSize = unitToPixel( settings.grid['size.size'], settings.grid['size.unit'] @@ -301,7 +307,7 @@ export const EditorArea = () => { // snap when rotating object on('object:rotating', (e) => { const snap = labelEditorStore.getState().pageSettings.snap; - const obj = e.target as fabric.Object; + const obj = e.target as FabricObject; if (snap['angle.enable']) { obj.snapAngle = e.e.altKey ? 0.1 : snap['angle.value']; } else { @@ -356,7 +362,7 @@ export const EditorArea = () => { top: object.top! + direction[1] * gridSize }); object.setCoords(); - object.canvas?.fire('object:moving', { target: object }); + object.canvas?.fire('object:moving', { target: object } as any); }); editor?.canvas.renderAll(); }; @@ -384,7 +390,7 @@ export const EditorArea = () => { const strokeWidth = 0.2; - const pageElement = new fabric.Rect({ + const pageElement = new FabricRect({ left: -(strokeWidth / 2), top: -(strokeWidth / 2), width: pageWidth + strokeWidth, @@ -464,7 +470,7 @@ export const EditorArea = () => { }, [editor]); const customOnReady = useCallback( - (canvas: fabric.Canvas) => { + async (canvas: FabricCanvas) => { // initialize fabricjs canvas onReady(canvas); @@ -476,20 +482,16 @@ export const EditorArea = () => { o.state = s; }); - fabric.util.enlivenObjects( - objects, - (objects: any) => { - objects.forEach((o: fabric.Object) => { - canvas.add(o); - }); - labelEditorStore.setState((s) => ({ - objects: [...s.objects, ...(objects as fabric.Object[])], - templateStr: undefined - })); - canvas.renderAll(); - }, - 'fabric.Custom' - ); + const fabricObjects = await util.enlivenObjects(objects); + fabricObjects.forEach((o) => { + canvas.add(o as FabricObject); + }); + + labelEditorStore.setState((s) => ({ + objects: [...s.objects, ...(fabricObjects as FabricObject[])], + templateStr: undefined + })); + canvas.renderAll(); } }, [onReady] diff --git a/inventree_template_editor/frontend/src/components/LabelEditor/LabelEditorContext.tsx b/inventree_template_editor/frontend/src/components/LabelEditor/LabelEditorContext.tsx index 1190f15..73e9955 100644 --- a/inventree_template_editor/frontend/src/components/LabelEditor/LabelEditorContext.tsx +++ b/inventree_template_editor/frontend/src/components/LabelEditor/LabelEditorContext.tsx @@ -1,4 +1,4 @@ -import { fabric } from 'fabric'; +import { FabricObject } from 'fabric'; import { FabricJSEditor } from '../fabricjs-react'; import { createContext, useContext } from 'react'; import { createStore, useStore } from 'zustand'; @@ -7,8 +7,8 @@ import { RightPanelKeyType } from './panels/RightPanel'; export type LabelEditorState = { editor?: FabricJSEditor; - objects: fabric.Object[]; - selectedObjects: fabric.Object[]; + objects: FabricObject[]; + selectedObjects: FabricObject[]; handleDrag?: (clientX?: number, clientY?: number) => void; zoomToFit?: () => void; setRightPanel?: (panel: RightPanelKeyType) => void; diff --git a/inventree_template_editor/frontend/src/components/LabelEditor/objects/Circle.tsx b/inventree_template_editor/frontend/src/components/LabelEditor/objects/Circle.tsx index 8d513b7..0f2c734 100644 --- a/inventree_template_editor/frontend/src/components/LabelEditor/objects/Circle.tsx +++ b/inventree_template_editor/frontend/src/components/LabelEditor/objects/Circle.tsx @@ -1,7 +1,7 @@ import { t } from '@lingui/macro'; import { Stack } from '@mantine/core'; import { IconCircleFilled, IconRuler2 } from '@tabler/icons-react'; -import { fabric } from 'fabric'; +import { Circle as FabricCircle } from 'fabric'; import { LabelEditorObject } from '.'; import { useLabelEditorState } from '../LabelEditorContext'; @@ -9,7 +9,7 @@ import { InputGroup } from '../panels/Components'; import { GeneralSettingBlock, buildStyle, - createFabricObject, + getCustomFabricBaseObject, styleHelper } from './_BaseObject'; import { @@ -43,6 +43,21 @@ const CircleRadiusInputGroup = () => { return ; }; +class CircleObject extends getCustomFabricBaseObject(FabricCircle, ['radiusUnit']) { + static type = 'circle'; + + radiusUnit: string; + + constructor(props: any) { + super(props); + + this.width = props.width || 50; + this.height = props.height || 50; + this.radius = props.radius || 25; + this.radiusUnit = props.radiusUnit || 'mm'; + } +} + export const Circle: LabelEditorObject = { key: 'circle', name: t`Circle`, @@ -71,22 +86,7 @@ export const Circle: LabelEditorObject = { ) } ], - fabricElement: createFabricObject( - fabric.Circle as any, - { - type: 'circle', - radiusUnit: 'mm', - - initialize(props) { - this.width = 50; - this.height = 50; - this.radius = 25; - - this.callSuper('initialize', props); - } - }, - ['radiusUnit'] - ), + fabricElement: CircleObject, useCanvasEvents: () => { const editor = useLabelEditorState((s) => s.editor); @@ -96,7 +96,7 @@ export const Circle: LabelEditorObject = { on('object:scaling', (e) => { if (e.target?.type !== 'circle') return; - const obj = e.target as fabric.Circle; + const obj = e.target as FabricCircle; obj.set({ radius: (obj.width! + obj.height!) / 4 }); }); }, diff --git a/inventree_template_editor/frontend/src/components/LabelEditor/objects/QrCode.tsx b/inventree_template_editor/frontend/src/components/LabelEditor/objects/QrCode.tsx index eca9379..a2dc5d7 100644 --- a/inventree_template_editor/frontend/src/components/LabelEditor/objects/QrCode.tsx +++ b/inventree_template_editor/frontend/src/components/LabelEditor/objects/QrCode.tsx @@ -1,7 +1,7 @@ import { t } from '@lingui/macro'; import { Stack } from '@mantine/core'; import { IconBoxMargin, IconDimensions, IconQrcode } from '@tabler/icons-react'; -import { fabric } from 'fabric'; +import { FabricObject, util } from 'fabric'; import QRCodeSVG from 'qrcode-svg'; import { LabelEditorObject } from '.'; @@ -11,7 +11,7 @@ import { unitToPixel } from '../utils'; import { GeneralSettingBlock, buildStyle, - createFabricObject, + getCustomFabricBaseObject, styleHelper } from './_BaseObject'; import { @@ -91,6 +91,161 @@ const QrCodeBorderInputGroup = () => { return ; }; +// inspired by https://medium.com/@andras.tovishati/how-to-create-qr-code-object-type-in-fabric-js-e99b84e8d6c5 +class QrCodeObject extends getCustomFabricBaseObject(FabricObject, ['data', 'border', 'box_size']) { + static type = 'qrcode'; + + data: string; + size: number; + strokeWidth: number; + stroke: string; + fill: string; + border: number; + box_size: number; + + private path: any[] = [] + + constructor(props: any) { + super(props); + + this.data = props.data ?? 'qr_data'; + this.size = props.size ?? 40; + this.strokeWidth = props.strokeWidth ?? 0; + this.stroke = props.stroke ?? '#000000'; + this.fill = props.fill ?? '#ffffff'; + this.border = props.border ?? 0; + this.box_size = props.box_size ?? 20; + this.width = this.size; + this.height = this.size; + + // scaling to the right does not work properly + this.setControlsVisibility({ + ml: false, + mr: false, + mt: false, + mb: false + }); + + this._createPathData(); + } + + // limit scaling so that the qr code is always a multiple of the QR grid size + getGridSize(settings: PageSettingsType) { + const gridSize = + unitToPixel(settings.grid['size.size'], settings.grid['size.unit']) * + (21 + this.border * 2); + + return gridSize; + } + + _createPathData() { + const qr = new QRCodeSVG({ + content: this.data ?? "A", + padding: this.border ?? 0, + width: this.size ?? 40, + height: this.size ?? 40, + color: this.stroke ?? '#000000', + background: this.fill ?? '#ffffff', + ecl: 'L', + join: true + }); + const svg = qr.svg(); + const match = svg.match(/]*?d=(["\'])?((?:.(?!\1|>))*.?)\1?/); + const path = match ? match[2] : ''; + this.path = util.makePathSimpler(util.parsePath(path)); + this.dirty = true; + return this; + } + + _set(key: string, value: any) { + super._set(key, value); + switch (key) { + case 'data': + case 'border': + this._createPathData(); + break; + case 'size': + if (value <= 0) value = 1; + this.set({ + width: value, + height: value + }); + this._createPathData(); + break; + case 'width': + if (value <= 0) value = 1; + this.height = value; + this.size = value; + this._createPathData(); + break; + case 'height': + if (value <= 0) value = 1; + this.size = value; + this.width = value; + this._createPathData(); + break; + default: + break; + } + return this; + } + + _renderQRCode(ctx: CanvasRenderingContext2D) { + let current, + i, + x = 0, + y = 0, + w2 = this.width / 2, + h2 = this.height / 2; + ctx.beginPath(); + for (i = 0; i < this.path.length; i++) { + current = this.path[i]; + x = current[1]; + y = current[2]; + switch (current[0]) { + case 'M': + ctx.moveTo(x - w2, y - h2); + break; + case 'L': + ctx.lineTo(x - w2, y - h2); + break; + case 'Z': + ctx.closePath(); + break; + default: + break; + } + } + ctx.save(); + ctx.fillStyle = this.stroke; + ctx.fill(); + ctx.restore(); + } + + _renderBackground(ctx: CanvasRenderingContext2D) { + const w = this.width; + const h = this.height; + const x = -(w / 2); + const y = -(h / 2); + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + w, y); + ctx.lineTo(x + w, y + h); + ctx.lineTo(x, y + h); + ctx.lineTo(x, y); + ctx.closePath(); + ctx.save(); + ctx.fillStyle = this.fill; + ctx.fill(); + ctx.restore(); + } + + _render(ctx: CanvasRenderingContext2D) { + this._renderBackground(ctx); + this._renderQRCode(ctx); + } +} + export const QrCode: LabelEditorObject = { key: 'qrcode', name: t`QR Code`, @@ -130,154 +285,7 @@ export const QrCode: LabelEditorObject = { ) } ], - // inspired by https://medium.com/@andras.tovishati/how-to-create-qr-code-object-type-in-fabric-js-e99b84e8d6c5 - fabricElement: createFabricObject( - fabric.Object, - { - type: 'qrcode', - data: 'qr_data', - size: 40, - strokeWidth: 0, - stroke: '#000000', - fill: '#ffffff', - border: 0, - box_size: 20, - - initialize(props) { - this.width = this.size; - this.height = this.size; - - // scaling to the right does not work properly - this.setControlsVisibility({ - ml: false, - mr: false, - mt: false, - mb: false - }); - - this.callSuper('initialize', props); - this._createPathData(); - }, - - // limit scaling so that the qr code is always a multiple of the QR grid size - getGridSize(settings: PageSettingsType) { - const gridSize = - unitToPixel(settings.grid['size.size'], settings.grid['size.unit']) * - (21 + this.border * 2); - - return gridSize; - }, - - _createPathData() { - const qr = new QRCodeSVG({ - content: this.data, - padding: this.border, - width: this.size, - height: this.size, - color: this.stroke, - background: this.fill, - ecl: 'L', - join: true - }); - const svg = qr.svg(); - const match = svg.match(/]*?d=(["\'])?((?:.(?!\1|>))*.?)\1?/); - const path = match ? match[2] : ''; - // @ts-ignore-next-line - this.path = fabric.util.makePathSimpler(fabric.util.parsePath(path)); - this.dirty = true; - return this; - }, - - _set(key: string, value: any) { - this.callSuper('_set', key, value); - switch (key) { - case 'data': - case 'border': - this._createPathData(); - break; - case 'size': - if (value <= 0) value = 1; - this.set({ - width: value, - height: value - }); - this._createPathData(); - break; - case 'width': - if (value <= 0) value = 1; - this.height = value; - this.size = value; - this._createPathData(); - break; - case 'height': - if (value <= 0) value = 1; - this.size = value; - this.width = value; - this._createPathData(); - break; - default: - break; - } - return this; - }, - - _renderQRCode(ctx: CanvasRenderingContext2D) { - let current, - i, - x = 0, - y = 0, - w2 = this.width / 2, - h2 = this.height / 2; - ctx.beginPath(); - for (i = 0; i < this.path.length; i++) { - current = this.path[i]; - x = current[1]; - y = current[2]; - switch (current[0]) { - case 'M': - ctx.moveTo(x - w2, y - h2); - break; - case 'L': - ctx.lineTo(x - w2, y - h2); - break; - case 'Z': - ctx.closePath(); - break; - default: - break; - } - } - ctx.save(); - ctx.fillStyle = this.stroke; - ctx.fill(); - ctx.restore(); - }, - - _renderBackground(ctx: CanvasRenderingContext2D) { - const w = this.width; - const h = this.height; - const x = -(w / 2); - const y = -(h / 2); - ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(x + w, y); - ctx.lineTo(x + w, y + h); - ctx.lineTo(x, y + h); - ctx.lineTo(x, y); - ctx.closePath(); - ctx.save(); - ctx.fillStyle = this.fill; - ctx.fill(); - ctx.restore(); - }, - - _render(ctx: CanvasRenderingContext2D) { - this._renderBackground(ctx); - this._renderQRCode(ctx); - } - }, - ['data', 'border', 'box_size'] - ), + fabricElement: QrCodeObject, export: { style: (object, id) => { return buildStyle(id, [ diff --git a/inventree_template_editor/frontend/src/components/LabelEditor/objects/Rectangle.tsx b/inventree_template_editor/frontend/src/components/LabelEditor/objects/Rectangle.tsx index c3a9b9a..2d380be 100644 --- a/inventree_template_editor/frontend/src/components/LabelEditor/objects/Rectangle.tsx +++ b/inventree_template_editor/frontend/src/components/LabelEditor/objects/Rectangle.tsx @@ -1,13 +1,13 @@ import { t } from '@lingui/macro'; import { Stack } from '@mantine/core'; import { IconRectangleFilled } from '@tabler/icons-react'; -import { fabric } from 'fabric'; +import { Rect as FabricRect } from 'fabric'; import { LabelEditorObject } from '.'; import { GeneralSettingBlock, buildStyle, - createFabricObject, + getCustomFabricBaseObject, styleHelper } from './_BaseObject'; import { @@ -18,6 +18,17 @@ import { SizeInputGroup } from './_InputGroups'; +class RectangleObject extends getCustomFabricBaseObject(FabricRect) { + static type = 'rect'; + + constructor(props: any) { + super(props); + + this.width = props.width ?? 50; + this.height = props.height ?? 50; + } +} + export const Rectangle: LabelEditorObject = { key: 'rect', name: t`Rectangle`, @@ -47,16 +58,7 @@ export const Rectangle: LabelEditorObject = { ) } ], - fabricElement: createFabricObject(fabric.Rect, { - type: 'rect', - - initialize(props) { - this.width = 50; - this.height = 50; - - this.callSuper('initialize', props); - } - }), + fabricElement: RectangleObject, export: { style: (object, id) => { return buildStyle(id, [ diff --git a/inventree_template_editor/frontend/src/components/LabelEditor/objects/Text.tsx b/inventree_template_editor/frontend/src/components/LabelEditor/objects/Text.tsx index 0377556..d780269 100644 --- a/inventree_template_editor/frontend/src/components/LabelEditor/objects/Text.tsx +++ b/inventree_template_editor/frontend/src/components/LabelEditor/objects/Text.tsx @@ -6,17 +6,18 @@ import { IconAlignBoxRightTop, IconTextSize } from '@tabler/icons-react'; -import { fabric } from 'fabric'; +import { IText as FabricIText, FabricObject } from 'fabric'; import { useEffect, useRef, useState } from 'react'; import { LabelEditorObject } from '.'; import { getFonts } from '../fonts'; import { InputGroup } from '../panels/Components'; import { + CustomFabricObject, GeneralSettingBlock, buildStyle, c, - createFabricObject, + getCustomFabricBaseObject, styleHelper } from './_BaseObject'; import { @@ -126,6 +127,38 @@ const TextAlignInputGroup = () => { return ; }; +class TextObject extends getCustomFabricBaseObject(FabricIText, ['fontSizeUnit']) { + static type = 'text'; + + fontSize = 20 + fontSizeUnit = 'mm' + + private tmpWidth = 0; + private tmpHeight = 0; + + constructor(text: string, props: any) { + super(text, props); + + this.fontSizeUnit = props.state.pageSettings.unit['length.unit']; + + // lock dimensions when editing + this.on('editing:entered', () => { + this.tmpWidth = this.width; + this.tmpHeight = this.height; + }); + + this.on('editing:exited', () => { + this.width = this.tmpWidth; + this.height = this.tmpHeight; + }); + + this.on('changed', () => { + this.width = this.tmpWidth; + this.height = this.tmpHeight; + }); + } +} + export const Text: LabelEditorObject = { key: 'text', name: t`Text`, @@ -164,39 +197,7 @@ export const Text: LabelEditorObject = { ) } ], - fabricElement: createFabricObject( - fabric.IText as typeof fabric.Object, - { - type: 'text', - fontSize: 20, - fontSizeUnit: 'mm', - - initialize(props) { - this.fontSizeUnit = props.state.pageSettings.unit['length.unit']; - this.width = 50; - this.height = 50; - - // lock dimensions when editing - this.on('editing:entered', () => { - this.tmpWidth = this.width; - this.tmpHeight = this.height; - }); - - this.on('editing:exited', () => { - this.width = this.tmpWidth; - this.height = this.tmpHeight; - }); - - this.on('changed', () => { - this.width = this.tmpWidth; - this.height = this.tmpHeight; - }); - - this.callSuper('initialize', t`Hello world`, props); - } - }, - ['fontSizeUnit'] - ), + fabricElement: TextObject as unknown as CustomFabricObject, export: { style: (object, id) => { return buildStyle(id, [ diff --git a/inventree_template_editor/frontend/src/components/LabelEditor/objects/_BaseObject.tsx b/inventree_template_editor/frontend/src/components/LabelEditor/objects/_BaseObject.tsx index c5e326a..b6d14ab 100644 --- a/inventree_template_editor/frontend/src/components/LabelEditor/objects/_BaseObject.tsx +++ b/inventree_template_editor/frontend/src/components/LabelEditor/objects/_BaseObject.tsx @@ -1,11 +1,11 @@ import { t } from '@lingui/macro'; import { Stack } from '@mantine/core'; -import { fabric } from 'fabric'; import { ObjectPanelBlock, SettingBlock } from '.'; import { LabelEditorState } from '../LabelEditorContext'; import { pixelToUnit } from '../utils'; import { NameInputGroup } from './_InputGroups'; +import { FabricObject } from "fabric"; type InitializeProps = { left: number; @@ -13,13 +13,7 @@ type InitializeProps = { state: LabelEditorState; }; -export const createFabricObject = ( - base: typeof fabric.Object, - properties: Record & { - initialize?: (props: InitializeProps) => void; - }, - customFields?: string[] -) => { +export const getCustomFabricBaseObject = {}>(Base: T, customFields?: string[]) => { const fields = [ 'positionUnit', 'sizeUnit', @@ -28,47 +22,38 @@ export const createFabricObject = ( ...(customFields || []) ]; - const customBase = fabric.util.createClass(base, { - positionUnit: 'mm', - sizeUnit: 'mm', - stroke: '#000000', - strokeWidth: 0, - strokeWidthUnit: 'mm', + return class CustomFabricObject extends Base { + positionUnit: string; + sizeUnit: string; + strokeWidthUnit: string; + name: string; + stroke: string; + strokeWidth: number; - initialize(...args: any[]) { + constructor(...args: any[]) { let props: InitializeProps = args[0]; if (typeof props !== 'object' || !('state' in props)) { props = args[1]; } - this.positionUnit = props.state.pageSettings.unit['length.unit']; - this.sizeUnit = props.state.pageSettings.unit['length.unit']; - this.strokeWidthUnit = props.state.pageSettings.unit['length.unit']; - this.callSuper('initialize', ...args); - }, + super(...args); - toObject() { - return this.callSuper('toObject', fields) as Record; + this.positionUnit = (props as any).positionUnit ?? props.state.pageSettings.unit['length.unit'] ?? 'mm'; + this.sizeUnit = (props as any).sizeUnit ?? props.state.pageSettings.unit['length.unit'] ?? 'mm'; + this.strokeWidthUnit = (props as any).strokeWidthUnit ?? props.state.pageSettings.unit['length.unit'] ?? 'mm'; + this.stroke = (props as any).stroke ?? '#000000'; + this.strokeWidth = (props as any).strokeWidth ?? 0; + this.name = (props as any).name; } - }); - - const cls = fabric.util.createClass(customBase, properties); - - cls.fromObject = function ( - o: Record, - callback: (obj: fabric.Object) => any - ) { - const obj = new cls(o); - - Object.entries(o).forEach(([key, value]) => { - obj[key] = value; - }); - callback(obj); - }; + toObject() { + // @ts-expect-error this method exists on a fabricjs object + return super.toObject(fields) as Record; + } + } +} - return cls; -}; +export type CustomFabricObject = ReturnType>; const GeneralPanelBlock: ObjectPanelBlock = () => { return ( @@ -86,9 +71,9 @@ export const GeneralSettingBlock: SettingBlock = { export const buildStyle = (id: string, style: string[]) => `#${id} { ${style - .filter((x) => !!x) - .map((x) => ' ' + x.trimStart()) - .join('\n')} + .filter((x) => !!x) + .map((x) => ' ' + x.trimStart()) + .join('\n')} }`; export const c = (n: number, u: string) => @@ -107,9 +92,9 @@ export const styleHelper = { rotation: (object) => object.angle !== 0 ? [ - 'transform-origin: top left;', - `transform: rotate(${object.angle}deg);` - ] + 'transform-origin: top left;', + `transform: rotate(${object.angle}deg);` + ] : [], background: (object, attr = 'fill') => { if (!object[attr]) return []; @@ -122,9 +107,7 @@ export const styleHelper = { border: (object) => { if (object.strokeWidth === 0) return []; return [ - `outline: ${c(object.strokeWidth, object.strokeWidthUnit)} solid ${ - object.stroke - };` + `outline: ${c(object.strokeWidth, object.strokeWidthUnit)} solid ${object.stroke};` ]; } } satisfies Record) => string[]>; diff --git a/inventree_template_editor/frontend/src/components/LabelEditor/objects/_InputGroups.tsx b/inventree_template_editor/frontend/src/components/LabelEditor/objects/_InputGroups.tsx index a4692ec..6e44a9d 100644 --- a/inventree_template_editor/frontend/src/components/LabelEditor/objects/_InputGroups.tsx +++ b/inventree_template_editor/frontend/src/components/LabelEditor/objects/_InputGroups.tsx @@ -7,7 +7,7 @@ import { IconPalette, IconTag } from '@tabler/icons-react'; -import { fabric } from 'fabric'; +import { FabricObject, CanvasEvents } from 'fabric'; import { useEffect, useMemo } from 'react'; import { useLabelEditorState } from '../LabelEditorContext'; @@ -28,12 +28,12 @@ type UseObjectInputGroupStateProps = { objAttr: string; inputKey: string; }[]; - triggerUpdateEvents?: fabric.EventName[]; + triggerUpdateEvents?: (keyof CanvasEvents)[]; beforeCanvasUpdate?: ( values: Record, - obj: fabric.Object + obj: FabricObject ) => void; - afterCanvasUpdate?: (values: Record, obj: fabric.Object) => void; + afterCanvasUpdate?: (values: Record, obj: FabricObject) => void; } & UseInputGroupProps; export const useObjectInputGroupState = ( @@ -87,7 +87,7 @@ export const useObjectInputGroupState = ( editor?.canvas.fire('object:modified', { target: obj }); editor?.canvas.requestRenderAll(); }, - updateInputs: (obj: fabric.Object) => { + updateInputs: (obj: FabricObject) => { // @ts-ignore-next-line const unit = obj[props.connectionUnitKey]; @@ -116,7 +116,7 @@ export const useObjectInputGroupState = ( editor?.canvas, (on) => { for (const event of props.triggerUpdateEvents || []) { - on(event, (e) => inputState.triggerUpdate(e.target as fabric.Object)); + on(event, (e) => inputState.triggerUpdate((e as any).target as FabricObject)); } }, [editor] diff --git a/inventree_template_editor/frontend/src/components/LabelEditor/objects/index.tsx b/inventree_template_editor/frontend/src/components/LabelEditor/objects/index.tsx index 05ea577..973b3c5 100644 --- a/inventree_template_editor/frontend/src/components/LabelEditor/objects/index.tsx +++ b/inventree_template_editor/frontend/src/components/LabelEditor/objects/index.tsx @@ -1,10 +1,10 @@ -import { fabric } from 'fabric'; - +import { classRegistry } from "fabric"; import { Circle } from './Circle'; import { QrCode } from './QrCode'; import { Rectangle } from './Rectangle'; import { Text } from './Text'; import { TablerIconType } from "../../../types"; +import { CustomFabricObject } from "./_BaseObject"; export type ObjectPanelBlock = (props: {}) => React.JSX.Element; @@ -19,7 +19,7 @@ export type LabelEditorObject = { name: string; icon: TablerIconType; settingBlocks: SettingBlock[]; - fabricElement: any; + fabricElement: CustomFabricObject; useCanvasEvents?: () => void; defaultOpen: string[]; export: { @@ -38,10 +38,8 @@ export const LabelEditorObjects: LabelEditorObject[] = [ export const LabelEditorObjectsMap: Record = Object.fromEntries(LabelEditorObjects.map((object) => [object.key, object])); -// @ts-ignore -fabric.Custom = Object.fromEntries( - Object.entries(LabelEditorObjectsMap).map(([key, value]) => [ - key[0].toUpperCase() + key.slice(1), - value.fabricElement - ]) -); +// register all objects in fabric class registry +Object.entries(LabelEditorObjectsMap).forEach(([key, value]) => classRegistry.setClass( + value.fabricElement, + key, +)); diff --git a/inventree_template_editor/frontend/src/components/LabelEditor/panels/Components.tsx b/inventree_template_editor/frontend/src/components/LabelEditor/panels/Components.tsx index 02c3b11..4d786dc 100644 --- a/inventree_template_editor/frontend/src/components/LabelEditor/panels/Components.tsx +++ b/inventree_template_editor/frontend/src/components/LabelEditor/panels/Components.tsx @@ -207,6 +207,7 @@ export const InputGroup = ({ label={input.label} value={value[key]} onChange={(value) => setValue(key, value)} + decimalScale={10} onBlur={() => state.onBlur?.(key, value[key], value, value, state)} stepHoldDelay={500} stepHoldInterval={100} @@ -288,6 +289,10 @@ export const InputGroup = ({ label={input.label} disabled={input.disabled} data={input.selectOptions} + searchable + comboboxProps={{ + withinPortal: false, + }} size="xs" style={{ width: '150px' }} value={value[key]} @@ -336,38 +341,49 @@ export const InputGroup = ({ if (input.type === 'radio') { return ( - {input.radioOptions?.map((option, idx) => ( - - option.icon ? ( - - ) : ( - - ) - } - disabled={input.disabled} - label={option.label ? option.label : undefined} - indeterminate={option.icon ? value[key] !== option.value : false} - checked={value[key] === option.value} - onChange={() => { - setValue(key, option.value); - const newValue = { - ...value, - [key]: option.value - }; - state.onBlur?.(key, option.value, newValue, value, state); - }} - size={option.icon ? 'lg' : undefined} - /> - ))} + {input.radioOptions?.map((option, idx) => { + // use indeterminate state if icon is set, because icons are only rendered for checked and indeterminate state + const indeterminate = option.icon ? value[key] !== option.value : false; + + return ( + + option.icon ? ( + + ) : ( + + ) + } + disabled={input.disabled} + label={option.label ? option.label : undefined} + indeterminate={indeterminate} + checked={value[key] === option.value} + onChange={() => { + setValue(key, option.value); + const newValue = { + ...value, + [key]: option.value + }; + state.onBlur?.(key, option.value, newValue, value, state); + }} + size={option.icon ? 'lg' : undefined} + /> + ) + })} ); } diff --git a/inventree_template_editor/frontend/src/components/LabelEditor/panels/LeftPanel.tsx b/inventree_template_editor/frontend/src/components/LabelEditor/panels/LeftPanel.tsx index 87f67b4..11b2c30 100644 --- a/inventree_template_editor/frontend/src/components/LabelEditor/panels/LeftPanel.tsx +++ b/inventree_template_editor/frontend/src/components/LabelEditor/panels/LeftPanel.tsx @@ -1,3 +1,4 @@ +import { FabricObject } from 'fabric'; import { ActionIcon, Stack, Tooltip } from '@mantine/core'; import { useCallback } from 'react'; @@ -6,6 +7,7 @@ import { useLabelEditorStore } from '../LabelEditorContext'; import { LabelEditorObject, LabelEditorObjects } from '../objects'; +import { CustomFabricObject } from "../objects/_BaseObject"; export function LeftPanel() { const editor = useLabelEditorState((s) => s.editor); @@ -26,10 +28,10 @@ export function LeftPanel() { // get the next name for the object let nextNum = 0; - editor.canvas.getObjects().forEach((o: fabric.Object) => { + editor.canvas.getObjects().forEach((o: FabricObject) => { if (o.type === obj.type) { // calculate the next free number for this element - const num = (o.name || '').match(/\((\d+)\)/); + const num = ((o as unknown as CustomFabricObject).name || '').match(/\((\d+)\)/); if (num) { nextNum = Math.max(nextNum, parseInt(num[1], 10) + 1); } diff --git a/inventree_template_editor/frontend/src/components/LabelEditor/panels/RightPanel.tsx b/inventree_template_editor/frontend/src/components/LabelEditor/panels/RightPanel.tsx index 51968ca..b894b79 100644 --- a/inventree_template_editor/frontend/src/components/LabelEditor/panels/RightPanel.tsx +++ b/inventree_template_editor/frontend/src/components/LabelEditor/panels/RightPanel.tsx @@ -38,6 +38,7 @@ import { useInputGroupState } from './Components'; import { TablerIconType } from "../../../types"; +import { CustomFabricObject } from "../objects/_BaseObject"; type RightPanelComponent = (props: {}) => React.ReactElement; @@ -295,7 +296,7 @@ const ObjectsRightPanel: RightPanelComponent = () => { fontWeight: selectedObjectsList.includes(object) ? 600 : 400 }} > - {object.name} + {(object as unknown as CustomFabricObject).name} ))} @@ -447,13 +448,6 @@ export function RightPanel() { onChange={setActivePanel as (panel: string | null) => void} placement="right" style={{ flex: 1, display: 'flex' }} - styles={(theme) => ({ - tab: { - '&[data-object-specific]': { - backgroundColor: theme.colors.blue[0], - }, - } - })} > {panels.filter(panel => panel.requiresSelection ? selectedObjects > 0 : true).map((panel) => ( @@ -468,7 +462,7 @@ export function RightPanel() { key={panel.key} value={panel.key} leftSection={} - data-object-specific={panel.requiresSelection} + bg={panel.requiresSelection ? 'var(--mantine-color-blue-0)' : undefined} /> ))} diff --git a/inventree_template_editor/frontend/src/components/fabricjs-react/index.tsx b/inventree_template_editor/frontend/src/components/fabricjs-react/index.tsx index 14df337..3d121c6 100644 --- a/inventree_template_editor/frontend/src/components/fabricjs-react/index.tsx +++ b/inventree_template_editor/frontend/src/components/fabricjs-react/index.tsx @@ -1,20 +1,22 @@ import { useEffect, useMemo, useRef, useState } from "react"; -import { fabric } from "fabric"; +import { Canvas } from "fabric"; export interface Props { className?: string; - onReady?: (canvas: fabric.Canvas) => void; + onReady?: (canvas: Canvas) => void; } export const FabricJSCanvas = ({ className, onReady }: Props) => { - const canvasEl = useRef(null); + const canvasEl = useRef(); const canvasElParent = useRef(null); useEffect(() => { - const canvas = new fabric.Canvas(canvasEl.current); + const canvas = new Canvas(canvasEl.current); const setCurrentDimensions = () => { - canvas.setHeight(canvasElParent.current?.clientHeight || 0); - canvas.setWidth(canvasElParent.current?.clientWidth || 0); + canvas.setDimensions({ + width: canvasElParent.current?.clientWidth || 0, + height: canvasElParent.current?.clientHeight || 0, + }); canvas.renderAll(); }; const resizeCanvas = () => { @@ -36,13 +38,13 @@ export const FabricJSCanvas = ({ className, onReady }: Props) => { return (
- +
); }; export const useFabricJSEditor = (): FabricJSEditorHook => { - const [canvas, setCanvas] = useState(null); + const [canvas, setCanvas] = useState(null); const editor = useMemo( () => canvas ? { canvas } : undefined, @@ -50,7 +52,7 @@ export const useFabricJSEditor = (): FabricJSEditorHook => { ); return { - onReady: (canvasReady: fabric.Canvas): void => { + onReady: (canvasReady: Canvas): void => { setCanvas(canvasReady) }, editor @@ -58,10 +60,10 @@ export const useFabricJSEditor = (): FabricJSEditorHook => { } export interface FabricJSEditor { - canvas: fabric.Canvas + canvas: Canvas } export interface FabricJSEditorHook { editor?: FabricJSEditor; - onReady: (canvas: fabric.Canvas) => void + onReady: (canvas: Canvas) => void }