diff --git a/package-lock.json b/package-lock.json index d4d19ba..0eb8dc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "react-icons": "^5.4.0", "react-image-file-resizer": "^0.4.8", "react-rnd": "^10.4.13", + "react-tooltip": "^5.28.0", "react-window": "^1.8.10", "tailwind-merge": "^2.5.4", "tailwind-scrollbar": "^3.1.0", @@ -54,6 +55,7 @@ "eslint-plugin-react-refresh": "^0.4.14", "globals": "^15.11.0", "postcss": "^8.4.48", + "sass-embedded": "^1.83.4", "tailwindcss": "^3.4.14", "vite": "^5.4.10" } @@ -378,6 +380,13 @@ "node": ">=6.9.0" } }, + "node_modules/@bufbuild/protobuf": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.3.tgz", + "integrity": "sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg==", + "dev": true, + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -3814,6 +3823,13 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", + "dev": true, + "license": "MIT/X11" + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -3965,6 +3981,12 @@ "url": "https://polar.sh/cva" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -4002,6 +4024,13 @@ "color-support": "bin.js" } }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -5458,6 +5487,13 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz", + "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==", + "dev": true, + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -7251,6 +7287,20 @@ } } }, + "node_modules/react-tooltip": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.28.0.tgz", + "integrity": "sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.1", + "classnames": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, "node_modules/react-window": { "version": "1.8.10", "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz", @@ -7474,6 +7524,16 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-array-concat": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", @@ -7539,6 +7599,407 @@ "license": "MIT", "optional": true }, + "node_modules/sass-embedded": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.83.4.tgz", + "integrity": "sha512-Hf2burRA/y5PGxsg6jB9UpoK/xZ6g/pgrkOcdl6j+rRg1Zj8XhGKZ1MTysZGtTPUUmiiErqzkP5+Kzp95yv9GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "^2.0.0", + "buffer-builder": "^0.2.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-android-arm": "1.83.4", + "sass-embedded-android-arm64": "1.83.4", + "sass-embedded-android-ia32": "1.83.4", + "sass-embedded-android-riscv64": "1.83.4", + "sass-embedded-android-x64": "1.83.4", + "sass-embedded-darwin-arm64": "1.83.4", + "sass-embedded-darwin-x64": "1.83.4", + "sass-embedded-linux-arm": "1.83.4", + "sass-embedded-linux-arm64": "1.83.4", + "sass-embedded-linux-ia32": "1.83.4", + "sass-embedded-linux-musl-arm": "1.83.4", + "sass-embedded-linux-musl-arm64": "1.83.4", + "sass-embedded-linux-musl-ia32": "1.83.4", + "sass-embedded-linux-musl-riscv64": "1.83.4", + "sass-embedded-linux-musl-x64": "1.83.4", + "sass-embedded-linux-riscv64": "1.83.4", + "sass-embedded-linux-x64": "1.83.4", + "sass-embedded-win32-arm64": "1.83.4", + "sass-embedded-win32-ia32": "1.83.4", + "sass-embedded-win32-x64": "1.83.4" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.83.4.tgz", + "integrity": "sha512-9Z4pJAOgEkXa3VDY/o+U6l5XvV0mZTJcSl0l/mSPHihjAHSpLYnOW6+KOWeM8dxqrsqTYcd6COzhanI/a++5Gw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.83.4.tgz", + "integrity": "sha512-tgX4FzmbVqnQmD67ZxQDvI+qFNABrboOQgwsG05E5bA/US42zGajW9AxpECJYiMXVOHmg+d81ICbjb0fsVHskw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-ia32": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.83.4.tgz", + "integrity": "sha512-RsFOziFqPcfZXdFRULC4Ayzy9aK6R6FwQ411broCjlOBX+b0gurjRadkue3cfUEUR5mmy0KeCbp7zVKPLTK+5Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.83.4.tgz", + "integrity": "sha512-EHwh0nmQarBBrMRU928eTZkFGx19k/XW2YwbPR4gBVdWLkbTgCA5aGe8hTE6/1zStyx++3nDGvTZ78+b/VvvLg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.83.4.tgz", + "integrity": "sha512-0PgQNuPWYy1jEOEPDVsV89KfqOsMLIp9CSbjBY7jRcwRhyVAcigqrUG6bDeNtojHUYKA1kU+Eh/85WxOHUOgBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.83.4.tgz", + "integrity": "sha512-rp2ywymWc3nymnSnAFG5R/8hvxWCsuhK3wOnD10IDlmNB7o4rzKby1c+2ZfpQGowlYGWsWWTgz8FW2qzmZsQRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.83.4.tgz", + "integrity": "sha512-kLkN2lXz9PCgGfDS8Ev5YVcl/V2173L6379en/CaFuJJi7WiyPgBymW7hOmfCt4uO4R1y7CP2Uc08DRtZsBlAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.83.4.tgz", + "integrity": "sha512-nL90ryxX2lNmFucr9jYUyHHx21AoAgdCL1O5Ltx2rKg2xTdytAGHYo2MT5S0LIeKLa/yKP/hjuSvrbICYNDvtA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.83.4.tgz", + "integrity": "sha512-E0zjsZX2HgESwyqw31EHtI39DKa7RgK7nvIhIRco1d0QEw227WnoR9pjH3M/ZQy4gQj3GKilOFHM5Krs/omeIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-ia32": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.83.4.tgz", + "integrity": "sha512-ew5HpchSzgAYbQoriRh8QhlWn5Kw2nQ2jHoV9YLwGKe3fwwOWA0KDedssvDv7FWnY/FCqXyymhLd6Bxae4Xquw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.83.4.tgz", + "integrity": "sha512-0RrJRwMrmm+gG0VOB5b5Cjs7Sd+lhqpQJa6EJNEaZHljJokEfpE5GejZsGMRMIQLxEvVphZnnxl6sonCGFE/QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.83.4.tgz", + "integrity": "sha512-IzMgalf6MZOxgp4AVCgsaWAFDP/IVWOrgVXxkyhw29fyAEoSWBJH4k87wyPhEtxSuzVHLxKNbc8k3UzdWmlBFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-ia32": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.83.4.tgz", + "integrity": "sha512-LLb4lYbcxPzX4UaJymYXC+WwokxUlfTJEFUv5VF0OTuSsHAGNRs/rslPtzVBTvMeG9TtlOQDhku1F7G6iaDotA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.83.4.tgz", + "integrity": "sha512-zoKlPzD5Z13HKin1UGR74QkEy+kZEk2AkGX5RelRG494mi+IWwRuWCppXIovor9+BQb9eDWPYPoMVahwN5F7VA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.83.4.tgz", + "integrity": "sha512-hB8+/PYhfEf2zTIcidO5Bpof9trK6WJjZ4T8g2MrxQh8REVtdPcgIkoxczRynqybf9+fbqbUwzXtiUao2GV+vQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.83.4.tgz", + "integrity": "sha512-83fL4n+oeDJ0Y4KjASmZ9jHS1Vl9ESVQYHMhJE0i4xDi/P3BNarm2rsKljq/QtrwGpbqwn8ujzOu7DsNCMDSHA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.83.4.tgz", + "integrity": "sha512-NlnGdvCmTD5PK+LKXlK3sAuxOgbRIEoZfnHvxd157imCm/s2SYF/R28D0DAAjEViyI8DovIWghgbcqwuertXsA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.83.4.tgz", + "integrity": "sha512-J2BFKrEaeSrVazU2qTjyQdAk+MvbzJeTuCET0uAJEXSKtvQ3AzxvzndS7LqkDPbF32eXAHLw8GVpwcBwKbB3Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-ia32": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.83.4.tgz", + "integrity": "sha512-uPAe9T/5sANFhJS5dcfAOhOJy8/l2TRYG4r+UO3Wp4yhqbN7bggPvY9c7zMYS0OC8tU/bCvfYUDFHYMCl91FgA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.83.4.tgz", + "integrity": "sha512-C9fkDY0jKITdJFij4UbfPFswxoXN9O/Dr79v17fJnstVwtUojzVJWKHUXvF0Zg2LIR7TCc4ju3adejKFxj7ueA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -7985,6 +8446,29 @@ "license": "MIT", "optional": true }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", + "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/tailwind-merge": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz", @@ -8396,6 +8880,13 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "5.4.11", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", diff --git a/package.json b/package.json index b4403e6..75aa933 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "react-icons": "^5.4.0", "react-image-file-resizer": "^0.4.8", "react-rnd": "^10.4.13", + "react-tooltip": "^5.28.0", "react-window": "^1.8.10", "tailwind-merge": "^2.5.4", "tailwind-scrollbar": "^3.1.0", @@ -56,6 +57,7 @@ "eslint-plugin-react-refresh": "^0.4.14", "globals": "^15.11.0", "postcss": "^8.4.48", + "sass-embedded": "^1.83.4", "tailwindcss": "^3.4.14", "vite": "^5.4.10" } diff --git a/src/App.css b/src/App.css index 43c1019..55d480c 100644 --- a/src/App.css +++ b/src/App.css @@ -1,4 +1,18 @@ .fabric-canvas-container { - box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.2); - border-radius: 0px; -} \ No newline at end of file + box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.2); + border-radius: 0px; +} + +.tooltip { + --tooltip-spacing: 30px; +} +/* Hide scrollbar for Chrome, Safari and Opera */ +.example::-webkit-scrollbar { + display: none; +} + +/* Hide scrollbar for IE, Edge and Firefox */ +.example { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} diff --git a/src/components/Canvas/Canvas.jsx b/src/components/Canvas/Canvas.jsx index c2993a3..ab7c44d 100644 --- a/src/components/Canvas/Canvas.jsx +++ b/src/components/Canvas/Canvas.jsx @@ -160,7 +160,7 @@ export default function Canvas() { return ( diff --git a/src/components/EachComponent/Customization/StrokeCustomization.jsx b/src/components/EachComponent/Customization/StrokeCustomization.jsx index ccef96c..9555049 100644 --- a/src/components/EachComponent/Customization/StrokeCustomization.jsx +++ b/src/components/EachComponent/Customization/StrokeCustomization.jsx @@ -1,4 +1,4 @@ -import { useContext, useState, useRef, useEffect } from "react"; +import { useContext, useState, useRef, useEffect, useCallback } from "react"; import { fabric } from "fabric"; import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext"; import CanvasContext from "@/components/Context/canvasContext/CanvasContext"; @@ -47,34 +47,37 @@ const StrokeCustomization = () => { } } if (object.strokeWidth) { - setStrokeWidth(0); + setStrokeWidth(object.strokeWidth || 0); } }; // Recursively process group objects - const processGroupObjects = (group) => { + const processGroupObjects = useCallback((group, callback) => { group._objects.forEach((obj) => { if (obj.type === "group") { - processGroupObjects(obj); // Handle nested groups + processGroupObjects(obj, callback); // Handle nested groups } else { - handleObjectStyle(obj); // Apply styles to each object + callback(obj); // Apply callback to each object } }); - }; + }, []); // Effect to get previous values from active object useEffect(() => { if (activeObject) { if (activeObject.type === "group") { - processGroupObjects(activeObject); // Process grouped objects + processGroupObjects(activeObject, handleObjectStyle); // Process grouped objects } else { handleObjectStyle(activeObject); // Process single object } } - }, [activeObject]); + }, [activeObject, processGroupObjects]); - const updatePreview = () => { - if (!previewRef.current) return; + // Update preview style + const updatePreview = useCallback(() => { + if (!previewRef.current) { + return; + } const previewStyle = { width: "80px", @@ -90,10 +93,6 @@ const StrokeCustomization = () => { } Object.assign(previewRef.current.style, previewStyle); - }; - - useEffect(() => { - updatePreview(); }, [ strokeWidth, strokeColor, @@ -102,24 +101,35 @@ const StrokeCustomization = () => { gradientDirection, ]); + useEffect(() => { + updatePreview(); + }, [updatePreview]); + + // Handle stroke width change const handleStrokeWidthChange = (value) => { setStrokeWidth(value); }; + // Handle color type change const handleColorTypeChange = (type) => { setColorType(type); }; + // Handle stroke color change const handleStrokeColorChange = (e) => { setStrokeColor(e.target.value); }; + // Handle gradient color change const handleGradientColorChange = (key, e) => { setGradientStrokeColors((prev) => ({ ...prev, [key]: e.target.value })); }; - const applyStrokeStyle = () => { - if (!activeObject || activeObject.type === "line") return; + // Apply stroke style to active object + const applyStrokeStyle = useCallback(() => { + if (!activeObject || activeObject.type === "line" || !canvas) { + return; + } const width = activeObject?.width || 0; const height = activeObject?.height || 0; @@ -132,7 +142,7 @@ const StrokeCustomization = () => { }; const directionCoords = coords[gradientDirection]; - const applyStrokeStyle = (object) => { + const applyStrokeToObject = (object) => { object.set("strokeWidth", strokeWidth); if (colorType === "color") { @@ -151,56 +161,42 @@ const StrokeCustomization = () => { } }; - const processGroupObjects = (group) => { - group._objects.forEach((obj) => { - if (obj.type === "group") { - processGroupObjects(obj); - } else { - applyStrokeStyle(obj); - } - }); - }; - if (activeObject.type === "group") { - processGroupObjects(activeObject); + processGroupObjects(activeObject, applyStrokeToObject); } else { - applyStrokeStyle(activeObject); + applyStrokeToObject(activeObject); } canvas.renderAll(); - }; - - // Automatically apply stroke styles on state change - useEffect(() => { - applyStrokeStyle(); }, [ + activeObject, strokeWidth, strokeColor, gradientStrokeColors, colorType, gradientDirection, + canvas, + processGroupObjects, ]); + // Automatically apply stroke styles on state change + useEffect(() => { + applyStrokeStyle(); + }, [applyStrokeStyle]); + + // Revert stroke styles const revertStroke = () => { - if (!activeObject) return; + if (!activeObject || !canvas) { + return; + } const revertObject = (object) => { object.set("strokeWidth", 0); object.set("stroke", null); }; - const processGroupObjects = (group) => { - group._objects.forEach((obj) => { - if (obj.type === "group") { - processGroupObjects(obj); - } else { - revertObject(obj); - } - }); - }; - if (activeObject.type === "group") { - processGroupObjects(activeObject); + processGroupObjects(activeObject, revertObject); } else { revertObject(activeObject); } @@ -208,187 +204,196 @@ const StrokeCustomization = () => { canvas.renderAll(); }; - const content = () => { - return ( - -
-
- -
- handleStrokeWidthChange(value)} - className="flex-grow" - /> - - handleStrokeWidthChange(Number(e.target.value)) - } - className="w-16" - /> + // Render the component + return ( + + + +
+
+ +
+ handleStrokeWidthChange(value)} + className="flex-grow" + /> + + handleStrokeWidthChange(Number(e.target.value)) + } + className="w-16" + /> +
-
-
- handleColorTypeChange(value)} - > - - - - Solid - - -
- Gradient - - - -
- -
-
+
+ handleColorTypeChange(value)} + > + + + + Solid + + +
+ Gradient + + + +
+ +
+
+ +
+
-
-
-
- - -
- - -
+
+ +
+ + +
-
- -
-
+
+ +
+
+ + handleGradientColorChange("color1", e) + } + className="w-10 h-10 p-1 rounded-md cursor-pointer" + /> +
+
handleGradientColorChange("color1", e)} - className="w-10 h-10 p-1 rounded-md cursor-pointer" + className="flex-grow" /> -
- handleGradientColorChange("color1", e)} - className="flex-grow" - />
-
-
- -
-
+
+ +
+
+ + handleGradientColorChange("color2", e) + } + className="w-10 h-10 p-1 rounded-md cursor-pointer" + /> +
+
handleGradientColorChange("color2", e)} - className="w-10 h-10 p-1 rounded-md cursor-pointer" + className="flex-grow" /> -
- handleGradientColorChange("color2", e)} - className="flex-grow" - />
-
- - -
+ + +
-
- -
+
+
+ className="border rounded-md p-2 flex items-center justify-center" + style={{ height: "120px" }} + > +
+
+
+ +
+ +
- -
- - -
-
- - ); - }; - - return ( - - - {content()} + ); diff --git a/src/components/EachComponent/Customization/TextCustomization.jsx b/src/components/EachComponent/Customization/TextCustomization.jsx index c0f545c..b440c04 100644 --- a/src/components/EachComponent/Customization/TextCustomization.jsx +++ b/src/components/EachComponent/Customization/TextCustomization.jsx @@ -17,7 +17,7 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; -import { useCallback, useContext, useEffect, useState } from "react"; +import { useCallback, useContext, useEffect, useRef, useState } from "react"; import { AlignLeft, AlignCenter, @@ -31,6 +31,7 @@ import { } from "lucide-react"; import { RiLineHeight } from "react-icons/ri"; import { Slider } from "@/components/ui/slider"; +import { Tooltip } from "react-tooltip"; const fonts = [ "Roboto", @@ -88,6 +89,11 @@ const TextCustomization = () => { const { activeObject } = useContext(ActiveObjectContext); const { canvas, setSelectedPanel, textColor } = useContext(CanvasContext); + const activeObjectType = activeObject?.type; + const hasClipPath = !!activeObject?.clipPath; + const customClipPath = activeObject?.isClipPath; + + const prevTextRef = useRef(""); const [text, setText] = useState(""); const [fontFamily, setFontFamily] = useState("Arial"); const [fontSize, setFontSize] = useState(20); @@ -102,6 +108,13 @@ const TextCustomization = () => { useEffect(() => { if (activeObject?.type === "i-text") { + if ( + activeObject?.text !== undefined && + activeObject.text !== prevTextRef.current + ) { + setText(activeObject.text); + prevTextRef.current = activeObject.text; + } setText(activeObject?.text || ""); setFontFamily(activeObject?.fontFamily || "Arial"); setFontSize(activeObject?.fontSize || 20); @@ -119,11 +132,16 @@ const TextCustomization = () => { const updateActiveObject = useCallback( (properties) => { if (activeObject?.type === "i-text") { - activeObject.set(properties); + // Preserve the text value when updating other properties + const updatedProperties = { + ...properties, + text: text || prevTextRef.current || properties.text, + }; + activeObject.set(updatedProperties); canvas?.renderAll(); } }, - [activeObject, canvas] + [activeObject, canvas, text] ); // Add dependencies const applyChanges = useCallback(() => { @@ -153,6 +171,12 @@ const TextCustomization = () => { updateActiveObject, // Add this dependency ]); + const handleColorPanelClick = () => { + // Store current text value before switching panels + prevTextRef.current = text; + setSelectedPanel("color"); + }; + // Automatically apply changes when state updates useEffect(() => { if (activeObject?.type === "i-text") { @@ -209,52 +233,75 @@ const TextCustomization = () => { {/* New Toolbar Design */}
{/* Font Family Select */} - - + + + + {/* Font Size Controls */}
- - { - const numericValue = e.target.value.replace(/\D/g, ""); - handleFontSizeChange(parseInt(numericValue) || 12); - }} - className="w-12 h-8 border-0 text-center" + + + + + + { + const numericValue = e.target.value.replace(/\D/g, ""); + handleFontSizeChange(parseInt(numericValue) || 12); + }} + className="w-12 h-8 border-0 text-center" + /> + + + + + + -
{/* Vertical Separator */} @@ -262,74 +309,102 @@ const TextCustomization = () => { {/* Text Formatting Controls */}
- + {activeObjectType !== "image" && + !hasClipPath && + !customClipPath && ( + + + + )} - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{/* Vertical Separator */} @@ -338,9 +413,11 @@ const TextCustomization = () => { {/* Spacing Controls */} - + + +
@@ -373,6 +450,7 @@ const TextCustomization = () => {
+
{/* Text Input */} diff --git a/src/components/ObjectShortcut.jsx b/src/components/ObjectShortcut.jsx index e879438..adbc276 100644 --- a/src/components/ObjectShortcut.jsx +++ b/src/components/ObjectShortcut.jsx @@ -1,19 +1,39 @@ -import { useCallback, useContext } from 'react' -import CanvasContext from './Context/canvasContext/CanvasContext'; -import ActiveObjectContext from './Context/activeObject/ObjectContext'; -import { fabric } from 'fabric'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'; -import { Button } from './ui/button'; -import { BringToFront, CopyPlus, GroupIcon, SquareX, Trash2, UngroupIcon } from 'lucide-react'; +import { useCallback, useContext, useMemo, useState } from "react"; +import CanvasContext from "./Context/canvasContext/CanvasContext"; +import ActiveObjectContext from "./Context/activeObject/ObjectContext"; +import { fabric } from "fabric"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "./ui/tooltip"; +import { Button } from "./ui/button"; +import { + BringToFront, + SendToBack, + CopyPlus, + GroupIcon, + SquareX, + Trash2, + UngroupIcon, + Layers, +} from "lucide-react"; +import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; export const ObjectShortcut = ({ value }) => { const { canvas } = useContext(CanvasContext); const { setActiveObject, activeObject } = useContext(ActiveObjectContext); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const activeObjects = canvas.getActiveObjects(); + const multipleObjects = activeObjects.length > 1; + const objectActive = canvas.getActiveObject(); + const isGroupObject = objectActive && objectActive.type === "group"; const groupSelectedObjects = () => { const activeObjects = canvas.getActiveObjects(); // Get selected objects if (activeObjects.length > 1) { - canvas.discardActiveObject(); const group = new fabric.Group(activeObjects, { @@ -21,70 +41,98 @@ export const ObjectShortcut = ({ value }) => { top: canvas?.height / 2, originX: "center", originY: "center", - selectable: true, // Allow group selection - subTargetCheck: true, // Allow individual object selection - hasControls: true, // Enable resizing/movement of the group - }) + selectable: true, // Allow group selection + subTargetCheck: true, // Allow individual object selection + hasControls: true, // Enable resizing/movement of the group + }); canvas.remove(...activeObjects); canvas.add(group); canvas.setActiveObject(group); setActiveObject(group); canvas.renderAll(); } else { - console.log("Select at least two objects") + console.log("Select at least two objects"); } }; const ungroupSelectedObjects = () => { const activeObject = canvas.getActiveObject(); if (activeObject && activeObject.type === "group") { - // Get the group - const group = activeObject; + const groupObjects = activeObject._objects; + canvas.discardActiveObject(); - // Remove the group from the canvas - canvas.remove(group); + canvas.remove(activeObject); setActiveObject(null); - // Iterate through each object in the group - group._objects.forEach((object) => { - // Calculate the absolute position based on the group's transformation - const objLeft = object.left * group.scaleX + group.left; - const objTop = object.top * group.scaleY + group.top; + const ungroupedObjects = []; + + groupObjects.forEach((object) => { + // Calculate absolute position + const objLeft = object.left * activeObject.scaleX + activeObject.left; + const objTop = object.top * activeObject.scaleY + activeObject.top; - // Reset transformations and positions object.set({ left: objLeft, top: objTop, - scaleX: object.scaleX * group.scaleX, // Adjust scale based on group - scaleY: object.scaleY * group.scaleY, // Adjust scale based on group - angle: object.angle + group.angle, // Adjust rotation - hasControls: true, // Allow resizing - selectable: true, // Make selectable - group: null, // Remove group reference + scaleX: object.scaleX * activeObject.scaleX, + scaleY: object.scaleY * activeObject.scaleY, + angle: object.angle + activeObject.angle, + hasControls: true, + selectable: true, + group: null, originX: "center", originY: "center", }); - // Update coordinates object.setCoords(); - // Add the object back to the canvas canvas.add(object); + + ungroupedObjects.push(object); }); - // Clear the selection - canvas.discardActiveObject(); + const selection = new fabric.ActiveSelection(ungroupedObjects, { + canvas: canvas, + originX: "center", + originY: "center", + }); + + canvas.setActiveObject(selection); + setActiveObject(selection); - // Render the canvas to reflect changes canvas.renderAll(); } }; - // Bring Selected Object to Front + // Check if object is at front or back + const objectPosition = useMemo(() => { + if (!activeObject || !canvas) { + return { isAtFront: false, isAtBack: false }; + } + + const allObjects = canvas.getObjects(); + const index = allObjects.indexOf(activeObject); + + return { + isAtFront: index === allObjects.length - 1, + isAtBack: index === 0, + }; + }, [activeObject, canvas]); + + // Layer ordering functions with checks const bringToFront = () => { - if (activeObject) { + if (activeObject && !objectPosition.isAtFront) { activeObject.bringToFront(); canvas.renderAll(); + setIsPopoverOpen(false); + } + }; + + const sendToBack = () => { + if (activeObject && !objectPosition.isAtBack) { + activeObject.sendToBack(); + canvas.renderAll(); + setIsPopoverOpen(false); } }; @@ -94,8 +142,10 @@ export const ObjectShortcut = ({ value }) => { const allObjects = canvas?.getObjects(); - const textObjects = allObjects.filter((obj) => obj.type === 'textbox' || obj.type === 'text' || - obj.type === 'i-text'); + const textObjects = allObjects.filter( + (obj) => + obj.type === "textbox" || obj.type === "text" || obj.type === "i-text" + ); if (activeObject) { canvas.remove(activeObject); @@ -116,47 +166,62 @@ export const ObjectShortcut = ({ value }) => { // Clone the active object to create a true deep copy activeObject.clone((clonedObject) => { // Add the cloned object to the canvas - clonedObject.set("left", clonedObject?.left + 30) + clonedObject.set("left", clonedObject?.left + 30); canvas.add(clonedObject); canvas.renderAll(); }); - } + }; // for clear canvas const clearCanvas = () => { canvas.clear(); canvas.renderAll(); setActiveObject(null); - } + }; return (
-
- } - label="Group" - onClick={groupSelectedObjects} - tooltipContent={ -
-

Group selected objects

-

To select multiple objects:

-
    -
  1. Hold down the Shift key
  2. -
  3. Click and drag with the left mouse button to select objects
  4. -
  5. Release the Shift key and mouse button
  6. -
-

Then click this button to group the selected objects.

-
- } - /> +
+ {multipleObjects && ( + } + label="Group" + onClick={groupSelectedObjects} + tooltipContent={ +
+

Group selected objects

+

To select multiple objects:

+
    +
  1. Hold down the Shift key
  2. +
  3. + Click and drag with the left mouse button to select + objects +
  4. +
  5. Release the Shift key and mouse button
  6. +
+

+ Then click this button to group the selected objects. +

+
+ } + /> + )} - } - label="Ungroup" - onClick={ungroupSelectedObjects} - tooltipContent="Ungroup selected objects" - /> + {isGroupObject && ( + } + label="Ungroup" + onClick={ungroupSelectedObjects} + tooltipContent="Ungroup selected objects" + /> + )} } @@ -164,13 +229,45 @@ export const ObjectShortcut = ({ value }) => { onClick={duplicating} tooltipContent="Duplicate selected objects" /> - - } - label="To Front" - onClick={bringToFront} - tooltipContent="Bring selected objects to front" - /> + + + + + + + + +

Change object position

+
+
+ +
+ + +
+
+
} @@ -188,15 +285,19 @@ export const ObjectShortcut = ({ value }) => {
- ) -} - + ); +}; function ActionButton({ icon, label, onClick, tooltipContent }) { return ( -
- +
diff --git a/src/components/Panel/EditorPanel.jsx b/src/components/Panel/EditorPanel.jsx index d28260b..10e01f2 100644 --- a/src/components/Panel/EditorPanel.jsx +++ b/src/components/Panel/EditorPanel.jsx @@ -20,7 +20,7 @@ const EditorPanel = () => { return ( <> {selectedPanel !== "" && ( -
+
{renderPanel()}
)} diff --git a/src/components/Panel/StrokePanel.jsx b/src/components/Panel/StrokePanel.jsx new file mode 100644 index 0000000..e8d7900 --- /dev/null +++ b/src/components/Panel/StrokePanel.jsx @@ -0,0 +1,29 @@ +import { useContext } from "react"; +import ApplyColor from "../EachComponent/ApplyColor"; +import { Button } from "../ui/button"; +import CanvasContext from "../Context/canvasContext/CanvasContext"; +import { X } from "lucide-react"; +import { ScrollArea } from "../ui/scroll-area"; + +const ColorPanel = () => { + const { setSelectedPanel } = useContext(CanvasContext); + return ( +
+
+

Color

+ +
+ + + +
+ ); +}; + +export default ColorPanel; diff --git a/src/components/Panel/TextPanel.jsx b/src/components/Panel/TextPanel.jsx index cdcc761..62c57b3 100644 --- a/src/components/Panel/TextPanel.jsx +++ b/src/components/Panel/TextPanel.jsx @@ -20,6 +20,7 @@ export default function TextPanel() { setOpen(false); } }, [activeObject]); + const addText = () => { if (canvas) { const text = new fabric.IText("Editable Text", { @@ -27,6 +28,8 @@ export default function TextPanel() { top: 100, fontFamily: "Poppins", fontSize: 16, + stroke: "", // empty string for no stroke color + strokeWidth: 0, // set stroke width to 0 }); // Add the text to the canvas and re-render canvas.add(text); @@ -50,7 +53,7 @@ export default function TextPanel() {
- +