Merge branch 'dev' into dev-yj

# Conflicts:
#	src/components/Roof2.jsx
This commit is contained in:
yjnoh 2024-08-22 16:05:41 +09:00
commit b9601e4fbb
9 changed files with 502 additions and 326 deletions

View File

@ -2,15 +2,19 @@
import { useCurrentLocale } from '@/locales/client'
import { LocaleProvider } from './LocaleProvider'
import ServerError from './error'
import { ErrorBoundary } from 'next/dist/client/components/error-boundary'
export default function LocaleLayout({ children }) {
const locale = useCurrentLocale()
return (
<>
<LocaleProvider locale={locale} fallback={''}>
{children}
</LocaleProvider>
<ErrorBoundary fallback={<ServerError />}>
<LocaleProvider locale={locale} fallback={<ServerError />}>
{children}
</LocaleProvider>
</ErrorBoundary>
</>
)
}

20
src/common/common.js Normal file
View File

@ -0,0 +1,20 @@
export const Mode = {
DRAW_LINE: 'drawLine', // 기준선 긋기모드`
EDIT: 'edit',
TEMPLATE: 'template',
PATTERNA: 'patterna',
PATTERNB: 'patternb',
TEXTBOX: 'textbox',
DRAW_RECT: 'drawRect',
ROOF_PATTERN: 'roofPattern', //지붕패턴 모드
ROOF_TRESTLE: 'roofTrestle', //지붕가대 모드
FILL_CELLS: 'fillCells', //태양광셀 모드
CELL_POWERCON: 'cellPowercon', //파워콘
DRAW_HELP_LINE: 'drawHelpLine', // 보조선 그리기 모드 지붕 존재해야함
DEFAULT: 'default',
}
export const LineType = {
EAVES: 'eaves', // 처마
RIDGE: 'ridge', // 용마루....
}

View File

