Merge branch 'dev' into feature/design-remake
This commit is contained in:
commit
340c7669af
@ -216,6 +216,8 @@ export const SAVE_KEY = [
|
|||||||
'isMultipleOf45',
|
'isMultipleOf45',
|
||||||
'from',
|
'from',
|
||||||
'originColor',
|
'originColor',
|
||||||
|
'originWidth',
|
||||||
|
'originHeight',
|
||||||
]
|
]
|
||||||
|
|
||||||
export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype]
|
export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype]
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import React, { useState, useRef, useEffect, forwardRef } from 'react'
|
|||||||
import { createCalculator } from '@/util/calc-utils'
|
import { createCalculator } from '@/util/calc-utils'
|
||||||
import '@/styles/calc.scss'
|
import '@/styles/calc.scss'
|
||||||
|
|
||||||
export const CalculatorInput = forwardRef(({ value, onChange, label, options = {}, id, className = 'calculator-input', readOnly = false, placeholder}, ref) => {
|
export const CalculatorInput = forwardRef(
|
||||||
|
({ value, onChange, label, options = {}, id, className = 'calculator-input', readOnly = false, placeholder }, ref) => {
|
||||||
const [showKeypad, setShowKeypad] = useState(false)
|
const [showKeypad, setShowKeypad] = useState(false)
|
||||||
const [displayValue, setDisplayValue] = useState(value || '0')
|
const [displayValue, setDisplayValue] = useState(value || '0')
|
||||||
const [hasOperation, setHasOperation] = useState(false)
|
const [hasOperation, setHasOperation] = useState(false)
|
||||||
@ -77,12 +78,14 @@ export const CalculatorInput = forwardRef(({ value, onChange, label, options = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 포커스와 커서 위치 설정 (새로운 값의 길이로 설정)
|
// 커서를 텍스트 끝으로 이동하고 스크롤 처리
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
inputRef.current.focus()
|
inputRef.current.focus()
|
||||||
const len = newDisplayValue.length
|
const len = newDisplayValue.length
|
||||||
inputRef.current.setSelectionRange(len, len)
|
inputRef.current.setSelectionRange(len, len)
|
||||||
|
// 텍스트 끝으로 스크롤
|
||||||
|
inputRef.current.scrollLeft = inputRef.current.scrollWidth
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -118,12 +121,14 @@ export const CalculatorInput = forwardRef(({ value, onChange, label, options = {
|
|||||||
setDisplayValue(newDisplayValue)
|
setDisplayValue(newDisplayValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 포커스와 커서 위치 설정 (새로운 값의 길이로 설정)
|
// 커서를 텍스트 끝으로 이동하고 스크롤 처리
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
inputRef.current.focus()
|
inputRef.current.focus()
|
||||||
const len = newDisplayValue.length
|
const len = newDisplayValue.length
|
||||||
inputRef.current.setSelectionRange(len, len)
|
inputRef.current.setSelectionRange(len, len)
|
||||||
|
// 텍스트 끝으로 스크롤
|
||||||
|
inputRef.current.scrollLeft = inputRef.current.scrollWidth
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -136,14 +141,14 @@ export const CalculatorInput = forwardRef(({ value, onChange, label, options = {
|
|||||||
setDisplayValue(displayValue)
|
setDisplayValue(displayValue)
|
||||||
setHasOperation(false)
|
setHasOperation(false)
|
||||||
onChange(displayValue)
|
onChange(displayValue)
|
||||||
// 포커스와 커서 위치 설정
|
// 커서를 텍스트 끝으로 이동하고 스크롤 처리
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
inputRef.current.focus()
|
inputRef.current.focus()
|
||||||
const len = displayValue.length
|
const len = displayValue.length
|
||||||
inputRef.current.setSelectionRange(len, len)
|
inputRef.current.setSelectionRange(len, len)
|
||||||
// Ensure focus is maintained
|
// 텍스트 끝으로 스크롤
|
||||||
inputRef.current.focus()
|
inputRef.current.scrollLeft = inputRef.current.scrollWidth
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -182,14 +187,14 @@ export const CalculatorInput = forwardRef(({ value, onChange, label, options = {
|
|||||||
setDisplayValue(displayValue)
|
setDisplayValue(displayValue)
|
||||||
setHasOperation(!!calculator.operation)
|
setHasOperation(!!calculator.operation)
|
||||||
onChange(displayValue)
|
onChange(displayValue)
|
||||||
// 포커스와 커서 위치 설정
|
// 커서를 텍스트 끝으로 이동하고 스크롤 처리
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
inputRef.current.focus()
|
inputRef.current.focus()
|
||||||
const len = displayValue.length
|
const len = displayValue.length
|
||||||
inputRef.current.setSelectionRange(len, len)
|
inputRef.current.setSelectionRange(len, len)
|
||||||
// Ensure focus is maintained
|
// 텍스트 끝으로 스크롤
|
||||||
inputRef.current.focus()
|
inputRef.current.scrollLeft = inputRef.current.scrollWidth
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -429,6 +434,7 @@ export const CalculatorInput = forwardRef(({ value, onChange, label, options = {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
CalculatorInput.displayName = 'CalculatorInput'
|
CalculatorInput.displayName = 'CalculatorInput'
|
||||||
|
|||||||
@ -87,6 +87,10 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
this.initLines()
|
this.initLines()
|
||||||
this.init()
|
this.init()
|
||||||
this.setShape()
|
this.setShape()
|
||||||
|
const originWidth = this.originWidth ?? this.width
|
||||||
|
const originHeight = this.originHeight ?? this.height
|
||||||
|
this.originWidth = this.angle === 90 || this.angle === 270 ? originHeight : originWidth
|
||||||
|
this.originHeight = this.angle === 90 || this.angle === 270 ? originWidth : originHeight
|
||||||
},
|
},
|
||||||
|
|
||||||
setShape() {
|
setShape() {
|
||||||
@ -126,11 +130,13 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
this.on('moving', () => {
|
this.on('moving', () => {
|
||||||
this.initLines()
|
this.initLines()
|
||||||
this.addLengthText()
|
this.addLengthText()
|
||||||
|
this.setCoords()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.on('modified', (e) => {
|
this.on('modified', (e) => {
|
||||||
this.initLines()
|
this.initLines()
|
||||||
this.addLengthText()
|
this.addLengthText()
|
||||||
|
this.setCoords()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.on('selected', () => {
|
this.on('selected', () => {
|
||||||
@ -733,7 +739,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
inPolygonImproved(point) {
|
inPolygonImproved(point) {
|
||||||
const vertices = this.points
|
const vertices = this.getCurrentPoints()
|
||||||
let inside = false
|
let inside = false
|
||||||
const testX = Number(point.x.toFixed(this.toFixed))
|
const testX = Number(point.x.toFixed(this.toFixed))
|
||||||
const testY = Number(point.y.toFixed(this.toFixed))
|
const testY = Number(point.y.toFixed(this.toFixed))
|
||||||
@ -745,7 +751,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
const yj = Number(vertices[j].y.toFixed(this.toFixed))
|
const yj = Number(vertices[j].y.toFixed(this.toFixed))
|
||||||
|
|
||||||
// 점이 정점 위에 있는지 확인
|
// 점이 정점 위에 있는지 확인
|
||||||
if (Math.abs(xi - testX) < 0.01 && Math.abs(yi - testY) < 0.01) {
|
if (Math.abs(xi - testX) <= 0.01 && Math.abs(yi - testY) <= 0.01) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -196,6 +196,14 @@ export default function CanvasMenu(props) {
|
|||||||
text: getMessage('module.delete.confirm'),
|
text: getMessage('module.delete.confirm'),
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
confirmFn: () => {
|
confirmFn: () => {
|
||||||
|
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
|
||||||
|
roofs.forEach((roof) => {
|
||||||
|
roof.set({
|
||||||
|
stroke: 'black',
|
||||||
|
strokeWidth: 3,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
//해당 메뉴 이동시 배치면 삭제
|
//해당 메뉴 이동시 배치면 삭제
|
||||||
|
|
||||||
setAllModuleSurfaceIsComplete(false)
|
setAllModuleSurfaceIsComplete(false)
|
||||||
|
|||||||
@ -7,16 +7,21 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
|||||||
import QSelectBox from '@/components/common/select/QSelectBox'
|
import QSelectBox from '@/components/common/select/QSelectBox'
|
||||||
import { roofsState } from '@/store/roofAtom'
|
import { roofsState } from '@/store/roofAtom'
|
||||||
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
|
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
|
||||||
|
import { useCommonCode } from '@/hooks/common/useCommonCode'
|
||||||
import Swal from 'sweetalert2'
|
import Swal from 'sweetalert2'
|
||||||
import { normalizeDecimal} from '@/util/input-utils'
|
import { normalizeDecimal} from '@/util/input-utils'
|
||||||
|
|
||||||
export const Orientation = forwardRef((props, ref) => {
|
export const Orientation = forwardRef((props, ref) => {
|
||||||
const { getMessage } = useMessage()
|
const { getMessage } = useMessage()
|
||||||
|
const { findCommonCode } = useCommonCode()
|
||||||
const [hasAnglePassivity, setHasAnglePassivity] = useState(false)
|
const [hasAnglePassivity, setHasAnglePassivity] = useState(false)
|
||||||
const basicSetting = useRecoilValue(basicSettingState)
|
const basicSetting = useRecoilValue(basicSettingState)
|
||||||
const [addedRoofs, setAddedRoofs] = useRecoilState(addedRoofsState) //지붕재 선택
|
const [addedRoofs, setAddedRoofs] = useRecoilState(addedRoofsState) //지붕재 선택
|
||||||
const [roofsStore, setRoofsStore] = useRecoilState(roofsState)
|
const [roofsStore, setRoofsStore] = useRecoilState(roofsState)
|
||||||
const [roofTab, setRoofTab] = useState(0) //지붕재 탭
|
const [roofTab, setRoofTab] = useState(0) //지붕재 탭
|
||||||
|
const [selectedModuleSeries, setSelectedModuleSeries] = useState(null)
|
||||||
|
const [moduleSeriesList, setModuleSeriesList] = useState([])
|
||||||
|
const [filteredModuleList, setFilteredModuleList] = useState([])
|
||||||
const {
|
const {
|
||||||
roofs,
|
roofs,
|
||||||
setRoofs,
|
setRoofs,
|
||||||
@ -66,8 +71,13 @@ export const Orientation = forwardRef((props, ref) => {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allOption = {
|
||||||
|
moduleSerCd: 'ALL',
|
||||||
|
moduleSerNm: getMessage("board.sub.total") || 'ALL'
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (basicSetting.roofSizeSet == '3') {
|
if (basicSetting.roofSizeSet === '3') {
|
||||||
restoreModuleInstArea()
|
restoreModuleInstArea()
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
@ -80,9 +90,22 @@ export const Orientation = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedModules) {
|
if (selectedModules) {
|
||||||
setSelectedModules(moduleList.find((module) => module.itemId === selectedModules.itemId))
|
const foundModule = moduleList.find((module) => module.itemId === selectedModules.itemId)
|
||||||
|
if (foundModule) {
|
||||||
|
setSelectedModules(foundModule)
|
||||||
|
|
||||||
|
// 선택된 모듈의 시리즈로 업데이트 (시리즈 목록이 있는 경우에만)
|
||||||
|
if (moduleSeriesList.length > 0 && foundModule.moduleSerCd) {
|
||||||
|
const currentSeries = moduleSeriesList.find(series => series.moduleSerCd === foundModule.moduleSerCd)
|
||||||
|
if (currentSeries && (!selectedModuleSeries || selectedModuleSeries.moduleSerCd !== currentSeries.moduleSerCd)) {
|
||||||
|
setSelectedModuleSeries(currentSeries)
|
||||||
}
|
}
|
||||||
}, [selectedModules])
|
}else{
|
||||||
|
setSelectedModuleSeries(allOption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [selectedModules, moduleList, moduleSeriesList])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedSurfaceType) {
|
if (selectedSurfaceType) {
|
||||||
@ -164,7 +187,7 @@ export const Orientation = forwardRef((props, ref) => {
|
|||||||
title: getMessage('module.not.found'),
|
title: getMessage('module.not.found'),
|
||||||
icon: 'warning',
|
icon: 'warning',
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,6 +229,41 @@ export const Orientation = forwardRef((props, ref) => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleChangeModuleSeries = (e) => {
|
||||||
|
resetRoofs()
|
||||||
|
setSelectedModuleSeries(e)
|
||||||
|
|
||||||
|
// 선택된 시리즈에 맞는 모듈 목록 필터링 및 첫 번째 모듈 선택
|
||||||
|
if (e && moduleList.length > 0) {
|
||||||
|
let filtered
|
||||||
|
|
||||||
|
if (e.moduleSerCd === 'ALL') {
|
||||||
|
// "전체" 선택 시 모든 모듈 표시
|
||||||
|
filtered = moduleList
|
||||||
|
} else {
|
||||||
|
// 특정 시리즈 선택 시 해당 시리즈 모듈만 표시
|
||||||
|
//filtered = moduleList.filter(module => module.moduleSerCd === e.moduleSerCd)
|
||||||
|
filtered = moduleList.filter(module => module && module.moduleSerCd && module.moduleSerCd === e.moduleSerCd)
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilteredModuleList(filtered)
|
||||||
|
|
||||||
|
// 필터링된 목록의 첫 번째 모듈을 자동 선택
|
||||||
|
if (filtered.length > 0) {
|
||||||
|
const firstModule = filtered[0]
|
||||||
|
setSelectedModules(firstModule)
|
||||||
|
// 상위 컴포넌트의 handleChangeModule 호출
|
||||||
|
if (handleChangeModule) {
|
||||||
|
handleChangeModule(firstModule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 모듈 리스트가 비어있는 경우
|
||||||
|
setFilteredModuleList([])
|
||||||
|
setSelectedModules(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleChangeModule = (e) => {
|
const handleChangeModule = (e) => {
|
||||||
resetRoofs()
|
resetRoofs()
|
||||||
setSelectedModules(e)
|
setSelectedModules(e)
|
||||||
@ -264,12 +322,71 @@ export const Orientation = forwardRef((props, ref) => {
|
|||||||
setRoofsStore(newRoofs)
|
setRoofsStore(newRoofs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 모듈시리즈 목록 생성 및 commonCode와 매핑
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 컴포넌트가 마운트될 때 selectedModules가 없으면 handleChangeModule 호출
|
if (moduleList.length > 0 && moduleSeriesList.length === 0) {
|
||||||
if (moduleList.length > 0 && (!selectedModules || !selectedModules.itemId)) {
|
const moduleSeriesCodes = findCommonCode(207100) || []
|
||||||
handleChangeModule(moduleList[0]);
|
|
||||||
|
// moduleList에서 고유한 moduleSerCd 추출
|
||||||
|
const uniqueSeriesCd = [...new Set(moduleList.map(module => module.moduleSerCd).filter(Boolean))]
|
||||||
|
|
||||||
|
if (uniqueSeriesCd.length > 0) {
|
||||||
|
// moduleSerCd와 commonCode를 매핑하여 기본 moduleSeriesList 생성
|
||||||
|
const mappedSeries = uniqueSeriesCd.map(serCd => {
|
||||||
|
const matchedCode = moduleSeriesCodes.find(code => code.clCode === serCd)
|
||||||
|
return {
|
||||||
|
moduleSerCd: serCd,
|
||||||
|
moduleSerNm: matchedCode ? matchedCode.clCodeNm : serCd
|
||||||
}
|
}
|
||||||
}, [moduleList]); // 빈 의존성 배열로 마운트 시 한 번만 실행
|
})
|
||||||
|
|
||||||
|
// "전체" 옵션을 맨 앞에 추가
|
||||||
|
const seriesList = [allOption, ...mappedSeries]
|
||||||
|
setModuleSeriesList(seriesList)
|
||||||
|
|
||||||
|
// 현재 선택된 모듈이 있으면 해당 모듈의 시리즈를 찾아서 선택
|
||||||
|
if (selectedModules && selectedModules.moduleSerCd) {
|
||||||
|
const currentSeries = seriesList.find(series => series.moduleSerCd === selectedModules.moduleSerCd)
|
||||||
|
if (currentSeries) {
|
||||||
|
setSelectedModuleSeries(currentSeries)
|
||||||
|
} else {
|
||||||
|
setSelectedModuleSeries(allOption)
|
||||||
|
// "ALL"이 선택되면 자동으로 모듈 필터링 및 선택 실행
|
||||||
|
setTimeout(() => handleChangeModuleSeries(allOption), 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 선택된 모듈이 없으면 "전체"를 기본 선택
|
||||||
|
setSelectedModuleSeries(allOption)
|
||||||
|
// "ALL"이 선택되면 자동으로 모듈 필터링 및 선택 실행
|
||||||
|
setTimeout(() => handleChangeModuleSeries(allOption), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [moduleList, selectedModules])
|
||||||
|
|
||||||
|
// 초기 로딩 시에만 필터링된 모듈 목록 설정
|
||||||
|
useEffect(() => {
|
||||||
|
if (moduleList.length > 0 && filteredModuleList.length === 0 && selectedModuleSeries) {
|
||||||
|
let filtered
|
||||||
|
|
||||||
|
if (selectedModuleSeries.moduleSerCd === 'ALL') {
|
||||||
|
// "전체" 선택 시 모든 모듈 표시
|
||||||
|
filtered = moduleList
|
||||||
|
} else {
|
||||||
|
// 특정 시리즈 선택 시 해당 시리즈 모듈만 표시
|
||||||
|
filtered = moduleList.filter(module => module.moduleSerCd === selectedModuleSeries.moduleSerCd)
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilteredModuleList(filtered)
|
||||||
|
|
||||||
|
if (filtered.length > 0 && !selectedModules) {
|
||||||
|
setSelectedModules(filtered[0])
|
||||||
|
}
|
||||||
|
} else if (moduleList.length === 0 && filteredModuleList.length === 0 && selectedModuleSeries) {
|
||||||
|
// 모듈 리스트가 비어있는 경우 빈 배열로 설정
|
||||||
|
setFilteredModuleList([])
|
||||||
|
}
|
||||||
|
}, [moduleList, selectedModuleSeries]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="properties-setting-wrap">
|
<div className="properties-setting-wrap">
|
||||||
@ -336,16 +453,32 @@ export const Orientation = forwardRef((props, ref) => {
|
|||||||
<div className="compas-table-wrap">
|
<div className="compas-table-wrap">
|
||||||
<div className="compas-table-box mb10">
|
<div className="compas-table-box mb10">
|
||||||
<div className="outline-form mb10">
|
<div className="outline-form mb10">
|
||||||
<span>{getMessage('modal.module.basic.setting.module.setting')}</span>
|
<span>{getMessage('modal.module.basic.setting.module.series.setting')}</span>
|
||||||
|
<div className="grid-select">
|
||||||
<div className="grid-select">
|
<div className="grid-select">
|
||||||
{moduleList && (
|
|
||||||
<QSelectBox
|
<QSelectBox
|
||||||
options={moduleList}
|
options={moduleSeriesList.length > 0 ? moduleSeriesList : [allOption]}
|
||||||
|
value={selectedModuleSeries}
|
||||||
|
targetKey={'moduleSerCd'}
|
||||||
|
sourceKey={'moduleSerCd'}
|
||||||
|
showKey={'moduleSerNm'}
|
||||||
|
onChange={(e) => handleChangeModuleSeries(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="outline-form mb10">
|
||||||
|
<span>{getMessage('modal.module.basic.setting.module.setting2')}</span>
|
||||||
|
<div className="grid-select">
|
||||||
|
{filteredModuleList && (
|
||||||
|
<QSelectBox
|
||||||
|
options={filteredModuleList}
|
||||||
value={selectedModules}
|
value={selectedModules}
|
||||||
targetKey={'itemId'}
|
targetKey={'itemId'}
|
||||||
sourceKey={'itemId'}
|
sourceKey={'itemId'}
|
||||||
showKey={'itemNm'}
|
showKey={'itemNm'}
|
||||||
onChange={(e) => handleChangeModule(e)}
|
onChange={(e) => handleChangeModule(e)}
|
||||||
|
showFirstOptionWhenEmpty = {true}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -396,7 +529,7 @@ export const Orientation = forwardRef((props, ref) => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{basicSetting && basicSetting.roofSizeSet == '3' && (
|
{basicSetting && basicSetting.roofSizeSet === '3' && (
|
||||||
<div className="outline-form mt15">
|
<div className="outline-form mt15">
|
||||||
<span>{getMessage('modal.module.basic.setting.module.placement.area')}</span>
|
<span>{getMessage('modal.module.basic.setting.module.placement.area')}</span>
|
||||||
<div className="input-grid mr10" style={{ width: '60px' }}>
|
<div className="input-grid mr10" style={{ width: '60px' }}>
|
||||||
@ -407,7 +540,7 @@ export const Orientation = forwardRef((props, ref) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{basicSetting && basicSetting.roofSizeSet != '3' && (
|
{basicSetting && basicSetting.roofSizeSet !== '3' && (
|
||||||
<div className="compas-table-box">
|
<div className="compas-table-box">
|
||||||
<div className="compas-grid-table">
|
<div className="compas-grid-table">
|
||||||
<div className="outline-form">
|
<div className="outline-form">
|
||||||
|
|||||||
@ -18,6 +18,12 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
|
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
|
||||||
const pitchText = useRecoilValue(pitchTextSelector)
|
const pitchText = useRecoilValue(pitchTextSelector)
|
||||||
const [selectedRoof, setSelectedRoof] = useState(null)
|
const [selectedRoof, setSelectedRoof] = useState(null)
|
||||||
|
const [isAutoSelecting, setIsAutoSelecting] = useState(false) // 자동 선택 중인지 상태
|
||||||
|
const [autoSelectTimeout, setAutoSelectTimeout] = useState(null) // 타임아웃 참조
|
||||||
|
const autoSelectTimeoutRef = useRef(null)
|
||||||
|
|
||||||
|
// 공통 타임아웃 설정 (밀리초)
|
||||||
|
const AUTO_SELECT_TIMEOUT = 500 // API 호출 완료 대기 시간
|
||||||
const {
|
const {
|
||||||
trestleState,
|
trestleState,
|
||||||
trestleDetail,
|
trestleDetail,
|
||||||
@ -60,23 +66,27 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
const [flag, setFlag] = useState(false)
|
const [flag, setFlag] = useState(false)
|
||||||
const tempModuleSelectionData = useRef(null)
|
const tempModuleSelectionData = useRef(null)
|
||||||
const [autoSelectStep, setAutoSelectStep] = useState(null) // 'raftBase', 'trestle', 'constMthd', 'roofBase', 'construction'
|
const [autoSelectStep, setAutoSelectStep] = useState(null) // 'raftBase', 'trestle', 'constMthd', 'roofBase', 'construction'
|
||||||
|
const prevHajebichiRef = useRef();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (roofs && !selectedRoof) {
|
if (roofs && roofs.length > 0 && !selectedRoof) {
|
||||||
console.log("roofs:::::", roofs.length)
|
console.log("roofs:::::", roofs.length)
|
||||||
setLengthBase(roofs[0].length);
|
setLengthBase(roofs[0].length);
|
||||||
setSelectedRoof(roofs[0])
|
setSelectedRoof(roofs[0])
|
||||||
}
|
}
|
||||||
if (selectedRoof && selectedRoof.lenAuth === "C") {
|
if (selectedRoof && selectedRoof.lenAuth === "C") {
|
||||||
onChangeLength(selectedRoof.length);
|
onChangeLength(selectedRoof.length);
|
||||||
}
|
}else if (selectedRoof && ["C", "R"].includes(selectedRoof.raftAuth) && roofs && roofs.length > 0) {
|
||||||
if (selectedRoof && ["C", "R"].includes(selectedRoof.raftAuth)) {
|
|
||||||
onChangeRaftBase(roofs[0]);
|
onChangeRaftBase(roofs[0]);
|
||||||
|
}else if (selectedRoof && ["C", "R"].includes(selectedRoof.roofPchAuth) && roofs && roofs.length > 0 &&
|
||||||
|
roofs[0].hajebichi !== prevHajebichiRef.current ) {
|
||||||
|
prevHajebichiRef.current = roofs[0].hajebichi;
|
||||||
|
onChangeHajebichi(roofs[0].hajebichi);
|
||||||
}
|
}
|
||||||
|
|
||||||
//모듈 설치 영역 복구
|
//모듈 설치 영역 복구
|
||||||
restoreModuleInstArea()
|
restoreModuleInstArea()
|
||||||
}, [roofs])
|
}, [roofs, selectedRoof]) // selectedRoof 추가
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (flag && moduleSelectionData) {
|
if (flag && moduleSelectionData) {
|
||||||
@ -161,7 +171,7 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (constructionList.length > 0) {
|
if (constructionList.length > 0) {
|
||||||
const existingConstruction = constructionList.find((construction) => construction.constTp === trestleState?.construction?.constTp)
|
const existingConstruction = constructionList.find((construction) => construction.constTp === trestleState.constTp)
|
||||||
if (existingConstruction) {
|
if (existingConstruction) {
|
||||||
setSelectedConstruction(existingConstruction)
|
setSelectedConstruction(existingConstruction)
|
||||||
} else if (autoSelectStep === 'construction') {
|
} else if (autoSelectStep === 'construction') {
|
||||||
@ -252,7 +262,7 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
// 다음 단계(가대메이커) 자동 선택 설정 - 지연 실행
|
// 다음 단계(가대메이커) 자동 선택 설정 - 지연 실행
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setAutoSelectStep('trestle')
|
setAutoSelectStep('trestle')
|
||||||
}, 500) // API 호출 완료를 위한 더 긴 지연
|
}, AUTO_SELECT_TIMEOUT) // API 호출 완료를 위한 더 긴 지연
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChangeHajebichi = (e) => {
|
const onChangeHajebichi = (e) => {
|
||||||
@ -274,7 +284,7 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
roof: {
|
roof: {
|
||||||
moduleTpCd: selectedModules.itemTp ?? '',
|
moduleTpCd: selectedModules.itemTp ?? '',
|
||||||
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
||||||
raft: selectedRaftBase?.clCode,
|
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
|
||||||
hajebichi: e,
|
hajebichi: e,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -282,7 +292,7 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
// 다음 단계(가대메이커) 자동 선택 설정 - 지연 실행
|
// 다음 단계(가대메이커) 자동 선택 설정 - 지연 실행
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setAutoSelectStep('trestle')
|
setAutoSelectStep('trestle')
|
||||||
}, 500)
|
}, AUTO_SELECT_TIMEOUT)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChangeTrestleMaker = (e) => {
|
const onChangeTrestleMaker = (e) => {
|
||||||
@ -297,7 +307,8 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
roof: {
|
roof: {
|
||||||
moduleTpCd: selectedModules.itemTp ?? '',
|
moduleTpCd: selectedModules.itemTp ?? '',
|
||||||
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
||||||
raft: selectedRaftBase?.clCode,
|
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
|
||||||
|
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi,
|
||||||
trestleMkrCd: e.trestleMkrCd,
|
trestleMkrCd: e.trestleMkrCd,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -305,7 +316,7 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
// API 호출 완료 후 다음 단계(공법) 자동 선택 설정
|
// API 호출 완료 후 다음 단계(공법) 자동 선택 설정
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setAutoSelectStep('constMthd')
|
setAutoSelectStep('constMthd')
|
||||||
}, 300)
|
}, AUTO_SELECT_TIMEOUT)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChangeConstMthd = (e) => {
|
const onChangeConstMthd = (e) => {
|
||||||
@ -319,16 +330,28 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
roof: {
|
roof: {
|
||||||
moduleTpCd: selectedModules.itemTp ?? '',
|
moduleTpCd: selectedModules.itemTp ?? '',
|
||||||
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
||||||
raft: selectedRaftBase?.clCode,
|
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
|
||||||
|
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi,
|
||||||
trestleMkrCd: selectedTrestle?.trestleMkrCd,
|
trestleMkrCd: selectedTrestle?.trestleMkrCd,
|
||||||
constMthdCd: e.constMthdCd,
|
constMthdCd: e.constMthdCd,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 기존 타임아웃 취소
|
||||||
|
if (autoSelectTimeoutRef.current) {
|
||||||
|
clearTimeout(autoSelectTimeoutRef.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 자동 선택 중 상태 활성화
|
||||||
|
setIsAutoSelecting(true)
|
||||||
|
|
||||||
// API 호출 완료 후 다음 단계(지붕밑바탕) 자동 선택 설정
|
// API 호출 완료 후 다음 단계(지붕밑바탕) 자동 선택 설정
|
||||||
setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
setAutoSelectStep('roofBase')
|
setAutoSelectStep('roofBase')
|
||||||
}, 300)
|
setIsAutoSelecting(false)
|
||||||
|
}, AUTO_SELECT_TIMEOUT)
|
||||||
|
|
||||||
|
autoSelectTimeoutRef.current = timeoutId
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChangeRoofBase = (e) => {
|
const onChangeRoofBase = (e) => {
|
||||||
@ -340,7 +363,8 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
roof: {
|
roof: {
|
||||||
moduleTpCd: selectedModules.itemTp ?? '',
|
moduleTpCd: selectedModules.itemTp ?? '',
|
||||||
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
||||||
raft: selectedRaftBase?.clCode,
|
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
|
||||||
|
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi,
|
||||||
trestleMkrCd: selectedTrestle?.trestleMkrCd,
|
trestleMkrCd: selectedTrestle?.trestleMkrCd,
|
||||||
constMthdCd: selectedConstMthd?.constMthdCd,
|
constMthdCd: selectedConstMthd?.constMthdCd,
|
||||||
roofBaseCd: e.roofBaseCd,
|
roofBaseCd: e.roofBaseCd,
|
||||||
@ -356,7 +380,7 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
// API 호출 완료 후 다음 단계(construction) 자동 선택 설정
|
// API 호출 완료 후 다음 단계(construction) 자동 선택 설정
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setAutoSelectStep('construction')
|
setAutoSelectStep('construction')
|
||||||
}, 300)
|
}, AUTO_SELECT_TIMEOUT)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleConstruction = (index) => {
|
const handleConstruction = (index) => {
|
||||||
@ -366,7 +390,8 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
roof: {
|
roof: {
|
||||||
moduleTpCd: selectedModules.itemTp ?? '',
|
moduleTpCd: selectedModules.itemTp ?? '',
|
||||||
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
||||||
raft: selectedRaftBase?.clCode,
|
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
|
||||||
|
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi,
|
||||||
trestleMkrCd: selectedTrestle.trestleMkrCd,
|
trestleMkrCd: selectedTrestle.trestleMkrCd,
|
||||||
constMthdCd: selectedConstMthd.constMthdCd,
|
constMthdCd: selectedConstMthd.constMthdCd,
|
||||||
roofBaseCd: selectedRoofBase.roofBaseCd,
|
roofBaseCd: selectedRoofBase.roofBaseCd,
|
||||||
@ -403,7 +428,7 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
ridgeMargin,
|
ridgeMargin,
|
||||||
kerabaMargin,
|
kerabaMargin,
|
||||||
roofIndex: selectedRoof.index,
|
roofIndex: selectedRoof.index,
|
||||||
raft: selectedRaftBase?.clCode,
|
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
|
||||||
trestle: {
|
trestle: {
|
||||||
hajebichi: hajebichi,
|
hajebichi: hajebichi,
|
||||||
length: lengthBase,
|
length: lengthBase,
|
||||||
@ -440,8 +465,8 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
ridgeMargin,
|
ridgeMargin,
|
||||||
kerabaMargin,
|
kerabaMargin,
|
||||||
roofIndex: roof.index,
|
roofIndex: roof.index,
|
||||||
raft: selectedRaftBase?.clCode,
|
raft: selectedRaftBase?.clCode ?? selectedRoof?.raft ?? '',
|
||||||
hajebichi: hajebichi,
|
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi ?? 0,
|
||||||
trestle: {
|
trestle: {
|
||||||
length: lengthBase,
|
length: lengthBase,
|
||||||
hajebichi: hajebichi,
|
hajebichi: hajebichi,
|
||||||
@ -451,7 +476,8 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
...selectedRoofBase,
|
...selectedRoofBase,
|
||||||
},
|
},
|
||||||
construction: {
|
construction: {
|
||||||
...constructionList.find((data) => data.constTp === trestleState.constTp),
|
//...constructionList.find((data) => newAddedRoofs[index].construction.constTp === data.constTp),
|
||||||
|
...constructionList.find((data) => trestleState.constTp === data.constTp),
|
||||||
cvrYn,
|
cvrYn,
|
||||||
snowGdPossYn,
|
snowGdPossYn,
|
||||||
cvrChecked,
|
cvrChecked,
|
||||||
@ -525,7 +551,7 @@ const Trestle = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (['C', 'R'].includes(roof.roofPchAuth)) {
|
if (['C', 'R'].includes(roof.roofPchAuth)) {
|
||||||
if (!roof?.roofPchBase) {
|
if (!roof?.hajebichi) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: getMessage('modal.module.basic.settting.module.error7', [roof.nameJp]), // 하제비치를 입력해주세요.
|
title: getMessage('modal.module.basic.settting.module.error7', [roof.nameJp]), // 하제비치를 입력해주세요.
|
||||||
icon: 'warning',
|
icon: 'warning',
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
|
|||||||
|
|
||||||
export default function SizeSetting(props) {
|
export default function SizeSetting(props) {
|
||||||
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
|
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
|
||||||
const [settingTarget, setSettingTarget] = useState(1)
|
const [settingTarget, setSettingTarget] = useState(props.side || 1)
|
||||||
const { id, pos = contextPopupPosition, target } = props
|
const { id, pos = contextPopupPosition, target } = props
|
||||||
const { getMessage } = useMessage()
|
const { getMessage } = useMessage()
|
||||||
const { closePopup } = usePopup()
|
const { closePopup } = usePopup()
|
||||||
@ -47,11 +47,11 @@ export default function SizeSetting(props) {
|
|||||||
<div className="size-option-top">
|
<div className="size-option-top">
|
||||||
<div className="size-option-wrap">
|
<div className="size-option-wrap">
|
||||||
<div className="size-option mb5">
|
<div className="size-option mb5">
|
||||||
<input type="text" className="input-origin mr5" value={target?.width.toFixed(0) * 10} readOnly={true} />
|
<input type="text" className="input-origin mr5" value={(target?.originWidth * 10).toFixed(0)} readOnly={true} />
|
||||||
<span className="normal-font">mm</span>
|
<span className="normal-font">mm</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="size-option">
|
<div className="size-option">
|
||||||
<input type="text" className="input-origin mr5" defaultValue={target?.width.toFixed(0) * 10} ref={widthRef} />
|
<input type="text" className="input-origin mr5" defaultValue={(target?.originWidth * 10).toFixed(0)} ref={widthRef} />
|
||||||
<span className="normal-font">mm</span>
|
<span className="normal-font">mm</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -60,11 +60,11 @@ export default function SizeSetting(props) {
|
|||||||
<div className="size-option-side">
|
<div className="size-option-side">
|
||||||
<div className="size-option-wrap">
|
<div className="size-option-wrap">
|
||||||
<div className="size-option mb5">
|
<div className="size-option mb5">
|
||||||
<input type="text" className="input-origin mr5" value={target?.height.toFixed(0) * 10} readOnly={true} />
|
<input type="text" className="input-origin mr5" value={(target?.originHeight * 10).toFixed(0)} readOnly={true} />
|
||||||
<span className="normal-font">mm</span>
|
<span className="normal-font">mm</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="size-option">
|
<div className="size-option">
|
||||||
<input type="text" className="input-origin mr5" defaultValue={target?.height.toFixed(0) * 10} ref={heightRef} />
|
<input type="text" className="input-origin mr5" defaultValue={(target?.originHeight * 10).toFixed(0)} ref={heightRef} />
|
||||||
<span className="normal-font">mm</span>
|
<span className="normal-font">mm</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -86,7 +86,7 @@ export default function ContextRoofAllocationSetting(props) {
|
|||||||
return (
|
return (
|
||||||
<div className="grid-option-box" key={index}>
|
<div className="grid-option-box" key={index}>
|
||||||
<div className="d-check-radio pop no-text">
|
<div className="d-check-radio pop no-text">
|
||||||
<input type="radio" name="radio01" checked={roof.selected && 'checked'} readOnly={true} />
|
<input type="radio" name="radio01" checked={!!roof.selected} readOnly={true} />
|
||||||
<label
|
<label
|
||||||
htmlFor="ra01"
|
htmlFor="ra01"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@ -189,7 +189,7 @@ export default function ContextRoofAllocationSetting(props) {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="input-origin block"
|
className="input-origin block"
|
||||||
value={roof.hajebichi === '' ? '0' : roof.hajebichi}
|
value={roof.hajebichi ?? ''}
|
||||||
readOnly={roof.roofPchAuth === 'R'}
|
readOnly={roof.roofPchAuth === 'R'}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
e.target.value = normalizeDigits(e.target.value)
|
e.target.value = normalizeDigits(e.target.value)
|
||||||
@ -211,8 +211,7 @@ export default function ContextRoofAllocationSetting(props) {
|
|||||||
e.target.value = normalizeDecimalLimit(e.target.value, 2)
|
e.target.value = normalizeDecimalLimit(e.target.value, 2)
|
||||||
handleChangePitch(e, index)
|
handleChangePitch(e, index)
|
||||||
}}
|
}}
|
||||||
value={currentAngleType === 'slope' ? (roof.pitch || '0') : (roof.angle || '0')}
|
value={currentAngleType === 'slope' ? (roof.pitch ?? '') : (roof.angle ?? '')}
|
||||||
defaultValue={currentAngleType === 'slope' ? (roof.pitch || '0') : (roof.angle || '0')}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="absol">{pitchText}</span>
|
<span className="absol">{pitchText}</span>
|
||||||
|
|||||||
@ -86,7 +86,7 @@ export default function RoofAllocationSetting(props) {
|
|||||||
return (
|
return (
|
||||||
<div className="grid-option-box" key={index}>
|
<div className="grid-option-box" key={index}>
|
||||||
<div className="d-check-radio pop no-text">
|
<div className="d-check-radio pop no-text">
|
||||||
<input type="radio" name="radio01" checked={roof.selected} readOnly />
|
<input type="radio" name="radio01" checked={!!roof.selected} readOnly />
|
||||||
<label
|
<label
|
||||||
htmlFor="ra01"
|
htmlFor="ra01"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@ -194,7 +194,7 @@ export default function RoofAllocationSetting(props) {
|
|||||||
e.target.value = normalizeDigits(e.target.value)
|
e.target.value = normalizeDigits(e.target.value)
|
||||||
handleChangeInput(e, 'hajebichi', index)
|
handleChangeInput(e, 'hajebichi', index)
|
||||||
}}
|
}}
|
||||||
value={parseInt(roof.hajebichi)}
|
value={roof.hajebichi ?? ''}
|
||||||
readOnly={roof.roofPchAuth === 'R'}
|
readOnly={roof.roofPchAuth === 'R'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -212,7 +212,7 @@ export default function RoofAllocationSetting(props) {
|
|||||||
e.target.value = normalizeDecimalLimit(e.target.value, 2)
|
e.target.value = normalizeDecimalLimit(e.target.value, 2)
|
||||||
handleChangePitch(e, index)
|
handleChangePitch(e, index)
|
||||||
}}
|
}}
|
||||||
value={currentAngleType === 'slope' ? roof.pitch : roof.angle}
|
value={currentAngleType === 'slope' ? (roof.pitch ?? '') : (roof.angle ?? '')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="absol">{pitchText}</span>
|
<span className="absol">{pitchText}</span>
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
} from '@/store/canvasAtom'
|
} from '@/store/canvasAtom'
|
||||||
|
|
||||||
import { calculateVisibleModuleHeight, getDegreeByChon, polygonToTurfPolygon, rectToPolygon, toFixedWithoutRounding } from '@/util/canvas-util'
|
import { calculateVisibleModuleHeight, getDegreeByChon, polygonToTurfPolygon, rectToPolygon, toFixedWithoutRounding } from '@/util/canvas-util'
|
||||||
|
import '@/util/fabric-extensions' // fabric 객체들에 getCurrentPoints 메서드 추가
|
||||||
import { basicSettingState, roofDisplaySelector } from '@/store/settingAtom'
|
import { basicSettingState, roofDisplaySelector } from '@/store/settingAtom'
|
||||||
import offsetPolygon, { calculateAngle, createLinesFromPolygon } from '@/util/qpolygon-utils'
|
import offsetPolygon, { calculateAngle, createLinesFromPolygon } from '@/util/qpolygon-utils'
|
||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
@ -265,14 +266,14 @@ export function useModuleBasicSetting(tabNum) {
|
|||||||
batchObjects.forEach((obj) => {
|
batchObjects.forEach((obj) => {
|
||||||
//도머일때
|
//도머일때
|
||||||
if (obj.name === BATCH_TYPE.TRIANGLE_DORMER || obj.name === BATCH_TYPE.PENTAGON_DORMER) {
|
if (obj.name === BATCH_TYPE.TRIANGLE_DORMER || obj.name === BATCH_TYPE.PENTAGON_DORMER) {
|
||||||
const groupPoints = obj.groupPoints
|
const groupPoints = obj.getCurrentPoints()
|
||||||
const offsetObjects = offsetPolygon(groupPoints, 10)
|
const offsetObjects = offsetPolygon(groupPoints, 10)
|
||||||
const dormerOffset = new QPolygon(offsetObjects, batchObjectOptions)
|
const dormerOffset = new QPolygon(offsetObjects, batchObjectOptions)
|
||||||
dormerOffset.setViewLengthText(false)
|
dormerOffset.setViewLengthText(false)
|
||||||
canvas.add(dormerOffset) //모듈설치면 만들기
|
canvas.add(dormerOffset) //모듈설치면 만들기
|
||||||
} else {
|
} else {
|
||||||
//개구, 그림자일때
|
//개구, 그림자일때
|
||||||
const points = obj.points
|
const points = obj.getCurrentPoints()
|
||||||
const offsetObjects = offsetPolygon(points, 10)
|
const offsetObjects = offsetPolygon(points, 10)
|
||||||
const offset = new QPolygon(offsetObjects, batchObjectOptions)
|
const offset = new QPolygon(offsetObjects, batchObjectOptions)
|
||||||
offset.setViewLengthText(false)
|
offset.setViewLengthText(false)
|
||||||
@ -319,7 +320,7 @@ export function useModuleBasicSetting(tabNum) {
|
|||||||
const margin = moduleSelectionData.common.margin ? moduleSelectionData.common.margin : 200
|
const margin = moduleSelectionData.common.margin ? moduleSelectionData.common.margin : 200
|
||||||
|
|
||||||
//육지붕일때는 그냥 하드코딩
|
//육지붕일때는 그냥 하드코딩
|
||||||
offsetPoints = offsetPolygon(roof.points, -Number(margin) / 10) //육지붕일때
|
offsetPoints = offsetPolygon(roof.getCurrentPoints(), -Number(margin) / 10) //육지붕일때
|
||||||
} else {
|
} else {
|
||||||
//육지붕이 아닐때
|
//육지붕이 아닐때
|
||||||
if (allPointsOutside) {
|
if (allPointsOutside) {
|
||||||
|
|||||||
@ -675,6 +675,8 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
objectGroup.recalculateGroupPoints()
|
||||||
|
|
||||||
isDown = false
|
isDown = false
|
||||||
initEvent()
|
initEvent()
|
||||||
// dbClickEvent()
|
// dbClickEvent()
|
||||||
@ -1426,7 +1428,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
|||||||
|
|
||||||
//그림자는 아무데나 설치 할 수 있게 해달라고 함
|
//그림자는 아무데나 설치 할 수 있게 해달라고 함
|
||||||
if (obj.name === BATCH_TYPE.OPENING) {
|
if (obj.name === BATCH_TYPE.OPENING) {
|
||||||
const turfObject = pointsToTurfPolygon(obj.points)
|
const turfObject = pointsToTurfPolygon(obj.getCurrentPoints())
|
||||||
|
|
||||||
if (turf.booleanWithin(turfObject, turfSurface)) {
|
if (turf.booleanWithin(turfObject, turfSurface)) {
|
||||||
obj.set({
|
obj.set({
|
||||||
@ -1459,7 +1461,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
|||||||
const calcLeft = obj.left - originLeft
|
const calcLeft = obj.left - originLeft
|
||||||
const calcTop = obj.top - originTop
|
const calcTop = obj.top - originTop
|
||||||
|
|
||||||
const currentDormerPoints = obj.groupPoints.map((item) => {
|
const currentDormerPoints = obj.getCurrentPoints().map((item) => {
|
||||||
return {
|
return {
|
||||||
x: item.x + calcLeft,
|
x: item.x + calcLeft,
|
||||||
y: item.y + calcTop,
|
y: item.y + calcTop,
|
||||||
|
|||||||
@ -174,22 +174,32 @@ export function useRoofAllocationSetting(id) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const firstRes = Array.isArray(res) && res.length > 0 ? res[0] : null
|
||||||
|
|
||||||
setBasicSetting({
|
setBasicSetting({
|
||||||
...basicSetting,
|
...basicSetting,
|
||||||
planNo: res[0].planNo,
|
planNo: firstRes?.planNo ?? planNo,
|
||||||
roofSizeSet: res[0].roofSizeSet,
|
roofSizeSet: firstRes?.roofSizeSet ?? 0,
|
||||||
roofAngleSet: res[0].roofAngleSet,
|
roofAngleSet: firstRes?.roofAngleSet ?? 0,
|
||||||
roofsData: roofsArray,
|
roofsData: roofsArray,
|
||||||
selectedRoofMaterial: selectRoofs.find((roof) => roof.selected),
|
selectedRoofMaterial: selectRoofs.find((roof) => roof.selected),
|
||||||
})
|
})
|
||||||
|
|
||||||
setBasicInfo({
|
setBasicInfo({
|
||||||
planNo: '' + res[0].planNo,
|
planNo: '' + (firstRes?.planNo ?? planNo),
|
||||||
roofSizeSet: '' + res[0].roofSizeSet,
|
roofSizeSet: '' + (firstRes?.roofSizeSet ?? 0),
|
||||||
roofAngleSet: '' + res[0].roofAngleSet,
|
roofAngleSet: '' + (firstRes?.roofAngleSet ?? 0),
|
||||||
})
|
})
|
||||||
//데이터 동기화
|
// 데이터 동기화: 렌더링용 필드 기본값 보정
|
||||||
setCurrentRoofList(selectRoofs)
|
const normalizedRoofs = selectRoofs.map((roof) => ({
|
||||||
|
...roof,
|
||||||
|
width: roof.width ?? '',
|
||||||
|
length: roof.length ?? '',
|
||||||
|
hajebichi: roof.hajebichi ?? '',
|
||||||
|
pitch: roof.pitch ?? '',
|
||||||
|
angle: roof.angle ?? '',
|
||||||
|
}))
|
||||||
|
setCurrentRoofList(normalizedRoofs)
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Data fetching error:', error)
|
console.error('Data fetching error:', error)
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useRecoilValue, useResetRecoilState } from 'recoil'
|
import { useRecoilValue, useResetRecoilState } from 'recoil'
|
||||||
import { canvasSettingState, canvasState, currentCanvasPlanState, globalPitchState } from '@/store/canvasAtom'
|
import { canvasSettingState, canvasState, currentCanvasPlanState, currentObjectState, globalPitchState } from '@/store/canvasAtom'
|
||||||
import { LINE_TYPE, MENU, POLYGON_TYPE } from '@/common/common'
|
import { LINE_TYPE, MENU, POLYGON_TYPE } from '@/common/common'
|
||||||
import { getIntersectionPoint, toFixedWithoutRounding } from '@/util/canvas-util'
|
import { getIntersectionPoint } from '@/util/canvas-util'
|
||||||
import { degreesToRadians } from '@turf/turf'
|
import { degreesToRadians } from '@turf/turf'
|
||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
import { useSwal } from '@/hooks/useSwal'
|
import { useSwal } from '@/hooks/useSwal'
|
||||||
@ -21,10 +21,13 @@ import { placementShapeDrawingPointsState } from '@/store/placementShapeDrawingA
|
|||||||
import { getBackGroundImage } from '@/lib/imageActions'
|
import { getBackGroundImage } from '@/lib/imageActions'
|
||||||
import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
|
import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
|
||||||
import { useText } from '@/hooks/useText'
|
import { useText } from '@/hooks/useText'
|
||||||
|
import SizeSetting from '@/components/floor-plan/modal/object/SizeSetting'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
||||||
const { getMessage } = useMessage()
|
const { getMessage } = useMessage()
|
||||||
const { drawDirectionArrow, addPolygon, addLengthText } = usePolygon()
|
const { drawDirectionArrow, addPolygon, addLengthText, setPolygonLinesActualSize } = usePolygon()
|
||||||
const lengthTextFont = useRecoilValue(fontSelector('lengthText'))
|
const lengthTextFont = useRecoilValue(fontSelector('lengthText'))
|
||||||
const resetOuterLinePoints = useResetRecoilState(outerLinePointsState)
|
const resetOuterLinePoints = useResetRecoilState(outerLinePointsState)
|
||||||
const resetPlacementShapeDrawingPoints = useResetRecoilState(placementShapeDrawingPointsState)
|
const resetPlacementShapeDrawingPoints = useResetRecoilState(placementShapeDrawingPointsState)
|
||||||
@ -36,11 +39,13 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
|||||||
const { swalFire } = useSwal()
|
const { swalFire } = useSwal()
|
||||||
const { addCanvasMouseEventListener, initEvent } = useEvent()
|
const { addCanvasMouseEventListener, initEvent } = useEvent()
|
||||||
// const { addCanvasMouseEventListener, initEvent } = useContext(EventContext)
|
// const { addCanvasMouseEventListener, initEvent } = useContext(EventContext)
|
||||||
const { addPopup, closePopup } = usePopup()
|
const { addPopup, closePopup, closeAll } = usePopup()
|
||||||
const { setSurfaceShapePattern } = useRoofFn()
|
const { setSurfaceShapePattern } = useRoofFn()
|
||||||
const { changeCorridorDimensionText } = useText()
|
const { changeCorridorDimensionText } = useText()
|
||||||
const currentCanvasPlan = useRecoilValue(currentCanvasPlanState)
|
const currentCanvasPlan = useRecoilValue(currentCanvasPlanState)
|
||||||
const { fetchSettings } = useCanvasSetting(false)
|
const { fetchSettings } = useCanvasSetting(false)
|
||||||
|
const currentObject = useRecoilValue(currentObjectState)
|
||||||
|
const [popupId, setPopupId] = useState(uuidv4())
|
||||||
|
|
||||||
const applySurfaceShape = (surfaceRefs, selectedType, id) => {
|
const applySurfaceShape = (surfaceRefs, selectedType, id) => {
|
||||||
let length1, length2, length3, length4, length5
|
let length1, length2, length3, length4, length5
|
||||||
@ -879,6 +884,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
|||||||
drawDirectionArrow(roof)
|
drawDirectionArrow(roof)
|
||||||
changeCorridorDimensionText()
|
changeCorridorDimensionText()
|
||||||
addLengthText(roof)
|
addLengthText(roof)
|
||||||
|
roof.setCoords()
|
||||||
initEvent()
|
initEvent()
|
||||||
canvas.renderAll()
|
canvas.renderAll()
|
||||||
})
|
})
|
||||||
@ -916,71 +922,138 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resizeSurfaceShapeBatch = (side, target, width, height) => {
|
const resizeSurfaceShapeBatch = (side, target, width, height) => {
|
||||||
const originTarget = { ...target }
|
if (!target || target.type !== 'QPolygon') return
|
||||||
|
|
||||||
const objectWidth = target.width
|
width = width / 10
|
||||||
const objectHeight = target.height
|
height = height / 10
|
||||||
const changeWidth = width / 10 / objectWidth
|
|
||||||
const changeHeight = height / 10 / objectHeight
|
|
||||||
let sideX = 'left'
|
|
||||||
let sideY = 'top'
|
|
||||||
|
|
||||||
//그룹 중심점 변경
|
// 현재 QPolygon의 점들 가져오기 (변형 적용된 실제 좌표)
|
||||||
if (side === 2) {
|
const currentPoints = target.getCurrentPoints() || []
|
||||||
sideX = 'right'
|
const angle = target.angle % 360
|
||||||
sideY = 'top'
|
if (currentPoints.length === 0) return
|
||||||
} else if (side === 3) {
|
|
||||||
sideX = 'left'
|
// 현재 바운딩 박스 계산
|
||||||
sideY = 'bottom'
|
let minX = Math.min(...currentPoints.map((p) => p.x))
|
||||||
} else if (side === 4) {
|
let maxX = Math.max(...currentPoints.map((p) => p.x))
|
||||||
sideX = 'right'
|
let minY = Math.min(...currentPoints.map((p) => p.y))
|
||||||
sideY = 'bottom'
|
let maxY = Math.max(...currentPoints.map((p) => p.y))
|
||||||
|
|
||||||
|
let currentWidth = maxX - minX
|
||||||
|
let currentHeight = maxY - minY
|
||||||
|
|
||||||
|
// 회전에 관계없이 단순한 앵커 포인트 계산
|
||||||
|
let anchorX, anchorY
|
||||||
|
switch (side) {
|
||||||
|
case 1: // left-top
|
||||||
|
anchorX = minX
|
||||||
|
anchorY = minY
|
||||||
|
break
|
||||||
|
case 2: // right-top
|
||||||
|
anchorX = maxX
|
||||||
|
anchorY = minY
|
||||||
|
break
|
||||||
|
case 3: // left-bottom
|
||||||
|
anchorX = minX
|
||||||
|
anchorY = maxY
|
||||||
|
break
|
||||||
|
case 4: // right-bottom
|
||||||
|
anchorX = maxX
|
||||||
|
anchorY = maxY
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//변경 전 좌표
|
// 목표 크기 (회전에 관계없이 동일하게 적용)
|
||||||
const newCoords = target.getPointByOrigin(sideX, sideY)
|
let targetWidth = width
|
||||||
|
let targetHeight = height
|
||||||
|
|
||||||
target.set({
|
// 새로운 점들 계산 - 앵커 포인트는 고정, 나머지는 비례적으로 확장
|
||||||
originX: sideX,
|
// 각도와 side에 따라 확장 방향 결정
|
||||||
originY: sideY,
|
const newPoints = currentPoints.map((point) => {
|
||||||
left: newCoords.x,
|
// 앵커 포인트 기준으로 새로운 위치 계산
|
||||||
top: newCoords.y,
|
// side와 각도에 관계없이 일관된 방식으로 처리
|
||||||
|
|
||||||
|
// 앵커 포인트에서 각 점까지의 절대 거리
|
||||||
|
const deltaX = point.x - anchorX
|
||||||
|
const deltaY = point.y - anchorY
|
||||||
|
|
||||||
|
// 새로운 크기에 맞춰 비례적으로 확장
|
||||||
|
const newDeltaX = (deltaX / currentWidth) * targetWidth
|
||||||
|
const newDeltaY = (deltaY / currentHeight) * targetHeight
|
||||||
|
|
||||||
|
const newX = anchorX + newDeltaX
|
||||||
|
const newY = anchorY + newDeltaY
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: newX, // 소수점 1자리로 반올림
|
||||||
|
y: newY,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
target.scaleX = changeWidth
|
// 기존 객체의 속성들을 복사 (scale은 1로 고정)
|
||||||
target.scaleY = changeHeight
|
const originalOptions = {
|
||||||
|
stroke: target.stroke,
|
||||||
const currentPoints = target.getCurrentPoints()
|
strokeWidth: target.strokeWidth,
|
||||||
|
fill: target.fill,
|
||||||
target.set({
|
opacity: target.opacity,
|
||||||
|
visible: target.visible,
|
||||||
|
selectable: target.selectable,
|
||||||
|
evented: target.evented,
|
||||||
|
hoverCursor: target.hoverCursor,
|
||||||
|
moveCursor: target.moveCursor,
|
||||||
|
lockMovementX: target.lockMovementX,
|
||||||
|
lockMovementY: target.lockMovementY,
|
||||||
|
lockRotation: target.lockRotation,
|
||||||
|
lockScalingX: target.lockScalingX,
|
||||||
|
lockScalingY: target.lockScalingY,
|
||||||
|
lockUniScaling: target.lockUniScaling,
|
||||||
|
name: target.name,
|
||||||
|
uuid: target.uuid,
|
||||||
|
roofType: target.roofType,
|
||||||
|
roofMaterial: target.roofMaterial,
|
||||||
|
azimuth: target.azimuth,
|
||||||
|
// tilt: target.tilt,
|
||||||
|
// angle: target.angle,
|
||||||
|
// scale은 항상 1로 고정
|
||||||
scaleX: 1,
|
scaleX: 1,
|
||||||
scaleY: 1,
|
scaleY: 1,
|
||||||
width: toFixedWithoutRounding(width / 10, 1),
|
lines: target.lines,
|
||||||
height: toFixedWithoutRounding(height / 10, 1),
|
// 기타 모든 사용자 정의 속성들
|
||||||
})
|
...Object.fromEntries(
|
||||||
//크기 변경후 좌표를 재 적용
|
Object.entries(target).filter(
|
||||||
const changedCoords = target.getPointByOrigin(originTarget.originX, originTarget.originY)
|
([key, value]) =>
|
||||||
|
!['type', 'left', 'top', 'width', 'height', 'scaleX', 'scaleY', 'points', 'lines', 'texts', 'canvas', 'angle', 'tilt'].includes(key) &&
|
||||||
target.set({
|
typeof value !== 'function',
|
||||||
originX: originTarget.originX,
|
),
|
||||||
originY: originTarget.originY,
|
),
|
||||||
left: changedCoords.x,
|
|
||||||
top: changedCoords.y,
|
|
||||||
})
|
|
||||||
canvas.renderAll()
|
|
||||||
|
|
||||||
//면형상 리사이즈시에만
|
|
||||||
target.fire('polygonMoved')
|
|
||||||
target.points = currentPoints
|
|
||||||
target.fire('modified')
|
|
||||||
|
|
||||||
setSurfaceShapePattern(target, roofDisplay.column, false, target.roofMaterial, true)
|
|
||||||
|
|
||||||
if (target.direction) {
|
|
||||||
drawDirectionArrow(target)
|
|
||||||
}
|
}
|
||||||
target.setCoords()
|
|
||||||
canvas.renderAll()
|
// 기존 QPolygon 제거
|
||||||
|
canvas.remove(target)
|
||||||
|
|
||||||
|
// 새로운 QPolygon 생성 (scale은 1로 고정됨)
|
||||||
|
const newPolygon = new QPolygon(newPoints, originalOptions, canvas)
|
||||||
|
|
||||||
|
newPolygon.set({
|
||||||
|
originWidth: width,
|
||||||
|
originHeight: height,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 캔버스에 추가
|
||||||
|
canvas.add(newPolygon)
|
||||||
|
|
||||||
|
// 선택 상태 유지
|
||||||
|
canvas.setActiveObject(newPolygon)
|
||||||
|
|
||||||
|
newPolygon.fire('modified')
|
||||||
|
setSurfaceShapePattern(newPolygon, null, null, newPolygon.roofMaterial)
|
||||||
|
drawDirectionArrow(newPolygon)
|
||||||
|
newPolygon.setCoords()
|
||||||
|
changeSurfaceLineType(newPolygon)
|
||||||
|
canvas?.renderAll()
|
||||||
|
closeAll()
|
||||||
|
addPopup(popupId, 1, <SizeSetting id={popupId} side={side} target={newPolygon} />)
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeSurfaceLinePropertyEvent = () => {
|
const changeSurfaceLinePropertyEvent = () => {
|
||||||
@ -1364,6 +1437,95 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
|||||||
return orderedPoints
|
return orderedPoints
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rotateSurfaceShapeBatch = () => {
|
||||||
|
if (currentObject) {
|
||||||
|
// 관련 객체들 찾기
|
||||||
|
// arrow는 제거
|
||||||
|
const arrow = canvas.getObjects().find((obj) => obj.parentId === currentObject.id && obj.name === 'arrow')
|
||||||
|
if (arrow) {
|
||||||
|
canvas.remove(arrow)
|
||||||
|
}
|
||||||
|
|
||||||
|
const relatedObjects = canvas.getObjects().filter((obj) => obj.parentId === currentObject.id)
|
||||||
|
|
||||||
|
// 그룹화할 객체들 배열 (currentObject + relatedObjects)
|
||||||
|
const objectsToGroup = [currentObject, ...relatedObjects]
|
||||||
|
|
||||||
|
// 기존 객체들을 캔버스에서 제거
|
||||||
|
objectsToGroup.forEach((obj) => canvas.remove(obj))
|
||||||
|
|
||||||
|
// fabric.Group 생성
|
||||||
|
const group = new fabric.Group(objectsToGroup, {
|
||||||
|
originX: 'center',
|
||||||
|
originY: 'center',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 그룹을 캔버스에 추가
|
||||||
|
canvas.add(group)
|
||||||
|
|
||||||
|
// 현재 회전값에 90도 추가
|
||||||
|
const currentAngle = group.angle || 0
|
||||||
|
const newAngle = (currentAngle + 90) % 360
|
||||||
|
|
||||||
|
// 그룹 전체를 회전
|
||||||
|
group.rotate(newAngle)
|
||||||
|
group.setCoords()
|
||||||
|
|
||||||
|
// 그룹을 해제하고 개별 객체로 복원
|
||||||
|
group._restoreObjectsState()
|
||||||
|
canvas.remove(group)
|
||||||
|
|
||||||
|
// 개별 객체들을 다시 캔버스에 추가하고 처리
|
||||||
|
group.getObjects().forEach((obj) => {
|
||||||
|
canvas.add(obj)
|
||||||
|
obj.setCoords()
|
||||||
|
|
||||||
|
// currentObject인 경우 추가 처리
|
||||||
|
if (obj.id === currentObject.id) {
|
||||||
|
const originWidth = obj.originWidth
|
||||||
|
const originHeight = obj.originHeight
|
||||||
|
|
||||||
|
// QPolygon 내부 구조 재구성 (선이 깨지는 문제 해결)
|
||||||
|
if (obj.type === 'QPolygon' && obj.lines) {
|
||||||
|
obj.initLines()
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.set({
|
||||||
|
originWidth: originHeight,
|
||||||
|
originHeight: originWidth,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// relatedObject인 경우에도 필요한 처리
|
||||||
|
if (obj.type === 'QPolygon' && obj.lines) {
|
||||||
|
obj.initLines()
|
||||||
|
}
|
||||||
|
if (obj.type === 'group') {
|
||||||
|
// 회전 후의 points를 groupPoints로 업데이트
|
||||||
|
// getCurrentPoints를 직접 호출하지 말고 recalculateGroupPoints만 실행
|
||||||
|
|
||||||
|
obj.recalculateGroupPoints()
|
||||||
|
|
||||||
|
obj._objects?.forEach((obj) => {
|
||||||
|
obj.initLines()
|
||||||
|
obj.fire('modified')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
currentObject.fire('modified')
|
||||||
|
// 화살표와 선 다시 그리기
|
||||||
|
drawDirectionArrow(currentObject)
|
||||||
|
setTimeout(() => {
|
||||||
|
setPolygonLinesActualSize(currentObject)
|
||||||
|
changeSurfaceLineType(currentObject)
|
||||||
|
}, 500)
|
||||||
|
|
||||||
|
// currentObject를 다시 선택 상태로 설정
|
||||||
|
canvas.setActiveObject(currentObject)
|
||||||
|
canvas.renderAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
applySurfaceShape,
|
applySurfaceShape,
|
||||||
deleteAllSurfacesAndObjects,
|
deleteAllSurfacesAndObjects,
|
||||||
@ -1373,5 +1535,6 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
|||||||
changeSurfaceLineProperty,
|
changeSurfaceLineProperty,
|
||||||
changeSurfaceLinePropertyReset,
|
changeSurfaceLinePropertyReset,
|
||||||
changeSurfaceLineType,
|
changeSurfaceLineType,
|
||||||
|
rotateSurfaceShapeBatch,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
||||||
import { canvasSettingState, canvasState, currentMenuState, currentObjectState } from '@/store/canvasAtom'
|
import { canvasSettingState, canvasState, currentMenuState, currentObjectState } from '@/store/canvasAtom'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { MENU, POLYGON_TYPE } from '@/common/common'
|
|
||||||
import AuxiliarySize from '@/components/floor-plan/modal/auxiliary/AuxiliarySize'
|
import AuxiliarySize from '@/components/floor-plan/modal/auxiliary/AuxiliarySize'
|
||||||
import { usePopup } from '@/hooks/usePopup'
|
import { usePopup } from '@/hooks/usePopup'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
@ -12,11 +11,9 @@ import { gridColorState } from '@/store/gridAtom'
|
|||||||
import { contextPopupPositionState, contextPopupState } from '@/store/popupAtom'
|
import { contextPopupPositionState, contextPopupState } from '@/store/popupAtom'
|
||||||
import AuxiliaryEdit from '@/components/floor-plan/modal/auxiliary/AuxiliaryEdit'
|
import AuxiliaryEdit from '@/components/floor-plan/modal/auxiliary/AuxiliaryEdit'
|
||||||
import SizeSetting from '@/components/floor-plan/modal/object/SizeSetting'
|
import SizeSetting from '@/components/floor-plan/modal/object/SizeSetting'
|
||||||
import RoofMaterialSetting from '@/components/floor-plan/modal/object/RoofMaterialSetting'
|
|
||||||
import DormerOffset from '@/components/floor-plan/modal/object/DormerOffset'
|
import DormerOffset from '@/components/floor-plan/modal/object/DormerOffset'
|
||||||
import FontSetting from '@/components/common/font/FontSetting'
|
import FontSetting from '@/components/common/font/FontSetting'
|
||||||
import RoofAllocationSetting from '@/components/floor-plan/modal/roofAllocation/RoofAllocationSetting'
|
import RoofAllocationSetting from '@/components/floor-plan/modal/roofAllocation/RoofAllocationSetting'
|
||||||
import LinePropertySetting from '@/components/floor-plan/modal/lineProperty/LinePropertySetting'
|
|
||||||
import FlowDirectionSetting from '@/components/floor-plan/modal/flowDirection/FlowDirectionSetting'
|
import FlowDirectionSetting from '@/components/floor-plan/modal/flowDirection/FlowDirectionSetting'
|
||||||
|
|
||||||
import { useCommonUtils } from './common/useCommonUtils'
|
import { useCommonUtils } from './common/useCommonUtils'
|
||||||
@ -29,7 +26,6 @@ import ColumnRemove from '@/components/floor-plan/modal/module/column/ColumnRemo
|
|||||||
import ColumnInsert from '@/components/floor-plan/modal/module/column/ColumnInsert'
|
import ColumnInsert from '@/components/floor-plan/modal/module/column/ColumnInsert'
|
||||||
import RowRemove from '@/components/floor-plan/modal/module/row/RowRemove'
|
import RowRemove from '@/components/floor-plan/modal/module/row/RowRemove'
|
||||||
import RowInsert from '@/components/floor-plan/modal/module/row/RowInsert'
|
import RowInsert from '@/components/floor-plan/modal/module/row/RowInsert'
|
||||||
import CircuitNumberEdit from '@/components/floor-plan/modal/module/CircuitNumberEdit'
|
|
||||||
import { useObjectBatch } from '@/hooks/object/useObjectBatch'
|
import { useObjectBatch } from '@/hooks/object/useObjectBatch'
|
||||||
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
|
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
|
||||||
import { fontSelector, globalFontAtom } from '@/store/fontAtom'
|
import { fontSelector, globalFontAtom } from '@/store/fontAtom'
|
||||||
@ -45,6 +41,8 @@ import PlacementSurfaceLineProperty from '@/components/floor-plan/modal/placemen
|
|||||||
import { selectedMenuState } from '@/store/menuAtom'
|
import { selectedMenuState } from '@/store/menuAtom'
|
||||||
import { useTrestle } from './module/useTrestle'
|
import { useTrestle } from './module/useTrestle'
|
||||||
import { useCircuitTrestle } from './useCirCuitTrestle'
|
import { useCircuitTrestle } from './useCirCuitTrestle'
|
||||||
|
import { usePolygon } from '@/hooks/usePolygon'
|
||||||
|
import { useText } from '@/hooks/useText'
|
||||||
|
|
||||||
export function useContextMenu() {
|
export function useContextMenu() {
|
||||||
const canvas = useRecoilValue(canvasState)
|
const canvas = useRecoilValue(canvasState)
|
||||||
@ -64,7 +62,7 @@ export function useContextMenu() {
|
|||||||
const [column, setColumn] = useState(null)
|
const [column, setColumn] = useState(null)
|
||||||
const { handleZoomClear } = useCanvasEvent()
|
const { handleZoomClear } = useCanvasEvent()
|
||||||
const { moveObjectBatch, copyObjectBatch } = useObjectBatch({})
|
const { moveObjectBatch, copyObjectBatch } = useObjectBatch({})
|
||||||
const { moveSurfaceShapeBatch } = useSurfaceShapeBatch({})
|
const { moveSurfaceShapeBatch, rotateSurfaceShapeBatch } = useSurfaceShapeBatch({})
|
||||||
const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom)
|
const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom)
|
||||||
const { addLine, removeLine } = useLine()
|
const { addLine, removeLine } = useLine()
|
||||||
const { removeGrid } = useGrid()
|
const { removeGrid } = useGrid()
|
||||||
@ -73,10 +71,12 @@ export function useContextMenu() {
|
|||||||
const { settingsData, setSettingsDataSave } = useCanvasSetting(false)
|
const { settingsData, setSettingsDataSave } = useCanvasSetting(false)
|
||||||
const { swalFire } = useSwal()
|
const { swalFire } = useSwal()
|
||||||
const { alignModule, modulesRemove, moduleRoofRemove } = useModule()
|
const { alignModule, modulesRemove, moduleRoofRemove } = useModule()
|
||||||
const { removeRoofMaterial, removeAllRoofMaterial, moveRoofMaterial, removeOuterLines } = useRoofFn()
|
const { removeRoofMaterial, removeAllRoofMaterial, moveRoofMaterial, removeOuterLines, setSurfaceShapePattern } = useRoofFn()
|
||||||
const selectedMenu = useRecoilValue(selectedMenuState)
|
const selectedMenu = useRecoilValue(selectedMenuState)
|
||||||
const { isAllComplete, clear: resetModule } = useTrestle()
|
const { isAllComplete, clear: resetModule } = useTrestle()
|
||||||
const { isExistCircuit } = useCircuitTrestle()
|
const { isExistCircuit } = useCircuitTrestle()
|
||||||
|
const { changeCorridorDimensionText } = useText()
|
||||||
|
const { setPolygonLinesActualSize, drawDirectionArrow } = usePolygon()
|
||||||
const currentMenuSetting = () => {
|
const currentMenuSetting = () => {
|
||||||
switch (selectedMenu) {
|
switch (selectedMenu) {
|
||||||
case 'outline':
|
case 'outline':
|
||||||
@ -170,6 +170,11 @@ export function useContextMenu() {
|
|||||||
name: getMessage('contextmenu.size.edit'),
|
name: getMessage('contextmenu.size.edit'),
|
||||||
component: <SizeSetting id={popupId} target={currentObject} />,
|
component: <SizeSetting id={popupId} target={currentObject} />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'rotate',
|
||||||
|
name: `${getMessage('contextmenu.rotate')}`,
|
||||||
|
fn: () => rotateSurfaceShapeBatch(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'roofMaterialRemove',
|
id: 'roofMaterialRemove',
|
||||||
shortcut: ['d', 'D'],
|
shortcut: ['d', 'D'],
|
||||||
|
|||||||
@ -213,7 +213,7 @@ export function useEvent() {
|
|||||||
const modulePoints = []
|
const modulePoints = []
|
||||||
const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
|
const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
|
||||||
modules.forEach((module) => {
|
modules.forEach((module) => {
|
||||||
module.points.forEach((point) => {
|
module.getCurrentPoints().forEach((point) => {
|
||||||
modulePoints.push({ x: point.x, y: point.y })
|
modulePoints.push({ x: point.x, y: point.y })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -173,6 +173,13 @@ export function usePlan(params = {}) {
|
|||||||
* @param {boolean} saveAlert - 저장 완료 알림 표시 여부
|
* @param {boolean} saveAlert - 저장 완료 알림 표시 여부
|
||||||
*/
|
*/
|
||||||
const saveCanvas = async (saveAlert = true) => {
|
const saveCanvas = async (saveAlert = true) => {
|
||||||
|
// 저장 전 선택되어 있는 object 제거
|
||||||
|
const setupSurfaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
|
||||||
|
|
||||||
|
setupSurfaces.forEach((surface) => {
|
||||||
|
surface.set({ fill: 'rgba(255,255,255,0.1)', strokeDashArray: [10, 4], strokeWidth: 1 })
|
||||||
|
})
|
||||||
|
|
||||||
const canvasStatus = currentCanvasData('save')
|
const canvasStatus = currentCanvasData('save')
|
||||||
const result = await putCanvasStatus(canvasStatus, saveAlert)
|
const result = await putCanvasStatus(canvasStatus, saveAlert)
|
||||||
//캔버스 저장 완료 후
|
//캔버스 저장 완료 후
|
||||||
|
|||||||
@ -99,6 +99,8 @@
|
|||||||
"modal.module.basic.setting.module.construction.method": "工法",
|
"modal.module.basic.setting.module.construction.method": "工法",
|
||||||
"modal.module.basic.setting.module.under.roof": "屋根下地",
|
"modal.module.basic.setting.module.under.roof": "屋根下地",
|
||||||
"modal.module.basic.setting.module.setting": "架台設定",
|
"modal.module.basic.setting.module.setting": "架台設定",
|
||||||
|
"modal.module.basic.setting.module.series.setting": "モジュールシリーズ",
|
||||||
|
"modal.module.basic.setting.module.setting2": "モジュール選択",
|
||||||
"modal.module.basic.setting.module.placement.area": "モジュール配置領域",
|
"modal.module.basic.setting.module.placement.area": "モジュール配置領域",
|
||||||
"modal.module.basic.setting.module.placement.margin": "モジュール間の間隙",
|
"modal.module.basic.setting.module.placement.margin": "モジュール間の間隙",
|
||||||
"modal.module.basic.setting.module.placement.area.eaves": "軒側",
|
"modal.module.basic.setting.module.placement.area.eaves": "軒側",
|
||||||
@ -444,6 +446,7 @@
|
|||||||
"contextmenu.remove": "削除",
|
"contextmenu.remove": "削除",
|
||||||
"contextmenu.remove.all": "完全削除",
|
"contextmenu.remove.all": "完全削除",
|
||||||
"contextmenu.move": "移動",
|
"contextmenu.move": "移動",
|
||||||
|
"contextmenu.rotate": "回転",
|
||||||
"contextmenu.copy": "コピー",
|
"contextmenu.copy": "コピー",
|
||||||
"contextmenu.edit": "編集",
|
"contextmenu.edit": "編集",
|
||||||
"contextmenu.module.vertical.align": "モジュールの垂直方向の中央揃え",
|
"contextmenu.module.vertical.align": "モジュールの垂直方向の中央揃え",
|
||||||
|
|||||||
@ -98,7 +98,9 @@
|
|||||||
"modal.module.basic.setting.module.rafter.margin": "서까래 간격",
|
"modal.module.basic.setting.module.rafter.margin": "서까래 간격",
|
||||||
"modal.module.basic.setting.module.construction.method": "공법",
|
"modal.module.basic.setting.module.construction.method": "공법",
|
||||||
"modal.module.basic.setting.module.under.roof": "지붕밑바탕",
|
"modal.module.basic.setting.module.under.roof": "지붕밑바탕",
|
||||||
"modal.module.basic.setting.module.setting": "모듈 선택",
|
"modal.module.basic.setting.module.setting": "가대 설정",
|
||||||
|
"modal.module.basic.setting.module.series.setting": "모듈 시리즈",
|
||||||
|
"modal.module.basic.setting.module.setting2": "모듈 선택",
|
||||||
"modal.module.basic.setting.module.placement.area": "모듈 배치 영역",
|
"modal.module.basic.setting.module.placement.area": "모듈 배치 영역",
|
||||||
"modal.module.basic.setting.module.placement.margin": "모듈 배치 간격",
|
"modal.module.basic.setting.module.placement.margin": "모듈 배치 간격",
|
||||||
"modal.module.basic.setting.module.placement.area.eaves": "처마쪽",
|
"modal.module.basic.setting.module.placement.area.eaves": "처마쪽",
|
||||||
@ -444,6 +446,7 @@
|
|||||||
"contextmenu.remove": "삭제",
|
"contextmenu.remove": "삭제",
|
||||||
"contextmenu.remove.all": "전체 삭제",
|
"contextmenu.remove.all": "전체 삭제",
|
||||||
"contextmenu.move": "이동",
|
"contextmenu.move": "이동",
|
||||||
|
"contextmenu.rotate": "회전",
|
||||||
"contextmenu.copy": "복사",
|
"contextmenu.copy": "복사",
|
||||||
"contextmenu.edit": "편집",
|
"contextmenu.edit": "편집",
|
||||||
"contextmenu.module.vertical.align": "모듈 세로 가운데 정렬",
|
"contextmenu.module.vertical.align": "모듈 세로 가운데 정렬",
|
||||||
|
|||||||
@ -11,7 +11,7 @@ $colors: (
|
|||||||
'danger-dark': #dc2626,
|
'danger-dark': #dc2626,
|
||||||
'warning': #f59e0b,
|
'warning': #f59e0b,
|
||||||
'warning-dark': #d97706,
|
'warning-dark': #d97706,
|
||||||
'border': #475569
|
'border': #475569,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mixins
|
// Mixins
|
||||||
@ -46,10 +46,15 @@ $colors: (
|
|||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
text-align: right;
|
text-align: left; // 왼쪽 정렬
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.625rem;
|
font-size: 0.625rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
direction: ltr; // 왼쪽에서 오른쪽으로 입력
|
||||||
|
white-space: nowrap; // 줄바꿈 방지
|
||||||
|
// input 요소에서는 overflow 대신 다른 방법 사용
|
||||||
|
text-overflow: clip; // 텍스트 잘림 처리
|
||||||
|
unicode-bidi: bidi-override; // 텍스트 방향 강제
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
@ -67,7 +72,9 @@ $colors: (
|
|||||||
width: 120px;
|
width: 120px;
|
||||||
background-color: map-get($colors, 'dark-700');
|
background-color: map-get($colors, 'dark-700');
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
box-shadow:
|
||||||
|
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||||
|
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
animation: fadeIn 0.15s ease-out;
|
animation: fadeIn 0.15s ease-out;
|
||||||
|
|||||||
232
src/util/fabric-extensions.js
Normal file
232
src/util/fabric-extensions.js
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import { fabric } from 'fabric'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fabric.Rect에 getCurrentPoints 메서드를 추가
|
||||||
|
* QPolygon의 getCurrentPoints와 동일한 방식으로 변형된 현재 점들을 반환
|
||||||
|
*/
|
||||||
|
fabric.Rect.prototype.getCurrentPoints = function () {
|
||||||
|
// 사각형의 네 모서리 점들을 계산
|
||||||
|
const width = this.width
|
||||||
|
const height = this.height
|
||||||
|
|
||||||
|
// 사각형의 로컬 좌표계에서의 네 모서리 점
|
||||||
|
const points = [
|
||||||
|
{ x: -width / 2, y: -height / 2 }, // 좌상단
|
||||||
|
{ x: width / 2, y: -height / 2 }, // 우상단
|
||||||
|
{ x: width / 2, y: height / 2 }, // 우하단
|
||||||
|
{ x: -width / 2, y: height / 2 }, // 좌하단
|
||||||
|
]
|
||||||
|
|
||||||
|
// 변형 매트릭스 계산
|
||||||
|
const matrix = this.calcTransformMatrix()
|
||||||
|
|
||||||
|
// 각 점을 변형 매트릭스로 변환
|
||||||
|
return points.map(function (p) {
|
||||||
|
const point = new fabric.Point(p.x, p.y)
|
||||||
|
return fabric.util.transformPoint(point, matrix)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fabric.Group에 getCurrentPoints 메서드를 추가 (도머 그룹용)
|
||||||
|
* 그룹의 groupPoints를 다시 계산하여 반환
|
||||||
|
*/
|
||||||
|
fabric.Group.prototype.getCurrentPoints = function () {
|
||||||
|
// groupPoints를 다시 계산
|
||||||
|
|
||||||
|
// 그룹에 groupPoints가 있으면 해당 점들을 사용 (도머의 경우)
|
||||||
|
if (this.groupPoints && Array.isArray(this.groupPoints)) {
|
||||||
|
const matrix = this.calcTransformMatrix()
|
||||||
|
console.log('this.groupPoints', this.groupPoints)
|
||||||
|
return this.groupPoints.map(function (p) {
|
||||||
|
const point = new fabric.Point(p.x, p.y)
|
||||||
|
return fabric.util.transformPoint(point, matrix)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// groupPoints가 없으면 바운딩 박스를 사용
|
||||||
|
const bounds = this.getBoundingRect()
|
||||||
|
const points = [
|
||||||
|
{ x: bounds.left, y: bounds.top },
|
||||||
|
{ x: bounds.left + bounds.width, y: bounds.top },
|
||||||
|
{ x: bounds.left + bounds.width, y: bounds.top + bounds.height },
|
||||||
|
{ x: bounds.left, y: bounds.top + bounds.height },
|
||||||
|
]
|
||||||
|
|
||||||
|
return points.map(function (p) {
|
||||||
|
return new fabric.Point(p.x, p.y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fabric.Group에 groupPoints 재계산 메서드 추가
|
||||||
|
* 그룹 내 모든 객체의 점들을 기반으로 groupPoints를 새로 계산
|
||||||
|
* Convex Hull 알고리즘을 사용하여 가장 외곽의 점들만 반환
|
||||||
|
*/
|
||||||
|
fabric.Group.prototype.recalculateGroupPoints = function () {
|
||||||
|
if (!this._objects || this._objects.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let allPoints = []
|
||||||
|
|
||||||
|
// 그룹 내 모든 객체의 점들을 수집
|
||||||
|
this._objects.forEach(function (obj) {
|
||||||
|
if (obj.getCurrentPoints && typeof obj.getCurrentPoints === 'function') {
|
||||||
|
// getCurrentPoints가 있는 객체는 해당 메서드 사용
|
||||||
|
const objPoints = obj.getCurrentPoints()
|
||||||
|
allPoints = allPoints.concat(objPoints)
|
||||||
|
} else if (obj.points && Array.isArray(obj.points)) {
|
||||||
|
// QPolygon과 같이 points 배열이 있는 경우
|
||||||
|
const pathOffset = obj.pathOffset || { x: 0, y: 0 }
|
||||||
|
const matrix = obj.calcTransformMatrix()
|
||||||
|
const transformedPoints = obj.points
|
||||||
|
.map(function (p) {
|
||||||
|
return new fabric.Point(p.x - pathOffset.x, p.y - pathOffset.y)
|
||||||
|
})
|
||||||
|
.map(function (p) {
|
||||||
|
return fabric.util.transformPoint(p, matrix)
|
||||||
|
})
|
||||||
|
allPoints = allPoints.concat(transformedPoints)
|
||||||
|
} else {
|
||||||
|
// 일반 객체는 바운딩 박스의 네 모서리 점 사용
|
||||||
|
const bounds = obj.getBoundingRect()
|
||||||
|
const cornerPoints = [
|
||||||
|
{ x: bounds.left, y: bounds.top },
|
||||||
|
{ x: bounds.left + bounds.width, y: bounds.top },
|
||||||
|
{ x: bounds.left + bounds.width, y: bounds.top + bounds.height },
|
||||||
|
{ x: bounds.left, y: bounds.top + bounds.height },
|
||||||
|
]
|
||||||
|
allPoints = allPoints.concat(
|
||||||
|
cornerPoints.map(function (p) {
|
||||||
|
return new fabric.Point(p.x, p.y)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (allPoints.length > 0) {
|
||||||
|
// Convex Hull 알고리즘을 사용하여 외곽 점들만 추출
|
||||||
|
const convexHullPoints = this.getConvexHull(allPoints)
|
||||||
|
|
||||||
|
// 그룹의 로컬 좌표계로 변환하기 위해 그룹의 역변환 적용
|
||||||
|
const groupMatrix = this.calcTransformMatrix()
|
||||||
|
const invertedMatrix = fabric.util.invertTransform(groupMatrix)
|
||||||
|
|
||||||
|
this.groupPoints = convexHullPoints.map(function (p) {
|
||||||
|
const localPoint = fabric.util.transformPoint(p, invertedMatrix)
|
||||||
|
return { x: localPoint.x, y: localPoint.y }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Graham Scan 알고리즘을 사용한 Convex Hull 계산
|
||||||
|
* 점들의 집합에서 가장 외곽의 점들만 반환
|
||||||
|
*/
|
||||||
|
fabric.Group.prototype.getConvexHull = function (points) {
|
||||||
|
if (points.length < 3) return points
|
||||||
|
|
||||||
|
// 중복 점 제거
|
||||||
|
const uniquePoints = []
|
||||||
|
const seen = new Set()
|
||||||
|
|
||||||
|
points.forEach(function (p) {
|
||||||
|
const key = `${Math.round(p.x * 10) / 10},${Math.round(p.y * 10) / 10}`
|
||||||
|
if (!seen.has(key)) {
|
||||||
|
seen.add(key)
|
||||||
|
uniquePoints.push({ x: p.x, y: p.y })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (uniquePoints.length < 3) return uniquePoints
|
||||||
|
|
||||||
|
// 가장 아래쪽 점을 찾기 (y가 가장 작고, 같으면 x가 가장 작은 점)
|
||||||
|
let pivot = uniquePoints[0]
|
||||||
|
for (let i = 1; i < uniquePoints.length; i++) {
|
||||||
|
if (uniquePoints[i].y < pivot.y || (uniquePoints[i].y === pivot.y && uniquePoints[i].x < pivot.x)) {
|
||||||
|
pivot = uniquePoints[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 극각에 따라 정렬
|
||||||
|
const sortedPoints = uniquePoints
|
||||||
|
.filter(function (p) { return p !== pivot })
|
||||||
|
.sort(function (a, b) {
|
||||||
|
const angleA = Math.atan2(a.y - pivot.y, a.x - pivot.x)
|
||||||
|
const angleB = Math.atan2(b.y - pivot.y, b.x - pivot.x)
|
||||||
|
if (angleA !== angleB) return angleA - angleB
|
||||||
|
|
||||||
|
// 각도가 같으면 거리로 정렬
|
||||||
|
const distA = Math.pow(a.x - pivot.x, 2) + Math.pow(a.y - pivot.y, 2)
|
||||||
|
const distB = Math.pow(b.x - pivot.x, 2) + Math.pow(b.y - pivot.y, 2)
|
||||||
|
return distA - distB
|
||||||
|
})
|
||||||
|
|
||||||
|
// Graham Scan 실행
|
||||||
|
const hull = [pivot]
|
||||||
|
|
||||||
|
for (let i = 0; i < sortedPoints.length; i++) {
|
||||||
|
const current = sortedPoints[i]
|
||||||
|
|
||||||
|
// 반시계방향이 아닌 점들 제거
|
||||||
|
while (hull.length > 1) {
|
||||||
|
const p1 = hull[hull.length - 2]
|
||||||
|
const p2 = hull[hull.length - 1]
|
||||||
|
const cross = (p2.x - p1.x) * (current.y - p1.y) - (p2.y - p1.y) * (current.x - p1.x)
|
||||||
|
|
||||||
|
if (cross > 0) break // 반시계방향이면 유지
|
||||||
|
hull.pop() // 시계방향이면 제거
|
||||||
|
}
|
||||||
|
|
||||||
|
hull.push(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hull
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fabric.Triangle에 getCurrentPoints 메서드를 추가
|
||||||
|
* 삼각형의 세 꼭짓점을 반환
|
||||||
|
*/
|
||||||
|
fabric.Triangle.prototype.getCurrentPoints = function () {
|
||||||
|
const width = this.width
|
||||||
|
const height = this.height
|
||||||
|
|
||||||
|
// 삼각형의 로컬 좌표계에서의 세 꼭짓점
|
||||||
|
const points = [
|
||||||
|
{ x: 0, y: -height / 2 }, // 상단 중앙
|
||||||
|
{ x: -width / 2, y: height / 2 }, // 좌하단
|
||||||
|
{ x: width / 2, y: height / 2 }, // 우하단
|
||||||
|
]
|
||||||
|
|
||||||
|
// 변형 매트릭스 계산
|
||||||
|
const matrix = this.calcTransformMatrix()
|
||||||
|
|
||||||
|
// 각 점을 변형 매트릭스로 변환
|
||||||
|
return points.map(function (p) {
|
||||||
|
const point = new fabric.Point(p.x, p.y)
|
||||||
|
return fabric.util.transformPoint(point, matrix)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fabric.Polygon에 getCurrentPoints 메서드를 추가 (QPolygon이 아닌 일반 Polygon용)
|
||||||
|
* QPolygon과 동일한 방식으로 구현
|
||||||
|
*/
|
||||||
|
if (!fabric.Polygon.prototype.getCurrentPoints) {
|
||||||
|
fabric.Polygon.prototype.getCurrentPoints = function () {
|
||||||
|
const pathOffset = this.get('pathOffset') || { x: 0, y: 0 }
|
||||||
|
const matrix = this.calcTransformMatrix()
|
||||||
|
|
||||||
|
return this.get('points')
|
||||||
|
.map(function (p) {
|
||||||
|
return new fabric.Point(p.x - pathOffset.x, p.y - pathOffset.y)
|
||||||
|
})
|
||||||
|
.map(function (p) {
|
||||||
|
return fabric.util.transformPoint(p, matrix)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {}
|
||||||
2050
src/util/skeleton-utils.js
Normal file
2050
src/util/skeleton-utils.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user