- | 담당자 |
+ {getMessage('stuff.search.schReceiveUser')} |
{
setReceiveUser(e.target.value)
@@ -264,7 +293,7 @@ export default function StuffSearchCondition() {
/>
|
- 기간검색 |
+ {getMessage('stuff.search.period')} |
@@ -314,189 +343,6 @@ export default function StuffSearchCondition() {
{/* 퍼블적용끝 */}
- {/*
-
-
-
-
-
-
-
- {Array(4)
- .fill()
- .map((_, i) => {
- if (i === 0) {
- return (
-
- {
- setObjectNo(e.target.value)
- setStuffSearch({ ...stuffSearch, code: 'S', schObjectNo: e.target.value })
- }}
- />
- {
- setSaleStoreId(e.target.value)
- setStuffSearch({ ...stuffSearch, code: 'S', schSaleStoreId: e.target.value })
- }}
- />
- {
- setAddress(e.target.value)
- setStuffSearch({ ...stuffSearch, code: 'S', schAddress: e.target.value })
- }}
- />
-
- )
- } else if (i === 1) {
- return (
-
- {
- setobjectName(e.target.value)
- setStuffSearch({ ...stuffSearch, code: 'S', schObjectName: e.target.value })
- }}
- />
- {
- setSaleStoreName(e.target.value)
- setStuffSearch({ ...stuffSearch, code: 'S', schSaleStoreName: e.target.value })
- }}
- />
-
- {
- setSpecDateYn(e.target.value)
- setStuffSearch({ ...stuffSearch, code: 'S', schSpecDateYn: e.target.value })
- }}
- />
-
-
-
- {
- setSpecDateYn(e.target.value)
- setStuffSearch({ ...stuffSearch, code: 'S', schSpecDateYn: e.target.value })
- }}
- />
-
-
-
- {
- setSpecDateYn(e.target.value)
- setStuffSearch({ ...stuffSearch, code: 'S', schSpecDateYn: e.target.value })
- }}
- />
-
-
-
- )
- } else if (i === 2) {
- return (
-
- )
- } else {
- return (
-
-
- {
- setDateType(e.target.value)
- setStuffSearch({ ...stuffSearch, code: 'S', schDateType: e.target.value })
- }}
- />
-
-
-
- {
- setDateType(e.target.value)
- setStuffSearch({ ...stuffSearch, code: 'S', schDateType: e.target.value })
- }}
- />
-
-
-
-
- )
- }
- })}
- */}
>
)
}
diff --git a/src/components/management/popup/FindAddressPop.jsx b/src/components/management/popup/FindAddressPop.jsx
new file mode 100644
index 00000000..b3702ded
--- /dev/null
+++ b/src/components/management/popup/FindAddressPop.jsx
@@ -0,0 +1,166 @@
+import React, { useState } from 'react'
+import { useForm } from 'react-hook-form'
+import { queryStringFormatter } from '@/util/common-utils'
+import { useAxios } from '@/hooks/useAxios'
+import { globalLocaleStore } from '@/store/localeAtom'
+import { useRecoilValue } from 'recoil'
+import FindAddressPopQGrid from './FindAddressPopQGrid'
+import { useMessage } from '@/hooks/useMessage'
+import { isNotEmptyArray } from '@/util/common-utils'
+export default function FindAddressPop(props) {
+ const globalLocaleState = useRecoilValue(globalLocaleStore)
+
+ const { get } = useAxios(globalLocaleState)
+
+ const { getMessage } = useMessage()
+
+ const [prefId, setPrefId] = useState(null)
+ const [zipNo, setZipNo] = useState(null)
+ const [address1, setAddress1] = useState(null)
+ const [address2, setAddress2] = useState(null)
+ const [address3, setAddress3] = useState(null)
+
+ const [gridProps, setGridProps] = useState({
+ gridData: [],
+ isPageable: false,
+ gridColumns: [
+ {
+ field: 'address1',
+ headerName: getMessage('stuff.addressPopup.gridHeader.address1'),
+ minWidth: 150,
+ checkboxSelection: true,
+ showDisabledCheckboxes: true,
+ },
+ {
+ field: 'address2',
+ headerName: getMessage('stuff.addressPopup.gridHeader.address2'),
+ minWidth: 150,
+ },
+ {
+ field: 'address3',
+ headerName: getMessage('stuff.addressPopup.gridHeader.address3'),
+ minWidth: 150,
+ },
+ ],
+ })
+
+ //검색필드
+ const formInitValue = {
+ zipNo: '',
+ }
+ const { register, setValue, getValues, handleSubmit, resetField, control, watch } = useForm({
+ defaultValues: formInitValue,
+ })
+
+ const form = { register, setValue, getValues, handleSubmit, resetField, control, watch }
+
+ //우편번호 검색
+ const searchPostNum = () => {
+ // //7830060
+ // //9302226
+ // //0790177 3개짜리
+ const params = {
+ zipcode: watch('zipNo'),
+ }
+
+ get({ url: `https://zipcloud.ibsnet.co.jp/api/search?${queryStringFormatter(params)}` }).then((res) => {
+ if (res.status === 200) {
+ if (isNotEmptyArray(res.results)) {
+ setGridProps({ ...gridProps, gridData: res.results })
+ } else {
+ alert(getMessage('stuff.addressPopup.error.message1'))
+ setGridProps({ ...gridProps, gridData: [] })
+ }
+ } else {
+ alert(getMessage('stuff.addressPopup.error.message1'))
+ setGridProps({ ...gridProps, gridData: [] })
+ }
+ })
+ }
+ // 주소적용 클릭
+ const applyAddress = () => {
+ // console.log('주소적용 클릭:::::::::', prefId, address1, address2, address3, zipNo)
+ if (prefId == null) {
+ alert(getMessage('stuff.addressPopup.error.message2'))
+ } else {
+ props.zipInfo({
+ zipNo: zipNo,
+ address1: address1,
+ address2: address2,
+ address3: address3,
+ prefId: prefId,
+ })
+
+ //팝업닫기
+ props.setShowAddressButtonValid(false)
+ }
+ }
+
+ //숫자만
+ const handleKeyUp = (e) => {
+ let input = e.target
+ input.value = input.value.replace(/[^0-9]/g, '')
+
+ if (e.key === 'Enter') {
+ searchPostNum()
+ }
+ }
+
+ //그리드에서 선택한 우편정보
+ const getSelectedRowdata = (data) => {
+ if (isNotEmptyArray(data)) {
+ setAddress1(data[0].address1)
+ setAddress2(data[0].address2)
+ setAddress3(data[0].address3)
+ setPrefId(data[0].prefcode)
+ setZipNo(data[0].zipcode)
+ } else {
+ setAddress1(null)
+ setAddress2(null)
+ setAddress3(null)
+ setPrefId(null)
+ setZipNo(null)
+ }
+ }
+
+ return (
+
+
+
+
+ {getMessage('stuff.addressPopup.title')}
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/management/popup/FindAddressPopQGrid.jsx b/src/components/management/popup/FindAddressPopQGrid.jsx
new file mode 100644
index 00000000..b9d1bb90
--- /dev/null
+++ b/src/components/management/popup/FindAddressPopQGrid.jsx
@@ -0,0 +1,62 @@
+import React from 'react'
+import { useCallback, useEffect, useMemo, useState } from 'react'
+import { AgGridReact } from 'ag-grid-react'
+
+import 'ag-grid-community/styles/ag-grid.css'
+import 'ag-grid-community/styles/ag-theme-quartz.css'
+
+export default function FindAddressPopGrid(props) {
+ const { gridData, gridColumns, isPageable = true } = props
+
+ const [rowData, setRowData] = useState(null)
+
+ const [gridApi, setGridApi] = useState(null)
+
+ const [colDefs, setColDefs] = useState(gridColumns)
+
+ const defaultColDef = useMemo(() => {
+ return {
+ flex: 1,
+ minWidth: 100,
+ sortable: false,
+ suppressMovable: false,
+ resizable: false,
+ suppressSizeToFit: false,
+ }
+ }, [])
+
+ const rowBuffer = 100
+
+ useEffect(() => {
+ gridData ? setRowData(gridData) : ''
+ }, [gridData])
+
+ const onGridReady = useCallback(
+ (params) => {
+ setGridApi(params.api)
+ gridData ? setRowData(gridData) : ''
+ },
+ [gridData],
+ )
+
+ //부모로 선택한 우편정보 보내기
+ const onSelectionChanged = () => {
+ const selectedData = gridApi.getSelectedRows()
+ props.getSelectedRowdata(selectedData)
+ }
+
+ return (
+
+ )
+}
diff --git a/src/components/management/popup/PlanRequestPop.jsx b/src/components/management/popup/PlanRequestPop.jsx
new file mode 100644
index 00000000..224519d0
--- /dev/null
+++ b/src/components/management/popup/PlanRequestPop.jsx
@@ -0,0 +1,302 @@
+import React, { useState, useRef, useEffect } from 'react'
+import { useForm } from 'react-hook-form'
+import { queryStringFormatter } from '@/util/common-utils'
+import { useAxios } from '@/hooks/useAxios'
+import { globalLocaleStore } from '@/store/localeAtom'
+import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
+import FindAddressPopQGrid from './FindAddressPopQGrid'
+import { useMessage } from '@/hooks/useMessage'
+import { isNotEmptyArray } from '@/util/common-utils'
+import SingleDatePicker from '@/components/common/datepicker/SingleDatePicker'
+import dayjs from 'dayjs'
+import PlanRequestPopQGrid from './PlanRequestPopQGrid'
+import { sessionStore } from '@/store/commonAtom'
+import { planReqSearchState } from '@/store/planReqAtom'
+export default function PlanRequestPop(props) {
+ const sessionState = useRecoilValue(sessionStore)
+
+ const globalLocaleState = useRecoilValue(globalLocaleStore)
+
+ const { get } = useAxios(globalLocaleState)
+
+ const { getMessage } = useMessage()
+ // 검색조건 달력 셋팅
+ const [startDate, setStartDate] = useState(dayjs(new Date()).add(-3, 'month').format('YYYY-MM-DD'))
+ const [endDate, setEndDate] = useState(dayjs(new Date()).format('YYYY-MM-DD'))
+
+ const rangeDatePickerProps1 = {
+ startDate, //시작일
+ setStartDate,
+ }
+
+ const rangeDatePickerProps2 = {
+ startDate: endDate, //종료일
+ setStartDate: setEndDate,
+ }
+
+ const ref = useRef()
+ const resetPlanReqRecoil = useResetRecoilState(planReqSearchState)
+
+ const [planReqSearch, setPlanReqSearch] = useRecoilState(planReqSearchState)
+ const [schPlanReqNo, setSchPlanReqNo] = useState('') //설계의뢰번호
+ const [schTitle, setSchTitle] = useState('') //안건명
+ const [schAddress, setSchAddress] = useState('') //도도부현
+ const [schSaleStoreName, setSchSaleStoreName] = useState('') //판매대리점명
+ const [schPlanReqName, setSchPlanReqName] = useState('') //의뢰자명
+ const [schPlanStatCd, setSchPlanStatCd] = useState('') //상태코드
+ const [schDateGbn, setSchDateGbn] = useState('S') //기간구분코드(S/R)
+
+ //초기화
+ const resetRecoil = () => {}
+
+ //초기화 눌렀을 때 자동완성도..
+ const handleClear = () => {
+ if (ref.current.state.dropDown) {
+ ref.current.methods.dropDown()
+ } else {
+ ref.current.state.values = []
+ }
+ }
+
+ useEffect(() => {
+ setStartDate(planReqSearch?.schStartDt ? planReqSearch.schStartDt : dayjs(new Date()).add(-3, 'month').format('YYYY-MM-DD'))
+ setEndDate(planReqSearch?.schEndDt ? planReqSearch.schEndDt : dayjs(new Date()).format('YYYY-MM-DD'))
+ }, [planReqSearch])
+
+ const [gridProps, setGridProps] = useState({
+ gridData: [],
+ isPageable: false,
+ gridColumns: [
+ {
+ field: 'planStatName',
+ headerName: getMessage('stuff.planReqPopup.gridHeader.planStatName'),
+ minWidth: 150,
+ checkboxSelection: true,
+ showDisabledCheckboxes: true,
+ },
+ {
+ field: 'planReqNo',
+ headerName: getMessage('stuff.planReqPopup.gridHeader.planReqNo'),
+ minWidth: 150,
+ },
+ {
+ field: 'saleStoreId',
+ headerName: getMessage('stuff.planReqPopup.gridHeader.saleStoreId'),
+ minWidth: 150,
+ },
+ {
+ field: 'saleStoreName',
+ headerName: getMessage('stuff.planReqPopup.gridHeader.saleStoreName'),
+ minWidth: 150,
+ },
+ {
+ field: 'title',
+ headerName: getMessage('stuff.planReqPopup.gridHeader.title'),
+ minWidth: 150,
+ },
+ {
+ field: 'address1',
+ headerName: getMessage('stuff.planReqPopup.gridHeader.address1'),
+ minWidth: 150,
+ },
+ {
+ field: 'houseCntName',
+ headerName: getMessage('stuff.planReqPopup.gridHeader.houseCntName'),
+ minWidth: 150,
+ },
+ {
+ field: 'planReqName',
+ headerName: getMessage('stuff.planReqPopup.gridHeader.planReqName'),
+ minWidth: 150,
+ },
+ {
+ field: 'submitDt',
+ headerName: getMessage('stuff.planReqPopup.gridHeader.submitDt'),
+ minWidth: 150,
+ },
+ ],
+ })
+
+ return (
+
+
+
+
+ {getMessage('stuff.planReqPopup.title')}
+
+
+
+
+
+ {getMessage('stuff.planReqPopup.popTitle')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/management/popup/PlanRequestPopQGrid.jsx b/src/components/management/popup/PlanRequestPopQGrid.jsx
new file mode 100644
index 00000000..b7dca164
--- /dev/null
+++ b/src/components/management/popup/PlanRequestPopQGrid.jsx
@@ -0,0 +1,56 @@
+import React from 'react'
+import { useCallback, useEffect, useMemo, useState } from 'react'
+import { AgGridReact } from 'ag-grid-react'
+
+import 'ag-grid-community/styles/ag-grid.css'
+import 'ag-grid-community/styles/ag-theme-quartz.css'
+
+export default function PlanRequestPopQGrid(props) {
+ const { gridData, gridColumns, isPageable = true } = props
+
+ const [rowData, setRowData] = useState(null)
+
+ const [gridApi, setGridApi] = useState(null)
+
+ const [colDefs, setColDefs] = useState(gridColumns)
+
+ const defaultColDef = useMemo(() => {
+ return {
+ flex: 1,
+ minWidth: 100,
+ sortable: false,
+ suppressMovable: false,
+ resizable: false,
+ suppressSizeToFit: false,
+ }
+ }, [])
+
+ const rowBuffer = 100
+
+ useEffect(() => {
+ gridData ? setRowData(gridData) : ''
+ }, [gridData])
+
+ const onGridReady = useCallback(
+ (params) => {
+ setGridApi(params.api)
+ gridData ? setRowData(gridData) : ''
+ },
+ [gridData],
+ )
+
+ return (
+
+ )
+}
diff --git a/src/components/management/popup/WindSelectPop.jsx b/src/components/management/popup/WindSelectPop.jsx
new file mode 100644
index 00000000..db1795f8
--- /dev/null
+++ b/src/components/management/popup/WindSelectPop.jsx
@@ -0,0 +1,115 @@
+import React, { useState, useEffect } from 'react'
+import { useMessage } from '@/hooks/useMessage'
+import { useAxios } from '@/hooks/useAxios'
+import { globalLocaleStore } from '@/store/localeAtom'
+import { useRecoilValue } from 'recoil'
+import { isEmptyArray, isNotEmptyArray } from '@/util/common-utils'
+export default function WindSelectPop(props) {
+ const globalLocaleState = useRecoilValue(globalLocaleStore)
+ const { promiseGet } = useAxios(globalLocaleState)
+ const [windSpeedList, setWindSpeedList] = useState([])
+ const [windSpeed, setWindSpeed] = useState(null)
+ const { getMessage } = useMessage()
+
+ //선택한 라디오 값 세팅
+ const handleChangeRadio = (e) => {
+ setWindSpeed(e.target.value)
+ }
+
+ //적용
+ const applyWindSpeed = () => {
+ if (windSpeed == null) {
+ alert(getMessage('stuff.windSelectPopup.error.message2'))
+ } else {
+ props.windSpeedInfo({ windSpeed: windSpeed })
+
+ //팝업닫기
+ props.setShowWindSpeedButtonValid(false)
+ }
+ }
+
+ useEffect(() => {
+ if (props.prefName !== '') {
+ promiseGet({ url: `/api/object/windSpeed/${props.prefName}/list` }).then((res) => {
+ if (res.status === 200) {
+ if (!isEmptyArray(res.data)) {
+ setWindSpeedList(res.data)
+ }
+ }
+ })
+ }
+ }, [props])
+
+ return (
+
+
+
+
+ {getMessage('stuff.windSelectPopup.title')}
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/myInfo/UserInfoModal.jsx b/src/components/myInfo/UserInfoModal.jsx
new file mode 100644
index 00000000..7643913c
--- /dev/null
+++ b/src/components/myInfo/UserInfoModal.jsx
@@ -0,0 +1,287 @@
+'use client'
+
+import { useState, useRef, useEffect } from 'react'
+import { useAxios } from '@/hooks/useAxios'
+import { useMessage } from '@/hooks/useMessage'
+
+export default function UserInfoModal({ userId, userInfoModal, setUserInfoModal }) {
+ const { getMessage } = useMessage()
+
+ // api 조회 관련
+ const { get, promisePatch, promisePost } = useAxios()
+ const [info, setInfo] = useState({
+ userId: '',
+ name: '',
+ nameKana: '',
+ category: '',
+ tel: '',
+ fax: '',
+ mail: '',
+ groupId: '',
+ groupName: '',
+ })
+
+ const [password, setPassword] = useState('')
+ const [showPwd, setShowPwd] = useState(false)
+ const [chkChgPwd, setChkChgPwd] = useState(false)
+ const [chgPwd, setChgPwd] = useState('')
+ const pwdInput = useRef()
+ const chgPwdInput = useRef()
+
+ useEffect(() => {
+ async function fetchData() {
+ const url = `/api/my-info/my-profile`
+ const params = new URLSearchParams({ userId: userId })
+
+ const apiUrl = `${url}?${params.toString()}`
+
+ const resultData = await get({ url: apiUrl })
+
+ if (resultData) {
+ setInfo(resultData)
+ } else {
+ alert(getMessage('common.message.no.data'))
+ }
+ }
+
+ if (userId) {
+ fetchData()
+ }
+ }, [])
+
+ const pattern = /^[\u0020-\u007E]{1,10}$/
+
+ // 비밀번호 변경
+ const handleChangePassword = async () => {
+ if (chgPwd === '') {
+ chgPwdInput.current.focus()
+ return alert(getMessage('myinfo.message.validation.password4'))
+ }
+ if (password === chgPwd) {
+ chgPwdInput.current.focus()
+ return alert(getMessage('myinfo.message.validation.password2'))
+ }
+ if (!pattern.test(chgPwd)) {
+ chgPwdInput.current.focus()
+ return alert(getMessage('myinfo.message.validation.password3'))
+ }
+
+ const params = { loginId: info.userId, chgType: 'C', pwd: password, chgPwd: chgPwd }
+
+ await promisePatch({ url: '/api/login/v1.0/user/change-password', data: params })
+ .then((res) => {
+ if (res) {
+ if (res.data.result.resultCode === 'S') {
+ alert(getMessage('myinfo.message.save'))
+ setChkChgPwd(false)
+ setPassword(chgPwd)
+ setChgPwd('')
+ } else {
+ alert(res.data.result.resultMsg)
+ }
+ }
+ })
+ .catch((error) => {
+ alert(error.response.data.message)
+ })
+ }
+
+ // 비밀번호 변경 버튼 클릭 시,
+ const checkPasswordProcess = async (e) => {
+ if (password === '') {
+ pwdInput.current.focus()
+ return alert(getMessage('myinfo.message.validation.password1'))
+ }
+
+ const param = {
+ loginId: userId,
+ pwd: password,
+ }
+ await promisePost({ url: '/api/login/v1.0/login', data: param })
+ .then((res) => {
+ if (res) {
+ if (res.data.result.resultCode === 'S') {
+ setChkChgPwd(true)
+ setTimeout(() => {
+ chgPwdInput.current.focus()
+ }, 10)
+ } else {
+ alert(getMessage('myinfo.message.password.error'))
+ setChkChgPwd(false)
+ setChgPwd('')
+ setTimeout(() => {
+ pwdInput.current.focus()
+ }, 10)
+ }
+ }
+ })
+ .catch((error) => {
+ alert(error.response.data.message)
+ })
+ }
+
+ return (
+ <>
+
+
+
+
+ {getMessage('myinfo.title')}
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/components/ui/QSelect.jsx b/src/components/ui/QSelect.jsx
deleted file mode 100644
index 198595d5..00000000
--- a/src/components/ui/QSelect.jsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { Select, SelectItem } from '@nextui-org/react'
-import styles from './QSelect.module.css'
-import { useEffect } from 'react'
-
-const animals = [
- { key: 'cat', label: 'Cat' },
- { key: 'dog', label: 'Dog' },
- { key: 'elephant', label: 'Elephant' },
- { key: 'lion', label: 'Lion' },
- { key: 'tiger', label: 'Tiger' },
- { key: 'giraffe', label: 'Giraffe' },
- { key: 'dolphin', label: 'Dolphin' },
- { key: 'penguin', label: 'Penguin' },
- { key: 'zebra', label: 'Zebra' },
- { key: 'shark', label: 'Shark' },
- { key: 'whale', label: 'Whale' },
- { key: 'otter', label: 'Otter' },
- { key: 'crocodile', label: 'Crocodile' },
-]
-
-export default function QSelect() {
- useEffect(() => {}, [])
- return (
- <>
-
-
-
- test
- >
- )
-}
diff --git a/src/hooks/object/useObjectBatch.js b/src/hooks/object/useObjectBatch.js
new file mode 100644
index 00000000..7229f470
--- /dev/null
+++ b/src/hooks/object/useObjectBatch.js
@@ -0,0 +1,415 @@
+'use client'
+import { useMessage } from '@/hooks/useMessage'
+import { useRecoilState, useRecoilValue } from 'recoil'
+import { canvasState } from '@/store/canvasAtom'
+import { INPUT_TYPE, BATCH_TYPE } from '@/common/common'
+import { useEvent } from '@/hooks/useEvent'
+import {
+ polygonToTurfPolygon,
+ rectToPolygon,
+ triangleToPolygon,
+ pointsToTurfPolygon,
+ splitDormerTriangle,
+ setSurfaceShapePattern,
+} from '@/util/canvas-util'
+import { useSwal } from '@/hooks/useSwal'
+import * as turf from '@turf/turf'
+import { QPolygon } from '@/components/fabric/QPolygon'
+import { drawDirectionArrow } from '@/util/qpolygon-utils'
+
+export function useObjectBatch() {
+ const { getMessage } = useMessage()
+ const canvas = useRecoilValue(canvasState)
+ const { addCanvasMouseEventListener, initEvent } = useEvent()
+ const { swalFire } = useSwal()
+
+ const applyOpeningAndShadow = (objectPlacement, buttonAct, surfaceShapePolygons) => {
+ const selectedType = objectPlacement.typeRef.current.find((radio) => radio.checked).value
+ const isCrossChecked = buttonAct === 1 ? objectPlacement.isCrossRef.current.checked : false
+ const objName = buttonAct === 1 ? BATCH_TYPE.OPENING : BATCH_TYPE.SHADOW
+ const objTempName = buttonAct === 1 ? BATCH_TYPE.OPENING_TEMP : BATCH_TYPE.SHADOW_TEMP
+
+ let rect, isDown, origX, origY
+ let selectedSurface
+
+ //프리입력
+ if (selectedType === INPUT_TYPE.FREE) {
+ addCanvasMouseEventListener('mouse:down', (e) => {
+ isDown = true
+ const pointer = canvas.getPointer(e.e)
+
+ surfaceShapePolygons.forEach((surface) => {
+ if (surface.inPolygon({ x: pointer.x, y: pointer.y })) {
+ selectedSurface = surface
+ }
+ })
+
+ if (!selectedSurface) {
+ swalFire({ text: '지붕안에 그려야해요', icon: 'error' })
+ initEvent() //이벤트 초기화
+ return
+ }
+
+ origX = pointer.x
+ origY = pointer.y
+
+ rect = new fabric.Rect({
+ left: origX,
+ top: origY,
+ originX: 'left',
+ originY: 'top',
+ width: 0,
+ height: 0,
+ angle: 0,
+ stroke: 'black',
+ })
+
+ //개구냐 그림자냐에 따라 변경
+ rect.set({
+ fill: buttonAct === 1 ? 'white' : 'rgba(0, 0, 0, 0.4)',
+ name: objTempName,
+ })
+
+ canvas?.add(rect)
+ })
+
+ addCanvasMouseEventListener('mouse:move', (e) => {
+ if (!isDown) return
+
+ if (selectedSurface) {
+ const pointer = canvas.getPointer(e.e)
+ const width = pointer.x - origX
+ const height = pointer.y - origY
+
+ rect.set({ width: Math.abs(width), height: Math.abs(height) })
+
+ if (width < 0) {
+ rect.set({ left: Math.abs(pointer.x) })
+ }
+ if (height < 0) {
+ rect.set({ top: Math.abs(pointer.y) })
+ }
+
+ canvas?.renderAll()
+ }
+ })
+
+ addCanvasMouseEventListener('mouse:up', (e) => {
+ if (rect) {
+ const rectPolygon = pointsToTurfPolygon(rectToPolygon(rect))
+ const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface)
+
+ //지붕 밖으로 그렸을때
+ if (!turf.booleanWithin(rectPolygon, selectedSurfacePolygon)) {
+ swalFire({ text: '개구를 배치할 수 없습니다.', icon: 'error' })
+ //일단 지워
+ deleteTempObjects()
+ return
+ }
+
+ if (!isCrossChecked) {
+ 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))
+
+ if (isCross) {
+ swalFire({ text: '겹치기 불가요...', icon: 'error' })
+ deleteTempObjects()
+ return
+ }
+ }
+
+ isDown = false
+ rect.set({ name: objName })
+ rect.setCoords()
+ initEvent()
+ }
+ })
+ } else if (selectedType === INPUT_TYPE.DIMENSION) {
+ const width = objectPlacement.widthRef.current.value / 10
+ const height = objectPlacement.heightRef.current.value / 10
+
+ if (width === '' || height === '' || width <= 0 || height <= 0) {
+ swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
+ return
+ }
+
+ // setShowObjectSettingModal(false) //메뉴보이고
+ addCanvasMouseEventListener('mouse:move', (e) => {
+ isDown = true
+ if (!isDown) return
+
+ canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === objTempName)) //움직일때 일단 지워가면서 움직임
+ const pointer = canvas.getPointer(e.e)
+
+ surfaceShapePolygons.forEach((surface) => {
+ if (surface.inPolygon({ x: pointer.x, y: pointer.y })) {
+ selectedSurface = surface
+ }
+ })
+
+ rect = new fabric.Rect({
+ fill: 'white',
+ stroke: 'black',
+ strokeWidth: 1,
+ width: width,
+ height: height,
+ left: pointer.x - width / 2,
+ top: pointer.y - height / 2,
+ selectable: true,
+ lockMovementX: true,
+ lockMovementY: true,
+ lockRotation: true,
+ lockScalingX: true,
+ lockScalingY: true,
+ })
+
+ //개구냐 그림자냐에 따라 변경
+ rect.set({
+ fill: buttonAct === 1 ? 'white' : 'rgba(0, 0, 0, 0.4)',
+ name: objTempName,
+ })
+
+ canvas?.add(rect)
+ })
+
+ addCanvasMouseEventListener('mouse:up', (e) => {
+ if (rect) {
+ const rectPolygon = pointsToTurfPolygon(rectToPolygon(rect))
+ const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface)
+
+ //지붕 밖으로 그렸을때
+ if (!turf.booleanWithin(rectPolygon, selectedSurfacePolygon)) {
+ swalFire({ text: '개구를 배치할 수 없습니다.', icon: 'error' })
+ //일단 지워
+ deleteTempObjects()
+ return
+ }
+
+ if (!isCrossChecked) {
+ 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))
+
+ if (isCross) {
+ swalFire({ text: '겹치기 불가요...', icon: 'error' })
+ deleteTempObjects()
+ return
+ }
+ }
+
+ isDown = false
+ rect.set({ name: objName })
+ rect.setCoords()
+ initEvent()
+ }
+ })
+ }
+ }
+
+ const applyDormers = (dormerPlacement, buttonAct, surfaceShapePolygons) => {
+ const dormerName = buttonAct === 3 ? BATCH_TYPE.TRIANGLE_DORMER : BATCH_TYPE.PENTAGON_DORMER
+ const dormerTempName = buttonAct === 3 ? BATCH_TYPE.TRIANGLE_DORMER_TEMP : BATCH_TYPE.PENTAGON_DORMER_TEMP
+ const height = dormerPlacement.heightRef.current.value / 10
+ const pitch = dormerPlacement.pitchRef.current.value
+ const directionRef = dormerPlacement.directionRef.current
+ const offsetRef = dormerPlacement.offsetRef.current.value === '' ? 0 : parseInt(dormerPlacement.offsetRef.current.value) / 10
+
+ let dormer, dormerOffset, isDown, selectedSurface
+
+ console.log('dormerPlacement', dormerPlacement)
+
+ if (height === '' || pitch === '' || height <= 0 || pitch <= 0) {
+ swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
+ return
+ }
+
+ //삼각형 도머
+ if (buttonAct === 3) {
+ const bottomLength = height / (pitch * 0.25)
+ const bottomOffsetLength = (height + offsetRef) / (pitch * 0.25)
+
+ console.log(bottomOffsetLength)
+
+ addCanvasMouseEventListener('mouse:move', (e) => {
+ isDown = true
+ if (!isDown) return
+
+ canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === dormerTempName)) //움직일때 일단 지워가면서 움직임
+ const pointer = canvas.getPointer(e.e)
+
+ surfaceShapePolygons.forEach((surface) => {
+ if (surface.inPolygon({ x: pointer.x, y: pointer.y })) {
+ selectedSurface = surface
+ }
+ })
+
+ let angle = 0
+ if (directionRef === 'left') {
+ //서
+ angle = 90
+ } else if (directionRef === 'right') {
+ //동
+ angle = 270
+ } else if (directionRef === 'up') {
+ //북
+ angle = 180
+ }
+
+ dormer = new fabric.Triangle({
+ fill: 'white',
+ stroke: 'red',
+ strokeDashArray: [5, 5],
+ strokeWidth: 1,
+ width: bottomLength * 2,
+ height: height,
+ left: pointer.x,
+ top: pointer.y,
+ selectable: true,
+ lockMovementX: true,
+ lockMovementY: true,
+ lockRotation: true,
+ lockScalingX: true,
+ lockScalingY: true,
+ name: dormerTempName,
+ originX: 'center',
+ originY: 'top',
+ angle: angle,
+ })
+ canvas?.add(dormer)
+
+ if (offsetRef > 0) {
+ dormerOffset = new fabric.Triangle({
+ fill: 'gray',
+ stroke: 'red',
+ strokeDashArray: [5, 5],
+ strokeWidth: 1,
+ width: bottomOffsetLength * 2,
+ height: height + offsetRef,
+ left: pointer.x,
+ top: pointer.y,
+ selectable: true,
+ lockMovementX: true,
+ lockMovementY: true,
+ lockRotation: true,
+ lockScalingX: true,
+ lockScalingY: true,
+ name: dormerTempName,
+ originX: 'center',
+ originY: 'top',
+ angle: angle,
+ })
+ canvas?.add(dormerOffset)
+ }
+ })
+
+ addCanvasMouseEventListener('mouse:up', (e) => {
+ if (dormer) {
+ // const trianglePolygon = pointsToTurfPolygon(triangleToPolygon(dormer))
+ // const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface)
+
+ // //지붕 밖으로 그렸을때
+ // if (!turf.booleanWithin(trianglePolygon, selectedSurfacePolygon)) {
+ // swalFire({ text: '개구를 배치할 수 없습니다.', icon: 'error' })
+ // //일단 지워
+ // deleteTempObjects()
+ // return
+ // }
+
+ //각도 추가
+ let originAngle = 0 //기본 남쪽
+ let direction = 'south'
+
+ if (directionRef === 'left') {
+ //서
+ originAngle = 90
+ direction = 'west'
+ } else if (directionRef === 'right') {
+ //동
+ originAngle = 270
+ direction = 'east'
+ } else if (directionRef === 'up') {
+ //북
+ originAngle = 180
+ direction = 'north'
+ }
+
+ let splitedTriangle = offsetRef > 0 ? splitDormerTriangle(dormerOffset, directionRef) : splitDormerTriangle(dormer, directionRef)
+ canvas?.remove(offsetRef > 0 ? dormerOffset : dormer)
+
+ if (offsetRef > 0)
+ dormer.set({
+ name: dormerName,
+ stroke: 'black',
+ strokeWidth: 1,
+ strokeDashArray: [0],
+ }) //오프셋이 있을땐 같이 도머로 만든다
+
+ const leftTriangle = new QPolygon(splitedTriangle[0], {
+ fill: 'transparent',
+ stroke: 'black',
+ strokeWidth: 1,
+ selectable: true,
+ lockMovementX: true, // X 축 이동 잠금
+ lockMovementY: true, // Y 축 이동 잠금
+ lockRotation: true, // 회전 잠금
+ viewLengthText: true,
+ fontSize: 14,
+ direction: direction,
+ originX: 'center',
+ originY: 'center',
+ name: dormerName,
+ })
+
+ const rightTriangle = new QPolygon(splitedTriangle[1], {
+ fill: 'transparent',
+ stroke: 'black',
+ strokeWidth: 1,
+ selectable: true,
+ lockMovementX: true, // X 축 이동 잠금
+ lockMovementY: true, // Y 축 이동 잠금
+ lockRotation: true, // 회전 잠금
+ viewLengthText: true,
+ fontSize: 14,
+ direction: direction,
+ originX: 'center',
+ originY: 'center',
+ name: dormerName,
+ })
+
+ canvas?.add(leftTriangle)
+ canvas?.add(rightTriangle)
+
+ //패턴
+ setSurfaceShapePattern(leftTriangle)
+ setSurfaceShapePattern(rightTriangle)
+ //방향
+ drawDirectionArrow(leftTriangle)
+ drawDirectionArrow(rightTriangle)
+
+ isDown = false
+ initEvent()
+ }
+ })
+ }
+ }
+
+ const deleteTempObjects = () => {
+ const deleteTarget = canvas
+ ?.getObjects()
+ .filter(
+ (obj) =>
+ obj.name === BATCH_TYPE.OPENING_TEMP ||
+ obj.name === BATCH_TYPE.SHADOW_TEMP ||
+ obj.name === BATCH_TYPE.TRIANGLE_DORMER_TEMP ||
+ obj.name === BATCH_TYPE.PENTAGON_DORMER_TEMP,
+ )
+ canvas?.remove(...deleteTarget)
+ initEvent() //이벤트 초기화
+ }
+
+ return {
+ applyOpeningAndShadow,
+ applyDormers,
+ }
+}
diff --git a/src/hooks/roofcover/useAuxiliaryDrawing.js b/src/hooks/roofcover/useAuxiliaryDrawing.js
new file mode 100644
index 00000000..cb33f407
--- /dev/null
+++ b/src/hooks/roofcover/useAuxiliaryDrawing.js
@@ -0,0 +1,661 @@
+import { useEffect, useRef, useState } from 'react'
+import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
+import {
+ adsorptionPointAddModeState,
+ adsorptionPointModeState,
+ adsorptionRangeState,
+ canvasState,
+ dotLineIntervalSelector,
+ verticalHorizontalModeState,
+} from '@/store/canvasAtom'
+import { useEvent } from '@/hooks/useEvent'
+import { useMouse } from '@/hooks/useMouse'
+import { useLine } from '@/hooks/useLine'
+import { useTempGrid } from '@/hooks/useTempGrid'
+import {
+ OUTER_LINE_TYPE,
+ outerLineAngle1State,
+ outerLineAngle2State,
+ outerLineArrow1State,
+ outerLineArrow2State,
+ outerLineDiagonalState,
+ outerLineLength1State,
+ outerLineLength2State,
+ outerLineTypeState,
+} from '@/store/outerLineAtom'
+import { calculateDistance, calculateIntersection, distanceBetweenPoints, findClosestPoint } from '@/util/canvas-util'
+import { fabric } from 'fabric'
+import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint'
+import { useSwal } from '@/hooks/useSwal'
+
+// 보조선 작성
+export function useAuxiliaryDrawing(setShowAuxiliaryModal) {
+ const canvas = useRecoilValue(canvasState)
+ const { addCanvasMouseEventListener, addDocumentEventListener, removeMouseLine, initEvent } = useEvent()
+ const { getIntersectMousePoint } = useMouse()
+ const { addLine, removeLine } = useLine()
+ const { tempGridMode } = useTempGrid()
+ const { swalFire } = useSwal()
+ const { getAdsorptionPoints } = useAdsorptionPoint()
+
+ const adsorptionRange = useRecoilValue(adsorptionRangeState)
+
+ const [buttonAct, setButtonAct] = useState(1)
+
+ const mousePointerArr = useRef([])
+ const roofAdsorptionPoints = useRef([])
+ const lineHistory = useRef([])
+
+ const length1Ref = useRef(0)
+ const length2Ref = useRef(0)
+ const angle1Ref = useRef('')
+ const angle2Ref = useRef('')
+ const [length1, setLength1] = useRecoilState(outerLineLength1State)
+ const [length2, setLength2] = useRecoilState(outerLineLength2State)
+ const [arrow1, setArrow1] = useRecoilState(outerLineArrow1State)
+ const [arrow2, setArrow2] = useRecoilState(outerLineArrow2State)
+ const [type, setType] = useRecoilState(outerLineTypeState)
+ const [angle1, setAngle1] = useRecoilState(outerLineAngle1State)
+ const [angle2, setAngle2] = useRecoilState(outerLineAngle2State)
+ const [outerLineDiagonalLength, setOuterLineDiagonalLength] = useRecoilState(outerLineDiagonalState)
+ const arrow1Ref = useRef(arrow1)
+ const arrow2Ref = useRef(arrow2)
+
+ const typeRef = useRef(type)
+
+ const outerLineDiagonalLengthRef = useRef(0)
+ const intersectionPoints = useRef([])
+
+ useEffect(() => {
+ arrow1Ref.current = arrow1
+ }, [arrow1])
+
+ useEffect(() => {
+ arrow2Ref.current = arrow2
+ }, [arrow2])
+
+ useEffect(() => {
+ typeRef.current = type
+ }, [type])
+
+ useEffect(() => {
+ // innerLines가 있을경우 삭제
+ const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roofBase')
+ if (roofs.length === 0) {
+ swalFire({ text: '지붕형상이 없습니다.' })
+ setShowAuxiliaryModal(false)
+ return
+ }
+
+ // 지붕의 각 꼭지점을 흡착점으로 설정
+ const roofsPoints = roofs.map((roof) => roof.points).flat()
+ roofAdsorptionPoints.current = [...roofsPoints]
+
+ addCanvasMouseEventListener('mouse:move', mouseMove)
+ addCanvasMouseEventListener('mouse:down', mouseDown)
+ addDocumentEventListener('contextmenu', document, cutAuxiliary)
+ addDocumentEventListener('keydown', document, keydown[type])
+
+ return () => {
+ canvas.remove(...canvas.getObjects().filter((obj) => obj.name === 'innerPoint'))
+ initEvent()
+ canvas.renderAll()
+ }
+ }, [])
+
+ useEffect(() => {
+ clear()
+ addDocumentEventListener('keydown', document, keydown[type])
+ }, [type])
+
+ const clear = () => {
+ setLength1(0)
+ setLength2(0)
+
+ setArrow1('')
+ setArrow2('')
+
+ setAngle1(0)
+ setAngle2(0)
+
+ setOuterLineDiagonalLength(0)
+ }
+
+ const keydown = {
+ outerLine: (e) => {
+ if (mousePointerArr.current.length === 0) {
+ return
+ }
+ // 포커스가 length1에 있지 않으면 length1에 포커스를 줌
+ const activeElem = document.activeElement
+ if (activeElem !== length1Ref.current) {
+ length1Ref.current.focus()
+ }
+
+ const key = e.key
+
+ if (!length1Ref.current) {
+ return
+ }
+
+ const lengthNum = Number(length1Ref.current.value) / 10
+ if (lengthNum === 0) {
+ return
+ }
+ const lastPoint = mousePointerArr.current[0]
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ setArrow1('↓')
+ mousePointerArr.current.push({ x: lastPoint.x, y: lastPoint.y + lengthNum })
+ drawLine()
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ setArrow1('↑')
+ mousePointerArr.current.push({ x: lastPoint.x, y: lastPoint.y - lengthNum })
+ drawLine()
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ setArrow1('←')
+ mousePointerArr.current.push({ x: lastPoint.x - lengthNum, y: lastPoint.y })
+ drawLine()
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ setArrow1('→')
+ mousePointerArr.current.push({ x: lastPoint.x + lengthNum, y: lastPoint.y })
+ drawLine()
+ break
+ }
+ },
+ rightAngle: (e) => {
+ if (mousePointerArr.current.length === 0) {
+ return
+ }
+ const key = e.key
+
+ const activeElem = document.activeElement
+ if (activeElem !== length1Ref.current && activeElem !== length2Ref.current) {
+ length1Ref.current.focus()
+ }
+
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ checkRightAngle('↓')
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ checkRightAngle('↑')
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ checkRightAngle('←')
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ checkRightAngle('→')
+ break
+ }
+ },
+ doublePitch: (e) => {
+ if (mousePointerArr.current.length === 0) {
+ return
+ }
+ const key = e.key
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ checkDoublePitch('↓')
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ checkDoublePitch('↑')
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ checkDoublePitch('←')
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ checkDoublePitch('→')
+ break
+ }
+ },
+ angle: (e) => {
+ if (mousePointerArr.current.length === 0) {
+ return
+ }
+ const key = e.key
+ switch (key) {
+ case 'Enter': {
+ const lastPoint = mousePointerArr.current[0]
+ const length = length1Ref.current.value / 10
+ const angle = angle1Ref.current.value
+ //lastPoint로부터 angle1만큼의 각도로 length1만큼의 길이를 가지는 선을 그림
+ const radian = (angle * Math.PI) / 180
+
+ const x = lastPoint.x + length * Math.cos(radian)
+ const y = lastPoint.y - length * Math.sin(radian)
+ return [...prev, { x, y }]
+ }
+ }
+ },
+ diagonalLine: (e) => {
+ if (mousePointerArr.current.length === 0) {
+ return
+ }
+
+ const key = e.key
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ checkDiagonal('↓')
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ checkDiagonal('↑')
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ checkDiagonal('←')
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ checkDiagonal('→')
+ break
+ }
+ },
+ }
+
+ // 직각 완료될 경우 확인
+ const checkRightAngle = (direction) => {
+ const activeElem = document.activeElement
+
+ const canDirection =
+ direction === '↓' || direction === '↑'
+ ? arrow1Ref.current === '←' || arrow1Ref.current === '→'
+ : arrow1Ref.current === '↓' || arrow1Ref.current === '↑'
+
+ if (activeElem === length1Ref.current || activeElem === angle1Ref.current) {
+ setArrow1(direction)
+ arrow1Ref.current = direction
+ length2Ref.current.focus()
+ } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) {
+ if (!canDirection) {
+ return
+ }
+ setArrow2(direction)
+ arrow2Ref.current = direction
+ }
+
+ const length1Num = Number(length1Ref.current.value) / 10
+ const length2Num = Number(length2Ref.current.value) / 10
+
+ if (mousePointerArr.current.length === 0) {
+ return
+ }
+
+ const lastPoint = mousePointerArr.current[0]
+
+ if (length1Num === 0 || length2Num === 0 || arrow1Ref.current === '' || arrow2Ref.current === '') {
+ return
+ }
+
+ if (arrow1Ref.current === '↓' && arrow2Ref.current === '→') {
+ mousePointerArr.current.push({ x: lastPoint.x + length2Num, y: lastPoint.y + length1Num })
+ } else if (arrow1Ref.current === '↓' && arrow2Ref.current === '←') {
+ mousePointerArr.current.push({ x: lastPoint.x - length2Num, y: lastPoint.y + length1Num })
+ } else if (arrow1Ref.current === '↑' && arrow2Ref.current === '→') {
+ mousePointerArr.current.push({ x: lastPoint.x + length2Num, y: lastPoint.y - length1Num })
+ } else if (arrow1Ref.current === '↑' && arrow2Ref.current === '←') {
+ mousePointerArr.current.push({ x: lastPoint.x - length2Num, y: lastPoint.y - length1Num })
+ } else if (arrow1Ref.current === '→' && arrow2Ref.current === '↓') {
+ mousePointerArr.current.push({ x: lastPoint.x + length1Num, y: lastPoint.y + length2Num })
+ } else if (arrow1Ref.current === '→' && arrow2Ref.current === '↑') {
+ mousePointerArr.current.push({ x: lastPoint.x + length1Num, y: lastPoint.y - length2Num })
+ } else if (arrow1Ref.current === '←' && arrow2Ref.current === '↓') {
+ mousePointerArr.current.push({ x: lastPoint.x - length1Num, y: lastPoint.y + length2Num })
+ } else if (arrow1Ref.current === '←' && arrow2Ref.current === '↑') {
+ mousePointerArr.current.push({ x: lastPoint.x - length1Num, y: lastPoint.y - length2Num })
+ }
+ drawLine()
+ }
+
+ //이구배 완료될 경우 확인 ↓, ↑, ←, →
+ const checkDoublePitch = (direction) => {
+ const activeElem = document.activeElement
+
+ const canDirection =
+ direction === '↓' || direction === '↑'
+ ? arrow1Ref.current === '←' || arrow1Ref.current === '→'
+ : arrow1Ref.current === '↓' || arrow1Ref.current === '↑'
+
+ if (activeElem === length1Ref.current || activeElem === angle1Ref.current) {
+ setArrow1(direction)
+ arrow1Ref.current = direction
+ angle2Ref.current.focus()
+ } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) {
+ if (!canDirection) {
+ return
+ }
+ setArrow2(direction)
+ arrow2Ref.current = direction
+ }
+
+ const angle1Value = Number(angle1Ref.current.value)
+ const angle2Value = Number(angle2Ref.current.value)
+ const length1Value = Number(length1Ref.current.value)
+ const length2Value = Number(length2Ref.current.value)
+
+ const arrow1Value = arrow1Ref.current
+ const arrow2Value = arrow2Ref.current
+ const lastPoint = mousePointerArr.current[0]
+
+ if (angle1Value !== 0 && length1Value !== 0 && angle2Value !== 0 && arrow1Value !== '' && arrow2Value !== '') {
+ if (arrow1Value === '↓' && arrow2Value === '→') {
+ mousePointerArr.current.push({ x: lastPoint.x + length1Value / 10, y: lastPoint.y + length2Value / 10 })
+ } else if (arrow1Value === '↓' && arrow2Value === '←') {
+ mousePointerArr.current.push({ x: lastPoint.x - length1Value / 10, y: lastPoint.y + length2Value / 10 })
+ } else if (arrow1Value === '↑' && arrow2Value === '→') {
+ mousePointerArr.current.push({ x: lastPoint.x + length1Value / 10, y: lastPoint.y - length2Value / 10 })
+ } else if (arrow1Value === '↑' && arrow2Value === '←') {
+ mousePointerArr.current.push({ x: lastPoint.x - length1Value / 10, y: lastPoint.y - length2Value / 10 })
+ } else if (arrow1Value === '→' && arrow2Value === '↓') {
+ mousePointerArr.current.push({ x: lastPoint.x + length2Value / 10, y: lastPoint.y + length1Value / 10 })
+ } else if (arrow1Value === '→' && arrow2Value === '↑') {
+ mousePointerArr.current.push({ x: lastPoint.x + length2Value / 10, y: lastPoint.y - length1Value / 10 })
+ } else if (arrow1Value === '←' && arrow2Value === '↓') {
+ mousePointerArr.current.push({ x: lastPoint.x - length2Value / 10, y: lastPoint.y + length1Value / 10 })
+ } else if (arrow1Value === '←' && arrow2Value === '↑') {
+ mousePointerArr.current.push({ x: lastPoint.x - length2Value / 10, y: lastPoint.y - length1Value / 10 })
+ }
+ drawLine()
+
+ angle1Ref.current.focus()
+ }
+ }
+
+ //대각선 완료될 경우 확인
+ const checkDiagonal = (direction) => {
+ const activeElem = document.activeElement
+ const diagonalLength = outerLineDiagonalLengthRef.current.value // 대각선 길이
+
+ const length1Value = length1Ref.current.value
+
+ if (diagonalLength <= length1Value) {
+ alert('대각선 길이는 직선 길이보다 길어야 합니다.')
+ return
+ }
+
+ const canDirection =
+ direction === '↓' || direction === '↑'
+ ? arrow1Ref.current === '←' || arrow1Ref.current === '→'
+ : arrow1Ref.current === '↓' || arrow1Ref.current === '↑'
+
+ if (activeElem === length1Ref.current) {
+ setArrow1(direction)
+ arrow1Ref.current = direction
+ } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) {
+ if (!canDirection) {
+ return
+ }
+ setArrow2(direction)
+ arrow2Ref.current = direction
+ }
+
+ const arrow1Value = arrow1Ref.current
+ const arrow2Value = arrow2Ref.current
+
+ const getLength2 = () => {
+ return Math.floor(Math.sqrt(diagonalLength ** 2 - length1Value ** 2))
+ }
+
+ const length2Value = getLength2()
+
+ if (diagonalLength !== 0 && length1Value !== 0 && arrow1Value !== '') {
+ setLength2(getLength2())
+ length2Ref.current.focus()
+ }
+
+ const lastPoint = mousePointerArr.current[0]
+
+ if (length1Value !== 0 && length2Value !== 0 && arrow1Value !== '' && arrow2Value !== '') {
+ if (arrow1Value === '↓' && arrow2Value === '→') {
+ mousePointerArr.current.push({ x: lastPoint.x + length2Value / 10, y: lastPoint.y + length1Value / 10 })
+ } else if (arrow1Value === '↓' && arrow2Value === '←') {
+ mousePointerArr.current.push({ x: lastPoint.x - length2Value / 10, y: lastPoint.y + length1Value / 10 })
+ } else if (arrow1Value === '↑' && arrow2Value === '→') {
+ mousePointerArr.current.push({ x: lastPoint.x + length2Value / 10, y: lastPoint.y - length1Value / 10 })
+ } else if (arrow1Value === '↑' && arrow2Value === '←') {
+ mousePointerArr.current.push({ x: lastPoint.x - length2Value / 10, y: lastPoint.y - length1Value / 10 })
+ } else if (arrow1Value === '→' && arrow2Value === '↓') {
+ mousePointerArr.current.push({ x: lastPoint.x + length1Value / 10, y: lastPoint.y + length2Value / 10 })
+ } else if (arrow1Value === '→' && arrow2Value === '↑') {
+ mousePointerArr.current.push({ x: lastPoint.x + length1Value / 10, y: lastPoint.y - length2Value / 10 })
+ }
+ drawLine()
+ }
+ }
+
+ const drawLine = () => {
+ canvas.remove(...canvas.getObjects().filter((obj) => obj.name === 'innerPoint'))
+ const line = addLine([mousePointerArr.current[0].x, mousePointerArr.current[0].y, mousePointerArr.current[1].x, mousePointerArr.current[1].y], {
+ stroke: 'black',
+ strokeWidth: 1,
+ selectable: false,
+ name: 'auxiliaryLine',
+ })
+
+ lineHistory.current.push(line)
+ mousePointerArr.current = []
+ clear()
+ }
+
+ const mouseDown = (e) => {
+ addCanvasMouseEventListener('mouse:move', mouseMove)
+ const pointer = getIntersectMousePoint(e)
+
+ mousePointerArr.current.push(pointer)
+ if (mousePointerArr.current.length === 2) {
+ drawLine(mousePointerArr.current[0], mousePointerArr.current[1])
+ } else {
+ const circle = new fabric.Circle({
+ radius: 3,
+ fill: 'red',
+ left: pointer.x - 3,
+ top: pointer.y - 3,
+ x: pointer.x,
+ y: pointer.y,
+ name: 'innerPoint',
+ selectable: true,
+ })
+ canvas.add(circle)
+ canvas.renderAll()
+ }
+ }
+
+ const mouseMove = (e) => {
+ removeMouseLine()
+ // 가로선
+ const pointer = canvas.getPointer(e.e)
+ const auxiliaryLines = canvas.getObjects().filter((obj) => obj.name === 'auxiliaryLine' && !obj.isFixed)
+
+ const otherAdsorptionPoints = []
+
+ auxiliaryLines.forEach((line1) => {
+ auxiliaryLines.forEach((line2) => {
+ if (line1 === line2) {
+ return
+ }
+
+ const intersectionPoint = calculateIntersection(line1, line2)
+ if (!intersectionPoint || intersectionPoints.current.some((point) => point.x === intersectionPoint.x && point.y === intersectionPoint.y)) {
+ return
+ }
+ otherAdsorptionPoints.push(intersectionPoint)
+ })
+ })
+
+ const adsorptionPoints = [...getAdsorptionPoints(), ...roofAdsorptionPoints.current, ...otherAdsorptionPoints, ...intersectionPoints.current]
+
+ let arrivalPoint = { x: pointer.x, y: pointer.y }
+
+ // pointer와 adsorptionPoints의 거리를 계산하여 가장 가까운 점을 찾는다.
+ let adsorptionPoint = findClosestPoint(pointer, adsorptionPoints)
+
+ if (adsorptionPoint && distanceBetweenPoints(pointer, adsorptionPoint) <= adsorptionRange) {
+ arrivalPoint = { ...adsorptionPoint }
+ }
+
+ const horizontalLine = new fabric.Line([-1 * canvas.width, arrivalPoint.y, 2 * canvas.width, arrivalPoint.y], {
+ stroke: 'red',
+ strokeWidth: 1,
+ selectable: false,
+ name: 'mouseLine',
+ })
+
+ // 세로선
+ const verticalLine = new fabric.Line([arrivalPoint.x, -1 * canvas.height, arrivalPoint.x, 2 * canvas.height], {
+ stroke: 'red',
+ strokeWidth: 1,
+ selectable: false,
+ name: 'mouseLine',
+ })
+
+ // 선들을 캔버스에 추가합니다.
+ canvas?.add(horizontalLine, verticalLine)
+
+ // 캔버스를 다시 그립니다.
+ canvas?.renderAll()
+ }
+
+ // 보조선 절삭
+ const cutAuxiliary = (e) => {
+ const auxiliaryLines = canvas.getObjects().filter((obj) => obj.name === 'auxiliaryLine' && !obj.isFixed)
+
+ if (auxiliaryLines.length === 0) {
+ return
+ }
+
+ auxiliaryLines.forEach((line1) => {
+ auxiliaryLines.forEach((line2) => {
+ const lines = [line1, line2]
+ if (line1 === line2) {
+ return
+ }
+
+ const intersectionPoint = calculateIntersection(line1, line2)
+ if (!intersectionPoint || intersectionPoints.current.some((point) => point.x === intersectionPoint.x && point.y === intersectionPoint.y)) {
+ return
+ }
+ roofAdsorptionPoints.current.push(intersectionPoint)
+ intersectionPoints.current.push(intersectionPoint)
+ lines.forEach((line) => {
+ const distance1 = distanceBetweenPoints({ x: line.x1, y: line.y1 }, intersectionPoint)
+ const distance2 = distanceBetweenPoints({ x: line.x2, y: line.y2 }, intersectionPoint)
+
+ if (distance1 === 0 || distance2 === 0) {
+ return
+ }
+ //historyLine에서 기존 line을 제거한다.
+ lineHistory.current = lineHistory.current.filter((history) => history !== line)
+
+ let newLine
+
+ if (distance1 >= distance2) {
+ newLine = addLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], {
+ stroke: 'black',
+ strokeWidth: 1,
+ selectable: false,
+ name: 'auxiliaryLine',
+ isFixed: true,
+ intersectionPoint,
+ })
+ } else {
+ newLine = addLine([line.x2, line.y2, intersectionPoint.x, intersectionPoint.y], {
+ stroke: 'black',
+ strokeWidth: 1,
+ selectable: false,
+ name: 'auxiliaryLine',
+ isFixed: true,
+ intersectionPoint,
+ })
+ }
+ lineHistory.current.push(newLine)
+ removeLine(line)
+ })
+ })
+ })
+ addCanvasMouseEventListener('mouse:move', mouseMove)
+ }
+
+ /**
+ * 일변전으로 돌아가기
+ */
+ const handleRollback = () => {
+ const lastLine = lineHistory.current.pop()
+ mousePointerArr.current = []
+ canvas.remove(...canvas.getObjects().filter((obj) => obj.name === 'innerPoint'))
+
+ if (lastLine) {
+ roofAdsorptionPoints.current = roofAdsorptionPoints.current.filter(
+ (point) => point.x !== lastLine.intersectionPoint?.x && point.y !== lastLine.intersectionPoint?.y,
+ )
+
+ canvas.remove(lastLine)
+ canvas.renderAll()
+ }
+
+ addCanvasMouseEventListener('mouse:move', mouseMove)
+ }
+
+ const handleFix = () => {
+ if (!confirm('지붕선 완료하시겠습니까?')) {
+ return
+ }
+
+ const roofBases = canvas.getObjects().find((obj) => obj.name === 'roofBase')
+ const innerLines = [...lineHistory.current]
+
+ roofBases.innerLines = [...innerLines]
+
+ setShowAuxiliaryModal(false)
+ }
+
+ return {
+ length1,
+ setLength1,
+ length2,
+ setLength2,
+ length1Ref,
+ length2Ref,
+ arrow1,
+ setArrow1,
+ arrow2,
+ setArrow2,
+ arrow1Ref,
+ arrow2Ref,
+ angle1,
+ setAngle1,
+ angle1Ref,
+ angle2,
+ setAngle2,
+ angle2Ref,
+ outerLineDiagonalLength,
+ setOuterLineDiagonalLength,
+ outerLineDiagonalLengthRef,
+ type,
+ setType,
+ handleFix,
+ handleRollback,
+ buttonAct,
+ setButtonAct,
+ }
+}
diff --git a/src/hooks/roofcover/useEavesGableEdit.js b/src/hooks/roofcover/useEavesGableEdit.js
new file mode 100644
index 00000000..083d8e03
--- /dev/null
+++ b/src/hooks/roofcover/useEavesGableEdit.js
@@ -0,0 +1,218 @@
+import { useEffect, useRef, useState } from 'react'
+import { useRecoilValue } from 'recoil'
+import { canvasState } from '@/store/canvasAtom'
+import { useMessage } from '@/hooks/useMessage'
+import { useEvent } from '@/hooks/useEvent'
+import { LINE_TYPE } from '@/common/common'
+import { useLine } from '@/hooks/useLine'
+import { useMode } from '@/hooks/useMode'
+import { outerLineFixState } from '@/store/outerLineAtom'
+import { useSwal } from '@/hooks/useSwal'
+
+// 처마.케라바 변경
+export function useEavesGableEdit(setShowEavesGableEditModal) {
+ const canvas = useRecoilValue(canvasState)
+ const { getMessage } = useMessage()
+ const { addCanvasMouseEventListener, initEvent } = useEvent()
+ const TYPES = {
+ EAVES: 'eaves',
+ GABLE: 'gable',
+ WALL_MERGE: 'wall.merge',
+ SHED: 'shed',
+ }
+ const [type, setType] = useState(TYPES.EAVES)
+ const typeRef = useRef(TYPES.EAVES)
+ const { removeLine } = useLine()
+ const { swalFire } = useSwal()
+
+ const { drawRoofPolygon } = useMode()
+
+ const pitchRef = useRef(null)
+ const offsetRef = useRef(null)
+ const widthRef = useRef(null)
+ const radioTypeRef = useRef('1') // 각 페이지에서 사용하는 radio type
+ const outerLineFix = useRecoilValue(outerLineFixState)
+
+ const buttonMenu = [
+ { id: 1, name: getMessage('eaves'), type: TYPES.EAVES },
+ { id: 2, name: getMessage('gable'), type: TYPES.GABLE },
+ { id: 3, name: getMessage('wall.merge'), type: TYPES.WALL_MERGE },
+ { id: 4, name: getMessage('shed'), type: TYPES.SHED },
+ ]
+
+ useEffect(() => {
+ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ if (!outerLineFix || outerLines.length === 0) {
+ swalFire({ text: '외벽선이 없습니다.' })
+ setShowEavesGableEditModal(false)
+ }
+ }, [])
+
+ useEffect(() => {
+ const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine')
+ wallLines.forEach((wallLine) => {
+ convertPolygonToLines(wallLine)
+ })
+
+ addCanvasMouseEventListener('mouse:over', mouseOverEvent)
+ addCanvasMouseEventListener('mouse:down', mouseDownEvent)
+
+ return () => {
+ canvas.discardActiveObject()
+ wallLines.forEach((wallLine) => {
+ convertLinesToPolygon(wallLine)
+ })
+ initEvent()
+ }
+ }, [])
+
+ useEffect(() => {
+ typeRef.current = type
+ }, [type])
+
+ const mouseOverEvent = (e) => {
+ if (e.target && e.target.name === 'outerLine') {
+ e.target.set({
+ stroke: 'red',
+ })
+ canvas.renderAll()
+ } else {
+ canvas
+ ?.getObjects()
+ .filter((obj) => obj.name === 'outerLine')
+ .forEach((line) => {
+ line.set({
+ stroke: 'black',
+ })
+ })
+ }
+ canvas.renderAll()
+ }
+
+ const mouseDownEvent = (e) => {
+ if (!e.target || (e.target && e.target.name !== 'outerLine')) {
+ return
+ }
+
+ const target = e.target
+
+ let attributes = target.get('attributes')
+
+ switch (typeRef.current) {
+ case TYPES.EAVES:
+ if (radioTypeRef.current === '1') {
+ attributes = {
+ type: LINE_TYPE.WALLLINE.EAVES,
+ pitch: pitchRef.current.value,
+ offset: offsetRef.current.value / 10,
+ }
+ } else {
+ attributes = {
+ type: LINE_TYPE.WALLLINE.HIPANDGABLE,
+ pitch: pitchRef.current.value,
+ offset: offsetRef.current.value / 10,
+ width: widthRef.current.value / 10,
+ }
+ }
+ break
+ case TYPES.GABLE:
+ if (radioTypeRef.current === '1') {
+ attributes = {
+ type: LINE_TYPE.WALLLINE.GABLE,
+ offset: offsetRef.current.value / 10,
+ }
+ } else {
+ attributes = {
+ type: LINE_TYPE.WALLLINE.JERKINHEAD,
+ pitch: pitchRef.current.value,
+ offset: offsetRef.current.value / 10,
+ width: widthRef.current.value / 10,
+ }
+ }
+ break
+ case TYPES.WALL_MERGE:
+ if (radioTypeRef.current === '1') {
+ attributes = {
+ type: LINE_TYPE.WALLLINE.WALL,
+ offset: 0,
+ }
+ } else {
+ attributes = {
+ type: LINE_TYPE.WALLLINE.WALL,
+ offset: offsetRef.current.value / 10,
+ }
+ }
+ break
+ case TYPES.SHED:
+ attributes = {
+ type: LINE_TYPE.WALLLINE.SHED,
+ offset: offsetRef.current.value / 10,
+ }
+ break
+ }
+
+ target.set({
+ attributes,
+ })
+
+ const roofBases = canvas?.getObjects().filter((obj) => obj.name === 'roofBase')
+
+ roofBases.forEach((roof) => {
+ roof.innerLines.forEach((line) => {
+ removeLine(line)
+ })
+ canvas.remove(roof)
+ })
+
+ const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine')
+
+ wallLines.forEach((wallLine) => {
+ const roof = drawRoofPolygon(wallLine)
+
+ canvas?.renderAll()
+ roof.drawHelpLine()
+ })
+
+ wallLines.forEach((wallLine) => {
+ convertPolygonToLines(wallLine)
+ })
+
+ addCanvasMouseEventListener('mouse:over', mouseOverEvent)
+ addCanvasMouseEventListener('mouse:down', mouseDownEvent)
+ }
+
+ // polygon의 lines를 이용해 line으로 변경하기
+ const convertPolygonToLines = (polygon) => {
+ polygon.set({ visible: false })
+ polygon.lines.forEach((line) => {
+ line.set({ visible: true })
+ line.set({ selectable: true })
+ line.set({ strokeWidth: 5 })
+ line.set({ parent: polygon })
+ line.bringToFront()
+ })
+
+ // canvas objects에서 polygon.lines를 제외한 다른 line의 selectable을 false로 변경
+ canvas
+ .getObjects()
+ .filter((obj) => obj.name !== 'outerLine' && obj.type === 'QLine')
+ .forEach((obj) => {
+ obj.set({ selectable: false })
+ })
+
+ canvas?.renderAll()
+ }
+
+ // 다시 다각형으로 변경하기
+ const convertLinesToPolygon = (polygon) => {
+ polygon.set({ visible: true })
+ polygon.lines.forEach((line) => {
+ line.set({ visible: false })
+ line.set({ selectable: false })
+ })
+
+ canvas?.renderAll()
+ }
+
+ return { type, setType, buttonMenu, TYPES, pitchRef, offsetRef, widthRef, radioTypeRef }
+}
diff --git a/src/hooks/roofcover/useOuterLineWall.js b/src/hooks/roofcover/useOuterLineWall.js
index 355eca16..07dab81e 100644
--- a/src/hooks/roofcover/useOuterLineWall.js
+++ b/src/hooks/roofcover/useOuterLineWall.js
@@ -1,11 +1,12 @@
import { useEffect, useRef } from 'react'
-import { distanceBetweenPoints } from '@/util/canvas-util'
-import { useRecoilState, useRecoilValue } from 'recoil'
+import { distanceBetweenPoints, getDegreeByChon } from '@/util/canvas-util'
+import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
import {
adsorptionPointAddModeState,
adsorptionPointModeState,
adsorptionRangeState,
canvasState,
+ currentCanvasPlanState,
dotLineIntervalSelector,
verticalHorizontalModeState,
} from '@/store/canvasAtom'
@@ -16,16 +17,21 @@ import { useTempGrid } from '@/hooks/useTempGrid'
import { usePolygon } from '@/hooks/usePolygon'
import {
outerLineAngle1State,
+ outerLineAngle2State,
outerLineArrow1State,
outerLineArrow2State,
+ outerLineDiagonalState,
+ outerLineFixState,
outerLineLength1State,
outerLineLength2State,
outerLinePointsState,
outerLineTypeState,
} from '@/store/outerLineAtom'
import { calculateAngle } from '@/util/qpolygon-utils'
+import { fabric } from 'fabric'
-export function useOuterLineWall() {
+//외벽선 그리기
+export function useOuterLineWall(setShowOutlineModal) {
const canvas = useRecoilValue(canvasState)
const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent } =
useEvent()
@@ -33,6 +39,7 @@ export function useOuterLineWall() {
const { addLine, removeLine } = useLine()
const { tempGridMode } = useTempGrid()
const { addPolygonByLines } = usePolygon()
+
const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState)
const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState)
const adsorptionPointMode = useRecoilValue(adsorptionPointModeState)
@@ -42,28 +49,32 @@ export function useOuterLineWall() {
const length1Ref = useRef(null)
const length2Ref = useRef(null)
const angle1Ref = useRef(null)
+ const angle2Ref = useRef(null)
const [length1, setLength1] = useRecoilState(outerLineLength1State)
const [length2, setLength2] = useRecoilState(outerLineLength2State)
const [arrow1, setArrow1] = useRecoilState(outerLineArrow1State)
const [arrow2, setArrow2] = useRecoilState(outerLineArrow2State)
const [points, setPoints] = useRecoilState(outerLinePointsState)
const [type, setType] = useRecoilState(outerLineTypeState)
+ const [angle1, setAngle1] = useRecoilState(outerLineAngle1State)
+ const [angle2, setAngle2] = useRecoilState(outerLineAngle2State)
+ const [outerLineDiagonalLength, setOuterLineDiagonalLength] = useRecoilState(outerLineDiagonalState)
const arrow1Ref = useRef(arrow1)
const arrow2Ref = useRef(arrow2)
+ const setOuterLineFix = useSetRecoilState(outerLineFixState)
+
+ const outerLineDiagonalLengthRef = useRef(null)
+
const isFix = useRef(false)
- const [angle1, setAngle1] = useRecoilState(outerLineAngle1State)
useEffect(() => {
if (adsorptionPointAddMode || tempGridMode) {
return
}
- removeMouseEvent('mouse:down', mouseDown)
+
addCanvasMouseEventListener('mouse:down', mouseDown)
clear()
- return () => {
- removeAllMouseEventListeners()
- }
}, [verticalHorizontalMode, points, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, interval, tempGridMode])
useEffect(() => {
@@ -75,9 +86,8 @@ export function useOuterLineWall() {
}, [arrow2])
useEffect(() => {
- removeAllDocumentEventListeners()
- addDocumentEventListener('keydown', document, keydown[type])
clear()
+ addDocumentEventListener('keydown', document, keydown[type])
}, [type])
const clear = () => {
@@ -88,6 +98,9 @@ export function useOuterLineWall() {
setArrow2('')
setAngle1(0)
+ setAngle2(0)
+
+ setOuterLineDiagonalLength(0)
}
const mouseDown = (e) => {
@@ -143,14 +156,14 @@ export function useOuterLineWall() {
canvas?.remove(canvas?.getObjects().find((obj) => obj.name === 'startPoint'))
- // point가 변경 될때마다 이벤트 리스너를 제거하고 다시 등록
- removeAllDocumentEventListeners()
- addDocumentEventListener('keydown', document, keydown[type])
-
if (points.length === 0) {
+ setOuterLineFix(true)
+ removeAllDocumentEventListeners()
return
}
+ addDocumentEventListener('keydown', document, keydown[type])
+
if (points.length === 1) {
const point = new fabric.Circle({
radius: 5,
@@ -164,6 +177,22 @@ export function useOuterLineWall() {
canvas?.add(point)
} else {
+ setOuterLineFix(false)
+ canvas
+ .getObjects()
+ .filter((obj) => obj.name === 'outerLinePoint')
+ .forEach((obj) => {
+ canvas.remove(obj)
+ })
+ points.forEach((point, idx) => {
+ const circle = new fabric.Circle({
+ left: point.x,
+ top: point.y,
+ visible: false,
+ name: 'outerLinePoint',
+ })
+ canvas.add(circle)
+ })
points.forEach((point, idx) => {
if (idx === 0) {
return
@@ -174,6 +203,14 @@ export function useOuterLineWall() {
const lastPoint = points[points.length - 1]
const firstPoint = points[0]
+ if (isFix.current) {
+ removeAllMouseEventListeners()
+ removeAllDocumentEventListeners()
+ canvas?.renderAll()
+ setOuterLineFix(true)
+ setShowOutlineModal(false)
+ }
+
if (points.length < 3) {
return
}
@@ -241,27 +278,36 @@ export function useOuterLineWall() {
stroke: 'black',
strokeWidth: 3,
idx: idx,
- selectable: false,
+ selectable: true,
name: 'outerLine',
+ x1: point1.x,
+ y1: point1.y,
+ x2: point2.x,
+ y2: point2.y,
})
}
- const settingLine = () => {
- const outerLines = canvas?.getObjects().filter((obj) => obj.name === 'outerLine')
- outerLines.forEach((line) => {
- removeLine(line)
- })
-
- addPolygonByLines(outerLines, {
- fill: 'rgba(0,0,0,0)',
- stroke: 'black',
- strokeWidth: 3,
- })
- setShowOutlineModal(false)
- }
-
// 직각 완료될 경우 확인
- const checkRightAngle = () => {
+ const checkRightAngle = (direction) => {
+ const activeElem = document.activeElement
+
+ const canDirection =
+ direction === '↓' || direction === '↑'
+ ? arrow1Ref.current === '←' || arrow1Ref.current === '→'
+ : arrow1Ref.current === '↓' || arrow1Ref.current === '↑'
+
+ if (activeElem === length1Ref.current || activeElem === angle1Ref.current) {
+ setArrow1(direction)
+ arrow1Ref.current = direction
+ length2Ref.current.focus()
+ } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) {
+ if (!canDirection) {
+ return
+ }
+ setArrow2(direction)
+ arrow2Ref.current = direction
+ }
+
const length1Num = Number(length1Ref.current.value) / 10
const length2Num = Number(length2Ref.current.value) / 10
@@ -332,6 +378,193 @@ export function useOuterLineWall() {
}
}
+ //이구배 완료될 경우 확인 ↓, ↑, ←, →
+ const checkDoublePitch = (direction) => {
+ const activeElem = document.activeElement
+
+ const canDirection =
+ direction === '↓' || direction === '↑'
+ ? arrow1Ref.current === '←' || arrow1Ref.current === '→'
+ : arrow1Ref.current === '↓' || arrow1Ref.current === '↑'
+
+ if (activeElem === length1Ref.current || activeElem === angle1Ref.current) {
+ setArrow1(direction)
+ arrow1Ref.current = direction
+ angle2Ref.current.focus()
+ } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) {
+ if (!canDirection) {
+ return
+ }
+ setArrow2(direction)
+ arrow2Ref.current = direction
+ }
+
+ const angle1Value = angle1Ref.current.value
+ const angle2Value = angle2Ref.current.value
+ const length1Value = length1Ref.current.value
+ const length2Value = length2Ref.current.value
+
+ const arrow1Value = arrow1Ref.current
+ const arrow2Value = arrow2Ref.current
+
+ if (angle1Value !== 0 && length1Value !== 0 && angle2Value !== 0 && arrow1Value !== '' && arrow2Value !== '') {
+ if (arrow1Value === '↓' && arrow2Value === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }]
+ })
+ } else if (arrow1Value === '↓' && arrow2Value === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }]
+ })
+ } else if (arrow1Value === '↑' && arrow2Value === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10 }]
+ })
+ } else if (arrow1Value === '↑' && arrow2Value === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10 }]
+ })
+ } else if (arrow1Value === '→' && arrow2Value === '↓') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }]
+ })
+ } else if (arrow1Value === '→' && arrow2Value === '↑') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }]
+ })
+ } else if (arrow1Value === '←' && arrow2Value === '↓') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }]
+ })
+ } else if (arrow1Value === '←' && arrow2Value === '↑') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }]
+ })
+ }
+
+ angle1Ref.current.focus()
+ }
+ }
+
+ //대각선 완료될 경우 확인
+ const checkDiagonal = (direction) => {
+ const activeElem = document.activeElement
+ const diagonalLength = outerLineDiagonalLengthRef.current.value // 대각선 길이
+
+ const length1Value = length1Ref.current.value
+
+ if (diagonalLength <= length1Value) {
+ alert('대각선 길이는 직선 길이보다 길어야 합니다.')
+ return
+ }
+
+ const canDirection =
+ direction === '↓' || direction === '↑'
+ ? arrow1Ref.current === '←' || arrow1Ref.current === '→'
+ : arrow1Ref.current === '↓' || arrow1Ref.current === '↑'
+
+ if (activeElem === length1Ref.current) {
+ setArrow1(direction)
+ arrow1Ref.current = direction
+ } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) {
+ if (!canDirection) {
+ return
+ }
+ setArrow2(direction)
+ arrow2Ref.current = direction
+ }
+
+ const arrow1Value = arrow1Ref.current
+ const arrow2Value = arrow2Ref.current
+
+ const getLength2 = () => {
+ return Math.floor(Math.sqrt(diagonalLength ** 2 - length1Value ** 2))
+ }
+
+ const length2Value = getLength2()
+
+ if (diagonalLength !== 0 && length1Value !== 0 && arrow1Value !== '') {
+ setLength2(getLength2())
+ length2Ref.current.focus()
+ }
+
+ if (length1Value !== 0 && length2Value !== 0 && arrow1Value !== '' && arrow2Value !== '') {
+ if (arrow1Value === '↓' && arrow2Value === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }]
+ })
+ } else if (arrow1Value === '↓' && arrow2Value === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }]
+ })
+ } else if (arrow1Value === '↑' && arrow2Value === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }]
+ })
+ } else if (arrow1Value === '↑' && arrow2Value === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }]
+ })
+ } else if (arrow1Value === '→' && arrow2Value === '↓') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }]
+ })
+ } else if (arrow1Value === '→' && arrow2Value === '↑') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [
+ ...prev,
+ {
+ x: prev[prev.length - 1].x + length1Value / 10,
+ y: prev[prev.length - 1].y - length2Value / 10,
+ },
+ ]
+ })
+ }
+ }
+ }
+
const keydown = {
outerLine: (e) => {
if (points.length === 0) {
@@ -411,75 +644,54 @@ export function useOuterLineWall() {
switch (key) {
case 'Down': // IE/Edge에서 사용되는 값
case 'ArrowDown': {
- if (activeElem === length1Ref.current) {
- setArrow1('↓')
- arrow1Ref.current = '↓'
- length2Ref.current.focus()
- } else if (activeElem === length2Ref.current) {
- if (arrow1Ref.current === '↓' || arrow1Ref.current === '↑') {
- break
- }
- setArrow2('↓')
- arrow2Ref.current = '↓'
- checkRightAngle()
- }
-
+ checkRightAngle('↓')
break
}
case 'Up': // IE/Edge에서 사용되는 값
case 'ArrowUp':
- if (activeElem === length1Ref.current) {
- setArrow1('↑')
- arrow1Ref.current = '↑'
- length2Ref.current.focus()
- } else if (activeElem === length2Ref.current) {
- if (arrow1Ref.current === '↓' || arrow1Ref.current === '↑') {
- break
- }
- setArrow2('↑')
- arrow2Ref.current = '↑'
- checkRightAngle()
- }
-
+ checkRightAngle('↑')
break
case 'Left': // IE/Edge에서 사용되는 값
case 'ArrowLeft':
- if (activeElem === length1Ref.current) {
- setArrow1('←')
- arrow1Ref.current = '←'
- length2Ref.current.focus()
- } else if (activeElem === length2Ref.current) {
- if (arrow1Ref.current === '←' || arrow1Ref.current === '→') {
- break
- }
- setArrow2('←')
- arrow2Ref.current = '←'
- checkRightAngle()
- }
-
+ checkRightAngle('←')
break
case 'Right': // IE/Edge에서 사용되는 값
case 'ArrowRight':
- if (activeElem === length1Ref.current) {
- setArrow1('→')
- arrow1Ref.current = '→'
- length2Ref.current.focus()
- } else if (activeElem === length2Ref.current) {
- if (arrow1Ref.current === '←' || arrow1Ref.current === '→') {
- break
- }
- setArrow2('→')
- arrow2Ref.current = '→'
- checkRightAngle()
- }
-
+ checkRightAngle('→')
+ break
+ case 'Enter':
break
}
},
- leeGubae: (e) => {
- console.log('leegubae')
+ doublePitch: (e) => {
+ if (points.length === 0) {
+ return
+ }
+ const key = e.key
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ checkDoublePitch('↓')
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ checkDoublePitch('↑')
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ checkDoublePitch('←')
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ checkDoublePitch('→')
+ break
+ }
},
angle: (e) => {
+ if (points.length === 0) {
+ return
+ }
const key = e.key
switch (key) {
case 'Enter': {
@@ -501,7 +713,30 @@ export function useOuterLineWall() {
}
},
diagonalLine: (e) => {
- console.log('diagonalLine')
+ if (points.length === 0) {
+ return
+ }
+
+ const key = e.key
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ checkDiagonal('↓')
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ checkDiagonal('↑')
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ checkDiagonal('←')
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ checkDiagonal('→')
+ break
+ }
},
}
@@ -541,6 +776,8 @@ export function useOuterLineWall() {
setPoints((prev) => {
return [...prev, { x: prev[0].x, y: prev[0].y }]
})
+
+ isFix.current = true
}
return {
@@ -558,6 +795,15 @@ export function useOuterLineWall() {
setArrow2,
arrow1Ref,
arrow2Ref,
+ angle1,
+ setAngle1,
+ angle1Ref,
+ angle2,
+ setAngle2,
+ angle2Ref,
+ outerLineDiagonalLength,
+ setOuterLineDiagonalLength,
+ outerLineDiagonalLengthRef,
type,
setType,
handleFix,
diff --git a/src/hooks/roofcover/usePropertiesSetting.js b/src/hooks/roofcover/usePropertiesSetting.js
new file mode 100644
index 00000000..ad703356
--- /dev/null
+++ b/src/hooks/roofcover/usePropertiesSetting.js
@@ -0,0 +1,168 @@
+import { useEffect, useRef } from 'react'
+import { LINE_TYPE } from '@/common/common'
+import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
+import { canvasState, currentObjectState } from '@/store/canvasAtom'
+import { useMode } from '@/hooks/useMode'
+import { usePolygon } from '@/hooks/usePolygon'
+import { useLine } from '@/hooks/useLine'
+import { outerLinePointsState } from '@/store/outerLineAtom'
+
+// 외벽선 속성 설정
+export function usePropertiesSetting(setShowPropertiesSettingModal) {
+ const canvas = useRecoilValue(canvasState)
+
+ const currentObject = useRecoilValue(currentObjectState)
+
+ const { drawRoofPolygon } = useMode()
+ const setPoints = useResetRecoilState(outerLinePointsState)
+
+ const { addPolygonByLines } = usePolygon()
+ const { removeLine, hideLine } = useLine()
+
+ useEffect(() => {
+ const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ lines.forEach((line) => {
+ const lineType = line.attributes?.type
+ if (!lineType) {
+ line.set({
+ stroke: '#000000',
+ strokeWidth: 4,
+ })
+ }
+ })
+ if (!currentObject) {
+ return
+ }
+ if (currentObject.name !== 'outerLine') {
+ return
+ }
+
+ const type = currentObject.attributes?.type
+
+ if (!type) {
+ currentObject.set({
+ stroke: '#EA10AC',
+ strokeWidth: 4,
+ })
+ }
+
+ canvas.renderAll()
+ }, [currentObject])
+
+ const history = useRef([])
+
+ const handleSetEaves = () => {
+ const selectedLine = canvas?.getActiveObject()
+ if (!selectedLine) {
+ return
+ }
+ selectedLine.set({
+ stroke: '#45CD7D',
+ strokeWidth: 4,
+ attributes: {
+ offset: 50,
+ type: LINE_TYPE.WALLLINE.EAVES,
+ pitch: 4,
+ },
+ })
+ history.current.push(selectedLine)
+ nextLineFocus(selectedLine)
+ canvas.renderAll()
+ }
+
+ const handleSetGable = () => {
+ const selectedLine = canvas?.getActiveObject()
+ if (!selectedLine) {
+ return
+ }
+ selectedLine.set({
+ stroke: '#3FBAE6',
+ strokeWidth: 4,
+ attributes: {
+ offset: 30,
+ type: LINE_TYPE.WALLLINE.GABLE,
+ },
+ })
+ history.current.push(selectedLine)
+ nextLineFocus(selectedLine)
+ canvas.renderAll()
+ }
+
+ const nextLineFocus = (selectedLine) => {
+ const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ const index = lines.findIndex((line) => line === selectedLine)
+
+ const nextLine = lines[index + 1] || lines[0]
+ if (!nextLine.attributes?.type) {
+ canvas.setActiveObject(nextLine)
+ } else {
+ //activeObject 해제
+ canvas.discardActiveObject()
+ }
+ }
+
+ const handleRollback = () => {
+ if (history.current.length === 0) {
+ return
+ }
+ const lastLine = history.current.pop()
+
+ delete lastLine.attributes
+ lastLine.set({
+ stroke: '#000000',
+ strokeWidth: 4,
+ })
+
+ canvas.setActiveObject(lastLine)
+ canvas.renderAll()
+ }
+
+ const handleFix = () => {
+ if (!confirm('외벽선 속성 설정을 완료하시겠습니까?')) {
+ return
+ }
+ const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+
+ lines.forEach((line) => {
+ line.set({
+ attributes: line.attributes ? line.attributes : { offset: 0, type: LINE_TYPE.WALLLINE.WALL },
+ stroke: '#000000',
+ strokeWidth: 4,
+ })
+
+ hideLine(line)
+ })
+
+ const wall = addPolygonByLines(lines, { name: 'wallLine', fill: 'transparent', stroke: 'black' })
+
+ wall.lines = [...lines]
+
+ const roof = drawRoofPolygon(wall)
+
+ setPoints([])
+ canvas.renderAll()
+ roof.drawHelpLine()
+ }
+
+ const closeModal = (fn) => {
+ if (!confirm('외벽선 속성 설정을 종료 하시겠습니까?')) {
+ return
+ }
+
+ const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+
+ lines.forEach((line) => {
+ line.set({
+ attributes: { offset: 0, type: LINE_TYPE.WALLLINE.WALL },
+ stroke: '#000000',
+ strokeWidth: 4,
+ })
+ })
+
+ canvas.renderAll()
+ setPoints([])
+ setShowPropertiesSettingModal(false)
+ }
+
+ return { handleSetEaves, handleSetGable, handleRollback, handleFix, closeModal }
+}
diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js
new file mode 100644
index 00000000..6533fe79
--- /dev/null
+++ b/src/hooks/roofcover/useRoofAllocationSetting.js
@@ -0,0 +1,139 @@
+import { useRecoilValue } from 'recoil'
+import { canvasState } from '@/store/canvasAtom'
+import { useEffect, useState } from 'react'
+import { setSurfaceShapePattern } from '@/util/canvas-util'
+import { drawDirectionArrow, splitPolygonWithLines } from '@/util/qpolygon-utils'
+import { useSwal } from '@/hooks/useSwal'
+
+// 지붕면 할당
+export function useRoofAllocationSetting(setShowRoofAllocationSettingModal) {
+ const canvas = useRecoilValue(canvasState)
+
+ const { swalFire } = useSwal()
+
+ const roofMaterials = [
+ {
+ id: 'A',
+ name: '기와1',
+ type: 'A',
+ width: '200',
+ length: '200',
+ alignType: 'parallel',
+ },
+ {
+ id: 'B',
+ name: '기와2',
+ type: 'B',
+ rafter: '200',
+ alignType: 'parallel',
+ },
+ {
+ id: 'C',
+ name: '기와3',
+ type: 'C',
+ hajebichi: '200',
+ alignType: 'stairs',
+ },
+ {
+ id: 'D',
+ name: '기와4',
+ type: 'D',
+ length: '200',
+ alignType: 'stairs',
+ },
+ ]
+ const widths = [
+ { name: '200', id: 'q' },
+ { name: '250', id: 'q1' },
+ { name: '300', id: 'q2' },
+ ]
+ const lengths = [
+ { name: '200', id: 'w' },
+ { name: '250', id: 'w1' },
+ { name: '300', id: 'w2' },
+ ]
+ const rafters = [
+ { name: '200', id: 'e' },
+ { name: '250', id: 'e1' },
+ { name: '300', id: 'e2' },
+ ]
+
+ const [values, setValues] = useState([
+ {
+ id: 'A',
+ type: 'A',
+ roofMaterial: { name: '기와1' },
+ width: { name: '200' },
+ length: { name: '250' },
+ rafter: { name: '300' },
+ alignType: 'stairs',
+ },
+ ])
+
+ const [radioValue, setRadioValue] = useState('A')
+
+ const [selectedRoofMaterial, setSelectedRoofMaterial] = useState(roofMaterials[0])
+
+ useEffect(() => {
+ const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase')
+ if (roofBases.length === 0) {
+ swalFire({ text: '할당할 지붕이 없습니다.' })
+ setShowRoofAllocationSettingModal(false)
+ }
+ }, [])
+
+ const onAddRoofMaterial = () => {
+ setValues([...values, selectedRoofMaterial])
+ }
+
+ const onDeleteRoofMaterial = (id) => {
+ setValues(values.filter((value) => value.id !== id))
+ }
+
+ // 선택한 지붕재로 할당
+ const handleSave = () => {
+ const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase')
+ const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine')
+ roofBases.forEach((roofBase) => {
+ splitPolygonWithLines(roofBase)
+
+ roofBase.innerLines.forEach((line) => {
+ canvas.remove(line)
+ })
+
+ canvas.remove(roofBase)
+ })
+
+ wallLines.forEach((wallLine) => {
+ canvas.remove(wallLine)
+ })
+
+ const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof')
+
+ roofs.forEach((roof) => {
+ setSurfaceShapePattern(roof)
+ drawDirectionArrow(roof)
+ })
+ setShowRoofAllocationSettingModal(false)
+ }
+
+ const handleRadioOnChange = (e) => {
+ setRadioValue(e.target)
+ }
+
+ return {
+ handleSave,
+ onAddRoofMaterial,
+ onDeleteRoofMaterial,
+ handleRadioOnChange,
+ widths,
+ lengths,
+ rafters,
+ values,
+ roofMaterials,
+ selectedRoofMaterial,
+ setSelectedRoofMaterial,
+ radioValue,
+ setRadioValue,
+ }
+}
diff --git a/src/hooks/roofcover/useRoofShapePassivitySetting.js b/src/hooks/roofcover/useRoofShapePassivitySetting.js
new file mode 100644
index 00000000..217e71bc
--- /dev/null
+++ b/src/hooks/roofcover/useRoofShapePassivitySetting.js
@@ -0,0 +1,188 @@
+import { canvasState, currentObjectState } from '@/store/canvasAtom'
+import { useRecoilValue } from 'recoil'
+import { useEffect, useRef, useState } from 'react'
+import { useLine } from '@/hooks/useLine'
+import { useMessage } from '@/hooks/useMessage'
+import { useEvent } from '@/hooks/useEvent'
+import { LINE_TYPE } from '@/common/common'
+import { useMode } from '@/hooks/useMode'
+import { usePolygon } from '@/hooks/usePolygon'
+import { outerLineFixState } from '@/store/outerLineAtom'
+import { useSwal } from '@/hooks/useSwal'
+
+//지붕형상 수동 설정
+export function useRoofShapePassivitySetting(setShowRoofShapePassivitySettingModal) {
+ const TYPES = {
+ EAVES: 'eaves',
+ GABLE: 'gable',
+ SHED: 'shed',
+ }
+ const canvas = useRecoilValue(canvasState)
+ const { getMessage } = useMessage()
+ const { showLine, hideLine } = useLine()
+ const { swalFire } = useSwal()
+ const { addCanvasMouseEventListener, initEvent } = useEvent()
+ const { drawRoofPolygon } = useMode()
+ const { addPolygonByLines } = usePolygon()
+ const currentObject = useRecoilValue(currentObjectState)
+ const offsetRef = useRef(null)
+ const pitchRef = useRef(null)
+ const currentLineRef = useRef(null)
+
+ const history = useRef([])
+
+ const [type, setType] = useState(TYPES.EAVES)
+
+ const buttons = [
+ { id: 1, name: getMessage('eaves'), type: TYPES.EAVES },
+ { id: 2, name: getMessage('gable'), type: TYPES.GABLE },
+ { id: 3, name: getMessage('windage'), type: TYPES.SHED },
+ ]
+
+ const outerLineFix = useRecoilValue(outerLineFixState)
+
+ useEffect(() => {
+ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ if (!outerLineFix || outerLines.length === 0) {
+ swalFire({ text: '외벽선이 없습니다.' })
+ setShowRoofShapePassivitySettingModal(false)
+ }
+ }, [])
+
+ useEffect(() => {
+ addCanvasMouseEventListener('mouse:down', mouseDown)
+ const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine')
+ canvas.remove(wallLines)
+
+ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ outerLines.forEach((outerLine, idx) => {
+ if (idx === 0) {
+ currentLineRef.current = outerLine
+ }
+ outerLine.set({ selectable: true })
+ showLine(outerLine)
+ outerLine.bringToFront()
+ })
+ canvas?.renderAll()
+
+ return () => {
+ canvas?.discardActiveObject()
+ initEvent()
+ }
+ }, [])
+
+ useEffect(() => {
+ const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ lines.forEach((line) => {
+ line.set({
+ stroke: '#000000',
+ strokeWidth: 4,
+ })
+ })
+ if (!currentObject) {
+ return
+ }
+ if (currentObject.name !== 'outerLine') {
+ return
+ }
+
+ currentObject.set({
+ stroke: '#EA10AC',
+ strokeWidth: 4,
+ })
+
+ currentLineRef.current = currentObject
+
+ canvas.renderAll()
+ }, [currentObject])
+
+ const mouseDown = (e) => {
+ if (!e.target) {
+ currentLineRef.current = null
+ return
+ }
+
+ if (e.target && e.target.name === 'outerLine') {
+ currentLineRef.current = e.target
+ }
+ }
+
+ const nextLineFocus = (selectedLine) => {
+ const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ const index = lines.findIndex((line) => line === selectedLine)
+
+ const nextLine = lines[index + 1] || lines[0]
+ canvas.setActiveObject(nextLine)
+ }
+
+ const handleConfirm = () => {
+ if (!currentLineRef.current) {
+ alert('선택된 외곽선이 없습니다.')
+ return
+ }
+ let attributes
+ const offset = Number(offsetRef.current.value) / 10
+ if (type === TYPES.EAVES) {
+ attributes = {
+ type: LINE_TYPE.WALLLINE.EAVES,
+ offset,
+ pitch: pitchRef.current.value,
+ }
+ } else if (type === TYPES.GABLE) {
+ attributes = {
+ type: LINE_TYPE.WALLLINE.GABLE,
+ pitch: pitchRef.current.value,
+ offset,
+ }
+ } else if (type === TYPES.SHED) {
+ attributes = {
+ type: LINE_TYPE.WALLLINE.SHED,
+ offset,
+ }
+ }
+
+ currentLineRef.current.set({
+ attributes,
+ })
+
+ history.current.push(currentLineRef.current)
+ nextLineFocus(currentLineRef.current)
+ }
+
+ const handleRollback = () => {
+ if (history.current.length === 0) {
+ return
+ }
+ const lastLine = history.current.pop()
+
+ delete lastLine.attributes
+ lastLine.set({
+ stroke: '#000000',
+ strokeWidth: 4,
+ })
+
+ canvas.setActiveObject(lastLine)
+ canvas.renderAll()
+ }
+
+ const handleSave = () => {
+ const exceptObjs = canvas.getObjects().filter((obj) => obj.name !== 'outerLine' && obj.parent?.name !== 'outerLine')
+ const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ exceptObjs.forEach((obj) => {
+ canvas.remove(obj)
+ })
+
+ lines.forEach((line) => {
+ hideLine(line)
+ })
+
+ const wall = addPolygonByLines(lines, { name: 'wallLine', fill: 'transparent', stroke: 'black' })
+
+ wall.lines = [...lines]
+
+ const roof = drawRoofPolygon(wall)
+ canvas.renderAll()
+ setShowRoofShapePassivitySettingModal(false)
+ }
+ return { handleSave, handleConfirm, buttons, type, setType, TYPES, offsetRef, pitchRef, handleRollback }
+}
diff --git a/src/hooks/roofcover/useRoofShapeSetting.js b/src/hooks/roofcover/useRoofShapeSetting.js
new file mode 100644
index 00000000..1722e287
--- /dev/null
+++ b/src/hooks/roofcover/useRoofShapeSetting.js
@@ -0,0 +1,494 @@
+import { useEffect, useRef, useState } from 'react'
+import { useMessage } from '@/hooks/useMessage'
+import { useRecoilValue, useSetRecoilState } from 'recoil'
+import { canvasState, currentMenuState, currentObjectState } from '@/store/canvasAtom'
+import { LINE_TYPE, MENU } from '@/common/common'
+import { usePolygon } from '@/hooks/usePolygon'
+import { useMode } from '@/hooks/useMode'
+import { useLine } from '@/hooks/useLine'
+import { outerLineFixState } from '@/store/outerLineAtom'
+import { useSwal } from '@/hooks/useSwal'
+
+// 지붕형상 설정
+export function useRoofShapeSetting(setShowRoofShapeSettingModal) {
+ const [shapeNum, setShapeNum] = useState(1)
+ const [buttonAct, setButtonAct] = useState(1)
+ const { swalFire } = useSwal()
+ const { getMessage } = useMessage()
+ const canvas = useRecoilValue(canvasState)
+ const { addPolygonByLines } = usePolygon()
+ const [pitch, setPitch] = useState(4)
+ const [eavesOffset, setEavesOffset] = useState(500) // 처마출폭
+ const [gableOffset, setGableOffset] = useState(300) // 케라바출폭
+ const [sleeveOffset, setSleeveOffset] = useState(300) // 소매출폭
+ const [jerkinHeadWidth, setJerkinHeadWidth] = useState(800) // 반절처 폭
+ const [jerkinHeadPitch, setJerkinHeadPitch] = useState(4.5) // 반절처 경사
+ const [hipAndGableWidth, setHipAndGableWidth] = useState(800) // 팔작지붕 폭
+ const [shedWidth, setShedWidth] = useState(300) // 한쪽흐름 폭
+ const [hasSleeve, setHasSleeve] = useState('0')
+ const currentObject = useRecoilValue(currentObjectState)
+ const { drawRoofPolygon } = useMode()
+ const { hideLine, showLine } = useLine()
+
+ const setCurrentMenu = useSetRecoilState(currentMenuState)
+ const outerLineFix = useRecoilValue(outerLineFixState)
+
+ const history = useRef([])
+
+ useEffect(() => {
+ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ if (!outerLineFix || outerLines.length === 0) {
+ swalFire({ text: '외벽선이 없습니다.' })
+ setShowRoofShapeSettingModal(false)
+ }
+ }, [])
+
+ useEffect(() => {
+ if (shapeNum !== 4) {
+ return
+ }
+ if (!currentObject) {
+ return
+ }
+ if (currentObject.name !== 'outerLine') {
+ return
+ }
+
+ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ outerLines.forEach((line) => {
+ line.set({
+ stroke: '#000000',
+ strokeWidth: 4,
+ })
+ })
+
+ currentObject.set({
+ stroke: '#EA10AC',
+ strokeWidth: 4,
+ })
+
+ canvas.renderAll()
+ }, [currentObject])
+
+ useEffect(() => {
+ if (shapeNum === 4) {
+ canvas?.remove(canvas.getObjects().find((obj) => obj.name === 'wallLine'))
+ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ outerLines.forEach((line) => {
+ showLine(line)
+ line.bringToFront()
+ })
+
+ canvas?.renderAll()
+ }
+ setPitch(4)
+ setEavesOffset(500)
+ setGableOffset(300)
+ setSleeveOffset(300)
+ setJerkinHeadWidth(800)
+ setJerkinHeadPitch(4.5)
+ setHipAndGableWidth(800)
+ setShedWidth(300)
+ }, [shapeNum])
+
+ const shapeMenu = [
+ { id: 1, name: getMessage('modal.roof.shape.setting.ridge') }, // 용마루
+ { id: 2, name: getMessage('modal.roof.shape.setting.patten.a') }, // 패턴A
+ { id: 3, name: getMessage('modal.roof.shape.setting.patten.b') }, // 패턴B
+ { id: 4, name: getMessage('modal.roof.shape.setting.side') }, // 변별로 설정
+ { id: 5, name: getMessage('commons.west') }, // 서
+ { id: 6, name: getMessage('commons.east') }, // 동
+ { id: 7, name: getMessage('commons.south') }, // 남
+ { id: 8, name: getMessage('commons.north') }, // 북
+ ]
+
+ const buttonMenu = [
+ { id: 1, name: getMessage('eaves') },
+ { id: 2, name: getMessage('gable') },
+ { id: 3, name: getMessage('wall') },
+ { id: 4, name: getMessage('hipandgable') },
+ { id: 5, name: getMessage('jerkinhead') },
+ { id: 6, name: getMessage('shed') },
+ ]
+
+ const handleSave = () => {
+ //기존 wallLine 삭제
+
+ let outerLines
+
+ switch (shapeNum) {
+ case 1: {
+ outerLines = saveRidge()
+ break
+ }
+ case 2: {
+ outerLines = saveAPattern()
+ break
+ }
+ case 3: {
+ outerLines = saveBPattern()
+ break
+ }
+ case 4: {
+ outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ outerLines.forEach((line) => {
+ hideLine(line)
+ })
+ break
+ }
+
+ case 5: {
+ // 서쪽
+ initLineSetting()
+ outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+
+ outerLines.forEach((line) => {
+ setWestAndEastRoof(line)
+ if (line.direction === 'bottom') {
+ line.attributes = {
+ offset: eavesOffset / 10,
+ pitch: pitch,
+ type: LINE_TYPE.WALLLINE.EAVES,
+ }
+ }
+
+ if (line.direction === 'top') {
+ line.attributes = {
+ offset: shedWidth / 10,
+ pitch: pitch,
+ type: LINE_TYPE.WALLLINE.SHED,
+ }
+ }
+
+ hideLine(line)
+ })
+ break
+ }
+ case 6: {
+ initLineSetting()
+ outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+
+ outerLines.forEach((line) => {
+ setWestAndEastRoof(line)
+ if (line.direction === 'top') {
+ line.attributes = {
+ offset: eavesOffset / 10,
+ pitch: pitch,
+ type: LINE_TYPE.WALLLINE.EAVES,
+ }
+ }
+
+ if (line.direction === 'bottom') {
+ line.attributes = {
+ offset: shedWidth / 10,
+ pitch: pitch,
+ type: LINE_TYPE.WALLLINE.SHED,
+ }
+ }
+
+ hideLine(line)
+ })
+ break
+ }
+ case 7: {
+ initLineSetting()
+ outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+
+ outerLines.forEach((line) => {
+ setSouthAndNorthRoof(line)
+ if (line.direction === 'right') {
+ line.attributes = {
+ offset: eavesOffset / 10,
+ pitch: pitch,
+ type: LINE_TYPE.WALLLINE.EAVES,
+ }
+ }
+
+ if (line.direction === 'left') {
+ line.attributes = {
+ offset: shedWidth / 10,
+ pitch: pitch,
+ type: LINE_TYPE.WALLLINE.SHED,
+ }
+ }
+ hideLine(line)
+ })
+ break
+ }
+ case 8: {
+ initLineSetting()
+ outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+
+ outerLines.forEach((line) => {
+ setSouthAndNorthRoof(line)
+
+ if (line.direction === 'left') {
+ line.attributes = {
+ offset: eavesOffset / 10,
+ pitch: pitch,
+ type: LINE_TYPE.WALLLINE.EAVES,
+ }
+ }
+
+ if (line.direction === 'right') {
+ line.attributes = {
+ offset: shedWidth / 10,
+ pitch: pitch,
+ type: LINE_TYPE.WALLLINE.SHED,
+ }
+ }
+
+ hideLine(line)
+ })
+ break
+ }
+ }
+
+ // 기존 wallLine 제거
+ canvas?.remove(canvas.getObjects().filter((obj) => obj.name === 'wallLine'))
+
+ const polygon = addPolygonByLines(outerLines, { name: 'wallLine' })
+ polygon.lines = [...outerLines]
+
+ const roof = drawRoofPolygon(polygon)
+
+ canvas?.renderAll()
+ roof.drawHelpLine()
+ setShowRoofShapeSettingModal(false)
+ }
+
+ const initLineSetting = () => {
+ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+
+ const tempPolygon = addPolygonByLines(outerLines)
+ tempPolygon.lines.forEach((line) => {
+ outerLines.forEach((outerLine) => {
+ if (
+ (line.startPoint === outerLine.startPoint && line.endPoint === outerLine.endPoint) ||
+ (line.startPoint === outerLine.endPoint && line.endPoint === outerLine.startPoint)
+ ) {
+ outerLine.direction = line.direction
+ outerLine.idx = line.idx
+ }
+ })
+ })
+
+ // 첫번째 line의 방향이 right일 경우
+ if (outerLines[0].direction === 'right') {
+ // top과 bottom의 방향을 바꾼다.
+ outerLines.forEach((line) => {
+ if (line.direction === 'top') {
+ line.direction = 'bottom'
+ } else if (line.direction === 'bottom') {
+ line.direction = 'top'
+ }
+ })
+ }
+ canvas.remove(tempPolygon)
+ }
+
+ // 동, 서 선택 시 가로라인을 케라바로 설정
+ const setWestAndEastRoof = (line) => {
+ if (line.direction === 'left' || line.direction === 'right') {
+ line.attributes = {
+ offset: gableOffset / 10,
+ type: LINE_TYPE.WALLLINE.GABLE,
+ }
+ }
+ }
+
+ // 남, 북 선택 시 세로라인을 케라바로 설정
+ const setSouthAndNorthRoof = (line) => {
+ if (line.direction === 'top' || line.direction === 'bottom') {
+ line.attributes = {
+ offset: gableOffset / 10,
+ type: LINE_TYPE.WALLLINE.GABLE,
+ }
+ }
+ }
+
+ const saveRidge = () => {
+ // 용마루 저장
+ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+
+ outerLines.forEach((line) => {
+ line.attributes = {
+ offset: eavesOffset / 10,
+ pitch: pitch,
+ type: LINE_TYPE.WALLLINE.EAVES,
+ }
+ hideLine(line)
+ })
+
+ return outerLines
+ }
+
+ // 패턴 A : 가로선이 모두 케라바
+ const saveAPattern = () => {
+ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+
+ outerLines.forEach((line) => {
+ if (line.direction === 'left' || line.direction === 'right') {
+ line.attributes = {
+ offset: gableOffset / 10,
+ type: LINE_TYPE.WALLLINE.GABLE,
+ }
+ } else if (line.direction === 'top' || line.direction === 'bottom') {
+ line.attributes = {
+ offset: eavesOffset / 10,
+ pitch: pitch,
+ type: LINE_TYPE.WALLLINE.EAVES,
+ }
+ }
+ hideLine(line)
+ })
+
+ return outerLines
+ }
+
+ // 패턴 B : 세로선이 모두 케라바
+ const saveBPattern = () => {
+ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+
+ outerLines.forEach((line) => {
+ if (line.direction === 'top' || line.direction === 'bottom') {
+ line.attributes = {
+ offset: gableOffset / 10,
+ type: LINE_TYPE.WALLLINE.GABLE,
+ }
+ } else if (line.direction === 'left' || line.direction === 'right') {
+ line.attributes = {
+ offset: eavesOffset / 10,
+ pitch: pitch,
+ type: LINE_TYPE.WALLLINE.EAVES,
+ }
+ }
+ hideLine(line)
+ })
+
+ return outerLines
+ }
+
+ // 변별로 설정 팝업 내 적용
+ const handleConfirm = () => {
+ const selectedLine = canvas?.getActiveObject()
+ if (!selectedLine) {
+ return
+ }
+
+ let attributes
+ switch (buttonAct) {
+ case 1: {
+ // 처마
+ attributes = {
+ type: LINE_TYPE.WALLLINE.EAVES,
+ pitch: pitch,
+ offset: eavesOffset / 10,
+ }
+ break
+ }
+ case 2: {
+ // 케라바
+ attributes = {
+ type: LINE_TYPE.WALLLINE.GABLE,
+ offset: gableOffset / 10,
+ }
+ break
+ }
+ case 3: {
+ // 벽
+ attributes = {
+ type: LINE_TYPE.WALLLINE.WALL,
+ width: hasSleeve === '0' ? 0 : sleeveOffset / 10,
+ sleeve: hasSleeve === '1',
+ }
+ break
+ }
+ case 4: {
+ // 팔작지붕
+ attributes = {
+ type: LINE_TYPE.WALLLINE.HIPANDGABLE,
+ pitch: pitch,
+ offset: eavesOffset / 10,
+ width: hipAndGableWidth / 10,
+ }
+ break
+ }
+ case 5: {
+ // 반절처
+ attributes = {
+ type: LINE_TYPE.WALLLINE.JERKINHEAD,
+ offset: gableOffset / 10,
+ width: jerkinHeadWidth / 10,
+ pitch: jerkinHeadPitch,
+ }
+ break
+ }
+ case 6: {
+ // 한쪽흐름
+ attributes = {
+ type: LINE_TYPE.WALLLINE.SHED,
+ width: shedWidth / 10,
+ }
+ break
+ }
+ }
+ selectedLine.attributes = attributes
+ history.current.push(selectedLine)
+ nextLineFocus(selectedLine)
+ }
+
+ const nextLineFocus = (selectedLine) => {
+ const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ const index = lines.findIndex((line) => line.idx === selectedLine.idx)
+
+ const nextLine = lines[index + 1] || lines[0]
+ canvas.setActiveObject(nextLine)
+ }
+
+ // 변별로 설정 내 일변 전으로 돌아가기
+ const handleRollBack = () => {
+ if (history.current.length === 0) {
+ return
+ }
+ const lastLine = history.current.pop()
+
+ lastLine.set({
+ stroke: '#000000',
+ strokeWidth: 4,
+ })
+
+ canvas.setActiveObject(lastLine)
+ canvas.renderAll()
+ }
+
+ return {
+ shapeNum,
+ setShapeNum,
+ shapeMenu,
+ handleSave,
+ buttonMenu,
+ pitch,
+ setPitch,
+ eavesOffset,
+ setEavesOffset,
+ gableOffset,
+ setGableOffset,
+ sleeveOffset,
+ setSleeveOffset,
+ jerkinHeadWidth,
+ setJerkinHeadWidth,
+ jerkinHeadPitch,
+ setJerkinHeadPitch,
+ hipAndGableWidth,
+ setHipAndGableWidth,
+ shedWidth,
+ setShedWidth,
+ hasSleeve,
+ setHasSleeve,
+ buttonAct,
+ setButtonAct,
+ handleConfirm,
+ handleRollBack,
+ }
+}
diff --git a/src/hooks/roofcover/useWallLineOffsetSetting.js b/src/hooks/roofcover/useWallLineOffsetSetting.js
new file mode 100644
index 00000000..6f65b58e
--- /dev/null
+++ b/src/hooks/roofcover/useWallLineOffsetSetting.js
@@ -0,0 +1,510 @@
+import { canvasState } from '@/store/canvasAtom'
+import { useRecoilValue } from 'recoil'
+import { useEffect, useRef, useState } from 'react'
+import { useMessage } from '@/hooks/useMessage'
+import { useEvent } from '@/hooks/useEvent'
+import { useLine } from '@/hooks/useLine'
+import { useSwal } from '@/hooks/useSwal'
+
+// 외벽선 편집 및 오프셋
+export function useWallLineOffsetSetting(setShowWallLineOffsetSettingModal) {
+ const canvas = useRecoilValue(canvasState)
+ const { showLine, addLine } = useLine()
+ const { getMessage } = useMessage()
+ const { swalFire } = useSwal()
+ const { addCanvasMouseEventListener, initEvent } = useEvent()
+ const wallLineEditRef = useRef(null)
+ const length1Ref = useRef(null)
+ const length2Ref = useRef(null)
+ const radioTypeRef = useRef('1')
+ const currentWallLineRef = useRef(null)
+ const arrow1Ref = useRef(null)
+ const arrow2Ref = useRef(null)
+
+ const [isLoading, setIsLoading] = useState(false)
+
+ const drawLine = (point1, point2, idx, direction = currentWallLineRef.current.direction) => {
+ const line = addLine([point1.x, point1.y, point2.x, point2.y], {
+ stroke: 'black',
+ strokeWidth: 4,
+ idx: idx,
+ selectable: true,
+ name: 'outerLine',
+ x1: point1.x,
+ y1: point1.y,
+ x2: point2.x,
+ y2: point2.y,
+ direction: direction,
+ })
+
+ line.attributes = { ...currentWallLineRef.current.attributes }
+ }
+
+ const TYPES = {
+ WALL_LINE_EDIT: 'wallLineEdit',
+ OFFSET: 'offset',
+ }
+
+ const buttonMenu = [
+ { id: 1, name: getMessage('modal.wallline.offset.setting.wallline.edit'), type: TYPES.WALL_LINE_EDIT },
+ { id: 2, name: getMessage('modal.wallline.offset.setting.offset'), type: TYPES.OFFSET },
+ ]
+
+ const [type, setType] = useState(TYPES.WALL_LINE_EDIT)
+
+ useEffect(() => {
+ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ if (outerLines.length === 0) {
+ swalFire({ text: '외벽선이 없습니다.' })
+ setShowWallLineOffsetSettingModal(false)
+ return
+ }
+ outerLines.forEach((outerLine) => {
+ outerLine.set({ selectable: true })
+ showLine(outerLine)
+ })
+
+ //outerLine과 그 text만 남겨둔다.
+ const exceptObjs = canvas.getObjects().filter((obj) => obj.name !== 'outerLine' && obj.parent?.name !== 'outerLine')
+ exceptObjs.forEach((obj) => {
+ canvas.remove(obj)
+ })
+
+ canvas.setActiveObject(outerLines[0])
+ currentWallLineRef.current = outerLines[0]
+ addCircleByLine(currentWallLineRef.current)
+
+ addCanvasMouseEventListener('mouse:down', mouseDown)
+ setIsLoading(true)
+ return () => {
+ removeOuterLineEditCircle()
+ canvas.discardActiveObject()
+ initEvent()
+ }
+ }, [])
+
+ useEffect(() => {
+ if (!isLoading) {
+ return
+ }
+ removeOuterLineEditCircle()
+ addCanvasMouseEventListener('mouse:down', mouseDown)
+ if (type === TYPES.WALL_LINE_EDIT) {
+ addCircleByLine(currentWallLineRef.current)
+ }
+ }, [type])
+
+ const removeOuterLineEditCircle = () => {
+ canvas.remove(...canvas.getObjects().filter((obj) => obj.name === 'outerLineEditCircleStart' || obj.name === 'outerLineEditCircleEnd'))
+ }
+
+ const mouseDown = (e) => {
+ removeOuterLineEditCircle()
+ if (!e.target || (e.target && e.target.name !== 'outerLine')) {
+ currentWallLineRef.current = null
+ return
+ }
+
+ currentWallLineRef.current = e.target
+ console.log(currentWallLineRef.current.idx, currentWallLineRef.current.direction)
+ if (type === TYPES.WALL_LINE_EDIT) {
+ addCircleByLine(currentWallLineRef.current)
+ }
+ }
+
+ const addCircleByLine = (line) => {
+ const direction = currentWallLineRef.current?.direction
+ let startPoint
+ let endPoint
+ let circle1, circle2
+
+ if (direction === 'top' || direction === 'bottom') {
+ // y1, y2중에 더 작은 값이 startPoint, 더 큰 값이 endPoint
+ if (line.y1 > line.y2) {
+ startPoint = { x: line.x2, y: line.y2 }
+ endPoint = { x: line.x1, y: line.y1 }
+ } else {
+ startPoint = { x: line.x1, y: line.y1 }
+ endPoint = { x: line.x2, y: line.y2 }
+ }
+ arrow1Ref.current = 'down'
+ arrow2Ref.current = 'up'
+ } else {
+ // x1, x2중에 더 작은 값이 startPoint, 더 큰 값이 endPoint
+ if (line.x1 > line.x2) {
+ startPoint = { x: line.x2, y: line.y2 }
+ endPoint = { x: line.x1, y: line.y1 }
+ } else {
+ startPoint = { x: line.x1, y: line.y1 }
+ endPoint = { x: line.x2, y: line.y2 }
+ }
+ arrow1Ref.current = 'right'
+ arrow2Ref.current = 'left'
+ }
+
+ circle1 = new fabric.Circle({
+ radius: 10,
+ fill: 'red',
+ top: startPoint.y - 10,
+ left: startPoint.x - 10,
+ name: 'outerLineEditCircleStart',
+ hasControls: false,
+ hasBorders: false,
+ lockMovementX: true,
+ lockMovementY: true,
+ })
+
+ circle2 = new fabric.Circle({
+ radius: 10,
+ fill: 'blue',
+ top: endPoint.y - 10,
+ left: endPoint.x - 10,
+ name: 'outerLineEditCircleEnd',
+ hasControls: false,
+ hasBorders: false,
+ lockMovementX: true,
+ lockMovementY: true,
+ })
+
+ wallLineEditRef.current.setArrow()
+ canvas.add(circle1)
+ canvas.add(circle2)
+ }
+
+ const handleSave = () => {
+ if (currentWallLineRef.current === null) {
+ alert('보조선을 먼저 선택하세요')
+ return
+ }
+ switch (type) {
+ case TYPES.WALL_LINE_EDIT:
+ handleWallLineEditSave()
+ break
+ case TYPES.OFFSET:
+ handleOffsetSave()
+ break
+ }
+ }
+
+ const handleWallLineEditSave = () => {
+ const startPoint = canvas
+ .getObjects()
+ .filter((obj) => obj.name === 'outerLineEditCircleStart')
+ .map((obj) => {
+ return {
+ x: obj.left + obj.radius,
+ y: obj.top + obj.radius,
+ }
+ })[0]
+ const endPoint = canvas
+ .getObjects()
+ .filter((obj) => obj.name === 'outerLineEditCircleEnd')
+ .map((obj) => {
+ return {
+ x: obj.left + obj.radius,
+ y: obj.top + obj.radius,
+ }
+ })[0]
+ const length = Number(length1Ref.current.value) / 10
+ const length2 = Number(length2Ref.current.value) / 10
+ const currentIdx = currentWallLineRef.current.idx
+ const currentDirection = currentWallLineRef.current.direction
+ let point1, point2, point3
+ if (radioTypeRef.current === '1') {
+ // 1지점 선택시 방향은 down, right만 가능
+ if (arrow1Ref.current === 'down') {
+ if (currentDirection === 'top') {
+ point1 = { x: endPoint.x, y: endPoint.y }
+ point2 = { x: startPoint.x, y: startPoint.y + length }
+ point3 = { x: startPoint.x, y: startPoint.y }
+ } else {
+ point1 = { x: startPoint.x, y: startPoint.y }
+ point2 = { x: startPoint.x, y: startPoint.y + length }
+ point3 = { x: endPoint.x, y: endPoint.y }
+ }
+ } else {
+ if (currentDirection === 'left') {
+ point1 = { x: endPoint.x, y: endPoint.y }
+ point2 = { x: startPoint.x + length, y: startPoint.y }
+ point3 = { x: startPoint.x, y: startPoint.y }
+ } else {
+ point1 = { x: startPoint.x, y: startPoint.y }
+ point2 = { x: startPoint.x + length, y: startPoint.y }
+ point3 = { x: endPoint.x, y: endPoint.y }
+ }
+ }
+ } else {
+ // 2지점일 경우 방향은 up, left만 가능
+ if (arrow2Ref.current === 'up') {
+ if (currentDirection === 'bottom') {
+ point1 = { x: startPoint.x, y: startPoint.y }
+ point2 = { x: endPoint.x, y: endPoint.y - length2 }
+ point3 = { x: endPoint.x, y: endPoint.y }
+ } else {
+ point1 = { x: endPoint.x, y: endPoint.y }
+ point2 = { x: endPoint.x, y: endPoint.y - length2 }
+ point3 = { x: startPoint.x, y: startPoint.y }
+ }
+ } else {
+ if (currentDirection === 'right') {
+ point1 = { x: startPoint.x, y: startPoint.y }
+ point2 = { x: endPoint.x - length2, y: endPoint.y }
+ point3 = { x: endPoint.x, y: endPoint.y }
+ } else {
+ point1 = { x: endPoint.x, y: endPoint.y }
+ point2 = { x: endPoint.x - length2, y: endPoint.y }
+ point3 = { x: startPoint.x, y: startPoint.y }
+ }
+ }
+ }
+
+ rearrangeOuterLine(currentIdx + 1)
+
+ drawLine(point1, point2, currentIdx)
+ drawLine(point2, point3, currentIdx + 1)
+
+ removeOuterLineEditCircle()
+ canvas.remove(currentWallLineRef.current)
+ currentWallLineRef.current = null
+ canvas.renderAll()
+ }
+
+ const rearrangeOuterLine = (idxParam) => {
+ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+ outerLines.forEach((outerLine) => {
+ if (outerLine.idx >= idxParam) {
+ outerLine.idx = outerLine.idx + 1
+ }
+ })
+ }
+
+ const handleOffsetSave = () => {
+ const direction = currentWallLineRef.current.direction
+ let canDirections = direction === 'left' || direction === 'right' ? ['up', 'down'] : ['left', 'right']
+ const currentIdx = currentWallLineRef.current.idx
+ if (!canDirections.includes(arrow1Ref.current)) {
+ alert('방향을 다시 선택하세요')
+ return
+ }
+
+ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
+
+ const idx = currentWallLineRef.current.idx
+ const prevIdx = idx - 1 <= 0 ? outerLines.length : idx - 1
+ const nextIdx = idx + 1 > outerLines.length ? 1 : idx + 1
+
+ const currentLine = currentWallLineRef.current
+ const prevLine = outerLines.find((line) => line.idx === prevIdx)
+ const nextLine = outerLines.find((line) => line.idx === nextIdx)
+
+ const length = length1Ref.current.value / 10
+ const currentLineX = Math.floor(Math.max(currentLine.x1, currentLine.x2))
+ const currentLineY = Math.floor(Math.max(currentLine.y1, currentLine.y2))
+ switch (arrow1Ref.current) {
+ case 'up': {
+ if (prevLine.direction === currentLine.direction) {
+ const newX =
+ currentLine.direction === 'left'
+ ? Math.floor(Math.max(currentLine.x1, currentLine.x2))
+ : Math.floor(Math.min(currentLine.x1, currentLine.x2))
+
+ const newPoint1 = { x: newX, y: currentLineY - length }
+ const newPoint2 = { x: prevLine.x2, y: prevLine.y2 }
+ rearrangeOuterLine(currentIdx)
+ drawLine(newPoint1, newPoint2, currentIdx, 'top')
+
+ if (Math.abs(currentLineY - nextLine.y1) < 2) {
+ nextLine.set({ y1: currentLineY - length })
+ } else {
+ nextLine.set({ y2: currentLineY - length })
+ }
+ } else if (nextLine.direction === currentLine.direction) {
+ const newX =
+ currentLine.direction === 'left'
+ ? Math.floor(Math.min(currentLine.x1, currentLine.x2))
+ : Math.floor(Math.max(currentLine.x1, currentLine.x2))
+
+ const newPoint1 = { x: newX, y: currentLineY - length }
+ const newPoint2 = { x: nextLine.x1, y: nextLine.y1 }
+ rearrangeOuterLine(currentIdx + 1)
+ drawLine(newPoint1, newPoint2, currentIdx + 1, 'top')
+
+ if (Math.abs(currentLineY - prevLine.y1) < 2) {
+ prevLine.set({ y1: currentLineY - length })
+ } else {
+ prevLine.set({ y2: currentLineY - length })
+ }
+ } else {
+ if (Math.abs(currentLineY - prevLine.y1) < 2) {
+ prevLine.set({ y1: prevLine.y1 - length })
+ } else {
+ prevLine.set({ y2: prevLine.y2 - length })
+ }
+ if (Math.abs(currentLineY - nextLine.y1) < 2) {
+ nextLine.set({ y1: nextLine.y1 - length })
+ } else {
+ nextLine.set({ y2: nextLine.y2 - length })
+ }
+ }
+
+ currentLine.set({ y1: currentLine.y1 - length, y2: currentLine.y2 - length })
+
+ break
+ }
+ case 'down': {
+ if (prevLine.direction === currentLine.direction) {
+ const newX =
+ currentLine.direction === 'left'
+ ? Math.floor(Math.max(currentLine.x1, currentLine.x2))
+ : Math.floor(Math.min(currentLine.x1, currentLine.x2))
+ const newPoint1 = { x: newX, y: currentLineY + length }
+ const newPoint2 = { x: prevLine.x2, y: prevLine.y2 }
+ rearrangeOuterLine(currentIdx)
+ drawLine(newPoint1, newPoint2, currentIdx, 'bottom')
+ if (Math.abs(currentLineY - nextLine.y1) < 2) {
+ nextLine.set({ y1: currentLineY + length })
+ } else {
+ nextLine.set({ y2: currentLineY + length })
+ }
+ } else if (nextLine.direction === currentLine.direction) {
+ const newX =
+ currentLine.direction === 'left'
+ ? Math.floor(Math.min(currentLine.x1, currentLine.x2))
+ : Math.floor(Math.max(currentLine.x1, currentLine.x2))
+ const newPoint1 = { x: newX, y: currentLineY + length }
+ const newPoint2 = { x: nextLine.x1, y: nextLine.y1 }
+ rearrangeOuterLine(currentIdx + 1)
+ drawLine(newPoint1, newPoint2, currentIdx + 1, 'bottom')
+ if (Math.abs(currentLineY - prevLine.y1) < 2) {
+ prevLine.set({ y1: currentLineY + length })
+ } else {
+ prevLine.set({ y2: currentLineY + length })
+ }
+ } else {
+ if (Math.abs(currentLineY - prevLine.y1) < 2) {
+ prevLine.set({ y1: prevLine.y1 + length })
+ } else {
+ prevLine.set({ y2: prevLine.y2 + length })
+ }
+ if (Math.abs(currentLineY - nextLine.y1) < 2) {
+ nextLine.set({ y1: nextLine.y1 + length })
+ } else {
+ nextLine.set({ y2: nextLine.y2 + length })
+ }
+ }
+
+ currentLine.set({ y1: currentLine.y1 + length, y2: currentLine.y2 + length })
+ break
+ }
+ case 'left': {
+ if (prevLine.direction === currentLine.direction) {
+ const newY =
+ currentLine.direction === 'top'
+ ? Math.floor(Math.max(currentLine.y1, currentLine.y2))
+ : Math.floor(Math.min(currentLine.y1, currentLine.y2))
+ const newPoint1 = { x: currentLineX - length, y: newY }
+ const newPoint2 = { x: prevLine.x2, y: prevLine.y2 }
+ rearrangeOuterLine(currentIdx)
+ drawLine(newPoint1, newPoint2, currentIdx, 'left')
+ if (Math.abs(currentLineX - nextLine.x1) < 2) {
+ nextLine.set({ x1: currentLineX - length })
+ } else {
+ nextLine.set({ x2: currentLineX - length })
+ }
+ } else if (nextLine.direction === currentLine.direction) {
+ const newY =
+ currentLine.direction === 'top'
+ ? Math.floor(Math.min(currentLine.y1, currentLine.y2))
+ : Math.floor(Math.max(currentLine.y1, currentLine.y2))
+ const newPoint1 = { x: currentLineX - length, y: newY }
+ const newPoint2 = { x: nextLine.x1, y: nextLine.y1 }
+ rearrangeOuterLine(currentIdx + 1)
+ drawLine(newPoint1, newPoint2, currentIdx + 1, 'left')
+ if (Math.abs(currentLineX - prevLine.x1) < 2) {
+ prevLine.set({ x1: currentLineX - length })
+ } else {
+ prevLine.set({ x2: currentLineX - length })
+ }
+ } else {
+ if (Math.abs(currentLineX - prevLine.x1) < 2) {
+ prevLine.set({ x1: prevLine.x1 - length })
+ } else {
+ prevLine.set({ x2: prevLine.x2 - length })
+ }
+
+ if (Math.abs(currentLineX - nextLine.x1) < 2) {
+ nextLine.set({ x1: nextLine.x1 - length })
+ } else {
+ nextLine.set({ x2: nextLine.x2 - length })
+ }
+ }
+
+ currentLine.set({ x1: currentLine.x1 - length, x2: currentLine.x2 - length })
+ break
+ }
+ case 'right': {
+ if (prevLine.direction === currentLine.direction) {
+ const newY =
+ currentLine.direction === 'top'
+ ? Math.floor(Math.max(currentLine.y1, currentLine.y2))
+ : Math.floor(Math.min(currentLine.y1, currentLine.y2))
+ const newPoint1 = { x: currentLineX + length, y: newY }
+ const newPoint2 = { x: prevLine.x2, y: prevLine.y2 }
+ rearrangeOuterLine(currentIdx)
+ drawLine(newPoint1, newPoint2, currentIdx, 'right')
+ if (Math.abs(currentLineX - nextLine.x1) < 2) {
+ nextLine.set({ x1: currentLineX + length })
+ } else {
+ nextLine.set({ x2: currentLineX + length })
+ }
+ } else if (nextLine.direction === currentLine.direction) {
+ const newY =
+ currentLine.direction === 'top'
+ ? Math.floor(Math.min(currentLine.y1, currentLine.y2))
+ : Math.floor(Math.max(currentLine.y1, currentLine.y2))
+ const newPoint1 = { x: currentLineX + length, y: newY }
+ const newPoint2 = { x: nextLine.x1, y: nextLine.y1 }
+ rearrangeOuterLine(currentIdx + 1)
+ drawLine(newPoint1, newPoint2, currentIdx + 1, 'right')
+
+ if (Math.abs(currentLineX - prevLine.x1) < 2) {
+ prevLine.set({ x1: currentLineX + length })
+ } else {
+ prevLine.set({ x2: currentLineX + length })
+ }
+ } else {
+ if (Math.abs(currentLineX - prevLine.x1) < 2) {
+ prevLine.set({ x1: prevLine.x1 + length })
+ } else {
+ prevLine.set({ x2: prevLine.x2 + length })
+ }
+ if (Math.abs(currentLineX - nextLine.x1) < 2) {
+ nextLine.set({ x1: nextLine.x1 + length })
+ } else {
+ nextLine.set({ x2: nextLine.x2 + length })
+ }
+ }
+
+ currentLine.set({ x1: currentLine.x1 + length, x2: currentLine.x2 + length })
+
+ break
+ }
+ }
+ canvas.renderAll()
+ }
+
+ return {
+ type,
+ setType,
+ buttonMenu,
+ TYPES,
+ length1Ref,
+ length2Ref,
+ radioTypeRef,
+ currentWallLineRef,
+ arrow1Ref,
+ arrow2Ref,
+ handleSave,
+ wallLineEditRef,
+ }
+}
diff --git a/src/hooks/surface/usePlacementShapeDrawing.js b/src/hooks/surface/usePlacementShapeDrawing.js
new file mode 100644
index 00000000..4df37618
--- /dev/null
+++ b/src/hooks/surface/usePlacementShapeDrawing.js
@@ -0,0 +1,829 @@
+import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
+import {
+ adsorptionPointAddModeState,
+ adsorptionPointModeState,
+ adsorptionRangeState,
+ canvasState,
+ dotLineIntervalSelector,
+ verticalHorizontalModeState,
+} from '@/store/canvasAtom'
+import { useEvent } from '@/hooks/useEvent'
+import { useMouse } from '@/hooks/useMouse'
+import { useLine } from '@/hooks/useLine'
+import { useTempGrid } from '@/hooks/useTempGrid'
+import { useEffect, useRef } from 'react'
+import { distanceBetweenPoints, setSurfaceShapePattern } from '@/util/canvas-util'
+import { fabric } from 'fabric'
+import { calculateAngle, drawDirectionArrow } from '@/util/qpolygon-utils'
+import {
+ placementShapeDrawingAngle1State,
+ placementShapeDrawingAngle2State,
+ placementShapeDrawingArrow1State,
+ placementShapeDrawingArrow2State,
+ placementShapeDrawingDiagonalState,
+ placementShapeDrawingFixState,
+ placementShapeDrawingLength1State,
+ placementShapeDrawingLength2State,
+ placementShapeDrawingPointsState,
+ placementShapeDrawingTypeState,
+} from '@/store/placementShapeDrawingAtom'
+import { usePolygon } from '@/hooks/usePolygon'
+import { POLYGON_TYPE } from '@/common/common'
+
+// 면형상 배치
+export function usePlacementShapeDrawing(setShowPlaceShapeDrawingModal) {
+ const canvas = useRecoilValue(canvasState)
+ const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent } =
+ useEvent()
+ const { getIntersectMousePoint } = useMouse()
+ const { addLine, removeLine } = useLine()
+ const { addPolygonByLines } = usePolygon()
+ const { tempGridMode } = useTempGrid()
+
+ const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState)
+ const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState)
+ const adsorptionPointMode = useRecoilValue(adsorptionPointModeState)
+ const adsorptionRange = useRecoilValue(adsorptionRangeState)
+ const interval = useRecoilValue(dotLineIntervalSelector) // 가로 세로 간격
+
+ const length1Ref = useRef(null)
+ const length2Ref = useRef(null)
+ const angle1Ref = useRef(null)
+ const angle2Ref = useRef(null)
+ const [length1, setLength1] = useRecoilState(placementShapeDrawingLength1State)
+ const [length2, setLength2] = useRecoilState(placementShapeDrawingLength2State)
+ const [arrow1, setArrow1] = useRecoilState(placementShapeDrawingArrow1State)
+ const [arrow2, setArrow2] = useRecoilState(placementShapeDrawingArrow2State)
+ const [points, setPoints] = useRecoilState(placementShapeDrawingPointsState)
+ const [type, setType] = useRecoilState(placementShapeDrawingTypeState)
+ const [angle1, setAngle1] = useRecoilState(placementShapeDrawingAngle1State)
+ const [angle2, setAngle2] = useRecoilState(placementShapeDrawingAngle2State)
+ const [outerLineDiagonalLength, setOuterLineDiagonalLength] = useRecoilState(placementShapeDrawingDiagonalState)
+ const setOuterLineFix = useSetRecoilState(placementShapeDrawingFixState)
+ const arrow1Ref = useRef(arrow1)
+ const arrow2Ref = useRef(arrow2)
+
+ const outerLineDiagonalLengthRef = useRef(null)
+
+ const isFix = useRef(false)
+
+ useEffect(() => {
+ if (adsorptionPointAddMode || tempGridMode) {
+ return
+ }
+
+ addCanvasMouseEventListener('mouse:down', mouseDown)
+ clear()
+ }, [verticalHorizontalMode, points, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, interval, tempGridMode])
+
+ useEffect(() => {
+ arrow1Ref.current = arrow1
+ }, [arrow1])
+
+ useEffect(() => {
+ arrow2Ref.current = arrow2
+ }, [arrow2])
+
+ useEffect(() => {
+ clear()
+ addDocumentEventListener('keydown', document, keydown[type])
+ }, [type])
+
+ const clear = () => {
+ setLength1(0)
+ setLength2(0)
+
+ setArrow1('')
+ setArrow2('')
+
+ setAngle1(0)
+ setAngle2(0)
+
+ setOuterLineDiagonalLength(0)
+ }
+
+ const mouseDown = (e) => {
+ let pointer = getIntersectMousePoint(e)
+
+ if (points.length === 0) {
+ setPoints((prev) => [...prev, pointer])
+ } else {
+ const lastPoint = points[points.length - 1]
+ let newPoint = { x: pointer.x, y: pointer.y }
+ const length = distanceBetweenPoints(lastPoint, newPoint)
+ if (verticalHorizontalMode) {
+ const vector = {
+ x: pointer.x - points[points.length - 1].x,
+ y: pointer.y - points[points.length - 1].y,
+ }
+ const slope = Math.abs(vector.y / vector.x) // 기울기 계산
+
+ let scaledVector
+ if (slope >= 1) {
+ // 기울기가 1 이상이면 x축 방향으로 그림
+ scaledVector = {
+ x: 0,
+ y: vector.y >= 0 ? Number(length) : -Number(length),
+ }
+ } else {
+ // 기울기가 1 미만이면 y축 방향으로 그림
+ scaledVector = {
+ x: vector.x >= 0 ? Number(length) : -Number(length),
+ y: 0,
+ }
+ }
+
+ const verticalLength = scaledVector.y
+ const horizontalLength = scaledVector.x
+
+ newPoint = {
+ x: lastPoint.x + horizontalLength,
+ y: lastPoint.y + verticalLength,
+ }
+ }
+ setPoints((prev) => [...prev, newPoint])
+ }
+ }
+
+ useEffect(() => {
+ canvas
+ ?.getObjects()
+ .filter((obj) => obj.name === 'placementShapeDrawingLine' || obj.name === 'helpGuideLine')
+ .forEach((obj) => {
+ removeLine(obj)
+ })
+
+ canvas?.remove(canvas?.getObjects().find((obj) => obj.name === 'placementShapeDrawingStartPoint'))
+
+ if (points.length === 0) {
+ setOuterLineFix(true)
+ removeAllDocumentEventListeners()
+ return
+ }
+
+ addDocumentEventListener('keydown', document, keydown[type])
+
+ if (points.length === 1) {
+ const point = new fabric.Circle({
+ radius: 5,
+ fill: 'transparent',
+ stroke: 'red',
+ left: points[0].x - 5,
+ top: points[0].y - 5,
+ selectable: false,
+ name: 'placementShapeDrawingStartPoint',
+ })
+
+ canvas?.add(point)
+ } else {
+ setOuterLineFix(false)
+ canvas
+ .getObjects()
+ .filter((obj) => obj.name === 'placementShapeDrawingPoint')
+ .forEach((obj) => {
+ canvas.remove(obj)
+ })
+ points.forEach((point, idx) => {
+ const circle = new fabric.Circle({
+ left: point.x,
+ top: point.y,
+ visible: false,
+ name: 'placementShapeDrawingPoint',
+ })
+ canvas.add(circle)
+ })
+ points.forEach((point, idx) => {
+ if (idx === 0) {
+ return
+ }
+ drawLine(points[idx - 1], point, idx)
+ })
+
+ const lastPoint = points[points.length - 1]
+ const firstPoint = points[0]
+
+ if (isFix.current) {
+ removeAllMouseEventListeners()
+ removeAllDocumentEventListeners()
+
+ const lines = canvas?.getObjects().filter((obj) => obj.name === 'placementShapeDrawingLine')
+ const roof = addPolygonByLines(lines, {
+ stroke: 'black',
+ strokeWidth: 3,
+ selectable: true,
+ name: POLYGON_TYPE.ROOF,
+ originX: 'center',
+ originY: 'center',
+ direction: 'south',
+ })
+
+ setSurfaceShapePattern(roof)
+ drawDirectionArrow(roof)
+
+ lines.forEach((line) => {
+ removeLine(line)
+ })
+
+ canvas?.renderAll()
+ setShowPlaceShapeDrawingModal(false)
+ }
+
+ if (points.length < 3) {
+ return
+ }
+
+ /*if (lastPoint.x === firstPoint.x && lastPoint.y === firstPoint.y) {
+ return
+ }
+
+ if (lastPoint.x === firstPoint.x || lastPoint.y === firstPoint.y) {
+ let isAllRightAngle = true
+
+ const firstPoint = points[0]
+
+ points.forEach((point, idx) => {
+ if (idx === 0 || !isAllRightAngle) {
+ return
+ }
+
+ const angle = calculateAngle(point, firstPoint)
+ if (angle % 90 !== 0) {
+ isAllRightAngle = false
+ }
+ })
+
+ if (isAllRightAngle) {
+ return
+ }
+ const line = new QLine([lastPoint.x, lastPoint.y, firstPoint.x, firstPoint.y], {
+ stroke: 'grey',
+ strokeWidth: 1,
+ selectable: false,
+ name: 'helpGuideLine',
+ })
+
+ canvas?.add(line)
+ addLineText(line)
+ } else {
+ const guideLine1 = new QLine([lastPoint.x, lastPoint.y, lastPoint.x, firstPoint.y], {
+ stroke: 'grey',
+ strokeWidth: 1,
+ strokeDashArray: [1, 1, 1],
+ name: 'helpGuideLine',
+ })
+
+ const guideLine2 = new QLine([guideLine1.x2, guideLine1.y2, firstPoint.x, firstPoint.y], {
+ stroke: 'grey',
+ strokeWidth: 1,
+ strokeDashArray: [1, 1, 1],
+ name: 'helpGuideLine',
+ })
+ if (guideLine1.length > 0) {
+ canvas?.add(guideLine1)
+ addLineText(guideLine1)
+ }
+
+ canvas?.add(guideLine2)
+
+ addLineText(guideLine2)
+ }*/
+ }
+ }, [points])
+
+ const drawLine = (point1, point2, idx) => {
+ addLine([point1.x, point1.y, point2.x, point2.y], {
+ stroke: 'black',
+ strokeWidth: 3,
+ idx: idx,
+ selectable: true,
+ name: 'placementShapeDrawingLine',
+ x1: point1.x,
+ y1: point1.y,
+ x2: point2.x,
+ y2: point2.y,
+ })
+ }
+
+ // 직각 완료될 경우 확인
+ const checkRightAngle = (direction) => {
+ const activeElem = document.activeElement
+
+ const canDirection =
+ direction === '↓' || direction === '↑'
+ ? arrow1Ref.current === '←' || arrow1Ref.current === '→'
+ : arrow1Ref.current === '↓' || arrow1Ref.current === '↑'
+
+ if (activeElem === length1Ref.current || activeElem === angle1Ref.current) {
+ setArrow1(direction)
+ arrow1Ref.current = direction
+ length2Ref.current.focus()
+ } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) {
+ if (!canDirection) {
+ return
+ }
+ setArrow2(direction)
+ arrow2Ref.current = direction
+ }
+
+ const length1Num = Number(length1Ref.current.value) / 10
+ const length2Num = Number(length2Ref.current.value) / 10
+
+ if (points.length === 0) {
+ return
+ }
+
+ if (length1Num === 0 || length2Num === 0 || arrow1Ref.current === '' || arrow2Ref.current === '') {
+ return
+ }
+
+ if (arrow1Ref.current === '↓' && arrow2Ref.current === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Num, y: prev[prev.length - 1].y + length1Num }]
+ })
+ } else if (arrow1Ref.current === '↓' && arrow2Ref.current === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Num, y: prev[prev.length - 1].y + length1Num }]
+ })
+ } else if (arrow1Ref.current === '↑' && arrow2Ref.current === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Num, y: prev[prev.length - 1].y - length1Num }]
+ })
+ } else if (arrow1Ref.current === '↑' && arrow2Ref.current === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Num, y: prev[prev.length - 1].y - length1Num }]
+ })
+ } else if (arrow1Ref.current === '→' && arrow2Ref.current === '↓') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length1Num, y: prev[prev.length - 1].y + length2Num }]
+ })
+ } else if (arrow1Ref.current === '→' && arrow2Ref.current === '↑') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length1Num, y: prev[prev.length - 1].y - length2Num }]
+ })
+ } else if (arrow1Ref.current === '←' && arrow2Ref.current === '↓') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length1Num, y: prev[prev.length - 1].y + length2Num }]
+ })
+ } else if (arrow1Ref.current === '←' && arrow2Ref.current === '↑') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length1Num, y: prev[prev.length - 1].y - length2Num }]
+ })
+ }
+ }
+
+ //이구배 완료될 경우 확인 ↓, ↑, ←, →
+ const checkDoublePitch = (direction) => {
+ const activeElem = document.activeElement
+
+ const canDirection =
+ direction === '↓' || direction === '↑'
+ ? arrow1Ref.current === '←' || arrow1Ref.current === '→'
+ : arrow1Ref.current === '↓' || arrow1Ref.current === '↑'
+
+ if (activeElem === length1Ref.current || activeElem === angle1Ref.current) {
+ setArrow1(direction)
+ arrow1Ref.current = direction
+ angle2Ref.current.focus()
+ } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) {
+ if (!canDirection) {
+ return
+ }
+ setArrow2(direction)
+ arrow2Ref.current = direction
+ }
+
+ const angle1Value = angle1Ref.current.value
+ const angle2Value = angle2Ref.current.value
+ const length1Value = length1Ref.current.value
+ const length2Value = length2Ref.current.value
+
+ const arrow1Value = arrow1Ref.current
+ const arrow2Value = arrow2Ref.current
+
+ if (angle1Value !== 0 && length1Value !== 0 && angle2Value !== 0 && arrow1Value !== '' && arrow2Value !== '') {
+ if (arrow1Value === '↓' && arrow2Value === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }]
+ })
+ } else if (arrow1Value === '↓' && arrow2Value === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }]
+ })
+ } else if (arrow1Value === '↑' && arrow2Value === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10 }]
+ })
+ } else if (arrow1Value === '↑' && arrow2Value === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10 }]
+ })
+ } else if (arrow1Value === '→' && arrow2Value === '↓') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }]
+ })
+ } else if (arrow1Value === '→' && arrow2Value === '↑') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }]
+ })
+ } else if (arrow1Value === '←' && arrow2Value === '↓') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }]
+ })
+ } else if (arrow1Value === '←' && arrow2Value === '↑') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }]
+ })
+ }
+
+ angle1Ref.current.focus()
+ }
+ }
+
+ //대각선 완료될 경우 확인
+ const checkDiagonal = (direction) => {
+ const activeElem = document.activeElement
+ const diagonalLength = outerLineDiagonalLengthRef.current.value // 대각선 길이
+
+ const length1Value = length1Ref.current.value
+
+ if (diagonalLength <= length1Value) {
+ alert('대각선 길이는 직선 길이보다 길어야 합니다.')
+ return
+ }
+
+ const canDirection =
+ direction === '↓' || direction === '↑'
+ ? arrow1Ref.current === '←' || arrow1Ref.current === '→'
+ : arrow1Ref.current === '↓' || arrow1Ref.current === '↑'
+
+ if (activeElem === length1Ref.current) {
+ setArrow1(direction)
+ arrow1Ref.current = direction
+ } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) {
+ if (!canDirection) {
+ return
+ }
+ setArrow2(direction)
+ arrow2Ref.current = direction
+ }
+
+ const arrow1Value = arrow1Ref.current
+ const arrow2Value = arrow2Ref.current
+
+ const getLength2 = () => {
+ return Math.floor(Math.sqrt(diagonalLength ** 2 - length1Value ** 2))
+ }
+
+ const length2Value = getLength2()
+
+ if (diagonalLength !== 0 && length1Value !== 0 && arrow1Value !== '') {
+ setLength2(getLength2())
+ length2Ref.current.focus()
+ }
+
+ if (length1Value !== 0 && length2Value !== 0 && arrow1Value !== '' && arrow2Value !== '') {
+ if (arrow1Value === '↓' && arrow2Value === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }]
+ })
+ } else if (arrow1Value === '↓' && arrow2Value === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }]
+ })
+ } else if (arrow1Value === '↑' && arrow2Value === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }]
+ })
+ } else if (arrow1Value === '↑' && arrow2Value === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }]
+ })
+ } else if (arrow1Value === '→' && arrow2Value === '↓') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }]
+ })
+ } else if (arrow1Value === '→' && arrow2Value === '↑') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [
+ ...prev,
+ {
+ x: prev[prev.length - 1].x + length1Value / 10,
+ y: prev[prev.length - 1].y - length2Value / 10,
+ },
+ ]
+ })
+ }
+ }
+ }
+
+ const keydown = {
+ outerLine: (e) => {
+ if (points.length === 0) {
+ return
+ }
+ // 포커스가 length1에 있지 않으면 length1에 포커스를 줌
+ const activeElem = document.activeElement
+ if (activeElem !== length1Ref.current) {
+ length1Ref.current.focus()
+ }
+
+ const key = e.key
+
+ if (!length1Ref.current) {
+ return
+ }
+
+ const lengthNum = Number(length1Ref.current.value) / 10
+ if (lengthNum === 0) {
+ return
+ }
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ setArrow1('↓')
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x, y: prev[prev.length - 1].y + lengthNum }]
+ })
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ setArrow1('↑')
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x, y: prev[prev.length - 1].y - lengthNum }]
+ })
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ setArrow1('←')
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - lengthNum, y: prev[prev.length - 1].y }]
+ })
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ setArrow1('→')
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + lengthNum, y: prev[prev.length - 1].y }]
+ })
+ break
+ }
+ },
+ rightAngle: (e) => {
+ if (points.length === 0) {
+ return
+ }
+ const key = e.key
+
+ const activeElem = document.activeElement
+ if (activeElem !== length1Ref.current && activeElem !== length2Ref.current) {
+ length1Ref.current.focus()
+ }
+
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ checkRightAngle('↓')
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ checkRightAngle('↑')
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ checkRightAngle('←')
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ checkRightAngle('→')
+ break
+ case 'Enter':
+ break
+ }
+ },
+ doublePitch: (e) => {
+ if (points.length === 0) {
+ return
+ }
+ const key = e.key
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ checkDoublePitch('↓')
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ checkDoublePitch('↑')
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ checkDoublePitch('←')
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ checkDoublePitch('→')
+ break
+ }
+ },
+ angle: (e) => {
+ if (points.length === 0) {
+ return
+ }
+ const key = e.key
+ switch (key) {
+ case 'Enter': {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ const lastPoint = prev[prev.length - 1]
+ const length = length1Ref.current.value / 10
+ const angle = angle1Ref.current.value
+ //lastPoint로부터 angle1만큼의 각도로 length1만큼의 길이를 가지는 선을 그림
+ const radian = (angle * Math.PI) / 180
+
+ const x = lastPoint.x + length * Math.cos(radian)
+ const y = lastPoint.y - length * Math.sin(radian)
+ return [...prev, { x, y }]
+ })
+ }
+ }
+ },
+ diagonalLine: (e) => {
+ if (points.length === 0) {
+ return
+ }
+
+ const key = e.key
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ checkDiagonal('↓')
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ checkDiagonal('↑')
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ checkDiagonal('←')
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ checkDiagonal('→')
+ break
+ }
+ },
+ }
+
+ /**
+ * 일변전으로 돌아가기
+ */
+ const handleRollback = () => {
+ //points의 마지막 요소를 제거
+ setPoints((prev) => prev.slice(0, prev.length - 1))
+ }
+
+ const handleFix = () => {
+ if (points.length < 3) {
+ return
+ }
+
+ let isAllRightAngle = true
+
+ const firstPoint = points[0]
+
+ points.forEach((point, idx) => {
+ if (idx === 0 || !isAllRightAngle) {
+ return
+ }
+
+ const angle = calculateAngle(point, firstPoint)
+ if (angle % 90 !== 0) {
+ isAllRightAngle = false
+ }
+ })
+
+ if (isAllRightAngle) {
+ alert('부정확한 다각형입니다.')
+ return
+ }
+
+ setPoints((prev) => {
+ return [...prev, { x: prev[0].x, y: prev[0].y }]
+ })
+
+ isFix.current = true
+ }
+
+ return {
+ points,
+ setPoints,
+ length1,
+ setLength1,
+ length2,
+ setLength2,
+ length1Ref,
+ length2Ref,
+ arrow1,
+ setArrow1,
+ arrow2,
+ setArrow2,
+ arrow1Ref,
+ arrow2Ref,
+ angle1,
+ setAngle1,
+ angle1Ref,
+ angle2,
+ setAngle2,
+ angle2Ref,
+ outerLineDiagonalLength,
+ setOuterLineDiagonalLength,
+ outerLineDiagonalLengthRef,
+ type,
+ setType,
+ handleFix,
+ handleRollback,
+ }
+}
diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js
new file mode 100644
index 00000000..7eb421cc
--- /dev/null
+++ b/src/hooks/surface/useSurfaceShapeBatch.js
@@ -0,0 +1,595 @@
+'use client'
+
+import { useRecoilValue } from 'recoil'
+import { canvasState } from '@/store/canvasAtom'
+import { MENU, BATCH_TYPE, POLYGON_TYPE } from '@/common/common'
+import { getIntersectionPoint, setSurfaceShapePattern } from '@/util/canvas-util'
+import { degreesToRadians } from '@turf/turf'
+import { QPolygon } from '@/components/fabric/QPolygon'
+import { useSwal } from '@/hooks/useSwal'
+import { useMessage } from '@/hooks/useMessage'
+import { useEvent } from '@/hooks/useEvent'
+
+export function useSurfaceShapeBatch() {
+ const { getMessage } = useMessage()
+
+ const canvas = useRecoilValue(canvasState)
+ const { swalFire } = useSwal()
+ const { addCanvasMouseEventListener, initEvent } = useEvent()
+
+ const applySurfaceShape = (surfaceRefs, selectedType, setShowPlacementSurfaceSettingModal) => {
+ let length1, length2, length3, length4, length5
+ const surfaceId = selectedType?.id
+ const azimuth = surfaceRefs.azimuth.current
+
+ if (surfaceId === 1) {
+ length1 = surfaceRefs.length1.current.value
+ length2 = surfaceRefs.length2.current.value
+ length3 = surfaceRefs.lengthetc.current.value //대각선
+ } else if ([2, 4].includes(surfaceId)) {
+ length1 = surfaceRefs.length1.current.value
+ length2 = surfaceRefs.length2.current.value
+ } else if ([3, 5, 6, 15, 18].includes(surfaceId)) {
+ length1 = surfaceRefs.length1.current.value
+ length2 = surfaceRefs.length2.current.value
+ length3 = surfaceRefs.length3.current.value
+ } else if ([8, 12, 13, 16, 17].includes(surfaceId)) {
+ length1 = surfaceRefs.length1.current.value
+ length2 = surfaceRefs.length2.current.value
+ length3 = surfaceRefs.length3.current.value
+ length4 = surfaceRefs.length4.current.value
+ } else if ([7, 9, 10, 11, 14].includes(surfaceId)) {
+ length1 = surfaceRefs.length1.current.value
+ length2 = surfaceRefs.length2.current.value
+ length3 = surfaceRefs.length3.current.value
+ length4 = surfaceRefs.length4.current.value
+ length5 = surfaceRefs.length5.current.value
+ }
+
+ length1 = parseInt(length1 === undefined ? 0 : length1 / 10)
+ length2 = parseInt(length2 === undefined ? 0 : length2 / 10)
+ length3 = parseInt(length3 === undefined ? 0 : length3 / 10)
+ length4 = parseInt(length4 === undefined ? 0 : length4 / 10)
+ length5 = parseInt(length5 === undefined ? 0 : length5 / 10)
+
+ let isDrawing = true
+ let obj = null
+ let points = []
+ if (checkSurfaceShape(surfaceId, { length1, length2, length3, length4, length5 })) {
+ setShowPlacementSurfaceSettingModal(false)
+ addCanvasMouseEventListener('mouse:move', (e) => {
+ if (!isDrawing) {
+ return
+ }
+ const pointer = canvas?.getPointer(e.e)
+ canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH_TEMP))
+ points = getSurfaceShape(surfaceId, pointer, { length1, length2, length3, length4, length5 })
+
+ const options = {
+ fill: 'transparent',
+ stroke: 'black',
+ strokeWidth: 1,
+ strokeDasharray: [10, 4],
+ fontSize: 12,
+ selectable: true,
+ lockMovementX: true, // X 축 이동 잠금
+ lockMovementY: true, // Y 축 이동 잠금
+ lockRotation: true, // 회전 잠금
+ lockScalingX: true, // X 축 크기 조정 잠금
+ lockScalingY: true, // Y 축 크기 조정 잠금
+ name: MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH_TEMP,
+ flipX: surfaceRefs.yInversion,
+ flipY: surfaceRefs.xInversion,
+ angle: surfaceRefs.rotate,
+ originX: 'center',
+ originY: 'center',
+ }
+
+ obj = new QPolygon(points, options)
+ obj.setCoords() //좌표 변경 적용
+
+ canvas?.add(obj)
+
+ //각도 추가
+ let originAngle = 0 //기본 남쪽
+ let direction = 'south'
+ if (azimuth === 'left') {
+ //서
+ originAngle = 90
+ direction = 'west'
+ } else if (azimuth === 'right') {
+ //동
+ originAngle = 270
+ direction = 'east'
+ } else if (azimuth === 'up') {
+ //북
+ originAngle = 180
+ direction = 'north'
+ }
+
+ obj.set({ direction: direction })
+ obj.set({ originAngle: originAngle })
+
+ canvas?.renderAll()
+ })
+
+ addCanvasMouseEventListener('mouse:down', (e) => {
+ isDrawing = false
+ obj.set('name', POLYGON_TYPE.ROOF)
+ initEvent()
+ setSurfaceShapePattern(obj)
+ setShowPlacementSurfaceSettingModal(true)
+ })
+ }
+ }
+
+ //면형상 입력 validate
+ const checkSurfaceShape = (surfaceId, lengths) => {
+ const { length1, length2, length3, length4, length5 } = lengths
+ let check = true
+
+ if (surfaceId === 1) {
+ if (length1 === 0) {
+ swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
+ check = false
+ }
+ if (length2 === 0) {
+ if (length3 === 0) {
+ swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
+ check = false
+ }
+ }
+ } else if ([2, 4].includes(surfaceId)) {
+ if (length1 === 0 || length2 === 0) {
+ swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
+ check = false
+ }
+ } else if ([3, 5, 6, 15, 18].includes(surfaceId)) {
+ if (length1 === 0 || length2 === 0 || length3 === 0) {
+ swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
+ check = false
+ }
+ if (surfaceId === 3 && length3 > length1) {
+ swalFire({ text: getMessage('surface.shape.validate.size.1to3'), icon: 'error' })
+ check = false
+ }
+
+ if (surfaceId === 5 || surfaceId === 15) {
+ if (length3 >= length2) {
+ swalFire({ text: getMessage('surface.shape.validate.size.2to3'), icon: 'error' })
+ check = false
+ }
+ }
+ if (surfaceId === 18) {
+ if (length1 >= length2) {
+ swalFire({ text: getMessage('surface.shape.validate.size.1to2'), icon: 'error' })
+ check = false
+ }
+ if (length4 >= length3) {
+ swalFire({ text: getMessage('surface.shape.validate.size.3to4'), icon: 'error' })
+ check = false
+ }
+ }
+ } else if ([8, 12, 13, 16, 17].includes(surfaceId)) {
+ if (length1 === 0 || length2 === 0 || length3 === 0 || length4 === 0) {
+ swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
+ check = false
+ }
+
+ if (surfaceId === 8) {
+ if (length2 >= length1) {
+ swalFire({ text: getMessage('surface.shape.validate.size.1to2'), icon: 'error' })
+ check = false
+ }
+ if (length4 >= length3) {
+ swalFire({ text: getMessage('surface.shape.validate.size.3to4'), icon: 'error' })
+ check = false
+ }
+ }
+
+ if (surfaceId === 12 || surfaceId === 13 || surfaceId === 16 || surfaceId === 17) {
+ if (length2 >= length1) {
+ swalFire({ text: getMessage('surface.shape.validate.size.1to2'), icon: 'error' })
+ check = false
+ }
+
+ if (length4 >= length3) {
+ swalFire({ text: getMessage('surface.shape.validate.size.1to2'), icon: 'error' })
+ check = false
+ }
+ }
+ } else if ([7, 9, 10, 11, 14].includes(surfaceId)) {
+ if (length1 === 0 || length2 === 0 || length3 === 0 || length4 === 0 || length5 === 0) {
+ swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
+ check = false
+ }
+ if (surfaceId === 9 || surfaceId === 10 || surfaceId === 11) {
+ if (length2 + length3 >= length1) {
+ swalFire({ text: getMessage('surface.shape.validate.size.1to23'), icon: 'error' })
+ check = false
+ }
+ if (length5 >= length4) {
+ swalFire({ text: getMessage('surface.shape.validate.size.4to5'), icon: 'error' })
+ check = false
+ }
+ }
+
+ if (surfaceId === 14) {
+ if (length2 + length3 >= length1) {
+ swalFire({ text: getMessage('surface.shape.validate.size.1to23'), icon: 'error' })
+ check = false
+ }
+ if (length5 >= length4) {
+ swalFire({ text: getMessage('surface.shape.validate.size.4to5'), icon: 'error' })
+ check = false
+ }
+ }
+ }
+ return check
+ }
+
+ //면형상 가져오기
+ const getSurfaceShape = (surfaceId, pointer, lengths) => {
+ let points = []
+ const { length1, length2, length3, length4, length5 } = lengths
+
+ switch (surfaceId) {
+ case 1: {
+ let newLength2 = length2
+
+ if (length3 !== 0) {
+ newLength2 = Math.sqrt(length3 ** 2 - (length1 / 2) ** 2)
+ }
+
+ points = [
+ { x: pointer.x, y: pointer.y - parseInt(newLength2) / 2 },
+ { x: pointer.x - parseInt(length1) / 2, y: pointer.y + parseInt(newLength2) / 2 },
+ { x: pointer.x + parseInt(length1) / 2, y: pointer.y + parseInt(newLength2) / 2 },
+ ]
+
+ break
+ }
+ case 2: {
+ points = [
+ { x: pointer.x - length1 / 2, y: pointer.y + length2 / 2 },
+ { x: pointer.x + length1 / 2, y: pointer.y + length2 / 2 },
+ { x: pointer.x + length1 / 2, y: pointer.y - length2 / 2 },
+ { x: pointer.x - length1 / 2, y: pointer.y - length2 / 2 },
+ ]
+
+ break
+ }
+ case 3: {
+ points = [
+ { x: pointer.x - length1 / 2, y: pointer.y + length2 / 2 },
+ { x: pointer.x + length1 / 2, y: pointer.y + length2 / 2 },
+ { x: pointer.x + length3 / 2, y: pointer.y - length2 / 2 },
+ { x: pointer.x - length3 / 2, y: pointer.y - length2 / 2 },
+ ]
+
+ break
+ }
+ case 4: {
+ points = [
+ { x: pointer.x - length1 / 2, y: pointer.y + length2 / 2 },
+ { x: pointer.x + length1 / 2, y: pointer.y + length2 / 2 },
+ { x: pointer.x + length1 / 2, y: pointer.y - length2 / 2 },
+ ]
+
+ break
+ }
+ case 5: {
+ points = [
+ { x: pointer.x - length1 / 2, y: pointer.y - length3 / 2 },
+ { x: pointer.x - length1 / 2, y: pointer.y + length3 / 2 },
+ { x: pointer.x + length1 / 2, y: pointer.y + length3 / 2 },
+ { x: pointer.x + length1 / 2, y: pointer.y + length3 / 2 - length2 },
+ ]
+
+ break
+ }
+ case 6: {
+ const angleInRadians = Math.asin(length2 / length3)
+ points = [
+ { x: pointer.x - length1 / 2, y: pointer.y + length2 / 2 },
+ { x: pointer.x - length1 / 2 + length3 * Math.cos(angleInRadians), y: pointer.y + length2 / 2 - length3 * Math.sin(angleInRadians) },
+ { x: pointer.x + length1 / 2 + length3 * Math.cos(angleInRadians), y: pointer.y + length2 / 2 - length3 * Math.sin(angleInRadians) },
+ { x: pointer.x + length1 / 2, y: pointer.y + length2 / 2 },
+ ]
+
+ break
+ }
+ case 7: {
+ points = [
+ { x: pointer.x - (length1 + length2 + length3) / 2, y: pointer.y - (length4 + length5) / 2 },
+ { x: pointer.x - (length1 + length2 + length3) / 2, y: pointer.y + (length4 + length5) / 2 },
+ { x: pointer.x - (length1 + length2 + length3) / 2 + length1, y: pointer.y + (length4 + length5) / 2 },
+ { x: pointer.x - (length1 + length2 + length3) / 2 + length1, y: pointer.y + (length4 + length5) / 2 - length5 },
+ { x: pointer.x - (length1 + length2 + length3) / 2 + length1 + length2, y: pointer.y + (length4 + length5) / 2 - length5 },
+ { x: pointer.x - (length1 + length2 + length3) / 2 + length1 + length2, y: pointer.y + (length4 + length5) / 2 - length5 + length5 },
+ {
+ x: pointer.x - (length1 + length2 + length3) / 2 + length1 + length2 + length3,
+ y: pointer.y + (length4 + length5) / 2 - length5 + length5,
+ },
+ {
+ x: pointer.x - (length1 + length2 + length3) / 2 + length1 + length2 + length3,
+ y: pointer.y + (length4 + length5) / 2 - length5 + length5 - (length4 + length5),
+ },
+ ]
+
+ break
+ }
+ case 8: {
+ points = [
+ { x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 },
+ { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 },
+ { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length3 },
+ { x: pointer.x - length1 / 2 + length1 - (length1 - length2), y: pointer.y + length4 / 2 - length3 },
+ { x: pointer.x - length1 / 2 + length1 - (length1 - length2), y: pointer.y + length4 / 2 - length3 + (length3 - length4) },
+ { x: pointer.x - length1 / 2 + length1 - (length1 - length2) - length2, y: pointer.y + length4 / 2 - length3 + (length3 - length4) },
+ ]
+
+ break
+ }
+ case 9: {
+ points = [
+ { x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 },
+ { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 },
+ { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 },
+ { x: pointer.x - length1 / 2 + length1 - length3, y: pointer.y + length4 / 2 - length4 },
+ { x: pointer.x - length1 / 2 + length1 - length3, y: pointer.y + length4 / 2 - length4 + (length4 - length5) },
+ { x: pointer.x - length1 / 2 + length1 - length3 - length2, y: pointer.y + length4 / 2 - length4 + (length4 - length5) },
+ ]
+ break
+ }
+ case 10: {
+ points = [
+ { x: pointer.x + length1 / 2, y: pointer.y + length4 / 2 },
+ { x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 },
+ { x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 - length5 },
+ { x: pointer.x + length1 / 2 - length1 + length2, y: pointer.y + length4 / 2 - length5 },
+ { x: pointer.x + length1 / 2 - length1 + length2, y: pointer.y + length4 / 2 - length5 - (length4 - length5) },
+ { x: pointer.x + length1 / 2 - length1 + length2 + length3, y: pointer.y + length4 / 2 - length5 - (length4 - length5) },
+ ]
+ break
+ }
+ case 11: {
+ points = [
+ { x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 },
+ { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 },
+ { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length5 },
+ { x: pointer.x - length1 / 2 + length1 - length2, y: pointer.y + length4 / 2 - length5 },
+ { x: pointer.x - length1 / 2 + length1 - length2, y: pointer.y + length4 / 2 - length5 - (length4 - length5) },
+ { x: pointer.x - length1 / 2 + length1 - length2 - length3, y: pointer.y + length4 / 2 - length5 - (length4 - length5) },
+ ]
+ break
+ }
+ case 12: {
+ const leftHypotenuse = Math.sqrt(((length1 - length2) / 2) ** 2 + length3 ** 2)
+ const rightHypotenuse = (length4 / length3) * leftHypotenuse
+
+ const leftAngle = Math.acos((length1 - length2) / 2 / leftHypotenuse)
+
+ points = [
+ { x: pointer.x - length1 / 2 + leftHypotenuse * Math.cos(leftAngle), y: pointer.y + length3 / 2 - leftHypotenuse * Math.sin(leftAngle) },
+ { x: pointer.x - length1 / 2, y: pointer.y + length3 / 2 },
+ { x: pointer.x + length1 / 2, y: pointer.y + length3 / 2 },
+ {
+ x: pointer.x + length1 / 2 - rightHypotenuse * Math.cos(leftAngle),
+ y: pointer.y + length3 / 2 - rightHypotenuse * Math.sin(leftAngle),
+ },
+ {
+ x: pointer.x + length1 / 2 - rightHypotenuse * Math.cos(leftAngle) - length2,
+ y: pointer.y + length3 / 2 - rightHypotenuse * Math.sin(leftAngle),
+ },
+ ]
+
+ break
+ }
+ case 13: {
+ const pointsArray = [
+ { x: 0, y: 0 },
+ { x: 0, y: 0 },
+ { x: 0, y: 0 },
+ { x: 0, y: 0 },
+ { x: 0, y: 0 },
+ ]
+
+ const tmpPolygon = new QPolygon(
+ [
+ { x: 0, y: length3 },
+ { x: length1 - length2, y: length3 },
+ { x: (length1 - length2) / 2, y: length3 - length3 },
+ ],
+ {
+ fill: 'transparent',
+ stroke: 'black', //black
+ strokeWidth: 2,
+ selectable: true,
+ fontSize: 0,
+ },
+ )
+
+ const coord = getIntersectionPoint(tmpPolygon.lines[1].startPoint, tmpPolygon.lines[2].startPoint, length3 - length4)
+ const scale = (length1 - length2) / coord.x
+
+ tmpPolygon.set({ scaleX: scale })
+
+ pointsArray[0].x = 0
+ pointsArray[0].y = length3 //바닥면부터 시작하게
+ pointsArray[1].x = pointsArray[0].x + length1
+ pointsArray[1].y = pointsArray[0].y
+ pointsArray[2].x = pointsArray[1].x
+ pointsArray[2].y = pointsArray[1].y - length4
+ pointsArray[3].x = pointsArray[2].x - length2
+ pointsArray[3].y = pointsArray[2].y
+ pointsArray[4].x = tmpPolygon.getCurrentPoints()[2].x
+ pointsArray[4].y = tmpPolygon.getCurrentPoints()[2].y
+
+ points = [
+ {
+ x: pointer.x - length1 / 2,
+ y: pointer.y + length4 / 2,
+ },
+ {
+ x: pointer.x + length1 / 2,
+ y: pointer.y + length4 / 2,
+ },
+ {
+ x: pointer.x + length1 / 2,
+ y: pointer.y - length4 / 2,
+ },
+ {
+ x: pointer.x - (length2 - length1 / 2),
+ y: pointer.y - length4 / 2,
+ },
+ {
+ x: pointer.x - length1 / 2 + pointsArray[4].x,
+ y: pointer.y - length3 + length4 / 2,
+ },
+ ]
+
+ break
+ }
+ case 14: {
+ points = [
+ { x: pointer.x - length1 / 2 + length2, y: pointer.y + length4 / 2 },
+ { x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 },
+ { x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 - length4 },
+ { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 },
+ { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 + length4 },
+ { x: pointer.x - length1 / 2 + length1 - length3, y: pointer.y + length4 / 2 - length4 + length4 },
+ { x: pointer.x - length1 / 2 + length2 + (length1 - length2 - length3) / 2, y: pointer.y + length4 / 2 - length4 + length5 },
+ ]
+ break
+ }
+
+ case 15: {
+ points = [
+ { x: pointer.x - length1 / 2, y: pointer.y + length2 - length2 / 2 },
+ { x: pointer.x - length1 / 2, y: pointer.y + length2 - length2 / 2 - length3 },
+ { x: pointer.x, y: pointer.y + length2 - length2 / 2 - length3 - (length2 - length3) },
+ { x: pointer.x + length1 / 2, y: pointer.y + length2 - length2 / 2 - length3 },
+ { x: pointer.x + length1 / 2, y: pointer.y + length2 - length2 / 2 - length3 + length3 },
+ ]
+ break
+ }
+
+ case 16: {
+ points = [
+ {
+ x: pointer.x - length1 / 2,
+ y: pointer.y + length3 / 2,
+ },
+ {
+ x: pointer.x - length1 / 2 + (length1 - length2) / 2,
+ y: pointer.y + length3 / 2 - (length3 - length4),
+ },
+ {
+ x: pointer.x - length1 / 2 + (length1 - length2) / 2,
+ y: pointer.y + length3 / 2 - (length3 - length4) - length4,
+ },
+ {
+ x: pointer.x - length1 / 2 + (length1 - length2) / 2 + length2,
+ y: pointer.y + length3 / 2 - (length3 - length4) - length4,
+ },
+ {
+ x: pointer.x - length1 / 2 + (length1 - length2) / 2 + length2,
+ y: pointer.y + length3 / 2 - (length3 - length4) - length4 + length4,
+ },
+ {
+ x: pointer.x - length1 / 2 + length1,
+ y: pointer.y + length3 / 2,
+ },
+ ]
+ break
+ }
+ case 17: {
+ const angle = (Math.asin(length3 / length4) * 180) / Math.PI // 높이와 빗변으로 먼저 각도구하기
+
+ const topL = (length1 - length2) / 2 / Math.cos((angle * Math.PI) / 180) // 꺽이는부분 윗쪽 길이
+
+ points = [
+ {
+ x: pointer.x - length1 / 2 + length1,
+ y: pointer.y + length3 / 2,
+ },
+ {
+ x: pointer.x - length1 / 2,
+ y: pointer.y + length3 / 2,
+ },
+ {
+ x: pointer.x - length1 / 2 + length4 * Math.cos(angle),
+ y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)),
+ },
+ {
+ x: pointer.x - length1 / 2 + length4 * Math.cos(degreesToRadians(angle)) + length2,
+ y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)),
+ },
+ {
+ x: pointer.x - length1 / 2 + length4 * Math.cos(degreesToRadians(angle)) + length2 + topL * Math.cos(degreesToRadians(angle)),
+ y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)) + topL * Math.sin(degreesToRadians(angle)),
+ },
+ ]
+ break
+ }
+
+ case 18: {
+ const a = Math.sqrt(length3 * length3 - length2 * length2) // 입력된 밑변과 높이
+
+ const sinA = a / length3
+ const angleInRadians = Math.asin(sinA)
+ const angleInDegrees = angleInRadians * (180 / Math.PI)
+ const b = a - length1 / 2
+
+ const c = b / Math.tan(angleInRadians)
+ const d = Math.sqrt(b * b + c * c)
+ const newAngleInRadians = (90 - angleInDegrees) * (Math.PI / 180)
+ points = [
+ { x: pointer.x - (length1 + b) / 2, y: pointer.y + length2 / 2 },
+ { x: pointer.x + length1 / 2 - b / 2, y: pointer.y + length2 / 2 },
+ {
+ x: pointer.x + length1 / 2 - b / 2 + d * Math.cos(newAngleInRadians),
+ y: pointer.y + length2 / 2 - d * Math.sin(newAngleInRadians),
+ },
+ {
+ x: pointer.x - (length1 + b) / 2 + length3 * Math.cos(newAngleInRadians),
+ y: pointer.y + length2 / 2 - length3 * Math.sin(newAngleInRadians),
+ },
+ ]
+ break
+ }
+ }
+
+ return points
+ }
+
+ const deleteAllSurfacesAndObjects = () => {
+ swalFire({
+ text: '배치면 내용을 전부 삭제하시겠습니까?',
+ type: 'confirm',
+ confirmFn: () => {
+ canvas?.getObjects().forEach((obj) => {
+ if (
+ obj.name === POLYGON_TYPE.ROOF ||
+ obj.name === BATCH_TYPE.OPENING ||
+ obj.name === BATCH_TYPE.SHADOW ||
+ obj.name === BATCH_TYPE.TRIANGLE_DORMER ||
+ obj.name === BATCH_TYPE.PENTAGON_DORMER
+ ) {
+ canvas?.remove(obj)
+ }
+ })
+ swalFire({ text: '삭제 완료 되었습니다.' })
+ },
+ // denyFn: () => {
+ // swalFire({ text: '취소되었습니다.', icon: 'error' })
+ // },
+ })
+ }
+
+ return {
+ applySurfaceShape,
+ deleteAllSurfacesAndObjects,
+ }
+}
diff --git a/src/hooks/useAdsorptionPoint.js b/src/hooks/useAdsorptionPoint.js
index 44e2495b..49d49714 100644
--- a/src/hooks/useAdsorptionPoint.js
+++ b/src/hooks/useAdsorptionPoint.js
@@ -26,7 +26,7 @@ export function useAdsorptionPoint() {
top: pointer.y - 3,
x: pointer.x,
y: pointer.y,
- selectable: false,
+ selectable: true,
name: 'adsorptionPoint',
})
diff --git a/src/hooks/useAxios.js b/src/hooks/useAxios.js
index a1ecda2a..c125c28a 100644
--- a/src/hooks/useAxios.js
+++ b/src/hooks/useAxios.js
@@ -5,32 +5,40 @@ const AxiosType = {
EXTERNAL: 'External',
}
+/**
+ * axios 인스턴스 생성 후 반환
+ * @param {String} lang
+ * @returns http request instance - get, post, put, patch, delete (promise 접수사가 붙은 함수는 promise 반환)
+ */
export function useAxios(lang = '') {
const getInstances = (url) => {
+ /**
+ * url이 http로 시작하면 외부 서버로 판단
+ */
let type = AxiosType.INTERNAL
url.startsWith('http') ? (type = AxiosType.EXTERNAL) : ''
+ /**
+ * 내부 서버로 요청 시 lang 헤더 추가
+ */
+ let headerValue = {
+ Accept: 'application/json',
+ }
+ url.startsWith('https') ? '' : (headerValue['lang'] = lang)
+
return axios.create({
baseURL: type === AxiosType.INTERNAL ? process.env.NEXT_PUBLIC_API_SERVER_PATH : '',
- headers: {
- Accept: 'application/json',
- lang,
- },
+ headers: headerValue,
})
}
+ // request 추가 로직
axios.interceptors.request.use((config) => {
- // config['Authorization'] = localStorage.getItem('token')
- //TODO: 인터셉터에서 추가 로직 구현
return config
})
- axios.interceptors.request.use(undefined, (error) => {
- //TODO: 인터셉터에서 에러 처리 로직 구현
- // if (error.isAxiosError && e.response?.status === 401) {
- // localStorage.removeItem('token')
- // }
- })
+ // response 추가 로직
+ axios.interceptors.request.use(undefined, (error) => {})
const get = async ({ url }) => {
return await getInstances(url)
@@ -72,6 +80,10 @@ export function useAxios(lang = '') {
.catch(console.error)
}
+ const promisePatch = async ({ url, data }) => {
+ return await getInstances(url).patch(url, data)
+ }
+
const del = async ({ url }) => {
return await getInstances(url)
.delete(url)
@@ -79,5 +91,9 @@ export function useAxios(lang = '') {
.catch(console.error)
}
- return { get, promiseGet, post, promisePost, put, promisePut, patch, del }
+ const promiseDel = async ({ url }) => {
+ return await getInstances(url).delete(url)
+ }
+
+ return { get, promiseGet, post, promisePost, put, promisePut, patch, promisePatch, del, promiseDel }
}
diff --git a/src/hooks/useCanvas.js b/src/hooks/useCanvas.js
index 37ffdc5f..a33c0de2 100644
--- a/src/hooks/useCanvas.js
+++ b/src/hooks/useCanvas.js
@@ -11,7 +11,7 @@ import { defineQLine } from '@/util/qline-utils'
import { defineQPloygon } from '@/util/qpolygon-utils'
import { writeImage } from '@/lib/canvas'
import { useCanvasEvent } from '@/hooks/useCanvasEvent'
-import { post } from '@/lib/Axios'
+import { useAxios } from '@/hooks/useAxios'
import { v4 as uuidv4 } from 'uuid'
export function useCanvas(id) {
@@ -22,6 +22,7 @@ export function useCanvas(id) {
const [canvasSize] = useRecoilState(canvasSizeState)
const [fontSize] = useRecoilState(fontSizeState)
const { setCanvasForEvent, attachDefaultEventOnCanvas } = useCanvasEvent()
+ const { post } = useAxios()
/**
* 처음 셋팅
@@ -36,6 +37,7 @@ export function useCanvas(id) {
setCanvas(c)
setCanvasForEvent(c)
+ attachDefaultEventOnCanvas()
return () => {
// c.dispose()
@@ -45,7 +47,6 @@ export function useCanvas(id) {
useEffect(() => {
// canvas 사이즈가 변경되면 다시
- attachDefaultEventOnCanvas()
}, [canvasSize])
useEffect(() => {
@@ -91,6 +92,13 @@ export function useCanvas(id) {
// settings for all canvas in the app
fabric.Object.prototype.transparentCorners = false
fabric.Object.prototype.id = uuidv4()
+ fabric.Object.prototype.selectable = true
+ fabric.Object.prototype.lockMovementX = true
+ fabric.Object.prototype.lockMovementY = true
+ fabric.Object.prototype.lockRotation = true
+ fabric.Object.prototype.lockScalingX = true
+ fabric.Object.prototype.lockScalingY = true
+
fabric.Object.prototype.cornerColor = '#2BEBC8'
fabric.Object.prototype.cornerStyle = 'rect'
fabric.Object.prototype.cornerStrokeColor = '#2BEBC8'
diff --git a/src/hooks/useCanvasEvent.js b/src/hooks/useCanvasEvent.js
index 55a34cb2..cf3b6244 100644
--- a/src/hooks/useCanvasEvent.js
+++ b/src/hooks/useCanvasEvent.js
@@ -1,6 +1,6 @@
import { useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
-import { canvasSizeState, currentObjectState } from '@/store/canvasAtom'
+import { canvasSizeState, currentObjectState, fontFamilyState, fontSizeState } from '@/store/canvasAtom'
import { QPolygon } from '@/components/fabric/QPolygon'
// 캔버스에 필요한 이벤트
@@ -8,6 +8,8 @@ export function useCanvasEvent() {
const [canvas, setCanvasForEvent] = useState(null)
const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
const canvasSize = useRecoilValue(canvasSizeState)
+ const fontSize = useRecoilValue(fontSizeState)
+ const fontFamily = useRecoilValue(fontFamilyState)
// 기본적인 이벤트 필요시 추가
const attachDefaultEventOnCanvas = () => {
@@ -74,9 +76,16 @@ export function useCanvasEvent() {
})
}
+ if (target.type.toLowerCase().includes('text')) {
+ target.set({ fontSize })
+ target.set({ fontFamily })
+ }
+
if (target.name === 'lengthText') {
const x = target.left
const y = target.top
+ target.lockMovementX = false
+ target.lockMovementY = false
// Add a property to store the previous value
const previousValue = target.text
target.on('selected', (e) => {
diff --git a/src/hooks/useContextMenu.js b/src/hooks/useContextMenu.js
new file mode 100644
index 00000000..665617ef
--- /dev/null
+++ b/src/hooks/useContextMenu.js
@@ -0,0 +1,160 @@
+import { useRecoilValue } from 'recoil'
+import { currentMenuState } from '@/store/canvasAtom'
+import { useEffect, useState } from 'react'
+import { MENU } from '@/common/common'
+import AuxiliaryMove from '@/components/floor-plan/modal/auxiliary/AuxiliaryMove'
+import AuxiliarySize from '@/components/floor-plan/modal/auxiliary/AuxiliarySize'
+
+export function useContextMenu() {
+ const currentMenu = useRecoilValue(currentMenuState)
+ const [contextMenu, setContextMenu] = useState([[]])
+ const [currentContextMenu, setCurrentContextMenu] = useState(null)
+
+ useEffect(() => {
+ switch (currentMenu) {
+ case MENU.PLAN_DRAWING:
+ setContextMenu([
+ [
+ {
+ id: 'gridMove',
+ name: '그리드 이동',
+ },
+ {
+ id: 'gridCopy',
+ name: '그리드 복사',
+ },
+ {
+ id: 'gridColorEdit',
+ name: '그리드 색 변경',
+ },
+ {
+ id: 'remove',
+ name: '삭제',
+ },
+ {
+ id: 'removeAll',
+ name: '전체 삭제',
+ },
+ ],
+ ])
+ break
+ case MENU.ROOF_COVERING.EXTERIOR_WALL_LINE:
+ case MENU.ROOF_COVERING.ROOF_SHAPE_SETTINGS:
+ case MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS:
+ case MENU.ROOF_COVERING.ROOF_SHAPE_EDITING:
+ case MENU.ROOF_COVERING.HELP_LINE_DRAWING:
+ case MENU.ROOF_COVERING.EAVES_KERAVA_EDIT:
+ case MENU.ROOF_COVERING.MOVEMENT_SHAPE_UPDOWN:
+ case MENU.ROOF_COVERING.OUTLINE_EDIT_OFFSET:
+ case MENU.ROOF_COVERING.ROOF_SHAPE_ALLOC:
+ case MENU.ROOF_COVERING.DEFAULT:
+ setContextMenu([
+ [
+ {
+ id: 'roofMaterialPlacement',
+ name: '지붕재 배치',
+ },
+ {
+ id: 'roofMaterialRemove',
+ name: '지붕재 삭제',
+ },
+ {
+ id: 'roofMaterialRemoveAll',
+ name: '지붕재 전체 삭제',
+ },
+ {
+ id: 'selectMove',
+ name: '선택・이동',
+ },
+ {
+ id: 'wallLineRemove',
+ name: '외벽선 삭제',
+ },
+ ],
+ [
+ {
+ id: 'sizeEdit',
+ name: '사이즈 변경',
+ component: ,
+ },
+ {
+ id: 'auxiliaryMove',
+ name: '보조선 이동(M)',
+ component: ,
+ },
+ {
+ id: 'auxiliaryCopy',
+ name: '보조선 복사(C)',
+ },
+ {
+ id: 'auxiliaryRemove',
+ name: '보조선 삭제(D)',
+ },
+ {
+ id: 'auxiliaryVerticalBisector',
+ name: '보조선 수직이등분선',
+ },
+ {
+ id: 'auxiliaryCut',
+ name: '보조선 절삭',
+ },
+ {
+ id: 'auxiliaryRemoveAll',
+ name: '보조선 전체 삭제',
+ },
+ ],
+ ])
+ break
+ case MENU.BATCH_CANVAS.SLOPE_SETTING:
+ case MENU.BATCH_CANVAS.BATCH_DRAWING:
+ case MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH:
+ case MENU.BATCH_CANVAS.OBJECT_BATCH:
+ case MENU.BATCH_CANVAS.ALL_REMOVE:
+ case MENU.BATCH_CANVAS.DEFAULT:
+ setContextMenu([
+ [
+ {
+ id: 'sizeEdit',
+ name: '사이즈 변경',
+ },
+ {
+ id: 'remove',
+ name: '삭제(D)',
+ },
+ {
+ id: 'move',
+ name: '이동(M)',
+ },
+ {
+ id: 'copy',
+ name: '복사(C)',
+ },
+ ],
+ [
+ {
+ id: 'roofMaterialEdit',
+ name: '지붕재 변경',
+ },
+ {
+ id: 'linePropertyEdit',
+ name: '각 변 속성 변경',
+ },
+ {
+ id: 'flowDirectionEdit',
+ name: '흐름 방향 변경',
+ },
+ ],
+ ])
+ break
+ default:
+ setContextMenu([])
+ break
+ }
+ }, [currentMenu])
+
+ return {
+ contextMenu,
+ currentContextMenu,
+ setCurrentContextMenu,
+ }
+}
diff --git a/src/hooks/useDotLineGrid.js b/src/hooks/useDotLineGrid.js
index 34d82fa7..0c21c030 100644
--- a/src/hooks/useDotLineGrid.js
+++ b/src/hooks/useDotLineGrid.js
@@ -6,6 +6,7 @@ import { gridColorState } from '@/store/gridAtom'
export function useDotLineGrid() {
const canvas = useRecoilValue(canvasState)
+
const gridColor = useRecoilValue(gridColorState)
const [dotLineGridSetting, setDotLineGridSettingState] = useRecoilState(dotLineGridSettingState)
const interval = useRecoilValue(dotLineIntervalSelector) // 가로 세로 간격
diff --git a/src/hooks/useEvent.js b/src/hooks/useEvent.js
index 31a2cd8a..639b61b3 100644
--- a/src/hooks/useEvent.js
+++ b/src/hooks/useEvent.js
@@ -1,38 +1,35 @@
import { useEffect, useRef } from 'react'
-import { useRecoilState, useRecoilValue } from 'recoil'
-import {
- adsorptionPointAddModeState,
- adsorptionPointModeState,
- adsorptionRangeState,
- canvasState,
- canvasZoomState,
- currentMenuState,
-} from '@/store/canvasAtom'
+import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
+import { canvasState, canvasZoomState, currentMenuState, textModeState } from '@/store/canvasAtom'
import { fabric } from 'fabric'
-import { calculateDistance, calculateIntersection, distanceBetweenPoints, findClosestPoint } from '@/util/canvas-util'
+import { calculateDistance, distanceBetweenPoints, findClosestPoint } from '@/util/canvas-util'
import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint'
-import { useMouse } from '@/hooks/useMouse'
import { useDotLineGrid } from '@/hooks/useDotLineGrid'
import { useTempGrid } from '@/hooks/useTempGrid'
export function useEvent() {
const canvas = useRecoilValue(canvasState)
const currentMenu = useRecoilValue(currentMenuState)
- const keyboardEventListeners = useRef([])
+ const documentEventListeners = useRef([])
const mouseEventListeners = useRef([])
- const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
+ const setCanvasZoom = useSetRecoilState(canvasZoomState)
const { adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, getAdsorptionPoints, adsorptionPointAddModeStateEvent } = useAdsorptionPoint()
const { dotLineGridSetting, interval, getClosestLineGrid } = useDotLineGrid()
const { tempGridModeStateLeftClickEvent, tempGridMode, tempGridRightClickEvent } = useTempGrid()
+ const textMode = useRecoilValue(textModeState)
+
useEffect(() => {
+ initEvent()
+ }, [currentMenu, canvas, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, dotLineGridSetting, tempGridMode])
+
+ const initEvent = () => {
if (!canvas) {
return
}
- removeAllMouseEventListeners()
removeAllDocumentEventListeners()
-
+ removeAllMouseEventListeners()
/**
* wheelEvent
*/
@@ -40,13 +37,14 @@ export function useEvent() {
canvas?.on('mouse:wheel', wheelEvent)
addDefaultEvent()
- }, [currentMenu, canvas, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, dotLineGridSetting, tempGridMode])
+ }
const addDefaultEvent = () => {
//default Event 추가
addCanvasMouseEventListener('mouse:move', defaultMouseMoveEvent)
addCanvasMouseEventListener('mouse:out', defaultMouseOutEvent)
addDocumentEventListener('keydown', document, defaultKeyboardEvent)
+ addDocumentEventListener('contextmenu', document, defaultContextMenuEvent)
if (adsorptionPointAddMode) {
addCanvasMouseEventListener('mouse:down', adsorptionPointAddModeStateEvent)
}
@@ -193,13 +191,14 @@ export function useEvent() {
}
const addCanvasMouseEventListener = (eventType, handler) => {
+ canvas.off(eventType)
canvas.on(eventType, handler)
mouseEventListeners.current.push({ eventType, handler })
}
const removeAllMouseEventListeners = () => {
mouseEventListeners.current.forEach(({ eventType, handler }) => {
- canvas.off(eventType, handler)
+ canvas.off(eventType)
})
mouseEventListeners.current.length = 0 // 배열 초기화
}
@@ -211,24 +210,35 @@ export function useEvent() {
* @param handler
*/
const addDocumentEventListener = (eventType, element, handler) => {
+ removeDocumentEvent(eventType)
element.addEventListener(eventType, handler)
- keyboardEventListeners.current.push({ eventType, element, handler })
+ documentEventListeners.current.push({ eventType, element, handler })
}
/**
* document에 등록되는 event 제거
*/
const removeAllDocumentEventListeners = () => {
- keyboardEventListeners.current.forEach(({ eventType, element, handler }) => {
+ documentEventListeners.current.forEach(({ eventType, element, handler }) => {
element.removeEventListener(eventType, handler)
})
- keyboardEventListeners.current.length = 0 // 배열 초기화
+ documentEventListeners.current.length = 0 // 배열 초기화
}
- const removeMouseEvent = (type, handler) => {
+ const removeMouseEvent = (type) => {
mouseEventListeners.current = mouseEventListeners.current.filter((event) => {
- if (event.type === type && event.handler === handler) {
- canvas.off(type, handler)
+ if (event.eventType === type) {
+ canvas.off(type, event.handler)
+ return false
+ }
+ return true
+ })
+ }
+
+ const removeDocumentEvent = (type) => {
+ documentEventListeners.current = documentEventListeners.current.filter((event) => {
+ if (event.eventType === type) {
+ document.removeEventListener(event.eventType, event.handler)
return false
}
return true
@@ -241,5 +251,7 @@ export function useEvent() {
removeAllMouseEventListeners,
removeAllDocumentEventListeners,
removeMouseEvent,
+ removeMouseLine,
+ initEvent,
}
}
diff --git a/src/hooks/useLine.js b/src/hooks/useLine.js
index e701a2ff..f2941e33 100644
--- a/src/hooks/useLine.js
+++ b/src/hooks/useLine.js
@@ -1,5 +1,6 @@
import { useRecoilValue } from 'recoil'
import { canvasState, fontFamilyState, fontSizeState } from '@/store/canvasAtom'
+import { QLine } from '@/components/fabric/QLine'
export const useLine = () => {
const canvas = useRecoilValue(canvasState)
@@ -7,37 +8,50 @@ export const useLine = () => {
const fontFamily = useRecoilValue(fontFamilyState)
const addLine = (points = [], options) => {
- const line = new fabric.Line(points, {
+ const line = new QLine(points, {
...options,
- selectable: options.selectable ?? false,
- })
-
- canvas?.add(line)
- addLineText(line)
- return line
- }
-
- const addLineText = (line, length = getLengthByLine(line)) => {
- removeLineText(line)
-
- const lengthTxt = isNaN(Number(length)) ? length : length.toFixed(0)
-
- const text = new fabric.Text(lengthTxt, {
- left: (line.x2 + line.x1) / 2,
- top: (line.y2 + line.y1) / 2,
- parent: line,
- name: 'lengthTxt',
fontSize: fontSize,
fontFamily: fontFamily,
})
- canvas?.add(text)
- return text
+ if (line.length === 0) {
+ return null
+ }
+
+ canvas?.add(line)
+ return line
+ }
+
+ const hideLine = (line) => {
+ line.set({
+ visible: false,
+ })
+ canvas
+ ?.getObjects()
+ .find((obj) => obj.parent === line)
+ .set({
+ visible: false,
+ })
+ canvas?.renderAll()
+ }
+
+ const showLine = (line) => {
+ line.set({
+ visible: true,
+ })
+ canvas
+ ?.getObjects()
+ .find((obj) => obj.parent === line)
+ .set({
+ visible: true,
+ })
+ canvas?.renderAll()
}
const removeLine = (line) => {
removeLineText(line)
canvas?.remove(line)
+ canvas?.renderAll()
}
const removeLineText = (line) => {
@@ -60,5 +74,7 @@ export const useLine = () => {
return {
addLine,
removeLine,
+ hideLine,
+ showLine,
}
}
diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js
index c2b29121..858c13f7 100644
--- a/src/hooks/useMode.js
+++ b/src/hooks/useMode.js
@@ -39,6 +39,7 @@ import * as turf from '@turf/turf'
import { INPUT_TYPE, Mode } from '@/common/common'
import { m } from 'framer-motion'
import { set } from 'react-hook-form'
+import { FaWineGlassEmpty } from 'react-icons/fa6'
export function useMode() {
const [mode, setMode] = useRecoilState(modeState)
@@ -1266,7 +1267,8 @@ export function useMode() {
historyPoints.current = []
const wall = makePolygon(null, sort)
- wall.set({ name: 'wall' })
+ wall.name = 'wall'
+ // wall.set({ name: 'wall' })
return wall
}
@@ -1507,344 +1509,254 @@ export function useMode() {
*벽 지붕 외곽선 생성 polygon을 입력받아 만들기
*/
const handleOuterlinesTest2 = (polygon, offset = 50) => {
- const offsetPoints = offsetPolygon(polygon.points, offset)
+ const roof = drawRoofPolygon(polygon) //지붕 그리기
+ roof.drawHelpLine()
+ // roof.divideLine()
+ }
+
+ function inwardEdgeNormal(vertex1, vertex2) {
+ // Assuming that polygon vertices are in clockwise order
+ const dx = vertex2.x - vertex1.x
+ const dy = vertex2.y - vertex1.y
+ const edgeLength = Math.sqrt(dx * dx + dy * dy)
+
+ return {
+ x: -dy / edgeLength,
+ y: dx / edgeLength,
+ }
+ }
+
+ function outwardEdgeNormal(vertex1, vertex2) {
+ const n = inwardEdgeNormal(vertex1, vertex2)
+
+ return {
+ x: -n.x,
+ y: -n.y,
+ }
+ }
+
+ function createRoofPolygon(vertices) {
+ const edges = []
+ let minX = vertices.length > 0 ? vertices[0].x : undefined
+ let minY = vertices.length > 0 ? vertices[0].y : undefined
+ let maxX = minX
+ let maxY = minY
+
+ for (let i = 0; i < vertices.length; i++) {
+ const vertex1 = vertices[i]
+ const vertex2 = vertices[(i + 1) % vertices.length]
+
+ const outwardNormal = outwardEdgeNormal(vertex1, vertex2)
+
+ const inwardNormal = inwardEdgeNormal(vertex1, vertex2)
+
+ const edge = {
+ vertex1,
+ vertex2,
+ index: i,
+ outwardNormal,
+ inwardNormal,
+ }
+
+ edges.push(edge)
+
+ const x = vertices[i].x
+ const y = vertices[i].y
+ minX = Math.min(x, minX)
+ minY = Math.min(y, minY)
+ maxX = Math.max(x, maxX)
+ maxY = Math.max(y, maxY)
+ }
+
+ return {
+ vertices,
+ edges,
+ minX,
+ minY,
+ maxX,
+ maxY,
+ }
+ }
+
+ function createOffsetEdge(edge, dx, dy) {
+ return {
+ vertex1: {
+ x: edge.vertex1.x + dx,
+ y: edge.vertex1.y + dy,
+ },
+ vertex2: {
+ x: edge.vertex2.x + dx,
+ y: edge.vertex2.y + dy,
+ },
+ }
+ }
+
+ function edgesIntersection(edgeA, edgeB) {
+ const den =
+ (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) -
+ (edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y)
+
+ if (den === 0) {
+ return null // lines are parallel or coincident
+ }
+
+ const ua =
+ ((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
+ (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
+ den
+
+ const ub =
+ ((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
+ (edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
+ den
+
+ // Edges are not intersecting but the lines defined by them are
+ const isIntersectionOutside = ua < 0 || ub < 0 || ua > 1 || ub > 1
+
+ return {
+ x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x),
+ y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y),
+ isIntersectionOutside,
+ }
+ }
+
+ function createMarginPolygon(polygon, lines, arcSegments = 0) {
+ const offsetEdges = []
+
+ polygon.edges.forEach((edge, i) => {
+ const offset = lines[i % lines.length].attributes.offset
+ const dx = edge.outwardNormal.x * offset
+ const dy = edge.outwardNormal.y * offset
+ offsetEdges.push(createOffsetEdge(edge, dx, dy))
+ })
+
+ const vertices = []
+
+ offsetEdges.forEach((thisEdge, i) => {
+ const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
+ const vertex = edgesIntersection(prevEdge, thisEdge)
+ if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
+ vertices.push({
+ x: vertex.x,
+ y: vertex.y,
+ })
+ }
+ })
+
+ const marginPolygon = createRoofPolygon(vertices)
+ marginPolygon.offsetEdges = offsetEdges
+ return marginPolygon
+ }
+
+ function createPaddingPolygon(polygon, lines, arcSegments = 0) {
+ const offsetEdges = []
+
+ polygon.edges.forEach((edge, i) => {
+ const offset = lines[i % lines.length].attributes.offset
+ const dx = edge.inwardNormal.x * offset
+ const dy = edge.inwardNormal.y * offset
+ offsetEdges.push(createOffsetEdge(edge, dx, dy))
+ })
+
+ const vertices = []
+
+ offsetEdges.forEach((thisEdge, i) => {
+ const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
+ const vertex = edgesIntersection(prevEdge, thisEdge)
+ if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
+ vertices.push({
+ x: vertex.x,
+ y: vertex.y,
+ })
+ }
+ })
+
+ const paddingPolygon = createRoofPolygon(vertices)
+ paddingPolygon.offsetEdges = offsetEdges
+ return paddingPolygon
+ }
+
+ const drawRoofPolygon = (wall) => {
+ // TODO [ljyoung] : offset 입력 처리 후 제거 해야함.
+ /*wall.lines.forEach((line, index) => {
+ if (index === wall.lines.length - 1 || index === 3) {
+ line.attributes = {
+ type: 'gable',
+ offset: 30,
+ }
+ } else {
+ line.attributes = {
+ type: 'hip',
+ offset: 50,
+ }
+ }
+ })*/
+
+ const polygon = createRoofPolygon(wall.points)
+ const originPolygon = new QPolygon(wall.points, { fontSize: 0 })
+ originPolygon.setViewLengthText(false)
+ let offsetPolygon
+
+ let result = createMarginPolygon(polygon, wall.lines).vertices
+ const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point))
+
+ if (allPointsOutside) {
+ offsetPolygon = createMarginPolygon(polygon, wall.lines).vertices
+ } else {
+ offsetPolygon = createPaddingPolygon(polygon, wall.lines).vertices
+ }
const roof = makePolygon(
- offsetPoints.map((point) => {
+ offsetPolygon.map((point) => {
return { x1: point.x, y1: point.y }
}),
)
roof.name = 'roofBase'
- roof.setWall(polygon)
+ roof.setWall(wall)
+
+ roof.lines.forEach((line, index) => {
+ line.attributes = { type: wall.lines[index].attributes.type }
+ })
+
setRoof(roof)
- setWall(polygon)
+ setWall(wall)
- roof.drawHelpLine()
- roof.divideLine()
- }
-
- const drawRoofPolygon = (wall, offset = 50) => {
- console.log(wall)
- let points = wall.points,
- expandedPoints = []
-
- let minX = points[0].x,
- minY = points[0].y,
- maxX = points[0].x,
- maxY = points[0].y
-
- points.forEach((point) => {
- if (point.x < minX) minX = point.x
- if (point.y < minY) minY = point.y
- if (point.x > maxX) maxX = point.x
- if (point.y > maxY) maxY = point.y
- })
-
- console.log(points)
- points.forEach((point, index) => {
- const prevIndex = index === 0 ? points.length - 1 : index - 1
- const nextIndex = index === points.length - 1 ? 0 : index + 1
- point.direction = getDirectionByPoint(point, points[nextIndex])
- point.length = Math.abs(point.x - points[nextIndex].x) === 0 ? Math.abs(point.y - points[nextIndex].y) : Math.abs(point.x - points[nextIndex].x)
- // point.degree = Math.round(getDegreeBetweenTwoLines(points[prevIndex], point, points[nextIndex]))
- })
- console.log('points : ', points)
-
- points.forEach((currentWall, index) => {
- let prevWall = points[index === 0 ? points.length - 1 : index - 1]
- let nextWall = points[index === points.length - 1 ? 0 : index + 1]
- let isStartPointIn = minX < currentWall.x && currentWall.x < maxX && minY < currentWall.y && currentWall.y < maxY
- // let isEndPointIn = minX < currentWall.x2 && currentWall.x2 < maxX && minY < currentWall.y2 && currentWall.y2 < maxY
-
- if (prevWall.direction !== nextWall.direction) {
- if (currentWall.direction === 'top') {
- console.log('prevWall degree : ', 45)
- if (prevWall.direction === 'right') {
- let addLength = getLineOffsetPoint(prevWall, currentWall, nextWall, offset)
- expandedPoints.push({
- x: currentWall.x + addLength,
- y: currentWall.y + addLength,
- })
- }
- if (prevWall.direction === 'left') {
- console.log('prevWall degree : ', 225)
- expandedPoints.push({
- x: currentWall.x - offset,
- y: currentWall.y + offset,
- })
- }
- }
- if (currentWall.direction === 'bottom') {
- if (prevWall.direction === 'right') {
- console.log('prevWall degree : ', 45)
- let addLength = getLineOffsetPoint(prevWall, currentWall, nextWall, offset)
- console.log(currentWall.x, '-', offset, '+', addLength)
- console.log('addLength : ', addLength)
- expandedPoints.push({
- x: currentWall.x + addLength,
- y: currentWall.y - addLength,
- })
- }
- if (prevWall.direction === 'left') {
- console.log('prevWall degree : ', 315)
- let addLength = getLineOffsetPoint(prevWall, currentWall, nextWall, offset)
- console.log(currentWall.x, '-', offset, '+', addLength)
- console.log('addLength : ', addLength)
- expandedPoints.push({
- x: currentWall.x - offset,
- y: currentWall.y - offset,
- })
- }
- }
- if (currentWall.direction === 'right') {
- if (prevWall.direction === 'top') {
- if (isStartPointIn) {
- console.log('prevWall degree : ', 135)
- expandedPoints.push({
- x: currentWall.x + offset,
- y: currentWall.y + offset,
- })
- } else {
- console.log('prevWall degree : ', 315)
- expandedPoints.push({
- x: currentWall.x - offset,
- y: currentWall.y - offset,
- })
- }
- }
- if (prevWall.direction === 'bottom') {
- if (isStartPointIn) {
- console.log('prevWall degree : ', 45)
-
- expandedPoints.push({
- x: currentWall.x + offset,
- y: currentWall.y - offset,
- })
- } else {
- console.log('prevWall degree : ', 225)
- let addLength = getLineOffsetPoint(prevWall, currentWall, nextWall, offset)
- console.log('addLength : ', addLength)
- expandedPoints.push({
- x: currentWall.x - offset,
- y: currentWall.y + offset,
- })
- }
- }
- }
- if (currentWall.direction === 'left') {
- if (prevWall.direction === 'top') {
- if (isStartPointIn) {
- console.log('prevWall degree : ', 225)
- let addLength = getLineOffsetPoint(prevWall, currentWall, nextWall, offset)
- console.log('addLength : ', addLength)
- expandedPoints.push({
- x: currentWall.x - offset,
- y: currentWall.y + offset,
- })
- } else {
- console.log('prevWall degree : ', 45)
-
- let addLength = getLineOffsetPoint(prevWall, currentWall, nextWall, offset)
-
- expandedPoints.push({
- x: currentWall.x + offset,
- y: currentWall.y - offset,
- })
- }
- }
- if (prevWall.direction === 'bottom') {
- if (isStartPointIn) {
- console.log('prevWall degree : ', 315)
- expandedPoints.push({
- x: currentWall.x - offset,
- y: currentWall.y - offset,
- })
- } else {
- console.log('prevWall degree : ', 135)
- expandedPoints.push({
- x: currentWall.x + offset,
- y: currentWall.y + offset,
- })
- }
- }
- }
- } else {
- console.log('else :::: ')
- if (currentWall.direction === 'top') {
- if (prevWall.direction === 'right') {
- if (isStartPointIn) {
- console.log('prevWall degree : ', 315)
- expandedPoints.push({
- x: currentWall.x - offset,
- y: currentWall.y - offset,
- })
- } else {
- console.log('prevWall degree : ', 135)
- expandedPoints.push({
- x: currentWall.x + offset,
- y: currentWall.y + offset,
- })
- }
- }
- if (prevWall.direction === 'left') {
- if (isStartPointIn) {
- console.log('prevWall degree : ', 45)
- expandedPoints.push({
- x: currentWall.x + offset,
- y: currentWall.y - offset,
- })
- } else {
- console.log('prevWall degree : ', 225)
- expandedPoints.push({
- x: currentWall.x - offset,
- y: currentWall.y + offset,
- })
- }
- }
- }
- if (currentWall.direction === 'bottom') {
- if (prevWall.direction === 'right') {
- if (isStartPointIn) {
- console.log('prevWall degree : ', 225)
- expandedPoints.push({
- x: currentWall.x - offset,
- y: currentWall.y + offset,
- })
- } else {
- console.log('prevWall degree : ', 45)
- expandedPoints.push({
- x: currentWall.x + offset,
- y: currentWall.y - offset,
- })
- }
- }
- }
- if (currentWall.direction === 'right') {
- if (prevWall.direction === 'top') {
- if (isStartPointIn) {
- console.log('prevWall degree : ', 135)
- expandedPoints.push({
- x: currentWall.x + offset,
- y: currentWall.y + offset,
- })
- } else {
- console.log('prevWall degree : ', 315)
- expandedPoints.push({
- x: currentWall.x - offset,
- y: currentWall.y - offset,
- })
- }
- }
- if (prevWall.direction === 'bottom') {
- if (isStartPointIn) {
- console.log('prevWall degree : ', 225)
- expandedPoints.push({
- x: currentWall.x - offset,
- y: currentWall.y + offset,
- })
- } else {
- console.log('prevWall degree : ', 45)
- expandedPoints.push({
- x: currentWall.x + offset,
- y: currentWall.y - offset,
- })
- }
- }
- }
- if (currentWall.direction === 'left') {
- if (prevWall.direction === 'top') {
- if (isStartPointIn) {
- console.log('prevWall degree : ', 225)
- expandedPoints.push({
- x: currentWall.x - offset,
- y: currentWall.y + offset,
- })
- } else {
- console.log('prevWall degree : ', 45)
- expandedPoints.push({
- x: currentWall.x + offset,
- y: currentWall.y - offset,
- })
- }
- }
- if (prevWall.direction === 'bottom') {
- if (isStartPointIn) {
- console.log('prevWall degree : ', 135)
- expandedPoints.push({
- x: currentWall.x + offset,
- y: currentWall.y + offset,
- })
- } else {
- console.log('prevWall degree : ', 315)
- expandedPoints.push({
- x: currentWall.x - offset,
- y: currentWall.y - offset,
- })
- }
- }
- }
- }
- })
-
- console.log('expandedPoints : ', expandedPoints)
-
- /*const roof = new fabric.Polygon(expandedPoints, {
- fill: 'transparent',
- stroke: 'red',
- strokeWidth: 1,
- selectable: true,
- fontSize: fontSize,
- name: 'QPolygon1',
- })
-
- roof.wall = wall
- canvas?.add(roof)*/
-
- // roof.drawHelpLine()
+ return roof
}
/**
- * 구하려는 라인의 x1,y1좌표가 기준
- * @param line1 이전 라인
- * @param line2 현재 라인
- * @param line3 다음 라인
- * @param offset
- * @returns {number}
+ * 라인 사이가 지붕골 인지 확인.
+ * @param polygon
+ * @param line1
+ * @param line2
+ * @returns {boolean}
*/
- const getLineOffsetPoint = (line1, line2, line3, offset) => {
- //밑변
- let a = Math.abs(line1.x - line2.x)
- //빗변
- let c = Math.sqrt(Math.abs(line1.x - line2.x) ** 2 + Math.abs(line1.y - line2.y) ** 2)
+ const checkValley = (polygon, line1, line2) => {
+ let points = [
+ { x: line1.x1, y: line1.y1 },
+ { x: line1.x2, y: line1.y2 },
+ { x: line2.x1, y: line2.y1 },
+ { x: line2.x2, y: line2.y2 },
+ ]
+ points = Array.from(new Set(points.map((point) => JSON.stringify(point)))).map((point) => JSON.parse(point))
+ const centroidX = points.reduce((acc, point) => acc + point.x, 0) / points.length
+ const centroidY = points.reduce((acc, point) => acc + point.y, 0) / points.length
- console.log(a, c)
- //밑변과 빗변사이의 각도
- let alphaDegree = getDegreeBetweenTwoLines(line1, line2, line3)
- alphaDegree = alphaDegree <= 90 ? alphaDegree : 180 - alphaDegree
- alphaDegree = 90 - alphaDegree
- console.log('alphaDegree : ', alphaDegree)
+ let isValley = true
+ const pPoints = polygon.points
+ pPoints.forEach((point, index) => {
+ if (index < pPoints.length - 1) {
+ let j = index + 1
+ let xi = pPoints[index].x + polygon.left,
+ yi = pPoints[index].y + polygon.top
+ let xj = pPoints[j].x + polygon.left,
+ yj = pPoints[j].y + polygon.top
- // console.log('Math.tan(alphaDegree * (Math.PI / 180)) : ', Math.tan(alphaDegree * (Math.PI / 180)))
- console.log(Math.round(offset * Math.tan(alphaDegree * (Math.PI / 180))))
-
- const angle = getDegreeBetweenTwoLines(line1, line2, line3)
- const side1 = line1.length
- const side2 = line2.length
- const side3 = Math.sqrt(side1 ** 2 + side2 ** 2 - 2 * side1 * side2 * Math.cos(angle * (Math.PI / 180)))
- const beta = Math.round(Math.asin((side2 * Math.sin(angle * (Math.PI / 180))) / side3) * (180 / Math.PI) * 10) / 10
- const alpha = 180 - angle - beta
-
- console.log('angle : ', angle, 'alpha : ', alpha, 'beta : ', beta)
-
- const h = side2 * Math.sin(alpha * (Math.PI / 180))
- const h_new = h + offset
- console.log('빗변까지 길이 : ', h, 'offset 적용된 빗변까지 길이 : ', h_new)
- const newAdjacent = h_new / Math.sin(angle * (Math.PI / 180))
- console.log('offset 적용된 밑변 : ', newAdjacent)
-
- // offset = Math.sqrt(diffAdjacent ** 2 + diffAdjacent ** 2) / 2
- console.log('offset : ', offset)
- return offset
+ let intersect = yi > centroidY !== yj > centroidY && centroidX < ((xj - xi) * (centroidY - yi)) / (yj - yi) + xi
+ if (intersect) isValley = !isValley
+ }
+ })
+ return isValley
}
/**
@@ -4941,6 +4853,12 @@ export function useMode() {
)
}
+ const coordToTurfPolygon = (points) => {
+ const coordinates = points.map((point) => [point.x, point.y])
+ coordinates.push(coordinates[0])
+ return turf.polygon([coordinates])
+ }
+
/**
* trestle에서 영역을 가져와 mouse:move 이벤트로 해당 영역에 진입했을때 booleanPointInPolygon 로 진입여부를 확인
* 확인 후 셀을 이동시킴
@@ -4948,56 +4866,190 @@ export function useMode() {
const drawCellManualInTrestle = () => {
const trestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'trestle') //가대를 가져옴
+ const dormerTrestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'dormerTrestle') //도머 객체
+
if (trestlePolygons.length !== 0) {
- let lastPointPosition = { x: 0, y: 0 }
let fabricPolygon = null
let inside = false
let turfPolygon
- let manualDrawCells = drewRoofCells //
-
+ let manualDrawCells = drewRoofCells // 앞에서 자동으로 했을때 추가됨
+ let direction
+ let trestlePolygon
canvas.on('mouse:move', (e) => {
//마우스 이벤트 삭제 후 재추가
const mousePoint = canvas.getPointer(e.e)
- const turfPoint = turf.point([mousePoint.x, mousePoint.y])
for (let i = 0; i < trestlePolygons.length; i++) {
turfPolygon = polygonToTurfPolygon(trestlePolygons[i])
- if (turf.booleanPointInPolygon(turfPoint, turfPolygon)) {
- //turf에 보면 폴리곤안에 포인트가 있는지 함수가 있다
- const direction = trestlePolygons[i].direction //도형의 방향
- let width = direction === 'south' || direction === 'north' ? 172.2 : 113.4
- let height = direction === 'south' || direction === 'north' ? 113.4 : 172.2
- if (Math.abs(mousePoint.x - lastPointPosition.x) >= 5 || Math.abs(mousePoint.y - lastPointPosition.y) >= 5) {
- let isDrawing = false
+ trestlePolygon = trestlePolygons[i]
+ direction = trestlePolygons[i].direction //도형의 방향
+ let width = direction === 'south' || direction === 'north' ? 172 : 113
+ let height = direction === 'south' || direction === 'north' ? 113 : 172
- if (isDrawing) return
- canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tmpCell')) //움직일때 일단 지워가면서 움직임
+ const points = [
+ { x: mousePoint.x - width / 2, y: mousePoint.y - height / 2 },
+ { x: mousePoint.x + width / 2, y: mousePoint.y - height / 2 },
+ { x: mousePoint.x + width / 2, y: mousePoint.y + height / 2 },
+ { x: mousePoint.x - width / 2, y: mousePoint.y + height / 2 },
+ ]
- const points = [
- { x: mousePoint.x - width / 2, y: mousePoint.y - height / 2 },
- { x: mousePoint.x + width / 2, y: mousePoint.y - height / 2 },
- { x: mousePoint.x + width / 2, y: mousePoint.y + height / 2 },
- { x: mousePoint.x - width / 2, y: mousePoint.y + height / 2 },
- ]
+ const turfPoints = coordToTurfPolygon(points)
- fabricPolygon = new QPolygon(points, {
- fill: '#BFFD9F',
- stroke: 'black',
- selectable: false, // 선택 가능하게 설정
- lockMovementX: true, // X 축 이동 잠금
- lockMovementY: true, // Y 축 이동 잠금
- lockRotation: true, // 회전 잠금
- lockScalingX: true, // X 축 크기 조정 잠금
- lockScalingY: true, // Y 축 크기 조정 잠금
- opacity: 0.8,
- parentId: trestlePolygons[i].parentId,
- name: 'tmpCell',
+ if (turf.booleanWithin(turfPoints, turfPolygon)) {
+ let isDrawing = false
+
+ if (isDrawing) return
+ canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tmpCell')) //움직일때 일단 지워가면서 움직임
+
+ fabricPolygon = new fabric.Rect({
+ fill: 'white',
+ stroke: 'black',
+ strokeWidth: 1,
+ width: width,
+ height: height,
+ left: mousePoint.x - width / 2,
+ top: mousePoint.y - height / 2,
+ selectable: false,
+ lockMovementX: true,
+ lockMovementY: true,
+ lockRotation: true,
+ lockScalingX: true,
+ lockScalingY: true,
+ opacity: 0.8,
+ name: 'tmpCell',
+ parentId: trestlePolygons[i].parentId,
+ })
+
+ canvas?.add(fabricPolygon) //움직여가면서 추가됨
+
+ /**
+ * 스냅기능
+ */
+ let snapDistance = 10
+ let cellSnapDistance = 20
+
+ const trestleLeft = trestlePolygons[i].left
+ const trestleTop = trestlePolygons[i].top
+ const trestleRight = trestleLeft + trestlePolygons[i].width * trestlePolygons[i].scaleX
+ const trestleBottom = trestleTop + trestlePolygons[i].height * trestlePolygons[i].scaleY
+ const bigCenterY = (trestleTop + trestleTop + trestlePolygons[i].height) / 2
+
+ // 작은 폴리곤의 경계 좌표 계산
+ const smallLeft = fabricPolygon.left
+ const smallTop = fabricPolygon.top
+ const smallRight = smallLeft + fabricPolygon.width * fabricPolygon.scaleX
+ const smallBottom = smallTop + fabricPolygon.height * fabricPolygon.scaleY
+ const smallCenterX = smallLeft + (fabricPolygon.width * fabricPolygon.scaleX) / 2
+ const smallCenterY = smallTop + (fabricPolygon.height * fabricPolygon.scaleX) / 2
+
+ /**
+ * 미리 깔아놓은 셀이 있을때 셀에 흡착됨
+ */
+ if (manualDrawCells) {
+ manualDrawCells.forEach((cell) => {
+ const holdCellLeft = cell.left
+ const holdCellTop = cell.top
+ const holdCellRight = holdCellLeft + cell.width * cell.scaleX
+ const holdCellBottom = holdCellTop + cell.height * cell.scaleY
+ const holdCellCenterX = holdCellLeft + (cell.width * cell.scaleX) / 2
+ const holdCellCenterY = holdCellTop + (cell.height * cell.scaleY) / 2
+
+ //설치된 셀에 좌측에 스냅
+ if (Math.abs(smallRight - holdCellLeft) < snapDistance) {
+ fabricPolygon.left = holdCellLeft - width - 0.5
+ }
+
+ //설치된 셀에 우측에 스냅
+ if (Math.abs(smallLeft - holdCellRight) < snapDistance) {
+ fabricPolygon.left = holdCellRight + 0.5
+ }
+
+ //설치된 셀에 위쪽에 스냅
+ if (Math.abs(smallBottom - holdCellTop) < snapDistance) {
+ fabricPolygon.top = holdCellTop - height - 0.5
+ }
+
+ //설치된 셀에 밑쪽에 스냅
+ if (Math.abs(smallTop - holdCellBottom) < snapDistance) {
+ fabricPolygon.top = holdCellBottom + 0.5
+ }
+ //가운데 -> 가운데
+ if (Math.abs(smallCenterX - holdCellCenterX) < cellSnapDistance) {
+ fabricPolygon.left = holdCellCenterX - width / 2
+ }
+ //왼쪽 -> 가운데
+ if (Math.abs(smallLeft - holdCellCenterX) < cellSnapDistance) {
+ fabricPolygon.left = holdCellCenterX
+ }
+ // 오른쪽 -> 가운데
+ if (Math.abs(smallRight - holdCellCenterX) < cellSnapDistance) {
+ fabricPolygon.left = holdCellCenterX - width
+ }
+ //세로 가운데 -> 가운데
+ if (Math.abs(smallCenterY - holdCellCenterY) < cellSnapDistance) {
+ fabricPolygon.top = holdCellCenterY - height / 2
+ }
+ //위쪽 -> 가운데
+ if (Math.abs(smallTop - holdCellCenterY) < cellSnapDistance) {
+ fabricPolygon.top = holdCellCenterY
+ }
+ //아랫쪽 -> 가운데
+ if (Math.abs(smallBottom - holdCellCenterY) < cellSnapDistance) {
+ fabricPolygon.top = holdCellCenterY - height
+ }
})
-
- canvas?.add(fabricPolygon) //움직여가면서 추가됨
- lastPointPosition = { x: mousePoint.x, y: mousePoint.y }
}
+ // 위쪽 변에 스냅
+ if (Math.abs(smallTop - trestleTop) < snapDistance) {
+ fabricPolygon.top = trestleTop
+ }
+
+ // 아래쪽 변에 스냅
+ if (Math.abs(smallTop + fabricPolygon.height * fabricPolygon.scaleY - (trestleTop + trestlePolygons[i].height)) < snapDistance) {
+ fabricPolygon.top = trestleTop + trestlePolygons[i].height - fabricPolygon.height * fabricPolygon.scaleY
+ }
+
+ // 왼쪽변에 스냅
+ if (Math.abs(smallLeft - trestleLeft) < snapDistance) {
+ fabricPolygon.left = trestleLeft
+ }
+ //오른쪽 변에 스냅
+ if (Math.abs(smallRight - trestleRight) < snapDistance) {
+ fabricPolygon.left = trestleRight - fabricPolygon.width * fabricPolygon.scaleX
+ }
+
+ if (direction === 'south' || direction === 'north') {
+ // 모듈왼쪽이 세로중앙선에 붙게 스냅
+ if (Math.abs(smallLeft - (trestleLeft + trestlePolygons[i].width / 2)) < snapDistance) {
+ fabricPolygon.left = trestleLeft + trestlePolygons[i].width / 2
+ }
+
+ // 모듈이 가운데가 세로중앙선에 붙게 스냅
+ if (Math.abs(smallCenterX - (trestleLeft + trestlePolygons[i].width / 2)) < snapDistance) {
+ fabricPolygon.left = trestleLeft + trestlePolygons[i].width / 2 - (fabricPolygon.width * fabricPolygon.scaleX) / 2
+ }
+
+ // 모듈오른쪽이 세로중앙선에 붙게 스냅
+ if (Math.abs(smallRight - (trestleLeft + trestlePolygons[i].width / 2)) < snapDistance) {
+ fabricPolygon.left = trestleLeft + trestlePolygons[i].width / 2 - fabricPolygon.width * fabricPolygon.scaleX
+ }
+ } else {
+ // 모듈이 가로중앙선에 스냅
+ if (Math.abs(smallTop + fabricPolygon.height / 2 - bigCenterY) < snapDistance) {
+ fabricPolygon.top = bigCenterY - fabricPolygon.height / 2
+ }
+
+ if (Math.abs(smallTop - (trestleTop + trestlePolygons[i].height / 2)) < snapDistance) {
+ fabricPolygon.top = trestleTop + trestlePolygons[i].height / 2
+ }
+ // 모듈 밑면이 가로중앙선에 스냅
+ if (Math.abs(smallBottom - (trestleTop + trestlePolygons[i].height / 2)) < snapDistance) {
+ fabricPolygon.top = trestleTop + trestlePolygons[i].height / 2 - fabricPolygon.height * fabricPolygon.scaleY
+ }
+ }
+
+ fabricPolygon.setCoords()
canvas?.renderAll()
inside = true
break
@@ -5013,23 +5065,56 @@ export function useMode() {
})
canvas?.on('mouse:up', (e) => {
+ let isIntersection = true
if (!inside) return
if (fabricPolygon) {
- const turfCellPolygon = polygonToTurfPolygon(fabricPolygon)
+ const rectPoints = [
+ { x: fabricPolygon.left + 0.5, y: fabricPolygon.top + 0.5 },
+ { x: fabricPolygon.left + 0.5 + fabricPolygon.width * fabricPolygon.scaleX, y: fabricPolygon.top + 0.5 },
+ {
+ x: fabricPolygon.left + fabricPolygon.width * fabricPolygon.scaleX + 0.5,
+ y: fabricPolygon.top + fabricPolygon.height * fabricPolygon.scaleY + 0.5,
+ },
+ { x: fabricPolygon.left + 0.5, y: fabricPolygon.top + fabricPolygon.height * fabricPolygon.scaleY + 0.5 },
+ ]
- if (turf.booleanWithin(turfCellPolygon, turfPolygon)) {
+ fabricPolygon.set({ points: rectPoints })
+ const tempTurfModule = polygonToTurfPolygon(fabricPolygon)
+
+ //도머 객체를 가져옴
+ if (dormerTrestlePolygons) {
+ dormerTrestlePolygons.forEach((dormerTrestle) => {
+ const dormerTurfPolygon = polygonToTurfPolygon(dormerTrestle) //turf객체로 변환
+ const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인
+ //겹치면 안됨
+ if (intersection) {
+ alert('도머위에 모듈을 올릴 수 없습니다.')
+ isIntersection = false
+ }
+ })
+ }
+
+ if (!isIntersection) return
+
+ fabricPolygon.setCoords() //좌표 재정렬
+
+ if (turf.booleanWithin(tempTurfModule, turfPolygon)) {
//마우스 클릭시 set으로 해당 위치에 셀을 넣음
- const isOverlap = manualDrawCells.some((cell) => turf.booleanOverlap(turfCellPolygon, polygonToTurfPolygon(cell)))
+ const isOverlap = manualDrawCells.some((cell) => turf.booleanOverlap(tempTurfModule, polygonToTurfPolygon(cell))) //겹치는지 확인
if (!isOverlap) {
//안겹치면 넣는다
- fabricPolygon.set({ name: 'cell' })
fabricPolygon.setCoords()
- manualDrawCells.push(fabricPolygon)
+ fabricPolygon.set({ name: 'cell', fill: '#BFFD9F' })
+ manualDrawCells.push(fabricPolygon) //모듈배열에 추가
+ //해당 모듈에 프로퍼티로 넣는다
+ trestlePolygon.set({
+ modules: manualDrawCells,
+ })
} else {
alert('셀끼리 겹치면 안되죠?')
}
} else {
- alert('나갔으요!!')
+ alert('나갔죠?!!')
}
setDrewRoofCells(manualDrawCells)
}
@@ -5104,29 +5189,6 @@ export function useMode() {
const cols = Math.floor((bbox[2] - bbox[0]) / width)
const rows = Math.floor((bbox[3] - bbox[1]) / height)
- // console.log('bbox', bbox)
-
- const boxes = []
- const installedCellsArray = []
-
- for (let x = bbox[0]; x < bbox[2]; x += width) {
- for (let y = bbox[1]; y < bbox[3]; y += height) {
- const box = turf.polygon([
- [
- [x, y],
- [x + width, y],
- [x + width, y + height],
- [x, y + height],
- [x, y],
- ],
- ])
-
- if (turf.booleanWithin(box, turfTrestlePolygon)) {
- boxes.push(box)
- }
- }
- }
-
for (let col = 0; col <= cols; col++) {
for (let row = 0; row <= rows; row++) {
let x = 0,
@@ -5176,20 +5238,6 @@ export function useMode() {
const squarePolygon = turf.polygon([square])
- // console.log('turfTrestlePolygon', turfTrestlePolygon)
- // console.log('squarePolygon', squarePolygon)
-
- const areaSize = turf.area(turfTrestlePolygon)
-
- // console.log('areaSize', areaSize)
- const objSize = turf.area(squarePolygon)
-
- // console.log('objSize', objSize)
-
- const maxObject = Math.floor(areaSize / objSize)
-
- // console.log('maxObjectSize', maxObject)
-
const disjointFromTrestle = turf.booleanContains(turfTrestlePolygon, squarePolygon) || turf.booleanWithin(squarePolygon, turfTrestlePolygon)
if (disjointFromTrestle) {
@@ -5230,6 +5278,8 @@ export function useMode() {
lockScalingY: true, // Y 축 크기 조정 잠금
opacity: 0.8,
parentId: trestle.parentId,
+ lineCol: col,
+ lineRow: row,
})
canvas?.add(fabricPolygon)
drawCellsArray.push(fabricPolygon)
diff --git a/src/hooks/usePagination.js b/src/hooks/usePagination.js
new file mode 100644
index 00000000..7d76d293
--- /dev/null
+++ b/src/hooks/usePagination.js
@@ -0,0 +1,34 @@
+import { useEffect, useState } from 'react'
+
+/**
+ * 페이지네이션 훅
+ * @param {number} pageNo 현재 페이지 {optional}
+ * @param {number} pageSize 페이지당 아이템 수 {optional}
+ * @param {number} pagePerBlock 페이지 그룹 수 {optional}
+ * @param {number} totalCount 전체 아이템 수 {required}
+ * @returns
+ */
+const usePagination = ({ pageNo = 1, pageSize = 10, pagePerBlock = 10, totalCount = 0 }) => {
+ const [currentPage, setCurrentPage] = useState(pageNo)
+ const changePage = (page) => {
+ setCurrentPage(page)
+ }
+
+ useEffect(() => {
+ setCurrentPage(pageNo)
+ }, [pageNo])
+
+ const pageGroup = Math.floor((currentPage - 1) / pagePerBlock) + 1
+ const totalPages = Math.ceil(totalCount / pageSize)
+ const pages = Array.from({ length: totalPages }, (_, i) => i + 1)
+ const startPage = Math.max(1, (pageGroup - 1) * pagePerBlock + 1)
+ const endPage = Math.min(totalPages, pageGroup * pagePerBlock)
+ const pageRange = Array.from({ length: endPage !== totalPages ? pagePerBlock : endPage - startPage + 1 }, (_, i) => {
+ if (i + startPage > endPage) return
+ return i + startPage
+ })
+
+ return { currentPage, changePage, pageGroup, totalPages, pages, startPage, endPage, pageRange }
+}
+
+export default usePagination
diff --git a/src/hooks/usePlan.js b/src/hooks/usePlan.js
index 55854cb7..6d74cd93 100644
--- a/src/hooks/usePlan.js
+++ b/src/hooks/usePlan.js
@@ -1,19 +1,17 @@
import { useRecoilState } from 'recoil'
-import { canvasState, currentCanvasPlanState, initCanvasPlansState } from '@/store/canvasAtom'
+import { canvasState, currentCanvasPlanState, initCanvasPlansState, plansState } from '@/store/canvasAtom'
import { useAxios } from '@/hooks/useAxios'
import { useMessage } from '@/hooks/useMessage'
-import { toastUp } from '@/hooks/useToast'
-import { sessionStore } from '@/store/commonAtom'
-import { useState } from 'react'
+import { useSwal } from '@/hooks/useSwal'
export function usePlan() {
const [canvas, setCanvas] = useRecoilState(canvasState)
- const [currentCanvasPlan, setcurrentCanvasPlan] = useRecoilState(currentCanvasPlanState)
+ const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState)
const [initCanvasPlans, setInitCanvasPlans] = useRecoilState(initCanvasPlansState)
+ const [plans, setPlans] = useRecoilState(plansState)
+ const { swalFire } = useSwal()
const { getMessage } = useMessage()
- const { get, promisePost, promisePut } = useAxios()
- const [sessionState, setSessionState] = useRecoilState(sessionStore)
- const [userId, setUserId] = useState(sessionState.userId)
+ const { get, promisePost, promisePut, promiseDel } = useAxios()
/**
* 마우스 포인터의 가이드라인을 제거합니다.
@@ -50,7 +48,13 @@ export function usePlan() {
'minY',
'x',
'y',
+ 'x1',
+ 'x2',
+ 'y1',
+ 'y2',
+ 'attributes',
'stickeyPoint',
+ 'text',
])
const str = JSON.stringify(objs)
@@ -68,18 +72,59 @@ export function usePlan() {
// }, 1000)
}
+ const currentCanvasData = () => {
+ removeMouseLines()
+ return addCanvas()
+ }
+
+ /**
+ * 실시간 캔버스 상태와 DB에 저장된 캔버스 상태를 비교하여 수정 여부를 판단
+ */
+ const checkModifiedCanvasPlan = () => {
+ removeMouseLines()
+ const canvasStatus = addCanvas()
+ const initPlanData = initCanvasPlans.find((plan) => plan.id === currentCanvasPlan.id)
+
+ if (!initPlanData) {
+ // 새로운 캔버스
+ return JSON.parse(canvasStatus).objects.length > 0
+ } else {
+ // 저장된 캔버스
+ if (canvasStatus === initPlanData.canvasStatus) {
+ return false
+ } else {
+ // 각각 object들의 id 목록을 추출하여 비교
+ const canvasObjsIds = getObjectIds(JSON.parse(canvasStatus).objects)
+ const dbObjsIds = getObjectIds(JSON.parse(initPlanData.canvasStatus).objects)
+
+ return canvasObjsIds.length !== dbObjsIds.length || !canvasObjsIds.every((id, index) => id === dbObjsIds[index])
+ }
+ }
+ }
+ const getObjectIds = (objects) => {
+ return objects
+ .filter((obj) => obj.hasOwnProperty('id'))
+ .map((obj) => obj.id)
+ .sort()
+ }
+
/**
* DB에 저장된 데이터를 canvas에서 사용할 수 있도록 포맷화
*/
const dbToCanvasFormat = (cs) => {
- return JSON.stringify(cs.replace(/##/g, '"').replace(/\\/g, '').slice(1, -1))
+ // return JSON.stringify(cs.replace(/##/g, '"')) //.replace(/\\/g, ''))//.slice(1, -1))
+ // JSON.stringify()를 사용하면 "가 \"로 바뀐다. 따라서, JSON.stringify를 제거
+ // canvasToDbFormat()에서 \\의 상황을 없앴으므로 replace(/\\/g, '')를 제거
+ return cs.replace(/##/g, '"')
}
/**
* canvas의 데이터를 DB에 저장할 수 있도록 포맷화
*/
const canvasToDbFormat = (cs) => {
- return JSON.stringify(cs).replace(/"/g, '##')
+ // return JSON.stringify(cs).replace(/"/g, '##')
+ // addCanvas()에서 JSON.stringify()를 거쳐서 나오는데, 또 감싸버려서 \가 \\로 된다. 따라서, JSON.stringify를 제거
+ return cs.replace(/"/g, '##')
}
/**
@@ -101,17 +146,18 @@ export function usePlan() {
canvasStatus: canvasToDbFormat(canvasStatus),
}
- await promisePut({ url: '/api/canvas-management/canvas-statuses', data: planData })
+ return await promisePut({ url: '/api/canvas-management/canvas-statuses', data: planData })
.then((res) => {
- toastUp({ message: getMessage('common.message.save'), type: 'success' }) // 성공 시 메세지 없음
- console.log('[PUT] canvas-statuses res :::::::: %o', res)
+ swalFire({ text: getMessage('common.message.save') })
+ // console.log('[PUT] canvas-statuses res :::::::: %o', res)
setInitCanvasPlans((initCanvasPlans) =>
initCanvasPlans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan)),
)
+ setPlans((plans) => plans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan)))
})
.catch((error) => {
- toastUp({ message: error.message, type: 'error' })
- console.error('[PUT] canvas-statuses error :::::::: %o', error)
+ swalFire({ text: error.message, icon: 'error' })
+ // console.error('[PUT] canvas-statuses error :::::::: %o', error)
})
} else {
// canvas 신규 등록
@@ -122,14 +168,38 @@ export function usePlan() {
canvasStatus: canvasToDbFormat(canvasStatus),
}
- await promisePost({ url: '/api/canvas-management/canvas-statuses', data: planData })
+ return await promisePost({ url: '/api/canvas-management/canvas-statuses', data: planData })
.then((res) => {
- toastUp({ message: getMessage('common.message.save'), type: 'success' }) // 성공 시 메세지 없음
- console.log('[POST] canvas-statuses response :::::::: %o', res)
+ swalFire({ text: getMessage('common.message.save') })
+ // console.log('[POST] canvas-statuses response :::::::: %o', res)
+ setInitCanvasPlans((initCanvasPlans) => [
+ ...initCanvasPlans,
+ {
+ id: res.data,
+ name: currentCanvasPlan.objectNo + '-' + res.data,
+ userId: userId,
+ canvasStatus: canvasStatus,
+ isNew: currentCanvasPlan.id,
+ },
+ ])
+ setPlans((plans) =>
+ plans.map((plan) =>
+ plan.id === currentCanvasPlan.id
+ ? {
+ ...plan,
+ id: res.data,
+ name: currentCanvasPlan.objectNo + '-' + res.data,
+ userId: userId,
+ canvasStatus: canvasStatus,
+ isNew: currentCanvasPlan.id,
+ }
+ : plan,
+ ),
+ )
})
.catch((error) => {
- toastUp({ message: error.message, type: 'error' })
- console.error('[POST] canvas-statuses res error :::::::: %o', error)
+ swalFire({ text: error.message, icon: 'error' })
+ // console.error('[POST] canvas-statuses res error :::::::: %o', error)
})
}
}
@@ -137,7 +207,7 @@ export function usePlan() {
/**
* objectNo에 해당하는 canvas 목록을 조회하는 함수
*/
- const getCanvasByObjectNo = async (objectNo) => {
+ const getCanvasByObjectNo = async (userId, objectNo) => {
return get({ url: `/api/canvas-management/canvas-statuses/by-object/${objectNo}/${userId}` }).then((res) =>
res.map((item) => ({
id: item.id,
@@ -149,11 +219,28 @@ export function usePlan() {
)
}
+ /**
+ * id에 해당하는 canvas 데이터를 삭제하는 함수
+ */
+ const delCanvasById = (id) => {
+ return promiseDel({ url: `/api/canvas-management/canvas-statuses/by-id/${id}` })
+ }
+
+ /**
+ * objectNo에 해당하는 canvas 데이터들을 삭제하는 함수
+ */
+ const delCanvasByObjectNo = (objectNo) => {
+ return promiseDel({ url: `/api/canvas-management/canvas-statuses/by-object/${objectNo}` })
+ }
+
return {
canvas,
removeMouseLines,
+ currentCanvasData,
saveCanvas,
addCanvas,
+ checkModifiedCanvasPlan,
getCanvasByObjectNo,
+ delCanvasById,
}
}
diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js
index b364bf86..7289a0d3 100644
--- a/src/hooks/usePolygon.js
+++ b/src/hooks/usePolygon.js
@@ -2,6 +2,8 @@ import { canvasState, fontFamilyState, fontSizeState } from '@/store/canvasAtom'
import { useRecoilValue } from 'recoil'
import { fabric } from 'fabric'
import { getDirectionByPoint } from '@/util/canvas-util'
+import { QPolygon } from '@/components/fabric/QPolygon'
+import { isSamePoint } from '@/util/qpolygon-utils'
export const usePolygon = () => {
const canvas = useRecoilValue(canvasState)
@@ -9,19 +11,26 @@ export const usePolygon = () => {
const fontFamily = useRecoilValue(fontFamilyState)
const addPolygon = (points, options) => {
- const polygon = new fabric.Polygon(points, {
+ const polygon = new QPolygon(points, {
...options,
- selectable: options.selectable ?? false,
+ fill: options.fill || 'transparent',
+ stroke: options.stroke || '#000000',
+ fontSize: fontSize,
+ fontFamily: fontFamily,
+ selectable: true,
})
canvas?.add(polygon)
- addLengthText(polygon)
+
+ return polygon
}
const addPolygonByLines = (lines, options) => {
+ //lines의 idx를 정렬한다.
+ lines.sort((a, b) => a.idx - b.idx)
const points = createPolygonPointsFromOuterLines(lines)
- addPolygon(points, {
+ return addPolygon(points, {
...options,
})
}
@@ -39,10 +48,11 @@ export const usePolygon = () => {
const degree = (Math.atan2(dy, dx) * 180) / Math.PI
// Create new text object if it doesn't exist
- const text = new fabric.IText(length.toString(), {
+ const text = new fabric.Text(length.toString(), {
left: midPoint.x,
top: midPoint.y,
fontSize: fontSize,
+ fontFamily: fontFamily,
parentId: polygon.id,
minX: Math.min(start.x, end.x),
maxX: Math.max(start.x, end.x),
diff --git a/src/hooks/useSwal.js b/src/hooks/useSwal.js
index ab747756..cb18e6a7 100644
--- a/src/hooks/useSwal.js
+++ b/src/hooks/useSwal.js
@@ -35,7 +35,7 @@ export const useSwal = () => {
}).then((result) => {
if (result.isConfirmed) {
confirmFn()
- } else if (result.isDenied) {
+ } else if (result.isDismissed) {
denyFn()
}
})
diff --git a/src/lib/Axios.js b/src/lib/Axios.js
deleted file mode 100644
index d773d352..00000000
--- a/src/lib/Axios.js
+++ /dev/null
@@ -1,56 +0,0 @@
-'use client'
-
-import axios from 'axios'
-
-axios.defaults.baseURL = process.env.NEXT_PUBLIC_API_SERVER_PATH
-
-const axiosInstance = axios.create({
- // baseURL: process.env.API_SERVER_URL,
- headers: {
- Accept: 'application/json',
- },
-})
-
-axiosInstance.interceptors.request.use((config) => {
- // config['Authorization'] = localStorage.getItem('token')
- //TODO: 인터셉터에서 추가 로직 구현
- return config
-})
-
-axiosInstance.interceptors.request.use(undefined, (error) => {
- //TODO: 인터셉터에서 에러 처리 로직 구현
- // if (error.isAxiosError && e.response?.status === 401) {
- // localStorage.removeItem('token')
- // }
-})
-
-export const get = ({ url }) =>
- axiosInstance
- .get(url)
- .then((res) => res.data)
- .catch(console.error)
-
-export const post = ({ url, data }) =>
- axiosInstance
- .post(url, data)
- .then((res) => res.data)
- .catch(console.error)
-
-export const put = ({ url, data }) =>
- axiosInstance
- .put(url, data)
- .then((res) => res.data)
- .catch(console.error)
-
-export const patch = ({ url, data }) =>
- axiosInstance
- .patch(url, data)
-
- .then((res) => res.data)
- .catch(console.error)
-
-export const del = ({ url }) =>
- axiosInstance
- .delete(url)
- .then((res) => res.data)
- .catch(console.error)
diff --git a/src/lib/authActions.js b/src/lib/authActions.js
index d3f5aaf3..618985b9 100644
--- a/src/lib/authActions.js
+++ b/src/lib/authActions.js
@@ -49,6 +49,8 @@ export async function setSession(data) {
session.telNo = data.telNo
session.fax = data.fax
session.email = data.email
+ session.storeLvl = data.storeLvl
+ session.groupId = data.groupId
session.pwdInitYn = data.pwdInitYn
session.isLoggedIn = true
// console.log('session:', session)
diff --git a/src/locales/ja.json b/src/locales/ja.json
index 8e62d630..980d1400 100644
--- a/src/locales/ja.json
+++ b/src/locales/ja.json
@@ -15,12 +15,45 @@
"header.stem": "ステム",
"plan.menu.plan.drawing": "도면작성",
"plan.menu.placement.surface.initial.setting": "配置面 初期設定",
+ "modal.placement.initial.setting.plan.drawing.size.stuff": "寸法入力による物品作成",
+ "modal.placement.initial.setting.plan.": "図面の作成方法",
+ "modal.placement.initial.setting.size": "寸法入力方法",
+ "modal.placement.initial.setting.size.info": "寸法入力方法案内",
+ "modal.placement.initial.setting.size.roof": "複視図入力",
+ "modal.placement.initial.setting.size.roof.info": "平面の外壁線と立面の屋根勾配に基づいて作画する場合に選択",
+ "modal.placement.initial.setting.size.actual": "実測値入力",
+ "modal.placement.initial.setting.size.actual.info": "現地屋根の外周寸法を入力して作画する場合選択",
+ "modal.placement.initial.setting.size.none.pitch": "陸上屋根",
+ "modal.placement.initial.setting.size.none.pitch.info": "傾斜のない平面形状の屋根にパネルを配置する場合に選択",
+ "modal.placement.initial.setting.roof.angle.setting": "屋根角度設定",
+ "modal.placement.initial.setting.roof.pitch": "傾斜",
+ "modal.placement.initial.setting.roof.angle": "角度",
+ "modal.placement.initial.setting.roof.material": "屋根材の選択(単位:mm)",
+ "modal.placement.initial.setting.roof.material.info": "対応可能な屋根材や足場は限定されますので、必ず事前マニュアルをご確認ください。",
+ "modal.placement.initial.setting.rafter": "垂木の間隔",
+ "modal.roof.shape.setting": "屋根形状の設定",
+ "modal.roof.shape.setting.ridge": "龍丸屋根",
+ "modal.roof.shape.setting.patten.a": "Aパターン",
+ "modal.roof.shape.setting.patten.b": "Bパターン",
+ "modal.roof.shape.setting.side": "別に設定",
"plan.menu.roof.cover": "지붕덮개",
"plan.menu.roof.cover.outline.drawing": "外壁線を描",
"plan.menu.roof.cover.roof.shape.setting": "屋根形状設定",
- "plan.menu.roof.cover.roof.shape.edit": "지붕형상 편집",
+ "plan.menu.roof.cover.roof.shape.passivity.setting": "屋根形状設定",
+ "plan.menu.roof.cover.eaves.kerava.edit": "처마·케라바 변경",
+ "plan.menu.roof.cover.movement.shape.updown": "동선이동·형올림내림(JA)",
+ "modal.movement.flow.line.move": "銅線の移動軒",
+ "modal.movement.flow.line.updown": "型上げ・降り",
+ "modal.movement.flow.line.updown.info": "を選択して幅を指定してください桁の異なる辺。",
+ "modal.movement.flow.line.updown.up": "桁を上げる",
+ "modal.movement.flow.line.updown.down": "桁数を下げる",
+ "modal.movement.flow.line.info": "家屋などの壁に面する屋根を作成します。",
+ "modal.movement.flow.line.bottom.left": "高さ変更:下、左",
+ "modal.movement.flow.line.top.right": "高さ変更:上、右",
+ "plan.menu.roof.cover.outline.edit.offset": "외벽선 편집 및 오프셋(JA)",
+ "plan.menu.roof.cover.roof.surface.alloc": "지붕면 할당(JA)",
+ "plan.menu.roof.cover.roof.shape.edit": "지붕형상 편집(JA)",
"plan.menu.roof.cover.auxiliary.line.drawing": "補助線を描",
- "plan.menu.roof.cover.roof.surface.alloc": "지붕면 할당",
"modal.cover.outline.drawing": "外壁線を描",
"modal.cover.outline": "外壁線",
"modal.cover.outline.right.angle": "直角",
@@ -32,6 +65,8 @@
"modal.cover.outline.arrow": "方向 (矢印)",
"modal.cover.outline.fix": "外壁線確定",
"modal.cover.outline.rollback": "一変戦に戻る",
+ "modal.cover.outline.finish": "設定完了",
+ "common.setting.finish": "設定完了",
"modal.cover.outline.remove": "外壁の削除",
"modal.cover.outline.select.move": "外壁の選択、移動",
"plan.menu.roof.cover.roof.setting": "屋根形状設定",
@@ -45,10 +80,74 @@
"plan.menu.placement.surface.all.remove": "配置面全体を削除",
"plan.menu.module.circuit.setting": "モジュール回路構成",
"plan.menu.module.circuit.setting.default": "基本設定",
+ "modal.module.basic.setting.orientation.setting": "方位設定",
+ "modal.module.basic.setting.orientation.setting.info": "※シミュレーション計算用方位を指定します。南の方位を設定してください。",
+ "modal.module.basic.setting.orientation.setting.angle.passivity": "角度を直接入力",
+ "modal.module.basic.setting.module.roof.material": "屋根材",
+ "modal.module.basic.setting.module.trestle.maker": "架台メーカー",
+ "modal.module.basic.setting.module.construction.method": "工法",
+ "modal.module.basic.setting.module.under.roof": "屋根の下",
+ "modal.module.basic.setting.module.setting": "モジュールの選択",
+ "modal.module.basic.setting.module.setting.info1": "※勾配の 範囲には制限があります。屋根傾斜が2.5値未満、10値を超える場合には施工が可能か 施工マニュアルを確認してください。",
+ "modal.module.basic.setting.module.setting.info2": "モジュール配置時は、施工マニュアルに記載されている<モジュール配置条件>を必ず確認してください",
+ "modal.module.basic.setting.module.cotton.classification": "綿調道区分",
+ "modal.module.basic.setting.module.fitting.height": "設置高さ",
+ "modal.module.basic.setting.module.standard.wind.speed": "基準風速",
+ "modal.module.basic.setting.module.standard.snowfall.amount": "基準積雪量",
+ "modal.module.basic.setting.module.standard.construction": "標準施工",
+ "modal.module.basic.setting.module.enforce.construction": "強化施工",
+ "modal.module.basic.setting.module.multiple.construction": "多設施工",
+ "modal.module.basic.setting.module.eaves.bar.fitting": "庇力バーの設置",
+ "modal.module.basic.setting.module.blind.metal.fitting": "目幕金具の設置",
+ "modal.module.basic.setting.module.placement": "モジュールの配置",
+ "modal.module.basic.setting.module.placement.select.fitting.type": "設置形態を選択してくださ",
+ "modal.module.basic.setting.module.placement.waterfowl.arrangement": "水鳥の配置",
+ "modal.module.basic.setting.module.placement.do": "する",
+ "modal.module.basic.setting.module.placement.do.not": "しない。",
+ "modal.module.basic.setting.module.placement.arrangement.standard": "配置基準",
+ "modal.module.basic.setting.module.placement.arrangement.standard.center": "中央配置",
+ "modal.module.basic.setting.module.placement.arrangement.standard.eaves": "軒側",
+ "modal.module.basic.setting.module.placement.arrangement.standard.ridge": "龍丸側",
+ "modal.module.basic.setting.module.placement.maximum": "最大配置する。",
+ "modal.module.basic.setting.pitch.module.placement.standard.setting": "配置基準の設定",
+ "modal.module.basic.setting.pitch.module.placement.standard.setting.south": "南向きに設置す",
+ "modal.module.basic.setting.pitch.module.placement.standard.setting.select": "指定した辺を基準に設置する",
+ "modal.module.basic.setting.pitch.module.allocation.setting": "割り当て設定",
+ "modal.module.basic.setting.pitch.module.allocation.setting.info": "※バッチパネルの種類が1種類の場合にのみ使用できます。",
+ "modal.module.basic.setting.pitch.module.row.amount": "単数",
+ "modal.module.basic.setting.pitch.module.row.margin": "上下間隔",
+ "modal.module.basic.setting.pitch.module.column.amount": "熱数",
+ "modal.module.basic.setting.pitch.module.column.margin": "左右間隔",
+ "modal.module.basic.setting.prev": "以前",
+ "modal.module.basic.setting.passivity.placement": "手動配置",
+ "modal.module.basic.setting.auto.placement": "設定値に自動配置",
"plan.menu.module.circuit.setting.circuit.trestle.setting": "回路と架台の設定",
+ "modal.circuit.trestle.setting": "回路と架台設定",
+ "modal.circuit.trestle.setting.power.conditional.select": "パワーコンディショナーを選択",
+ "modal.circuit.trestle.setting.power.conditional.select.name": "名称",
+ "modal.circuit.trestle.setting.power.conditional.select.rated.output": "定格出力",
+ "modal.circuit.trestle.setting.power.conditional.select.circuit.amount": "回路数",
+ "modal.circuit.trestle.setting.power.conditional.select.max.connection": "最大接続枚数",
+ "modal.circuit.trestle.setting.power.conditional.select.max.overload": "過積最大枚数",
+ "modal.circuit.trestle.setting.power.conditional.select.output.current": "出力電流",
+ "modal.circuit.trestle.setting.circuit.allocation": "回路割り当て",
+ "modal.circuit.trestle.setting.circuit.allocation.auto": "自動回路割り当て",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity": "手動回路割当",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity.info": "同じ回路のモジュールを選択状態にした後、 [番号確認]ボタンを押すと番号が割り当てられます。",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity.selected.power.conditional": "選択したパワーコンディショナー",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num": "設定する回路番号 (1~)",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity.selected.power.conditional.reset": "選択されたパワーコンディショナーの回路番号の初期化",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.reset": "すべての回路番号の初期化",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num.fix": "番号確定",
+ "modal.circuit.trestle.setting.step.up.allocation": "昇圧設定",
"plan.menu.module.circuit.setting.plan.orientation": "図面方位の適用",
"plan.menu.estimate": "見積",
"plan.menu.estimate.roof.alloc": "屋根面の割り当て",
+ "modal.roof.alloc.info": "※配置面初期設定で保存した[基本屋根材]を変更したり、屋根材を追加して割り当てることができます。",
+ "modal.roof.alloc.select.roof.material": "屋根材の選択",
+ "modal.roof.alloc.select.parallel": "並列式",
+ "modal.roof.alloc.select.stairs": "カスケード",
+ "modal.roof.alloc.apply": "選択した屋根材として割り当て",
"plan.menu.estimate.save": "保存",
"plan.menu.estimate.reset": "初期化",
"plan.menu.estimate.copy": "コピー",
@@ -74,6 +173,7 @@
"modal.canvas.setting.grid.dot.line.setting.save": "保存",
"modal.canvas.setting.grid.absorption.add": "吸着点を追加",
"modal.canvas.setting.grid.color.setting": "グリッド色の設定",
+ "modal.auxiliary.drawing": "補助線の作成",
"modal.grid.move": "グリッド移動",
"modal.grid.move.info": "移動する方向を入力してください",
"modal.grid.move.all": "グリッド全体移動",
@@ -83,6 +183,9 @@
"modal.grid.copy.info": "間隔を設定し、コピー方向を選択します",
"modal.grid.copy.length": "長さ",
"modal.grid.copy.save": "保存",
+ "modal.common.save": "保存",
+ "modal.common.add": "追加",
+ "modal.common.prev": "以前",
"modal.canvas.setting.font.plan.edit": "フォントとサイズの変更",
"modal.canvas.setting.font.plan.edit.word": "文字フォントの変更",
"modal.canvas.setting.font.plan.edit.flow": "フロー方向フォントの変更",
@@ -115,6 +218,36 @@
"modal.canvas.setting.first.option.border": "ボーダーのみ",
"modal.canvas.setting.first.option.line": "ラインハッチ",
"modal.canvas.setting.first.option.all": "All painted",
+ "modal.canvas.setting.wallline.properties.setting": "外壁のプロパティの設定",
+ "modal.canvas.setting.wallline.properties.setting.info": "※属性を変更する外壁線を選択し、軒で設定またはケラバで設定 ボタンをクリックして設定値を適用します。",
+ "modal.canvas.setting.wallline.properties.setting.eaves": "軒で設定",
+ "modal.canvas.setting.wallline.properties.setting.edge": "ケラバに設定",
+ "modal.eaves.gable.edit": "軒・ケラバ変更",
+ "modal.eaves.gable.edit.basic": "通常",
+ "modal.eaves.gable.edit.wall.merge.info": "家屋などの壁に面する屋根を作成します。",
+ "modal.wallline.offset.setting": "外壁の編集とオフセット",
+ "modal.wallline.offset.setting.wallline.edit": "外壁の編集",
+ "modal.wallline.offset.setting.wallline.edit.info": "辺と始点を選択して長さと方向を指定してください。",
+ "modal.wallline.offset.setting.wallline.edit.position": "支店",
+ "modal.wallline.offset.setting.offset": "オフセット",
+ "modal.wallline.offset.setting.offset.info": "オフセットしたい外壁を選択してください。",
+ "modal.object.setting.type.open.space.placement": "開口部の配置",
+ "modal.object.setting.type.shadow.placement": "影の配置",
+ "modal.object.setting.type.triangle.dormer": "三角形ドーマー",
+ "modal.object.setting.type.pentagon.dormer": "五角形ドーマー",
+ "modal.object.setting.free.input": "フリー入力",
+ "modal.object.setting.size.input": "寸法入力",
+ "modal.object.setting.width": "横長",
+ "modal.object.setting.height": "縦長",
+ "modal.object.setting.area.cross": "エリア交差",
+ "modal.object.setting.size.setting": "サイズ設定",
+ "modal.object.setting.agreement.depth": "同意の深さ",
+ "modal.object.setting.offset.depth": "出幅 (深さ)",
+ "modal.object.setting.offset.width": "出幅 (幅)",
+ "modal.object.setting.direction.select": "方向の選択",
+ "modal.placement.surface.setting.info": "ⓘ ①の長さ入力後に対角線の長さを入力すると、②の長さを自動計算します。",
+ "modal.placement.surface.setting.diagonal.length": "斜めの長さ",
+ "setting": "設定",
"common.message.no.data": "No data",
"common.message.no.dataDown": "ダウンロードするデータがありません",
"common.message.noData": "表示するデータがありません",
@@ -203,63 +336,247 @@
"common.message.password.init.success": "パスワード [{0}] に初期化されました。",
"common.message.no.edit.save": "この文書は変更できません。",
"common.require": "필수",
+ "commons.west": "立つ",
+ "commons.east": "ドン",
+ "commons.south": "立つ",
+ "commons.north": "北",
"site.name": "Q.CAST III",
- "site.sub_name": "태양광 발전 시스템 도면관리 사이트",
- "login": "로그인",
- "login.init_password.btn": "비밀번호 초기화 ja",
- "login.init_password.title": "비밀번호 초기화",
- "login.init_password.sub_title": "비밀번호를 초기화할 아이디와 이메일 주소를 입력해 주세요.",
- "login.init_password.complete_message": "비밀번호가 초기화 되었습니다. 초기화된 비밀번호는 아이디와 같습니다.",
- "join.title": "Q.CAST3 로그인ID 발행 신청",
- "join.sub1.title": "판매대리점 정보",
- "join.sub1.comment": "※ 등록되는 리셀러의 회사 이름을 입력하십시오. (2차점은 「○○판매주식회사(2차점:××설비주식회사)」로 기입해 주세요.)",
- "join.sub1.storeQcastNm": "판매대리점명",
- "join.sub1.storeQcastNm_placeholder": "株式会社エネルギア・ソリューション・アンド・サービス(2次店:山口住機販売有限会社)",
- "join.sub1.storeQcastNmKana": "판매대리점명 후리가나",
- "join.sub1.storeQcastNmKana_placeholder": "カブシキガイシャエネルギア・ソリューション・アン",
- "join.sub1.postCd": "우편번호",
- "join.sub1.postCd_placeholder": "숫자 7자리",
- "join.sub1.addr": "주소",
- "join.sub1.addr_placeholder": "전각50자이내",
- "join.sub1.telNo": "전화번호",
+ "site.sub_name": "太陽光発電システム図面管理サイト",
+ "board.notice.title": "お知らせ",
+ "board.notice.sub.title": "お知らせ一覧",
+ "board.faq.title": "FAQ",
+ "board.faq.sub.title": "FAQ 一覧",
+ "board.archive.title": "資料ダウンロード",
+ "board.archive.sub.title": "文書一覧",
+ "board.list.header.rownum": "番号",
+ "board.list.header.title": "タイトル",
+ "board.list.header.regDt": "登録日",
+ "board.sub.search.placeholder": "検索語を入力してください。",
+ "board.sub.search.result": "{0}について、合計{1}件の投稿が検索されました。",
+ "board.sub.search.archive.result": "{0}について、合計{1}件の文書が検索されました。",
+ "board.sub.total": "全体",
+ "board.sub.fileList": "添付ファイル一覧",
+ "board.sub.updDt": "更新日",
+ "myinfo.title": "マイプロフィール",
+ "myinfo.info.userId": "ユーザーID",
+ "myinfo.info.nameKana": "担当者名ふりがな",
+ "myinfo.info.name": "担当者名",
+ "myinfo.info.password": "パスワード",
+ "myinfo.info.chg.password": "新しいパスワード入力",
+ "myinfo.info.category": "部署名",
+ "myinfo.info.tel": "電話番号",
+ "myinfo.info.fax": "FAX番号",
+ "myinfo.info.mail": "メールアドレス",
+ "myinfo.sub.validation.password": "※ 半角10文字以内",
+ "myinfo.btn.close": "閉じる",
+ "myinfo.btn.chg.password": "パスワード変更",
+ "myinfo.btn.chg": "変更",
+ "myinfo.btn.noChg": "変更しない",
+ "myinfo.btn.confirm": "確認",
+ "myinfo.message.validation.password1": "パスワードを入力してください。",
+ "myinfo.message.validation.password2": "既存のパスワードと同じです。",
+ "myinfo.message.validation.password3": "半角文字10文字以内で入力してください。",
+ "myinfo.message.validation.password4": "変更パスワードを入力してください。",
+ "myinfo.message.save": "パスワードが変更されました。",
+ "myinfo.message.password.error": "パスワードが間違っています。",
+ "login": "ログイン",
+ "login.id.save": "ID保存",
+ "login.id.placeholder": "IDを入力してください。",
+ "login.password.placeholder": "パスワードを入力してください。",
+ "login.guide.text": "当サイトを利用するには、事前申請が必要です。",
+ "login.guide.sub1": "IDをお持ちでない方は",
+ "login.guide.sub2": "をクリックしてください。",
+ "login.guide.join.btn": "ID申請",
+ "login.init_password.btn": "パスワードリセット",
+ "login.init_password.btn.back": "前の画面に戻る",
+ "login.init_password.title": "パスワードリセット",
+ "login.init_password.sub_title": "パスワードをリセットするIDとメールアドレスを入力してください。",
+ "login.init_password.complete_message": "パスワードがリセットされました。リセット後のパスワードはIDと同じです。",
+ "login.init_password.id.placeholder": "IDを入力してください。",
+ "login.init_password.email.placeholder": "メールアドレスを入力してください。",
+ "join.title": "Q.CAST3 ログインID発行申請",
+ "join.sub1.title": "販売代理店情報",
+ "join.sub1.comment": "※ 登録する販売店の会社名を入力してください。(2次店の場合「○○販売株式会社(2次店:××設備株式会社)」と記載してください。)",
+ "join.sub1.storeQcastNm": "販売代理店名",
+ "join.sub1.storeQcastNm_placeholder": "株式会社 エナジー ギア ソリューション アンド サービス(2次店: 山口重機販売有限会社)",
+ "join.sub1.storeQcastNmKana": "販売代理店名ふりがな",
+ "join.sub1.storeQcastNmKana_placeholder": "株式会社 エナジー ギア ソリューション",
+ "join.sub1.postCd": "郵便番号",
+ "join.sub1.postCd_placeholder": "7桁の数字",
+ "join.sub1.addr": "住所",
+ "join.sub1.addr_placeholder": "全角50文字以内",
+ "join.sub1.telNo": "電話番号",
"join.sub1.telNo_placeholder": "00-0000-0000",
- "join.sub1.fax": "FAX 번호",
+ "join.sub1.fax": "FAX番号",
"join.sub1.fax_placeholder": "00-0000-0000",
- "join.sub2.title": "담당자 정보",
- "join.sub2.userNm": "담당자명",
- "join.sub2.userNmKana": "담당자명 후리가나",
- "join.sub2.userId": "신청 ID",
- "join.sub2.email": "이메일 주소",
- "join.sub2.telNo": "전화번호",
+ "join.sub1.bizNo": "法人番号",
+ "join.sub2.title": "担当者情報",
+ "join.sub2.userNm": "担当者名",
+ "join.sub2.userNmKana": "担当者名ふりがな",
+ "join.sub2.userId": "申請ID",
+ "join.sub2.email": "メールアドレス",
+ "join.sub2.telNo": "電話番号",
"join.sub2.telNo_placeholder": "00-0000-0000",
- "join.sub2.fax": "FAX 번호",
+ "join.sub2.fax": "FAX番号",
"join.sub2.fax_placeholder": "00-0000-0000",
- "join.sub2.category": "부서명",
- "join.sub3.title": "견적서 제출용 회사정보",
- "join.sub3.qtCompNm": "회사명",
- "join.sub3.qtPostCd": "우편번호",
- "join.sub3.qtPostCd_placeholder": "숫자 7자리",
- "join.sub3.qtAddr": "주소",
- "join.sub3.qtAddr_placeholder": "전각50자이내",
- "join.sub3.qtEmail": "이메일 주소",
- "join.sub3.qtTelNo": "전화번호",
- "join.sub3.qtTelNo_placeholder": "00-0000-0000",
- "join.sub3.qtFax": "FAX 번호",
- "join.sub3.qtFax_placeholder": "00-0000-0000",
- "join.btn.approval_request": "ID 승인요청",
- "join.complete.title": "Q.CAST3 로그인ID 발행신청 완료",
- "join.complete.contents": "※ 신청한 ID가 승인되면, 담당자 정보에 입력한 이메일 주소로 로그인 관련 안내 메일이 전송됩니다.",
- "join.complete.email_comment": "담당자 이메일 주소",
- "join.complete.email": "test@naver.com",
- "stuff.gridHeader.lastEditDatetime": "갱신일시",
- "stuff.gridHeader.objectNo": "물건번호",
- "stuff.gridHeader.planTotCnt": "플랜 수",
- "stuff.gridHeader.objectName": "물건명",
- "stuff.gridHeader.saleStoreId": "대리점ID",
- "stuff.gridHeader.saleStoreName": "대리점명",
- "stuff.gridHeader.address": "물건주소",
- "stuff.gridHeader.dispCompanyName": "견적처",
- "stuff.gridHeader.receiveUser": "담당자",
- "stuff.gridHeader.specDate": "사양확인",
- "stuff.gridHeader.createDatetime": "등록일"
+ "join.sub2.category": "部署名",
+ "join.btn.login_page": "ログイン画面に移動",
+ "join.btn.approval_request": "ID承認申請",
+ "join.complete.title": "Q.CAST3 ログインID発行申請完了",
+ "join.complete.contents": "※ 申請したIDが承認されると、担当者情報に入力されたメールアドレスにログイン案内メールが送信されます。",
+ "join.complete.email_comment": "担当者メールアドレス",
+ "stuff.gridHeader.lastEditDatetime": "更新日時",
+ "stuff.gridHeader.objectNo": "品番",
+ "stuff.gridHeader.planTotCnt": "プラン数",
+ "stuff.gridHeader.objectName": "商品名",
+ "stuff.gridHeader.saleStoreId": "代理店ID",
+ "stuff.gridHeader.saleStoreName": "代理店名",
+ "stuff.gridHeader.address": "商品アドレス",
+ "stuff.gridHeader.dispCompanyName": "見積もり",
+ "stuff.gridHeader.receiveUser": "担当者",
+ "stuff.gridHeader.specDate": "仕様確認日",
+ "stuff.gridHeader.createDatetime": "登録日",
+ "stuff.message.periodError": "最大1年間閲覧可能.",
+ "stuff.addressPopup.title": "郵便番号",
+ "stuff.addressPopup.placeholder": "郵便番号の7桁を入力してください。",
+ "stuff.addressPopup.error.message1": "登録された郵便番号に住所が見つかりません。もう一度入力してください。",
+ "stuff.addressPopup.error.message2": "住所を選択してください.",
+ "stuff.addressPopup.gridHeader.address1": "都道府県",
+ "stuff.addressPopup.gridHeader.address2": "市区町村",
+ "stuff.addressPopup.gridHeader.address3": "市区町村 以下",
+ "stuff.addressPopup.btn1": "閉じる",
+ "stuff.addressPopup.btn2": "住所適用",
+ "stuff.planReqPopup.title": "設計依頼のインポート",
+ "stuff.detail.required": "必須入力項目",
+ "stuff.detail.planReqNo": "設計依頼No.",
+ "stuff.detail.dispCompanyName": "担当者",
+ "stuff.detail.objectStatusId": "物品区分/物件名",
+ "stuff.detail.objectStatus0": "新築",
+ "stuff.detail.objectStatus1": "基軸",
+ "stuff.detail.objectNameKana": "商品名 ふりがな",
+ "stuff.detail.saleStoreId": "一次販売店名/ID",
+ "stuff.detail.otherSaleStoreId": "二次販売店名/ID",
+ "stuff.detail.zipNo": "郵便番号 ",
+ "stuff.detail.btn.addressPop": "住所検索",
+ "stuff.detail.btn.addressPop.guide": "※ 郵便番号7桁を入力した後、アドレス検索ボタンをクリックしてください",
+ "stuff.detail.prefId": "都道府県 / 住所 ",
+ "stuff.detail.areaId": "発電量シミュレーション地域 ",
+ "stuff.detail.windSpeed": "基準風速",
+ "stuff.detail.windSpeedSpan": "m/s以下",
+ "stuff.detail.btn.windSpeedPop": "風速選択",
+ "stuff.detail.verticalSnowCover": "垂直説説",
+ "stuff.detail.coldRegionFlg": "寒冷地対策施行",
+ "stuff.detail.surfaceType": "面調図区分",
+ "stuff.detail.saltAreaFlg": "塩害地域用アイテムの使用",
+ "stuff.detail.installHeight": "設置高さ",
+ "stuff.detail.conType": "契約条件",
+ "stuff.detail.conType0": "余剰",
+ "stuff.detail.conType1": "全量",
+ "stuff.detail.remarks": "メモ",
+ "stuff.detail.tooltip.saleStoreId": "販売代理店または販売代理店IDを1文字以上入力してください",
+ "stuff.planReqPopup.popTitle": "設計依頼検索",
+ "stuff.planReqPopup.btn1": "検索",
+ "stuff.planReqPopup.btn2": "初期化",
+ "stuff.planReqPopup.btn3": "閉じる",
+ "stuff.planReqPopup.btn4": "選択の適用",
+ "stuff.planReqPopup.gridHeader.planStatName": "상태",
+ "stuff.planReqPopup.gridHeader.planReqNo": "설계의뢰 번호",
+ "stuff.planReqPopup.gridHeader.saleStoreId": "판매대리점ID",
+ "stuff.planReqPopup.gridHeader.saleStoreName": "판매대리점명",
+ "stuff.planReqPopup.gridHeader.title": "안건명",
+ "stuff.planReqPopup.gridHeader.address1": "도도부현",
+ "stuff.planReqPopup.gridHeader.houseCntName": "설치가옥수",
+ "stuff.planReqPopup.gridHeader.planReqName": "의뢰자명",
+ "stuff.planReqPopup.gridHeader.submitDt": "설계의뢰 제출일",
+ "stuff.planReqPopup.search.planReqNo": "設計依頼番号",
+ "stuff.planReqPopup.search.title": "案件名",
+ "stuff.planReqPopup.search.address1": "都道府県",
+ "stuff.planReqPopup.search.saleStoreName": "販売代理店名",
+ "stuff.planReqPopup.search.planReqName": "依頼者名",
+ "stuff.planReqPopup.search.planStatName": "状態",
+ "stuff.planReqPopup.search.period": "期間検索",
+ "stuff.planReqPopup.search.schDateGbnS": "提出日",
+ "stuff.planReqPopup.search.schDateGbnR": "受付日",
+ "stuff.search.title": "物件状況",
+ "stuff.search.btn1": "物件登録",
+ "stuff.search.btn2": "照会",
+ "stuff.search.btn3": "初期化",
+ "stuff.search.schObjectNo": "品番",
+ "stuff.search.schSaleStoreName": "販売代理店名",
+ "stuff.search.schAddress": "商品アドレス",
+ "stuff.search.schObjectName": "商品名",
+ "stuff.search.schDispCompanyName": "見積もり",
+ "stuff.search.schSelSaleStoreId": "販売代理店の選択",
+ "stuff.search.schReceiveUser": "担当者",
+ "stuff.search.period": "期間検索",
+ "stuff.search.schDateTypeU": "更新日",
+ "stuff.search.schDateTypeR": "登録日",
+ "stuff.windSelectPopup.title": "風速選択",
+ "stuff.windSelectPopup.table.selected": "選択",
+ "stuff.windSelectPopup.table.windspeed": "風速",
+ "stuff.windSelectPopup.error.message1": "住所を先に検索してください",
+ "stuff.windSelectPopup.error.message2": "風速を選択してください。",
+ "stuff.windSelectPopup.search.address1": "県",
+ "stuff.windSelectPopup.btn1": "閉じる",
+ "stuff.windSelectPopup.btn2": "選択",
+ "length": "長さ",
+ "height": "高さ",
+ "output": "出力",
+ "slope": "傾斜",
+ "eaves.offset": "軒の",
+ "gable.offset": "ケラバ出幅",
+ "offset": "出幅",
+ "width": "幅",
+ "size": "寸",
+ "size.angle": "寸(度)",
+ "eaves": "軒",
+ "gable": "ケラバ",
+ "wall": "壁",
+ "wall.merge": "壁取り",
+ "hajebichi": "ハゼビーチ",
+ "straight.line": "直線",
+ "right.angle": "直角",
+ "double.pitch": "イ・グベ",
+ "angle": "角度",
+ "diagonal": "対角線",
+ "hipandgable": "八作屋根",
+ "hipandgable.width": "八作屋根 出幅",
+ "jerkinhead": "半折",
+ "shed": "片側の流れ",
+ "apply": "適用",
+ "module": "モジュール",
+ "has.sleeve": "袖あり",
+ "has.not.sleeve": "袖なし",
+ "jerkinhead.width": "半折先幅",
+ "jerkinhead.slope": "半折先傾斜",
+ "shed.width": "片流幅",
+ "windage": "漂流",
+ "windage.width": "漂流の出幅",
+ "write": "作成",
+ "main.storeId": "販売店ID",
+ "main.storeName": "販売店名",
+ "main.objectNo": "物件番号",
+ "main.faq": "FAQ",
+ "main.content.objectList": "最近の更新物件一覧",
+ "main.content.notice": "お知らせ",
+ "main.content.download1": "操作マニュアル",
+ "main.content.download2": "屋根の説明書",
+ "main.popup.login.popupTitle": "パスワード変更",
+ "main.popup.login.newPassword1": "新しいパスワードを入力",
+ "main.popup.login.newPassword2": "新規パスワード再入力",
+ "main.popup.login.placeholder": "半角10文字以内 ",
+ "main.popup.login.guide1": "初期化されたパスワードでログインした場合、パスワードを変更しなければサイト利用が可能です。",
+ "main.popup.login.guide2": "パスワードを変更しない場合は、ログイン画面に進みます。",
+ "main.popup.login.btn1": "変更",
+ "main.popup.login.btn2": "変更しない",
+ "main.popup.login.validate1": "入力したパスワードが異なります。",
+ "main.popup.login.validate2": "半角10文字以内で入力してください。",
+ "main.popup.login.success": "パスワードが変更されました。",
+ "common.canvas.validate.size": "寸法を入力してください.",
+ "surface.shape.validate.size.1to2": "①길이는 ②보다 큰 값을 넣어주세요.",
+ "surface.shape.validate.size.1to3": "①길이는 ③보다 큰 값을 넣어주세요.",
+ "surface.shape.validate.size.1to23": "①길이는 ②+③보다 큰 값을 넣어주세요.",
+ "surface.shape.validate.size.2to3": "②길이는 ③보다 큰 값을 넣어주세요.",
+ "surface.shape.validate.size.3to4": "③길이는 ④보다 큰 값을 넣어주세요.",
+ "surface.shape.validate.size.4to5": "④길이는 ⑤보다 큰 값을 넣어주세요."
}
diff --git a/src/locales/ko.json b/src/locales/ko.json
index 9bf624d6..21aca2be 100644
--- a/src/locales/ko.json
+++ b/src/locales/ko.json
@@ -15,12 +15,41 @@
"header.stem": "Stem",
"plan.menu.plan.drawing": "도면작성",
"plan.menu.placement.surface.initial.setting": "배치면 초기 설정",
+ "modal.placement.initial.setting.plan.drawing": "도면 작성방법",
+ "modal.placement.initial.setting.plan.drawing.size.stuff": "치수 입력에 의한 물건 작성",
+ "modal.placement.initial.setting.size": "치수 입력방법",
+ "modal.placement.initial.setting.size.info": "치수 입력방법 안내",
+ "modal.placement.initial.setting.size.roof": "복시도 입력",
+ "modal.placement.initial.setting.size.roof.info": "평면의 외벽선과 입면의 지붕 구배를 바탕으로 작화할 경우 선택",
+ "modal.placement.initial.setting.size.actual": "실측값 입력",
+ "modal.placement.initial.setting.size.actual.info": "현지 지붕의 외주 치수를 입력하여 작화하는 경우 선택",
+ "modal.placement.initial.setting.size.none.pitch": "육지붕",
+ "modal.placement.initial.setting.size.none.pitch.info": "경사가 없는 평면 형태의 지붕에 패널을 배치할 경우 선택",
+ "modal.placement.initial.setting.roof.angle.setting": "지붕각도 설정",
+ "modal.placement.initial.setting.roof.pitch": "경사",
+ "modal.placement.initial.setting.roof.angle": "각도",
+ "modal.placement.initial.setting.roof.material": "지붕재 선택(단위: mm)",
+ "modal.placement.initial.setting.roof.material.info": "대응 가능한 지붕재 및 발판은 한정되므로 반드시 사전 매뉴얼을 확인하십시오.",
+ "modal.placement.initial.setting.rafter": "서까래",
+ "modal.roof.shape.setting": "지붕형상 설정",
+ "modal.roof.shape.setting.ridge": "용마루",
+ "modal.roof.shape.setting.patten.a": "A 패턴",
+ "modal.roof.shape.setting.patten.b": "B 패턴",
+ "modal.roof.shape.setting.side": "변별로 설정",
"plan.menu.roof.cover": "지붕덮개",
"plan.menu.roof.cover.outline.drawing": "외벽선 그리기",
"plan.menu.roof.cover.roof.shape.setting": "지붕형상 설정",
"plan.menu.roof.cover.roof.shape.passivity.setting": "지붕형상 수동 설정",
"plan.menu.roof.cover.eaves.kerava.edit": "처마·케라바 변경",
"plan.menu.roof.cover.movement.shape.updown": "동선이동·형올림내림",
+ "modal.movement.flow.line.move": "동선 이동",
+ "modal.movement.flow.line.updown": "형 올림·내림",
+ "modal.movement.flow.line.updown.info": "자릿수가 다른 변을 선택하고 폭을 지정하십시오.",
+ "modal.movement.flow.line.updown.up": "자릿수를 올리다",
+ "modal.movement.flow.line.updown.down": "자릿수를 낮추다",
+ "modal.movement.flow.line.info": "동선을 선택하고 이동 폭을 지정하십시오",
+ "modal.movement.flow.line.bottom.left": "높이 변경: 아래, 왼쪽",
+ "modal.movement.flow.line.top.right": "높이 변경: 위, 오른쪽",
"plan.menu.roof.cover.outline.edit.offset": "외벽선 편집 및 오프셋",
"plan.menu.roof.cover.roof.surface.alloc": "지붕면 할당",
"plan.menu.roof.cover.roof.shape.edit": "지붕형상 편집",
@@ -36,20 +65,93 @@
"modal.cover.outline.arrow": "방향(화살표)",
"modal.cover.outline.fix": "외벽선 확정",
"modal.cover.outline.rollback": "일변전으로 돌아가기",
+ "modal.cover.outline.finish": "설정완료",
+ "common.setting.finish": "설정완료",
+ "common.setting.rollback": "일변전으로 돌아가기",
"modal.cover.outline.remove": "외벽 제거",
"modal.cover.outline.select.move": "외벽 선택, 이동",
"plan.menu.placement.surface": "배치면",
"plan.menu.placement.surface.slope.setting": "경사설정",
"plan.menu.placement.surface.drawing": "배치면 그리기",
+ "modal.placement.surface.drawing.straight.line": "직선",
+ "modal.placement.surface.drawing.right.angle": "직각",
+ "modal.placement.surface.drawing.double.pitch": "이구배",
+ "modal.placement.surface.drawing.angle": "각도",
+ "modal.placement.surface.drawing.diagonal": "대각선",
"plan.menu.placement.surface.arrangement": "면형상 배치",
"plan.menu.placement.surface.object": "오브젝트 배치",
"plan.menu.placement.surface.all.remove": "배치면 전체 삭제",
"plan.menu.module.circuit.setting": "모듈,회로 구성",
"plan.menu.module.circuit.setting.default": "기본 설정",
+ "modal.module.basic.setting.orientation.setting": "방위 설정",
+ "modal.module.basic.setting.orientation.setting.info": "※시뮬레이션 계산용 방위를 지정합니다. 남쪽의 방위를 설정해주세요.",
+ "modal.module.basic.setting.orientation.setting.angle.passivity": "각도를 직접 입력",
+ "modal.module.basic.setting.module.roof.material": "지붕재",
+ "modal.module.basic.setting.module.trestle.maker": "가대메이거",
+ "modal.module.basic.setting.module.construction.method": "공법",
+ "modal.module.basic.setting.module.under.roof": "지붕밑바탕",
+ "modal.module.basic.setting.module.setting": "모듈 설정",
+ "modal.module.basic.setting.module.setting.info1": "※ 구배의 범위에는 제한이 있습니다. 지붕경사가 2.5치 미만, 10치를 초과하는 경우에는 시공이 가능한지 시공 매뉴얼을 확인해주십시오.",
+ "modal.module.basic.setting.module.setting.info2": "※ 모듈 배치 시에는 시공 매뉴얼에 기재된 <모듈 배치 조건>을 반드시 확인해주십시오.",
+ "modal.module.basic.setting.module.cotton.classification": "면조도구분",
+ "modal.module.basic.setting.module.fitting.height": "설치높이",
+ "modal.module.basic.setting.module.standard.wind.speed": "기준 풍속",
+ "modal.module.basic.setting.module.standard.snowfall.amount": "기준 적설량",
+ "modal.module.basic.setting.module.standard.construction": "표준시공",
+ "modal.module.basic.setting.module.enforce.construction": "강화시공",
+ "modal.module.basic.setting.module.multiple.construction": "다설시공",
+ "modal.module.basic.setting.module.eaves.bar.fitting": "처마력 바의 설치",
+ "modal.module.basic.setting.module.blind.metal.fitting": "눈막이 금구 설치",
+ "modal.module.basic.setting.module.select": "모듈 선택",
+ "modal.module.basic.setting.module.placement": "모듈 배치",
+ "modal.module.basic.setting.module.placement.select.fitting.type": "설치형태를 선택해주세요.",
+ "modal.module.basic.setting.module.placement.waterfowl.arrangement": "물떼새 배치",
+ "modal.module.basic.setting.module.placement.do": "한다",
+ "modal.module.basic.setting.module.placement.do.not": "하지 않는다",
+ "modal.module.basic.setting.module.placement.arrangement.standard": "배치 기준",
+ "modal.module.basic.setting.module.placement.arrangement.standard.center": "중앙배치",
+ "modal.module.basic.setting.module.placement.arrangement.standard.eaves": "처마쪽",
+ "modal.module.basic.setting.module.placement.arrangement.standard.ridge": "용마루쪽",
+ "modal.module.basic.setting.module.placement.maximum": "최대배치 실시한다.",
+ "modal.module.basic.setting.pitch.module.placement.standard.setting": "배치 기준 설정",
+ "modal.module.basic.setting.pitch.module.placement.standard.setting.south": "남향으로 설치한다",
+ "modal.module.basic.setting.pitch.module.placement.standard.setting.select": "지정한 변을 기준으로 설치한다",
+ "modal.module.basic.setting.pitch.module.allocation.setting": "할당 설정",
+ "modal.module.basic.setting.pitch.module.allocation.setting.info": "※배치 패널 종류가 1종류일 경우에만 사용할 수 있습니다.",
+ "modal.module.basic.setting.pitch.module.row.amount": "단수",
+ "modal.module.basic.setting.pitch.module.row.margin": "상하간격",
+ "modal.module.basic.setting.pitch.module.column.amount": "열수",
+ "modal.module.basic.setting.pitch.module.column.margin": "좌우간격",
+ "modal.module.basic.setting.prev": "이전",
+ "modal.module.basic.setting.passivity.placement": "수동 배치",
+ "modal.module.basic.setting.auto.placement": "설정값으로 자동 배치",
"plan.menu.module.circuit.setting.circuit.trestle.setting": "회로 및 가대 설정",
+ "modal.circuit.trestle.setting": "회로 및 가대설정",
+ "modal.circuit.trestle.setting.power.conditional.select": "파워컨디셔너 선택",
+ "modal.circuit.trestle.setting.power.conditional.select.name": "명칭",
+ "modal.circuit.trestle.setting.power.conditional.select.rated.output": "정격출력",
+ "modal.circuit.trestle.setting.power.conditional.select.circuit.amount": "회로수",
+ "modal.circuit.trestle.setting.power.conditional.select.max.connection": "최대접속매수",
+ "modal.circuit.trestle.setting.power.conditional.select.max.overload": "과적최대매수",
+ "modal.circuit.trestle.setting.power.conditional.select.output.current": "출력전류",
+ "modal.circuit.trestle.setting.circuit.allocation": "회로 할당",
+ "modal.circuit.trestle.setting.circuit.allocation.auto": "자동 회로 할당",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity": "수동 회로 할당",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity.info": "동일한 회로의 모듈을 선택 상태로 만든 후 [번호 확정] 버튼을 누르면 번호가 할당됩니다.",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity.selected.power.conditional": "선택된 파워컨디셔너",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num": "설정할 회로번호(1~)",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity.selected.power.conditional.reset": "선택된 파워컨디셔너의 회로번호 초기화",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.reset": "모든 회로번호 초기화",
+ "modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num.fix": "번호 확정",
+ "modal.circuit.trestle.setting.step.up.allocation": "승압 설정",
"plan.menu.module.circuit.setting.plan.orientation": "도면 방위 적용",
"plan.menu.estimate": "견적서",
"plan.menu.estimate.roof.alloc": "지붕면 할당",
+ "modal.roof.alloc.info": "※ 배치면 초기설정에서 저장한 [기본 지붕재]를 변경하거나, 지붕재를 추가하여 할당할 수 있습니다.",
+ "modal.roof.alloc.select.roof.material": "지붕재 선택",
+ "modal.roof.alloc.select.parallel": "병렬식",
+ "modal.roof.alloc.select.stairs": "계단식",
+ "modal.roof.alloc.apply": "선택한 지붕재로 할당",
"plan.menu.estimate.save": "저장",
"plan.menu.estimate.reset": "초기화",
"plan.menu.estimate.copy": "복사",
@@ -75,6 +177,7 @@
"modal.canvas.setting.grid.dot.line.setting.save": "저장",
"modal.canvas.setting.grid.absorption.add": "흡착점 추가",
"modal.canvas.setting.grid.color.setting": "그리드 색 설정",
+ "modal.auxiliary.drawing": "보조선 작성",
"modal.grid.move": "그리드 이동",
"modal.grid.move.info": "이동할 방향을 입력해주세요",
"modal.grid.move.all": "그리드 전체 이동",
@@ -84,6 +187,10 @@
"modal.grid.copy.info": "간격을 설정하고 복사 방향을 선택하십시오",
"modal.grid.copy.length": "길이",
"modal.grid.copy.save": "저장",
+ "modal.common.save": "저장",
+ "modal.common.add": "추가",
+ "modal.common.prev": "이전",
+ "modal.common.next": "다음",
"modal.canvas.setting.font.plan.edit": "글꼴 및 크기 변경",
"modal.canvas.setting.font.plan.edit.word": "문자 글꼴 변경",
"modal.canvas.setting.font.plan.edit.flow": "흐름 방향 글꼴 변경",
@@ -116,6 +223,36 @@
"modal.canvas.setting.first.option.border": "테두리만",
"modal.canvas.setting.first.option.line": "라인해치",
"modal.canvas.setting.first.option.all": "All painted",
+ "modal.canvas.setting.wallline.properties.setting": "외벽선 속성 설정",
+ "modal.canvas.setting.wallline.properties.setting.info": "※ 속성을 변경할 외벽선을 선택하고, 처마로 설정 또는 케라바로 설정\n 버튼을 클릭하여 설정값을 적용하십시오.\n",
+ "modal.canvas.setting.wallline.properties.setting.eaves": "처마로 설정",
+ "modal.canvas.setting.wallline.properties.setting.edge": "케라바로 설정",
+ "modal.eaves.gable.edit": "처마・케라바 변경",
+ "modal.eaves.gable.edit.basic": "통상",
+ "modal.eaves.gable.edit.wall.merge.info": "하옥 등 벽에 접하는 지붕을 작성합니다.",
+ "modal.wallline.offset.setting": "외벽선 편집 및 오프셋",
+ "modal.wallline.offset.setting.wallline.edit": "외벽선 편집",
+ "modal.wallline.offset.setting.wallline.edit.info": "변과 시작점을 선택하고 길이와 방향을 지정해 주세요.",
+ "modal.wallline.offset.setting.wallline.edit.position": "지점",
+ "modal.wallline.offset.setting.offset": "오프셋",
+ "modal.wallline.offset.setting.offset.info": "오프셋 하고 싶은 외벽선을 선택하세요.",
+ "modal.object.setting.type.open.space.placement": "개구 배치",
+ "modal.object.setting.type.shadow.placement": "그림자 배치",
+ "modal.object.setting.type.triangle.dormer": "삼각형 도머",
+ "modal.object.setting.type.pentagon.dormer": "오각형 도머",
+ "modal.object.setting.free.input": "프리입력",
+ "modal.object.setting.size.input": "치수입력",
+ "modal.object.setting.width": "가로길이",
+ "modal.object.setting.height": "세로길이",
+ "modal.object.setting.area.cross": "영역교차",
+ "modal.object.setting.size.setting": "사이즈설정",
+ "modal.object.setting.agreement.depth": "동의길이 깊이",
+ "modal.object.setting.offset.depth": "출폭(깊이)",
+ "modal.object.setting.offset.width": "출폭(폭)",
+ "modal.object.setting.direction.select": "방향 선택",
+ "modal.placement.surface.setting.info": "ⓘ ①의 길이 입력 후 대각선 길이를 입력하면 ②의 길이를 자동 계산합니다.",
+ "modal.placement.surface.setting.diagonal.length": "대각선 길이",
+ "setting": "설정",
"common.message.no.data": "No data",
"common.message.no.dataDown": "No data to download",
"common.message.noData": "No data to display",
@@ -204,13 +341,64 @@
"common.message.password.init.success": "비밀번호 [{0}]로 초기화 되었습니다.",
"common.message.no.edit.save": "This document cannot be changed.",
"common.require": "필수",
+ "commons.west": "서",
+ "commons.east": "동",
+ "commons.south": "남",
+ "commons.north": "북",
"site.name": "Q.CAST III",
"site.sub_name": "태양광 발전 시스템 도면관리 사이트",
+ "board.notice.title": "공지사항",
+ "board.notice.sub.title": "공지사항 목록",
+ "board.faq.title": "FAQ",
+ "board.faq.sub.title": "FAQ 목록",
+ "board.archive.title": "자료 다운로드",
+ "board.archive.sub.title": "문서 목록",
+ "board.list.header.rownum": "번호",
+ "board.list.header.title": "제목",
+ "board.list.header.regDt": "등록일",
+ "board.sub.search.placeholder": "검색어를 입력하세요.",
+ "board.sub.search.result": "{0}에 대해 총 {1}건의 글이 검색 되었습니다.",
+ "board.sub.search.archive.result": "{0}에 대해 총 {1}건의 문서가 검색 되었습니다.",
+ "board.sub.total": "전체",
+ "board.sub.fileList": "첨부파일 목록",
+ "board.sub.updDt": "업데이트",
+ "myinfo.title": "My profile",
+ "myinfo.info.userId": "사용자ID",
+ "myinfo.info.nameKana": "담당자명 후리가나",
+ "myinfo.info.name": "담당자명",
+ "myinfo.info.password": "비밀번호",
+ "myinfo.info.chg.password": "변경 비밀번호 입력",
+ "myinfo.info.category": "부서명",
+ "myinfo.info.tel": "전화번호",
+ "myinfo.info.fax": "FAX번호",
+ "myinfo.info.mail": "이메일 주소",
+ "myinfo.sub.validation.password": "※ 반각10자 이내",
+ "myinfo.btn.close": "닫기",
+ "myinfo.btn.chg.password": "비밀번호 변경",
+ "myinfo.btn.chg": "변경",
+ "myinfo.btn.noChg": "변경안함",
+ "myinfo.btn.confirm": "확인",
+ "myinfo.message.validation.password1": "비밀번호를 입력하세요.",
+ "myinfo.message.validation.password2": "기존 비밀번호와 동일합니다.",
+ "myinfo.message.validation.password3": "반각 문자 10자이내여야 합니다.",
+ "myinfo.message.validation.password4": "변경 비밀번호를 입력하세요.",
+ "myinfo.message.save": "비밀번호가 변경되었습니다.",
+ "myinfo.message.password.error": "비밀번호가 틀렸습니다.",
"login": "로그인",
+ "login.id.save": "ID Save",
+ "login.id.placeholder": "아이디를 입력해주세요.",
+ "login.password.placeholder": "비밀번호를 입력해주세요.",
+ "login.guide.text": "당 사이트를 이용할 때는, 사전 신청이 필요합니다.",
+ "login.guide.sub1": "ID가 없는 분은",
+ "login.guide.sub2": "을 클릭해주십시오.",
+ "login.guide.join.btn": "ID신청",
"login.init_password.btn": "비밀번호 초기화",
+ "login.init_password.btn.back": "이전 화면으로",
"login.init_password.title": "비밀번호 초기화",
"login.init_password.sub_title": "비밀번호를 초기화할 아이디와 이메일 주소를 입력해 주세요.",
"login.init_password.complete_message": "비밀번호가 초기화 되었습니다. 초기화된 비밀번호는 아이디와 같습니다.",
+ "login.init_password.id.placeholder": "ID를 입력하세요.",
+ "login.init_password.email.placeholder": "이메일을 입력하세요.",
"join.title": "Q.CAST3 로그인ID 발행 신청",
"join.sub1.title": "판매대리점 정보",
"join.sub1.comment": "※ 등록되는 리셀러의 회사 이름을 입력하십시오. (2차점은 「○○판매주식회사(2차점:××설비주식회사)」로 기입해 주세요.)",
@@ -226,6 +414,7 @@
"join.sub1.telNo_placeholder": "00-0000-0000",
"join.sub1.fax": "FAX 번호",
"join.sub1.fax_placeholder": "00-0000-0000",
+ "join.sub1.bizNo": "법인번호",
"join.sub2.title": "담당자 정보",
"join.sub2.userNm": "담당자명",
"join.sub2.userNmKana": "담당자명 후리가나",
@@ -236,22 +425,11 @@
"join.sub2.fax": "FAX 번호",
"join.sub2.fax_placeholder": "00-0000-0000",
"join.sub2.category": "부서명",
- "join.sub3.title": "견적서 제출용 회사정보",
- "join.sub3.qtCompNm": "회사명",
- "join.sub3.qtPostCd": "우편번호",
- "join.sub3.qtPostCd_placeholder": "숫자 7자리",
- "join.sub3.qtAddr": "주소",
- "join.sub3.qtAddr_placeholder": "전각50자이내",
- "join.sub3.qtEmail": "이메일 주소",
- "join.sub3.qtTelNo": "전화번호",
- "join.sub3.qtTelNo_placeholder": "00-0000-0000",
- "join.sub3.qtFax": "FAX 번호",
- "join.sub3.qtFax_placeholder": "00-0000-0000",
+ "join.btn.login_page": "로그인 화면으로 이동",
"join.btn.approval_request": "ID 승인요청",
"join.complete.title": "Q.CAST3 로그인ID 발행신청 완료",
"join.complete.contents": "※ 신청한 ID가 승인되면, 담당자 정보에 입력한 이메일 주소로 로그인 관련 안내 메일이 전송됩니다.",
"join.complete.email_comment": "담당자 이메일 주소",
- "join.complete.email": "test@naver.com",
"stuff.gridHeader.lastEditDatetime": "갱신일시",
"stuff.gridHeader.objectNo": "물건번호",
"stuff.gridHeader.planTotCnt": "플랜 수",
@@ -261,6 +439,149 @@
"stuff.gridHeader.address": "물건주소",
"stuff.gridHeader.dispCompanyName": "견적처",
"stuff.gridHeader.receiveUser": "담당자",
- "stuff.gridHeader.specDate": "사양확인",
- "stuff.gridHeader.createDatetime": "등록일"
+ "stuff.gridHeader.specDate": "사양확인일",
+ "stuff.gridHeader.createDatetime": "등록일",
+ "stuff.message.periodError": "최대1년 조회 가능합니다.",
+ "stuff.addressPopup.title": "우편번호",
+ "stuff.addressPopup.placeholder": "우편번호의 7자리를 입력하세요.",
+ "stuff.addressPopup.error.message1": "등록된 우편번호에서 주소를 찾을 수 없습니다. 다시 입력해주세요.",
+ "stuff.addressPopup.error.message2": "주소를 선택해주세요.",
+ "stuff.addressPopup.gridHeader.address1": "도도부현",
+ "stuff.addressPopup.gridHeader.address2": "시구정촌",
+ "stuff.addressPopup.gridHeader.address3": "시구정촌 이하",
+ "stuff.addressPopup.btn1": "닫기",
+ "stuff.addressPopup.btn2": "주소적용",
+ "stuff.planReqPopup.title": "설계의뢰 불러오기",
+ "stuff.detail.required": "필수 입력항목",
+ "stuff.detail.planReqNo": "설계의뢰No.",
+ "stuff.detail.dispCompanyName": "담당자",
+ "stuff.detail.objectStatusId": "물건구분/물건명",
+ "stuff.detail.objectStatus0": "신축",
+ "stuff.detail.objectStatus1": "기축",
+ "stuff.detail.objectNameKana": "물건명 후리가나",
+ "stuff.detail.saleStoreId": "1차 판매점명 / ID",
+ "stuff.detail.otherSaleStoreId": "2차 판매점명 / ID",
+ "stuff.detail.zipNo": "우편번호",
+ "stuff.detail.btn.addressPop": "주소검색",
+ "stuff.detail.btn.addressPop.guide": "※ 주소검색 버튼을 클릭한 후, 도도부현 정보를 선택해주십시오.",
+ "stuff.detail.prefId": "도도부현 / 주소",
+ "stuff.detail.areaId": "발전량시뮬레이션지역",
+ "stuff.detail.windSpeed": "기준풍속",
+ "stuff.detail.windSpeedSpan": "m/s이하",
+ "stuff.detail.btn.windSpeedPop": "풍속선택",
+ "stuff.detail.verticalSnowCover": "수직적설량",
+ "stuff.detail.coldRegionFlg": "한랭지대책시행",
+ "stuff.detail.surfaceType": "면조도구분",
+ "stuff.detail.saltAreaFlg": "염해지역용아이템사용",
+ "stuff.detail.installHeight": "설치높이",
+ "stuff.detail.conType": "계약조건",
+ "stuff.detail.conType0": "잉여",
+ "stuff.detail.conType1": "전량",
+ "stuff.detail.remarks": "메모",
+ "stuff.detail.tooltip.saleStoreId": "판매대리점 또는 판매대리점ID를 1자 이상 입력하세요",
+ "stuff.planReqPopup.popTitle": "설계 요청 검색",
+ "stuff.planReqPopup.btn1": "검색",
+ "stuff.planReqPopup.btn2": "초기화",
+ "stuff.planReqPopup.btn3": "닫기",
+ "stuff.planReqPopup.btn4": "선택적용",
+ "stuff.planReqPopup.gridHeader.planStatName": "상태",
+ "stuff.planReqPopup.gridHeader.planReqNo": "설계의뢰 번호",
+ "stuff.planReqPopup.gridHeader.saleStoreId": "판매대리점ID",
+ "stuff.planReqPopup.gridHeader.saleStoreName": "판매대리점명",
+ "stuff.planReqPopup.gridHeader.title": "안건명",
+ "stuff.planReqPopup.gridHeader.address1": "도도부현",
+ "stuff.planReqPopup.gridHeader.houseCntName": "설치가옥수",
+ "stuff.planReqPopup.gridHeader.planReqName": "의뢰자명",
+ "stuff.planReqPopup.gridHeader.submitDt": "설계의뢰 제출일",
+ "stuff.planReqPopup.search.planReqNo": "설계의뢰번호",
+ "stuff.planReqPopup.search.title": "안건명",
+ "stuff.planReqPopup.search.address1": "도도부현",
+ "stuff.planReqPopup.search.saleStoreName": "판매대리점명",
+ "stuff.planReqPopup.search.planReqName": "의뢰자명",
+ "stuff.planReqPopup.search.planStatName": "상태",
+ "stuff.planReqPopup.search.period": "기간검색",
+ "stuff.planReqPopup.search.schDateGbnS": "제출일",
+ "stuff.planReqPopup.search.schDateGbnR": "접수일",
+ "stuff.search.title": "물건현황",
+ "stuff.search.btn1": "신규등록",
+ "stuff.search.btn2": "조회",
+ "stuff.search.btn3": "초기화",
+ "stuff.search.schObjectNo": "물건번호",
+ "stuff.search.schSaleStoreName": "판매대리점명",
+ "stuff.search.schAddress": "물건주소",
+ "stuff.search.schObjectName": "물건명",
+ "stuff.search.schDispCompanyName": "견적처",
+ "stuff.search.schSelSaleStoreId": "판매대리점 선택",
+ "stuff.search.schReceiveUser": "담당자",
+ "stuff.search.period": "기간검색",
+ "stuff.search.schDateTypeU": "갱신일",
+ "stuff.search.schDateTypeR": "등록일",
+ "stuff.windSelectPopup.title": "풍속선택",
+ "stuff.windSelectPopup.table.selected": "선택",
+ "stuff.windSelectPopup.table.windspeed": "풍속",
+ "stuff.windSelectPopup.error.message1": "주소를 먼저 검색해주세요.",
+ "stuff.windSelectPopup.error.message2": "풍속을 선택해주세요.",
+ "stuff.windSelectPopup.search.address1": "현",
+ "stuff.windSelectPopup.btn1": "닫기",
+ "stuff.windSelectPopup.btn2": "선택",
+ "length": "길이",
+ "height": "높이",
+ "output": "출력",
+ "slope": "경사",
+ "eaves.offset": "처마 출폭",
+ "gable.offset": "케라바 출폭",
+ "offset": "출폭",
+ "width": "폭",
+ "size": "치수",
+ "size.angle": "寸(度)",
+ "eaves": "처마",
+ "gable": "케라바",
+ "wall": "벽",
+ "wall.merge": "벽취합",
+ "hajebichi": "하제비치",
+ "straight.line": "직선",
+ "right.angle": "직각",
+ "double.pitch": "이구배",
+ "angle": "각도",
+ "diagonal": "대각선",
+ "hipandgable": "팔작지붕",
+ "hipandgable.width": "팔작지붕의 폭",
+ "jerkinhead": "반절처",
+ "shed": "한쪽흐름",
+ "apply": "적용",
+ "module": "모듈",
+ "has.sleeve": "소매 있음",
+ "has.not.sleeve": "소매 없음",
+ "jerkinhead.width": "반절처 폭",
+ "jerkinhead.slope": "반절처 경사",
+ "shed.width": "한쪽흐름 폭",
+ "windage": "편류",
+ "windage.width": "편류의 출폭",
+ "write": "작성",
+ "main.storeId": "판매점 ID",
+ "main.storeName": "판매점명",
+ "main.objectNo": "물건번호",
+ "main.faq": "FAQ",
+ "main.content.objectList": "최근 갱신 물건목록",
+ "main.content.notice": "공지사항",
+ "main.content.download1": "조작메뉴얼",
+ "main.content.download2": "지붕설명서",
+ "main.popup.login.popupTitle": "비밀번호변경",
+ "main.popup.login.newPassword1": "새 비밀번호 입력",
+ "main.popup.login.newPassword2": "새 비밀번호 재입력",
+ "main.popup.login.placeholder": "반각 10자 이내",
+ "main.popup.login.guide1": "초기화된 비밀번호로 로그인한 경우 비밀번호를 변경하지 않으면 사이트 이용이 가능합니다.",
+ "main.popup.login.guide2": "비밀번호를 변경하지 않으려면 로그인 화면으로 이동합니다.",
+ "main.popup.login.btn1": "변경",
+ "main.popup.login.btn2": "변경안함",
+ "main.popup.login.validate1": "입력한 패스워드가 다릅니다.",
+ "main.popup.login.validate2": "반각 10자 이내로 입력해주세요.",
+ "main.popup.login.success": "비밀번호가 변경되었습니다.",
+ "common.canvas.validate.size": "사이즈를 입력해 주세요.",
+ "surface.shape.validate.size.1to2": "①길이는 ②보다 큰 값을 넣어주세요.",
+ "surface.shape.validate.size.1to3": "①길이는 ③보다 큰 값을 넣어주세요.",
+ "surface.shape.validate.size.1to23": "①길이는 ②+③보다 큰 값을 넣어주세요.",
+ "surface.shape.validate.size.2to3": "②길이는 ③보다 큰 값을 넣어주세요.",
+ "surface.shape.validate.size.3to4": "③길이는 ④보다 큰 값을 넣어주세요.",
+ "surface.shape.validate.size.4to5": "④길이는 ⑤보다 큰 값을 넣어주세요."
}
diff --git a/src/store/boardAtom.js b/src/store/boardAtom.js
new file mode 100644
index 00000000..3444eba0
--- /dev/null
+++ b/src/store/boardAtom.js
@@ -0,0 +1,14 @@
+import { atom } from 'recoil'
+
+export const searchState = atom({
+ key: 'searchState',
+ default: {
+ currentPage: 1,
+ totalPage: 1,
+ pageBlock: 100,
+ totalCount: 0,
+ searchValue: '',
+ mainFlag: 'N',
+ searchFlag: false,
+ },
+})
diff --git a/src/store/canvasAtom.js b/src/store/canvasAtom.js
index aa400a46..7806b8f2 100644
--- a/src/store/canvasAtom.js
+++ b/src/store/canvasAtom.js
@@ -1,5 +1,6 @@
import { atom, selector } from 'recoil'
import { MENU } from '@/common/common'
+import { outerLineFixState, outerLinePointsState } from '@/store/outerLineAtom'
export const canvasState = atom({
key: 'canvasState',
@@ -34,7 +35,7 @@ export const fontFamilyState = atom({
export const fontSizeState = atom({
key: 'fontSizeState',
- default: 16,
+ default: 20,
})
export const canvasSizeState = atom({
@@ -263,7 +264,28 @@ export const currentCanvasPlanState = atom({
default: {},
})
+// 전체 canvas plan
+export const plansState = atom({
+ key: 'plan',
+ default: [],
+})
+
export const tempGridModeState = atom({
key: 'tempGridModeState',
default: false,
})
+
+export const textModeState = atom({
+ key: 'textModeState',
+ default: false,
+})
+
+export const canGridOptionSeletor = selector({
+ key: 'canGridOptionSeletor',
+ get: ({ get }) => {
+ const points = get(outerLinePointsState)
+ const currentMenu = get(currentMenuState)
+ const outerLineFix = get(outerLineFixState)
+ return points.length === 0 || outerLineFix
+ },
+})
diff --git a/src/store/gridAtom.js b/src/store/gridAtom.js
index 9b21a9f5..a4b4b15b 100644
--- a/src/store/gridAtom.js
+++ b/src/store/gridAtom.js
@@ -2,5 +2,5 @@ import { atom } from 'recoil'
export const gridColorState = atom({
key: 'gridColorState',
- default: '#000000',
+ default: '#FF0000',
})
diff --git a/src/store/modalAtom.js b/src/store/modalAtom.js
index a9a708ac..301b5b81 100644
--- a/src/store/modalAtom.js
+++ b/src/store/modalAtom.js
@@ -13,3 +13,12 @@ export const modalContent = atom({
>
),
})
+
+export const modalProps = atom({
+ key: 'modalProps',
+ default: {
+ closeOnOverlayClick: true,
+ closeOnEsc: true,
+ showCloseIcon: true,
+ },
+})
diff --git a/src/store/outerLineAtom.js b/src/store/outerLineAtom.js
index 2fcc5f9d..361bc526 100644
--- a/src/store/outerLineAtom.js
+++ b/src/store/outerLineAtom.js
@@ -63,3 +63,8 @@ export const outerLinePointsState = atom({
key: 'outerLinePointsState',
default: [],
})
+
+export const outerLineFixState = atom({
+ key: 'outerLineFixState',
+ default: false,
+})
diff --git a/src/store/placementShapeDrawingAtom.js b/src/store/placementShapeDrawingAtom.js
new file mode 100644
index 00000000..b7a65107
--- /dev/null
+++ b/src/store/placementShapeDrawingAtom.js
@@ -0,0 +1,70 @@
+import { atom } from 'recoil'
+
+export const OUTER_LINE_TYPE = {
+ OUTER_LINE: 'outerLine', // 외벽선
+ RIGHT_ANGLE: 'rightAngle', // 직각
+ DOUBLE_PITCH: 'doublePitch',
+ ANGLE: 'angle', // 각도
+ DIAGONAL_LINE: 'diagonalLine', // 대각선
+}
+
+/**
+ * 외벽선 작성에서 사용하는 recoilState
+ */
+
+export const placementShapeDrawingLength1State = atom({
+ //길이1
+ key: 'placementShapeDrawingLength1State',
+ default: 0,
+})
+
+export const placementShapeDrawingLength2State = atom({
+ // 길이2
+ key: 'placementShapeDrawingLength2State',
+ default: 0,
+})
+
+export const placementShapeDrawingArrow1State = atom({
+ // 방향1
+ key: 'placementShapeDrawingArrow1State',
+ default: '',
+})
+
+export const placementShapeDrawingArrow2State = atom({
+ // 방향2
+ key: 'placementShapeDrawingArrow2State',
+ default: '',
+})
+
+export const placementShapeDrawingAngle1State = atom({
+ // 각도1
+ key: 'placementShapeDrawingAngle1State',
+ default: 0,
+})
+
+export const placementShapeDrawingAngle2State = atom({
+ // 각도2
+ key: 'placementShapeDrawingAngle2State',
+ default: 0,
+})
+
+export const placementShapeDrawingDiagonalState = atom({
+ // 대각선
+ key: 'placementShapeDrawingDiagonalState',
+ default: 0,
+})
+
+export const placementShapeDrawingTypeState = atom({
+ key: 'placementShapeDrawingTypeState',
+ default: OUTER_LINE_TYPE.OUTER_LINE,
+})
+
+export const placementShapeDrawingPointsState = atom({
+ key: 'placementShapeDrawingPointsState',
+ default: [],
+})
+
+export const placementShapeDrawingFixState = atom({
+ key: 'placementShapeDrawingFixState',
+ default: false,
+})
diff --git a/src/store/planReqAtom.js b/src/store/planReqAtom.js
new file mode 100644
index 00000000..717d506e
--- /dev/null
+++ b/src/store/planReqAtom.js
@@ -0,0 +1,22 @@
+import { atom } from 'recoil'
+import dayjs from 'dayjs'
+import { v1 } from 'uuid'
+export const planReqSearchState = atom({
+ key: `planReqSearchState/${v1()}`,
+ default: {
+ saleStoreId: '', //판매점ID 세션
+ saleStoreLevel: '', //판매점레벨 세션
+ schPlanReqNo: '', //설계의뢰 번호
+ schTitle: '', //안건명
+ schAddress: '', //도도부현
+ schSaleStoreName: '', //판매대리점명
+ schPlanReqName: '', //의뢰자명
+ schPlanStatCd: '', //상태코드
+ schDateGbn: 'S', //기간구분코드(S/R)
+ schStartDt: dayjs(new Date()).add(-3, 'month').format('YYYY-MM-DD'), //시작일
+ schEndDt: dayjs(new Date()).format('YYYY-MM-DD'), //종료일
+ startRow: 1,
+ endRow: 100,
+ },
+ dangerouslyAllowMutability: true,
+})
diff --git a/src/store/settingAtom.js b/src/store/settingAtom.js
index 7c637c2f..6ec454be 100644
--- a/src/store/settingAtom.js
+++ b/src/store/settingAtom.js
@@ -1,4 +1,4 @@
-import { atom } from 'recoil'
+import { atom, selector } from 'recoil'
export const settingModalFirstOptionsState = atom({
key: 'settingModalFirstOptions',
@@ -39,9 +39,9 @@ export const settingModalSecondOptionsState = atom({
],
option4: [
{ id: 1, column: 'adsorpRangeSmall', name: 'modal.canvas.setting.font.plan.absorption.small', selected: true, range: 10 },
- { id: 2, column: 'adsorpRangeSmallSemi', name: 'modal.canvas.setting.font.plan.absorption.small.semi', selected: false, range: 20 },
- { id: 3, column: 'adsorpRangeMedium', name: 'modal.canvas.setting.font.plan.absorption.medium', selected: false, range: 30 },
- { id: 4, column: 'adsorpRangeLarge', name: 'modal.canvas.setting.font.plan.absorption.large', selected: false, range: 40 },
+ { id: 2, column: 'adsorpRangeSmallSemi', name: 'modal.canvas.setting.font.plan.absorption.small.semi', selected: false, range: 30 },
+ { id: 3, column: 'adsorpRangeMedium', name: 'modal.canvas.setting.font.plan.absorption.medium', selected: false, range: 50 },
+ { id: 4, column: 'adsorpRangeLarge', name: 'modal.canvas.setting.font.plan.absorption.large', selected: false, range: 70 },
],
},
dangerouslyAllowMutability: true,
diff --git a/src/store/stuffAtom.js b/src/store/stuffAtom.js
index 5de2c04e..ceb5def0 100644
--- a/src/store/stuffAtom.js
+++ b/src/store/stuffAtom.js
@@ -1,16 +1,13 @@
import { atom } from 'recoil'
import dayjs from 'dayjs'
-import isLeapYear from 'dayjs/plugin/isLeapYear' // 윤년 판단 플러그인
-dayjs.extend(isLeapYear)
+import { v1 } from 'uuid'
export const stuffSearchState = atom({
- key: 'stuffSearchState',
+ key: `stuffSearchState/${v1()}`,
default: {
schObjectNo: '', //물건번호
- schSaleStoreId: '', //판매대리점ID
schAddress: '', //물건주소
schObjectName: '', //물건명
schSaleStoreName: '', //판매대리점명
- schSpecDateYn: '', //사양타입 ('', 'Y', 'N')
schReceiveUser: '', //담당자
schDispCompanyName: '', //견적처
schDateType: 'U', //갱신일(U)/등록일(R)
@@ -21,6 +18,10 @@ export const stuffSearchState = atom({
startRow: 1,
endRow: 100,
schSortType: 'R', //정렬조건 (R:최근등록일 U:최근수정일)
+ selObject: {
+ value: '',
+ label: '',
+ },
},
dangerouslyAllowMutability: true,
})
diff --git a/src/styles/_canvasside.scss b/src/styles/_canvasside.scss
new file mode 100644
index 00000000..66bba83d
--- /dev/null
+++ b/src/styles/_canvasside.scss
@@ -0,0 +1,106 @@
+// 패널 배치 집계
+.penal-wrap{
+ position: fixed;
+ top: 200px;
+ left: 50px;
+ z-index: 999999;
+ width: 237px;
+ height: 40px;
+ line-height: 40px;
+ background-color: #fff;
+ border: 1px solid #DFDFDF;
+ padding: 0 34px 0 10px;
+ border-radius: 2px;
+ box-shadow: 0px 7px 14px 0px rgba(0, 0, 0, 0.05);
+ cursor: pointer;
+ &::before{
+ content: '';
+ position: absolute;
+ top: 50%;
+ right: 12px;
+ transform: translateY(-50%);
+ width: 10px;
+ height: 6px;
+ background: url(../../public/static/images/canvas/penal_arr.svg)no-repeat center;
+ background-size: cover;
+ }
+ h2{
+ font-size: 12px;
+ font-weight: 500;
+ color: #3D3D3D;
+ }
+ .penal-table-wrap{
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: -1px;
+ min-width: calc(100% + 2px);
+ background-color: #3D3D3D;
+ border: 1px solid #3D3D3D;
+ padding: 20px;
+ .penal-table{
+ table-layout: fixed;
+ border-collapse: collapse;
+ thead{
+ th{
+ text-align: center;
+ background-color:rgba(255, 255, 255, 0.05);
+ font-size: 12px;
+ font-weight: 500;
+ color: #fff;
+ border: 1px solid #505050;
+ }
+ }
+ tbody{
+ td{
+ font-size: 12px;
+ color: #fff;
+ font-weight: 400;
+ text-align: center;
+ padding: 0 10px;
+ border: 1px solid #505050;
+ }
+ }
+ }
+ }
+ &.act{
+ border: 1px solid #3D3D3D;
+ background-color: #3D3D3D;
+ h2{
+ color: #fff;
+ }
+ &::before{
+ background: url(../../public/static/images/canvas/penal_arr_white.svg)no-repeat center;
+ }
+ .penal-table-wrap{
+ display: block;
+ }
+ }
+}
+
+// context menu
+.context-menu-wrap{
+ min-width: 238px;
+ border-radius: 4px;
+ border: 1px solid #E9E9E9;
+ background: #FFF;
+ box-shadow: 0px 6px 14px 0px rgba(0, 0, 0, 0.05);
+ ul{
+ padding: 17px 0;
+ border-bottom: 1px solid #E9E9E9;
+ &:last-child{
+ border: none;
+ }
+ li{
+ padding: 4px 30px;
+ cursor: pointer;
+ font-size: 12px;
+ font-weight: 400;
+ color: #101010;
+ &:hover{
+ color: #fff;
+ background-color: #0D99FF;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/styles/_contents.scss b/src/styles/_contents.scss
index 3d503b0b..37233d13 100644
--- a/src/styles/_contents.scss
+++ b/src/styles/_contents.scss
@@ -542,6 +542,32 @@
color: #101010;
font-weight: 600;
margin-right: 14px;
+ &.product{
+ margin-right: 10px;
+ }
+ }
+ .product_tit{
+ position: relative;
+ font-size: 15px;
+ font-weight: 600;
+ color: #1083E3;
+ padding-left: 10px;
+ &::before{
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 0;
+ transform: translateY(-50%);
+ width: 1px;
+ height: 11px;
+ background-color: #D9D9D9;
+ }
+ }
+ .option{
+ padding-left: 5px;
+ font-size: 13px;
+ color: #101010;
+ font-weight: 400;
}
.info-wrap{
display: flex;
@@ -589,16 +615,26 @@
.important{
color: #f00;
}
- .sub-table-footer{
+ .sub-center-footer{
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
- .pagination-wrap{
- margin-top: 24px;
+ .sub-right-footer{
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ margin-top: 20px;
}
}
+.pagination-wrap{
+ margin-top: 24px;
+}
+
+.infomation-wrap{
+ margin-bottom: 30px;
+}
.infomation-box-wrap{
display: flex;
@@ -634,6 +670,13 @@
display: flex;
align-items: center;
margin-bottom: 10px;
+ &.one{
+ .estimate-box{
+ &:last-child{
+ min-width: unset;
+ }
+ }
+ }
.estimate-box{
flex: 1 ;
display: flex;
@@ -658,6 +701,16 @@
color: #344356;
margin-left: 14px;
font-weight: 400;
+ &.blue{
+ font-size: 16px;
+ font-weight: 700;
+ color: #1083E3;
+ }
+ &.red{
+ font-size: 16px;
+ font-weight: 700;
+ color: #D72A2A;
+ }
}
}
&:last-child{
@@ -714,6 +767,162 @@
}
}
+.special-note-check-wrap{
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ border: 1px solid #ECF0F4;
+ border-radius: 3px;
+ margin-bottom: 30px;
+ .special-note-check-item{
+ padding: 14px 10px;
+ border-right: 1px solid #ECF0F4;
+ border-top: 1px solid #ECF0F4;
+ &:nth-child(5n){
+ border-right: none;
+ }
+ &:nth-child(-n+5){
+ border-top: none;
+ }
+ &.act{
+ background-color: #F7F9FA;
+ }
+ }
+}
+
+.calculation-estimate{
+ border: 1px solid #ECF0F4;
+ border-radius: 3px;
+ padding: 24px;
+ max-height: 350px;
+ overflow-y: auto;
+ margin-bottom: 30px;
+ dl{
+ margin-bottom: 35px;
+ &:last-child{
+ margin-bottom: 0;
+ }
+ dt{
+ font-size: 13px;
+ font-weight: 600;
+ color: #1083E3;
+ margin-bottom: 15px;
+ }
+ dd{
+ font-size: 12px;
+ font-weight: 400;
+ color: #45576F;
+ margin-bottom: 8px;
+ &:last-child{
+ margin-bottom: 0;
+ }
+ }
+ }
+ &::-webkit-scrollbar {
+ width: 4px;
+ background-color: transparent;
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: #d9dee2;
+ }
+ &::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
+}
+.esimate-wrap{
+ margin-bottom: 20px;
+}
+
+.estimate-product-option{
+ display: flex;
+ align-items: center;
+ margin-bottom: 15px;
+ .product-price-wrap{
+ display: flex;
+ align-items: center;
+ .product-price-tit{
+ font-size: 13px;
+ font-weight: 400;
+ color: #45576F;
+ margin-right: 10px;
+ }
+ .select-wrap{
+ width: 110px;
+ }
+ }
+ .product-edit-wrap{
+ display: flex;
+ align-items: center;
+ margin-left: auto;
+ .product-edit-explane{
+ display: flex;
+ align-items: center;
+ margin-right: 15px;
+ .attachment-required{
+ position: relative;
+ display: flex;
+ align-items: center;
+ font-size: 12px;
+ font-weight: 400;
+ color: #45576F;
+ padding-right: 10px;
+ .ico{
+ width: 23px;
+ height: 23px;
+ margin-right: 5px;
+ background: url(../../public/static/images/sub/attachment_ico.svg)no-repeat center;
+ background-size: cover;
+ }
+ &::before{
+ content: '';
+ position: absolute;
+ top: 50%;
+ right: 0;
+ transform: translateY(-50%);
+ width: 1px;
+ height: 12px;
+ background-color: #D9D9D9;
+ }
+ }
+ .click-check{
+ display: flex;
+ align-items: center;
+ font-size: 12px;
+ font-weight: 400;
+ color: #F16A6A ;
+ padding-left: 10px;
+ .ico{
+ width: 14px;
+ height: 14px;
+ margin-right: 5px;
+ background: url(../../public/static/images/sub/click_check_ico.svg)no-repeat center;
+ background-size: cover;
+ }
+ }
+ }
+ .product-edit-btn{
+ display: flex;
+ align-items: center;
+ button{
+ display: flex;
+ align-items: center;
+ span{
+ width: 13px;
+ height: 13px;
+ margin-right: 5px;
+ background-size: cover;
+ &.plus{
+ background: url(../../public/static/images/sub/plus_btn.svg)no-repeat center;
+ }
+ &.minus{
+ background: url(../../public/static/images/sub/minus_btn.svg)no-repeat center;
+ }
+ }
+ }
+ }
+ }
+
+}
+
// 발전시물레이션
.chart-wrap{
display: flex;
@@ -842,4 +1051,153 @@
color: #344356;
font-weight: 500;
}
+}
+
+// 물건상세
+.information-help-wrap{
+ display: flex;
+ padding: 24px;
+ background-color: #F4F4F4;
+ border-radius: 4px;
+ margin-bottom: 15px;
+ .information-help-tit-wrap{
+ position: relative;
+ display: flex;
+ align-items: center;
+ padding-right: 40px;
+ border-right: 1px solid #E0E0E3;
+ .help-tit-icon{
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ margin-right: 10px;
+ background: #fff url(../../public/static/images/sub/information_help.svg)no-repeat center;
+ background-size: 20px 20px;
+ }
+ .help-tit{
+ font-size: 13px;
+ font-weight: 600;
+ color: #45576F;
+ }
+ }
+ .information-help-guide{
+ padding-left: 40px;
+ span{
+ display: block;
+ font-size: 12px;
+ font-weight: 400;
+ color: #45576F;
+ margin-bottom: 7px;
+ &:last-child{
+ margin-bottom: 0;
+ }
+ }
+ }
+}
+
+.community-search-warp{
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 10px 0 30px 0;
+ border-bottom: 1px solid #E5E5E5;
+ margin-bottom: 24px;
+ .community-search-box{
+ position: relative;
+ display: flex;
+ align-items: center;
+ width: 580px;
+ height: 45px;
+ padding: 0 45px 0 20px;
+ margin-bottom: 20px;
+ border-radius: 2px;
+ border: 1px solid #101010;
+ .community-input{
+ width: 100%;
+ height: 100%;
+ font-size: 13px;
+ font-weight: 400;
+ color: #101010;
+ &::placeholder{
+ color: #C8C8C8;
+ }
+ }
+ .community-search-ico{
+ position: absolute;
+ top: 50%;
+ right: 20px;
+ transform: translateY(-50%);
+ flex: none;
+ width: 21px;
+ height: 100%;
+ background: url(../../public/static/images/sub/community_search.svg)no-repeat center;
+ background-size: 21px 21px;
+ z-index: 3;
+ }
+ }
+ .community-search-keyword{
+ font-size: 13px;
+ font-weight: 400;
+ color: #45576F;
+ span{
+ font-weight: 600;
+ color: #F16A6A;
+ }
+ }
+}
+
+// 자료 다운로드
+.file-down-list{
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 14px;
+ .file-down-item{
+ display: flex;
+ align-items: center;
+ padding: 24px;
+ border-radius: 4px;
+ border: 1px solid #E5E5E5;
+ background: #FFF;
+ transition: all .15s ease-in-out;
+ cursor: pointer;
+ .file-item-info{
+ .item-num{
+ display: inline-block;
+ padding: 6px 17.5px;
+ border-radius: 60px;
+ background-color: #F4F4F7;
+ font-size: 13px;
+ font-weight: 600;
+ color: #101010;
+ margin-bottom: 15px;
+ }
+ .item-name{
+ font-size: 16px;
+ color: #101010;
+ font-weight: 500;
+ margin-bottom: 13px;
+ }
+ .item-date{
+ font-size: 13px;
+ font-weight: 400;
+ color: #344356;
+ }
+ }
+ .file-down-box{
+ display: flex;
+ align-items: center;
+ flex: none;
+ margin-left: auto;
+ height: 100%;
+ .file-down-btn{
+ width: 36px;
+ height: 36px;
+ background: url(../../public/static/images/sub/file_down_btn.svg)no-repeat center;
+ background-size: cover;
+ }
+ }
+ &:hover{
+ background-color: #F4F4F7;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/styles/_grid-detail.scss b/src/styles/_grid-detail.scss
index a673236a..048a1946 100644
--- a/src/styles/_grid-detail.scss
+++ b/src/styles/_grid-detail.scss
@@ -1,5 +1,5 @@
.q-grid{
- height: fit-content;
+ position: relative;
.ag-theme-quartz {
outline: none;
border: none;
@@ -36,6 +36,9 @@
&:nth-child(2n){
background-color: #F7F9FA;
}
+ &.important_row{
+ background-color: #e7e7e7;
+ }
}
.ag-cell{
font-size: 13px;
@@ -58,4 +61,68 @@
margin-left: 12px;
}
}
+ &.no-cols{
+ .ag-row{
+ &:nth-child(2n){
+ background-color: #fff;
+ }
+ }
+ }
+ .form-flex-wrap{
+ display: flex;
+ align-items: center;
+ width: 100%;
+ .grid-tip{
+ margin-left: auto;
+ }
+ }
+}
+
+// grid-button
+.grid-cell-btn{
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ width: 117px;
+ margin: 0 auto;
+ .grid-btn{
+ display: block;
+ width: 100%;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #fff;
+ border: 1px solid #94A0AD;
+ background-color: transparent;
+ border-radius: 2px;
+ font-size: 13px;
+ color: #94A0AD;
+ font-weight: 400;
+ text-align: center;
+ span{
+ display: block;
+ margin-right: 5px;
+ &.file{
+ width: 13px;
+ height: 14px;
+ background: url(../../public/static/images/sub/grid-btn-file.svg)no-repeat center;
+ background-size: cover;
+ }
+ &.excel{
+ width: 14px;
+ height: 13px;
+ background: url(../../public/static/images/sub/grid-btn-excel.svg)no-repeat center;
+ background-size: cover;
+ }
+ }
+ }
+}
+
+.grid-tip{
+ display: block;
+ width: 15px;
+ height: 15px;
+ background: url(../../public/static/images/sub/grid_tip.svg)no-repeat center;
+ background-size: cover;
}
\ No newline at end of file
diff --git a/src/styles/_main.scss b/src/styles/_main.scss
index 334a0485..37170a8b 100644
--- a/src/styles/_main.scss
+++ b/src/styles/_main.scss
@@ -394,6 +394,9 @@
font-weight: 600;
margin-bottom: 5px;
}
+ &.pw-reset{
+ font-size: 13px;
+ }
}
.login-input-wrap{
margin-top: 30px;
@@ -412,8 +415,10 @@
background-color: transparent;
font-size: 13px;
font-weight: 400;
- color: #D1D7E0;
+ color: #6c819c;
&::placeholder{
+ font-size: 13px;
+ font-weight: 400;
color: #D1D7E0;
}
}
@@ -442,6 +447,17 @@
background-size: 17px 17px;
}
}
+ &.email{
+ &::before{
+ background: url(../../public/static/images/main/login_email.svg)no-repeat center;
+ width: 12px;
+ height: 9px;
+ }
+ .id-delete{
+ background-image: url(../../public/static/images/main/id_delete.svg);
+ background-size: 17px 17px;
+ }
+ }
&.password{
margin-bottom: 20px;
&::before{
@@ -456,27 +472,35 @@
}
}
}
+ .login-btn{
+ display: block;
+ width: 100%;
+ height: 45px;
+ background-color: #5C6773;
+ color: #fff;
+ font-size: 15px;
+ font-weight: 600;
+ border-radius: 4px;
+ transition: background .15s ease-in-out;
+ &:hover{
+ background-color: #717e8d;
+ }
+ &.light{
+ background-color: #fff;
+ border: 1px solid #5C6773;
+ color: #5C6773;
+ }
+ }
.login-btn-box{
margin-bottom: 20px;
- .login-btn{
- display: block;
- width: 100%;
- height: 45px;
- background-color: #5C6773;
- color: #fff;
- font-size: 15px;
- font-weight: 600;
- border-radius: 4px;
- transition: background .15s ease-in-out;
- &:hover{
- background-color: #717e8d;
- }
- }
+ }
+ .pwreset-btn-box{
+ display: flex;
}
.reset-password{
width: 100%;
text-align: center;
- a{
+ button{
position: relative;
font-size: 13px;
color: #364864;
@@ -554,4 +578,75 @@
top: -2px;
left: 1px;
}
+}
+
+// 회원가입
+.center-page-wrap{
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ width: 100%;
+ min-height: 100vh;
+ background-color: #F4F4F7;
+ overflow-x: hidden;
+ .center-page-inner{
+ width: 100%;
+ max-width: 1720px;
+ margin: 0 auto;
+ .center-page-tit{
+ font-size: 18px;
+ font-weight: 600;
+ color: #101010;
+ margin-bottom: 24px;
+ }
+ .sub-table-box{
+ &.signup{
+ margin-bottom: 20px;
+ }
+ }
+ .sign-up-btn-wrap{
+ display: flex;
+ justify-content: flex-end;
+ }
+ &.complete{
+ max-width: 1000px;
+ }
+ }
+
+}
+
+// 회원가입 완료
+.complete-box-wrap{
+ padding: 72px 80px;
+ .complete-tit{
+ font-size: 18px;
+ font-weight: 600;
+ color: #101010;
+ margin-bottom: 17px;
+ }
+ .complete-txt{
+ font-size: 13px;
+ font-weight: 400;
+ color: #101010;
+ margin-bottom: 27px;
+ }
+ .complete-email-wrap{
+ padding: 36px 30px;
+ border-radius: 2px;
+ background: #F4F4F7;
+ margin-bottom: 20px;
+ .email-info{
+ font-size: 13px;
+ font-weight: 400;
+ color: #000;
+ span{
+ color: #204AF4;
+ font-weight: 500;
+ }
+ }
+ }
+ .complete-btn{
+ display: flex;
+ justify-content: flex-end;
+ }
}
\ No newline at end of file
diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss
index 6309f50e..9bd59e3d 100644
--- a/src/styles/_modal.scss
+++ b/src/styles/_modal.scss
@@ -13,12 +13,20 @@ $alert-color: #101010;
to{opacity: 0; scale: 0.95;}
}
+.normal-font{
+ font-size: 12px;
+ font-weight: 400;
+ color: #fff;
+}
+.bold-font{
+ font-size: 12px;
+ font-weight: 500;
+ color: #fff;
+}
+
.modal-pop-wrap{
position: fixed;
- top: 200px;
- right: 100px;
width: 100%;
- min-width: 300px;
height: -webkit-fit-content;
height: -moz-fit-content;
height: fit-content;
@@ -26,18 +34,41 @@ $alert-color: #101010;
border-radius: 4px;
background-color: #272727;
z-index: 9999999;
- overflow: hidden;
- &.r{
- width: 400px;
+ &.xxxm{
+ width: 240px;
}
- &.sm{
- width: 580px;
+ &.xxm{
+ width: 270px;
+ }
+ &.xm{
+ width: 300px;
}
&.ssm{
width: 380px;
}
- &.xm{
- width: 300px;
+ &.sm{
+ width: 580px;
+ }
+ &.r{
+ width: 400px;
+ }
+ &.lr{
+ width: 440px;
+ }
+ &.lrr{
+ width: 480px;
+ }
+ &.ml{
+ width: 530px;
+ }
+ &.l-2{
+ width: 640px;
+ }
+ &.lx-2{
+ width: 740px;
+ }
+ &.lx{
+ width: 770px;
}
&.l{
width: 800px;
@@ -84,6 +115,7 @@ $alert-color: #101010;
align-items: center;
padding: 10px 24px;
background-color: #000;
+ // overflow: hidden;
h1.title{
font-size: 13px;
color: $pop-color;
@@ -91,8 +123,8 @@ $alert-color: #101010;
}
.modal-close{
margin-left: auto;
- color: $pop-color;
- text-indent: -999999999px;
+ color: transparent;
+ font-size: 0;
width: 10px;
height: 10px;
background: url(../../public/static/images/canvas/modal_close.svg)no-repeat center;
@@ -107,6 +139,13 @@ $alert-color: #101010;
button{
flex: 1;
}
+ &.sub{
+ button{
+ flex: 1 1 auto;
+ padding: 0;
+ }
+ margin-bottom: 14px;
+ }
}
.modal-check-btn-wrap{
margin-top: 15px;
@@ -197,19 +236,20 @@ $alert-color: #101010;
align-items: center;
gap: 15px;
padding-bottom: 15px;
- border-bottom: 1px solid #3C3C3C;
+ &.border{
+ border-bottom: 1px solid #424242;
+ }
}
.grid-option-wrap{
- padding: 15px 0;
- border-bottom: 1px solid #3C3C3C;
.grid-option-box{
display: flex;
align-items: center;
- background-color: #3D3D3D;
+ background-color: transparent;
+ border: 1px solid #3D3D3D;
border-radius: 2px;
- padding: 10px;
+ padding: 15px 10px;
gap: 20px;
- margin-bottom: 5px;
+ margin-bottom: 10px;
.grid-input-form{
display: flex;
align-items: center;
@@ -231,18 +271,43 @@ $alert-color: #101010;
}
}
}
+.select-form{
+ .sort-select{width: 100%;}
+}
.grid-select{
flex: 1;
+ &.no-flx{
+ flex: unset;
+ }
.sort-select{
width: 100%;
background-color: #313131;
+ min-width: auto;
+ font-size: 12px;
+ border: none;
+ p{
+ font-size: 12px;
+ }
+ > ul{
+ border: none;
+ }
+ }
+ &.right{
+ p{
+ text-align: right;
+ }
+ ul{
+ li{
+ justify-content: flex-end;
+ }
+ }
}
}
.grid-btn-wrap{
padding-top: 15px;
text-align: right;
button{
- padding: 0 20px;
+ padding: 0 10px;
}
}
@@ -252,7 +317,7 @@ $alert-color: #101010;
color: $pop-color;
font-weight: $pop-normal-weight;
padding-bottom: 15px;
- border-bottom: 1px solid #3C3C3C;
+
}
.grid-direction{
display: flex;
@@ -403,7 +468,8 @@ $alert-color: #101010;
// 외벽선 그리기
.outline-wrap{
padding: 24px 0;
- border-bottom: 1px solid #424242;
+ border-top: 1px solid #424242;
+
.outline-inner{
display: flex;
align-items: center;
@@ -411,13 +477,19 @@ $alert-color: #101010;
&:last-child{
margin-bottom: 0;
}
+ .outline-form{
+ // width: 50%;
+ margin-right: 15px;
+ }
+ }
+ &:last-child{
+ border-bottom: 1px solid #424242;
}
}
.outline-form{
- width: 50%;
display: flex;
align-items: center;
- margin-right: 15px;
+
span{
width: 60px;
flex: none;
@@ -425,7 +497,13 @@ $alert-color: #101010;
font-weight: $pop-bold-weight;
color: $pop-color;
margin-right: 10px;
+ &.thin{
+ width: auto;
+ font-weight: $pop-normal-weight;
+ margin-right: 0;
+ }
}
+
.reset-btn{
flex: none;
width: 30px;
@@ -466,4 +544,974 @@ $alert-color: #101010;
background-color: #3D3D3D;
border-radius: 2px ;
}
+}
+
+// 외벽선 속성 설정
+.properties-guide{
+ font-size: $pop-normal-size;
+ color: #AAA;
+ font-weight: $pop-normal-weight;
+ margin-bottom: 14px;
+}
+
+.setting-tit{
+ font-size: 13px;
+ color: $pop-color;
+ font-weight: $pop-bold-weight;
+ margin-bottom: 10px;
+}
+.properties-setting-wrap{
+ &.outer{
+ margin-top: 24px;
+ }
+ .setting-btn-wrap{
+ display: flex;
+ align-items: center;
+ padding: 14px 0;
+ border-top: 1px solid #424242;
+ border-bottom: 1px solid #424242;
+ .setting-btn{
+ display: block;
+ width: 100%;
+ height: 40px;
+ font-size: 13px;
+ color: #fff;
+ font-weight: 700;
+ border-radius: 2px;
+ transition: all .15s ease-in-out;
+ &.green{
+ background-color: #305941;
+ border: 1px solid #45CD7D;
+ &:hover{
+ background-color: #3a6b4e;
+ }
+ }
+ &.blue{
+ background-color: #2E5360;
+ border: 1px solid #3FBAE6;
+ &:hover{
+ background-color: #365f6e;
+ }
+ }
+ }
+ }
+}
+
+// 지붕형상 설정
+.roof-shape-menu{
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ grid-template-rows: 1fr 1fr;
+ gap: 24px 10px;
+ margin-bottom: 24px;
+ .shape-box{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ padding: 13px;
+ background-color: #3D3D3D;
+ transition: background .15s ease-in-out;
+ img{
+ max-width: 100%;
+ }
+ }
+ .shape-title{
+ font-size: $pop-normal-size;
+ font-weight: $pop-bold-weight;
+ color: $pop-color;
+ margin-top: 10px;
+ text-align: center;
+ transition: color .15s ease-in-out;
+ }
+ .shape-menu-box{
+ &.act,
+ &:hover{
+ .shape-box{background-color: #008BFF;}
+ .shape-title{color: #008BFF;}
+ }
+ }
+}
+
+.setting-box{
+ padding: 14px 0;
+ border-top: 1px solid #424242;
+ border-bottom: 1px solid #424242;
+}
+.padding-form{
+ padding-left: 23px;
+}
+.discrimination-box{
+ padding: 16px 12px;
+ border: 1px solid #3D3D3D;
+ border-radius: 2px;
+}
+
+.modal-bottom-border-bx{
+ margin-top: 24px;
+ padding-bottom: 14px;
+ border-bottom: 1px solid #424242;
+}
+
+// 처마∙케라바 변경
+.eaves-keraba-table{
+ display: table;
+ border-collapse: collapse;
+ .eaves-keraba-item{
+ display: table-row;
+ .eaves-keraba-th,
+ .eaves-keraba-td{
+ font-size: $pop-normal-size;
+ color: $pop-color;
+ font-weight: $pop-normal-weight;
+ display: table-cell;
+ vertical-align: middle;
+ padding-bottom: 14px;
+ }
+ .eaves-keraba-td{
+ padding-left: 10px;
+ }
+ .eaves-keraba-ico{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 5px;
+ background-color: #3D3D3D;
+ border: 1px solid #3D3D3D;
+ border-radius: 2px;
+ cursor: pointer;
+ &.act{
+ border: 1px solid #ED0004;
+ }
+ }
+ &:last-child{
+ .eaves-keraba-th,
+ .eaves-keraba-td{
+ padding-bottom: 0;
+ }
+ }
+ }
+}
+.guide{
+ font-size: $pop-normal-size;
+ font-weight: $pop-normal-weight;
+ color: $pop-color;
+ margin-bottom: 24px;
+ &.sm{
+ margin-bottom: 15px;
+ }
+ span{
+ display: block;
+ }
+}
+
+// 지붕면 할당
+.allocation-select-wrap{
+ display: flex;
+ align-items: center;
+ padding-bottom: 14px;
+ border-bottom: 1px solid #424242;
+ margin-bottom: 14px;
+ span{
+ font-size: $pop-normal-size;
+ color: $pop-color;
+ font-weight: $pop-bold-weight;
+ margin-right: 10px;
+ }
+ .allocation-edit{
+ display: flex;
+ align-items: center;
+ height: 30px;
+ padding: 0 10px;
+ margin-left: 5px;
+ font-size: $pop-normal-size;
+ color: $pop-color;
+ font-weight: $pop-normal-weight;
+ border: 1px solid #484848;
+ background-color: #323234;
+ i{
+ display: block;
+ width: 12px;
+ height: 12px;
+ margin-right: 5px;
+ background: url(../../public/static/images/canvas/allocation_edit.svg)no-repeat center;
+ background-size: cover;
+ }
+ }
+}
+
+.block-box{
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 10px;
+ .flex-ment{
+ gap: 10px;
+ .dec{
+ text-decoration: underline;
+ }
+ .delete{
+ display: block;
+ width: 15px;
+ height: 15px;
+ background: url(../../public/static/images/canvas/allocation_delete.svg)no-repeat center;
+ background-size: cover;
+ }
+ }
+ &:last-child{
+ margin-bottom: 0;
+ }
+}
+
+.icon-btn-wrap{
+ flex: 1;
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ button{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 30px;
+ font-size: $pop-normal-size;
+ font-weight: $pop-normal-weight;
+ color: $pop-color;
+ border: 1px solid #646464;
+ border-radius: 2px;
+ padding: 0 10px;
+ transition: all .15s ease-in-out;
+ i{
+ height: 15px;
+ display: block;
+ margin-left: 10px;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: cover;
+ transition: all .15s ease-in-out;
+ &.allocation01{
+ background-image: url(../../public/static/images/canvas/allocation_icon01_white.svg);
+ width: 15px;
+ }
+ &.allocation02{
+ background-image: url(../../public/static/images/canvas/allocation_icon02_white.svg);
+ width: 18px;
+ }
+ }
+ &.act,
+ &:hover{
+ color: #101010;
+ border: 1px solid #101010;
+ background-color: #fff;
+ i{
+ &.allocation01{
+ background-image: url(../../public/static/images/canvas/allocation_icon01_black.svg);
+ }
+ &.allocation02{
+ background-image: url(../../public/static/images/canvas/allocation_icon02_black.svg);
+ }
+ }
+ }
+ }
+}
+
+// 경사설정
+.slope-wrap{
+ padding-bottom: 24px;
+ border-bottom: 1px solid #424242;
+}
+
+// 면형상 배치
+.plane-shape-menu{
+ display: grid;
+ grid-template-columns: repeat(6, 1fr);
+ grid-template-rows: repeat(3, 90px);
+ gap: 10px;
+ margin-bottom: 10px;
+ .shape-menu-box{
+ border-radius: 2px;
+ background-color: #3D3D3D;
+ padding: 8px;
+ transition: all .15s ease-in-out;
+ .shape-box{
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ width: 100%;
+ height: 100%;
+ background-color: #313131;
+ border-radius: 2px;
+ }
+ &.act,
+ &:hover{
+ background-color: #008BFF;
+ }
+ }
+}
+
+.shape-library{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 5px;
+ padding: 5px;
+ background-color: #3D3D3D;
+ margin-bottom: 24px;
+ .library-btn{
+ width: 30px;
+ height: 30px;
+ border: 1px solid #6C6C6C;
+ border-radius: 2px;
+ background-color: transparent;
+ background-repeat: no-repeat;
+ background-position: center;
+ transition: all .15s ease-in-out;
+ &.ico01{background-image: url(../../public/static/images/canvas/shape_labrary01.svg); background-size: 14px 14px;}
+ &.ico02{background-image: url(../../public/static/images/canvas/shape_labrary02.svg); background-size: 13px 17px;}
+ &.ico03{background-image: url(../../public/static/images/canvas/shape_labrary03.svg); background-size: 17px 13px;}
+ &:hover{
+ border-color: #1083E3;
+ background-color: #1083E3;
+ }
+ }
+}
+
+.plane-shape-wrapper{
+ display: flex;
+ gap: 10px;
+ .plane-box{
+ padding: 10px;
+ border-radius: 2px;
+ background-color: #3D3D3D;
+ .plane-box-tit{
+ font-size: $pop-normal-size;
+ font-weight: 600;
+ color: $pop-color;
+ margin-bottom: 10px;
+ }
+ &.shape-box{
+ flex: 1;
+ .shape-box-inner{
+ display: flex;
+ gap:10px;
+ min-height: 140px;
+ .shape-img{
+ position: relative;
+ flex: 1;
+ background-color: #fff;
+ border-radius: 2px;
+ img{
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+ }
+ .shape-data{
+ flex: none;
+ width: 190px;
+ background-color: #313131;
+ border-radius: 2px;
+ padding: 15px;
+ .eaves-keraba-table{
+ .eaves-keraba-item{
+ .eaves-keraba-th,
+ .eaves-keraba-td{
+ padding-bottom: 10px;
+ }
+ &:last-child{
+ .eaves-keraba-th,
+ .eaves-keraba-td{
+ padding-bottom: 0px;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ &.direction-box{
+ display: flex;
+ flex-direction: column;
+ flex: none;
+ width: 180px;
+ .plane-direction-box{
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ padding: 10px 5px;
+ }
+ }
+ }
+}
+.plane-direction{
+ width: 150px;
+ position: relative;
+ height: 120px;
+ span{
+ position: absolute;
+ font-size: 12px;
+ font-weight: 500;
+ color: #B1B1B1;
+ &.top{top: 0; left: 50%; transform: translateX(-50%);}
+ &.right{top: 50%; right: 0; transform: translateY(-50%);}
+ &.bottom{bottom: 0; left: 50%; transform: translateX(-50%);}
+ &.left{top: 50%; left: 0; transform: translateY(-50%);}
+ }
+ .plane-btn{
+ position: absolute;
+ width: 28px;
+ height: 28px;
+ background-color: #777777;
+ background-image: url(../../public/static/images/canvas/plane_arr.svg);
+ background-size: 12px 13px;
+ background-repeat: no-repeat;
+ background-position: center;
+ border-radius: 50%;
+ transition: all .15s ease-in-out;
+ &.up{top: 22px; left: 50%; transform: translateX(-50%);}
+ &.right{top: 50%; right: 32px; transform: translateY(-50%) rotate(90deg);}
+ &.down{bottom: 22px; left: 50%; transform: translateX(-50%) rotate(180deg);}
+ &.left{top: 50%; left: 32px; transform: translateY(-50%) rotate(270deg);}
+ &:hover,
+ &.act{
+ background-color: #fff;
+ background-image: url(../../public/static/images/canvas/plane_arr_act.svg);
+ }
+ }
+}
+
+.plane-tab-guide{
+ font-size: $pop-normal-size;
+ font-weight: $pop-normal-weight;
+ color: $pop-color;
+ margin-top: 24px;
+ padding-bottom: 14px;
+ border-bottom: 1px solid #424242;
+}
+
+// 오브젝트 배치
+.mb-box{
+ margin-bottom: 24px;
+}
+
+.object-direction-wrap{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.discrimination-tit{
+ font-size: 13px;
+ color: #fff;
+ font-weight: 500;
+}
+
+.object-size-wrap{
+ display: flex;
+ min-height: 206px;
+ gap: 24px;
+ margin-top: 14px;
+ .object-size-img{
+ position: relative;
+ flex: none;
+ width: 200px;
+ background-color: #fff;
+ img{
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+ }
+}
+
+// 표시변경
+.display-change-wrap{
+ margin: 24px 0;
+}
+.warning{
+ font-size: $pop-normal-size;
+ font-weight: $pop-normal-weight;
+ color: #FFAFAF;
+}
+
+// 각 변 속성 변경
+.radio-grid-wrap{
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 24px 15px;
+}
+
+// 면 흐름 설정
+.drawing-flow-wrap{
+ display: flex;
+ gap: 10px;
+ .discrimination-box{
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ .object-direction-wrap{
+ flex: 1;
+ }
+ }
+}
+
+.compas-box{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.compas-box-inner {
+ position: relative;
+ width: 200px;
+ height: 200px;
+ border-radius: 50%;
+
+ .circle {
+ position: absolute;
+ width: 12px;
+ height: 12px;
+ border: 1px solid #fff;
+ border-radius: 50%;
+ top: 95%;
+ left: 50%;
+ transform-origin: 0 -90px; /* 중심에서 반지름 거리만큼 떨어져 위치 */
+ cursor:pointer;
+ z-index: 3;
+ /* 0번을 180도 위치(아래)에, 13번을 0도 위치(위)에 배치 */
+ i{
+ position: absolute;
+ top: 12.5px;
+ left: 50%;
+ font-size: 11px;
+ color: #8B8B8B;
+ font-weight: 500;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-use-select: none;
+ user-select: none;
+ }
+ &:nth-child(1) { transform: rotate(180deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(180deg);}}
+ &:nth-child(2) { transform: rotate(195deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(165deg);}}
+ &:nth-child(3) { transform: rotate(210deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(150deg);}}
+ &:nth-child(4) { transform: rotate(225deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(135deg);}}
+ &:nth-child(5) { transform: rotate(240deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(120deg);}}
+ &:nth-child(6) { transform: rotate(255deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(105deg);}}
+ &:nth-child(7) { transform: rotate(270deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(90deg);}}
+ &:nth-child(8) { transform: rotate(285deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(75deg);}}
+ &:nth-child(9) { transform: rotate(300deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(60deg);}}
+ &:nth-child(10) { transform: rotate(315deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(45deg);}}
+ &:nth-child(11) { transform: rotate(330deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(30deg);}}
+ &:nth-child(12) { transform: rotate(345deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(15deg);}}
+ &:nth-child(13) { transform: rotate(0deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(0deg);}}
+ &:nth-child(14) { transform: rotate(15deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-15deg);}}
+ &:nth-child(15) { transform: rotate(30deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-30deg);}}
+ &:nth-child(16) { transform: rotate(45deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-45deg);}}
+ &:nth-child(17) { transform: rotate(60deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-60deg);}}
+ &:nth-child(18) { transform: rotate(75deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-75deg);}}
+ &:nth-child(19) { transform: rotate(90deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-90deg);}}
+ &:nth-child(20) { transform: rotate(105deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-105deg);}}
+ &:nth-child(21) { transform: rotate(120deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-120deg);}}
+ &:nth-child(22) { transform: rotate(135deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-135deg);}}
+ &:nth-child(23) { transform: rotate(150deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-150deg);}}
+ &:nth-child(24) { transform: rotate(165deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-165deg);}}
+ &.act{
+ &::after{
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 5px;
+ height: 5px;
+ background-color: #fff;
+ }
+ i{
+ color: #fff;
+ }
+ }
+ }
+ .compas{
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 148px;
+ height: 148px;
+ border: 4px solid #fff;
+ border-radius: 50%;
+ .compas-arr{
+ width: 100%;
+ height: 100%;
+ background: url(../../public/static/images/canvas/compas.svg)no-repeat center;
+ background-size: 122px 122px;
+ }
+ }
+}
+
+
+// 지붕모듈선택
+.roof-module-tab{
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 14px;
+ .module-tab-bx{
+ flex: 1;
+ height: 34px;
+ line-height: 31px;
+ border: 1px solid #484848;
+ border-radius: 2px;
+ background-color: transparent;
+ font-size: 12px;
+ color: #AAA;
+ text-align: center;
+ cursor: default;
+ transition: all .15s ease-in-out;
+ &.act{
+ background-color: #1083E3;
+ border: 1px solid #1083E3;
+ color: #fff;
+ font-weight: 500;
+ }
+ }
+ .tab-arr{
+ display: block;
+ width: 9px;
+ height: 14px;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: cover;
+ background-image: url(../../public/static/images/canvas/module_tab_arr.svg);
+ transition: all .15s ease-in-out;
+ &.act{
+ background-image: url(../../public/static/images/canvas/module_tab_arr_white.svg);
+ }
+ }
+}
+
+.roof-module-compas{
+ margin-bottom: 24px;
+ .compas-box-inner{
+ width: 280px;
+ height: 253px;
+ .circle{
+ top: 86%;
+ &:nth-child(1),
+ &:nth-child(7),
+ &:nth-child(13),
+ &:nth-child(19){
+ &::before{
+ content: '';
+ position: absolute;
+ top: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 1px;
+ height: 6px;
+ background-color: #8B8B8B;
+ }
+ }
+ i{
+ top: 32px;
+ }
+ &.act{
+ i{color: #8B8B8B;}
+ }
+ }
+ }
+}
+.center-wrap{
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+}
+
+.module-table-flex-wrap{
+ display: flex;
+ gap: 10px;
+ .outline-form{
+ flex: 1;
+ }
+}
+
+.module-box-tab{
+ display: flex;
+ .module-btn{
+ flex: 1;
+ border-top: 1px solid #505050;
+ border-bottom: 1px solid #505050;
+ border-right: 1px solid #505050;
+ background-color: #454545;
+ color: #fff;
+ height: 30px;
+ font-size: 12px;
+ font-weight: 400;
+ transition: all .15s ease-in-out;
+ &:first-child{
+ border-left: 1px solid #505050;
+ }
+ &.act{
+ border-color: #fff;
+ background-color: #fff;
+ color: #101010;
+ }
+ }
+}
+
+.module-table-box{
+ flex: 1;
+ background-color: #3D3D3D;
+ border-radius: 2px;
+ .module-table-inner{
+ padding: 10px;
+ .outline-form{
+ span{
+ width: auto;
+ }
+ }
+ .module-table-tit{
+ padding: 10px 0;
+ font-size: 12px;
+ color: #fff;
+ border-bottom: 1px solid #4D4D4D;
+ }
+ .eaves-keraba-table{
+ width: 100%;
+ margin-top: 15px;
+ .eaves-keraba-th{
+ width: 72px;
+ }
+ .eaves-keraba-th,
+ .eaves-keraba-td{
+ padding-bottom: 5px;
+ }
+ }
+ .self-table-tit{
+ font-size: 12px;
+ font-weight: 500;
+ color: #fff;
+ padding-bottom: 15px;
+ }
+ }
+ .warning-guide{
+ padding: 20px;
+ .warning{
+ color: #FFCACA;
+ max-height: 55px;
+ overflow-y: auto;
+ padding-right: 30px;
+ &::-webkit-scrollbar {
+ width: 4px;
+ background-color: transparent;
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: #D9D9D9;
+ }
+ &::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
+ }
+ }
+}
+
+.module-self-table{
+ display: table;
+ border-top: 1px solid #4D4D4D;
+ border-collapse: collapse;
+ width: 100%;
+ .self-table-item{
+ display: table-row;
+ .self-item-td,
+ .self-item-th{
+ display: table-cell;
+ vertical-align: middle;
+ border-bottom: 1px solid #4D4D4D;
+ }
+ .self-item-th{
+ width: 60px;
+ font-size: 12px;
+ font-weight: 500;
+ color: #fff;
+ }
+ .self-item-td{
+ font-size: 12px;
+ font-weight: 400;
+ color: #fff;
+ padding: 15px 20px;
+ }
+ }
+}
+
+.self-table-flx{
+ display: flex;
+ align-items: center;
+ margin-top: 15px;
+ button{
+ margin-left: auto;
+ }
+}
+.hexagonal-wrap{
+ .hexagonal-item{
+ padding: 15px 0;
+ border-bottom: 1px solid #4D4D4D;
+ &:first-child{
+ padding-top: 0;
+ }
+ &:last-child{
+ padding-bottom: 0;
+ border: none;
+ }
+ .hexagonal-flx-auto{
+ display: flex;
+ justify-content: space-between;
+ }
+ .hexagonal-flx{
+ display: flex;
+ align-items: center;
+ button{
+ margin-left: auto;
+ }
+ }
+ }
+}
+
+// 회로 및 가대설정
+.circuit-check-inner{
+ padding: 5px 0;
+}
+
+.x-scroll-table{
+ overflow-x: auto;
+ padding-bottom: 5px;
+ .roof-module-table{
+ min-width: 1200px;
+ }
+ &::-webkit-scrollbar {
+ height: 4px;
+ background-color: transparent;
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: #D9D9D9;
+ }
+ &::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
+}
+
+.circuit-right-wrap{
+ display: flex;
+ justify-content: flex-end;
+}
+
+.circuit-data-form{
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ min-height: 60px;
+ padding: 12px;
+ border: 1px solid rgba(255, 255, 255, 0.20);
+ span{
+ display: inline-flex;
+ align-items: center;
+ .del{
+ display: block;
+ margin-left: 10px;
+ width: 15px;
+ height: 15px;
+ background: url(../../public/static/images/canvas/circuit_del.svg)no-repeat center;
+ background-size: cover;
+ }
+ }
+}
+.circuit-table-tit{
+ color: #fff;
+ font-size: 12px;
+ font-weight: 600;
+ padding: 11px 10px;
+ background-color: #474747;
+ border: 1px solid #505050;
+ border-bottom: none;
+}
+
+.circuit-overflow{
+ max-height: 400px;
+ overflow-y: auto;
+ margin-bottom: 15px;
+ &::-webkit-scrollbar {
+ width: 4px;
+ background-color: transparent;
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: #D9D9D9;
+ }
+ &::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
+}
+
+.circuit-table-flx-wrap{
+ display: flex;
+ gap: 10px;
+ margin-bottom: 10px;
+ .circuit-table-flx-box{
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ .bottom-wrap{
+ margin-top: auto;
+ }
+ .roof-module-table{
+ table{
+ table-layout: fixed;
+ }
+ }
+ }
+}
+
+.circuit-count-input{
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+// 모듈부가기능
+.additional-radio-wrap{
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 15px 0;
+ margin-bottom: 24px;
+}
+.additional-wrap{
+ padding: 24px 0;
+ border-top: 1px solid #424242;
+}
+
+.additional-color-wrap{
+ display: flex;
+ align-items: center;
+ padding: 5px 0;
+ gap: 15px;
+ .additional-color-box{
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ .additional-color{
+ display: block;
+ width: 16px;
+ height: 16px;
+ &.pink{
+ border: 2px solid #EA10AC;
+ background-color: #16417D;
+ }
+ &.white{
+ border: 2px solid #FFF;
+ background-color: #001027;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/styles/_reset.scss b/src/styles/_reset.scss
index 2c5b1e22..d345f643 100644
--- a/src/styles/_reset.scss
+++ b/src/styles/_reset.scss
@@ -133,12 +133,22 @@ button{
// margin
.mt5{margin-top: 5px !important;}
.mt10{margin-top: 10px !important;}
+.mt15{margin-top: 15px !important;}
.mb5{margin-bottom: 5px !important;}
.mb10{margin-bottom: 10px !important;}
+.mb15{margin-bottom: 15px !important;}
.mr5{margin-right: 5px !important;}
.mr10{margin-right: 10px !important;}
+.mr15{margin-right: 15px !important;}
.ml5{margin-left: 5px !important;}
.ml10{margin-left: 10px !important;}
+.ml15{margin-left: 15px !important;}
+
+// align
+.al-l{text-align: left !important;}
+.al-r{text-align: right !important;}
+.al-c{text-align: center !important;}
+
// button
.btn-frame{
@@ -189,6 +199,69 @@ button{
font-weight: 500;
}
}
+ &.sub-tab{
+ height: 30px;
+ padding: 0 10px;
+ line-height: 28px;
+ font-family: 'Noto Sans JP', sans-serif;
+ background-color: #2D2D2D;
+ border: 1px solid #393939;
+ color: #aaa;
+ &.act,
+ &:hover{
+ background-color: #414E6C;
+ border: 1px solid #414E6C;
+ color: #fff;
+ font-weight: 500;
+ }
+ }
+ &.roof{
+ height: 30px;
+ padding: 0 10px;
+ line-height: 28px;
+ font-family: 'Noto Sans JP', sans-serif;
+ background-color: transparent;
+ border: 1px solid #484848;
+ color: #fff;
+ &.blue{
+ background-color: #414E6C;
+ border: 1px solid #414E6C;
+ &:hover{
+ background-color: #414E6C;
+ border: 1px solid #414E6C;
+ }
+ }
+ &.white{
+ background-color: #fff;
+ border: 1px solid #fff;
+ color: #101010;
+ &:hover{
+ background-color: #fff;
+ border: 1px solid #fff;
+ color: #101010;
+ }
+ }
+ &:hover{
+ font-weight: 400;
+ background-color: transparent;
+ border: 1px solid #484848;
+ color: #fff;
+ }
+ }
+ &.self{
+ height: 30px;
+ padding: 0 10px;
+ line-height: 28px;
+ font-family: 'Noto Sans JP', sans-serif;
+ background-color: transparent;
+ border: 1px solid #676767;
+ color: #AAAAAA;
+ &:hover{
+ background-color: #272727;
+ border-color: #676767;
+ font-weight: 400;
+ }
+ }
&:hover,
&.act{
background-color: #1083E3;
@@ -216,7 +289,7 @@ button{
.btn-origin{
display: inline-block;
height: 30px;
- padding: 0 14px;
+ padding: 0 10px;
border-radius: 2px;
background-color: #101010;
color: #fff;
@@ -226,7 +299,7 @@ button{
&.navy{
background-color: #304961;
&:hover{
- background-color: #1083E3;
+ background-color: #233546;
}
}
&.grey{
@@ -235,6 +308,20 @@ button{
background-color: #607F9A;
}
}
+ &.green{
+ background-color: #A6BBA8;
+ &:hover{
+ background-color: #98af9b;
+ }
+ }
+ &.white{
+ border: 1px solid #94A0AD;
+ background-color: #fff;
+ color: #94A0AD;
+ &:hover{
+ background-color: #fff;
+ }
+ }
}
// select
@@ -244,7 +331,7 @@ button{
min-width: 100px;
height: 30px;
line-height: 30px;
- padding: 0 10px;
+ padding: 0 25px 0 10px;
background-color: #373737;
border: 1px solid #3F3F3F;
border-radius: 2px;
@@ -263,11 +350,14 @@ button{
clip-path:inset(0 0 100% 0);
width: calc(100% + 2px);
padding: 8px 0;
+ max-height: 100px;
+ overflow-y: auto;
background-color: #373737;
border: 1px solid #3F3F3F;
border-radius: 2px;
transition: all 0.17s ease-in-out;
visibility: hidden;
+ z-index: 999;
.select-item{
display: flex;
align-items: center;
@@ -283,6 +373,18 @@ button{
background-color: #2C2C2C;
}
}
+ &::-webkit-scrollbar {
+ width: 2px;
+ background-color: transparent;
+
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: #5a5a5a;
+ border-radius: 10px;
+ }
+ &::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
}
&::after{
content: '';
@@ -349,6 +451,7 @@ button{
margin-bottom: 10px;
}
}
+input[type=password],
input[type=number],
input[type=text]{
&.input-origin{
@@ -372,6 +475,14 @@ input[type=text]{
&.block{
width: 100%;
}
+ &:read-only{
+ color: #AAA;
+ }
+ &.plane{
+ font-family: 'Noto Sans JP', sans-serif;
+ border: 1px solid #525252;
+ background-color: transparent;
+ }
}
&.input-light{
display: block;
@@ -586,6 +697,7 @@ input[type=text]{
}
&.pop{
label{
+ font-size: 12px;
&:before{
width: 16px;
height: 16px;
@@ -603,6 +715,7 @@ input[type=text]{
// check-box
.d-check-box{
label{
+ &.red{color: #FFCACA;}
&::before{
cursor: pointer;
content: "";
@@ -614,7 +727,7 @@ input[type=text]{
left: 0;
margin-left: -12px;
border: 1px solid #D6D6D7;
- background-color: transparent;
+ background-color: #fff;
transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
}
&:after{
@@ -645,9 +758,28 @@ input[type=text]{
-ms-transform: translate(7.75px,4.5px) rotate(45deg);
}
&.pop{
+ label{
+ &:before{
+ background-color: transparent;
+ }
+ }
input[type=checkbox]:checked + label::after{
border-color: #fff;
}
+ &.no-text{
+ label{
+ padding-left: 0;
+ }
+ }
+ }
+ &.sel{
+ input[type=checkbox]:checked + label{
+ color: #D7C863;
+ }
+ input[type=checkbox]:checked + label::before,
+ input[type=checkbox]:checked + label::after{
+ border-color: #D7C863;
+ }
}
}
diff --git a/src/styles/_submodal.scss b/src/styles/_submodal.scss
new file mode 100644
index 00000000..5b18f74a
--- /dev/null
+++ b/src/styles/_submodal.scss
@@ -0,0 +1,318 @@
+.modal-popup{
+ position: fixed;
+ top: 0;
+ left: 0;
+ width:100%;
+ height:100%;
+ overflow-x: hidden;
+ overflow-y: auto;
+ background:rgba(0,0,0,.75);
+ z-index:110000;
+ .modal-dialog {
+ position: relative;
+ display: flex;
+ align-items: center;
+ min-height: calc(100% - (1.5rem * 2));
+ width: 680px;
+ z-index:200000;
+ margin: 1.5rem auto;
+ pointer-events: none;
+ &.middle{
+ width: 800px;
+ }
+ &.big{
+ width: 1000px;
+ }
+ .modal-content{
+ flex:1;
+ position: relative;
+ background-clip: padding-box;
+ background-color: transparent;
+ outline: 0 none;
+ pointer-events: auto;
+ border-radius: 4px;
+ .modal-header{
+ display: flex;
+ align-items: center;
+ padding: 10px 24px;
+ background-color: #7992ba;
+ border-radius: 4px 4px 0px 0px;
+ // overflow: hidden;
+ h1.title{
+ font-size: 13px;
+ color: $pop-color;
+ font-weight: 700;
+ }
+ .modal-close{
+ margin-left: auto;
+ color: transparent;
+ font-size: 0;
+ width: 10px;
+ height: 10px;
+ background: url(../../public/static/images/canvas/modal_close.svg)no-repeat center;
+ }
+ }
+ .modal-body{
+ padding: 30px;
+ background-color: #fff;
+ border-radius: 0px 0px 4px 4px;
+ .modal-body-inner{
+ margin-bottom: 20px;
+ &.border{
+ padding-bottom: 20px;
+ border-bottom: 1px solid #ECF0F4;
+ }
+ }
+ .footer-btn-wrap{
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ }
+ }
+ }
+ }
+ &.community{
+ .modal-dialog{
+ .modal-content{
+ .modal-header{
+ padding: 19px 24px;
+ background-color: #fff;
+ .modal-close{
+ background: url(../../public/static/images/sub/community_pop_close.svg)no-repeat center;
+ }
+ }
+ .modal-body{
+ padding: 0 30px 30px;
+ }
+ }
+ }
+ }
+}
+
+.explane{
+ font-size: 13px;
+ font-weight: 400;
+ color: #101010;
+ margin-bottom: 20px;
+}
+.red{
+ color: #F00;
+}
+// modal-contents
+
+// 비밀번호 변경
+.change-password-guide{
+
+ span{
+ display: block;
+ margin-bottom: 5px;
+ font-size: 13px;
+ font-weight: 400;
+ color: #101010;
+ &:last-child{
+ margin-bottom: 0;
+ }
+ }
+}
+
+.change-password-box{
+ padding: 30px;
+ border-radius: 4px;
+ background: #F4F4F7;
+ margin-bottom: 20px;
+ .change-password-tit{
+ .tit-b{
+ font-size: 13px;
+ font-weight: 500;
+ color: #344356;
+ }
+ .tit-s{
+ font-size: 12px;
+ font-weight: 400;
+ color: #898989;
+ }
+ }
+ .change-password-input{
+ width: 100%;
+ .change-password{
+ width: 100%;
+ height: 45px;
+ border-radius: 4px;
+ border: 1px solid #E9E9E9;
+ background-color: #fff;
+ padding: 0 10px;
+ font-size: 12px;
+ color: #364864;
+ font-family: 'Noto Sans JP', sans-serif;
+ &::placeholder{
+ font-size: 12px;
+ }
+ }
+ }
+ .table-item-th{
+ width: 145px;
+ }
+}
+.form-table{
+ display: table;
+ table-layout: auto;
+ width: 100%;
+ .table-item{
+ display: table-row;
+ .table-item-th,
+ .table-item-td{
+ display: table-cell;
+ vertical-align: middle;
+ padding-bottom: 10px;
+ }
+ &:last-child{
+ .table-item-th,
+ .table-item-td{
+ padding-bottom: 0;
+ }
+ }
+ .table-item-td{
+ padding-left: 10px;
+ }
+ }
+}
+
+// 주소찾기 팝업
+.address-input-wrap{
+ position: relative;
+ height: 45px;
+ padding: 0 40px 0 15px;
+ border-radius: 4px;
+ border: 1px solid #E9E9E9;
+ background: #FFF;
+ margin-bottom: 20px;
+ input{
+ width: 100%;
+ height: 100%;
+ font-size: 13px;
+ font-weight: 400;
+ font-family: "Noto Sans JP" sans-serif;
+ color: #868686;
+ &::placeholder{
+ color: #AEAEAE;
+ font-size: 13px;
+ font-weight: 400;
+ }
+ }
+ .search-btn{
+ position: absolute;
+ top: 0;
+ right: 15px;
+ width: 21px;
+ height: 100%;
+ background: url(../../public/static/images/sub/address_search.svg)no-repeat center;
+ background-size: 21px 21px;
+ }
+}
+
+// 설계의뢰 불러오기
+.design-tit-wrap{
+ display: flex;
+ align-items: center;
+ margin-bottom: 10px;
+ h3{
+ font-size: 13px;
+ font-weight: 600;
+ color: #101010;
+ }
+ .design-search-wrap{
+ margin-left: auto;
+ }
+}
+.design-request-table{
+ margin-bottom: 20px;
+}
+.design-request-grid{
+ .design-request-grid-tit{
+ font-size: 13px;
+ font-weight: 600;
+ color: #101010;
+ margin-bottom: 15px;
+ }
+}
+
+// 제품 특이사항 팝업
+.calculation-estimate{
+ &.usemodal{
+ margin-bottom: 0;
+ border: none;
+ padding: 0;
+ }
+}
+
+// 내정보 조회 팝업
+.password-input{
+ flex: 1;
+ display: flex;
+ align-items: center;
+ height: 30px;
+ border: 1px solid #EEE;
+ padding: 0 10px;
+ border-radius: 2px;
+ input{
+ width: 100%;
+ height: 100%;
+ font-size: 13px;
+ color: #45576F;
+ font-family: 'Noto Sans JP', sans-serif;
+ font-weight: 400;
+ &::placeholder{
+ color: #D1D7E0;
+ }
+ }
+ .blink{
+ flex: none;
+ width: 19px;
+ height: 100%;
+ background-image: url(../../public/static/images/main/password_hidden.svg);
+ background-size: 19px 13px;
+ background-repeat: no-repeat;
+ background-position: center;
+ &.on{
+ background-image: url(../../public/static/images/main/password_visible.svg);
+ }
+ }
+}
+
+// 커뮤니티
+.community_detail{
+ .community_detail-tit{
+ font-size: 16px;
+ color: #101010;
+ font-weight: 600;
+ padding-bottom: 14px;
+ border-bottom: 2px solid #101010;
+ }
+ .community_detail-file-wrap{
+ padding: 24px 0;
+ border-bottom: 1px solid #E5E5E5;
+ dt{
+ font-size: 13px;
+ color: #101010;
+ font-weight: 500;
+ margin-bottom: 15px;
+ }
+ dd{
+ font-size: 12px;
+ font-weight: 400;
+ margin-bottom: 3px;
+ color: #344356;
+ &:last-child{
+ margin-bottom: 0;
+ }
+ }
+ }
+ .community_detail-inner{
+ padding-top: 20px;
+ padding-bottom: 20px;
+ font-size: 13px;
+ font-weight: 400;
+ color: #45576F;
+ line-height: 26px;
+ }
+}
\ No newline at end of file
diff --git a/src/styles/_table.scss b/src/styles/_table.scss
index d3c207a7..d7e1c407 100644
--- a/src/styles/_table.scss
+++ b/src/styles/_table.scss
@@ -14,6 +14,10 @@ table{
}
}
+.flx-box{
+ @include flexbox;
+}
+
.common-table{
table{
table-layout: fixed;
@@ -85,6 +89,7 @@ table{
color: #697C8F;
font-weight: normal;
margin-left: 15px;
+ margin-bottom: 0;
}
span{
font-size: 13px;
@@ -97,11 +102,10 @@ table{
}
}
}
- .flx-box{
- @include flexbox;
- }
+
}
.tooltips{
+ position: relative;
display: block;
width: 14px;
height: 14px;
@@ -109,6 +113,43 @@ table{
background: url(../../public/static/images/sub/tooltips.svg)no-repeat center;
background-size: cover;
cursor: pointer;
+ span{
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ left: 25px;
+ padding: 11px 7px;
+ font-size: 12px;
+ font-weight: 400;
+ color: #45576F;
+ background-color: #fff;
+ border: 2px solid #45576F;
+ border-radius: 2px;
+ white-space: nowrap;
+ opacity: 0;
+ visibility: hidden;
+ z-index: 99;
+ transition: all .15s ease-in-out;
+ &::before{
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: -6px;
+ transform: translateY(-50%) rotate(45deg);
+ width: 9px;
+ height: 9px;
+ border: 2px solid #45576F;
+ background-color: #fff;
+ border-top: none;
+ border-right: none;
+ }
+ }
+ &:hover{
+ span{
+ opacity: 1;
+ visibility: visible;
+ }
+ }
}
}
@@ -174,4 +215,219 @@ table{
}
}
}
+}
+
+.roof-module-table{
+ table{
+ border-collapse: collapse;
+ thead{
+ th{
+ height: 40px;
+ padding: 0px 10px;
+ font-size: 12px;
+ line-height: 1.1;
+ color: #fff;
+ font-weight: 500;
+ border: 1px solid #505050;
+ vertical-align: middle;
+ background-color: rgba(255, 255, 255, 0.05);
+ text-align: center;
+ word-break: keep-all;
+ .d-check-box{
+ opacity: 0.5;
+ }
+ }
+ }
+ tbody{
+ tr{
+ cursor: pointer;
+ &.on{
+ background-color: #272727;
+ }
+ }
+ td{
+ height: 40px;
+ vertical-align: middle;
+ font-size: 12px;
+ color: #fff;
+ font-weight: 400;
+ border: 1px solid #505050;
+ padding: 0 10px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ .warning{
+ color: PCSオプションマスター;
+ }
+ .color-wrap{
+ display: flex;
+ align-items: center;
+ .color-box{
+ width: 14px;
+ height: 14px;
+ margin-right: 8px;
+ }
+ .name{
+ width: 0;
+ flex: 1;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+ }
+ }
+ }
+ }
+ &.overflow-y{
+ table{
+ table-layout: fixed;
+ border-collapse: collapse;
+ thead{
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+ border-collapse: collapse;
+ }
+ tbody{
+ display: block;
+ max-height: 81px;
+ overflow-y: auto;
+ tr{
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+ border-collapse: collapse;
+ margin-top: -1px;
+ }
+ &::-webkit-scrollbar {
+ width: 2px;
+ background-color: rgba(255, 255, 255, 0.05);
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: #C1CCD7;
+ }
+ &::-webkit-scrollbar-track {
+ background-color: rgba(255, 255, 255, 0.05);
+ }
+ }
+ }
+ }
+}
+
+// 커뮤니티 테이블
+.community-table{
+ margin-bottom: 24px;
+ table{
+ table-layout: fixed;
+ border-collapse: collapse;
+ border-top: 2px solid #101010;
+ tbody{
+ td{
+ font-size: 13px;
+ font-weight: 400;
+ color: #45576F;
+ padding: 10.5px 10px;
+ border-bottom: 1px solid #ECF0F4;
+ vertical-align: middle;
+ .text-frame{
+ display: flex;
+ align-items: center;
+ .text-overflow{
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 1;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+ .clip{
+ flex: none;
+ display: block;
+ margin-left: 10px;
+ width: 14px;
+ height: 14px;
+ background: url(../../public/static/images/sub/community_clip.svg)no-repeat center;
+ background-size: cover;
+ }
+ }
+ }
+ tr{
+ background-color: transparent;
+ transition: all .15s ease-in-out;
+ cursor: pointer;
+ &:hover{
+ background: #F7F9FA;
+ }
+ }
+ }
+ }
+}
+
+// 풍속 선택 테이블
+.wind-table{
+ margin-top: 20px;
+ table{
+ width: 100%;
+ table-layout: fixed;
+ border-collapse: separate;
+ thead{
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+ tr{
+ th{
+ text-align: center;
+ font-size: 13px;
+ font-weight: 600;
+ color: #344356;
+ background-color: #F7F9FA;
+ padding: 10.5px 10px;
+ vertical-align: middle;
+ border-bottom: 1px solid #ECF0F4;
+ border-top: 1px solid #ECF0F4;
+ &:first-child{
+ border-left: 1px solid #ECF0F4;
+ border-radius: 4px 0 0 4px;
+ }
+ &:last-child{
+ border-right: 1px solid #ECF0F4;
+ border-radius: 0 4px 4px 0;
+ }
+ }
+ }
+ }
+ tbody{
+ display: block;
+ max-height: 200px;
+ overflow-y: auto;
+ tr{
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+ td{
+ padding: 13.5px 10px;
+ font-size: 13px;
+ font-weight: 400;
+ color: #45576F;
+ vertical-align: middle;
+ border-bottom: 1px solid #ECF0F4;
+ &:nth-child(2){
+ width: 110px;
+ }
+ &:nth-child(1){
+ width: 50px;
+ }
+ }
+ }
+ &::-webkit-scrollbar {
+ width: 4px;
+ background-color: transparent;
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: #ECF0F4;
+ }
+ &::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/styles/contents.scss b/src/styles/contents.scss
index 3e82124a..2f690380 100644
--- a/src/styles/contents.scss
+++ b/src/styles/contents.scss
@@ -1,3 +1,6 @@
@import '_contents.scss';
@import '_modal.scss';
-@import '_table.scss';
\ No newline at end of file
+@import '_submodal.scss';
+@import '_table.scss';
+@import '_canvasside.scss';
+@import '_pagination.scss';
\ No newline at end of file
diff --git a/src/styles/grid.scss b/src/styles/grid.scss
index 71f42715..4b20cd8c 100644
--- a/src/styles/grid.scss
+++ b/src/styles/grid.scss
@@ -1,2 +1 @@
@import '_grid-detail.scss';
-@import '_pagination.scss';
\ No newline at end of file
diff --git a/src/util/board-utils.js b/src/util/board-utils.js
new file mode 100644
index 00000000..a129c4d4
--- /dev/null
+++ b/src/util/board-utils.js
@@ -0,0 +1,50 @@
+import { useAxios } from '@/hooks/useAxios'
+
+// 파일 다운로드
+export const handleFileDown = async (file) => {
+ const { promiseGet } = useAxios()
+
+ const url = `/api/board/file/download`
+ const params = new URLSearchParams({
+ encodeFileNo: file.encodeFileNo,
+ })
+ const apiUrl = `${url}?${params.toString()}`
+ await promiseGet({ url: apiUrl, responseType: 'blob' })
+ .then((resultData) => {
+ if (resultData) {
+ const blob = new Blob([resultData.data], { type: resultData.headers['content-type'] || 'application/octet-stream' })
+ const fileUrl = window.URL.createObjectURL(blob)
+ const link = document.createElement('a')
+
+ link.href = fileUrl
+ link.download = file.srcFileNm
+ document.body.appendChild(link)
+ link.click()
+ link.remove()
+ window.URL.revokeObjectURL(fileUrl)
+ }
+ })
+ .catch((error) => {
+ alert(error.response.data.message)
+ })
+}
+
+// 페이지 번호 생성
+export const generateBlockPagination = (currentPage, totalPages, pageBlock) => {
+ const currentBlock = Math.ceil(currentPage / pageBlock)
+
+ let startPage = (currentBlock - 1) * pageBlock + 1
+ let endPage = startPage + pageBlock - 1
+
+ if (endPage > totalPages) {
+ endPage = totalPages
+ }
+
+ let pageArr = []
+
+ for (let i = startPage; i <= endPage; i++) {
+ pageArr.push(i)
+ }
+
+ return pageArr
+}
diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js
index 4efd9138..17c30f07 100644
--- a/src/util/canvas-util.js
+++ b/src/util/canvas-util.js
@@ -1,5 +1,5 @@
import { intersect } from 'mathjs'
-
+import * as turf from '@turf/turf'
/**
* Collection of function to use on canvas
*/
@@ -725,3 +725,169 @@ export const getIntersectionPoint = (p1, p2, y) => {
const x = (y - intercept) / slope
return { x, y }
}
+
+export const pointsToTurfPolygon = (points) => {
+ const coordinates = points.map((point) => [point.x, point.y])
+ coordinates.push(coordinates[0])
+ return turf.polygon([coordinates])
+}
+
+export const polygonToTurfPolygon = (polygon) => {
+ const coordinates = polygon.points.map((point) => [point.x, point.y])
+ coordinates.push(coordinates[0])
+ return turf.polygon(
+ [coordinates],
+ {},
+ {
+ parentId: polygon.parentId,
+ },
+ )
+}
+
+export const splitDormerTriangle = (triangle, direction) => {
+ const halfWidth = triangle.width / 2
+
+ let leftPoints = []
+ let rightPoints = []
+ let leftPointOffset = []
+ let rightPointOffset = []
+
+ if (direction === 'down') {
+ leftPoints = [
+ { x: triangle.left, y: triangle.top },
+ { x: triangle.left - halfWidth, y: triangle.top + triangle.height },
+ { x: triangle.left, y: triangle.top + triangle.height },
+ ]
+
+ rightPoints = [
+ { x: triangle.left, y: triangle.top },
+ { x: triangle.left, y: triangle.top + triangle.height },
+ { x: triangle.left + halfWidth, y: triangle.top + triangle.height },
+ ]
+ } else if (direction === 'up') {
+ leftPoints = [
+ { x: triangle.left, y: triangle.top },
+ { x: triangle.left - halfWidth, y: triangle.top - triangle.height },
+ { x: triangle.left, y: triangle.top - triangle.height },
+ ]
+
+ rightPoints = [
+ { x: triangle.left, y: triangle.top },
+ { x: triangle.left, y: triangle.top - triangle.height },
+ { x: triangle.left + halfWidth, y: triangle.top - triangle.height },
+ ]
+ } else if (direction === 'left') {
+ leftPoints = [
+ { x: triangle.left, y: triangle.top },
+ { x: triangle.left - triangle.height, y: triangle.top - halfWidth },
+ { x: triangle.left - triangle.height, y: triangle.top },
+ ]
+
+ rightPoints = [
+ { x: triangle.left, y: triangle.top },
+ { x: triangle.left - triangle.height, y: triangle.top },
+ { x: triangle.left - triangle.height, y: triangle.top + halfWidth },
+ ]
+ } else if (direction === 'right') {
+ leftPoints = [
+ { x: triangle.left, y: triangle.top },
+ { x: triangle.left + triangle.height, y: triangle.top },
+ { x: triangle.left + triangle.height, y: triangle.top + triangle.height },
+ ]
+
+ rightPoints = [
+ { x: triangle.left, y: triangle.top },
+ { x: triangle.left + triangle.height, y: triangle.top },
+ { x: triangle.left + triangle.height, y: triangle.top - triangle.height },
+ ]
+ }
+
+ return [leftPoints, rightPoints]
+}
+
+export const triangleToPolygon = (triangle) => {
+ const points = []
+ const halfWidth = triangle.width / 2
+ const height = triangle.height
+
+ points.push({ x: triangle.left + halfWidth, y: triangle.top })
+ points.push({ x: triangle.left, y: triangle.top + height })
+ points.push({ x: triangle.left + triangle.width, y: triangle.top + height })
+
+ return points
+}
+
+export const rectToPolygon = (rect) => {
+ const points = []
+ const left = rect.left
+ const top = rect.top
+ const width = rect.width * rect.scaleX // 스케일 적용
+ const height = rect.height * rect.scaleY // 스케일 적용
+
+ // 네 개의 꼭짓점 좌표 계산
+ points.push({ x: left, y: top }) // 좌상단
+ points.push({ x: left + width, y: top }) // 우상단
+ points.push({ x: left + width, y: top + height }) // 우하단
+ points.push({ x: left, y: top + height }) // 좌하단
+
+ return points
+}
+
+//면형상 선택 클릭시 지붕 패턴 입히기
+export function setSurfaceShapePattern(polygon) {
+ const ratio = window.devicePixelRatio || 1
+
+ let width = 265 / 10
+ let height = 150 / 10
+ let roofStyle = 2
+ const inputPatternSize = { width: width, height: height } //임시 사이즈
+ const patternSize = { ...inputPatternSize } // 입력된 값을 뒤집기 위해
+
+ if (polygon.direction === 'east' || polygon.direction === 'west') {
+ //세로형이면 width height를 바꿈
+ ;[patternSize.width, patternSize.height] = [inputPatternSize.height, patternSize.width]
+ }
+
+ // 패턴 소스를 위한 임시 캔버스 생성
+ const patternSourceCanvas = document.createElement('canvas')
+ patternSourceCanvas.width = polygon.width * ratio
+ patternSourceCanvas.height = polygon.height * ratio
+ const ctx = patternSourceCanvas.getContext('2d')
+ const offset = roofStyle === 1 ? 0 : patternSize.width / 2
+
+ const rows = Math.floor(patternSourceCanvas.height / patternSize.height)
+ const cols = Math.floor(patternSourceCanvas.width / patternSize.width)
+
+ ctx.strokeStyle = 'green'
+ ctx.lineWidth = 0.4
+
+ for (let row = 0; row <= rows; row++) {
+ const y = row * patternSize.height
+
+ ctx.beginPath()
+ ctx.moveTo(0, y) // 선 시작점
+ ctx.lineTo(patternSourceCanvas.width, y) // 선 끝점
+ ctx.stroke()
+
+ for (let col = 0; col <= cols; col++) {
+ const x = col * patternSize.width + (row % 2 === 0 ? 0 : offset)
+ const yStart = row * patternSize.height
+ const yEnd = yStart + patternSize.height
+
+ ctx.beginPath()
+ ctx.moveTo(x, yStart) // 선 시작점
+ ctx.lineTo(x, yEnd) // 선 끝점
+ ctx.stroke()
+ }
+ }
+
+ // 패턴 생성
+ const pattern = new fabric.Pattern({
+ source: patternSourceCanvas,
+ repeat: 'repeat',
+ })
+
+ polygon.set('fill', null)
+ polygon.set('fill', pattern)
+ polygon.canvas?.renderAll()
+}
diff --git a/src/util/common-utils.js b/src/util/common-utils.js
index 1b7cd166..9244b046 100644
--- a/src/util/common-utils.js
+++ b/src/util/common-utils.js
@@ -59,3 +59,10 @@ export const queryStringFormatter = (params = {}) => {
})
return queries.join('&')
}
+
+// 43000 --> 43,000
+export const convertNumberToPriceDecimal = (value) => {
+ if (value) return Number(value).toLocaleString()
+ else if (value === 0) return 0
+ else return ''
+}
diff --git a/src/util/input-utils.js b/src/util/input-utils.js
index f674e6cd..18f93425 100644
--- a/src/util/input-utils.js
+++ b/src/util/input-utils.js
@@ -1,6 +1,6 @@
// 숫자만 입력 가능한 input onChange 함수
export const onlyNumberInputChange = (e, callback) => {
- let value = e.target.value.replace(/^0+/, '')
+ let value = e.target.value
value = value.replace(/[^-0-9]/g, '')
callback(value, e)
}
diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js
index 641016d5..07b46bc4 100644
--- a/src/util/qpolygon-utils.js
+++ b/src/util/qpolygon-utils.js
@@ -1,15 +1,9 @@
import { fabric } from 'fabric'
import { QLine } from '@/components/fabric/QLine'
-import {
- calculateIntersection,
- distanceBetweenPoints,
- findClosestPoint,
- getAdjacent,
- getDirectionByPoint,
- getRoofHypotenuse,
-} from '@/util/canvas-util'
+import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDirectionByPoint } from '@/util/canvas-util'
import { QPolygon } from '@/components/fabric/QPolygon'
import * as turf from '@turf/turf'
+import { POLYGON_TYPE } from '@/common/common'
const TWO_PI = Math.PI * 2
@@ -1156,9 +1150,12 @@ export const splitPolygonWithLines = (polygon) => {
stroke: 'black',
fill: 'transparent',
strokeWidth: 3,
- name: 'roof',
- selectable: false,
+ name: POLYGON_TYPE.ROOF,
+ originX: 'center',
+ originY: 'center',
+ selectable: true,
defense: defense,
+ direction: defense,
})
polygon.canvas.add(roof)
@@ -1194,314 +1191,295 @@ function calculateAngleBetweenLines(line1, line2) {
// Calculate the angle in radians and then convert to degrees
const angleInRadians = Math.acos(cosTheta)
- const angleInDegrees = (angleInRadians * 180) / Math.PI
- return angleInDegrees
+ return (angleInRadians * 180) / Math.PI
}
export const drawHippedRoof = (polygon, chon) => {
- drawRoofRidge(polygon, chon)
+ const hasNonParallelLines = polygon.lines.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2)
+ if (hasNonParallelLines.length > 0) {
+ alert('대각선이 존재합니다.')
+ return
+ }
+
+ drawRidgeRoof(polygon, chon)
drawHips(polygon)
connectLinePoint(polygon)
}
-const drawRoofRidge = (polygon, chon) => {
- const points = []
- polygon.wall.points.forEach((point) => points.push({ x: point.x, y: point.y }))
- console.log('points : ', points)
-
+/**
+ *
+ * @param polygon
+ * @param chon
+ */
+const drawRidgeRoof = (polygon, chon) => {
const walls = polygon.wall.lines // 외벽의 라인
const roofs = polygon.lines // 지붕의 라인
- let ridgeWall = []
- walls.forEach((wall, index) => {
- let currentRoof, prevWall, currentWall, nextWall
+ let ridgeRoof = []
- if (index === 0) {
- prevWall = walls[walls.length - 1]
- } else {
- prevWall = walls[index - 1]
- }
- currentRoof = roofs[index]
- currentWall = walls[index]
+ roofs.forEach((currentRoof, index) => {
+ let prevRoof,
+ nextRoof,
+ currentWall = walls[index]
- if (index === walls.length - 1) {
- nextWall = walls[0]
- } else if (index === walls.length) {
- nextWall = walls[1]
- } else {
- nextWall = walls[index + 1]
- }
+ prevRoof = index === 0 ? walls[walls.length - 1] : walls[index - 1]
+ nextRoof = index === walls.length - 1 ? walls[0] : index === walls.length ? walls[1] : walls[index + 1]
- if (prevWall.direction !== nextWall.direction && currentWall.length < currentRoof.length) {
- ridgeWall.push({ index: index, wall: currentWall, length: currentWall.length })
+ if (prevRoof.direction !== nextRoof.direction && currentWall.length <= currentRoof.length) {
+ ridgeRoof.push({ index: index, roof: currentRoof, length: currentRoof.length })
}
})
// 지붕의 길이가 짧은 순으로 정렬
- ridgeWall.sort((a, b) => a.length - b.length)
+ ridgeRoof.sort((a, b) => a.length - b.length)
- ridgeWall.forEach((item) => {
- if (getMaxRidge(walls.length) > polygon.ridges.length) {
+ ridgeRoof.forEach((item) => {
+ if (getMaxRidge(roofs.length) > polygon.ridges.length) {
let index = item.index,
- // currentRoof = roofs[index],
- beforePrevWall,
- prevWall,
- currentWall = item.wall,
- nextWall,
- afterNextWall
+ beforePrevRoof,
+ prevRoof,
+ currentRoof = item.roof,
+ nextRoof,
+ afterNextRoof
let startXPoint, startYPoint, endXPoint, endYPoint
- if (index === 0) {
- prevWall = walls[walls.length - 1]
- } else {
- prevWall = walls[index - 1]
- }
+ prevRoof = index === 0 ? roofs[walls.length - 1] : roofs[index - 1]
+ nextRoof = index === roofs.length - 1 ? roofs[0] : index === roofs.length ? roofs[1] : roofs[index + 1]
- if (index === 0) {
- beforePrevWall = walls[roofs.length - 2]
- } else if (index === 1) {
- beforePrevWall = walls[roofs.length - 1]
- } else {
- beforePrevWall = walls[index - 2]
- }
+ beforePrevRoof = index <= 1 ? roofs[roofs.length - 2 + index] : roofs[index - 2]
+ afterNextRoof = index >= roofs.length - 2 ? roofs[(index + 2) % roofs.length] : roofs[index + 2]
- if (index === walls.length - 1) {
- nextWall = walls[0]
- } else if (index === walls.length) {
- nextWall = walls[1]
- } else {
- nextWall = walls[index + 1]
- }
+ const anotherRoof = roofs.filter((roof) => roof !== currentRoof && roof !== prevRoof && roof !== nextRoof)
- if (index === walls.length - 2) {
- afterNextWall = walls[0]
- } else if (index === walls.length - 1) {
- afterNextWall = walls[1]
- } else if (index === walls.length) {
- afterNextWall = walls[2]
- } else {
- afterNextWall = walls[index + 2]
- }
+ let xEqualInnerLines = anotherRoof.filter((roof) => roof.x1 === roof.x2 && isInnerLine(prevRoof, currentRoof, nextRoof, roof)), //x가 같은 내부선
+ yEqualInnerLines = anotherRoof.filter((roof) => roof.y1 === roof.y2 && isInnerLine(prevRoof, currentRoof, nextRoof, roof)) //y가 같은 내부선
- const anotherRoof = walls.filter((wall) => wall !== currentWall && wall !== prevWall && wall !== nextWall)
- let acrossRoof, xEqualInnerLines, yEqualInnerLines
- xEqualInnerLines = anotherRoof.filter((roof) => roof.x1 === roof.x2 && isInnerLine(prevWall, currentWall, nextWall, roof)) //x가 같은 내부선
- yEqualInnerLines = anotherRoof.filter((roof) => roof.y1 === roof.y2 && isInnerLine(prevWall, currentWall, nextWall, roof)) //y가 같은 내부선
+ let ridgeBaseLength = currentRoof.length / 2, // 지붕의 기반 길이
+ ridgeMaxLength = Math.min(prevRoof.length, nextRoof.length), // 지붕의 최대 길이. 이전, 다음 벽 중 짧은 길이
+ ridgeAcrossLength = Math.max(prevRoof.length, nextRoof.length) - currentRoof.length // 맞은편 벽까지의 길이 - 지붕의 기반 길이
- let ridgeBaseLength = currentWall.length / 2, // 지붕의 기반 길이
- ridgeMaxLength = Math.min(prevWall.length, nextWall.length), // 지붕의 최대 길이. 이전, 다음 벽 중 짧은 길이
- ridgeAcrossLength = Math.max(prevWall.length, nextWall.length) - currentWall.length // 맞은편 벽까지의 길이 - 지붕의 기반 길이
-
- acrossRoof = anotherRoof
+ let acrossRoof = anotherRoof
.filter((roof) => {
- if (currentWall.direction === 'top' && roof.direction === 'bottom') {
- if (nextWall.direction === 'right' && roof.x1 > currentWall.x1) {
- return roof
- }
- if (nextWall.direction === 'left' && roof.x1 < currentWall.x1) {
+ if (roof.x1 === roof.x2) {
+ if ((nextRoof.direction === 'right' && roof.x1 > currentRoof.x1) || (nextRoof.direction === 'left' && roof.x1 < currentRoof.x1)) {
return roof
}
}
- if (currentWall.direction === 'right' && roof.direction === 'left') {
- if (nextWall.direction === 'top' && roof.y1 < currentWall.y1) {
- return roof
- }
- if (nextWall.direction === 'bottom' && roof.y1 > currentWall.y1) {
+ if (roof.y1 === roof.y2) {
+ if ((nextRoof.direction === 'top' && roof.y1 < currentRoof.y1) || (nextRoof.direction === 'bottom' && roof.y1 > currentRoof.y1)) {
return roof
}
}
})
.reduce((prev, current) => {
- let hasBetweenWall = false
- if (current.direction === 'top' || current.direction === 'bottom') {
- hasBetweenWall = walls
- .filter((wall) => wall !== current && wall !== currentWall)
+ let hasBetweenRoof = false
+ if (current.x1 === current.x2) {
+ hasBetweenRoof = roofs
+ .filter((roof) => roof !== current && roof !== currentRoof)
.some((line) => {
- let currentY2 = currentWall.y2
+ let currentY2 = currentRoof.y2
if (yEqualInnerLines.length > 0) {
yEqualInnerLines.forEach((line) => {
- currentY2 = Math.abs(currentWall.y1 - currentY2) < Math.abs(currentWall.y1 - line.y1) ? currentY2 : line.y1
+ currentY2 = Math.abs(currentRoof.y1 - currentY2) < Math.abs(currentRoof.y1 - line.y1) ? currentY2 : line.y1
})
}
- const isY1Between = (line.y1 > currentWall.y1 && line.y1 < currentY2) || (line.y1 > currentY2 && line.y1 < currentWall.y1)
- const isY2Between = (line.y2 > currentWall.y1 && line.y2 < currentY2) || (line.y2 > currentY2 && line.y2 < currentWall.y1)
- const isX1Between = (line.x1 > currentWall.x1 && line.x1 < current.x1) || (line.x1 > currentWall.x1 && line.x1 < current.x1)
- const isX2Between = (line.x2 > currentWall.x1 && line.x2 < current.x1) || (line.x2 > currentWall.x1 && line.x2 < current.x1)
+ const isY1Between = (line.y1 > currentRoof.y1 && line.y1 < currentY2) || (line.y1 > currentY2 && line.y1 < currentRoof.y1)
+ const isY2Between = (line.y2 > currentRoof.y1 && line.y2 < currentY2) || (line.y2 > currentY2 && line.y2 < currentRoof.y1)
+ const isX1Between = (line.x1 > currentRoof.x1 && line.x1 < current.x1) || (line.x1 > currentRoof.x1 && line.x1 < current.x1)
+ const isX2Between = (line.x2 > currentRoof.x1 && line.x2 < current.x1) || (line.x2 > currentRoof.x1 && line.x2 < current.x1)
return isY1Between && isY2Between && isX1Between && isX2Between
})
}
- if (current.direction === 'right' || current.direction === 'left') {
- hasBetweenWall = walls
- .filter((wall) => wall !== current && wall !== currentWall)
+ if (current.y1 === current.y2) {
+ hasBetweenRoof = walls
+ .filter((roof) => roof !== current && roof !== currentRoof)
.some((line) => {
- let currentX2 = currentWall.x2
+ let currentX2 = currentRoof.x2
if (xEqualInnerLines.length > 0) {
xEqualInnerLines.forEach((line) => {
- currentX2 = Math.abs(currentWall.x1 - currentX2) < Math.abs(currentWall.x1 - line.x1) ? currentX2 : line.x1
+ currentX2 = Math.abs(currentRoof.x1 - currentX2) < Math.abs(currentRoof.x1 - line.x1) ? currentX2 : line.x1
})
}
- const isX1Between = (line.x1 > currentWall.x1 && line.x1 < currentX2) || (line.x1 > currentX2 && line.x1 < currentWall.x1)
- const isX2Between = (line.x2 > currentWall.x1 && line.x2 < currentX2) || (line.x2 > currentX2 && line.x2 < currentWall.x1)
- const isY1Between = (line.y1 > currentWall.y1 && line.y1 < current.y1) || (line.y1 > currentWall.y1 && line.y1 < current.y1)
- const isY2Between = (line.y2 > currentWall.y1 && line.y2 < current.y1) || (line.y2 > currentWall.y1 && line.y2 < current.y1)
+ const isX1Between = (line.x1 > currentRoof.x1 && line.x1 < currentX2) || (line.x1 > currentX2 && line.x1 < currentRoof.x1)
+ const isX2Between = (line.x2 > currentRoof.x1 && line.x2 < currentX2) || (line.x2 > currentX2 && line.x2 < currentRoof.x1)
+ const isY1Between = (line.y1 > currentRoof.y1 && line.y1 < current.y1) || (line.y1 > currentRoof.y1 && line.y1 < current.y1)
+ const isY2Between = (line.y2 > currentRoof.y1 && line.y2 < current.y1) || (line.y2 > currentRoof.y1 && line.y2 < current.y1)
return isX1Between && isX2Between && isY1Between && isY2Between
})
}
+
if (prev !== undefined) {
- if (currentWall.direction === 'top' || currentWall.direction === 'bottom') {
- return Math.abs(currentWall.y1 - prev.y1) > Math.abs(currentWall.y1 - current.y1) ? prev : current
+ if (currentRoof.x1 === currentRoof.x2) {
+ return Math.abs(currentRoof.y1 - prev.y1) > Math.abs(currentRoof.y1 - current.y1) ? prev : current
}
- if (currentWall.direction === 'right' || currentWall.direction === 'left') {
- return Math.abs(currentWall.x1 - prev.x1) > Math.abs(currentWall.x1 - current.x1) ? prev : current
+ if (currentRoof.y1 === currentRoof.y2) {
+ return Math.abs(currentRoof.x1 - prev.x1) > Math.abs(currentRoof.x1 - current.x1) ? prev : current
}
} else {
- if (!hasBetweenWall) {
- return current
+ if (!hasBetweenRoof) {
+ if (currentRoof.x1 === currentRoof.x2) {
+ return Math.sign(currentRoof.y1 - currentRoof.y2) !== Math.sign(current.y1 - current.y2) ? current : undefined
+ }
+ if (currentRoof.y1 === currentRoof.y2) {
+ return Math.sign(currentRoof.x1 - currentRoof.x2) !== Math.sign(current.x1 - current.x2) ? current : undefined
+ }
+ return undefined
} else {
return undefined
}
}
}, undefined)
+
if (acrossRoof !== undefined) {
- if (currentWall.direction === 'top' || currentWall.direction === 'bottom') {
- if (ridgeAcrossLength < Math.abs(currentWall.x1 - acrossRoof.x1)) {
- ridgeAcrossLength = Math.abs(currentWall.x1 - acrossRoof.x1) - currentWall.length
+ if (currentRoof.x1 === currentRoof.x2) {
+ if (ridgeAcrossLength < Math.abs(currentRoof.x1 - acrossRoof.x1)) {
+ ridgeAcrossLength = Math.abs(currentRoof.x1 - acrossRoof.x1) - currentRoof.length
}
}
- if (currentWall.direction === 'right' || currentWall.direction === 'left') {
- if (ridgeAcrossLength < Math.abs(currentWall.y1 - acrossRoof.y1)) {
- ridgeAcrossLength = Math.abs(currentWall.y1 - acrossRoof.y1) - currentWall.length
+ if (currentRoof.y1 === currentRoof.y2) {
+ if (ridgeAcrossLength < Math.abs(currentRoof.y1 - acrossRoof.y1)) {
+ ridgeAcrossLength = Math.abs(currentRoof.y1 - acrossRoof.y1) - currentRoof.length
}
}
}
if (ridgeBaseLength > 0 && ridgeMaxLength > 0 && ridgeAcrossLength > 0) {
let ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength)
- if (currentWall.direction === 'top' || currentWall.direction === 'bottom') {
- startXPoint = currentWall.x1 + (nextWall.direction === 'right' ? 1 : -1) * ridgeBaseLength
- startYPoint = currentWall.y1 + (currentWall.direction === 'top' ? -1 : 1) * ridgeBaseLength
- endXPoint = startXPoint + (nextWall.direction === 'right' ? 1 : -1) * ridgeLength
+ if (currentRoof.x1 === currentRoof.x2) {
+ startXPoint = currentRoof.x1 + (nextRoof.direction === 'right' ? 1 : -1) * ridgeBaseLength
+ startYPoint = currentRoof.y1 + (currentRoof.direction === 'top' ? -1 : 1) * ridgeBaseLength
+ endXPoint = startXPoint + (nextRoof.direction === 'right' ? 1 : -1) * ridgeLength
endYPoint = startYPoint
let adjustY
- if (currentWall.direction === 'top') {
- if (afterNextWall.direction === 'bottom' && beforePrevWall.direction === 'bottom') {
+ if (currentRoof.direction === 'top') {
+ if (afterNextRoof.direction === 'bottom' && beforePrevRoof.direction === 'bottom') {
adjustY =
- Math.abs(currentWall.x1 - afterNextWall.x1) < Math.abs(currentWall.x1 - beforePrevWall.x1) ? afterNextWall.y2 : beforePrevWall.y1
- } else if (afterNextWall.direction === 'bottom' && afterNextWall.y2 > currentWall.y2 && afterNextWall.y2 < currentWall.y1) {
- adjustY = afterNextWall.y2
- } else if (beforePrevWall.direction === 'bottom' && beforePrevWall.y1 > currentWall.y2 && beforePrevWall.y1 < currentWall.y1) {
- adjustY = beforePrevWall.y1
+ Math.abs(currentRoof.x1 - afterNextRoof.x1) < Math.abs(currentRoof.x1 - beforePrevRoof.x1) ? afterNextRoof.y2 : beforePrevRoof.y1
+ } else if (afterNextRoof.direction === 'bottom' && afterNextRoof.y2 > currentRoof.y2 && afterNextRoof.y2 < currentRoof.y1) {
+ adjustY = afterNextRoof.y2
+ } else if (beforePrevRoof.direction === 'bottom' && beforePrevRoof.y1 > currentRoof.y2 && beforePrevRoof.y1 < currentRoof.y1) {
+ adjustY = beforePrevRoof.y1
}
if (adjustY) {
- startYPoint = currentWall.y1 - Math.abs(currentWall.y1 - adjustY) / 2
+ startYPoint = currentRoof.y1 - Math.abs(currentRoof.y1 - adjustY) / 2
endYPoint = startYPoint
}
}
- if (currentWall.direction === 'bottom') {
- if (afterNextWall.direction === 'top' && beforePrevWall.direction === 'top') {
+ if (currentRoof.direction === 'bottom') {
+ if (afterNextRoof.direction === 'top' && beforePrevRoof.direction === 'top') {
adjustY =
- Math.abs(currentWall.x1 - afterNextWall.x1) < Math.abs(currentWall.x1 - beforePrevWall.x1) ? afterNextWall.y2 : beforePrevWall.y1
- } else if (afterNextWall.direction === 'top' && afterNextWall.y2 < currentWall.y2 && afterNextWall.y2 > currentWall.y1) {
- adjustY = afterNextWall.y2
- } else if (beforePrevWall.direction === 'top' && beforePrevWall.y1 < currentWall.y2 && beforePrevWall.y1 > currentWall.y1) {
- adjustY = beforePrevWall.y1
+ Math.abs(currentRoof.x1 - afterNextRoof.x1) < Math.abs(currentRoof.x1 - beforePrevRoof.x1) ? afterNextRoof.y2 : beforePrevRoof.y1
+ } else if (afterNextRoof.direction === 'top' && afterNextRoof.y2 < currentRoof.y2 && afterNextRoof.y2 > currentRoof.y1) {
+ adjustY = afterNextRoof.y2
+ } else if (beforePrevRoof.direction === 'top' && beforePrevRoof.y1 < currentRoof.y2 && beforePrevRoof.y1 > currentRoof.y1) {
+ adjustY = beforePrevRoof.y1
}
if (adjustY) {
- startYPoint = currentWall.y1 + Math.abs(currentWall.y1 - adjustY) / 2
+ startYPoint = currentRoof.y1 + Math.abs(currentRoof.y1 - adjustY) / 2
endYPoint = startYPoint
}
}
if (yEqualInnerLines.length > 0) {
yEqualInnerLines.reduce((prev, current) => {
if (prev !== undefined) {
- return Math.abs(currentWall.y1 - prev.y1) < Math.abs(currentWall.y1 - current.y1) ? prev : current
+ return Math.abs(currentRoof.y1 - prev.y1) < Math.abs(currentRoof.y1 - current.y1) ? prev : current
} else {
return current
}
}, undefined)
startYPoint =
- Math.abs(currentWall.y1 - startYPoint) * 2 <= Math.abs(currentWall.y1 - yEqualInnerLines[0].y1)
+ Math.abs(currentRoof.y1 - startYPoint) * 2 <= Math.abs(currentRoof.y1 - yEqualInnerLines[0].y1)
? startYPoint
- : Math.abs(currentWall.y1 - yEqualInnerLines[0].y1)
+ : Math.abs(currentRoof.y1 - yEqualInnerLines[0].y1)
endYPoint = startYPoint
- ridgeAcrossLength = Math.max(prevWall.length, nextWall.length) - Math.abs(currentWall.y1 - startYPoint) * 2
+ ridgeAcrossLength = Math.max(prevRoof.length, nextRoof.length) - Math.abs(currentRoof.y1 - startYPoint) * 2
if (
//yEqualInnerLines 이 다음 벽보다 안쪽에 있을때
- Math.abs(currentWall.y1 - yEqualInnerLines[0].y1) <= Math.abs(currentWall.y1 - nextWall.y1) &&
- Math.abs(currentWall.x1 - yEqualInnerLines[0].x2) >= Math.abs(currentWall.x1 - nextWall.x2)
+ Math.abs(currentRoof.y1 - yEqualInnerLines[0].y1) <= Math.abs(currentRoof.y1 - nextRoof.y1) &&
+ Math.abs(currentRoof.x1 - yEqualInnerLines[0].x2) >= Math.abs(currentRoof.x1 - nextRoof.x2)
) {
- ridgeMaxLength = Math.abs(currentWall.x1 - yEqualInnerLines[0].x2)
+ ridgeMaxLength = Math.abs(currentRoof.x1 - yEqualInnerLines[0].x2)
}
ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength)
- startXPoint = currentWall.x1 + (nextWall.direction === 'right' ? 1 : -1) * Math.abs(currentWall.y1 - startYPoint)
- endXPoint = startXPoint + (nextWall.direction === 'right' ? 1 : -1) * ridgeLength
+ startXPoint = currentRoof.x1 + (nextRoof.direction === 'right' ? 1 : -1) * Math.abs(currentRoof.y1 - startYPoint)
+ endXPoint = startXPoint + (nextRoof.direction === 'right' ? 1 : -1) * ridgeLength
}
}
- if (currentWall.direction === 'left' || currentWall.direction === 'right') {
- startXPoint = currentWall.x1 + (currentWall.direction === 'left' ? -1 : 1) * ridgeBaseLength
- startYPoint = currentWall.y1 + (nextWall.direction === 'bottom' ? 1 : -1) * ridgeBaseLength
+ if (currentRoof.y1 === currentRoof.y2) {
+ startXPoint = currentRoof.x1 + (currentRoof.direction === 'left' ? -1 : 1) * ridgeBaseLength
+ startYPoint = currentRoof.y1 + (nextRoof.direction === 'bottom' ? 1 : -1) * ridgeBaseLength
endXPoint = startXPoint
- endYPoint = startYPoint + (nextWall.direction === 'bottom' ? 1 : -1) * ridgeLength
+ endYPoint = startYPoint + (nextRoof.direction === 'bottom' ? 1 : -1) * ridgeLength
+
let adjustX
- if (currentWall.direction === 'right') {
- if (afterNextWall.direction === 'left' && beforePrevWall.direction === 'left') {
+ if (currentRoof.direction === 'right') {
+ if (afterNextRoof.direction === 'left' && beforePrevRoof.direction === 'left') {
adjustX =
- Math.abs(currentWall.y1 - afterNextWall.y1) < Math.abs(currentWall.y1 - beforePrevWall.y1) ? afterNextWall.x2 : beforePrevWall.x1
- } else if (afterNextWall.direction === 'left' && afterNextWall.x2 < currentWall.x2 && afterNextWall.x2 > currentWall.x1) {
- adjustX = afterNextWall.x2
- } else if (beforePrevWall.direction === 'left' && beforePrevWall.x1 < currentWall.x2 && beforePrevWall.x1 > currentWall.x1) {
- adjustX = beforePrevWall.x1
+ Math.abs(currentRoof.y1 - afterNextRoof.y1) < Math.abs(currentRoof.y1 - beforePrevRoof.y1) ? afterNextRoof.x2 : beforePrevRoof.x1
+ } else if (afterNextRoof.direction === 'left' && afterNextRoof.x2 < currentRoof.x2 && afterNextRoof.x2 > currentRoof.x1) {
+ adjustX = afterNextRoof.x2
+ } else if (beforePrevRoof.direction === 'left' && beforePrevRoof.x1 < currentRoof.x2 && beforePrevRoof.x1 > currentRoof.x1) {
+ adjustX = beforePrevRoof.x1
}
if (adjustX) {
- startXPoint = currentWall.x1 + Math.abs(currentWall.x1 - adjustX) / 2
+ startXPoint = currentRoof.x1 + Math.abs(currentRoof.x1 - adjustX) / 2
endXPoint = startXPoint
}
}
- if (currentWall.direction === 'left') {
- if (afterNextWall.direction === 'right' && beforePrevWall.direction === 'right') {
+ if (currentRoof.direction === 'left') {
+ if (afterNextRoof.direction === 'right' && beforePrevRoof.direction === 'right') {
adjustX =
- Math.abs(currentWall.y1 - afterNextWall.y1) < Math.abs(currentWall.y1 - beforePrevWall.y1) ? afterNextWall.x2 : beforePrevWall.x1
- } else if (afterNextWall.direction === 'right' && afterNextWall.x2 > currentWall.x2 && afterNextWall.x2 < currentWall.x1) {
- adjustX = afterNextWall.x2
- } else if (beforePrevWall.direction === 'right' && beforePrevWall.x1 > currentWall.x2 && beforePrevWall.x1 < currentWall.x1) {
- adjustX = beforePrevWall.x1
+ Math.abs(currentRoof.y1 - afterNextRoof.y1) < Math.abs(currentRoof.y1 - beforePrevRoof.y1) ? afterNextRoof.x2 : beforePrevRoof.x1
+ } else if (afterNextRoof.direction === 'right' && afterNextRoof.x2 > currentRoof.x2 && afterNextRoof.x2 < currentRoof.x1) {
+ adjustX = afterNextRoof.x2
+ } else if (beforePrevRoof.direction === 'right' && beforePrevRoof.x1 > currentRoof.x2 && beforePrevRoof.x1 < currentRoof.x1) {
+ adjustX = beforePrevRoof.x1
}
if (adjustX) {
- startXPoint = currentWall.x1 - Math.abs(currentWall.x1 - adjustX) / 2
+ startXPoint = currentRoof.x1 - Math.abs(currentRoof.x1 - adjustX) / 2
endXPoint = startXPoint
}
}
if (xEqualInnerLines.length > 0) {
xEqualInnerLines.reduce((prev, current) => {
if (prev !== undefined) {
- return Math.abs(currentWall.x1 - prev.x1) < Math.abs(currentWall.x1 - current.x1) ? prev : current
+ return Math.abs(currentRoof.x1 - prev.x1) < Math.abs(currentRoof.x1 - current.x1) ? prev : current
} else {
return current
}
}, undefined)
startXPoint =
- Math.abs(currentWall.x1 - startXPoint) * 2 <= Math.abs(currentWall.x1 - xEqualInnerLines[0].x1)
+ Math.abs(currentRoof.x1 - startXPoint) * 2 <= Math.abs(currentRoof.x1 - xEqualInnerLines[0].x1)
? startXPoint
- : Math.abs(currentWall.x1 - xEqualInnerLines[0].x1)
+ : Math.abs(currentRoof.x1 - xEqualInnerLines[0].x1)
endXPoint = startXPoint
- ridgeAcrossLength = Math.max(prevWall.length, nextWall.length) - Math.abs(currentWall.x1 - startXPoint) * 2
+ ridgeAcrossLength = Math.max(prevRoof.length, nextRoof.length) - Math.abs(currentRoof.x1 - startXPoint) * 2
if (
//xEqualInnerLines 이 다음 벽보다 안쪽에 있을때
- Math.abs(currentWall.x1 - xEqualInnerLines[0].x1) <= Math.abs(currentWall.x1 - nextWall.x1) &&
- Math.abs(currentWall.y1 - xEqualInnerLines[0].y2) >= Math.abs(currentWall.y1 - nextWall.y2)
+ Math.abs(currentRoof.x1 - xEqualInnerLines[0].x1) <= Math.abs(currentRoof.x1 - nextRoof.x1) &&
+ Math.abs(currentRoof.y1 - xEqualInnerLines[0].y2) >= Math.abs(currentRoof.y1 - nextRoof.y2)
) {
- ridgeMaxLength = Math.abs(currentWall.y1 - xEqualInnerLines[0].y2)
+ ridgeMaxLength = Math.abs(currentRoof.y1 - xEqualInnerLines[0].y2)
}
ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength)
- startYPoint = currentWall.y1 + (nextWall.direction === 'bottom' ? 1 : -1) * Math.abs(currentWall.x1 - startXPoint)
- endYPoint = startYPoint + (nextWall.direction === 'bottom' ? 1 : -1) * ridgeLength
+ startYPoint = currentRoof.y1 + (nextRoof.direction === 'bottom' ? 1 : -1) * Math.abs(currentRoof.x1 - startXPoint)
+ endYPoint = startYPoint + (nextRoof.direction === 'bottom' ? 1 : -1) * ridgeLength
}
}
}
+ const currentWall = walls[index]
+ if (currentWall.attributes.type === 'gable') {
+ if (currentRoof.x1 === currentRoof.x2) {
+ startXPoint = currentRoof.x1
+ }
+ if (currentRoof.y1 === currentRoof.y2) {
+ startYPoint = currentRoof.y1
+ }
+ }
// 마루 그리기
- if (!(startXPoint === undefined && startYPoint === undefined && endXPoint === undefined && endYPoint === undefined)) {
+ if (startXPoint !== undefined && startYPoint !== undefined && endXPoint !== undefined && endYPoint !== undefined) {
const ridge = new QLine(
[Math.min(startXPoint, endXPoint), Math.min(startYPoint, endYPoint), Math.max(startXPoint, endXPoint), Math.max(startYPoint, endYPoint)],
{
@@ -1762,7 +1740,6 @@ const drawHips = (polygon) => {
}
}
if (ridge.x1 === ridge.x2) {
- console.log('세로방향 마루')
//위쪽 좌표 기준 45, 315도 방향 라인확인
leftTop = polygon.lines
.filter((line) => line.x1 < ridge.x1 && line.y1 < ridge.y1 && Math.abs(line.x1 - ridge.x1) === Math.abs(line.y1 - ridge.y1))
@@ -2229,8 +2206,9 @@ const drawHips = (polygon) => {
})
// 마루와 연결되지 않은 hip을 그린다.
- polygon.lines.forEach((line, index) => {
+ /*polygon.lines.forEach((line, index) => {
if (!isAlreadyHip(polygon, line)) {
+ console.log(' 확인 : ', line)
let prevLine, currentLine, nextLine
if (index === 0) {
prevLine = polygon.lines[polygon.lines.length - 1]
@@ -2264,6 +2242,7 @@ const drawHips = (polygon) => {
let acrossLine = getAcrossLine(polygon, currentLine, dVector)
let hypotenuse, adjacent
+ console.log(acrossLine)
if (getLineDirection(prevLine) === getLineDirection(nextLine)) {
hypotenuse = Math.round(getRoofHypotenuse(Math.abs(currentLine.x1 - acrossLine.x1) / 2))
@@ -2304,8 +2283,7 @@ const drawHips = (polygon) => {
polygon.hips.push(hip)
polygon.innerLines.push(hip)
}
- })
- console.log('polygon.hips : ', polygon.hips)
+ })*/
}
const getPointInPolygon = (polygon, point, isInclude = false) => {
@@ -2332,7 +2310,7 @@ const getPointInPolygon = (polygon, point, isInclude = false) => {
*/
const getAcrossLine = (polygon, currentLine, dVector) => {
let acrossLine
-
+ console.log('dVector : ', dVector)
switch (dVector) {
case 45:
acrossLine = polygon.lines
@@ -2404,6 +2382,24 @@ const connectLinePoint = (polygon) => {
let missedPoints = []
//마루
polygon.ridges.forEach((ridge) => {
+ if (ridge.x1 === ridge.x2) {
+ if (
+ polygon.lines
+ .filter((roof) => roof.y1 === roof.y2)
+ .filter((roof) => roof.y1 === ridge.y1 || roof.y1 === ridge.y2 || roof.y2 === ridge.y1 || roof.y2 === ridge.y2).length > 0
+ ) {
+ return
+ }
+ }
+ if (ridge.y1 === ridge.y2) {
+ if (
+ polygon.lines
+ .filter((roof) => roof.x1 === roof.x2)
+ .filter((roof) => roof.x1 === ridge.x1 || roof.x1 === ridge.x2 || roof.x2 === ridge.x1 || roof.x2 === ridge.x2).length > 0
+ ) {
+ return
+ }
+ }
if (polygon.hips.filter((hip) => hip.x2 === ridge.x1 && hip.y2 === ridge.y1).length < 2) {
missedPoints.push({ x: ridge.x1, y: ridge.y1 })
}
@@ -2412,10 +2408,6 @@ const connectLinePoint = (polygon) => {
}
})
- console.log('polygon.ridges : ', polygon.ridges)
-
- console.log('missedPoints : ', missedPoints)
-
//추녀마루
polygon.hips.forEach((hip) => {
let count = 0
@@ -2428,7 +2420,6 @@ const connectLinePoint = (polygon) => {
})
let missedLine = []
- console.log('missedPoints : ', missedPoints)
//중복포인트제거
missedPoints = [...new Set(missedPoints.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line))
@@ -2490,14 +2481,10 @@ const connectLinePoint = (polygon) => {
missedPoints = [...new Set(missedPoints.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line))
- console.log(missedPoints)
-
missedPoints.forEach((p1) => {
let p2 = missedPoints
.filter((p) => !(p.x === p1.x && p.y === p1.y))
.reduce((prev, current) => {
- console.log('current : ', current)
- console.log('prev : ', prev)
if (prev !== undefined) {
return Math.abs(current.x - p1.x) + Math.abs(current.y - p1.y) < Math.abs(prev.x - p1.x) + Math.abs(prev.y - p1.y) ? current : prev
} else {
@@ -2506,7 +2493,6 @@ const connectLinePoint = (polygon) => {
}, undefined)
if (p2 !== undefined) {
- console.log(p1.x, p2.x, p1.y, p2.y)
if (p1.x === p2.x && p1.y < p2.y) {
missedLine.push({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y })
}
@@ -2584,7 +2570,6 @@ const connectLinePoint = (polygon) => {
strokeWidth: 1,
name: 'ridgeLine',
})
- console.log('newRidge : ', newRidge)
if (polygon.ridges.filter((r) => newRidge.x1 === r.x1 && newRidge.y1 === r.y1 && newRidge.x2 === r.x2 && newRidge.y2 === r.y2).length === 0) {
polygon.canvas.remove(ridge)
polygon.canvas.remove(ridge2)
@@ -2679,12 +2664,14 @@ const getLineDirection = (line) => {
}
}
-export const changeAllGableRoof = (polygon, offset, canvas) => {
+export const changeAllHipAndGableRoof = (polygon, offset, canvas) => {
const roof = polygon.filter((p) => p.name === 'roofBase')[0] // 지붕
const roofLines = roof.lines // 지붕의 라인
const ridges = roof.ridges // 마루의 라인
const hips = roof.hips // 추녀마루의 라인
+ console.log('roofLines : ', roofLines)
+
ridges.forEach((ridge) => {
let ridgeHip1 = hips.filter((hip) => hip.x2 === ridge.x1 && hip.y2 === ridge.y1)
let ridgeHip2 = hips.filter((hip) => hip.x2 === ridge.x2 && hip.y2 === ridge.y2)
@@ -2699,7 +2686,7 @@ export const changeAllGableRoof = (polygon, offset, canvas) => {
(roofLine.x1 === x1 && roofLine.y1 === y1 && roofLine.x2 === x2 && roofLine.y2 === y2) ||
(roofLine.x1 === x2 && roofLine.y1 === y2 && roofLine.x2 === x1 && roofLine.y2 === y1)
) {
- gableLines.push(setGableRoof(polygon, ridge, ridgeHip1[0], ridgeHip1[1], offset, canvas))
+ gableLines.push(setHipAndGableRoof(roof, ridge, ridgeHip1[0], ridgeHip1[1], offset, canvas))
}
})
}
@@ -2713,7 +2700,7 @@ export const changeAllGableRoof = (polygon, offset, canvas) => {
(roofLine.x1 === x1 && roofLine.y1 === y1 && roofLine.x2 === x2 && roofLine.y2 === y2) ||
(roofLine.x1 === x2 && roofLine.y1 === y2 && roofLine.x2 === x1 && roofLine.y2 === y1)
) {
- gableLines.push(setGableRoof(polygon, ridge, ridgeHip2[0], ridgeHip2[1], offset, canvas))
+ gableLines.push(setHipAndGableRoof(roof, ridge, ridgeHip2[0], ridgeHip2[1], offset, canvas))
}
})
}
@@ -2725,7 +2712,17 @@ export const changeAllGableRoof = (polygon, offset, canvas) => {
// splitPolygonWithLines(roof)
}
-const setGableRoof = (polygon, ridge, hip1, hip2, offset, canvas) => {
+/**
+ * 모임지붕 -> 팔작지붕 변경
+ * @param roof
+ * @param ridge
+ * @param hip1
+ * @param hip2
+ * @param offset
+ * @param canvas
+ * @returns {*}
+ */
+const setHipAndGableRoof = (roof, ridge, hip1, hip2, offset, canvas) => {
let x1 = hip1.x1,
y1 = hip1.y1
let gableLine, diffOffset
@@ -2741,7 +2738,7 @@ const setGableRoof = (polygon, ridge, hip1, hip2, offset, canvas) => {
})
gableLine = new QLine([ridge.x2 - offset, ridge.y2, ridge.x2 + offset, ridge.y2], {
- fontSize: polygon.fontSize,
+ fontSize: roof.fontSize,
stroke: 'blue',
strokeWidth: 1,
name: 'gableLine',
@@ -2772,7 +2769,7 @@ const setGableRoof = (polygon, ridge, hip1, hip2, offset, canvas) => {
y2: ridge.y2,
})
gableLine = new QLine([ridge.x1 - offset, ridge.y1, ridge.x1 + offset, ridge.y1], {
- fontSize: polygon.fontSize,
+ fontSize: roof.fontSize,
stroke: 'blue',
strokeWidth: 1,
name: 'gableLine',
@@ -2805,7 +2802,7 @@ const setGableRoof = (polygon, ridge, hip1, hip2, offset, canvas) => {
y2: ridge.y2,
})
gableLine = new QLine([ridge.x1 - offset, ridge.y1, ridge.x1 + offset, ridge.y1], {
- fontSize: polygon.fontSize,
+ fontSize: roof.fontSize,
stroke: 'blue',
strokeWidth: 1,
name: 'gableLine',
@@ -2837,7 +2834,7 @@ const setGableRoof = (polygon, ridge, hip1, hip2, offset, canvas) => {
})
gableLine = new QLine([ridge.x2 - offset, ridge.y2, ridge.x2 + offset, ridge.y2], {
- fontSize: polygon.fontSize,
+ fontSize: roof.fontSize,
stroke: 'blue',
strokeWidth: 1,
name: 'gableLine',
@@ -2870,7 +2867,7 @@ const setGableRoof = (polygon, ridge, hip1, hip2, offset, canvas) => {
y2: ridge.y2,
})
gableLine = new QLine([ridge.x1, ridge.y1 - offset, ridge.x1, ridge.y1 + offset], {
- fontSize: polygon.fontSize,
+ fontSize: roof.fontSize,
stroke: 'blue',
strokeWidth: 1,
name: 'gableLine',
@@ -2901,7 +2898,7 @@ const setGableRoof = (polygon, ridge, hip1, hip2, offset, canvas) => {
y2: ridge.y2,
})
gableLine = new QLine([ridge.x2, ridge.y2 - offset, ridge.x2, ridge.y2 + offset], {
- fontSize: polygon.fontSize,
+ fontSize: roof.fontSize,
stroke: 'blue',
strokeWidth: 1,
name: 'gableLine',
@@ -2934,7 +2931,7 @@ const setGableRoof = (polygon, ridge, hip1, hip2, offset, canvas) => {
y2: ridge.y2,
})
gableLine = new QLine([ridge.x2, ridge.y2 - offset, ridge.x2, ridge.y2 + offset], {
- fontSize: polygon.fontSize,
+ fontSize: roof.fontSize,
stroke: 'blue',
strokeWidth: 1,
name: 'gableLine',
@@ -2965,7 +2962,7 @@ const setGableRoof = (polygon, ridge, hip1, hip2, offset, canvas) => {
y2: ridge.y2,
})
gableLine = new QLine([ridge.x1, ridge.y1 - offset, ridge.x1, ridge.y1 + offset], {
- fontSize: polygon.fontSize,
+ fontSize: roof.fontSize,
stroke: 'blue',
strokeWidth: 1,
name: 'gableLine',
@@ -3064,7 +3061,7 @@ export const drawDirectionArrow = (polygon) => {
polygon.canvas.remove(polygon.arrow)
}
- let centerPoint = { x: polygon.width / 2 + polygon.left, y: polygon.height / 2 + polygon.top }
+ let centerPoint = { x: polygon.left, y: polygon.top }
let stickeyPoint
const polygonMaxX = Math.max(...polygon.getCurrentPoints().map((point) => point.x))
@@ -3075,58 +3072,58 @@ export const drawDirectionArrow = (polygon) => {
switch (direction) {
case 'north':
points = [
- { x: centerPoint.x, y: polygonMinY - 20 },
- { x: centerPoint.x + 20, y: polygonMinY - 20 },
+ { x: centerPoint.x, y: polygonMinY - 50 },
{ x: centerPoint.x + 20, y: polygonMinY - 50 },
- { x: centerPoint.x + 50, y: polygonMinY - 50 },
- { x: centerPoint.x, y: polygonMinY - 80 },
- { x: centerPoint.x - 50, y: polygonMinY - 50 },
+ { x: centerPoint.x + 20, y: polygonMinY - 80 },
+ { x: centerPoint.x + 50, y: polygonMinY - 80 },
+ { x: centerPoint.x, y: polygonMinY - 110 },
+ { x: centerPoint.x - 50, y: polygonMinY - 80 },
+ { x: centerPoint.x - 20, y: polygonMinY - 80 },
{ x: centerPoint.x - 20, y: polygonMinY - 50 },
- { x: centerPoint.x - 20, y: polygonMinY - 20 },
]
- stickeyPoint = { x: centerPoint.x, y: polygonMinY - 80 }
+ stickeyPoint = { x: centerPoint.x, y: polygonMinY - 110 }
break
case 'south':
points = [
- { x: centerPoint.x, y: polygonMaxY + 20 },
- { x: centerPoint.x + 20, y: polygonMaxY + 20 },
+ { x: centerPoint.x, y: polygonMaxY + 50 },
{ x: centerPoint.x + 20, y: polygonMaxY + 50 },
- { x: centerPoint.x + 50, y: polygonMaxY + 50 },
- { x: centerPoint.x, y: polygonMaxY + 80 },
- { x: centerPoint.x - 50, y: polygonMaxY + 50 },
+ { x: centerPoint.x + 20, y: polygonMaxY + 80 },
+ { x: centerPoint.x + 50, y: polygonMaxY + 80 },
+ { x: centerPoint.x, y: polygonMaxY + 110 },
+ { x: centerPoint.x - 50, y: polygonMaxY + 80 },
+ { x: centerPoint.x - 20, y: polygonMaxY + 80 },
{ x: centerPoint.x - 20, y: polygonMaxY + 50 },
- { x: centerPoint.x - 20, y: polygonMaxY + 20 },
]
- stickeyPoint = { x: centerPoint.x, y: polygonMaxY + 80 }
+ stickeyPoint = { x: centerPoint.x, y: polygonMaxY + 110 }
break
case 'west':
points = [
- { x: polygonMinX - 20, y: centerPoint.y },
- { x: polygonMinX - 20, y: centerPoint.y + 20 },
+ { x: polygonMinX - 50, y: centerPoint.y },
{ x: polygonMinX - 50, y: centerPoint.y + 20 },
- { x: polygonMinX - 50, y: centerPoint.y + 50 },
- { x: polygonMinX - 80, y: centerPoint.y },
- { x: polygonMinX - 50, y: centerPoint.y - 50 },
+ { x: polygonMinX - 80, y: centerPoint.y + 20 },
+ { x: polygonMinX - 80, y: centerPoint.y + 50 },
+ { x: polygonMinX - 110, y: centerPoint.y },
+ { x: polygonMinX - 80, y: centerPoint.y - 50 },
+ { x: polygonMinX - 80, y: centerPoint.y - 20 },
{ x: polygonMinX - 50, y: centerPoint.y - 20 },
- { x: polygonMinX - 20, y: centerPoint.y - 20 },
]
- stickeyPoint = { x: polygonMinX - 80, y: centerPoint.y }
+ stickeyPoint = { x: polygonMinX - 110, y: centerPoint.y }
break
case 'east':
points = [
- { x: polygonMaxX + 20, y: centerPoint.y },
- { x: polygonMaxX + 20, y: centerPoint.y + 20 },
+ { x: polygonMaxX + 50, y: centerPoint.y },
{ x: polygonMaxX + 50, y: centerPoint.y + 20 },
- { x: polygonMaxX + 50, y: centerPoint.y + 50 },
- { x: polygonMaxX + 80, y: centerPoint.y },
- { x: polygonMaxX + 50, y: centerPoint.y - 50 },
+ { x: polygonMaxX + 80, y: centerPoint.y + 20 },
+ { x: polygonMaxX + 80, y: centerPoint.y + 50 },
+ { x: polygonMaxX + 110, y: centerPoint.y },
+ { x: polygonMaxX + 80, y: centerPoint.y - 50 },
+ { x: polygonMaxX + 80, y: centerPoint.y - 20 },
{ x: polygonMaxX + 50, y: centerPoint.y - 20 },
- { x: polygonMaxX + 20, y: centerPoint.y - 20 },
]
- stickeyPoint = { x: polygonMaxX + 80, y: centerPoint.y }
+ stickeyPoint = { x: polygonMaxX + 110, y: centerPoint.y }
break
}
diff --git a/yarn.lock b/yarn.lock
index affc38d7..1472f5ac 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -163,6 +163,66 @@
jsonwebtoken "^9.0.0"
uuid "^8.3.0"
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7"
+ integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==
+ dependencies:
+ "@babel/highlight" "^7.25.7"
+ picocolors "^1.0.0"
+
+"@babel/generator@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.7.tgz#de86acbeb975a3e11ee92dd52223e6b03b479c56"
+ integrity sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==
+ dependencies:
+ "@babel/types" "^7.25.7"
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.25"
+ jsesc "^3.0.2"
+
+"@babel/helper-module-imports@^7.16.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz#dba00d9523539152906ba49263e36d7261040472"
+ integrity sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==
+ dependencies:
+ "@babel/traverse" "^7.25.7"
+ "@babel/types" "^7.25.7"
+
+"@babel/helper-string-parser@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54"
+ integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==
+
+"@babel/helper-validator-identifier@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5"
+ integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==
+
+"@babel/highlight@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.7.tgz#20383b5f442aa606e7b5e3043b0b1aafe9f37de5"
+ integrity sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.25.7"
+ chalk "^2.4.2"
+ js-tokens "^4.0.0"
+ picocolors "^1.0.0"
+
+"@babel/parser@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.7.tgz#99b927720f4ddbfeb8cd195a363ed4532f87c590"
+ integrity sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==
+ dependencies:
+ "@babel/types" "^7.25.7"
+
+"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6"
+ integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==
+ dependencies:
+ regenerator-runtime "^0.14.0"
+
"@babel/runtime@^7.20.13", "@babel/runtime@^7.24.8":
version "7.25.0"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz"
@@ -170,6 +230,37 @@
dependencies:
regenerator-runtime "^0.14.0"
+"@babel/template@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.7.tgz#27f69ce382855d915b14ab0fe5fb4cbf88fa0769"
+ integrity sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==
+ dependencies:
+ "@babel/code-frame" "^7.25.7"
+ "@babel/parser" "^7.25.7"
+ "@babel/types" "^7.25.7"
+
+"@babel/traverse@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.7.tgz#83e367619be1cab8e4f2892ef30ba04c26a40fa8"
+ integrity sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==
+ dependencies:
+ "@babel/code-frame" "^7.25.7"
+ "@babel/generator" "^7.25.7"
+ "@babel/parser" "^7.25.7"
+ "@babel/template" "^7.25.7"
+ "@babel/types" "^7.25.7"
+ debug "^4.3.1"
+ globals "^11.1.0"
+
+"@babel/types@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.7.tgz#1b7725c1d3a59f328cb700ce704c46371e6eef9b"
+ integrity sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==
+ dependencies:
+ "@babel/helper-string-parser" "^7.25.7"
+ "@babel/helper-validator-identifier" "^7.25.7"
+ to-fast-properties "^2.0.0"
+
"@bedrock-layout/use-forwarded-ref@^1.3.1":
version "1.6.1"
resolved "https://registry.npmjs.org/@bedrock-layout/use-forwarded-ref/-/use-forwarded-ref-1.6.1.tgz"
@@ -182,6 +273,132 @@
resolved "https://registry.npmjs.org/@bedrock-layout/use-stateful-ref/-/use-stateful-ref-1.4.1.tgz"
integrity sha512-4eKO2KdQEXcR5LI4QcxqlJykJUDQJWDeWYAukIn6sRQYoabcfI5kDl61PUi6FR6o8VFgQ8IEP7HleKqWlSe8SQ==
+"@emotion/babel-plugin@^11.11.0", "@emotion/babel-plugin@^11.12.0":
+ version "11.12.0"
+ resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz#7b43debb250c313101b3f885eba634f1d723fcc2"
+ integrity sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==
+ dependencies:
+ "@babel/helper-module-imports" "^7.16.7"
+ "@babel/runtime" "^7.18.3"
+ "@emotion/hash" "^0.9.2"
+ "@emotion/memoize" "^0.9.0"
+ "@emotion/serialize" "^1.2.0"
+ babel-plugin-macros "^3.1.0"
+ convert-source-map "^1.5.0"
+ escape-string-regexp "^4.0.0"
+ find-root "^1.1.0"
+ source-map "^0.5.7"
+ stylis "4.2.0"
+
+"@emotion/cache@^11.11.0", "@emotion/cache@^11.13.0", "@emotion/cache@^11.4.0":
+ version "11.13.1"
+ resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.13.1.tgz#fecfc54d51810beebf05bf2a161271a1a91895d7"
+ integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==
+ dependencies:
+ "@emotion/memoize" "^0.9.0"
+ "@emotion/sheet" "^1.4.0"
+ "@emotion/utils" "^1.4.0"
+ "@emotion/weak-memoize" "^0.4.0"
+ stylis "4.2.0"
+
+"@emotion/hash@^0.9.2":
+ version "0.9.2"
+ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b"
+ integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==
+
+"@emotion/is-prop-valid@^1.2.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz#8d5cf1132f836d7adbe42cf0b49df7816fc88240"
+ integrity sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==
+ dependencies:
+ "@emotion/memoize" "^0.9.0"
+
+"@emotion/memoize@^0.9.0":
+ version "0.9.0"
+ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102"
+ integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==
+
+"@emotion/react@11.11.0":
+ version "11.11.0"
+ resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.0.tgz#408196b7ef8729d8ad08fc061b03b046d1460e02"
+ integrity sha512-ZSK3ZJsNkwfjT3JpDAWJZlrGD81Z3ytNDsxw1LKq1o+xkmO5pnWfr6gmCC8gHEFf3nSSX/09YrG67jybNPxSUw==
+ dependencies:
+ "@babel/runtime" "^7.18.3"
+ "@emotion/babel-plugin" "^11.11.0"
+ "@emotion/cache" "^11.11.0"
+ "@emotion/serialize" "^1.1.2"
+ "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1"
+ "@emotion/utils" "^1.2.1"
+ "@emotion/weak-memoize" "^0.3.1"
+ hoist-non-react-statics "^3.3.1"
+
+"@emotion/react@^11.8.1":
+ version "11.13.3"
+ resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.13.3.tgz#a69d0de2a23f5b48e0acf210416638010e4bd2e4"
+ integrity sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==
+ dependencies:
+ "@babel/runtime" "^7.18.3"
+ "@emotion/babel-plugin" "^11.12.0"
+ "@emotion/cache" "^11.13.0"
+ "@emotion/serialize" "^1.3.1"
+ "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0"
+ "@emotion/utils" "^1.4.0"
+ "@emotion/weak-memoize" "^0.4.0"
+ hoist-non-react-statics "^3.3.1"
+
+"@emotion/serialize@^1.1.2", "@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.1":
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.2.tgz#e1c1a2e90708d5d85d81ccaee2dfeb3cc0cccf7a"
+ integrity sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==
+ dependencies:
+ "@emotion/hash" "^0.9.2"
+ "@emotion/memoize" "^0.9.0"
+ "@emotion/unitless" "^0.10.0"
+ "@emotion/utils" "^1.4.1"
+ csstype "^3.0.2"
+
+"@emotion/sheet@^1.4.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c"
+ integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==
+
+"@emotion/styled@11.11.0":
+ version "11.11.0"
+ resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.0.tgz#26b75e1b5a1b7a629d7c0a8b708fbf5a9cdce346"
+ integrity sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==
+ dependencies:
+ "@babel/runtime" "^7.18.3"
+ "@emotion/babel-plugin" "^11.11.0"
+ "@emotion/is-prop-valid" "^1.2.1"
+ "@emotion/serialize" "^1.1.2"
+ "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1"
+ "@emotion/utils" "^1.2.1"
+
+"@emotion/unitless@^0.10.0":
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.10.0.tgz#2af2f7c7e5150f497bdabd848ce7b218a27cf745"
+ integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==
+
+"@emotion/use-insertion-effect-with-fallbacks@^1.0.1", "@emotion/use-insertion-effect-with-fallbacks@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz#1a818a0b2c481efba0cf34e5ab1e0cb2dcb9dfaf"
+ integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==
+
+"@emotion/utils@^1.2.1", "@emotion/utils@^1.4.0", "@emotion/utils@^1.4.1":
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.1.tgz#b3adbb43de12ee2149541c4f1337d2eb7774f0ad"
+ integrity sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==
+
+"@emotion/weak-memoize@^0.3.1":
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6"
+ integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
+
+"@emotion/weak-memoize@^0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6"
+ integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
+
"@floating-ui/core@^1.6.0":
version "1.6.7"
resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz"
@@ -197,6 +414,14 @@
"@floating-ui/core" "^1.6.0"
"@floating-ui/utils" "^0.2.7"
+"@floating-ui/dom@^1.0.1":
+ version "1.6.11"
+ resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.11.tgz#8631857838d34ee5712339eb7cbdfb8ad34da723"
+ integrity sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==
+ dependencies:
+ "@floating-ui/core" "^1.6.0"
+ "@floating-ui/utils" "^0.2.8"
+
"@floating-ui/react-dom@^2.1.1":
version "2.1.1"
resolved "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz"
@@ -218,6 +443,11 @@
resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz"
integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==
+"@floating-ui/utils@^0.2.8":
+ version "0.2.8"
+ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62"
+ integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==
+
"@formatjs/ecma402-abstract@2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz"
@@ -298,7 +528,7 @@
wrap-ansi "^8.1.0"
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
-"@jridgewell/gen-mapping@^0.3.2":
+"@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5":
version "0.3.5"
resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz"
integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==
@@ -322,7 +552,7 @@
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
-"@jridgewell/trace-mapping@^0.3.24":
+"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
version "0.3.25"
resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz"
integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
@@ -3877,6 +4107,31 @@
dependencies:
undici-types "~6.18.2"
+"@types/parse-json@^4.0.0":
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
+ integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==
+
+"@types/prop-types@*":
+ version "15.7.13"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451"
+ integrity sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==
+
+"@types/react-transition-group@^4.4.0":
+ version "4.4.11"
+ resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.11.tgz#d963253a611d757de01ebb241143b1017d5d63d5"
+ integrity sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react@*":
+ version "18.3.11"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.11.tgz#9d530601ff843ee0d7030d4227ea4360236bd537"
+ integrity sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==
+ dependencies:
+ "@types/prop-types" "*"
+ csstype "^3.0.2"
+
"@types/readable-stream@^4.0.0":
version "4.0.15"
resolved "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.15.tgz"
@@ -3969,6 +4224,13 @@ ansi-regex@^6.0.1:
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz"
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
+ansi-styles@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+ integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+ dependencies:
+ color-convert "^1.9.0"
+
ansi-styles@^4.0.0:
version "4.3.0"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
@@ -4035,6 +4297,15 @@ axios@^1.7.3:
form-data "^4.0.0"
proxy-from-env "^1.1.0"
+babel-plugin-macros@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1"
+ integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+ cosmiconfig "^7.0.0"
+ resolve "^1.19.0"
+
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
@@ -4112,6 +4383,11 @@ busboy@1.6.0:
dependencies:
streamsearch "^1.1.0"
+callsites@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
+ integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
camelcase-css@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz"
@@ -4131,6 +4407,15 @@ canvas@^2.8.0:
nan "^2.17.0"
simple-get "^3.0.3"
+chalk@^2.4.2:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
version "3.6.0"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
@@ -4171,6 +4456,13 @@ clsx@^2.0.0, clsx@^2.1.0:
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
+color-convert@^1.9.0:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+ integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+ dependencies:
+ color-name "1.1.3"
+
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
@@ -4178,6 +4470,11 @@ color-convert@^2.0.1:
dependencies:
color-name "~1.1.4"
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+ integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
+
color-name@^1.0.0, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
@@ -4261,6 +4558,11 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0:
resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz"
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
+convert-source-map@^1.5.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
+ integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
+
convertapi@^1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/convertapi/-/convertapi-1.14.0.tgz#a291a98cb986ae1e0f2340a130adbe17f65c8c76"
@@ -4273,6 +4575,17 @@ cookie@0.6.0:
resolved "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
+cosmiconfig@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
+ integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==
+ dependencies:
+ "@types/parse-json" "^4.0.0"
+ import-fresh "^3.2.1"
+ parse-json "^5.0.0"
+ path-type "^4.0.0"
+ yaml "^1.10.0"
+
cross-spawn@^7.0.0:
version "7.0.3"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
@@ -4304,6 +4617,11 @@ cssstyle@^2.3.0:
dependencies:
cssom "~0.3.6"
+csstype@^3.0.2:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
+ integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
+
d3-array@1:
version "1.2.4"
resolved "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz"
@@ -4347,6 +4665,13 @@ debug@4, debug@^4.3.3, debug@^4.3.4:
dependencies:
ms "2.1.2"
+debug@^4.3.1:
+ version "4.3.7"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
+ integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
+ dependencies:
+ ms "^2.1.3"
+
decimal.js@^10.3.1, decimal.js@^10.4.3:
version "10.4.3"
resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz"
@@ -4399,6 +4724,14 @@ dlv@^1.1.3:
resolved "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
+dom-helpers@^5.0.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
+ integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
+ dependencies:
+ "@babel/runtime" "^7.8.7"
+ csstype "^3.0.2"
+
domexception@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz"
@@ -4433,11 +4766,28 @@ emoji-regex@^9.2.2:
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
+error-ex@^1.3.1:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
+ integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
+ dependencies:
+ is-arrayish "^0.2.1"
+
escape-latex@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz"
integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
+
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
escodegen@^2.0.0:
version "2.1.0"
resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz"
@@ -4512,6 +4862,11 @@ fill-range@^7.1.1:
dependencies:
to-regex-range "^5.0.1"
+find-root@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
+ integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
+
flat@^5.0.2:
version "5.0.2"
resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz"
@@ -4649,6 +5004,11 @@ glob@^7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
+globals@^11.1.0:
+ version "11.12.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
+ integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
+
graceful-fs@^4.2.11:
version "4.2.11"
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz"
@@ -4659,6 +5019,11 @@ hamt_plus@1.0.2:
resolved "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz"
integrity sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+ integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
+
has-unicode@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz"
@@ -4671,6 +5036,13 @@ hasown@^2.0.0:
dependencies:
function-bind "^1.1.2"
+hoist-non-react-statics@^3.3.1:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
html-encoding-sniffer@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz"
@@ -4728,6 +5100,14 @@ immutable@^4.0.0:
resolved "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz"
integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==
+import-fresh@^3.2.1:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
+ integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
@@ -4777,6 +5157,11 @@ iron-webcrypto@1.2.1:
resolved "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz"
integrity sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==
+is-arrayish@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+ integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
+
is-arrayish@^0.3.1:
version "0.3.2"
resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz"
@@ -4859,12 +5244,17 @@ jiti@^1.21.0:
resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz"
integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==
+js-cookie@^3.0.5:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
+ integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
+
js-md4@^0.3.2:
version "0.3.2"
resolved "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz"
integrity sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==
-"js-tokens@^3.0.0 || ^4.0.0":
+"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
@@ -4902,6 +5292,16 @@ jsdom@^19.0.0:
ws "^8.2.3"
xml-name-validator "^4.0.0"
+jsesc@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"
+ integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==
+
+json-parse-even-better-errors@^2.3.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
+ integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
jsonwebtoken@^9.0.0:
version "9.0.2"
resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz"
@@ -5076,6 +5476,11 @@ mathjs@^13.0.2:
tiny-emitter "^2.1.0"
typed-function "^4.2.1"
+memoize-one@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
+ integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
+
merge2@^1.3.0:
version "1.4.1"
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
@@ -5155,6 +5560,11 @@ ms@2.1.2, ms@^2.1.1:
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+ms@^2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
mssql@^11.0.1:
version "11.0.1"
resolved "https://registry.npmjs.org/mssql/-/mssql-11.0.1.tgz"
@@ -5283,6 +5693,23 @@ open@^8.0.0:
is-docker "^2.1.1"
is-wsl "^2.2.0"
+parent-module@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+ integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+ dependencies:
+ callsites "^3.0.0"
+
+parse-json@^5.0.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
+ integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ error-ex "^1.3.1"
+ json-parse-even-better-errors "^2.3.0"
+ lines-and-columns "^1.1.6"
+
parse5@6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz"
@@ -5311,6 +5738,11 @@ path-scurry@^1.11.1:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+path-type@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
+ integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+
picocolors@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz"
@@ -5428,7 +5860,7 @@ process@^0.11.10:
resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
-prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -5523,6 +5955,14 @@ react-draggable@^4.4.6:
clsx "^1.1.1"
prop-types "^15.8.1"
+react-dropdown-select@^4.11.3:
+ version "4.11.3"
+ resolved "https://registry.yarnpkg.com/react-dropdown-select/-/react-dropdown-select-4.11.3.tgz#b23b8906f3bedc9d6a1a2125af936b34d4057158"
+ integrity sha512-/mOGSqqhmKsxxrmotLM+qn1Ss3nxGN6QnYusyQ7f0wizsWrc7ZmbcZhGRtwkJwpL6JYDQVTn19EYxJU1XfXrDA==
+ dependencies:
+ "@emotion/react" "11.11.0"
+ "@emotion/styled" "11.11.0"
+
react-hook-form@^7.53.0:
version "7.53.0"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.0.tgz#3cf70951bf41fa95207b34486203ebefbd3a05ab"
@@ -5533,7 +5973,7 @@ react-icons@^5.3.0:
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.3.0.tgz#ccad07a30aebd40a89f8cfa7d82e466019203f1c"
integrity sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==
-react-is@^16.13.1:
+react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -5571,6 +6011,21 @@ react-responsive-modal@^6.4.2:
body-scroll-lock "^3.1.5"
classnames "^2.3.1"
+react-select@^5.8.1:
+ version "5.8.1"
+ resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.8.1.tgz#3284a93b7633b5e893306b2a8007ea0f793e62b9"
+ integrity sha512-RT1CJmuc+ejqm5MPgzyZujqDskdvB9a9ZqrdnVLsvAHjJ3Tj0hELnLeVPQlmYdVKCdCpxanepl6z7R5KhXhWzg==
+ dependencies:
+ "@babel/runtime" "^7.12.0"
+ "@emotion/cache" "^11.4.0"
+ "@emotion/react" "^11.8.1"
+ "@floating-ui/dom" "^1.0.1"
+ "@types/react-transition-group" "^4.4.0"
+ memoize-one "^6.0.0"
+ prop-types "^15.6.0"
+ react-transition-group "^4.3.0"
+ use-isomorphic-layout-effect "^1.1.2"
+
react-style-singleton@^2.2.1:
version "2.2.1"
resolved "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz"
@@ -5596,6 +6051,16 @@ react-toastify@^10.0.5:
dependencies:
clsx "^2.1.0"
+react-transition-group@^4.3.0:
+ version "4.4.5"
+ resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
+ integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
+ dependencies:
+ "@babel/runtime" "^7.5.5"
+ dom-helpers "^5.0.1"
+ loose-envify "^1.4.0"
+ prop-types "^15.6.2"
+
react@^18:
version "18.3.1"
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
@@ -5654,7 +6119,12 @@ requires-port@^1.0.0:
resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
-resolve@^1.1.7, resolve@^1.22.2:
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+resolve@^1.1.7, resolve@^1.19.0, resolve@^1.22.2:
version "1.22.8"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@@ -5820,6 +6290,11 @@ skmeans@0.9.7:
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz"
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
+source-map@^0.5.7:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
+ integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
+
source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
@@ -5907,6 +6382,11 @@ styled-jsx@5.1.1:
dependencies:
client-only "0.0.1"
+stylis@4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
+ integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
+
sucrase@^3.32.0:
version "3.35.0"
resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz"
@@ -5920,6 +6400,13 @@ sucrase@^3.32.0:
pirates "^4.0.1"
ts-interface-checker "^0.1.9"
+supports-color@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+ integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+ dependencies:
+ has-flag "^3.0.0"
+
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
@@ -6049,6 +6536,11 @@ tinyqueue@^2.0.0, tinyqueue@^2.0.3:
resolved "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz"
integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==
+to-fast-properties@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
+ integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
+
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
@@ -6142,7 +6634,7 @@ use-composed-ref@^1.3.0:
resolved "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz"
integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==
-use-isomorphic-layout-effect@^1.1.1:
+use-isomorphic-layout-effect@^1.1.1, use-isomorphic-layout-effect@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz"
integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==
@@ -6294,6 +6786,11 @@ yallist@^4.0.0:
resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+yaml@^1.10.0:
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
+ integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
+
yaml@^2.3.4:
version "2.4.5"
resolved "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz"
|