dev #616
@ -98,6 +98,10 @@ export default function Join() {
|
||||
alert(getMessage('common.message.required.data', [getMessage('join.sub1.fax')]))
|
||||
faxRef.current.focus()
|
||||
return false
|
||||
}else if (!telRegex.test(fax)) {
|
||||
alert(getMessage('join.validation.check1', [getMessage('join.sub1.fax')]))
|
||||
faxRef.current.focus()
|
||||
return false
|
||||
}
|
||||
|
||||
const bizNo = formData.get('bizNo')
|
||||
@ -174,6 +178,10 @@ export default function Join() {
|
||||
alert(getMessage('common.message.required.data', [getMessage('join.sub2.fax')]))
|
||||
userFaxRef.current.focus()
|
||||
return false
|
||||
} else if (!telRegex.test(userFax)) {
|
||||
alert(getMessage('join.validation.check1', [getMessage('join.sub2.fax')]))
|
||||
userFaxRef.current.focus()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
@ -349,7 +357,15 @@ export default function Join() {
|
||||
<th>{getMessage('join.sub1.fax')}<span className="important">*</span></th>
|
||||
<td>
|
||||
<div className="input-wrap" style={{ width: '200px' }}>
|
||||
<input type="text" id="fax" name="fax" className="input-light" maxLength={15} onChange={inputNumberCheck} ref={faxRef} />
|
||||
<input
|
||||
type="text"
|
||||
id="fax"
|
||||
name="fax"
|
||||
className="input-light"
|
||||
maxLength={15}
|
||||
placeholder={getMessage('join.sub1.telNo_placeholder')}
|
||||
onChange={inputTelNumberCheck}
|
||||
ref={faxRef} />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -466,7 +482,8 @@ export default function Join() {
|
||||
name="userFax"
|
||||
className="input-light"
|
||||
maxLength={15}
|
||||
onChange={inputNumberCheck}
|
||||
placeholder={getMessage('join.sub1.telNo_placeholder')}
|
||||
onChange={inputTelNumberCheck}
|
||||
ref={userFaxRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -13,6 +13,7 @@ import { usePolygon } from '@/hooks/usePolygon'
|
||||
import { useObjectBatch } from '@/hooks/object/useObjectBatch'
|
||||
import { BATCH_TYPE } from '@/common/common'
|
||||
import { useMouse } from '@/hooks/useMouse'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
|
||||
export function useCommonUtils() {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
@ -617,6 +618,168 @@ export function useCommonUtils() {
|
||||
|
||||
const buttonAct = dormerName == BATCH_TYPE.TRIANGLE_DORMER ? 3 : 4
|
||||
applyDormers(dormerParams, buttonAct)
|
||||
} else if (obj.name === 'roof' && obj.type === 'QPolygon') {
|
||||
// roof(QPolygon) 객체는 순환 참조(lines[].parent -> polygon)로 인해
|
||||
// fabric.clone() 사용 시 Maximum call stack size exceeded 에러 발생
|
||||
// getCurrentPoints()를 사용하여 새 QPolygon을 직접 생성
|
||||
|
||||
// 원본 객체의 line attributes 복사 (순환 참조 제거)
|
||||
const lineAttributes = obj.lines.map((line) => ({
|
||||
type: line.attributes?.type,
|
||||
offset: line.attributes?.offset,
|
||||
actualSize: line.attributes?.actualSize,
|
||||
planeSize: line.attributes?.planeSize,
|
||||
}))
|
||||
|
||||
// 원본 roof의 자식 오브젝트들 찾기 (개구, 그림자, 도머 등)
|
||||
const childObjectTypes = [BATCH_TYPE.OPENING, BATCH_TYPE.SHADOW, BATCH_TYPE.TRIANGLE_DORMER, BATCH_TYPE.PENTAGON_DORMER]
|
||||
const childObjects = canvas.getObjects().filter(
|
||||
(o) => o.parentId === obj.id && childObjectTypes.includes(o.name)
|
||||
)
|
||||
|
||||
// 원본 roof 중심점 계산
|
||||
const originalPoints = obj.getCurrentPoints()
|
||||
const originalCenterX = originalPoints.reduce((sum, p) => sum + p.x, 0) / originalPoints.length
|
||||
const originalCenterY = originalPoints.reduce((sum, p) => sum + p.y, 0) / originalPoints.length
|
||||
|
||||
let clonedObj = null
|
||||
let clonedChildren = []
|
||||
|
||||
addCanvasMouseEventListener('mouse:move', (e) => {
|
||||
const pointer = canvas?.getPointer(e.e)
|
||||
|
||||
// 이전 임시 객체들 제거
|
||||
canvas
|
||||
.getObjects()
|
||||
.filter((o) => o.name === 'clonedObj' || o.name === 'clonedChildTemp')
|
||||
.forEach((o) => canvas?.remove(o))
|
||||
|
||||
// 새 QPolygon 생성 (매 move마다 생성하여 위치 업데이트)
|
||||
const currentPoints = obj.getCurrentPoints()
|
||||
const centerX = currentPoints.reduce((sum, p) => sum + p.x, 0) / currentPoints.length
|
||||
const centerY = currentPoints.reduce((sum, p) => sum + p.y, 0) / currentPoints.length
|
||||
|
||||
// 이동 오프셋 계산
|
||||
const offsetX = pointer.x - centerX
|
||||
const offsetY = pointer.y - centerY
|
||||
|
||||
// 포인터 위치로 이동된 새 points 계산
|
||||
const newPoints = currentPoints.map((p) => ({
|
||||
x: p.x + offsetX,
|
||||
y: p.y + offsetY,
|
||||
}))
|
||||
|
||||
clonedObj = new QPolygon(newPoints, {
|
||||
fill: obj.fill || 'transparent',
|
||||
stroke: obj.stroke || 'black',
|
||||
strokeWidth: obj.strokeWidth || 1,
|
||||
fontSize: 0, // 이동 중에는 lengthText 생성하지 않음 (fontSize=0이면 addLengthText가 스킵됨)
|
||||
selectable: true,
|
||||
lockMovementX: true,
|
||||
lockMovementY: true,
|
||||
lockRotation: true,
|
||||
lockScalingX: true,
|
||||
lockScalingY: true,
|
||||
name: 'clonedObj',
|
||||
originX: 'center',
|
||||
originY: 'center',
|
||||
pitch: obj.pitch,
|
||||
surfaceId: obj.surfaceId,
|
||||
sort: false,
|
||||
}, canvas)
|
||||
|
||||
canvas.add(clonedObj)
|
||||
|
||||
// 자식 오브젝트들도 이동해서 미리보기 표시
|
||||
clonedChildren = []
|
||||
childObjects.forEach((child) => {
|
||||
child.clone((clonedChild) => {
|
||||
clonedChild.set({
|
||||
left: child.left + offsetX,
|
||||
top: child.top + offsetY,
|
||||
name: 'clonedChildTemp',
|
||||
selectable: false,
|
||||
evented: false,
|
||||
})
|
||||
clonedChildren.push({ original: child, cloned: clonedChild })
|
||||
canvas.add(clonedChild)
|
||||
})
|
||||
})
|
||||
|
||||
canvas.renderAll()
|
||||
})
|
||||
|
||||
addCanvasMouseEventListener('mouse:down', (e) => {
|
||||
if (!clonedObj) return
|
||||
|
||||
const newRoofId = uuidv4()
|
||||
|
||||
clonedObj.set({
|
||||
lockMovementX: true,
|
||||
lockMovementY: true,
|
||||
name: 'roof',
|
||||
editable: false,
|
||||
selectable: true,
|
||||
id: newRoofId,
|
||||
direction: obj.direction,
|
||||
directionText: obj.directionText,
|
||||
roofMaterial: obj.roofMaterial,
|
||||
stroke: 'black',
|
||||
evented: true,
|
||||
isFixed: false,
|
||||
fontSize: lengthTextFont.fontSize.value, // 최종 확정 시 fontSize 설정
|
||||
})
|
||||
|
||||
// line attributes 복원
|
||||
lineAttributes.forEach((attr, index) => {
|
||||
if (clonedObj.lines[index]) {
|
||||
clonedObj.lines[index].set({ attributes: attr })
|
||||
}
|
||||
})
|
||||
|
||||
// 임시 자식 오브젝트들 제거
|
||||
canvas
|
||||
.getObjects()
|
||||
.filter((o) => o.name === 'clonedChildTemp')
|
||||
.forEach((o) => canvas?.remove(o))
|
||||
|
||||
// 자식 오브젝트들 최종 복사 (새 roof의 id를 parentId로 설정)
|
||||
const pointer = canvas?.getPointer(e.e)
|
||||
const currentPoints = obj.getCurrentPoints()
|
||||
const centerX = currentPoints.reduce((sum, p) => sum + p.x, 0) / currentPoints.length
|
||||
const centerY = currentPoints.reduce((sum, p) => sum + p.y, 0) / currentPoints.length
|
||||
const offsetX = pointer.x - centerX
|
||||
const offsetY = pointer.y - centerY
|
||||
|
||||
childObjects.forEach((child) => {
|
||||
child.clone((clonedChild) => {
|
||||
clonedChild.set({
|
||||
left: child.left + offsetX,
|
||||
top: child.top + offsetY,
|
||||
id: uuidv4(),
|
||||
parentId: newRoofId, // 새 roof의 id를 부모로 설정
|
||||
name: child.name,
|
||||
selectable: true,
|
||||
evented: true,
|
||||
})
|
||||
// 그룹 객체인 경우 groupId도 새로 설정
|
||||
if (clonedChild.type === 'group') {
|
||||
clonedChild.set({ groupId: uuidv4() })
|
||||
}
|
||||
canvas.add(clonedChild)
|
||||
})
|
||||
})
|
||||
|
||||
clonedObj.fire('polygonMoved')
|
||||
clonedObj.fire('modified')
|
||||
clonedObj.setCoords()
|
||||
canvas.setActiveObject(clonedObj)
|
||||
canvas.renderAll()
|
||||
addLengthText(clonedObj) // fontSize가 설정된 후 lengthText 추가
|
||||
drawDirectionArrow(clonedObj)
|
||||
|
||||
initEvent()
|
||||
})
|
||||
} else {
|
||||
let clonedObj = null
|
||||
|
||||
@ -655,32 +818,6 @@ export function useCommonUtils() {
|
||||
//객체가 그룹일 경우에는 그룹 아이디를 따로 넣어준다
|
||||
if (clonedObj.type === 'group') clonedObj.set({ groupId: uuidv4() })
|
||||
|
||||
//배치면일 경우
|
||||
if (obj.name === 'roof') {
|
||||
clonedObj.canvas = canvas // canvas 참조 설정
|
||||
clonedObj.set({
|
||||
direction: obj.direction,
|
||||
directionText: obj.directionText,
|
||||
roofMaterial: obj.roofMaterial,
|
||||
stroke: 'black', // 복사된 객체는 선택 해제 상태의 색상으로 설정
|
||||
selectable: true, // 선택 가능하도록 설정
|
||||
evented: true, // 마우스 이벤트를 받을 수 있도록 설정
|
||||
isFixed: false, // containsPoint에서 특별 처리 방지
|
||||
})
|
||||
|
||||
obj.lines.forEach((line, index) => {
|
||||
clonedObj.lines[index].set({ attributes: line.attributes })
|
||||
})
|
||||
|
||||
clonedObj.fire('polygonMoved') // 내부 좌표 재계산 (points, pathOffset)
|
||||
clonedObj.fire('modified')
|
||||
clonedObj.setCoords() // 모든 속성 설정 후 좌표 업데이트
|
||||
canvas.setActiveObject(clonedObj)
|
||||
canvas.renderAll()
|
||||
addLengthText(clonedObj) //수치 추가
|
||||
drawDirectionArrow(clonedObj) //방향 화살표 추가
|
||||
}
|
||||
|
||||
initEvent()
|
||||
})
|
||||
}
|
||||
|
||||
@ -152,11 +152,17 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
||||
|
||||
rect.set({ width: Math.abs(width), height: Math.abs(height) })
|
||||
|
||||
// 마우스를 왼쪽으로 드래그한 경우 left를 현재 포인터 위치로 설정
|
||||
if (width < 0) {
|
||||
rect.set({ left: Math.abs(pointer.x) })
|
||||
rect.set({ left: pointer.x })
|
||||
} else {
|
||||
rect.set({ left: origX })
|
||||
}
|
||||
// 마우스를 위쪽으로 드래그한 경우 top을 현재 포인터 위치로 설정
|
||||
if (height < 0) {
|
||||
rect.set({ top: Math.abs(pointer.y) })
|
||||
rect.set({ top: pointer.y })
|
||||
} else {
|
||||
rect.set({ top: origY })
|
||||
}
|
||||
|
||||
canvas?.renderAll()
|
||||
@ -179,7 +185,8 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isCrossChecked) {
|
||||
// 그림자(SHADOW)는 중복 설치 허용, 개구(OPENING)만 중복 체크
|
||||
if (!isCrossChecked && buttonAct === 1) {
|
||||
const preObjects = canvas?.getObjects().filter((obj) => obj.name === BATCH_TYPE.OPENING || obj.name === BATCH_TYPE.SHADOW)
|
||||
const preObjectsArray = preObjects.map((obj) => rectToPolygon(obj))
|
||||
const isCross = preObjectsArray.some((object) => turf.booleanOverlap(pointsToTurfPolygon(object), rectPolygon))
|
||||
@ -266,7 +273,8 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isCrossChecked) {
|
||||
// 그림자(SHADOW)는 중복 설치 허용, 개구(OPENING)만 중복 체크
|
||||
if (!isCrossChecked && buttonAct === 1) {
|
||||
const preObjects = canvas?.getObjects().filter((obj) => obj.name === BATCH_TYPE.OPENING || obj.name === BATCH_TYPE.SHADOW)
|
||||
const preObjectsArray = preObjects.map((obj) => rectToPolygon(obj))
|
||||
const isCross = preObjectsArray.some((object) => turf.booleanOverlap(pointsToTurfPolygon(object), rectPolygon))
|
||||
|
||||
@ -159,10 +159,6 @@ export function useContextMenu() {
|
||||
}
|
||||
break
|
||||
case 'roof':
|
||||
case 'auxiliaryLine':
|
||||
case 'hip':
|
||||
case 'ridge':
|
||||
case 'eaveHelpLine':
|
||||
if (selectedMenu === 'surface') {
|
||||
setContextMenu([
|
||||
[
|
||||
@ -249,6 +245,73 @@ export function useContextMenu() {
|
||||
},
|
||||
},
|
||||
],
|
||||
])
|
||||
}
|
||||
|
||||
break
|
||||
case 'auxiliaryLine':
|
||||
case 'hip':
|
||||
case 'ridge':
|
||||
case 'eaveHelpLine':
|
||||
if (selectedMenu === 'surface') {
|
||||
setContextMenu([
|
||||
[
|
||||
{
|
||||
id: 'sizeEdit',
|
||||
name: getMessage('contextmenu.size.edit'),
|
||||
component: <SizeSetting id={popupId} target={currentObject} />,
|
||||
},
|
||||
{
|
||||
id: 'rotate',
|
||||
name: `${getMessage('contextmenu.rotate')}`,
|
||||
fn: () => rotateSurfaceShapeBatch(),
|
||||
},
|
||||
{
|
||||
id: 'roofMaterialRemove',
|
||||
shortcut: ['d', 'D'],
|
||||
name: `${getMessage('contextmenu.remove')}(D)`,
|
||||
fn: () => deleteObject(),
|
||||
},
|
||||
{
|
||||
id: 'roofMaterialMove',
|
||||
shortcut: ['m', 'M'],
|
||||
name: `${getMessage('contextmenu.move')}(M)`,
|
||||
fn: () => moveSurfaceShapeBatch(),
|
||||
},
|
||||
{
|
||||
id: 'roofMaterialCopy',
|
||||
shortcut: ['c', 'C'],
|
||||
name: `${getMessage('contextmenu.copy')}(C)`,
|
||||
fn: () => copyObject(),
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
id: 'roofMaterialEdit',
|
||||
name: getMessage('contextmenu.roof.material.edit'),
|
||||
component: <ContextRoofAllocationSetting id={popupId} />,
|
||||
},
|
||||
{
|
||||
id: 'linePropertyEdit',
|
||||
name: getMessage('contextmenu.line.property.edit'),
|
||||
fn: () => {
|
||||
if (+canvasSetting.roofSizeSet === 3) {
|
||||
swalFire({ text: getMessage('contextmenu.line.property.edit.roof.size.3') })
|
||||
} else {
|
||||
addPopup(popupId, 1, <PlacementSurfaceLineProperty id={popupId} roof={currentObject} />)
|
||||
}
|
||||
},
|
||||
// component: <LinePropertySetting id={popupId} target={currentObject} />,
|
||||
},
|
||||
{
|
||||
id: 'flowDirectionEdit',
|
||||
name: getMessage('contextmenu.flow.direction.edit'),
|
||||
component: <FlowDirectionSetting id={popupId} target={currentObject} />,
|
||||
},
|
||||
],
|
||||
])
|
||||
} else if (selectedMenu === 'outline') {
|
||||
setContextMenu([
|
||||
[
|
||||
{
|
||||
id: 'sizeEdit',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user