feat: Add canvas plan load and save
This commit is contained in:
parent
1c2d3b7968
commit
08ef5a63ca
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -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
123
src/hooks/usePlan.js
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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: {},
|
||||||
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user