feat: Add canvas plan load and save

This commit is contained in:
Daseul Kim 2024-09-25 14:10:40 +09:00
parent 1c2d3b7968
commit 08ef5a63ca
6 changed files with 241 additions and 25 deletions

View File

@ -1,12 +1,30 @@
'use client'
import { useEffect, useRef } from 'react'
import { useCanvas } from '@/hooks/useCanvas' import { useCanvas } from '@/hooks/useCanvas'
import { useRef } from 'react'
import { useEvent } from '@/hooks/useEvent' import { useEvent } from '@/hooks/useEvent'
export default function CanvasFrame() { export default function CanvasFrame({ plan }) {
const canvasRef = useRef(null) const canvasRef = useRef(null)
useCanvas('canvas') const { canvas } = useCanvas('canvas')
useEvent() useEvent()
const loadCanvas = () => {
if (canvas) {
canvas?.clear() // .
if (plan?.canvasStatus) {
canvas?.loadFromJSON(JSON.parse(plan.canvasStatus), function () {
canvas?.renderAll() // .
})
}
}
}
useEffect(() => {
loadCanvas()
}, [plan])
return ( return (
<div className="canvas-frame flex justify-center"> <div className="canvas-frame flex justify-center">
<canvas ref={canvasRef} id={'canvas'}></canvas> <canvas ref={canvasRef} id={'canvas'}></canvas>

View File

@ -1,43 +1,96 @@
'use client' 'use client'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import CanvasFrame from './CanvasFrame'
import { useRecoilState, useRecoilValue } from 'recoil' import { useRecoilState, useRecoilValue } from 'recoil'
import { currentMenuState, stepState } from '@/store/canvasAtom' import CanvasFrame from './CanvasFrame'
import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom'
import { currentCanvasPlanState, initCanvasPlansState } from '@/store/canvasAtom'
export default function CanvasLayout() { export default function CanvasLayout() {
const [plans, setPlans] = useState([ const [objectNo, setObjectNo] = useState('test123240822001') //
{ id: 0, name: 'Plan 1', isCurrent: false }, const [addCanvasPlans, setAddCanvasPlans] = useState([])
{ id: 1, name: 'Plan 2', isCurrent: false }, const [idxNum, setIdxNum] = useState(0)
{ id: 2, name: 'Plan 3', isCurrent: false }, const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState)
]) const [initCanvasPlans, setInitCanvasPlans] = useRecoilState(initCanvasPlansState)
const [idxNum, setIdxNum] = useState(null) const globalLocaleState = useRecoilValue(globalLocaleStore)
const { get } = useAxios(globalLocaleState)
const onClickPlane = (num) => { const handleCurrentPlan = (newCurrentId) => {
setIdxNum(num) if (!currentCanvasPlan?.id || currentCanvasPlan.id !== newCurrentId) {
setInitCanvasPlans((plans) =>
plans.map((plan) => {
return { ...plan, isCurrent: plan.id === newCurrentId }
}),
)
setAddCanvasPlans((plans) =>
plans.map((plan) => {
return { ...plan, isCurrent: plan.id === newCurrentId }
}),
)
}
} }
useEffect(() => {
setCurrentCanvasPlan([...initCanvasPlans, ...addCanvasPlans].find((plan) => plan.isCurrent) || null)
}, [initCanvasPlans, addCanvasPlans])
const handleDeletePlan = (e, id) => { const handleDeletePlan = (e, id) => {
e.stopPropagation() // e.stopPropagation() //
setPlans(plans.filter((plan) => plan.id !== id)) //
//
const filterInitPlans = initCanvasPlans.filter((plan) => plan.id !== id)
setInitCanvasPlans(filterInitPlans)
const filterAddPlans = addCanvasPlans.filter((plan) => plan.id !== id)
setAddCanvasPlans(filterAddPlans)
const combinedPlans = [...filterInitPlans, ...filterAddPlans]
if (combinedPlans.length === 0) {
//
setIdxNum(0)
} else {
const lastPlanId = combinedPlans.at(-1).id
if (id !== lastPlanId) {
handleCurrentPlan(lastPlanId)
}
}
} }
const addNewPlan = () => { const addNewPlan = () => {
setPlans([...plans, { id: plans.length, name: `Plan ${plans.length + 1}` }]) setAddCanvasPlans([...addCanvasPlans, { id: idxNum, name: `Plan ${idxNum + 1}`, objectNo: `${objectNo}` }])
handleCurrentPlan(idxNum)
setIdxNum(idxNum + 1)
} }
useEffect(() => { useEffect(() => {
if (plans.length === 1) { get({ url: `/api/canvas-management/canvas-statuses/by-object/${objectNo}` }).then((res) => {
setPlans([{ id: 0, name: 'Plan 1', isCurrent: false }]) // console.log('canvas ', res)
} const arrangeData = res.map((item) => {
const test = item.canvasStatus.replace(/##/g, '"').replace(/\\/g, '')
const test2 = test.substring(1, test.length - 1)
return {
id: item.id,
name: item.objectNo + '-' + item.id, // tab button
userId: item.userId,
canvasStatus: JSON.stringify(test2),
isCurrent: false,
}
})
if (arrangeData.length > 0) {
setInitCanvasPlans(arrangeData)
handleCurrentPlan(arrangeData.at(-1).id) // last
setIdxNum(arrangeData.length)
} else {
addNewPlan()
}
})
}, []) }, [])
return ( return (
<div className="canvas-layout"> <div className="canvas-layout">
<div className="canvas-page-list"> <div className="canvas-page-list">
<div className="canvas-plane-wrap"> <div className="canvas-plane-wrap">
{plans.map((plan, idx) => ( {[...initCanvasPlans, ...addCanvasPlans].map((plan) => (
<button key={plan.id} className={`canvas-page-box ${idx === idxNum ? 'on' : ''}`} onClick={() => onClickPlane(idx)}> <button key={plan.id} className={`canvas-page-box ${plan.isCurrent === true ? 'on' : ''}`} onClick={() => handleCurrentPlan(plan.id)}>
<span>{plan.name}</span> <span>{plan.name}</span>
<i className="close" onClick={(e) => handleDeletePlan(e, plan.id)}></i> <i className="close" onClick={(e) => handleDeletePlan(e, plan.id)}></i>
</button> </button>
@ -47,7 +100,7 @@ export default function CanvasLayout() {
<span></span> <span></span>
</button> </button>
</div> </div>
<CanvasFrame /> <CanvasFrame plan={[...initCanvasPlans, ...addCanvasPlans].find((plan) => plan.isCurrent === true)} />
</div> </div>
) )
} }

View File

@ -8,7 +8,9 @@ import MenuDepth01 from './MenuDepth01'
import QSelectBox from '@/components/common/select/QSelectBox' import QSelectBox from '@/components/common/select/QSelectBox'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { usePlan } from '@/hooks/usePlan'
import { canvasState, canvasZoomState, currentMenuState, verticalHorizontalModeState } from '@/store/canvasAtom' import { canvasState, canvasZoomState, currentMenuState, verticalHorizontalModeState } from '@/store/canvasAtom'
import { sessionStore } from '@/store/commonAtom'
import { outerLinePointsState } from '@/store/outerLineAtom' import { outerLinePointsState } from '@/store/outerLineAtom'
import { appMessageStore, globalLocaleStore } from '@/store/localeAtom' import { appMessageStore, globalLocaleStore } from '@/store/localeAtom'
import { MENU } from '@/common/common' import { MENU } from '@/common/common'
@ -37,12 +39,14 @@ export default function CanvasMenu(props) {
const [appMessageState, setAppMessageState] = useRecoilState(appMessageStore) const [appMessageState, setAppMessageState] = useRecoilState(appMessageStore)
const setCurrentMenu = useSetRecoilState(currentMenuState) const setCurrentMenu = useSetRecoilState(currentMenuState)
const setPoints = useSetRecoilState(outerLinePointsState) const setPoints = useSetRecoilState(outerLinePointsState)
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState) const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
const [sessionState, setSessionState] = useRecoilState(sessionStore)
const globalLocale = useRecoilValue(globalLocaleStore) const globalLocale = useRecoilValue(globalLocaleStore)
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { saveCanvas } = usePlan()
const SelectOption = [{ name: '瓦53A' }, { name: '瓦53A' }] const SelectOption = [{ name: '瓦53A' }, { name: '瓦53A' }]
const onClickNav = (number) => { const onClickNav = (number) => {
@ -63,7 +67,9 @@ export default function CanvasMenu(props) {
}, [menuNumber, type]) }, [menuNumber, type])
// (btn08) // (btn08)
const handleSaveSettings = async () => {} const handleSaveCanvas = () => {
saveCanvas(sessionState.userId)
}
const handleClear = () => { const handleClear = () => {
setPoints([]) setPoints([])
@ -201,7 +207,7 @@ export default function CanvasMenu(props) {
</div> </div>
<div className="btn-from"> <div className="btn-from">
<button className="btn07" onClick={handleClear}></button> <button className="btn07" onClick={handleClear}></button>
<button className="btn08" onClick={handleSaveSettings}></button> <button className="btn08" onClick={handleSaveCanvas}></button>
<button className="btn09"></button> <button className="btn09"></button>
</div> </div>
</> </>

View File

@ -61,6 +61,10 @@ export function useAxios(lang = '') {
.catch(console.error) .catch(console.error)
} }
const promisePut = async ({ url, data }) => {
return await getInstances(url).put(url, data)
}
const patch = async ({ url, data }) => { const patch = async ({ url, data }) => {
return await getInstances(url) return await getInstances(url)
.patch(url, data) .patch(url, data)
@ -75,5 +79,5 @@ export function useAxios(lang = '') {
.catch(console.error) .catch(console.error)
} }
return { get, promiseGet, post, promisePost, put, patch, del } return { get, promiseGet, post, promisePost, put, promisePut, patch, del }
} }

123
src/hooks/usePlan.js Normal file
View File

@ -0,0 +1,123 @@
import { useState } from 'react'
import { useRecoilState } from 'recoil'
import { canvasState, currentCanvasPlanState, initCanvasPlansState } from '@/store/canvasAtom'
import { useAxios } from '@/hooks/useAxios'
import { useMessage } from '@/hooks/useMessage'
import { toastUp } from '@/hooks/useToast'
export function usePlan() {
const [canvas, setCanvas] = useRecoilState(canvasState)
const [currentCanvasPlan, setcurrentCanvasPlan] = useRecoilState(currentCanvasPlanState)
const [initCanvasPlans, setInitCanvasPlans] = useRecoilState(initCanvasPlansState)
const { getMessage } = useMessage()
const { post, promisePost, put, promisePut } = useAxios()
/**
* 마우스 포인터의 가이드라인을 제거합니다.
*/
const removeMouseLines = () => {
if (canvas?._objects.length > 0) {
const mouseLines = canvas?._objects.filter((obj) => obj.name === 'mouseLine')
mouseLines.forEach((item) => canvas?.remove(item))
}
canvas?.renderAll()
}
const addCanvas = () => {
const objs = canvas?.toJSON([
'selectable',
'name',
'parentId',
'id',
'length',
'idx',
'direction',
'lines',
'points',
'lockMovementX',
'lockMovementY',
'lockRotation',
'lockScalingX',
'lockScalingY',
'opacity',
'cells',
'maxX',
'maxY',
'minX',
'minY',
'x',
'y',
'stickeyPoint',
])
const str = JSON.stringify(objs)
// canvas?.clear()
return str
// setTimeout(() => {
// // 역직렬화하여 캔버스에 객체를 다시 추가합니다.
// canvas?.loadFromJSON(JSON.parse(str), function () {
// // 모든 객체가 로드되고 캔버스에 추가된 후 호출됩니다.
// console.log(canvas?.getObjects().filter((obj) => obj.name === 'roof'))
// canvas?.renderAll() // 캔버스를 다시 그립니다.
// })
// }, 1000)
}
/**
* 페이지 캔버스를 저장하는 함수
*
* 1. 신규 저장 : POST
* param(body) : userId, objectNo, canvasStatus
* 2. 수정 저장 : PUT
* param(body) : id, canvasStatus
*/
const saveCanvas = async (userId) => {
removeMouseLines()
const canvasStatus = addCanvas()
if (initCanvasPlans.some((plan) => plan.id === currentCanvasPlan.id)) {
// canvas 수정
const planData = {
id: currentCanvasPlan.id,
canvasStatus: JSON.stringify(canvasStatus).replace(/"/g, '##'),
}
await promisePut({ url: '/api/canvas-management/canvas-statuses', data: planData })
.then((res) => {
toastUp({ message: getMessage('res.message'), type: 'success' }) // 성공 시 메세지 없음
console.log('[PUT] canvas-statuses res :::::::: %o', res)
})
.catch((error) => {
toastUp({ message: getMessage(error.message), type: 'error' })
console.error('[PUT] canvas-statuses error :::::::: %o', error)
})
} else {
// canvas 신규 등록
const planData = {
userId: 'NEW016610', // userId,
imageName: 'image_name', // api 필수항목이여서 임시로 넣음, 이후 삭제 필요
objectNo: currentCanvasPlan.objectNo,
canvasStatus: JSON.stringify(canvasStatus).replace(/"/g, '##'),
}
await promisePost({ url: '/api/canvas-management/canvas-statuses', data: planData })
.then((res) => {
toastUp({ message: getMessage('res.message'), type: 'success' }) // 성공 시 메세지 없음
console.log('[POST] canvas-statuses response :::::::: %o', res)
})
.catch((error) => {
toastUp({ message: getMessage(error.message), type: 'error' })
console.error('[POST] canvas-statuses res error :::::::: %o', error)
})
}
}
return {
canvas,
removeMouseLines,
saveCanvas,
addCanvas,
}
}

View File

@ -218,3 +218,15 @@ export const adsorptionRangeState = atom({
key: 'adsorptionRangeState', key: 'adsorptionRangeState',
default: 50, default: 50,
}) })
// canvas plan 초기 목록
export const initCanvasPlansState = atom({
key: 'initCanvasPlans',
default: [],
})
// 현재 canvas plan
export const currentCanvasPlanState = atom({
key: 'currentCanvasPlan',
default: {},
})