feat: add basic konva implementation

This commit is contained in:
trafficlunar 2024-12-03 21:36:06 +00:00
parent a536253511
commit fd71c506c6
4 changed files with 182 additions and 30 deletions

View file

@ -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",

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

View file

@ -1,9 +1,68 @@
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() {
const stageContainerRef = useRef<HTMLDivElement>(null);
const stageRef = useRef(null);
const [stageSize, setStageSize] = useState({
width: 0,
height: 0,
});
const [stageScale, setStageScale] = useState(1);
const [stageCoords, setStageCoords] = useState({
x: 0,
y: 0,
});
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 (
<>
<main className="h-screen grid grid-rows-[2.5rem_1fr]">
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
@ -31,8 +90,27 @@ function App() {
</MenubarContent>
</MenubarMenu>
</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;