@ -12,7 +12,7 @@ export default function Login() {
</div>
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
<form action={login} method="POST" className="space-y-6">
<form action={login} method="POST" encType="application/json" className="space-y-6">
<div>
<label htmlFor="userId" className="block text-sm font-medium leading-6 text-gray-900">
User ID

View File

@ -1,6 +1,6 @@
import { fabric } from 'fabric'
import { v4 as uuidv4 } from 'uuid'
import { getDirection } from '@/util/canvas-util'
import { getDirection, getDirectionByPoint } from '@/util/canvas-util'
export const QLine = fabric.util.createClass(fabric.Line, {
type: 'QLine',
@ -28,7 +28,7 @@ export const QLine = fabric.util.createClass(fabric.Line, {
this.setLength()
this.direction = options.direction ?? getDirection({ x: this.x1, y: this.y1 }, { x: this.x2, y: this.y2 })
this.direction = options.direction ?? getDirectionByPoint({ x: this.x1, y: this.y1 }, { x: this.x2, y: this.y2 })
this.startPoint = { x: this.x1, y: this.y1 }
this.endPoint = { x: this.x2, y: this.y2 }
@ -91,13 +91,37 @@ export const QLine = fabric.util.createClass(fabric.Line, {
this.text = thisText
return
}
let left, top
if (this.direction === 'left' || this.direction === 'right') {
left = (x1 + x2) / 2
top = (y1 + y2) / 2 + 10
} else if (this.direction === 'top' || this.direction === 'bottom') {
left = (x1 + x2) / 2 + 10
top = (y1 + y2) / 2
}
const minX = this.left
const maxX = this.left + this.width
const minY = this.top
const maxY = this.top + this.length
const degree = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI
const text = new fabric.Textbox(this.length.toFixed(0).toString(), {
left: (x1 + x2) / 2,
top: (y1 + y2) / 2,
left: left,
top: top,
fontSize: this.fontSize,
selectable: false,
minX,
maxX,
minY,
maxY,
parentDirection: this.direction,
parentDegree: degree,
parentId: this.id,
editable: false,
selectable: true,
lockRotation: true,
lockScalingX: true,
lockScalingY: true,
name: 'lengthText',
})

View File

@ -129,6 +129,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
})
this.texts = null
})
// polygon.fillCell({ width: 50, height: 30, padding: 10 })
},
initLines() {
@ -156,34 +158,41 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
},
addLengthText() {
if (this.texts.length > 0) {
this.texts.forEach((text) => {
this.canvas.remove(text)
})
}
let points = this.getCurrentPoints()
points.forEach((start, i) => {
const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id && obj.idx === i)
const end = points[(i + 1) % points.length]
const dx = end.x - start.x
const dy = end.y - start.y
const length = Math.sqrt(dx * dx + dy * dy)
if (thisText) {
thisText.set({
left: (start.x + points[(i + 1) % points.length].x) / 2,
top: (start.y + points[(i + 1) % points.length].y) / 2,
text: length.toFixed(0),
})
return
}
const midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2)
const degree = (Math.atan2(dy, dx) * 180) / Math.PI
// Create new text object if it doesn't exist
const text = new fabric.Text(length.toFixed(0), {
left: midPoint.x,
top: midPoint.y,
fontSize: this.fontSize,
selectable: false,
parentId: this.id,
minX: Math.min(start.x, end.x),
maxX: Math.max(start.x, end.x),
minY: Math.min(start.y, end.y),
maxY: Math.max(start.y, end.y),
parentDirection: getDirectionByPoint(start, end),
parentDegree: degree,
dirty: true,
editable: false,
selectable: true,
lockRotation: true,
lockScalingX: true,
lockScalingY: true,
idx: i,
name: 'lengthText',
})

View File

@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from 'react'
import { fabric } from 'fabric'
import { actionHandler, anchorWrapper, calculateIntersection, distanceBetweenPoints, polygonPositionHandler, test } from '@/util/canvas-util'
import { actionHandler, anchorWrapper, polygonPositionHandler } from '@/util/canvas-util'
import { useRecoilState } from 'recoil'
import { canvasSizeState, fontSizeState } from '@/store/canvasAtom'
@ -9,6 +10,7 @@ import { QPolygon } from '@/components/fabric/QPolygon'
import { defineQLine } from '@/util/qline-utils'
import { defineQPloygon } from '@/util/qpolygon-utils'
import { writeImage } from '@/lib/canvas'
import { useCanvasEvent } from '@/hooks/useCanvasEvent'
export function useCanvas(id) {
const [canvas, setCanvas] = useState()
@ -16,7 +18,7 @@ export function useCanvas(id) {
const [history, setHistory] = useState([])
const [canvasSize] = useRecoilState(canvasSizeState)
const [fontSize] = useRecoilState(fontSizeState)
const points = useRef([])
const { setCanvasForEvent, attachDefaultEventOnCanvas } = useCanvasEvent()
/**
* 처음 셋팅
@ -30,6 +32,8 @@ export function useCanvas(id) {
})
setCanvas(c)
setCanvasForEvent(c)
return () => {
c.dispose()
}
@ -37,8 +41,7 @@ export function useCanvas(id) {
useEffect(() => {
// canvas 사이즈가 변경되면 다시
removeEventOnCanvas()
addEventOnCanvas()
attachDefaultEventOnCanvas()
}, [canvasSize])
useEffect(() => {
@ -64,68 +67,10 @@ export function useCanvas(id) {
useEffect(() => {
if (canvas) {
initialize()
removeEventOnCanvas()
addEventOnCanvas()
attachDefaultEventOnCanvas()
}
}, [canvas])
const addEventOnCanvas = () => {
canvas?.on('object:added', onChange)
canvas?.on('object:modified', onChange)
canvas?.on('object:removed', onChange)
canvas?.on('object:added', () => {
document.addEventListener('keydown', handleKeyDown)
})
canvas?.on('mouse:move', drawMouseLines)
canvas?.on('mouse:down', handleMouseDown)
canvas?.on('mouse:out', removeMouseLines)
}
const removeEventOnCanvas = () => {
canvas?.off('object:added')
canvas?.off('object:modified')
canvas?.off('object:removed')
canvas?.off('object:added')
canvas?.off('mouse:move')
canvas?.off('mouse:down')
}
const addEventOnObject = (e) => {
const target = e.target
if (target.name === 'cell') {
target.on('mousedown', () => {
if (target.get('selected')) {
target.set({ selected: false })
target.set({ fill: '#BFFD9F' })
} else {
target.set({ selected: true })
target.set({ fill: 'red' })
}
canvas?.renderAll()
})
}
if (target.name === 'trestle') {
target.on('mousedown', () => {
if (target.defense === 'north') {
alert('북쪽은 선택 불가합니다.')
return
}
if (target.get('selected')) {
target.set({ strokeWidth: 1 })
target.set({ strokeDashArray: [5, 5] })
target.set({ selected: false })
} else {
target.set({ strokeWidth: 5 })
target.set({ strokeDashArray: [0, 0] })
target.set({ selected: true })
}
canvas?.renderAll()
})
}
}
/**
* 마우스 포인터의 가이드라인을 제거합니다.
*/
@ -174,71 +119,6 @@ export function useCanvas(id) {
setIsLocked(false)
}
const drawMouseLines = (e) => {
// 현재 마우스 포인터의 위치를 가져옵니다.
const pointer = canvas?.getPointer(e.e)
// 기존에 그려진 가이드라인을 제거합니다.
removeMouseLines()
if (canvas?.getActiveObject()) {
return
}
// 가로선을 그립니다.
const horizontalLine = new fabric.Line([0, pointer.y, canvasSize.horizontal, pointer.y], {
stroke: 'black',
strokeWidth: 1,
selectable: false,
name: 'mouseLine',
})
// 세로선을 그립니다.
const verticalLine = new fabric.Line([pointer.x, 0, pointer.x, canvasSize.vertical], {
stroke: 'black',
strokeWidth: 1,
selectable: false,
name: 'mouseLine',
})
// 선들을 캔버스에 추가합니다.
canvas?.add(horizontalLine, verticalLine)
// 캔버스를 다시 그립니다.
canvas?.renderAll()
}
const handleMouseDown = (e) => {
// 현재 마우스 포인터의 위치를 가져옵니다.
if (canvas?.getActiveObject()) {
points.current = []
return
}
const pointer = canvas?.getPointer(e.e)
// 클릭한 위치를 배열에 추가합니다.
points.current.push(pointer)
// 두 점을 모두 찍었을 때 사각형을 그립니다.
if (points.current.length === 2) {
const rect = new fabric.Rect({
left: points.current[0].x,
top: points.current[0].y,
width: points.current[1].x - points.current[0].x,
height: points.current[1].y - points.current[0].y,
fill: 'transparent',
stroke: 'black',
strokeWidth: 1,
})
// 사각형을 캔버스에 추가합니다.
canvas?.add(rect)
// 배열을 초기화합니다.
points.current = []
}
}
/**
* 눈금 모양에 맞게 움직이도록 한다.
*/
@ -413,50 +293,6 @@ export function useCanvas(id) {
canvas?.renderAll()
}
/**
* 각종 키보드 이벤트
* https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
*/
const handleKeyDown = (e) => {
const key = e.key
switch (key) {
case 'Delete':
case 'Backspace':
handleDelete()
break
case 'Down': // IE/Edge에서 사용되는 값
case 'ArrowDown':
// "아래 화살표" 키가 눌렸을 때의 동작입니다.
moveDown()
break
case 'Up': // IE/Edge에서 사용되는 값
case 'ArrowUp':
// "위 화살표" 키가 눌렸을 때의 동작입니다.
moveUp()
break
case 'Left': // IE/Edge에서 사용되는 값
case 'ArrowLeft':
// "왼쪽 화살표" 키가 눌렸을 때의 동작입니다.
moveLeft()
break
case 'Right': // IE/Edge에서 사용되는 값
case 'ArrowRight':
// "오른쪽 화살표" 키가 눌렸을 때의 동작입니다.
moveRight()
break
case 'Enter':
// "enter" 또는 "return" 키가 눌렸을 때의 동작입니다.
break
case 'Esc': // IE/Edge에서 사용되는 값
case 'Escape':
break
default:
return // 키 이벤트를 처리하지 않는다면 종료합니다.
}
e.preventDefault()
}
const handleRotate = (degree = 45) => {
const target = canvas?.getActiveObject()
@ -590,6 +426,10 @@ export function useCanvas(id) {
'lockScalingY',
'opacity',
'cells',
'maxX',
'maxY',
'minX',
'minY',
])
const str = JSON.stringify(objs)

277
src/hooks/useCanvasEvent.js Normal file
View File

@ -0,0 +1,277 @@
import { useEffect, useState } from 'react'
import { fabric } from 'fabric'
import { useRecoilValue } from 'recoil'
import { canvasSizeState, modeState } from '@/store/canvasAtom'
// 캔버스에 필요한 이벤트
export function useCanvasEvent() {
const [canvas, setCanvasForEvent] = useState(null)
const canvasSize = useRecoilValue(canvasSizeState)
// 기본적인 이벤트 필요시 추가
const attachDefaultEventOnCanvas = () => {
removeEventOnCanvas()
canvas?.on('object:added', onChange)
canvas?.on('object:added', addEventOnObject)
canvas?.on('object:modified', onChange)
canvas?.on('object:removed', onChange)
canvas?.on('object:added', () => {
document.addEventListener('keydown', handleKeyDown)
})
canvas?.on('mouse:move', drawMouseLines)
canvas?.on('mouse:out', removeMouseLines)
}
const onChange = (e) => {
const target = e.target
if (target) {
// settleDown(target)
}
}
const removeEventOnCanvas = () => {
canvas?.off('object:added')
canvas?.off('object:modified')
canvas?.off('object:removed')
canvas?.off('object:added')
canvas?.off('mouse:move')
canvas?.off('mouse:down')
}
const addEventOnObject = (e) => {
const target = e.target
if (target.type === 'QPolygon' || target.type === 'QLine') {
const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText')
textObjs.forEach((obj) => {
obj.bringToFront()
})
}
if (target.name === 'cell') {
target.on('mousedown', () => {
if (target.get('selected')) {
target.set({ selected: false })
target.set({ fill: '#BFFD9F' })
} else {
target.set({ selected: true })
target.set({ fill: 'red' })
}
canvas?.renderAll()
})
}
if (target.name === 'trestle') {
target.on('mousedown', () => {
if (target.defense === 'north') {
alert('북쪽은 선택 불가합니다.')
return
}
if (target.get('selected')) {
target.set({ strokeWidth: 1 })
target.set({ strokeDashArray: [5, 5] })
target.set({ selected: false })
} else {
target.set({ strokeWidth: 5 })
target.set({ strokeDashArray: [0, 0] })
target.set({ selected: true })
}
canvas?.renderAll()
})
}
if (target.name === 'lengthText') {
const x = target.left
const y = target.top
target.on('selected', (e) => {
Object.keys(target.controls).forEach((controlKey) => {
target.setControlVisible(controlKey, false)
})
})
target.on('moving', (e) => {
if (target.parentDirection === 'left' || target.parentDirection === 'right') {
const minX = target.minX
const maxX = target.maxX
if (target.left <= minX) {
target.set({ left: minX, top: y })
} else if (target.left >= maxX) {
target.set({ left: maxX, top: y })
} else {
target.set({ top: y })
}
} else if (target.parentDirection === 'top' || target.parentDirection === 'bottom') {
const minY = target.minY
const maxY = target.maxY
if (target.top <= minY) {
target.set({ left: x, top: minY })
} else if (target.top >= maxY) {
target.set({ left: x, top: maxY })
} else {
target.set({ left: x })
}
}
canvas?.renderAll()
})
}
}
// 마우스 가로, 세로선 그리기
const drawMouseLines = (e) => {
// 현재 마우스 포인터의 위치를 가져옵니다.
const pointer = canvas?.getPointer(e.e)
// 기존에 그려진 가이드라인을 제거합니다.
removeMouseLines()
if (canvas?.getActiveObject()) {
return
}
// 가로선을 그립니다.
const horizontalLine = new fabric.Line([0, pointer.y, canvasSize.horizontal, pointer.y], {
stroke: 'black',
strokeWidth: 1,
selectable: false,
name: 'mouseLine',
})
// 세로선을 그립니다.
const verticalLine = new fabric.Line([pointer.x, 0, pointer.x, canvasSize.vertical], {
stroke: 'black',
strokeWidth: 1,
selectable: false,
name: 'mouseLine',
})
// 선들을 캔버스에 추가합니다.
canvas?.add(horizontalLine, verticalLine)
// 캔버스를 다시 그립니다.
canvas?.renderAll()
}
//가로, 세로 선 삭제
const removeMouseLines = () => {
if (canvas?._objects.length > 0) {
const mouseLines = canvas?._objects.filter((obj) => obj.name === 'mouseLine')
mouseLines.forEach((item) => canvas?.remove(item))
}
canvas?.renderAll()
}
/**
* 각종 키보드 이벤트
* https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
*/
const handleKeyDown = (e) => {
const key = e.key
switch (key) {
case 'Delete':
case 'Backspace':
handleDelete()
break
case 'Down': // IE/Edge에서 사용되는 값
case 'ArrowDown':
// "아래 화살표" 키가 눌렸을 때의 동작입니다.
moveDown()
break
case 'Up': // IE/Edge에서 사용되는 값
case 'ArrowUp':
// "위 화살표" 키가 눌렸을 때의 동작입니다.
moveUp()
break
case 'Left': // IE/Edge에서 사용되는 값
case 'ArrowLeft':
// "왼쪽 화살표" 키가 눌렸을 때의 동작입니다.
moveLeft()
break
case 'Right': // IE/Edge에서 사용되는 값
case 'ArrowRight':
// "오른쪽 화살표" 키가 눌렸을 때의 동작입니다.
moveRight()
break
case 'Enter':
// "enter" 또는 "return" 키가 눌렸을 때의 동작입니다.
break
case 'Esc': // IE/Edge에서 사용되는 값
case 'Escape':
break
default:
return // 키 이벤트를 처리하지 않는다면 종료합니다.
}
e.preventDefault()
}
const moveDown = () => {
const targetObj = canvas?.getActiveObject()
if (!targetObj) {
return
}
let top = targetObj.top + 10
if (top > canvasSize.vertical) {
top = canvasSize.vertical
}
targetObj.set({ top: top })
canvas?.renderAll()
}
const moveUp = () => {
const targetObj = canvas?.getActiveObject()
if (!targetObj) {
return
}
let top = targetObj.top - 10
if (top < 0) {
top = 0
}
targetObj.set({ top: top })
canvas?.renderAll()
}
const moveLeft = () => {
const targetObj = canvas?.getActiveObject()
if (!targetObj) {
return
}
let left = targetObj.left - 10
if (left < 0) {
left = 0
}
targetObj.set({ left: left })
canvas?.renderAll()
}
const moveRight = () => {
const targetObj = canvas?.getActiveObject()
if (!targetObj) {
return
}
let left = targetObj.left + 10
if (left > canvasSize.horizontal) {
left = canvasSize.horizontal
}
targetObj.set({ left: left })
canvas?.renderAll()
}
return {
setCanvasForEvent,
attachDefaultEventOnCanvas,
}
}

View File

@ -23,6 +23,7 @@ import {
templateTypeState,
wallState,
compassState,
modeState,
} from '@/store/canvasAtom'
import { QLine } from '@/components/fabric/QLine'
import { fabric } from 'fabric'
@ -30,25 +31,10 @@ import { QPolygon } from '@/components/fabric/QPolygon'
import offsetPolygon from '@/util/qpolygon-utils'
import { isObjectNotEmpty } from '@/util/common-utils'
import * as turf from '@turf/turf'
export const Mode = {
DRAW_LINE: 'drawLine', // 기준선 긋기모드`
EDIT: 'edit',
TEMPLATE: 'template',
PATTERNA: 'patterna',
PATTERNB: 'patternb',
TEXTBOX: 'textbox',
DRAW_RECT: 'drawRect',
ROOF_PATTERN: 'roofPattern', //지붕패턴 모드
ROOF_TRESTLE: 'roofTrestle', //지붕가대 모드
FILL_CELLS: 'fillCells', //태양광셀 모드
CELL_POWERCON: 'cellPowercon', //파워콘
DRAW_HELP_LINE: 'drawHelpLine', // 보조선 그리기 모드 지붕 존재해야함
DEFAULT: 'default',
}
import { Mode } from '@/common/common'
export function useMode() {
const [mode, setMode] = useState()
const [mode, setMode] = useRecoilState(modeState)
const points = useRef([])
const historyPoints = useRef([])
const historyLines = useRef([])
@ -233,55 +219,37 @@ export function useMode() {
canvas?.renderAll()
}
const addEvent = (mode) => {
// 모드에 따른 마우스 이벤트 변경
const changeMouseEvent = (mode) => {
canvas?.off('mouse:down')
switch (mode) {
case 'drawLine':
drawLineMode()
canvas?.on('mouse:down', mouseEvent.drawLineMode)
break
case 'edit':
editMode()
break
case 'template':
templateMode()
break
case 'patterna':
applyTemplateA()
break
case 'patternb':
applyTemplateB()
canvas?.on('mouse:down', mouseEvent.editMode)
break
case 'textbox':
textboxMode()
canvas?.on('mouse:down', mouseEvent.textboxMode)
break
case 'drawRect':
drawRectMode()
break
case 'roofPattern':
makeRoofPatternPolygon()
break
case 'roofTrestle':
makeRoofTrestle()
break
case 'fillCells':
makeRoofFillCells()
break
case 'cellPowercon':
makeCellPowercon()
canvas?.on('mouse:down', mouseEvent.drawRectMode)
break
case 'drawHelpLine':
canvas?.off('selection:created', addSelectCreatedEvent)
canvas?.off('selection:cleared', addSelectClearedEvent)
canvas?.on('selection:created', addSelectCreatedEvent)
canvas?.on('selection:cleared', addSelectClearedEvent)
drawHelpLineMode()
break
case 'default':
canvas?.off('mouse:down')
break
}
}
// 모드에 따른 키보드 이벤트 변경
const changeKeyboardEvent = (mode) => {}
const keyValid = () => {
if (points.current.length === 0) {
alert('시작점을 선택해주세요')
@ -504,19 +472,63 @@ export function useMode() {
}
const changeMode = (canvas, mode) => {
setEndPoint(null)
pointCount.current = 0
setMode(mode)
// mode변경 시 이전 이벤트 제거
setCanvas(canvas)
canvas?.off('mouse:down')
addEvent(mode)
changeMouseEvent(mode)
changeKeyboardEvent(mode)
switch (mode) {
case 'template':
templateMode()
break
case 'patterna':
applyTemplateA()
break
case 'patternb':
applyTemplateB()
break
case 'roofPattern':
makeRoofPatternPolygon()
break
case 'roofTrestle':
makeRoofTrestle()
break
case 'fillCells':
makeRoofFillCells()
break
case 'cellPowercon':
makeCellPowercon()
break
case 'drawHelpLine':
drawHelpLineMode()
break
case 'default':
clearEditMode()
break
}
}
const editMode = () => {
canvas?.on('mouse:down', function (options) {
const mouseEvent = {
drawLineMode: (options) => {
const pointer = canvas?.getPointer(options.e)
const line = new QLine(
[pointer.x, 0, pointer.x, canvas.height], // y축에 1자 선을 그립니다.
{
stroke: 'black',
strokeWidth: 2,
viewLengthText: true,
selectable: false,
fontSize: fontSize,
},
)
canvas?.add(line)
canvas?.renderAll()
},
editMode: (options) => {
const pointer = canvas?.getPointer(options.e)
const circle = new fabric.Circle({
radius: 5,
@ -585,7 +597,62 @@ export function useMode() {
}
canvas?.renderAll()
})
},
textboxMode: (options) => {
if (canvas?.getActiveObject()?.type === 'textbox') return
const pointer = canvas?.getPointer(options.e)
const textbox = new fabric.Textbox('텍스트를 입력하세요', {
left: pointer.x,
top: pointer.y,
width: 150, // 텍스트박스의 너비를 설정합니다.
fontSize: fontSize, // 텍스트의 크기를 설정합니다.
})
canvas?.add(textbox)
canvas?.setActiveObject(textbox) // 생성된 텍스트박스를 활성 객체로 설정합니다.
canvas?.renderAll()
// textbox가 active가 풀린 경우 editing mode로 변경
textbox?.on('editing:exited', function () {
changeMode(canvas, Mode.EDIT)
})
},
drawRectMode: () => {
let rect, isDown, origX, origY
canvas.on('mouse:down', function (o) {
isDown = true
const pointer = canvas.getPointer(o.e)
origX = pointer.x
origY = pointer.y
rect = new fabric.Rect({
left: origX,
top: origY,
originX: 'left',
originY: 'top',
width: pointer.x - origX,
height: pointer.y - origY,
angle: 0,
fill: 'transparent',
stroke: 'black',
transparentCorners: false,
})
canvas.add(rect)
})
canvas.on('mouse:move', function (o) {
if (!isDown) return
const pointer = canvas.getPointer(o.e)
if (origX > pointer.x) {
rect.set({ left: Math.abs(pointer.x) })
}
if (origY > pointer.y) {
rect.set({ top: Math.abs(pointer.y) })
}
rect.set({ width: Math.abs(origX - pointer.x) })
rect.set({ height: Math.abs(origY - pointer.y) })
})
},
}
const pushHistoryLine = (line) => {
@ -643,86 +710,6 @@ export function useMode() {
setTemplateType(1)
}
}
const textboxMode = () => {
canvas?.on('mouse:down', function (options) {
if (canvas?.getActiveObject()?.type === 'textbox') return
const pointer = canvas?.getPointer(options.e)
const textbox = new fabric.Textbox('텍스트를 입력하세요', {
left: pointer.x,
top: pointer.y,
width: 150, // 텍스트박스의 너비를 설정합니다.
fontSize: fontSize, // 텍스트의 크기를 설정합니다.
})
canvas?.add(textbox)
canvas?.setActiveObject(textbox) // 생성된 텍스트박스를 활성 객체로 설정합니다.
canvas?.renderAll()
// textbox가 active가 풀린 경우 editing mode로 변경
textbox?.on('editing:exited', function () {
changeMode(canvas, Mode.EDIT)
})
})
}
const drawLineMode = () => {
canvas?.on('mouse:down', function (options) {
const pointer = canvas?.getPointer(options.e)
const line = new QLine(
[pointer.x, 0, pointer.x, canvas.height], // y축에 1자 선을 그립니다.
{
stroke: 'black',
strokeWidth: 2,
viewLengthText: true,
selectable: false,
fontSize: fontSize,
},
)
canvas?.add(line)
canvas?.renderAll()
})
}
const drawRectMode = () => {
let rect, isDown, origX, origY
canvas.on('mouse:down', function (o) {
isDown = true
const pointer = canvas.getPointer(o.e)
origX = pointer.x
origY = pointer.y
rect = new fabric.Rect({
left: origX,
top: origY,
originX: 'left',
originY: 'top',
width: pointer.x - origX,
height: pointer.y - origY,
angle: 0,
fill: 'transparent',
stroke: 'black',
transparentCorners: false,
})
canvas.add(rect)
})
canvas.on('mouse:move', function (o) {
if (!isDown) return
const pointer = canvas.getPointer(o.e)
if (origX > pointer.x) {
rect.set({ left: Math.abs(pointer.x) })
}
if (origY > pointer.y) {
rect.set({ top: Math.abs(pointer.y) })
}
rect.set({ width: Math.abs(origX - pointer.x) })
rect.set({ height: Math.abs(origY - pointer.y) })
})
}
/**
* 점을 연결하는 선과 길이를 그립니다.
* a : 시작점, b : 끝점
@ -818,6 +805,7 @@ export function useMode() {
canvas?.clear()
startPoint.current = null
setEndPoint(null)
pointCount.current = 0
setTemplateType(0)
points.current = []
historyPoints.current = []
@ -826,6 +814,15 @@ export function useMode() {
setSelectedCellRoofArray([]) //셀 그린거 삭제
}
const clearEditMode = () => {
startPoint.current = null
setEndPoint(null)
pointCount.current = 0
points.current = []
historyPoints.current = []
historyLines.current = []
}
const zoomIn = () => {
canvas?.setZoom(canvas.getZoom() + 0.1)
setZoom(Math.round(zoom + 10))

View File

@ -5,6 +5,11 @@ export const textState = atom({
default: 'test text',
})
export const modeState = atom({
key: 'modeState',
default: 'default',
})
export const fontSizeState = atom({
key: 'fontSizeState',
default: 16,