diff --git a/package.json b/package.json index 470c717..182a30e 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,14 @@ "@radix-ui/react-menubar": "^1.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "konva": "^9.3.16", "lucide-react": "^0.464.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-konva": "^18.2.10", "tailwind-merge": "^2.5.5", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "use-image": "^1.1.1" }, "devDependencies": { "@eslint/js": "^9.15.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 252a356..1aa3c1b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + konva: + specifier: ^9.3.16 + version: 9.3.16 lucide-react: specifier: ^0.464.0 version: 0.464.0(react@18.3.1) @@ -26,12 +29,18 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-konva: + specifier: ^18.2.10 + version: 18.2.10(konva@9.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tailwind-merge: specifier: ^2.5.5 version: 2.5.5 tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.16) + use-image: + specifier: ^1.1.1 + version: 1.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: '@eslint/js': specifier: ^9.15.0 @@ -802,6 +811,9 @@ packages: '@types/react-dom@18.3.1': resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} + '@types/react-reconciler@0.28.8': + resolution: {integrity: sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==} + '@types/react@18.3.12': resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} @@ -1237,6 +1249,11 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + its-fine@1.2.5: + resolution: {integrity: sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==} + peerDependencies: + react: '>=18.0' + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -1273,6 +1290,9 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + konva@9.3.16: + resolution: {integrity: sha512-qa47cefGDDHzkToGRGDsy24f/Njrz7EHP56jQ8mlDcjAPO7vkfTDeoBDIfmF7PZtpfzDdooafQmEUJMDU2F7FQ==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1464,6 +1484,19 @@ packages: peerDependencies: react: ^18.3.1 + react-konva@18.2.10: + resolution: {integrity: sha512-ohcX1BJINL43m4ynjZ24MxFI1syjBdrXhqVxYVDw2rKgr3yuS0x/6m1Y2Z4sl4T/gKhfreBx8KHisd0XC6OT1g==} + peerDependencies: + konva: ^8.0.1 || ^7.2.5 || ^9.0.0 + react: '>=18.0.0' + react-dom: '>=18.0.0' + + react-reconciler@0.29.2: + resolution: {integrity: sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^18.3.1 + react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -1667,6 +1700,12 @@ packages: '@types/react': optional: true + use-image@1.1.1: + resolution: {integrity: sha512-n4YO2k8AJG/BcDtxmBx8Aa+47kxY5m335dJiCQA5tTeVU4XdhrhqR6wT0WISRXwdMEOv5CSjqekDZkEMiiWaYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + use-sidecar@1.1.2: resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} @@ -2380,6 +2419,10 @@ snapshots: dependencies: '@types/react': 18.3.12 + '@types/react-reconciler@0.28.8': + dependencies: + '@types/react': 18.3.12 + '@types/react@18.3.12': dependencies: '@types/prop-types': 15.7.13 @@ -2846,6 +2889,11 @@ snapshots: isexe@2.0.0: {} + its-fine@1.2.5(react@18.3.1): + dependencies: + '@types/react-reconciler': 0.28.8 + react: 18.3.1 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -2874,6 +2922,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + konva@9.3.16: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -3033,6 +3083,22 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-konva@18.2.10(konva@9.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@types/react-reconciler': 0.28.8 + its-fine: 1.2.5(react@18.3.1) + konva: 9.3.16 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-reconciler: 0.29.2(react@18.3.1) + scheduler: 0.23.2 + + react-reconciler@0.29.2(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + react-refresh@0.14.2: {} react-remove-scroll-bar@2.3.6(@types/react@18.3.12)(react@18.3.1): @@ -3258,6 +3324,11 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + use-image@1.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + use-sidecar@1.1.2(@types/react@18.3.12)(react@18.3.1): dependencies: detect-node-es: 1.1.0 diff --git a/public/blocks/programmer-art/stone.png b/public/blocks/programmer-art/stone.png new file mode 100644 index 0000000..87e19ff Binary files /dev/null and b/public/blocks/programmer-art/stone.png differ diff --git a/src/App.tsx b/src/App.tsx index 3d38c47..ac62d14 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,38 +1,116 @@ -import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSeparator, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger } from "@/components/ui/menubar" -import ThemeChanger from "./components/menubar/theme-changer" +import { useEffect, useRef, useState } from "react"; +import { Image, Layer, Stage, Text } from "react-konva"; + +import { + Menubar, + MenubarContent, + MenubarItem, + MenubarMenu, + MenubarSeparator, + MenubarSub, + MenubarSubContent, + MenubarSubTrigger, + MenubarTrigger, +} from "@/components/ui/menubar"; + +import ThemeChanger from "./components/menubar/theme-changer"; +import useImage from "use-image"; function App() { - return ( - <> - - - File - - Open Schematic - Open Image + const stageContainerRef = useRef(null); + const stageRef = useRef(null); - + const [stageSize, setStageSize] = useState({ + width: 0, + height: 0, + }); - - Export to... - - .schematic - .litematic - image - - - - + const [stageScale, setStageScale] = useState(1); + const [stageCoords, setStageCoords] = useState({ + x: 0, + y: 0, + }); - - More - + const [image] = useImage("/blocks/programmer-art/stone.png"); + + const onWheel = (e) => { + const stage = e.target.getStage(); + const oldScale = stage.scaleX(); + const pointer = stage.getPointerPosition(); + + const mousePoint = { + x: (pointer.x - stage.x()) / oldScale, + y: (pointer.y - stage.y()) / oldScale, + }; + + const newScale = e.evt.deltaY < 0 ? oldScale * 1.05 : oldScale / 1.05; + + setStageScale(newScale); + setStageCoords({ + x: pointer.x - mousePoint.x * newScale, + y: pointer.y - mousePoint.y * newScale, + }); + }; + + useEffect(() => { + if (stageContainerRef.current && stageRef.current) { + setStageSize({ + width: stageContainerRef.current.offsetWidth, + height: stageContainerRef.current.offsetHeight, + }); + } + }, []); + + return ( +
+ + + File + + Open Schematic + Open Image + + + + + Export to... + + .schematic + .litematic + image + + + + + + + More + - - - - ) + + + +
+ + + + + + +
+
+ ); } -export default App +export default App;