Compare commits
No commits in common. "735fb360be0b1f5a5a90e8d6cf8c0009e21b8442" and "0483f7487a595943f788b3e38e393d0890ec2e6d" have entirely different histories.
735fb360be
...
0483f7487a
@ -1166,7 +1166,7 @@ export default function StuffSearchCondition() {
|
|||||||
<th>{getMessage('stuff.search.schSelSaleStoreId')}</th>
|
<th>{getMessage('stuff.search.schSelSaleStoreId')}</th>
|
||||||
<td colSpan={5}>
|
<td colSpan={5}>
|
||||||
<div className="form-flex-wrap">
|
<div className="form-flex-wrap">
|
||||||
<div className="select-wrap mr5" style={{ flex: 1, display: session?.storeId !== 'T01' && session?.storeLvl !== '1' ? 'none' : undefined }}>
|
<div className="select-wrap mr5" style={{ flex: 1 }}>
|
||||||
{session?.storeId === 'T01' && (
|
{session?.storeId === 'T01' && (
|
||||||
<Select
|
<Select
|
||||||
id="long-value-select1"
|
id="long-value-select1"
|
||||||
@ -1235,7 +1235,7 @@ export default function StuffSearchCondition() {
|
|||||||
isClearable={false}
|
isClearable={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{false && session?.storeId !== 'T01' && session?.storeLvl !== '1' && (
|
{session?.storeId !== 'T01' && session?.storeLvl !== '1' && (
|
||||||
<Select
|
<Select
|
||||||
id="long-value-select1"
|
id="long-value-select1"
|
||||||
instanceId="long-value-select1"
|
instanceId="long-value-select1"
|
||||||
@ -1267,7 +1267,7 @@ export default function StuffSearchCondition() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="select-wrap mr10" style={{ flex: session?.storeId !== 'T01' && session?.storeLvl !== '1' ? '0 0 50%' : 1 }}>
|
<div className="select-wrap mr10" style={{ flex: 1 }}>
|
||||||
<Select
|
<Select
|
||||||
id="long-value-select2"
|
id="long-value-select2"
|
||||||
instanceId="long-value-select2"
|
instanceId="long-value-select2"
|
||||||
|
|||||||
@ -7,10 +7,7 @@ import { atom, useRecoilState, useRecoilValue } from 'recoil'
|
|||||||
|
|
||||||
|
|
||||||
// API 요청을 저장할 모듈 레벨 변수 (Hook 외부)
|
// API 요청을 저장할 모듈 레벨 변수 (Hook 외부)
|
||||||
// 전역 Promise 캐시
|
let roofMaterialPromise = null;
|
||||||
const roofMaterialPromiseCache = new Map()
|
|
||||||
let cacheKey = 'default'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 마스터 컨트롤러 훅
|
* 마스터 컨트롤러 훅
|
||||||
* @returns
|
* @returns
|
||||||
@ -25,117 +22,24 @@ export function useMasterController() {
|
|||||||
* 지붕재 목록 조회
|
* 지붕재 목록 조회
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
||||||
|
|
||||||
const getRoofMaterialList = async () => {
|
const getRoofMaterialList = async () => {
|
||||||
// 1. 이미 진행 중이거나 완료된 Promise가 있으면 그것을 반환
|
// 1. 이미 진행 중이거나 완료된 Promise가 있으면 그것을 반환
|
||||||
if (roofMaterialPromiseCache.has(cacheKey)) {
|
if (roofMaterialPromise) {
|
||||||
console.log('🔄 getRoofMaterialList: Returning cached promise')
|
return roofMaterialPromise;
|
||||||
return roofMaterialPromiseCache.get(cacheKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🚀 getRoofMaterialList: Creating new promise')
|
// 2. 처음 호출될 때 Promise를 생성하여 변수에 할당
|
||||||
|
roofMaterialPromise = get({ url: '/api/v1/master/getRoofMaterialList' })
|
||||||
const MAX_RETRY = 15 // 15번으로 증가 (일본 지역 대응)
|
.then((res) => {
|
||||||
const RETRY_DELAY = 2000 // 2초 간격
|
return res;
|
||||||
const MAX_BACKOFF = 10000 // 최대 10초까지 지수 백오프
|
|
||||||
|
|
||||||
const fetchWithRetry = async () => {
|
|
||||||
let lastError = null
|
|
||||||
let retryCount = 0
|
|
||||||
|
|
||||||
for (let attempt = 0; attempt < MAX_RETRY; attempt++) {
|
|
||||||
try {
|
|
||||||
// 지수 백오프 적용: 2초, 4초, 8초, ... 최대 10초
|
|
||||||
const backoffDelay = Math.min(RETRY_DELAY * Math.pow(2, attempt), MAX_BACKOFF)
|
|
||||||
if (attempt > 0) {
|
|
||||||
console.log(`⏳ Waiting ${backoffDelay/1000}s before retry ${attempt + 1}/${MAX_RETRY}`)
|
|
||||||
await delay(backoffDelay)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`🌐 API Request ${attempt + 1}/${MAX_RETRY}: /api/v1/master/getRoofMaterialList`)
|
|
||||||
const res = await get({ url: '/api/v1/master/getRoofMaterialList' })
|
|
||||||
console.log('✅ getRoofMaterialList: Success on attempt', attempt + 1)
|
|
||||||
|
|
||||||
// 재시도 중이었으면 성공 메시지 표시
|
|
||||||
if (retryCount > 0) {
|
|
||||||
swalFire({
|
|
||||||
icon: 'success',
|
|
||||||
title: getMessage('server.restored.title'),
|
|
||||||
text: `${getMessage('server.restored.text')} (${retryCount}${getMessage('server.retries')})`,
|
|
||||||
timer: 3000, // 메시지 표시 시간 증가
|
|
||||||
showConfirmButton: false
|
|
||||||
})
|
})
|
||||||
}
|
.catch((error) => {
|
||||||
|
// 에러 발생 시 다음 호출을 위해 초기화
|
||||||
|
roofMaterialPromise = null;
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
return res
|
return roofMaterialPromise;
|
||||||
} catch (error) {
|
|
||||||
lastError = error
|
|
||||||
retryCount++
|
|
||||||
|
|
||||||
// 500 계열만 기다렸다가 재시도
|
|
||||||
const status = error?.response?.status
|
|
||||||
if (status && status >= 500) {
|
|
||||||
console.log(`⚠️ getRoofMaterialList: 500 error on attempt ${attempt + 1}, retrying...`)
|
|
||||||
|
|
||||||
// 첫 번째 500 에러에만 메시지 표시
|
|
||||||
if (attempt === 0) {
|
|
||||||
swalFire({
|
|
||||||
icon: 'warning',
|
|
||||||
title: getMessage('server.error.title'),
|
|
||||||
text: getMessage('server.error.text'),
|
|
||||||
timer: 4000, // 경고 메시지 시간 증가
|
|
||||||
showConfirmButton: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 네트워크 에러(ECONNRESET, ETIMEDOUT 등)도 재시도
|
|
||||||
if (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT' || error.code === 'ENOTFOUND') {
|
|
||||||
console.log(`🌐 Network error on attempt ${attempt + 1}:`, error.code)
|
|
||||||
if (attempt === 0) {
|
|
||||||
swalFire({
|
|
||||||
icon: 'warning',
|
|
||||||
title: getMessage('network.error.title'),
|
|
||||||
text: getMessage('network.error.text'),
|
|
||||||
timer: 4000,
|
|
||||||
showConfirmButton: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 500이나 네트워크 에러가 아니면 즉시 실패
|
|
||||||
console.log(`❌ getRoofMaterialList: Non-retryable error on attempt ${attempt + 1}:`, error)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 모든 재시도 실패 시
|
|
||||||
console.log('💥 getRoofMaterialList: All retries failed')
|
|
||||||
swalFire({
|
|
||||||
icon: 'error',
|
|
||||||
title: getMessage('server.failed.title'),
|
|
||||||
text: `${getMessage('server.failed.text')} (${MAX_RETRY}${getMessage('server.retries')})`,
|
|
||||||
confirmButtonText: getMessage('common.ok')
|
|
||||||
})
|
|
||||||
|
|
||||||
throw lastError
|
|
||||||
}
|
|
||||||
|
|
||||||
const promise = fetchWithRetry()
|
|
||||||
.finally(() => {
|
|
||||||
// 성공/실패 모두 끝나면 캐시에서 제거
|
|
||||||
console.log('🏁 getRoofMaterialList: Promise resolved, removing from cache')
|
|
||||||
roofMaterialPromiseCache.delete(cacheKey)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 캐시에 Promise 저장
|
|
||||||
roofMaterialPromiseCache.set(cacheKey, promise)
|
|
||||||
return promise
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -359,10 +263,6 @@ export function useMasterController() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const quotationErrSave = async (params) => {
|
|
||||||
return await post({ url: '/api/v1/master/quotationErrSave', data: params })
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getRoofMaterialList,
|
getRoofMaterialList,
|
||||||
getModuleTypeItemList,
|
getModuleTypeItemList,
|
||||||
@ -378,6 +278,5 @@ export function useMasterController() {
|
|||||||
updateObjectDate,
|
updateObjectDate,
|
||||||
getQuotationItem,
|
getQuotationItem,
|
||||||
getPcsConnOptionItemList,
|
getPcsConnOptionItemList,
|
||||||
quotationErrSave,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useRecoilValue } from 'recoil'
|
import { useRecoilValue } from 'recoil'
|
||||||
import { canvasState, currentAngleTypeSelector, currentCanvasPlanState } from '@/store/canvasAtom'
|
import { canvasState, currentAngleTypeSelector } from '@/store/canvasAtom'
|
||||||
import { POLYGON_TYPE, TRESTLE_MATERIAL } from '@/common/common'
|
import { POLYGON_TYPE, TRESTLE_MATERIAL } from '@/common/common'
|
||||||
import { moduleSelectionDataState } from '@/store/selectedModuleOptions'
|
import { moduleSelectionDataState } from '@/store/selectedModuleOptions'
|
||||||
import { getDegreeByChon } from '@/util/canvas-util'
|
import { getDegreeByChon } from '@/util/canvas-util'
|
||||||
@ -11,7 +11,6 @@ import { useContext } from 'react'
|
|||||||
import { QcastContext } from '@/app/QcastProvider'
|
import { QcastContext } from '@/app/QcastProvider'
|
||||||
import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle'
|
import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle'
|
||||||
import { useMessage } from '@/hooks/useMessage'
|
import { useMessage } from '@/hooks/useMessage'
|
||||||
import { loginUserStore } from '@/store/commonAtom'
|
|
||||||
|
|
||||||
// 모듈간 같은 행, 열의 마진이 10 이하인 경우는 같은 행, 열로 간주
|
// 모듈간 같은 행, 열의 마진이 10 이하인 경우는 같은 행, 열로 간주
|
||||||
const MODULE_MARGIN = 10
|
const MODULE_MARGIN = 10
|
||||||
@ -19,9 +18,7 @@ const MODULE_MARGIN = 10
|
|||||||
export const useTrestle = () => {
|
export const useTrestle = () => {
|
||||||
const canvas = useRecoilValue(canvasState)
|
const canvas = useRecoilValue(canvasState)
|
||||||
const moduleSelectionData = useRecoilValue(moduleSelectionDataState) //다음으로 넘어가는 최종 데이터
|
const moduleSelectionData = useRecoilValue(moduleSelectionDataState) //다음으로 넘어가는 최종 데이터
|
||||||
const { getQuotationItem, quotationErrSave } = useMasterController()
|
const { getQuotationItem } = useMasterController()
|
||||||
const currentCanvasPlan = useRecoilValue(currentCanvasPlanState)
|
|
||||||
const loginUserState = useRecoilValue(loginUserStore)
|
|
||||||
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
|
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
|
||||||
const roofSizeSet = useRecoilValue(basicSettingState).roofSizeSet
|
const roofSizeSet = useRecoilValue(basicSettingState).roofSizeSet
|
||||||
const isTrestleDisplay = useRecoilValue(trestleDisplaySelector)
|
const isTrestleDisplay = useRecoilValue(trestleDisplaySelector)
|
||||||
@ -760,12 +757,6 @@ export const useTrestle = () => {
|
|||||||
text: result.resultMsg || getMessage('common.message.noData'),
|
text: result.resultMsg || getMessage('common.message.noData'),
|
||||||
icon: 'error'
|
icon: 'error'
|
||||||
})
|
})
|
||||||
quotationErrSave({
|
|
||||||
objectNo: currentCanvasPlan.objectNo,
|
|
||||||
planNo: currentCanvasPlan.planNo,
|
|
||||||
rejectRsn: result.resultMsg,
|
|
||||||
regId: loginUserState.userId,
|
|
||||||
})
|
|
||||||
clear()
|
clear()
|
||||||
setViewCircuitNumberTexts(true)
|
setViewCircuitNumberTexts(true)
|
||||||
setIsGlobalLoading(false)
|
setIsGlobalLoading(false)
|
||||||
|
|||||||
@ -153,12 +153,12 @@ export function useCanvasSetting(executeEffect = true) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Roof materials are now initialized in Header.jsx to avoid duplicate calls
|
/** 초 1회만 실행하도록 처리 */
|
||||||
|
addRoofMaterials()
|
||||||
}, [executeEffect])
|
}, [executeEffect])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 지붕재 초기세팅 (fallback - primary initialization is in Header.jsx)
|
* 지붕재 초기세팅
|
||||||
* This function is only called when roof materials are needed but not yet loaded
|
|
||||||
*/
|
*/
|
||||||
const addRoofMaterials = async () => {
|
const addRoofMaterials = async () => {
|
||||||
if (roofMaterials.length !== 0) {
|
if (roofMaterials.length !== 0) {
|
||||||
@ -320,26 +320,12 @@ export function useCanvasSetting(executeEffect = true) {
|
|||||||
// objectNo가 전달되면 사용, 아니면 Recoil state의 correntObjectNo 사용
|
// objectNo가 전달되면 사용, 아니면 Recoil state의 correntObjectNo 사용
|
||||||
const targetObjectNo = objectNo || correntObjectNo
|
const targetObjectNo = objectNo || correntObjectNo
|
||||||
|
|
||||||
// 지붕재 데이터가 없으면 Header.jsx에서 로드될 때까지 기다림
|
// 지붕재 데이터가 없으면 먼저 로드
|
||||||
let materials = roofMaterials
|
let materials = roofMaterials
|
||||||
if (!materials || materials.length === 0) {
|
if (!materials || materials.length === 0) {
|
||||||
logger.log('Waiting for roofMaterials to be loaded from Header.jsx...')
|
logger.log('Waiting for roofMaterials to be loaded...')
|
||||||
// 최대 2초간 기다림 (20번 × 100ms)
|
|
||||||
let waitCount = 0
|
|
||||||
while ((!materials || materials.length === 0) && waitCount < 20) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100))
|
|
||||||
materials = roofMaterials
|
|
||||||
waitCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!materials || materials.length === 0) {
|
|
||||||
logger.log('roofMaterials still not loaded after 2 seconds, proceeding without them')
|
|
||||||
// 비상시에만 addRoofMaterials 호출 (fallback)
|
|
||||||
materials = await addRoofMaterials()
|
materials = await addRoofMaterials()
|
||||||
logger.log('roofMaterials loaded via fallback:', materials)
|
logger.log('roofMaterials loaded:', materials)
|
||||||
} else {
|
|
||||||
logger.log('roofMaterials loaded from Header.jsx:', materials)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -86,11 +86,6 @@ export function useAxios(lang = '') {
|
|||||||
.get(url, option)
|
.get(url, option)
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
// 500 에러는 재시도되므로 로깅을 줄임
|
|
||||||
const status = error?.response?.status
|
|
||||||
if (status === 500) {
|
|
||||||
console.warn('🔄 Server Error (500): Will retry...', url)
|
|
||||||
} else {
|
|
||||||
console.error('🚨 GET Error:', {
|
console.error('🚨 GET Error:', {
|
||||||
url,
|
url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -100,7 +95,6 @@ export function useAxios(lang = '') {
|
|||||||
statusText: error.response?.statusText,
|
statusText: error.response?.statusText,
|
||||||
config: error.config
|
config: error.config
|
||||||
})
|
})
|
||||||
}
|
|
||||||
throw error
|
throw error
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,6 @@ export function useCanvasEvent() {
|
|||||||
const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
|
const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
|
||||||
const canvasSize = useRecoilValue(canvasSizeState)
|
const canvasSize = useRecoilValue(canvasSizeState)
|
||||||
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
|
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
|
||||||
const [shouldCenter, setShouldCenter] = useState(false) // 중앙 정렬 플래그 추가
|
|
||||||
const lengthTextOption = useRecoilValue(fontSelector('lengthText'))
|
const lengthTextOption = useRecoilValue(fontSelector('lengthText'))
|
||||||
const currentMenu = useRecoilValue(currentMenuState)
|
const currentMenu = useRecoilValue(currentMenuState)
|
||||||
const { changeCorridorDimensionText } = useText()
|
const { changeCorridorDimensionText } = useText()
|
||||||
@ -25,17 +24,6 @@ export function useCanvasEvent() {
|
|||||||
canvas?.setZoom(canvasZoom / 100)
|
canvas?.setZoom(canvasZoom / 100)
|
||||||
}, [canvasZoom])
|
}, [canvasZoom])
|
||||||
|
|
||||||
// canvasZoom이 100으로 변경되고 shouldCenter가 true일 때만 중앙 정렬 실행
|
|
||||||
useEffect(() => {
|
|
||||||
if (canvasZoom === 100 && canvas && shouldCenter) {
|
|
||||||
setTimeout(() => {
|
|
||||||
zoomToAllObjects()
|
|
||||||
canvas.renderAll()
|
|
||||||
setShouldCenter(false) // 실행 후 플래그 초기화
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
}, [canvasZoom, shouldCenter])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
attachDefaultEventOnCanvas()
|
attachDefaultEventOnCanvas()
|
||||||
}, [currentMenu])
|
}, [currentMenu])
|
||||||
@ -428,35 +416,15 @@ export function useCanvasEvent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleZoomClear = () => {
|
const handleZoomClear = () => {
|
||||||
setShouldCenter(true) // 중앙 정렬 플래그 설정
|
|
||||||
setCanvasZoom(100)
|
setCanvasZoom(100)
|
||||||
|
|
||||||
|
zoomToAllObjects()
|
||||||
|
canvas.renderAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
const zoomToAllObjects = () => {
|
const zoomToAllObjects = () => {
|
||||||
const allObjects = canvas.getObjects()
|
const objects = canvas.getObjects().filter((obj) => obj.visible)
|
||||||
// 주요 도형 객체만 필터링 (roof, arrow 등)
|
if (objects.length === 0) return
|
||||||
const objects = allObjects.filter((obj) => {
|
|
||||||
// 주요 도형 객체만 포함
|
|
||||||
return obj.name === 'roof' ||
|
|
||||||
obj.name === 'arrow' ||
|
|
||||||
obj.name === 'module' ||
|
|
||||||
obj.name === 'dormer' ||
|
|
||||||
obj.name === 'object'
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('🔍 All objects:', allObjects.length)
|
|
||||||
console.log('🔍 Main objects for centering:', objects.length)
|
|
||||||
console.log('🔍 Main object details:', objects.map(obj => ({
|
|
||||||
name: obj.name,
|
|
||||||
type: obj.type,
|
|
||||||
visible: obj.visible,
|
|
||||||
id: obj.id
|
|
||||||
})))
|
|
||||||
|
|
||||||
if (objects.length === 0) {
|
|
||||||
console.log('❌ No main objects found for centering')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let minX = Infinity,
|
let minX = Infinity,
|
||||||
minY = Infinity
|
minY = Infinity
|
||||||
@ -471,52 +439,11 @@ export function useCanvasEvent() {
|
|||||||
maxY = Math.max(maxY, bounds.top + bounds.height)
|
maxY = Math.max(maxY, bounds.top + bounds.height)
|
||||||
})
|
})
|
||||||
|
|
||||||
const objectsCenterX = (minX + maxX) / 2
|
const centerX = (minX + maxX) / 2
|
||||||
const objectsCenterY = (minY + maxY) / 2
|
const centerY = (minY + maxY) / 2
|
||||||
|
const centerPoint = new fabric.Point(centerX, centerY)
|
||||||
// 화면 중앙 좌표 (메뉴 높이 고려하여 Y 위치 조정)
|
|
||||||
const screenCenterX = canvas.getWidth() / 2
|
|
||||||
const menuHeightOffset = 80 // 위쪽 메뉴 높이만큼 위로 조정
|
|
||||||
const screenCenterY = canvas.getHeight() / 2 - menuHeightOffset
|
|
||||||
|
|
||||||
// 객체 중심을 화면 중앙으로 이동
|
|
||||||
const deltaX = screenCenterX - objectsCenterX
|
|
||||||
const deltaY = screenCenterY - objectsCenterY
|
|
||||||
|
|
||||||
// canvas viewport transform 조정
|
|
||||||
const viewportTransform = canvas.viewportTransform
|
|
||||||
viewportTransform[4] += deltaX
|
|
||||||
viewportTransform[5] += deltaY
|
|
||||||
|
|
||||||
canvas.setViewportTransform(viewportTransform)
|
|
||||||
|
|
||||||
// 브라우저 스크롤을 중앙으로 조정하여 전체 canvas를 모니터 중앙에 위치
|
|
||||||
const canvasElement = canvas.getElement()
|
|
||||||
const canvasRect = canvasElement.getBoundingClientRect()
|
|
||||||
|
|
||||||
// 현재 스크롤 위치
|
|
||||||
const currentScrollTop = window.pageYOffset || document.documentElement.scrollTop
|
|
||||||
const currentScrollLeft = window.pageXOffset || document.documentElement.scrollLeft
|
|
||||||
|
|
||||||
// canvas의 현재 화면 위치
|
|
||||||
const canvasScreenTop = canvasRect.top + currentScrollTop
|
|
||||||
const canvasScreenLeft = canvasRect.left + currentScrollLeft
|
|
||||||
|
|
||||||
// 모니터 중앙
|
|
||||||
const monitorCenterX = window.innerWidth / 2
|
|
||||||
const monitorCenterY = window.innerHeight / 2
|
|
||||||
|
|
||||||
// canvas가 모니터 중앙에 오도록 스크롤 위치 계산
|
|
||||||
const targetScrollLeft = canvasScreenLeft + (canvasRect.width / 2) - monitorCenterX
|
|
||||||
const targetScrollTop = canvasScreenTop + (canvasRect.height / 2) - monitorCenterY + menuHeightOffset
|
|
||||||
|
|
||||||
// 스크롤을 중앙으로 이동
|
|
||||||
window.scrollTo({
|
|
||||||
top: targetScrollTop,
|
|
||||||
left: targetScrollLeft,
|
|
||||||
behavior: 'smooth' // 부드러운 스크롤
|
|
||||||
})
|
|
||||||
|
|
||||||
|
canvas.zoomToPoint(centerPoint, 1)
|
||||||
canvas.renderAll()
|
canvas.renderAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -567,15 +567,6 @@
|
|||||||
"common.finish": "完了",
|
"common.finish": "完了",
|
||||||
"common.ok": "確認",
|
"common.ok": "確認",
|
||||||
"common.cancel": "キャンセル",
|
"common.cancel": "キャンセル",
|
||||||
"server.error.title": "サーバーエラー",
|
|
||||||
"server.error.text": "サーバーで一時的な問題が発生しました。自動的に再試行します...",
|
|
||||||
"server.restored.title": "サーバー接続復旧",
|
|
||||||
"server.restored.text": "サーバー接続が復旧しました",
|
|
||||||
"server.failed.title": "サーバー接続失敗",
|
|
||||||
"server.failed.text": "サーバー接続に失敗しました。しばらくしてから再度お試しください",
|
|
||||||
"server.retries": "回再試行",
|
|
||||||
"network.error.title": "ネットワーク接続不安定",
|
|
||||||
"network.error.text": "インターネット接続が不安定です。自動的に再試行します...",
|
|
||||||
"commons.west": "西",
|
"commons.west": "西",
|
||||||
"commons.east": "東",
|
"commons.east": "東",
|
||||||
"commons.south": "南",
|
"commons.south": "南",
|
||||||
|
|||||||
@ -567,15 +567,6 @@
|
|||||||
"common.finish": "완료",
|
"common.finish": "완료",
|
||||||
"common.ok": "확인",
|
"common.ok": "확인",
|
||||||
"common.cancel": "취소",
|
"common.cancel": "취소",
|
||||||
"server.error.title": "서버 오류",
|
|
||||||
"server.error.text": "서버에 일시적인 문제가 발생했습니다. 자동으로 재시도합니다...",
|
|
||||||
"server.restored.title": "서버 연결 복구",
|
|
||||||
"server.restored.text": "서버 연결이 복구되었습니다",
|
|
||||||
"server.failed.title": "서버 연결 실패",
|
|
||||||
"server.failed.text": "서버 연결에 실패했습니다. 잠시 후 다시 시도해주세요",
|
|
||||||
"server.retries": "회 재시도",
|
|
||||||
"network.error.title": "네트워크 연결 불안",
|
|
||||||
"network.error.text": "인터넷 연결이 불안정합니다. 자동으로 재시도합니다...",
|
|
||||||
"commons.west": "서",
|
"commons.west": "서",
|
||||||
"commons.east": "동",
|
"commons.east": "동",
|
||||||
"commons.south": "남",
|
"commons.south": "남",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user