feat: add basic konva implementation
This commit is contained in:
parent
a536253511
commit
fd71c506c6
4 changed files with 182 additions and 30 deletions
|
|
@ -13,11 +13,14 @@
|
||||||
"@radix-ui/react-menubar": "^1.1.2",
|
"@radix-ui/react-menubar": "^1.1.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"konva": "^9.3.16",
|
||||||
"lucide-react": "^0.464.0",
|
"lucide-react": "^0.464.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-konva": "^18.2.10",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"use-image": "^1.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.15.0",
|
"@eslint/js": "^9.15.0",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ importers:
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
|
konva:
|
||||||
|
specifier: ^9.3.16
|
||||||
|
version: 9.3.16
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.464.0
|
specifier: ^0.464.0
|
||||||
version: 0.464.0(react@18.3.1)
|
version: 0.464.0(react@18.3.1)
|
||||||
|
|
@ -26,12 +29,18 @@ importers:
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18.3.1
|
specifier: ^18.3.1
|
||||||
version: 18.3.1(react@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:
|
tailwind-merge:
|
||||||
specifier: ^2.5.5
|
specifier: ^2.5.5
|
||||||
version: 2.5.5
|
version: 2.5.5
|
||||||
tailwindcss-animate:
|
tailwindcss-animate:
|
||||||
specifier: ^1.0.7
|
specifier: ^1.0.7
|
||||||
version: 1.0.7(tailwindcss@3.4.16)
|
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:
|
devDependencies:
|
||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^9.15.0
|
specifier: ^9.15.0
|
||||||
|
|
@ -802,6 +811,9 @@ packages:
|
||||||
'@types/react-dom@18.3.1':
|
'@types/react-dom@18.3.1':
|
||||||
resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==}
|
resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==}
|
||||||
|
|
||||||
|
'@types/react-reconciler@0.28.8':
|
||||||
|
resolution: {integrity: sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==}
|
||||||
|
|
||||||
'@types/react@18.3.12':
|
'@types/react@18.3.12':
|
||||||
resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==}
|
resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==}
|
||||||
|
|
||||||
|
|
@ -1237,6 +1249,11 @@ packages:
|
||||||
isexe@2.0.0:
|
isexe@2.0.0:
|
||||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
|
|
||||||
|
its-fine@1.2.5:
|
||||||
|
resolution: {integrity: sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=18.0'
|
||||||
|
|
||||||
jackspeak@3.4.3:
|
jackspeak@3.4.3:
|
||||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||||
|
|
||||||
|
|
@ -1273,6 +1290,9 @@ packages:
|
||||||
keyv@4.5.4:
|
keyv@4.5.4:
|
||||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||||
|
|
||||||
|
konva@9.3.16:
|
||||||
|
resolution: {integrity: sha512-qa47cefGDDHzkToGRGDsy24f/Njrz7EHP56jQ8mlDcjAPO7vkfTDeoBDIfmF7PZtpfzDdooafQmEUJMDU2F7FQ==}
|
||||||
|
|
||||||
levn@0.4.1:
|
levn@0.4.1:
|
||||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
@ -1464,6 +1484,19 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18.3.1
|
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:
|
react-refresh@0.14.2:
|
||||||
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
@ -1667,6 +1700,12 @@ packages:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
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:
|
use-sidecar@1.1.2:
|
||||||
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
|
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
@ -2380,6 +2419,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 18.3.12
|
'@types/react': 18.3.12
|
||||||
|
|
||||||
|
'@types/react-reconciler@0.28.8':
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.12
|
||||||
|
|
||||||
'@types/react@18.3.12':
|
'@types/react@18.3.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prop-types': 15.7.13
|
'@types/prop-types': 15.7.13
|
||||||
|
|
@ -2846,6 +2889,11 @@ snapshots:
|
||||||
|
|
||||||
isexe@2.0.0: {}
|
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:
|
jackspeak@3.4.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@isaacs/cliui': 8.0.2
|
'@isaacs/cliui': 8.0.2
|
||||||
|
|
@ -2874,6 +2922,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
json-buffer: 3.0.1
|
json-buffer: 3.0.1
|
||||||
|
|
||||||
|
konva@9.3.16: {}
|
||||||
|
|
||||||
levn@0.4.1:
|
levn@0.4.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
|
|
@ -3033,6 +3083,22 @@ snapshots:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
scheduler: 0.23.2
|
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-refresh@0.14.2: {}
|
||||||
|
|
||||||
react-remove-scroll-bar@2.3.6(@types/react@18.3.12)(react@18.3.1):
|
react-remove-scroll-bar@2.3.6(@types/react@18.3.12)(react@18.3.1):
|
||||||
|
|
@ -3258,6 +3324,11 @@ snapshots:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.3.12
|
'@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):
|
use-sidecar@1.1.2(@types/react@18.3.12)(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
detect-node-es: 1.1.0
|
detect-node-es: 1.1.0
|
||||||
|
|
|
||||||
BIN
public/blocks/programmer-art/stone.png
Normal file
BIN
public/blocks/programmer-art/stone.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 223 B |
136
src/App.tsx
136
src/App.tsx
|
|
@ -1,38 +1,116 @@
|
||||||
import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSeparator, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger } from "@/components/ui/menubar"
|
import { useEffect, useRef, useState } from "react";
|
||||||
import ThemeChanger from "./components/menubar/theme-changer"
|
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() {
|
function App() {
|
||||||
return (
|
const stageContainerRef = useRef<HTMLDivElement>(null);
|
||||||
<>
|
const stageRef = useRef(null);
|
||||||
<Menubar>
|
|
||||||
<MenubarMenu>
|
|
||||||
<MenubarTrigger>File</MenubarTrigger>
|
|
||||||
<MenubarContent>
|
|
||||||
<MenubarItem>Open Schematic</MenubarItem>
|
|
||||||
<MenubarItem>Open Image</MenubarItem>
|
|
||||||
|
|
||||||
<MenubarSeparator />
|
const [stageSize, setStageSize] = useState({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
});
|
||||||
|
|
||||||
<MenubarSub>
|
const [stageScale, setStageScale] = useState(1);
|
||||||
<MenubarSubTrigger>Export to...</MenubarSubTrigger>
|
const [stageCoords, setStageCoords] = useState({
|
||||||
<MenubarSubContent>
|
x: 0,
|
||||||
<MenubarItem>.schematic</MenubarItem>
|
y: 0,
|
||||||
<MenubarItem>.litematic</MenubarItem>
|
});
|
||||||
<MenubarItem>image</MenubarItem>
|
|
||||||
</MenubarSubContent>
|
|
||||||
</MenubarSub>
|
|
||||||
</MenubarContent>
|
|
||||||
</MenubarMenu>
|
|
||||||
|
|
||||||
<MenubarMenu>
|
const [image] = useImage("/blocks/programmer-art/stone.png");
|
||||||
<MenubarTrigger>More</MenubarTrigger>
|
|
||||||
<MenubarContent>
|
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 (
|
||||||
|
<main className="h-screen grid grid-rows-[2.5rem_1fr]">
|
||||||
|
<Menubar>
|
||||||
|
<MenubarMenu>
|
||||||
|
<MenubarTrigger>File</MenubarTrigger>
|
||||||
|
<MenubarContent>
|
||||||
|
<MenubarItem>Open Schematic</MenubarItem>
|
||||||
|
<MenubarItem>Open Image</MenubarItem>
|
||||||
|
|
||||||
|
<MenubarSeparator />
|
||||||
|
|
||||||
|
<MenubarSub>
|
||||||
|
<MenubarSubTrigger>Export to...</MenubarSubTrigger>
|
||||||
|
<MenubarSubContent>
|
||||||
|
<MenubarItem>.schematic</MenubarItem>
|
||||||
|
<MenubarItem>.litematic</MenubarItem>
|
||||||
|
<MenubarItem>image</MenubarItem>
|
||||||
|
</MenubarSubContent>
|
||||||
|
</MenubarSub>
|
||||||
|
</MenubarContent>
|
||||||
|
</MenubarMenu>
|
||||||
|
|
||||||
|
<MenubarMenu>
|
||||||
|
<MenubarTrigger>More</MenubarTrigger>
|
||||||
|
<MenubarContent>
|
||||||
<ThemeChanger />
|
<ThemeChanger />
|
||||||
</MenubarContent>
|
</MenubarContent>
|
||||||
</MenubarMenu>
|
</MenubarMenu>
|
||||||
</Menubar>
|
</Menubar>
|
||||||
</>
|
|
||||||
)
|
<div ref={stageContainerRef} className="w-full h-full">
|
||||||
|
<Stage
|
||||||
|
width={stageSize.width}
|
||||||
|
height={stageSize.height}
|
||||||
|
draggable
|
||||||
|
ref={stageRef}
|
||||||
|
x={stageCoords.x}
|
||||||
|
y={stageCoords.y}
|
||||||
|
scaleX={stageScale}
|
||||||
|
scaleY={stageScale}
|
||||||
|
onWheel={onWheel}
|
||||||
|
>
|
||||||
|
<Layer>
|
||||||
|
<Text text="test" fontSize={20} fill="white" />
|
||||||
|
<Image image={image} />
|
||||||
|
</Layer>
|
||||||
|
</Stage>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App
|
export default App;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue