Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
eda80451a1
9
src/app/community/qna/page.jsx
Normal file
9
src/app/community/qna/page.jsx
Normal file
@ -0,0 +1,9 @@
|
||||
import Qna from '@/components/community/Qna'
|
||||
|
||||
export default async function CommunityQnaPage() {
|
||||
return (
|
||||
<>
|
||||
<Qna />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -208,6 +208,8 @@ export const SAVE_KEY = [
|
||||
'fontWeight',
|
||||
'dormerAttributes',
|
||||
'toFixed',
|
||||
'startPoint',
|
||||
'endPoint',
|
||||
'isSortedPoints',
|
||||
]
|
||||
|
||||
|
||||
214
src/components/community/Qna.jsx
Normal file
214
src/components/community/Qna.jsx
Normal file
@ -0,0 +1,214 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
|
||||
import Search from '@/components/community/Search'
|
||||
import Pagination from '@/components/community/Pagination'
|
||||
|
||||
import { useContext } from 'react'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useResetRecoilState, useRecoilValue, useRecoilState } from 'recoil'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
|
||||
import { searchState } from '@/store/boardAtom'
|
||||
|
||||
import { QcastContext } from '@/app/QcastProvider'
|
||||
import QnaBoardDetailModal from '@/components/community/modal/QnaDetailModal'
|
||||
import { sessionStore } from '@/store/commonAtom'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { useCommonCode } from '@/hooks/common/useCommonCode'
|
||||
|
||||
export default function Qna() {
|
||||
const { getMessage } = useMessage()
|
||||
const resetSearch = useResetRecoilState(searchState)
|
||||
const [isInitialized, setIsInitialized] = useState(false)
|
||||
|
||||
//const search = useRecoilValue(searchState)
|
||||
const [searchForm, setSearchForm] = useRecoilState(searchState)
|
||||
const { findCommonCode } = useCommonCode()
|
||||
const { setIsGlobalLoading } = useContext(QcastContext)
|
||||
const { get } = useAxios()
|
||||
const [boardList, setBoardList] = useState([])
|
||||
const [sessionState, setSessionState] = useRecoilState(sessionStore)
|
||||
const [search, setSearch] = useRecoilState(searchState)
|
||||
// 팝업 관련
|
||||
const [open, setOpen] = useState(false)
|
||||
const [modalQnaNo, setModalQnaNo] = useState('')
|
||||
const [modalQnaType, setModalQnaType] = useState('')
|
||||
|
||||
// 목록 조회
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
setIsGlobalLoading(true)
|
||||
const startRow = (search.currentPage - 1) * search.pageBlock > 0 ? (search.currentPage - 1) * search.pageBlock + 1 : 1
|
||||
const endRow = search.currentPage * search.pageBlock
|
||||
|
||||
const url = `/api/board/list`
|
||||
const params = new URLSearchParams({
|
||||
schNoticeTpCd : 'QC',
|
||||
schNoticeClsCd: 'QNA',
|
||||
compCd : 5200,
|
||||
storeId : sessionState.storeId,
|
||||
loginId : sessionState.userId,
|
||||
schTitle : search.searchValue ? search.searchValue : '',
|
||||
startRow : startRow,
|
||||
endRow : endRow,
|
||||
schMainYn : 'N',
|
||||
siteTpCd : 'QC',
|
||||
})
|
||||
const apiUrl = `${url}?${params.toString()}`
|
||||
|
||||
const resultData = await get({ url: apiUrl })
|
||||
|
||||
if (resultData) {
|
||||
if (resultData.result.code === 200) {
|
||||
if (resultData.data.length > 0) {
|
||||
setBoardList(resultData.data)
|
||||
setSearch({ ...search, totalCount: resultData.data[0].totCnt })
|
||||
} else {
|
||||
setBoardList([])
|
||||
setSearch({ ...search, totalCount: 0 })
|
||||
}
|
||||
} else {
|
||||
alert(resultData.result.message)
|
||||
}
|
||||
}
|
||||
setIsGlobalLoading(false)
|
||||
}
|
||||
|
||||
fetchData()
|
||||
}, [search.currentPage, search.searchValue, search.pageBlock, search.searchFlag])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (search.mainFlag === 'N') {
|
||||
resetSearch()
|
||||
} else {
|
||||
//메인에서 FAQ 조회 왔을때 로딩바 해제
|
||||
setIsGlobalLoading(false)
|
||||
setSearchForm({ ...searchForm, mainFlag: 'N' })
|
||||
}
|
||||
setIsInitialized(true)
|
||||
|
||||
//문의구분코드
|
||||
// const codeL = findCommonCode(204200)
|
||||
// const codeM = findCommonCode(204300)
|
||||
// const codeS = findCommonCode(204400)
|
||||
|
||||
|
||||
|
||||
}, [])
|
||||
|
||||
if (!isInitialized) {
|
||||
return null
|
||||
}
|
||||
|
||||
const boardType = {
|
||||
boardTitle: getMessage('qna.title'),
|
||||
subTitle: getMessage('qna.sub.title'),
|
||||
clsCode: 'QNA',
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="sub-header">
|
||||
<div className="sub-header-inner">
|
||||
<ul className="sub-header-title-wrap">
|
||||
<li className="title-item">
|
||||
<Link className="sub-header-title" href={'#'}>
|
||||
{getMessage('qna.title')}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="sub-header-location">
|
||||
<li className="location-item">
|
||||
<span className="home">
|
||||
<Image src="/static/images/main/home_icon.svg" alt="react" width={16} height={16} />
|
||||
</span>
|
||||
</li>
|
||||
<li className="location-item">
|
||||
<span>{getMessage('header.menus.community')}</span>
|
||||
</li>
|
||||
<li className="location-item">
|
||||
<span>{getMessage('qna.title')}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sub-content">
|
||||
<div className="sub-content-inner">
|
||||
<div className="sub-table-box">
|
||||
<Search title={boardType.boardTitle} subTitle={boardType.subTitle} isSelectUse={true} clsCode={boardType.clsCode} />
|
||||
{/*<QnaTable clsCode={boardType.clsCode} />*/}
|
||||
<div className="community-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={100}/>
|
||||
<col width={150}/>
|
||||
<col />
|
||||
<col width={150}/>
|
||||
<col width={150}/>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{boardList.length > 0 ? (
|
||||
boardList?.map((board) => (
|
||||
<tr
|
||||
key={board.qnaNo}
|
||||
onClick={() => {
|
||||
setOpen(true)
|
||||
setModalQnaNo(board.qnaNo)
|
||||
setModalQnaType("["+board?.qnaClsLrgCd+"/"+board?.qnaClsMidCd+"/"+board?.qnaClsSmlCd+"]")
|
||||
}}
|
||||
>
|
||||
<td className="al-c">
|
||||
{/* 번호 */}
|
||||
{board.totCnt - board.rowNumber + 1}
|
||||
</td>
|
||||
{/* 답변 */}
|
||||
{board?.answerYn === 'Y'? (<td className="al-c "> {getMessage('qna.list.header.answer.yes')}</td>) : (<td className="al-c org"> {getMessage('qna.list.header.answer.no')}</td>)}
|
||||
<td>
|
||||
<div className="mb5">[{board?.qnaClsLrgCd} / {board?.qnaClsMidCd} / {board?.qnaClsSmlCd}]</div>
|
||||
{/* 제목 */}
|
||||
<div className="text-frame">
|
||||
<div className="text-overflow">{board.title}{board.qstTitle}</div>
|
||||
{board.attachYn === 'Y' && <span className="clip"></span>}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="renewal">
|
||||
{/*{board.uptDt && (*/}
|
||||
{/* <>*/}
|
||||
{/* (<span>{getMessage('board.uptDt')}</span> : {board.uptDt})*/}
|
||||
{/* </>*/}
|
||||
{/*)}*/}
|
||||
{board.regUserNm}
|
||||
</div>
|
||||
</td>
|
||||
<td className="al-c">
|
||||
{/* 등록일 */}
|
||||
{board.regDt.split(' ')[0]}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={4} className="al-c">
|
||||
{getMessage('common.message.no.data')}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<Pagination />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{open && <QnaBoardDetailModal qnaNo={modalQnaNo} setOpen={setOpen} qnaType = {modalQnaType} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -4,10 +4,11 @@ import { searchState } from '@/store/boardAtom'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import { useState } from 'react'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import QnaRegModal from '@/components/community/modal/QnaRegModal'
|
||||
|
||||
export default function Search({ title = '', subTitle = '', isSelectUse = false }) {
|
||||
export default function Search({ title = '', subTitle = '', isSelectUse = false, clsCode = '' }) {
|
||||
const { getMessage } = useMessage()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const search = useRecoilValue(searchState)
|
||||
const [searchForm, setSearchForm] = useRecoilState(searchState)
|
||||
|
||||
@ -32,7 +33,13 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false
|
||||
} else {
|
||||
setSearchView(false)
|
||||
setSearchViewText('')
|
||||
setSearchForm({ ...searchForm, currentPage: 1, searchValue: '', pageBlock: block, searchFlag: !searchForm.searchFlag })
|
||||
setSearchForm({
|
||||
...searchForm,
|
||||
currentPage: 1,
|
||||
searchValue: '',
|
||||
pageBlock : block,
|
||||
searchFlag : !searchForm.searchFlag,
|
||||
})
|
||||
}
|
||||
// 조회 후 값 비워주기
|
||||
setSearchValue('')
|
||||
@ -57,7 +64,10 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false
|
||||
onKeyDown={handleKeyDown}
|
||||
value={searchValue}
|
||||
/>
|
||||
<button type="button" className="community-search-ico" onClick={() => handleSearch(searchValue, selectPageBlock)}></button>
|
||||
|
||||
<button type="button" className="community-search-ico"
|
||||
onClick={() => handleSearch(searchValue, selectPageBlock)}></button>
|
||||
|
||||
</div>
|
||||
{searchView && (
|
||||
<div className="community-search-keyword">
|
||||
@ -92,6 +102,14 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false
|
||||
</div>
|
||||
{isSelectUse && (
|
||||
<div className="left-unit-box">
|
||||
{clsCode === 'QNA' &&
|
||||
<div>
|
||||
<button className="btn-origin navy mr10"
|
||||
onClick={() => {
|
||||
setOpen(true)
|
||||
}}> {getMessage('qna.sub.btn.inquiry')}</button>
|
||||
</div>
|
||||
}
|
||||
<div className="select-box" style={{ width: '80px' }}>
|
||||
<select
|
||||
className="select-light black"
|
||||
@ -112,6 +130,7 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{open && <QnaRegModal setOpen={setOpen} setReload={handleSearch} searchValue={searchValue ? searchValue : searchViewText} selectPageBlock = {selectPageBlock}/>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
127
src/components/community/modal/QnaDetailModal.jsx
Normal file
127
src/components/community/modal/QnaDetailModal.jsx
Normal file
@ -0,0 +1,127 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { handleFileDown } from '@/util/board-utils'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { sessionStore } from '@/store/commonAtom'
|
||||
import { useRecoilState } from 'recoil'
|
||||
|
||||
export default function QnaDetailModal({ qnaNo, setOpen, qnaType }) {
|
||||
const { getMessage } = useMessage()
|
||||
// api 조회 관련
|
||||
const { get } = useAxios()
|
||||
const [boardDetail, setBoardDetail] = useState({})
|
||||
const [sessionState, setSessionState] = useRecoilState(sessionStore)
|
||||
|
||||
useEffect(() => {
|
||||
// 상세 조회
|
||||
const fetchDetail = async (qnaNo) => {
|
||||
const url = `/api/board/detail`
|
||||
const params = new URLSearchParams({
|
||||
noticeNo : qnaNo,
|
||||
qnaNo : qnaNo,
|
||||
schNoticeClsCd: 'QNA',
|
||||
compCd : 5200,
|
||||
loginId : sessionState.userId,
|
||||
langCd : 'JA',
|
||||
})
|
||||
const apiUrl = `${url}?${params.toString()}`
|
||||
|
||||
const resultData = await get({ url: apiUrl })
|
||||
|
||||
if (resultData) {
|
||||
if (resultData.result.code === 200) {
|
||||
const boardDetail = resultData.data
|
||||
setBoardDetail(boardDetail)
|
||||
} else {
|
||||
alert(resultData.result.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fetchDetail(qnaNo)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div key={qnaNo} className="modal-popup community">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button
|
||||
type="button"
|
||||
className="modal-close"
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{getMessage('board.sub.btn.close')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="oneonone-header-wrap">
|
||||
<div className="oneonone-title"> {qnaType} {boardDetail.qstTitle}</div>
|
||||
<div className="oneonone-infor">
|
||||
<div className="profile">{boardDetail.regUserNm}</div>
|
||||
<div className="date">{boardDetail.regDt}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="oneonone-detail">
|
||||
{boardDetail.listFile && (
|
||||
<dl className="community_detail-file-wrap">
|
||||
<dt>{getMessage('qna.detail.sub.fileList')}</dt>
|
||||
{boardDetail.listFile.map((boardFile) => (
|
||||
<dd key={boardFile.encodeFileNo}>
|
||||
<button type="button" className="down" onClick={() => handleFileDown(boardFile.fileNo, 'N')}>
|
||||
{boardFile.srcFileNm}
|
||||
</button>
|
||||
</dd>
|
||||
))}
|
||||
</dl>
|
||||
)}
|
||||
<div
|
||||
className="community_detail-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: boardDetail.qstContents ? boardDetail.qstContents.replaceAll('\n', '<br/>') : '',
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
{boardDetail?.answerYn === 'Y' && (
|
||||
<div className="oneonone-answer">
|
||||
<div className="answer-title-wrap">
|
||||
<div className="answer-title">Hanwha Japan {getMessage('qna.detail.sub.answer')}</div>
|
||||
<div className="oneonone-infor">
|
||||
<div className="profile">{boardDetail.ansRegNm}</div>
|
||||
<div className="date">{boardDetail.ansRegDt}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="community_detail-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: boardDetail.ansContents ? boardDetail.ansContents.replaceAll('\n', '<br/>') : '',
|
||||
}}
|
||||
></div>
|
||||
{boardDetail.ansListFile && (
|
||||
<dl className="community_detail-file-wrap">
|
||||
<dt>{getMessage('qna.detail.sub.fileList')}</dt>
|
||||
{boardDetail.ansListFile.map((boardFile) => (
|
||||
<dd key={boardFile.encodeFileNo}>
|
||||
<button type="button" className="down" onClick={() => handleFileDown(boardFile.fileNo, 'N')}>
|
||||
{boardFile.srcFileNm}
|
||||
</button>
|
||||
</dd>
|
||||
))}
|
||||
</dl>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
140
src/components/community/modal/QnaFileUploader.jsx
Normal file
140
src/components/community/modal/QnaFileUploader.jsx
Normal file
@ -0,0 +1,140 @@
|
||||
'use client'
|
||||
|
||||
import { useRef } from 'react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
|
||||
export default function QnaFileUploader({ uploadFiles, setUploadFiles, qnaData, setQnaData }) {
|
||||
const fileInputRef = useRef(null)
|
||||
const { getMessage } = useMessage()
|
||||
|
||||
const { swalFire } = useSwal()
|
||||
|
||||
const handleButtonClick = (e) => {
|
||||
e.preventDefault()
|
||||
fileInputRef.current.click()
|
||||
}
|
||||
|
||||
const onChangeFiles = async (e) => {
|
||||
if (e.target.files.length <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const fileList = []
|
||||
let passFlag = true
|
||||
const allowedFileTypes = [
|
||||
'image/',
|
||||
'application/pdf',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation', // PPTX 형식
|
||||
'application/vnd.ms-powerpoint', // PPT 형식
|
||||
]
|
||||
Array.from(e.target.files).forEach((file) => {
|
||||
//엑셀, pdf, 이미지
|
||||
const fileType = file.type
|
||||
if (!allowedFileTypes.some((type) => fileType.includes(type))) {
|
||||
passFlag = false
|
||||
} else {
|
||||
fileList.push({ data: file, id: uuidv4() })
|
||||
}
|
||||
})
|
||||
|
||||
if (!passFlag) {
|
||||
swalFire({ text: getMessage('estimate.detail.fileList.extCheck'), type: 'alert', icon: 'error' })
|
||||
}
|
||||
|
||||
setUploadFiles([...uploadFiles, ...fileList])
|
||||
setQnaData({...qnaData, files:[...uploadFiles, ...fileList]})
|
||||
e.target.value = ''
|
||||
}
|
||||
|
||||
const deleteFile = (id) => {
|
||||
setUploadFiles(uploadFiles.filter((file) => file.id !== id))
|
||||
setQnaData({...qnaData, files:uploadFiles.filter((file) => file.id !== id)})
|
||||
}
|
||||
|
||||
const handleDrop = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const fileList = []
|
||||
let passFlag = true
|
||||
const allowedFileTypes = [
|
||||
'image/',
|
||||
'application/pdf',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation', // PPTX 형식
|
||||
'application/vnd.ms-powerpoint', // PPT 형식
|
||||
]
|
||||
|
||||
Array.from(e.dataTransfer.files).forEach((file) => {
|
||||
//엑셀, pdf, 이미지
|
||||
let fileType = file.type
|
||||
if (!allowedFileTypes.some((type) => fileType.includes(type))) {
|
||||
passFlag = false
|
||||
} else {
|
||||
fileList.push({ data: file, id: uuidv4() })
|
||||
}
|
||||
})
|
||||
|
||||
if (!passFlag) {
|
||||
swalFire({ text: getMessage('estimate.detail.fileList.extCheck'), type: 'alert', icon: 'error' })
|
||||
}
|
||||
|
||||
setUploadFiles([...uploadFiles, ...fileList])
|
||||
setQnaData({...qnaData, files:[...uploadFiles, ...fileList]})
|
||||
}
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
const handleDragEnd = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
const handleDragLeave = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<div className="design-request-grid mt15">
|
||||
<div className="design-request-count">
|
||||
<div className="design-request-grid-tit">{getMessage("qna.reg.header.fileList")}</div>
|
||||
<div className="btn-area one-on-one">
|
||||
<label className="file-upload" htmlFor="img" onClick={handleButtonClick}>
|
||||
Attach File
|
||||
</label>
|
||||
<input type="file" multiple name="file" ref={fileInputRef} style={{ display: 'none' }} onChange={(e) => onChangeFiles(e)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="drag-file-box one-on-one">
|
||||
<div className="drag-file-area"
|
||||
draggable
|
||||
onDrop={(e) => handleDrop(e)}
|
||||
onDragOver={(e) => handleDragOver(e)}
|
||||
onDragEnd={(e) => handleDragEnd(e)}
|
||||
onDragLeave={(e) => handleDragLeave(e)}>
|
||||
<p>Drag file here</p>
|
||||
<ul className="file-list">
|
||||
{uploadFiles.length > 0 &&
|
||||
uploadFiles.map((file) => (
|
||||
<li className="file-item" key={file.id}>
|
||||
<span>
|
||||
{file.data.name} <button className="delete" onClick={() => deleteFile(file.id)}></button>
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
414
src/components/community/modal/QnaRegModal.jsx
Normal file
414
src/components/community/modal/QnaRegModal.jsx
Normal file
@ -0,0 +1,414 @@
|
||||
'use client'
|
||||
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { sessionStore } from '@/store/commonAtom'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import QnaFileUploader from '@/components/community/modal/QnaFileUploader'
|
||||
import { useContext, useEffect, useRef, useState } from 'react'
|
||||
import { useCommonCode } from '@/hooks/common/useCommonCode'
|
||||
import Select from 'react-select'
|
||||
import dayjs from 'dayjs'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
import { QcastContext } from '@/app/QcastProvider'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { globalLocaleStore } from '@/store/localeAtom'
|
||||
import { e } from 'mathjs'
|
||||
|
||||
|
||||
export default function QnaRegModal({ setOpen, setReload, searchValue, selectPageBlock }) {
|
||||
const { getMessage } = useMessage()
|
||||
const [fileList, setFileList] = useState([])
|
||||
const [sessionState, setSessionState] = useRecoilState(sessionStore)
|
||||
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
||||
const [files, setFiles] = useState([])
|
||||
const [qnaData, setQnaData] = useState([])
|
||||
const [closeMdFlg, setCloseMdFlg] = useState(true)
|
||||
const [closeSmFlg, setCloseSmFlg] = useState(true)
|
||||
const qnaTypeLgCodeRef = useRef(null)
|
||||
const qnaTypeMdCodeRef = useRef(null)
|
||||
const qnaTypeSmCodeRef = useRef(null)
|
||||
const regUserNmRef = useRef(null)
|
||||
const regUserTelNoRef = useRef(null)
|
||||
const titleRef = useRef(null)
|
||||
const contentsRef = useRef(null)
|
||||
const { findCommonCode } = useCommonCode()
|
||||
const [qnaTypeLgCodeList, setQnaTypeLgCodeList] = useState([])
|
||||
const [qnaTypeMdCodeList, setQnaTypeMdCodeList] = useState([])
|
||||
const [qnaTypeSmCodeList, setQnaTypeSmCodeList] = useState([])
|
||||
const [phoneNumber, setPhoneNumber] = useState("");
|
||||
const { swalFire } = useSwal()
|
||||
const { setIsGlobalLoading } = useContext(QcastContext)
|
||||
const [isBtnDisable, setIsBtnDisable] = useState(false);
|
||||
const { promiseGet, post, promisePost } = useAxios(globalLocaleState)
|
||||
|
||||
let fileCheck = false;
|
||||
const regPhoneNumber = (e) => {
|
||||
const result = e.target.value
|
||||
.replace(/[^0-9.]/g, "")
|
||||
//.replace(/^(\d{0,3})(\d{0,4})(\d{0,4})$/g, "$1-$2-$3")
|
||||
//.replace(/(-{1,2})$/g, "");
|
||||
//setPhoneNumber(result);
|
||||
|
||||
setQnaData({...qnaData, regUserTelNo: result })
|
||||
}
|
||||
|
||||
const fileUploadProps = {
|
||||
uploadFiles: files,
|
||||
setUploadFiles: setFiles,
|
||||
|
||||
}
|
||||
|
||||
// const fileSave = (qnaData, fileUploadProps) => {
|
||||
// return qnaData.files.push(fileUploadProps.uploadFiles)
|
||||
// }
|
||||
|
||||
const initQnaReg = async () => {
|
||||
|
||||
|
||||
regUserNmRef.current.value = ''
|
||||
regUserTelNoRef.current.value = ''
|
||||
qnaTypeLgCodeRef.current.setValue();
|
||||
qnaTypeMdCodeRef.current.setValue();
|
||||
qnaTypeSmCodeRef.current.setValue();
|
||||
titleRef.current.value = ''
|
||||
contentsRef.current.value = ''
|
||||
|
||||
//setQnaData([])
|
||||
|
||||
setQnaData({
|
||||
...qnaData,
|
||||
compCd: "5200",
|
||||
siteTpCd: "QC",
|
||||
schNoticeClsCd: "QNA",
|
||||
regId: sessionState.userId,
|
||||
storeId: sessionState.userId,
|
||||
qstMail : sessionState.email
|
||||
})
|
||||
|
||||
const codeL = findCommonCode(204200)
|
||||
if (codeL != null) {
|
||||
setQnaTypeLgCodeList(codeL)
|
||||
}
|
||||
setIsGlobalLoading(false)
|
||||
setIsBtnDisable(false);
|
||||
}
|
||||
const onChangeQnaTypeL = (e) => {
|
||||
if(e === undefined || e === null) return;
|
||||
const codeM = findCommonCode(204300)
|
||||
if (codeM != null) {
|
||||
|
||||
let codeList = []
|
||||
|
||||
codeM.map((item) => {
|
||||
|
||||
if(item.clRefChr1 === e.clCode) {
|
||||
codeList.push(item);
|
||||
|
||||
}
|
||||
})
|
||||
setQnaTypeMdCodeList(codeList)
|
||||
setQnaData({ ...qnaData, qnaClsLrgCd:e.clCode})
|
||||
setCloseMdFlg(false)
|
||||
qnaTypeMdCodeRef.current.setValue();
|
||||
qnaTypeSmCodeRef.current.setValue();
|
||||
}
|
||||
|
||||
}
|
||||
const onChangeQnaTypeM = (e) => {
|
||||
|
||||
if(e === undefined || e === null) return;
|
||||
const codeS = findCommonCode(204400)
|
||||
if (codeS != null) {
|
||||
|
||||
let codeList = []
|
||||
|
||||
codeS.map((item) => {
|
||||
|
||||
if (item.clRefChr1 === e.clCode) {
|
||||
codeList.push(item);
|
||||
|
||||
}
|
||||
})
|
||||
setQnaTypeSmCodeList(codeList)
|
||||
setQnaData({ ...qnaData, qnaClsMidCd: e.clCode })
|
||||
setCloseSmFlg(false)
|
||||
qnaTypeSmCodeRef.current.setValue();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
const onChangeQnaTypeS = (e) => {
|
||||
if(e === undefined || e === null) return;
|
||||
setQnaData({ ...qnaData, qnaClsSmlCd:e.clCode})
|
||||
}
|
||||
|
||||
const onFileSave = () => {
|
||||
const formData= []
|
||||
if(fileUploadProps.uploadFiles.length === 0) return;
|
||||
if(!fileCheck) return;
|
||||
|
||||
fileUploadProps.uploadFiles.forEach((file) => {
|
||||
console.log("file::::::::",file)
|
||||
formData.push(file)
|
||||
|
||||
})
|
||||
setQnaData({ ...qnaData, files:formData })
|
||||
fileCheck = false;
|
||||
}
|
||||
|
||||
const handleQnaSubmit = async () => {
|
||||
//필수 체크
|
||||
|
||||
//console.log("1::::",qnaData)
|
||||
|
||||
|
||||
let regUserNm = qnaData?.regUserNm??'';
|
||||
|
||||
if (regUserNm.trim().length === 0) {
|
||||
|
||||
regUserNmRef.current.value = '';
|
||||
regUserNmRef.current.focus()
|
||||
swalFire({
|
||||
text: getMessage('qna.reg.alert.require.regUserNm'),
|
||||
type: 'alert',
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
let qnaClsLrgCd = qnaData?.qnaClsLrgCd??'';
|
||||
let qnaClsMidCd = qnaData?.qnaClsMidCd??'';
|
||||
|
||||
if (qnaClsLrgCd.trim().length === 0 || qnaClsMidCd.trim().length === 0 ) {
|
||||
(qnaClsLrgCd.trim().length === 0)?qnaTypeLgCodeRef.current.focus():qnaTypeMdCodeRef.current.focus()
|
||||
swalFire({
|
||||
text: getMessage('qna.reg.alert.select.type'),
|
||||
type: 'alert',
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
let title = qnaData?.title??'';
|
||||
|
||||
if (title.trim().length === 0) {
|
||||
titleRef.current.value = '';
|
||||
titleRef.current.focus()
|
||||
swalFire({
|
||||
text: getMessage('qna.reg.alert.require.title'),
|
||||
type: 'alert',
|
||||
})
|
||||
return false
|
||||
}
|
||||
//console.log("5::::",qnaData)
|
||||
let contents = qnaData?.contents??'';
|
||||
|
||||
if (contents.trim().length === 0) {
|
||||
contentsRef.current.value = '';
|
||||
contentsRef.current.focus()
|
||||
swalFire({
|
||||
text: getMessage('qna.reg.alert.require.contents'),
|
||||
type: 'alert',
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
const formData = new FormData()
|
||||
if(qnaData?.files?.length > 0) {
|
||||
qnaData?.files.forEach((file) => {
|
||||
formData.append('files', file.data)
|
||||
})
|
||||
}
|
||||
formData.append("compCd", qnaData.compCd)
|
||||
formData.append("siteTpCd", qnaData.siteTpCd)
|
||||
formData.append("qnaClsLrgCd", qnaData.qnaClsLrgCd)
|
||||
formData.append("qnaClsMidCd", qnaData.qnaClsMidCd)
|
||||
formData.append("qnaClsSmlCd", qnaData.qnaClsSmlCd)
|
||||
formData.append("title", qnaData.title)
|
||||
formData.append("contents", qnaData.contents)
|
||||
formData.append("regId", qnaData.regId)
|
||||
formData.append("storeId", qnaData.storeId)
|
||||
formData.append("regUserNm", qnaData.regUserNm)
|
||||
formData.append("regUserTelNo", qnaData.regUserTelNo)
|
||||
formData.append("qstMail", qnaData.qstMail)
|
||||
formData.append("schNoticeClsCd", qnaData.schNoticeClsCd)
|
||||
|
||||
|
||||
|
||||
//console.log(Array.from(formData));
|
||||
|
||||
swalFire({
|
||||
html: getMessage('qna.reg.confirm.save'),
|
||||
type: 'confirm',
|
||||
confirmFn: async () => {
|
||||
|
||||
setIsBtnDisable(true);
|
||||
setIsGlobalLoading(true)
|
||||
|
||||
try {
|
||||
|
||||
const apiUrl = 'api/board'
|
||||
//console.log("7::::",qnaData)
|
||||
await post({ url: `${apiUrl}/saveQna`, data: formData }).then((res) => {
|
||||
if (res?.result.code === 200) {
|
||||
//qnaData.newFileList = []
|
||||
setIsGlobalLoading(false)
|
||||
swalFire({ text: getMessage('qna.reg.alert.save'), type: 'alert' })
|
||||
setOpen(false)
|
||||
setReload(searchValue, selectPageBlock);
|
||||
}else{
|
||||
setIsGlobalLoading(false)
|
||||
swalFire({ text: getMessage('qna.reg.alert.saveFail'), type: 'alert', icon: 'error' })
|
||||
console.error('error::::::::::::', res)
|
||||
}
|
||||
|
||||
setIsBtnDisable(false)
|
||||
})
|
||||
} catch (e) {
|
||||
setIsGlobalLoading(false)
|
||||
setIsBtnDisable(false);
|
||||
console.error('error::::::::::::', e.message)
|
||||
swalFire({ text: e.message, type: 'alert' , icon: 'error'})
|
||||
console.error('error::::::::::::', e.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
initQnaReg()
|
||||
},[])
|
||||
|
||||
// useEffect(() => {
|
||||
// onFileSave()
|
||||
//
|
||||
// }, [onFileSave])
|
||||
return (
|
||||
<div className="modal-popup">
|
||||
<div className="modal-dialog big">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h1 className="title">{getMessage('qna.title')}</h1>
|
||||
<button className="modal-close"
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
}}>{getMessage('board.sub.btn.close')}</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="modal-body-inner">
|
||||
<div className="design-request-table">
|
||||
<div className="common-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style={{ width: '100px' }} />
|
||||
<col />
|
||||
<col style={{ width: '120px' }} />
|
||||
<col />
|
||||
<col style={{ width: '150px' }} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{getMessage('qna.list.header.regNm')}</th>
|
||||
<td><input type="text" className="input-light" value={sessionState?.userNm || ''} readOnly /></td>
|
||||
<th>E-Mail<span className="red">*</span></th>
|
||||
<td ><input type="text" className="input-light" value={sessionState?.email || ''} readOnly /></td>
|
||||
<th>{getMessage('qna.reg.header.regDt')}</th>
|
||||
<td>{dayjs(new Date()).format('YYYY-MM-DD')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{getMessage('qna.reg.header.regUserNm')}<span className="red">*</span></th>
|
||||
<td ><input type="text" className="input-light" required
|
||||
ref={regUserNmRef}
|
||||
value={qnaData?.regUserNm || '' }
|
||||
onChange={(e) => setQnaData({...qnaData, regUserNm: e.target.value })}
|
||||
onBlur={(e) => setQnaData({ ...qnaData, regUserNm: e.target.value })} /> </td>
|
||||
<th>{getMessage('qna.reg.header.regUserTelNo')}</th>
|
||||
<td colSpan={3}><input type="text" className="input-light"
|
||||
ref={regUserTelNoRef}
|
||||
maxLength={13}
|
||||
value={qnaData?.regUserTelNo || '' }
|
||||
onChange={regPhoneNumber}
|
||||
/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="design-request-grid">
|
||||
<div className="design-request-count">
|
||||
<div className="design-request-grid-tit">{getMessage("qna.reg.header.type")}, {getMessage("qna.reg.header.title")} <span
|
||||
className="red">*</span></div>
|
||||
</div>
|
||||
<div className="flx-box one-on-one">
|
||||
<div className="select-wrap mr5" >
|
||||
<Select name="" ref={qnaTypeLgCodeRef}
|
||||
options={qnaTypeLgCodeList}
|
||||
placeholder="Select"
|
||||
onChange={(e) => onChangeQnaTypeL(e)}
|
||||
getOptionLabel={(x) => x.clCodeNm}
|
||||
getOptionValue={(x) => x.clCode}
|
||||
isClearable={false}
|
||||
isSearchable={false}
|
||||
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div className="select-wrap mr5" >
|
||||
<Select name="" ref={qnaTypeMdCodeRef}
|
||||
options={qnaTypeMdCodeList}
|
||||
placeholder="Select"
|
||||
onChange={(e) => onChangeQnaTypeM(e)}
|
||||
getOptionLabel={(x) => x.clCodeNm}
|
||||
getOptionValue={(x) => x.clCode}
|
||||
isClearable={false}
|
||||
isSearchable={false}
|
||||
isDisabled={closeMdFlg}
|
||||
defaultValue={''}
|
||||
/>
|
||||
</div>
|
||||
<div className="select-wrap" >
|
||||
<Select name="" ref={qnaTypeSmCodeRef}
|
||||
options={qnaTypeSmCodeList}
|
||||
placeholder="Select"
|
||||
onChange={(e) => onChangeQnaTypeS(e)}
|
||||
getOptionLabel={(x) => x.clCodeNm}
|
||||
getOptionValue={(x) => x.clCode}
|
||||
isClearable={false}
|
||||
isSearchable={false}
|
||||
isDisabled={closeSmFlg}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="input-wrap mt5">
|
||||
<input type="text" className="input-light" maxLength={200}
|
||||
ref = {titleRef}
|
||||
value={qnaData?.title || '' }
|
||||
onChange={(e) => {setQnaData({ ...qnaData, title: e.target.value })}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="design-request-grid mt15">
|
||||
<div className="design-request-count">
|
||||
<div className="design-request-grid-tit">{getMessage("qna.reg.header.contents")} <span className="red">*</span></div>
|
||||
</div>
|
||||
<div>
|
||||
<textarea className="textarea-form" name="" id="" maxLength={4000}
|
||||
ref={contentsRef}
|
||||
value={qnaData?.contents || '' }
|
||||
onChange={(e) => {setQnaData({ ...qnaData, contents: e.target.value })}} ></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<QnaFileUploader {...fileUploadProps} qnaData={qnaData} setQnaData={setQnaData} />
|
||||
|
||||
</div>
|
||||
<div className="footer-btn-wrap">
|
||||
{isBtnDisable === false && <button className="btn-origin grey mr5" onClick={handleQnaSubmit}>{getMessage("qna.reg.header.save")}</button>}
|
||||
<button className="btn-origin navy" onClick={() => {
|
||||
setOpen(false)
|
||||
}}>{getMessage("board.sub.btn.close")}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -2,7 +2,7 @@ import { fabric } from 'fabric'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util'
|
||||
import { calculateAngle, drawGabledRoof, drawRidgeRoof, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils'
|
||||
import { calculateAngle, drawRidgeRoof, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils'
|
||||
import * as turf from '@turf/turf'
|
||||
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
|
||||
import Big from 'big.js'
|
||||
@ -29,12 +29,13 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
this.texts = []
|
||||
this.hips = []
|
||||
this.ridges = []
|
||||
this.connectRidges = []
|
||||
this.cells = []
|
||||
this.innerLines = []
|
||||
this.children = []
|
||||
this.separatePolygon = []
|
||||
this.toFixed = options.toFixed ?? 1
|
||||
this.baseLines = []
|
||||
// this.colorLines = []
|
||||
|
||||
// 소수점 전부 제거
|
||||
points.forEach((point) => {
|
||||
@ -217,6 +218,12 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
* @param settingModalFirstOptions
|
||||
*/
|
||||
drawHelpLine(settingModalFirstOptions) {
|
||||
/* innerLines 초기화 */
|
||||
this.innerLines.forEach((line) => {
|
||||
this.canvas.remove(line)
|
||||
})
|
||||
this.canvas.renderAll()
|
||||
|
||||
let textMode = 'plane'
|
||||
|
||||
const dimensionDisplay = settingModalFirstOptions?.dimensionDisplay.find((opt) => opt.selected).id
|
||||
@ -246,12 +253,13 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
const hasShed = types.includes(LINE_TYPE.WALLLINE.SHED)
|
||||
|
||||
// A형, B형 박공 지붕
|
||||
if (
|
||||
/* if (
|
||||
(gableOdd.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableEven.every((type) => gableType.includes(type))) ||
|
||||
(gableEven.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableOdd.every((type) => gableType.includes(type)))
|
||||
) {
|
||||
drawGabledRoof(this.id, this.canvas, textMode)
|
||||
} else if (hasShed) {
|
||||
} else*/
|
||||
if (hasShed) {
|
||||
const sheds = this.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.SHED)
|
||||
const areLinesParallel = function (line1, line2) {
|
||||
const angle1 = calculateAngle(line1.startPoint, line1.endPoint)
|
||||
|
||||
@ -12,7 +12,7 @@ import { usePlan } from '@/hooks/usePlan'
|
||||
import { useContextMenu } from '@/hooks/useContextMenu'
|
||||
import { useCanvasConfigInitialize } from '@/hooks/common/useCanvasConfigInitialize'
|
||||
import { currentMenuState } from '@/store/canvasAtom'
|
||||
import { totalDisplaySelector } from '@/store/settingAtom'
|
||||
import { roofMaterialsAtom, totalDisplaySelector } from '@/store/settingAtom'
|
||||
import { MENU, POLYGON_TYPE } from '@/common/common'
|
||||
import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider'
|
||||
import { QcastContext } from '@/app/QcastProvider'
|
||||
@ -30,10 +30,40 @@ import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
|
||||
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
|
||||
import { useEvent } from '@/hooks/useEvent'
|
||||
import { compasDegAtom } from '@/store/orientationAtom'
|
||||
import { ROOF_MATERIAL_LAYOUT } from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting'
|
||||
import { useMasterController } from '@/hooks/common/useMasterController'
|
||||
import { hotkeyStore } from '@/store/hotkeyAtom'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
|
||||
export default function CanvasFrame() {
|
||||
const [roofMaterials, setRoofMaterials] = useRecoilState(roofMaterialsAtom)
|
||||
const { getRoofMaterialList } = useMasterController()
|
||||
useEffect(() => {
|
||||
async function initRoofMaterial() {
|
||||
if (roofMaterials.length !== 0) {
|
||||
return
|
||||
}
|
||||
const { data } = await getRoofMaterialList()
|
||||
|
||||
const roofLists = data.map((item, idx) => ({
|
||||
...item,
|
||||
id: item.roofMatlCd,
|
||||
name: item.roofMatlNm,
|
||||
selected: idx === 0,
|
||||
index: idx,
|
||||
nameJp: item.roofMatlNmJp,
|
||||
length: item.lenBase && parseInt(item.lenBase),
|
||||
width: item.widBase && parseInt(item.widBase),
|
||||
raft: item.raftBase && parseInt(item.raftBase),
|
||||
layout: ['ROOF_ID_SLATE', 'ROOF_ID_SINGLE'].includes(item.roofMatlCd) ? ROOF_MATERIAL_LAYOUT.STAIRS : ROOF_MATERIAL_LAYOUT.PARALLEL,
|
||||
hajebichi: item.roofPchBase && parseInt(item.roofPchBase),
|
||||
pitch: item.pitch ? parseInt(item.pitch) : 4,
|
||||
angle: item.angle ? parseInt(item.angle) : 21.8,
|
||||
}))
|
||||
setRoofMaterials(roofLists)
|
||||
}
|
||||
initRoofMaterial()
|
||||
}, [])
|
||||
const canvasRef = useRef(null)
|
||||
const { canvas } = useCanvas('canvas')
|
||||
const { canvasLoadInit, gridInit } = useCanvasConfigInitialize()
|
||||
|
||||
@ -86,6 +86,26 @@ export default function PassivityCircuitAllocation(props) {
|
||||
.map((obj) => obj.circuitNumber),
|
||||
),
|
||||
]
|
||||
|
||||
const surfaceList = targetModules.map((module) => {
|
||||
return canvas.getObjects().filter((obj) => obj.id === canvas.getObjects().filter((obj) => obj.id === module)[0].surfaceId)[0]
|
||||
})
|
||||
|
||||
if (surfaceList.length > 1) {
|
||||
let surfaceType = {}
|
||||
|
||||
surfaceList.forEach((surface) => {
|
||||
surfaceType[`${surface.direction}-${surface.roofMaterial.pitch}`] = surface
|
||||
})
|
||||
if (Object.keys(surfaceType).length > 1) {
|
||||
swalFire({
|
||||
text: getMessage('module.circuit.fix.not.same.roof.error'),
|
||||
type: 'alert',
|
||||
icon: 'warning',
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!circuitNumber || circuitNumber === 0) {
|
||||
swalFire({
|
||||
text: getMessage('module.circuit.minimun.error'),
|
||||
|
||||
@ -4,9 +4,11 @@ import { usePopup } from '@/hooks/usePopup'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { contextPopupPositionState } from '@/store/popupAtom'
|
||||
import { useState } from 'react'
|
||||
import { currentObjectState } from '@/store/canvasAtom'
|
||||
import { canvasState, currentObjectState } from '@/store/canvasAtom'
|
||||
import { useGrid } from '@/hooks/common/useGrid'
|
||||
|
||||
import { gridColorState } from '@/store/gridAtom'
|
||||
import { gridDisplaySelector } from '@/store/settingAtom'
|
||||
const GRID_PADDING = 5
|
||||
export default function GridCopy(props) {
|
||||
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
|
||||
const { id, pos = contextPopupPosition } = props
|
||||
@ -15,9 +17,39 @@ export default function GridCopy(props) {
|
||||
const [length, setLength] = useState('0')
|
||||
const [arrow, setArrow] = useState(null)
|
||||
const currentObject = useRecoilValue(currentObjectState)
|
||||
const { copy } = useGrid()
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const gridColor = useRecoilValue(gridColorState)
|
||||
const isGridDisplay = useRecoilValue(gridDisplaySelector)
|
||||
const handleApply = () => {
|
||||
copy(currentObject, ['↑', '←'].includes(arrow) ? +length * -1 : +length)
|
||||
copy(currentObject, ['↑', '←'].includes(arrow) ? (+length * -1) / 10 : +length / 10)
|
||||
}
|
||||
|
||||
const copy = (object, length) => {
|
||||
const lineStartX = object.direction === 'vertical' ? object.x1 + length : 0
|
||||
const lineEndX = object.direction === 'vertical' ? object.x2 + length : canvas.width
|
||||
const lineStartY = object.direction === 'vertical' ? 0 : object.y1 + length
|
||||
const lineEndY = object.direction === 'vertical' ? canvas.width : object.y1 + length
|
||||
|
||||
const line = new fabric.Line([lineStartX, lineStartY, lineEndX, lineEndY], {
|
||||
stroke: gridColor,
|
||||
strokeWidth: 1,
|
||||
selectable: true,
|
||||
lockMovementX: true,
|
||||
lockMovementY: true,
|
||||
lockRotation: true,
|
||||
lockScalingX: true,
|
||||
lockScalingY: true,
|
||||
strokeDashArray: [5, 2],
|
||||
opacity: 0.3,
|
||||
padding: GRID_PADDING,
|
||||
direction: object.direction,
|
||||
visible: isGridDisplay,
|
||||
name: object.name,
|
||||
})
|
||||
|
||||
canvas.add(line)
|
||||
canvas.setActiveObject(line)
|
||||
canvas.renderAll()
|
||||
}
|
||||
return (
|
||||
<WithDraggable isShow={true} pos={pos} className="xm">
|
||||
|
||||
@ -6,7 +6,6 @@ import { contextPopupPositionState } from '@/store/popupAtom'
|
||||
import { useCanvas } from '@/hooks/useCanvas'
|
||||
import { canvasState, currentObjectState } from '@/store/canvasAtom'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useGrid } from '@/hooks/common/useGrid'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
import { set } from 'react-hook-form'
|
||||
|
||||
@ -17,7 +16,6 @@ export default function GridMove(props) {
|
||||
const { getMessage } = useMessage()
|
||||
const { closePopup } = usePopup()
|
||||
const { swalFire } = useSwal()
|
||||
const { move } = useGrid()
|
||||
const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
|
||||
const [isAll, setIsAll] = useState(false)
|
||||
const [verticalSize, setVerticalSize] = useState('0')
|
||||
@ -54,21 +52,31 @@ export default function GridMove(props) {
|
||||
.forEach((grid) => {
|
||||
move(
|
||||
grid,
|
||||
arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize),
|
||||
arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize),
|
||||
arrow2 === '←' ? (Number(horizonSize) * -1) / 10 : Number(horizonSize) / 10,
|
||||
arrow1 === '↑' ? (Number(verticalSize) * -1) / 10 : Number(verticalSize) / 10,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
move(
|
||||
currentObject,
|
||||
arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize),
|
||||
arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize),
|
||||
arrow2 === '←' ? (Number(horizonSize) * -1) / 10 : Number(horizonSize) / 10,
|
||||
arrow1 === '↑' ? (Number(verticalSize) * -1) / 10 : Number(verticalSize) / 10,
|
||||
)
|
||||
}
|
||||
canvas.renderAll()
|
||||
handleClose()
|
||||
}
|
||||
|
||||
const move = (object, x, y) => {
|
||||
object.set({
|
||||
...object,
|
||||
x1: object.direction === 'vertical' ? object.x1 + x : 0,
|
||||
x2: object.direction === 'vertical' ? object.x1 + x : canvas.width,
|
||||
y1: object.direction === 'vertical' ? 0 : object.y1 + y,
|
||||
y2: object.direction === 'vertical' ? canvas.height : object.y1 + y,
|
||||
})
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
closePopup(id)
|
||||
}
|
||||
|
||||
@ -15,13 +15,18 @@ export default function FlowLine({ FLOW_LINE_REF }) {
|
||||
const currentObject = useRecoilValue(currentObjectState)
|
||||
const handleFocus = () => {
|
||||
if (currentObject === null) {
|
||||
FLOW_LINE_REF.POINTER_INPUT_REF.current.value = ''
|
||||
FLOW_LINE_REF.FILLED_INPUT_REF.current.value = ''
|
||||
FLOW_LINE_REF.FILLED_INPUT_REF.current.blur()
|
||||
}
|
||||
}
|
||||
const handleInput = (e) => {
|
||||
const value = e.target.value.replace(/^0+/, '')
|
||||
setFilledInput(value.replace(/[^0-9]/g, ''))
|
||||
const regex = /^-?\d*$/
|
||||
let value = e.target.value
|
||||
if (!regex.test(value) && value !== '') return
|
||||
if (value.startsWith('0') || value === '-0') return
|
||||
|
||||
setFilledInput(value.replace(/[^0-9-]/g, ''))
|
||||
}
|
||||
return (
|
||||
<>
|
||||
@ -47,7 +52,7 @@ export default function FlowLine({ FLOW_LINE_REF }) {
|
||||
<div className="eaves-keraba-td">
|
||||
<div className="outline-form">
|
||||
<div className="input-grid mr5" style={{ width: '100px' }}>
|
||||
{/*<input type="text" className="input-origin block" readOnly={true} ref={FLOW_LINE_REF.POINTER_INPUT_REF} />*/}
|
||||
{<input type="text" className="input-origin block" readOnly={true} ref={FLOW_LINE_REF.POINTER_INPUT_REF} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -15,6 +15,7 @@ export default function Updown({ UP_DOWN_REF }) {
|
||||
const currentObject = useRecoilValue(currentObjectState)
|
||||
const handleFocus = () => {
|
||||
if (currentObject === null) {
|
||||
UP_DOWN_REF.POINTER_INPUT_REF.current.value = ''
|
||||
UP_DOWN_REF.FILLED_INPUT_REF.current.value = ''
|
||||
UP_DOWN_REF.FILLED_INPUT_REF.current.blur()
|
||||
}
|
||||
@ -48,7 +49,7 @@ export default function Updown({ UP_DOWN_REF }) {
|
||||
<div className="eaves-keraba-td">
|
||||
<div className="outline-form">
|
||||
<div className="input-grid mr5" style={{ width: '100px' }}>
|
||||
{/*<input type="text" className="input-origin block" readOnly={true} ref={UP_DOWN_REF.UP_INPUT_REF} />*/}
|
||||
{<input type="text" className="input-origin block" readOnly={true} ref={UP_DOWN_REF.POINTER_INPUT_REF} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -105,6 +105,16 @@ export default function GridOption(props) {
|
||||
initEvent()
|
||||
}, [gridOptions])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setAdsorptionPointAddMode(false)
|
||||
setTempGridMode(false)
|
||||
setTimeout(() => {
|
||||
initEvent()
|
||||
}, 100)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const dotLineGridProps = {
|
||||
id: dotLineId,
|
||||
setIsShow: setShowDotLineGridModal,
|
||||
|
||||
@ -185,7 +185,7 @@ export default function SecondOption(props) {
|
||||
|
||||
const onClickOption = async (item) => {
|
||||
let option4Data = settingModalSecondOptions?.option4
|
||||
let adsorpPointData = adsorptionPointMode.adsorptionPoint
|
||||
let adsorpPointData = adsorptionPointMode
|
||||
|
||||
//흡착범위 설정(단 건 선택)
|
||||
if (
|
||||
@ -203,11 +203,9 @@ export default function SecondOption(props) {
|
||||
|
||||
//흡착점 범위
|
||||
setAdsorptionRange(item.range)
|
||||
|
||||
setAdsorptionPointMode({ ...adsorptionPointMode, adsorptionPoint: adsorpPointData })
|
||||
setAdsorptionPointMode(adsorpPointData)
|
||||
} else if (item === 'adsorpPoint') {
|
||||
setAdsorptionPointMode({ ...adsorptionPointMode, adsorptionPoint: !adsorpPointData })
|
||||
adsorpPointData = !adsorpPointData
|
||||
setAdsorptionPointMode(!adsorpPointData)
|
||||
}
|
||||
|
||||
setSettingsData({ ...settingsData, option4: [...option4Data], adsorptionPoint: adsorpPointData })
|
||||
@ -257,7 +255,7 @@ export default function SecondOption(props) {
|
||||
}}
|
||||
>
|
||||
<span>{getMessage('modal.canvas.setting.font.plan.absorption.point')}</span>
|
||||
<i>{adsorptionPointMode.adsorptionPoint ? 'ON' : 'OFF'}</i>
|
||||
<i>{adsorptionPointMode ? 'ON' : 'OFF'}</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -6,16 +6,22 @@ import WithDraggable from '@/components/common/draggable/WithDraggable'
|
||||
import SecondOption from '@/components/floor-plan/modal/setting01/SecondOption'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import GridOption from '@/components/floor-plan/modal/setting01/GridOption'
|
||||
import { canGridOptionSeletor } from '@/store/canvasAtom'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { adsorptionPointAddModeState, canGridOptionSeletor, tempGridModeState } from '@/store/canvasAtom'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
|
||||
import { useTempGrid } from '@/hooks/useTempGrid'
|
||||
import { settingModalGridOptionsState } from '@/store/settingAtom'
|
||||
import { useEvent } from '@/hooks/useEvent'
|
||||
|
||||
export default function SettingModal01(props) {
|
||||
const { id } = props
|
||||
const [buttonAct, setButtonAct] = useState(1)
|
||||
const { getMessage } = useMessage()
|
||||
const canGridOptionSeletorValue = useRecoilValue(canGridOptionSeletor)
|
||||
const [gridOptions, setGridOptions] = useRecoilState(settingModalGridOptionsState)
|
||||
const [tempGridMode, setTempGridMode] = useRecoilState(tempGridModeState)
|
||||
const [adsorptionPointAddMode, setAdsorptionPointAddMode] = useRecoilState(adsorptionPointAddModeState)
|
||||
const { closePopup } = usePopup()
|
||||
|
||||
const {
|
||||
@ -71,9 +77,22 @@ export default function SettingModal01(props) {
|
||||
setButtonAct(num)
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
setTempGridMode(false)
|
||||
setAdsorptionPointAddMode(false)
|
||||
setGridOptions((prev) => {
|
||||
const newSettingOptions = [...prev]
|
||||
newSettingOptions[0].selected = false
|
||||
newSettingOptions[2].selected = false
|
||||
return [...newSettingOptions]
|
||||
})
|
||||
|
||||
closePopup(id, true)
|
||||
}
|
||||
|
||||
return (
|
||||
<WithDraggable isShow={true} pos={{ x: 1275, y: 180 }} className="sm">
|
||||
<WithDraggable.Header title={getMessage('modal.canvas.setting')} onClose={() => closePopup(id, true)} />
|
||||
<WithDraggable.Header title={getMessage('modal.canvas.setting')} onClose={onClose} />
|
||||
<WithDraggable.Body>
|
||||
<div className="modal-btn-wrap">
|
||||
<button className={`btn-frame modal ${buttonAct === 1 ? 'act' : ''}`} onClick={() => handleBtnClick(1)}>
|
||||
|
||||
@ -74,7 +74,7 @@ export default function Offset({ length1Ref, arrow1Ref, currentWallLineRef }) {
|
||||
<div className="eaves-keraba-td">
|
||||
<div className="outline-form">
|
||||
<div className="input-grid mr5" style={{ width: '100px' }}>
|
||||
<input type="text" className="input-origin block" defaultValue={0} ref={length1Ref} />
|
||||
<input type="text" className="input-origin block" placeholder={0} ref={length1Ref} />
|
||||
</div>
|
||||
<span className="thin">mm</span>
|
||||
</div>
|
||||
|
||||
@ -46,7 +46,7 @@ export default forwardRef(function WallLine({ length1Ref, length2Ref, arrow1Ref,
|
||||
<div className="eaves-keraba-td">
|
||||
<div className="outline-form">
|
||||
<div className="input-grid mr5" style={{ width: '100px' }}>
|
||||
<input type="text" className="input-origin block" defaultValue={0} readOnly={type !== 1} ref={length1Ref} />
|
||||
<input type="text" className="input-origin block" placeholder={0} readOnly={type !== 1} ref={length1Ref} />
|
||||
</div>
|
||||
<span className="thin">mm</span>
|
||||
</div>
|
||||
@ -80,7 +80,7 @@ export default forwardRef(function WallLine({ length1Ref, length2Ref, arrow1Ref,
|
||||
<div className="eaves-keraba-td">
|
||||
<div className="outline-form">
|
||||
<div className="input-grid mr5" style={{ width: '100px' }}>
|
||||
<input type="text" className="input-origin block" defaultValue={0} readOnly={type !== 2} ref={length2Ref} />
|
||||
<input type="text" className="input-origin block" placeholder={0} readOnly={type !== 2} ref={length2Ref} />
|
||||
</div>
|
||||
<span className="thin">mm</span>
|
||||
</div>
|
||||
|
||||
@ -142,6 +142,7 @@ export default function Header(props) {
|
||||
{ id: 7, name: 'header.menus.community.archive', url: '/community/archive', children: [] },
|
||||
],
|
||||
},
|
||||
{ id: 8, name: 'qna.title', url: '/community/qna', children: [] },
|
||||
]
|
||||
|
||||
const syncSession = useCallback(() => {
|
||||
@ -199,7 +200,7 @@ export default function Header(props) {
|
||||
onClick={() => {
|
||||
// moveHome()
|
||||
removeStuffRecoil(menu)
|
||||
if (pathName === '/') {
|
||||
if (pathName === '/' && menu.id !== 8) {
|
||||
window.location.reload()
|
||||
}
|
||||
}}
|
||||
|
||||
@ -1362,10 +1362,11 @@ export default function StuffDetail() {
|
||||
}
|
||||
|
||||
if (params?.receiveUser !== '') {
|
||||
if (checkLength(params?.receiveUser.trim()) > 10) {
|
||||
if (checkLength(params?.receiveUser.trim()) > 40) {
|
||||
return swalFire({ text: getMessage('stuff.detail.tempSave.message2'), type: 'alert', icon: 'warning' })
|
||||
}
|
||||
}
|
||||
|
||||
//로그인이 2차점인데 otherSaleStoreId가 없으면 알럿
|
||||
if (session.storeLvl !== '1') {
|
||||
if (params.saleStoreLevel === '1') {
|
||||
@ -1435,20 +1436,15 @@ export default function StuffDetail() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 전각20자 (반각40자)
|
||||
*/
|
||||
const checkLength = (value) => {
|
||||
let str = new String(value)
|
||||
let _byte = 0
|
||||
if (str.length !== 0) {
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
let str2 = str.charAt(i)
|
||||
if (encodeURIComponent(str2).length > 4) {
|
||||
_byte += 2
|
||||
} else {
|
||||
_byte++
|
||||
}
|
||||
}
|
||||
}
|
||||
return _byte
|
||||
let fullWidthLength = value.replace(/[^\u3000-\u9FFF\uFF01-\uFF5E]/g, '').length
|
||||
let halfWidthLength = value.replace(/[\u3000-\u9FFF\uFF01-\uFF5E]/g, '').length
|
||||
|
||||
let totalLength = fullWidthLength * 2 + halfWidthLength
|
||||
return totalLength
|
||||
}
|
||||
// 임시저장
|
||||
const onTempSave = async () => {
|
||||
@ -1498,7 +1494,7 @@ export default function StuffDetail() {
|
||||
|
||||
// 담당자 자리수 체크
|
||||
if (params?.receiveUser !== '') {
|
||||
if (checkLength(params?.receiveUser.trim()) > 10) {
|
||||
if (checkLength(params?.receiveUser.trim()) > 40) {
|
||||
return swalFire({ text: getMessage('stuff.detail.tempSave.message2'), type: 'alert', icon: 'warning' })
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,6 +66,11 @@ export default function FindAddressPop(props) {
|
||||
zipcode: watch('zipNo'),
|
||||
}
|
||||
|
||||
if (params.zipcode.length < 7) {
|
||||
swalFire({ text: getMessage('stuff.addressPopup.error.message1'), type: 'alert', icon: 'warning' })
|
||||
return
|
||||
}
|
||||
|
||||
setIsGlobalLoading(true)
|
||||
get({ url: `https://zipcloud.ibsnet.co.jp/api/search?${queryStringFormatter(params)}` }).then((res) => {
|
||||
if (res.status === 200) {
|
||||
@ -85,15 +90,20 @@ export default function FindAddressPop(props) {
|
||||
}
|
||||
// 주소적용 클릭
|
||||
const applyAddress = () => {
|
||||
if (prefId == null) {
|
||||
if (!isNotEmptyArray(gridProps.gridData)) {
|
||||
swalFire({ text: getMessage('stuff.addressPopup.error.message2'), type: 'alert', icon: 'warning' })
|
||||
return
|
||||
}
|
||||
if (gridProps.gridData[0].zipcode == '') {
|
||||
swalFire({ text: getMessage('stuff.addressPopup.error.message2'), type: 'alert', icon: 'warning' })
|
||||
} else {
|
||||
//검색결과 무조건 1:1
|
||||
props.zipInfo({
|
||||
zipNo: zipNo,
|
||||
address1: address1,
|
||||
address2: address2,
|
||||
address3: address3,
|
||||
prefId: prefId,
|
||||
zipNo: gridProps.gridData[0].zipcode,
|
||||
address1: gridProps.gridData[0].address1,
|
||||
address2: gridProps.gridData[0].address2,
|
||||
address3: gridProps.gridData[0].address3,
|
||||
prefId: gridProps.gridData[0].prefcode,
|
||||
})
|
||||
|
||||
//팝업닫기
|
||||
|
||||
@ -9,7 +9,7 @@ import { globalFontAtom } from '@/store/fontAtom'
|
||||
import { useRoof } from '@/hooks/common/useRoof'
|
||||
import { usePolygon } from '@/hooks/usePolygon'
|
||||
import { useRoofFn } from '@/hooks/common/useRoofFn'
|
||||
import { POLYGON_TYPE } from '@/common/common'
|
||||
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
|
||||
|
||||
export function useCanvasConfigInitialize() {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
@ -64,6 +64,7 @@ export function useCanvasConfigInitialize() {
|
||||
groupDimensionInit()
|
||||
reGroupInit() //그룹 객체 재그룹
|
||||
moduleInit()
|
||||
innerLinesInit() // innerLines 세팅 추가
|
||||
}
|
||||
|
||||
const gridInit = () => {
|
||||
@ -228,5 +229,23 @@ export function useCanvasConfigInitialize() {
|
||||
})
|
||||
}
|
||||
|
||||
const innerLinesInit = () => {
|
||||
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
|
||||
// innerLine이 세팅이 안되어있는경우 찾아서 세팅한다.
|
||||
let innerLineTypes = Object.keys(LINE_TYPE.SUBLINE).map((key, value) => LINE_TYPE.SUBLINE[key])
|
||||
|
||||
roofs.forEach((roof) => {
|
||||
roof.innerLines = canvas
|
||||
.getObjects()
|
||||
.filter(
|
||||
(obj) =>
|
||||
obj.type === 'QLine' &&
|
||||
obj.attributes?.type !== 'pitchSizeLine' &&
|
||||
obj.attributes?.roofId === roof.id &&
|
||||
innerLineTypes.includes(obj.name),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return { canvasLoadInit, gridInit }
|
||||
}
|
||||
|
||||
@ -153,18 +153,18 @@ export function useGrid() {
|
||||
const move = (object, x, y) => {
|
||||
object.set({
|
||||
...object,
|
||||
x1: object.direction === 'vertical' ? object.x1 + x : 0,
|
||||
x2: object.direction === 'vertical' ? object.x1 + x : canvas.width,
|
||||
y1: object.direction === 'vertical' ? 0 : object.y1 + y,
|
||||
y2: object.direction === 'vertical' ? canvas.height : object.y1 + y,
|
||||
x1: object.direction === 'vertical' ? object.x1 + x : -1500,
|
||||
x2: object.direction === 'vertical' ? object.x1 + x : 2500,
|
||||
y1: object.direction === 'vertical' ? -1500 : object.y1 + y,
|
||||
y2: object.direction === 'vertical' ? 2500 : object.y1 + y,
|
||||
})
|
||||
}
|
||||
|
||||
const copy = (object, length) => {
|
||||
const lineStartX = object.direction === 'vertical' ? object.x1 + length : 0
|
||||
const lineEndX = object.direction === 'vertical' ? object.x2 + length : canvas.width
|
||||
const lineStartY = object.direction === 'vertical' ? 0 : object.y1 + length
|
||||
const lineEndY = object.direction === 'vertical' ? canvas.width : object.y1 + length
|
||||
const lineStartX = object.direction === 'vertical' ? object.x1 + length : -1500
|
||||
const lineEndX = object.direction === 'vertical' ? object.x2 + length : 2500
|
||||
const lineStartY = object.direction === 'vertical' ? -1500 : object.y1 + length
|
||||
const lineEndY = object.direction === 'vertical' ? 2500 : object.y1 + length
|
||||
|
||||
const line = new fabric.Line([lineStartX, lineStartY, lineEndX, lineEndY], {
|
||||
stroke: gridColor,
|
||||
|
||||
@ -23,102 +23,65 @@ export function useRoofFn() {
|
||||
|
||||
//면형상 선택 클릭시 지붕 패턴 입히기
|
||||
function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false, roofMaterial, isForceChange = false, isDisplay = false) {
|
||||
if (!polygon) {
|
||||
return
|
||||
}
|
||||
if (polygon.points.length < 3) {
|
||||
return
|
||||
}
|
||||
if (isForceChange && !isDisplay) {
|
||||
/*if (polygon.roofMaterial) {
|
||||
try {
|
||||
if (!polygon) {
|
||||
return
|
||||
}
|
||||
if (polygon.points.length < 3) {
|
||||
return
|
||||
}
|
||||
if (isForceChange && !isDisplay) {
|
||||
/*if (polygon.roofMaterial) {
|
||||
polygon.roofMaterial = null
|
||||
}*/
|
||||
}
|
||||
if (!roofMaterial) {
|
||||
roofMaterial = polygon.roofMaterial ?? selectedRoofMaterial
|
||||
}
|
||||
|
||||
const ratio = window.devicePixelRatio || 1
|
||||
const layout = roofMaterial.layout
|
||||
|
||||
let width = (roofMaterial.width || 226) / 10
|
||||
let height = (roofMaterial.length || 158) / 10
|
||||
const index = roofMaterial.index ?? 0
|
||||
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')
|
||||
let 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 = mode === 'allPainted' ? 'black' : ROOF_COLOR[index]
|
||||
ctx.lineWidth = 2
|
||||
ctx.fillStyle = mode === 'allPainted' ? 'rgba(0, 159, 64, 0.7)' : 'white'
|
||||
|
||||
if (trestleMode) {
|
||||
ctx.strokeStyle = 'black'
|
||||
ctx.lineWidth = 0.2
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'
|
||||
} else {
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 1)'
|
||||
}
|
||||
|
||||
if (polygon.direction === 'east' || polygon.direction === 'west') {
|
||||
offset = roofStyle === 1 ? 0 : patternSize.height / 2
|
||||
for (let col = 0; col <= cols; col++) {
|
||||
const x = col * patternSize.width
|
||||
const yStart = 0
|
||||
const yEnd = patternSourceCanvas.height
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x, yStart) // 선 시작점
|
||||
ctx.lineTo(x, yEnd) // 선 끝점
|
||||
ctx.stroke()
|
||||
if (mode === 'allPainted' || trestleMode) {
|
||||
ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart)
|
||||
}
|
||||
|
||||
for (let row = 0; row <= rows; row++) {
|
||||
const y = layout === ROOF_MATERIAL_LAYOUT.STAIRS ? row * patternSize.height + (col % 2 === 0 ? 0 : offset) : row * patternSize.height
|
||||
const xStart = col * patternSize.width
|
||||
const xEnd = xStart + patternSize.width
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(xStart, y) // 선 시작점
|
||||
ctx.lineTo(xEnd, y) // 선 끝점
|
||||
ctx.stroke()
|
||||
if (mode === 'allPainted' || trestleMode) {
|
||||
ctx.fillRect(xStart, y, xEnd - xStart, patternSize.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let row = 0; row <= rows; row++) {
|
||||
const y = row * patternSize.height
|
||||
if (!roofMaterial) {
|
||||
roofMaterial = polygon.roofMaterial ?? selectedRoofMaterial
|
||||
}
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(0, y) // 선 시작점
|
||||
ctx.lineTo(patternSourceCanvas.width, y) // 선 끝점
|
||||
ctx.stroke()
|
||||
if (mode === 'allPainted' || trestleMode) {
|
||||
ctx.fillRect(0, y, patternSourceCanvas.width, patternSize.height)
|
||||
}
|
||||
const ratio = window.devicePixelRatio || 1
|
||||
const layout = roofMaterial.layout
|
||||
|
||||
let width = (roofMaterial.width || 226) / 10
|
||||
let height = (roofMaterial.length || 158) / 10
|
||||
const index = roofMaterial.index ?? 0
|
||||
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')
|
||||
let 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 = mode === 'allPainted' ? 'black' : ROOF_COLOR[index]
|
||||
ctx.lineWidth = 2
|
||||
ctx.fillStyle = mode === 'allPainted' ? 'rgba(0, 159, 64, 0.7)' : 'white'
|
||||
|
||||
if (trestleMode) {
|
||||
ctx.strokeStyle = 'black'
|
||||
ctx.lineWidth = 0.2
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'
|
||||
} else {
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 1)'
|
||||
}
|
||||
|
||||
if (polygon.direction === 'east' || polygon.direction === 'west') {
|
||||
offset = roofStyle === 1 ? 0 : patternSize.height / 2
|
||||
for (let col = 0; col <= cols; col++) {
|
||||
const x = layout === ROOF_MATERIAL_LAYOUT.STAIRS ? col * patternSize.width + (row % 2 === 0 ? 0 : offset) : col * patternSize.width
|
||||
const yStart = row * patternSize.height
|
||||
const yEnd = yStart + patternSize.height
|
||||
|
||||
const x = col * patternSize.width
|
||||
const yStart = 0
|
||||
const yEnd = patternSourceCanvas.height
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x, yStart) // 선 시작점
|
||||
ctx.lineTo(x, yEnd) // 선 끝점
|
||||
@ -126,49 +89,90 @@ export function useRoofFn() {
|
||||
if (mode === 'allPainted' || trestleMode) {
|
||||
ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart)
|
||||
}
|
||||
|
||||
for (let row = 0; row <= rows; row++) {
|
||||
const y = layout === ROOF_MATERIAL_LAYOUT.STAIRS ? row * patternSize.height + (col % 2 === 0 ? 0 : offset) : row * patternSize.height
|
||||
const xStart = col * patternSize.width
|
||||
const xEnd = xStart + patternSize.width
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(xStart, y) // 선 시작점
|
||||
ctx.lineTo(xEnd, y) // 선 끝점
|
||||
ctx.stroke()
|
||||
if (mode === 'allPainted' || trestleMode) {
|
||||
ctx.fillRect(xStart, y, xEnd - xStart, patternSize.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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()
|
||||
if (mode === 'allPainted' || trestleMode) {
|
||||
ctx.fillRect(0, y, patternSourceCanvas.width, patternSize.height)
|
||||
}
|
||||
|
||||
for (let col = 0; col <= cols; col++) {
|
||||
const x = layout === ROOF_MATERIAL_LAYOUT.STAIRS ? col * patternSize.width + (row % 2 === 0 ? 0 : offset) : col * patternSize.width
|
||||
const yStart = row * patternSize.height
|
||||
const yEnd = yStart + patternSize.height
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x, yStart) // 선 시작점
|
||||
ctx.lineTo(x, yEnd) // 선 끝점
|
||||
ctx.stroke()
|
||||
if (mode === 'allPainted' || trestleMode) {
|
||||
ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hachingPatternSourceCanvas = document.createElement('canvas')
|
||||
const hachingPatternSourceCanvas = document.createElement('canvas')
|
||||
|
||||
if (mode === 'lineHatch') {
|
||||
hachingPatternSourceCanvas.width = polygon.width * ratio
|
||||
hachingPatternSourceCanvas.height = polygon.height * ratio
|
||||
if (mode === 'lineHatch') {
|
||||
hachingPatternSourceCanvas.width = polygon.width * ratio
|
||||
hachingPatternSourceCanvas.height = polygon.height * ratio
|
||||
|
||||
const ctx1 = hachingPatternSourceCanvas.getContext('2d')
|
||||
const ctx1 = hachingPatternSourceCanvas.getContext('2d')
|
||||
|
||||
const gap = 10
|
||||
const gap = 10
|
||||
|
||||
ctx1.strokeStyle = 'green' // 선 색상
|
||||
ctx1.lineWidth = 0.3 // 선 두께
|
||||
ctx1.strokeStyle = 'green' // 선 색상
|
||||
ctx1.lineWidth = 0.3 // 선 두께
|
||||
|
||||
for (let x = 0; x < hachingPatternSourceCanvas.width + hachingPatternSourceCanvas.height; x += gap) {
|
||||
ctx1.beginPath()
|
||||
ctx1.moveTo(x, 0) // 선 시작점
|
||||
ctx1.lineTo(0, x) // 선 끝점
|
||||
ctx1.stroke()
|
||||
for (let x = 0; x < hachingPatternSourceCanvas.width + hachingPatternSourceCanvas.height; x += gap) {
|
||||
ctx1.beginPath()
|
||||
ctx1.moveTo(x, 0) // 선 시작점
|
||||
ctx1.lineTo(0, x) // 선 끝점
|
||||
ctx1.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
const combinedPatternCanvas = document.createElement('canvas')
|
||||
combinedPatternCanvas.width = polygon.width * ratio
|
||||
combinedPatternCanvas.height = polygon.height * ratio
|
||||
const combinedCtx = combinedPatternCanvas.getContext('2d')
|
||||
// 첫 번째 패턴을 그린 후 두 번째 패턴을 덧입힘
|
||||
combinedCtx.drawImage(patternSourceCanvas, 0, 0)
|
||||
combinedCtx.drawImage(hachingPatternSourceCanvas, 0, 0)
|
||||
|
||||
// 패턴 생성
|
||||
const pattern = new fabric.Pattern({
|
||||
source: combinedPatternCanvas,
|
||||
repeat: 'repeat',
|
||||
})
|
||||
|
||||
polygon.set('fill', null)
|
||||
polygon.set('fill', pattern)
|
||||
polygon.roofMaterial = roofMaterial
|
||||
polygon.canvas?.renderAll()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
const combinedPatternCanvas = document.createElement('canvas')
|
||||
combinedPatternCanvas.width = polygon.width * ratio
|
||||
combinedPatternCanvas.height = polygon.height * ratio
|
||||
const combinedCtx = combinedPatternCanvas.getContext('2d')
|
||||
// 첫 번째 패턴을 그린 후 두 번째 패턴을 덧입힘
|
||||
combinedCtx.drawImage(patternSourceCanvas, 0, 0)
|
||||
combinedCtx.drawImage(hachingPatternSourceCanvas, 0, 0)
|
||||
|
||||
// 패턴 생성
|
||||
const pattern = new fabric.Pattern({
|
||||
source: combinedPatternCanvas,
|
||||
repeat: 'repeat',
|
||||
})
|
||||
|
||||
polygon.set('fill', null)
|
||||
polygon.set('fill', pattern)
|
||||
polygon.roofMaterial = roofMaterial
|
||||
polygon.canvas?.renderAll()
|
||||
}
|
||||
|
||||
function removeRoofMaterial(roof = currentObject) {
|
||||
|
||||
@ -449,7 +449,7 @@ export const useEstimateController = (planNo, flag) => {
|
||||
icon: 'warning',
|
||||
})
|
||||
} else {
|
||||
if (checkLength(copyReceiveUser.trim()) > 10) {
|
||||
if (checkLength(copyReceiveUser.trim()) > 40) {
|
||||
return swalFire({ text: getMessage('stuff.detail.tempSave.message2'), type: 'alert', icon: 'warning' })
|
||||
}
|
||||
}
|
||||
@ -489,20 +489,15 @@ export const useEstimateController = (planNo, flag) => {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 전각20자 (반각40자)
|
||||
*/
|
||||
const checkLength = (value) => {
|
||||
let str = new String(value)
|
||||
let _byte = 0
|
||||
if (str.length !== 0) {
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
let str2 = str.charAt(i)
|
||||
if (encodeURIComponent(str2).length > 4) {
|
||||
_byte += 2
|
||||
} else {
|
||||
_byte++
|
||||
}
|
||||
}
|
||||
}
|
||||
return _byte
|
||||
let fullWidthLength = value.replace(/[^\u3000-\u9FFF\uFF01-\uFF5E]/g, '').length
|
||||
let halfWidthLength = value.replace(/[\u3000-\u9FFF\uFF01-\uFF5E]/g, '').length
|
||||
|
||||
let totalLength = fullWidthLength * 2 + halfWidthLength
|
||||
return totalLength
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -441,7 +441,7 @@ export const useTrestle = () => {
|
||||
drawRacks(rightRacks, rackQty, rackIntvlPct / 3, module, direction, 'R', rackYn)
|
||||
}
|
||||
}
|
||||
module.set({ leftRows, rightRows, centerRows })
|
||||
module.set({ leftRows, rightRows, centerRows, leftFindModuleList, rightFindModuleList, centerFindModuleList })
|
||||
})
|
||||
// 왼쪽아래에 모듈이 없는 모듈들
|
||||
leftExposedHalfBottomModules.forEach((module) => {
|
||||
@ -531,7 +531,7 @@ export const useTrestle = () => {
|
||||
drawRacks(leftRacks, rackQty, rackIntvlPct, module, direction, 'L', rackYn)
|
||||
}
|
||||
|
||||
module.set({ leftRows })
|
||||
module.set({ leftRows, leftFindModuleList: findModuleList })
|
||||
})
|
||||
// 오른쪽 아래에 모듈이 없는 모듈들
|
||||
rightExposedHalfBottomPoints.forEach((module) => {
|
||||
@ -612,7 +612,7 @@ export const useTrestle = () => {
|
||||
drawRacks(rightRacks, rackQty, rackIntvlPct, module, direction, 'R', rackYn)
|
||||
}
|
||||
|
||||
module.set({ rightRows })
|
||||
module.set({ rightRows, rightFindModuleList: findModuleList })
|
||||
})
|
||||
|
||||
surface.set({ moduleRowsTotCnt: mostRowsModule })
|
||||
@ -1547,7 +1547,6 @@ export const useTrestle = () => {
|
||||
|
||||
canvas.renderAll()
|
||||
exposedBottomModules.forEach((module) => {
|
||||
canvas.renderAll()
|
||||
drawBracketWithOutRack(module, rackIntvlPct, module.leftRows + 1, 'L', surface.direction, moduleIntvlHor, moduleIntvlVer)
|
||||
drawBracketWithOutRack(module, rackIntvlPct, module.rightRows + 1, 'R', surface.direction, moduleIntvlHor, moduleIntvlVer)
|
||||
if (!isChidory && rackQty === 3) {
|
||||
@ -1627,6 +1626,8 @@ export const useTrestle = () => {
|
||||
|
||||
// 랙 없음의 지지금구를 그린다.
|
||||
const drawBracketWithOutRack = (module, rackIntvlPct, count, l, direction, moduleIntvlHor, moduleIntvlVer) => {
|
||||
const { leftFindModuleList, rightFindModuleList, centerFindModuleList } = module
|
||||
|
||||
let { width, height, left, top } = module
|
||||
let startPointX
|
||||
let startPointY
|
||||
@ -1723,6 +1724,39 @@ export const useTrestle = () => {
|
||||
}
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
let moduleWidth, moduleHeight
|
||||
|
||||
switch (l) {
|
||||
case 'L': {
|
||||
let leftFindModule = leftFindModuleList[i]
|
||||
if (!leftFindModule) {
|
||||
leftFindModule = leftFindModuleList[leftFindModuleList.length - 1]
|
||||
}
|
||||
moduleWidth = leftFindModule.width
|
||||
moduleHeight = leftFindModule.height
|
||||
break
|
||||
}
|
||||
case 'R': {
|
||||
let rightFindModule = rightFindModuleList[i]
|
||||
if (!rightFindModule) {
|
||||
rightFindModule = rightFindModuleList[rightFindModuleList.length - 1]
|
||||
}
|
||||
moduleWidth = rightFindModule.width
|
||||
moduleHeight = rightFindModule.height
|
||||
break
|
||||
}
|
||||
case 'C': {
|
||||
let centerFindModule = centerFindModuleList[i]
|
||||
if (!centerFindModule) {
|
||||
centerFindModule = centerFindModuleList[centerFindModuleList.length - 1]
|
||||
}
|
||||
|
||||
moduleWidth = centerFindModule.width
|
||||
moduleHeight = centerFindModule.height
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const bracket = new fabric.Rect({
|
||||
left: startPointX,
|
||||
top: startPointY,
|
||||
@ -1739,13 +1773,13 @@ export const useTrestle = () => {
|
||||
canvas.renderAll()
|
||||
const maxIntvl = Math.max(moduleIntvlHor, moduleIntvlVer)
|
||||
if (direction === 'south') {
|
||||
startPointY -= height + maxIntvl / 10
|
||||
startPointY -= moduleHeight + maxIntvl / 10
|
||||
} else if (direction === 'north') {
|
||||
startPointY += height + maxIntvl / 10
|
||||
startPointY += moduleHeight + maxIntvl / 10
|
||||
} else if (direction === 'east') {
|
||||
startPointX -= width + maxIntvl / 10
|
||||
startPointX -= moduleWidth + maxIntvl / 10
|
||||
} else if (direction === 'west') {
|
||||
startPointX += width + maxIntvl / 10
|
||||
startPointX += moduleWidth + maxIntvl / 10
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ export function useCanvasSetting(executeEffect = true) {
|
||||
}
|
||||
|
||||
/** 초 1회만 실행하도록 처리 */
|
||||
addRoofMaterials()
|
||||
//addRoofMaterials()
|
||||
}, [])
|
||||
|
||||
/**
|
||||
@ -609,7 +609,7 @@ export function useCanvasSetting(executeEffect = true) {
|
||||
const optionData5 = settingModalFirstOptions.dimensionDisplay.map((item) => ({ ...item }))
|
||||
|
||||
/** 흡착점 ON/OFF */
|
||||
setAdsorptionPointMode({ ...adsorptionPointMode, adsorptionPoint: res.adsorpPoint })
|
||||
setAdsorptionPointMode(res.adsorpPoint)
|
||||
|
||||
/** 치수선 설정 */
|
||||
setDimensionLineSettings({ ...dimensionLineSettings, pixel: res.originPixel, color: res.originColor })
|
||||
@ -695,7 +695,7 @@ export function useCanvasSetting(executeEffect = true) {
|
||||
/** 조회된 글꼴 데이터가 없는 경우 (데이터 초기화) */
|
||||
|
||||
/** 흡착점 ON/OFF */
|
||||
setAdsorptionPointMode({ ...adsorptionPointMode, adsorptionPoint: false })
|
||||
setAdsorptionPointMode(false)
|
||||
|
||||
/** 치수선 설정 */
|
||||
resetDimensionLineSettings()
|
||||
@ -775,7 +775,7 @@ export function useCanvasSetting(executeEffect = true) {
|
||||
adsorpRangeMedium: dataToSend.secondOption2[2].selected,
|
||||
adsorpRangeLarge: dataToSend.secondOption2[3].selected,
|
||||
/** 흡착점 ON/OFF */
|
||||
adsorpPoint: adsorptionPointMode.adsorptionPoint,
|
||||
adsorpPoint: adsorptionPointMode,
|
||||
//??: adsorptionRange, 사용여부 확인 필요
|
||||
|
||||
/** 문자 글꼴 설정 */
|
||||
|
||||
@ -675,7 +675,7 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) {
|
||||
const distance1 = distanceBetweenPoints({ x: line1.x1, y: line1.y1 }, interSectionPointsWithRoofLines[0])
|
||||
const distance2 = distanceBetweenPoints({ x: line1.x2, y: line1.y2 }, interSectionPointsWithRoofLines[0])
|
||||
|
||||
if (!(distance1 === 0 || distance2 === 0)) {
|
||||
if (!(distance1 < 1 || distance2 < 1)) {
|
||||
if (distance1 >= distance2) {
|
||||
const newLine = addLine([line1.x1, line1.y1, interSectionPointsWithRoofLines[0].x, interSectionPointsWithRoofLines[0].y], {
|
||||
stroke: 'black',
|
||||
@ -704,6 +704,7 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) {
|
||||
removeLine(line1)
|
||||
}
|
||||
intersectionPoints.current.push(interSectionPointsWithRoofLines[0])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -722,7 +723,7 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) {
|
||||
const distance1 = distanceBetweenPoints({ x: line1.x1, y: line1.y1 }, intersectionPoint)
|
||||
const distance2 = distanceBetweenPoints({ x: line1.x2, y: line1.y2 }, intersectionPoint)
|
||||
|
||||
if (distance1 === 0 || distance2 === 0) {
|
||||
if (distance1 < 1 || distance2 < 1) {
|
||||
return
|
||||
}
|
||||
//historyLine에서 기존 line을 제거한다.
|
||||
@ -849,8 +850,14 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) {
|
||||
if (
|
||||
lineHistory.current.some(
|
||||
(history) =>
|
||||
JSON.stringify(history.startPoint) === JSON.stringify(line.startPoint) &&
|
||||
JSON.stringify(history.endPoint) === JSON.stringify(line.endPoint),
|
||||
(Math.abs(history.startPoint.x - line.startPoint.x) < 2 &&
|
||||
Math.abs(history.startPoint.y - line.startPoint.y) < 2 &&
|
||||
Math.abs(history.endPoint.x - line.endPoint.x) < 2 &&
|
||||
Math.abs(history.endPoint.y - line.endPoint.y) < 2) ||
|
||||
(Math.abs(history.startPoint.x - line.endPoint.x) < 2 &&
|
||||
Math.abs(history.startPoint.y - line.endPoint.y) < 2 &&
|
||||
Math.abs(history.endPoint.x - line.startPoint.x) < 2 &&
|
||||
Math.abs(history.endPoint.y - line.startPoint.y) < 2),
|
||||
)
|
||||
) {
|
||||
canvas.remove(line)
|
||||
|
||||
@ -51,7 +51,7 @@ export function useEavesGableEdit(id) {
|
||||
|
||||
useEffect(() => {
|
||||
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||
if (!outerLineFix || outerLines.length === 0) {
|
||||
if (outerLines.length === 0) {
|
||||
swalFire({ text: getMessage('wall.line.not.found') })
|
||||
closePopup(id)
|
||||
}
|
||||
@ -59,6 +59,11 @@ export function useEavesGableEdit(id) {
|
||||
|
||||
useEffect(() => {
|
||||
const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL)
|
||||
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||
wallLines.forEach((wallLine) => {
|
||||
const id = wallLine.id
|
||||
wallLine.lines = outerLines.filter((line) => line.attributes?.wallId === id)
|
||||
})
|
||||
wallLines.forEach((wallLine) => {
|
||||
convertPolygonToLines(wallLine)
|
||||
})
|
||||
@ -84,6 +89,7 @@ export function useEavesGableEdit(id) {
|
||||
e.target.set({
|
||||
stroke: 'red',
|
||||
})
|
||||
e.target.bringToFront()
|
||||
canvas.renderAll()
|
||||
} else {
|
||||
canvas
|
||||
@ -93,6 +99,7 @@ export function useEavesGableEdit(id) {
|
||||
line.set({
|
||||
stroke: 'black',
|
||||
})
|
||||
line.bringToFront()
|
||||
})
|
||||
}
|
||||
canvas.renderAll()
|
||||
@ -111,12 +118,14 @@ export function useEavesGableEdit(id) {
|
||||
case TYPES.EAVES:
|
||||
if (radioTypeRef.current === '1') {
|
||||
attributes = {
|
||||
...attributes,
|
||||
type: LINE_TYPE.WALLLINE.EAVES,
|
||||
pitch: currentAngleType === ANGLE_TYPE.SLOPE ? pitchRef.current.value : getChonByDegree(pitchRef.current.value),
|
||||
offset: offsetRef.current.value / 10,
|
||||
}
|
||||
} else {
|
||||
attributes = {
|
||||
...attributes,
|
||||
type: LINE_TYPE.WALLLINE.HIPANDGABLE,
|
||||
pitch: currentAngleType === ANGLE_TYPE.SLOPE ? pitchRef.current.value : getChonByDegree(pitchRef.current.value),
|
||||
offset: offsetRef.current.value / 10,
|
||||
@ -127,11 +136,13 @@ export function useEavesGableEdit(id) {
|
||||
case TYPES.GABLE:
|
||||
if (radioTypeRef.current === '1') {
|
||||
attributes = {
|
||||
...attributes,
|
||||
type: LINE_TYPE.WALLLINE.GABLE,
|
||||
offset: offsetRef.current.value / 10,
|
||||
}
|
||||
} else {
|
||||
attributes = {
|
||||
...attributes,
|
||||
type: LINE_TYPE.WALLLINE.JERKINHEAD,
|
||||
pitch: currentAngleType === ANGLE_TYPE.SLOPE ? pitchRef.current.value : getChonByDegree(pitchRef.current.value),
|
||||
offset: offsetRef.current.value / 10,
|
||||
@ -142,11 +153,13 @@ export function useEavesGableEdit(id) {
|
||||
case TYPES.WALL_MERGE:
|
||||
if (radioTypeRef.current === '1') {
|
||||
attributes = {
|
||||
...attributes,
|
||||
type: LINE_TYPE.WALLLINE.WALL,
|
||||
offset: 0,
|
||||
}
|
||||
} else {
|
||||
attributes = {
|
||||
...attributes,
|
||||
type: LINE_TYPE.WALLLINE.WALL,
|
||||
offset: offsetRef.current.value / 10,
|
||||
}
|
||||
@ -154,6 +167,7 @@ export function useEavesGableEdit(id) {
|
||||
break
|
||||
case TYPES.SHED:
|
||||
attributes = {
|
||||
...attributes,
|
||||
type: LINE_TYPE.WALLLINE.SHED,
|
||||
offset: offsetRef.current.value / 10,
|
||||
}
|
||||
@ -181,7 +195,6 @@ export function useEavesGableEdit(id) {
|
||||
wallLines.forEach((wallLine) => {
|
||||
addPitchTextsByOuterLines()
|
||||
const roof = drawRoofPolygon(wallLine)
|
||||
|
||||
canvas?.renderAll()
|
||||
roof.drawHelpLine(settingModalFirstOptions)
|
||||
})
|
||||
@ -223,7 +236,7 @@ export function useEavesGableEdit(id) {
|
||||
polygon.set({ visible: true })
|
||||
polygon.lines.forEach((line) => {
|
||||
line.set({ visible: false })
|
||||
line.set({ selectable: false })
|
||||
// line.set({ selectable: false })
|
||||
})
|
||||
|
||||
canvas?.renderAll()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -33,6 +33,7 @@ import { usePopup } from '@/hooks/usePopup'
|
||||
import PropertiesSetting from '@/components/floor-plan/modal/outerlinesetting/PropertiesSetting'
|
||||
import Big from 'big.js'
|
||||
import RoofShapeSetting from '@/components/floor-plan/modal/roofShape/RoofShapeSetting'
|
||||
import { useObject } from '@/hooks/useObject'
|
||||
|
||||
//외벽선 그리기
|
||||
export function useOuterLineWall(id, propertiesId) {
|
||||
@ -57,6 +58,7 @@ export function useOuterLineWall(id, propertiesId) {
|
||||
const { addLine, removeLine } = useLine()
|
||||
const { tempGridMode } = useTempGrid()
|
||||
const { addPolygonByLines } = usePolygon()
|
||||
const { handleSelectableObjects } = useObject()
|
||||
|
||||
const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState)
|
||||
const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState)
|
||||
@ -91,14 +93,16 @@ export function useOuterLineWall(id, propertiesId) {
|
||||
const isFix = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (adsorptionPointAddMode || tempGridMode) {
|
||||
return
|
||||
}
|
||||
|
||||
addCanvasMouseEventListener('mouse:down', mouseDown)
|
||||
addDocumentEventListener('contextmenu', document, (e) => {
|
||||
handleRollback()
|
||||
})
|
||||
handleSelectableObjects(['lineGrid', 'tempGrid', 'adsorptionPoint'], false)
|
||||
|
||||
clear()
|
||||
return () => {
|
||||
initEvent()
|
||||
handleSelectableObjects(['lineGrid', 'tempGrid', 'adsorptionPoint'], true)
|
||||
|
||||
canvas
|
||||
.getObjects()
|
||||
@ -144,7 +148,6 @@ export function useOuterLineWall(id, propertiesId) {
|
||||
const mouseDown = (e) => {
|
||||
let pointer = getIntersectMousePoint(e)
|
||||
pointer = { x: Big(pointer.x).round(1).toNumber(), y: Big(pointer.y).round(1).toNumber() }
|
||||
console.log('mouseDown', pointer, points)
|
||||
|
||||
if (points.length === 0) {
|
||||
setPoints((prev) => [...prev, pointer])
|
||||
@ -152,14 +155,11 @@ export function useOuterLineWall(id, propertiesId) {
|
||||
const lastPoint = points[points.length - 1]
|
||||
let newPoint = { x: pointer.x, y: pointer.y }
|
||||
const length = distanceBetweenPoints(lastPoint, newPoint)
|
||||
console.log('length', length)
|
||||
if (verticalHorizontalMode) {
|
||||
const vector = {
|
||||
x: Big(pointer.x).minus(Big(points[points.length - 1].x)),
|
||||
y: Big(pointer.y).minus(Big(points[points.length - 1].y)),
|
||||
}
|
||||
// const slope = Math.abs(vector.y / vector.x) // 기울기 계산
|
||||
console.log('vector', vector.x.toNumber(), vector.y.toNumber(), Math.abs(vector.y.toNumber() / vector.x.toNumber()) >= 1)
|
||||
const slope = vector.x.eq(0) ? Big(1) : vector.y.div(vector.x).abs() // 기울기 계산
|
||||
|
||||
let scaledVector
|
||||
@ -168,13 +168,11 @@ export function useOuterLineWall(id, propertiesId) {
|
||||
// 기울기가 1 이상이면 x축 방향으로 그림
|
||||
scaledVector = {
|
||||
x: 0,
|
||||
// y: vector.y >= 0 ? Number(length) : -Number(length),
|
||||
y: vector.y.gte(0) ? Big(length).toNumber() : Big(length).neg().toNumber(),
|
||||
}
|
||||
} else {
|
||||
// 기울기가 1 미만이면 y축 방향으로 그림
|
||||
scaledVector = {
|
||||
// x: vector.x >= 0 ? Number(length) : -Number(length),
|
||||
x: vector.x.gte(0) ? Big(length).toNumber() : Big(length).neg().toNumber(),
|
||||
y: 0,
|
||||
}
|
||||
@ -183,8 +181,6 @@ export function useOuterLineWall(id, propertiesId) {
|
||||
const verticalLength = scaledVector.y
|
||||
const horizontalLength = scaledVector.x
|
||||
|
||||
console.log('verticalLength', verticalLength, 'horizontalLength', horizontalLength)
|
||||
|
||||
newPoint = {
|
||||
x: Big(lastPoint.x).plus(horizontalLength).toNumber(),
|
||||
y: Big(lastPoint.y).plus(verticalLength).toNumber(),
|
||||
@ -690,6 +686,7 @@ export function useOuterLineWall(id, propertiesId) {
|
||||
if (points.length === 0) {
|
||||
return
|
||||
}
|
||||
enterCheck(e)
|
||||
// 포커스가 length1에 있지 않으면 length1에 포커스를 줌
|
||||
const activeElem = document.activeElement
|
||||
if (activeElem !== length1Ref.current) {
|
||||
@ -754,6 +751,7 @@ export function useOuterLineWall(id, propertiesId) {
|
||||
if (points.length === 0) {
|
||||
return
|
||||
}
|
||||
enterCheck(e)
|
||||
const key = e.key
|
||||
|
||||
const activeElem = document.activeElement
|
||||
@ -787,6 +785,7 @@ export function useOuterLineWall(id, propertiesId) {
|
||||
if (points.length === 0) {
|
||||
return
|
||||
}
|
||||
enterCheck(e)
|
||||
const key = e.key
|
||||
switch (key) {
|
||||
case 'Down': // IE/Edge에서 사용되는 값
|
||||
@ -812,6 +811,7 @@ export function useOuterLineWall(id, propertiesId) {
|
||||
if (points.length === 0) {
|
||||
return
|
||||
}
|
||||
enterCheck(e)
|
||||
const key = e.key
|
||||
switch (key) {
|
||||
case 'Enter': {
|
||||
@ -836,7 +836,7 @@ export function useOuterLineWall(id, propertiesId) {
|
||||
if (points.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
enterCheck(e)
|
||||
const key = e.key
|
||||
switch (key) {
|
||||
case 'Down': // IE/Edge에서 사용되는 값
|
||||
@ -877,8 +877,6 @@ export function useOuterLineWall(id, propertiesId) {
|
||||
|
||||
const firstPoint = points[0]
|
||||
|
||||
console.log('points 좌표 : ', points)
|
||||
|
||||
points.forEach((point, idx) => {
|
||||
if (idx === 0 || !isAllRightAngle) {
|
||||
return
|
||||
@ -902,6 +900,12 @@ export function useOuterLineWall(id, propertiesId) {
|
||||
isFix.current = true
|
||||
}
|
||||
|
||||
const enterCheck = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleFix()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
points,
|
||||
setPoints,
|
||||
|
||||
@ -214,7 +214,6 @@ export function useRoofAllocationSetting(id) {
|
||||
await post({ url: `/api/canvas-management/roof-allocation-settings`, data: patternData }).then((res) => {
|
||||
swalFire({ text: getMessage(res.returnMessage) })
|
||||
setIsGlobalLoading(false)
|
||||
saveCanvas(false)
|
||||
})
|
||||
|
||||
//Recoil 설정
|
||||
@ -357,7 +356,7 @@ export function useRoofAllocationSetting(id) {
|
||||
roofBases.forEach((roof) => {
|
||||
if (roof.separatePolygon.length === 0) {
|
||||
roof.innerLines.forEach((line) => {
|
||||
if (!line.attributes.actualSize || line.attributes?.actualSize === 0) {
|
||||
if ((!line.attributes.actualSize || line.attributes?.actualSize === 0) && line.length > 1) {
|
||||
line.set({ strokeWidth: 4, stroke: 'black', selectable: true })
|
||||
result = true
|
||||
}
|
||||
@ -384,6 +383,7 @@ export function useRoofAllocationSetting(id) {
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
canvas.discardActiveObject()
|
||||
return
|
||||
}
|
||||
|
||||
@ -429,7 +429,7 @@ export function useRoofAllocationSetting(id) {
|
||||
setRoofMaterials(newRoofList)
|
||||
setRoofsStore(newRoofList)
|
||||
/** 외곽선 삭제 */
|
||||
const removeTargets = canvas.getObjects().filter((obj) => obj.name === 'outerLinePoint' || obj.name === 'outerLine')
|
||||
const removeTargets = canvas.getObjects().filter((obj) => obj.name === 'outerLinePoint' || obj.name === 'outerLine' || obj.name === 'pitchText')
|
||||
removeTargets.forEach((obj) => {
|
||||
canvas.remove(obj)
|
||||
})
|
||||
|
||||
@ -79,13 +79,6 @@ export function useRoofShapeSetting(id) {
|
||||
}, [jerkinHeadPitch])
|
||||
|
||||
useEffect(() => {
|
||||
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||
// if (!outerLineFix || outerLines.length === 0) {
|
||||
// swalFire({ text: '외벽선이 없습니다.' })
|
||||
// // setShowRoofShapeSettingModal(false)
|
||||
// closePopup(id)
|
||||
// }
|
||||
|
||||
return () => {
|
||||
if (!isFixRef.current) {
|
||||
return
|
||||
@ -93,6 +86,7 @@ export function useRoofShapeSetting(id) {
|
||||
|
||||
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||
const pitchTexts = canvas.getObjects().filter((obj) => obj.name === 'pitchText')
|
||||
|
||||
canvas.remove(...pitchTexts)
|
||||
outerLines.forEach((line) => {
|
||||
let stroke, strokeWidth
|
||||
@ -114,6 +108,7 @@ export function useRoofShapeSetting(id) {
|
||||
})
|
||||
|
||||
addPitchText(line)
|
||||
line.bringToFront()
|
||||
}
|
||||
})
|
||||
canvas.renderAll()
|
||||
@ -131,14 +126,6 @@ export function useRoofShapeSetting(id) {
|
||||
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,
|
||||
@ -187,7 +174,7 @@ export function useRoofShapeSetting(id) {
|
||||
]
|
||||
|
||||
const handleSave = () => {
|
||||
let outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine' && obj.visible)
|
||||
let outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||
let direction
|
||||
|
||||
if (outerLines.length < 2) {
|
||||
|
||||
@ -6,6 +6,8 @@ import { useEvent } from '@/hooks/useEvent'
|
||||
import { useLine } from '@/hooks/useLine'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
import Big from 'big.js'
|
||||
import { outerLineFixState } from '@/store/outerLineAtom'
|
||||
|
||||
// 외벽선 편집 및 오프셋
|
||||
export function useWallLineOffsetSetting(id) {
|
||||
@ -28,6 +30,8 @@ export function useWallLineOffsetSetting(id) {
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const outerLineFix = useRecoilValue(outerLineFixState)
|
||||
|
||||
const drawLine = (point1, point2, idx, direction = currentWallLineRef.current.direction) => {
|
||||
const line = addLine([point1.x, point1.y, point2.x, point2.y], {
|
||||
stroke: 'black',
|
||||
@ -59,6 +63,7 @@ export function useWallLineOffsetSetting(id) {
|
||||
|
||||
useEffect(() => {
|
||||
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||
|
||||
if (outerLines.length === 0) {
|
||||
swalFire({ text: getMessage('wall.line.not.found') })
|
||||
closePopup(id)
|
||||
@ -277,7 +282,7 @@ export function useWallLineOffsetSetting(id) {
|
||||
}
|
||||
}
|
||||
|
||||
rearrangeOuterLine(currentIdx + 1)
|
||||
reArrangeOuterLine(currentIdx + 1)
|
||||
|
||||
drawLine(point1, point2, currentIdx)
|
||||
drawLine(point2, point3, currentIdx + 1)
|
||||
@ -286,229 +291,217 @@ export function useWallLineOffsetSetting(id) {
|
||||
canvas.remove(currentWallLineRef.current)
|
||||
currentWallLineRef.current = null
|
||||
canvas.renderAll()
|
||||
|
||||
canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.name === 'outerLine')
|
||||
.forEach((obj) => obj.fire('modified'))
|
||||
}
|
||||
|
||||
const rearrangeOuterLine = (idxParam) => {
|
||||
/**
|
||||
* outreLine의 index를 조절한다.
|
||||
* @param idxParam
|
||||
* @param isNegative
|
||||
*/
|
||||
const reArrangeOuterLine = (idxParam, isNegative = false) => {
|
||||
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||
outerLines.forEach((outerLine) => {
|
||||
if (outerLine.idx >= idxParam) {
|
||||
outerLine.idx = outerLine.idx + 1
|
||||
outerLine.idx = isNegative ? outerLine.idx - 1 : outerLine.idx + 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* offset 저장
|
||||
*/
|
||||
const handleOffsetSave = () => {
|
||||
const direction = currentWallLineRef.current.direction
|
||||
let canDirections = direction === 'left' || direction === 'right' ? ['up', 'down'] : ['left', 'right']
|
||||
const currentIdx = currentWallLineRef.current.idx
|
||||
if (!currentObject) return
|
||||
const currentLine = currentObject
|
||||
const currentVector = currentLine.y1 === currentLine.y2 ? 'horizontal' : 'vertical'
|
||||
const canDirections = currentVector === 'horizontal' ? ['up', 'down'] : ['left', 'right']
|
||||
if (!canDirections.includes(arrow1Ref.current)) {
|
||||
alert('방향을 다시 선택하세요')
|
||||
return
|
||||
}
|
||||
|
||||
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||
outerLines.sort((a, b) => a.idx - b.idx)
|
||||
|
||||
const idx = currentWallLineRef.current.idx
|
||||
const prevIdx = idx - 1 <= 0 ? outerLines.length : idx - 1
|
||||
const nextIdx = idx + 1 > outerLines.length ? 1 : idx + 1
|
||||
const currentIdx = currentLine.idx
|
||||
const prevIdx = currentIdx - 1 <= 0 ? outerLines.length : currentIdx - 1
|
||||
const nextIdx = currentLine.idx + 1 > outerLines.length ? 1 : currentIdx + 1
|
||||
|
||||
const currentLine = currentWallLineRef.current
|
||||
const prevLine = outerLines.find((line) => line.idx === prevIdx)
|
||||
const nextLine = outerLines.find((line) => line.idx === nextIdx)
|
||||
const prevVector = prevLine.y1 === prevLine.y2 ? 'horizontal' : 'vertical'
|
||||
const nextVector = nextLine.y1 === nextLine.y2 ? 'horizontal' : 'vertical'
|
||||
|
||||
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 offsetLength = Big(Number(length1Ref.current.value)).div(10)
|
||||
if (offsetLength.eq(0)) return
|
||||
|
||||
const newPoint1 = { x: newX, y: currentLineY - length }
|
||||
const newPoint2 = { x: prevLine.x2, y: prevLine.y2 }
|
||||
rearrangeOuterLine(currentIdx)
|
||||
drawLine(newPoint1, newPoint2, currentIdx, 'top')
|
||||
const currentLineMinX = Big(Math.max(currentLine.x1, currentLine.x2))
|
||||
const currentLineMaxX = Big(Math.max(currentLine.x1, currentLine.x2))
|
||||
const currentLineMinY = Big(Math.max(currentLine.y1, currentLine.y2))
|
||||
const currentLineMaxY = Big(Math.max(currentLine.y1, currentLine.y2))
|
||||
|
||||
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 })
|
||||
}
|
||||
if (currentVector === 'horizontal') {
|
||||
const prevX = currentLine.x1 < currentLine.x2 ? Math.min(currentLine.x1, currentLine.x2) : Math.max(currentLine.x1, currentLine.x2)
|
||||
const nextX = currentLine.x1 < currentLine.x2 ? Math.max(currentLine.x1, currentLine.x2) : Math.min(currentLine.x1, currentLine.x2)
|
||||
if (arrow1Ref.current === 'up') {
|
||||
currentLine.set({ y1: currentLineMaxY.minus(offsetLength).toNumber(), y2: currentLineMaxY.minus(offsetLength).toNumber() })
|
||||
if (prevVector === currentVector) {
|
||||
const point1 = { x: prevX, y: prevLine.y2 }
|
||||
const point2 = { x: prevX, y: currentLine.y1 }
|
||||
reArrangeOuterLine(currentIdx)
|
||||
drawLine(point1, point2, currentIdx, arrow1Ref.current)
|
||||
} else {
|
||||
if (Math.abs(currentLineY - prevLine.y1) < 2) {
|
||||
prevLine.set({ y1: prevLine.y1 - length })
|
||||
if (Big(prevLine.y1).minus(currentLineMinY).abs().lte(Big(prevLine.y2).minus(currentLineMinY).abs())) {
|
||||
prevLine.set({ y1: currentLine.y1 })
|
||||
} else {
|
||||
prevLine.set({ y2: prevLine.y2 - length })
|
||||
prevLine.set({ y2: currentLine.y1 })
|
||||
}
|
||||
if (Math.abs(currentLineY - nextLine.y1) < 2) {
|
||||
nextLine.set({ y1: nextLine.y1 - length })
|
||||
} else {
|
||||
nextLine.set({ y2: nextLine.y2 - length })
|
||||
if (Big(prevLine.y1).minus(Big(prevLine.y2)).eq(0)) {
|
||||
reArrangeOuterLine(currentIdx - 1, true)
|
||||
canvas.remove(prevLine)
|
||||
}
|
||||
}
|
||||
if (nextVector === currentVector) {
|
||||
const point1 = { x: nextX, y: nextLine.y2 }
|
||||
const point2 = { x: nextX, y: currentLine.y1 }
|
||||
reArrangeOuterLine(currentIdx + 1)
|
||||
drawLine(point1, point2, currentIdx + 1, arrow1Ref.current)
|
||||
} else {
|
||||
if (Big(nextLine.y1).minus(currentLineMaxY).abs().lte(Big(nextLine.y2).minus(currentLineMaxY).abs())) {
|
||||
nextLine.set({ y1: currentLine.y1 })
|
||||
} else {
|
||||
nextLine.set({ y2: currentLine.y1 })
|
||||
}
|
||||
if (Big(nextLine.y1).minus(Big(nextLine.y2)).eq(0)) {
|
||||
reArrangeOuterLine(currentIdx + 1, true)
|
||||
canvas.remove(nextLine)
|
||||
}
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
if (arrow1Ref.current === 'down') {
|
||||
currentLine.set({ y1: currentLineMaxY.plus(offsetLength).toNumber(), y2: currentLineMaxY.plus(offsetLength).toNumber() })
|
||||
if (prevVector === currentVector) {
|
||||
const point1 = { x: prevX, y: prevLine.y2 }
|
||||
const point2 = { x: prevX, y: currentLine.y1 }
|
||||
reArrangeOuterLine(currentIdx)
|
||||
drawLine(point1, point2, currentIdx, arrow1Ref.current)
|
||||
} else {
|
||||
if (Math.abs(currentLineY - prevLine.y1) < 2) {
|
||||
prevLine.set({ y1: prevLine.y1 + length })
|
||||
if (Big(prevLine.y1).minus(currentLineMinY).abs().lte(Big(prevLine.y2).minus(currentLineMinY).abs())) {
|
||||
prevLine.set({ y1: currentLine.y1 })
|
||||
} else {
|
||||
prevLine.set({ y2: prevLine.y2 + length })
|
||||
prevLine.set({ y2: currentLine.y1 })
|
||||
}
|
||||
if (Math.abs(currentLineY - nextLine.y1) < 2) {
|
||||
nextLine.set({ y1: nextLine.y1 + length })
|
||||
} else {
|
||||
nextLine.set({ y2: nextLine.y2 + length })
|
||||
if (Big(prevLine.y1).minus(Big(prevLine.y2)).eq(0)) {
|
||||
reArrangeOuterLine(currentIdx - 1, true)
|
||||
canvas.remove(prevLine)
|
||||
}
|
||||
}
|
||||
if (nextVector === currentVector) {
|
||||
const point1 = { x: nextX, y: nextLine.y2 }
|
||||
const point2 = { x: nextX, y: currentLine.y1 }
|
||||
reArrangeOuterLine(currentIdx + 1)
|
||||
drawLine(point1, point2, currentIdx + 1, arrow1Ref.current)
|
||||
} else {
|
||||
if (Big(nextLine.y1).minus(currentLineMaxY).abs().lte(Big(nextLine.y2).minus(currentLineMaxY).abs())) {
|
||||
nextLine.set({ y1: currentLine.y1 })
|
||||
} else {
|
||||
nextLine.set({ y2: currentLine.y1 })
|
||||
}
|
||||
if (Big(nextLine.y1).minus(Big(nextLine.y2)).eq(0)) {
|
||||
reArrangeOuterLine(currentIdx + 1, true)
|
||||
canvas.remove(nextLine)
|
||||
}
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
} else {
|
||||
const prevY = currentLine.y1 < currentLine.y2 ? Math.min(currentLine.y1, currentLine.y2) : Math.max(currentLine.y1, currentLine.y2)
|
||||
const nextY = currentLine.y1 < currentLine.y2 ? Math.max(currentLine.y1, currentLine.y2) : Math.min(currentLine.y1, currentLine.y2)
|
||||
if (arrow1Ref.current === 'left') {
|
||||
currentLine.set({ x1: currentLineMaxX.minus(offsetLength).toNumber(), x2: currentLineMaxX.minus(offsetLength).toNumber() })
|
||||
|
||||
if (Math.abs(currentLineX - nextLine.x1) < 2) {
|
||||
nextLine.set({ x1: nextLine.x1 - length })
|
||||
if (prevVector === currentVector) {
|
||||
const point1 = { x: prevLine.x2, y: prevY }
|
||||
const point2 = { x: currentLine.x1, y: prevY }
|
||||
reArrangeOuterLine(currentIdx)
|
||||
drawLine(point1, point2, currentIdx, arrow1Ref.current)
|
||||
} else {
|
||||
if (Big(prevLine.x1).minus(currentLineMinX).abs().lte(Big(prevLine.x2).minus(currentLineMinX).abs())) {
|
||||
prevLine.set({ x1: currentLine.x1 })
|
||||
} else {
|
||||
nextLine.set({ x2: nextLine.x2 - length })
|
||||
prevLine.set({ x2: currentLine.x1 })
|
||||
}
|
||||
if (Big(prevLine.x1).minus(Big(prevLine.x2)).eq(0)) {
|
||||
reArrangeOuterLine(currentIdx - 1, true)
|
||||
canvas.remove(prevLine)
|
||||
}
|
||||
}
|
||||
if (nextVector === currentVector) {
|
||||
const point1 = { x: currentLine.x2, y: nextY }
|
||||
const point2 = { x: nextLine.x1, y: nextY }
|
||||
reArrangeOuterLine(currentIdx + 1)
|
||||
drawLine(point1, point2, currentIdx + 1, arrow1Ref.current)
|
||||
} else {
|
||||
if (Big(nextLine.x1).minus(currentLineMaxX).abs().lte(Big(nextLine.x2).minus(currentLineMaxX).abs())) {
|
||||
nextLine.set({ x1: currentLine.x2 })
|
||||
} else {
|
||||
nextLine.set({ x2: currentLine.x2 })
|
||||
}
|
||||
if (Big(nextLine.x1).minus(Big(nextLine.x2)).eq(0)) {
|
||||
reArrangeOuterLine(currentIdx + 1, true)
|
||||
canvas.remove(nextLine)
|
||||
}
|
||||
}
|
||||
|
||||
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 (arrow1Ref.current === 'right') {
|
||||
currentLine.set({ x1: currentLineMaxX.plus(offsetLength).toNumber(), x2: currentLineMaxX.plus(offsetLength).toNumber() })
|
||||
|
||||
if (Math.abs(currentLineX - prevLine.x1) < 2) {
|
||||
prevLine.set({ x1: currentLineX + length })
|
||||
} else {
|
||||
prevLine.set({ x2: currentLineX + length })
|
||||
}
|
||||
if (prevVector === currentVector) {
|
||||
const point1 = { x: prevLine.x2, y: prevY }
|
||||
const point2 = { x: currentLine.x1, y: prevY }
|
||||
reArrangeOuterLine(currentIdx)
|
||||
drawLine(point1, point2, currentIdx, arrow1Ref.current)
|
||||
} else {
|
||||
if (Math.abs(currentLineX - prevLine.x1) < 2) {
|
||||
prevLine.set({ x1: prevLine.x1 + length })
|
||||
if (Big(prevLine.x1).minus(currentLineMinX).abs().lte(Big(prevLine.x2).minus(currentLineMinX).abs())) {
|
||||
prevLine.set({ x1: currentLine.x1 })
|
||||
} else {
|
||||
prevLine.set({ x2: prevLine.x2 + length })
|
||||
prevLine.set({ x2: currentLine.x1 })
|
||||
}
|
||||
if (Math.abs(currentLineX - nextLine.x1) < 2) {
|
||||
nextLine.set({ x1: nextLine.x1 + length })
|
||||
} else {
|
||||
nextLine.set({ x2: nextLine.x2 + length })
|
||||
|
||||
if (Big(prevLine.x1).minus(Big(prevLine.x2)).eq(0)) {
|
||||
reArrangeOuterLine(currentIdx - 1, true)
|
||||
canvas.remove(prevLine)
|
||||
}
|
||||
}
|
||||
if (nextVector === currentVector) {
|
||||
const point1 = { x: currentLine.x2, y: nextY }
|
||||
const point2 = { x: nextLine.x1, y: nextY }
|
||||
reArrangeOuterLine(currentIdx + 1)
|
||||
drawLine(point1, point2, currentIdx + 1, arrow1Ref.current)
|
||||
} else {
|
||||
if (Big(nextLine.x1).minus(currentLineMaxX).abs().lte(Big(nextLine.x2).minus(currentLineMaxX).abs())) {
|
||||
nextLine.set({ x1: currentLine.x2 })
|
||||
} else {
|
||||
nextLine.set({ x2: currentLine.x2 })
|
||||
}
|
||||
|
||||
currentLine.set({ x1: currentLine.x1 + length, x2: currentLine.x2 + length })
|
||||
|
||||
break
|
||||
if (Big(nextLine.x1).minus(Big(nextLine.x2)).eq(0)) {
|
||||
reArrangeOuterLine(currentIdx + 1, true)
|
||||
canvas.remove(nextLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newOuterLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||
newOuterLines.sort((a, b) => a.idx - b.idx)
|
||||
newOuterLines.forEach((line, idx) => {
|
||||
line.fire('modified')
|
||||
})
|
||||
|
||||
canvas.renderAll()
|
||||
}
|
||||
|
||||
|
||||
@ -38,28 +38,32 @@ import { roofDisplaySelector } from '@/store/settingAtom'
|
||||
import { useRoofFn } from '@/hooks/common/useRoofFn'
|
||||
import PlacementSurfaceLineProperty from '@/components/floor-plan/modal/placementShape/PlacementSurfaceLineProperty'
|
||||
import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint'
|
||||
import { useObject } from '@/hooks/useObject'
|
||||
|
||||
// 배치면 그리기
|
||||
export function usePlacementShapeDrawing(id) {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const roofDisplay = useRecoilValue(roofDisplaySelector)
|
||||
const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseLine } =
|
||||
useEvent()
|
||||
const {
|
||||
initEvent,
|
||||
addCanvasMouseEventListener,
|
||||
addDocumentEventListener,
|
||||
removeAllMouseEventListeners,
|
||||
removeAllDocumentEventListeners,
|
||||
removeMouseLine,
|
||||
} = useEvent()
|
||||
// const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent } =
|
||||
// useContext(EventContext)
|
||||
const { getIntersectMousePoint } = useMouse()
|
||||
const { addLine, removeLine } = useLine()
|
||||
const { addPolygonByLines, drawDirectionArrow } = usePolygon()
|
||||
const { tempGridMode } = useTempGrid()
|
||||
const { setSurfaceShapePattern } = useRoofFn()
|
||||
const { changeSurfaceLineType } = useSurfaceShapeBatch({})
|
||||
const { handleSelectableObjects } = useObject()
|
||||
|
||||
const canvasSetting = useRecoilValue(canvasSettingState)
|
||||
const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState)
|
||||
const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState)
|
||||
const adsorptionPointMode = useRecoilValue(adsorptionPointModeState)
|
||||
const adsorptionRange = useRecoilValue(adsorptionRangeState)
|
||||
const interval = useRecoilValue(dotLineIntervalSelector) // 가로 세로 간격
|
||||
const adsorptionPointMode = useRecoilValue(adsorptionPointModeState)
|
||||
|
||||
const length1Ref = useRef(null)
|
||||
const length2Ref = useRef(null)
|
||||
@ -87,13 +91,17 @@ export function usePlacementShapeDrawing(id) {
|
||||
const globalPitch = useRecoilValue(globalPitchState)
|
||||
|
||||
useEffect(() => {
|
||||
if (adsorptionPointAddMode || tempGridMode) {
|
||||
return
|
||||
}
|
||||
|
||||
addCanvasMouseEventListener('mouse:down', mouseDown)
|
||||
addDocumentEventListener('contextmenu', document, (e) => {
|
||||
handleRollback()
|
||||
})
|
||||
handleSelectableObjects(['lineGrid', 'tempGrid', 'adsorptionPoint'], false)
|
||||
clear()
|
||||
}, [verticalHorizontalMode, points, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, interval, tempGridMode])
|
||||
return () => {
|
||||
initEvent()
|
||||
handleSelectableObjects(['lineGrid', 'tempGrid', 'adsorptionPoint'], true)
|
||||
}
|
||||
}, [verticalHorizontalMode, points, adsorptionRange, adsorptionPointMode])
|
||||
|
||||
useEffect(() => {
|
||||
setPoints([])
|
||||
@ -119,7 +127,6 @@ export function usePlacementShapeDrawing(id) {
|
||||
}, [type])
|
||||
|
||||
const clear = () => {
|
||||
addCanvasMouseEventListener('mouse:move', mouseMove)
|
||||
setLength1(0)
|
||||
setLength2(0)
|
||||
|
||||
@ -175,80 +182,6 @@ export function usePlacementShapeDrawing(id) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
mouseMove
|
||||
*/
|
||||
const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
|
||||
const roofAdsorptionPoints = useRef([])
|
||||
const intersectionPoints = useRef([])
|
||||
const { getAdsorptionPoints } = useAdsorptionPoint()
|
||||
|
||||
const mouseMove = (e) => {
|
||||
removeMouseLine();
|
||||
const pointer = canvas.getPointer(e.e)
|
||||
const roofsPoints = roofs.map((roof) => roof.points).flat()
|
||||
roofAdsorptionPoints.current = [...roofsPoints]
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
let innerLinePoints = []
|
||||
canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.innerLines)
|
||||
.forEach((polygon) => {
|
||||
polygon.innerLines.forEach((line) => {
|
||||
innerLinePoints.push({ x: line.x1, y: line.y1 })
|
||||
innerLinePoints.push({ x: line.x2, y: line.y2 })
|
||||
})
|
||||
})
|
||||
|
||||
const adsorptionPoints = [
|
||||
...getAdsorptionPoints(),
|
||||
...roofAdsorptionPoints.current,
|
||||
...otherAdsorptionPoints,
|
||||
...intersectionPoints.current,
|
||||
...innerLinePoints,
|
||||
]
|
||||
|
||||
let arrivalPoint = { x: pointer.x, y: pointer.y }
|
||||
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()
|
||||
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
canvas
|
||||
?.getObjects()
|
||||
@ -768,6 +701,7 @@ mouseMove
|
||||
if (points.length === 0) {
|
||||
return
|
||||
}
|
||||
enterCheck(e)
|
||||
// 포커스가 length1에 있지 않으면 length1에 포커스를 줌
|
||||
const activeElem = document.activeElement
|
||||
|
||||
@ -833,6 +767,7 @@ mouseMove
|
||||
if (points.length === 0) {
|
||||
return
|
||||
}
|
||||
enterCheck(e)
|
||||
const key = e.key
|
||||
|
||||
const activeElem = document.activeElement
|
||||
@ -866,6 +801,7 @@ mouseMove
|
||||
if (points.length === 0) {
|
||||
return
|
||||
}
|
||||
enterCheck(e)
|
||||
const key = e.key
|
||||
switch (key) {
|
||||
case 'Down': // IE/Edge에서 사용되는 값
|
||||
@ -891,6 +827,7 @@ mouseMove
|
||||
if (points.length === 0) {
|
||||
return
|
||||
}
|
||||
enterCheck(e)
|
||||
const key = e.key
|
||||
switch (key) {
|
||||
case 'Enter': {
|
||||
@ -915,6 +852,7 @@ mouseMove
|
||||
if (points.length === 0) {
|
||||
return
|
||||
}
|
||||
enterCheck(e)
|
||||
|
||||
const key = e.key
|
||||
switch (key) {
|
||||
@ -979,6 +917,12 @@ mouseMove
|
||||
isFix.current = true
|
||||
}
|
||||
|
||||
const enterCheck = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleFix()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
points,
|
||||
setPoints,
|
||||
|
||||
@ -6,6 +6,9 @@ import { calculateDistance, calculateDistancePoint, calculateIntersection, dista
|
||||
import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint'
|
||||
import { useDotLineGrid } from '@/hooks/useDotLineGrid'
|
||||
import { useTempGrid } from '@/hooks/useTempGrid'
|
||||
import { gridColorState } from '@/store/gridAtom'
|
||||
import { gridDisplaySelector } from '@/store/settingAtom'
|
||||
import { POLYGON_TYPE } from '@/common/common'
|
||||
|
||||
export function useEvent() {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
@ -13,10 +16,14 @@ export function useEvent() {
|
||||
const documentEventListeners = useRef([])
|
||||
const mouseEventListeners = useRef([])
|
||||
const setCanvasZoom = useSetRecoilState(canvasZoomState)
|
||||
const gridColor = useRecoilValue(gridColorState)
|
||||
const isGridDisplay = useRecoilValue(gridDisplaySelector)
|
||||
|
||||
const { adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, getAdsorptionPoints, adsorptionPointAddModeStateEvent } = useAdsorptionPoint()
|
||||
const { dotLineGridSetting, interval, getClosestLineGrid } = useDotLineGrid()
|
||||
const { tempGridModeStateLeftClickEvent, tempGridMode, tempGridRightClickEvent } = useTempGrid()
|
||||
const { tempGridModeStateLeftClickEvent, tempGridMode } = useTempGrid()
|
||||
const roofAdsorptionPoints = useRef([])
|
||||
const intersectionPoints = useRef([])
|
||||
|
||||
const textMode = useRecoilValue(textModeState)
|
||||
|
||||
@ -93,35 +100,84 @@ export function useEvent() {
|
||||
|
||||
const defaultMouseMoveEvent = (e) => {
|
||||
removeMouseLine()
|
||||
const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
|
||||
// 가로선
|
||||
const pointer = canvas.getPointer(e.e)
|
||||
|
||||
const adsorptionPoints = getAdsorptionPoints()
|
||||
|
||||
let arrivalPoint = { x: pointer.x, y: pointer.y }
|
||||
|
||||
if (adsorptionPointMode) {
|
||||
const roofsPoints = roofs.map((roof) => roof.points).flat()
|
||||
roofAdsorptionPoints.current = [...roofsPoints]
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
let innerLinePoints = []
|
||||
canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.innerLines)
|
||||
.forEach((polygon) => {
|
||||
polygon.innerLines.forEach((line) => {
|
||||
innerLinePoints.push({ x: line.x1, y: line.y1 })
|
||||
innerLinePoints.push({ x: line.x2, y: line.y2 })
|
||||
})
|
||||
})
|
||||
|
||||
const adsorptionPoints = [
|
||||
...getAdsorptionPoints(),
|
||||
...roofAdsorptionPoints.current,
|
||||
...otherAdsorptionPoints,
|
||||
...intersectionPoints.current,
|
||||
...innerLinePoints,
|
||||
]
|
||||
|
||||
if (dotLineGridSetting.LINE || canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name)).length > 0) {
|
||||
const closestLine = getClosestLineGrid(pointer)
|
||||
|
||||
const horizonLines = canvas.getObjects().filter((obj) => obj.name === 'lineGrid' && obj.direction === 'horizontal')
|
||||
const verticalLines = canvas.getObjects().filter((obj) => obj.name === 'lineGrid' && obj.direction === 'vertical')
|
||||
const horizonLines = canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name) && obj.direction === 'horizontal')
|
||||
const verticalLines = canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name) && obj.direction === 'vertical')
|
||||
|
||||
if (!horizonLines || !verticalLines) {
|
||||
return
|
||||
}
|
||||
|
||||
const closestHorizontalLine = horizonLines.reduce((prev, curr) => {
|
||||
const prevDistance = calculateDistance(pointer, prev)
|
||||
const currDistance = calculateDistance(pointer, curr)
|
||||
return prevDistance < currDistance ? prev : curr
|
||||
})
|
||||
let closestHorizontalLine = null
|
||||
let closestVerticalLine = null
|
||||
|
||||
const closestVerticalLine = verticalLines.reduce((prev, curr) => {
|
||||
const prevDistance = calculateDistance(pointer, prev)
|
||||
const currDistance = calculateDistance(pointer, curr)
|
||||
return prevDistance < currDistance ? prev : curr
|
||||
})
|
||||
if (horizonLines && horizonLines.length > 0) {
|
||||
closestHorizontalLine = horizonLines.reduce((prev, curr) => {
|
||||
const prevDistance = calculateDistance(pointer, prev)
|
||||
const currDistance = calculateDistance(pointer, curr)
|
||||
return prevDistance < currDistance ? prev : curr
|
||||
})
|
||||
}
|
||||
|
||||
if (verticalLines && verticalLines.length > 0) {
|
||||
closestVerticalLine = verticalLines.reduce((prev, curr) => {
|
||||
const prevDistance = calculateDistance(pointer, prev)
|
||||
const currDistance = calculateDistance(pointer, curr)
|
||||
return prevDistance < currDistance ? prev : curr
|
||||
})
|
||||
}
|
||||
|
||||
if (!closestVerticalLine || !closestHorizontalLine) {
|
||||
return
|
||||
}
|
||||
|
||||
const closestIntersectionPoint = calculateIntersection(closestHorizontalLine, closestVerticalLine)
|
||||
|
||||
@ -238,6 +294,34 @@ export function useEvent() {
|
||||
})
|
||||
}
|
||||
|
||||
const tempGridRightClickEvent = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
//임의 그리드 모드일 경우
|
||||
let pointer = { x: e.offsetX, y: e.offsetY }
|
||||
|
||||
const tempGrid = new fabric.Line([-1500, pointer.y, 2500, pointer.y], {
|
||||
stroke: gridColor,
|
||||
strokeWidth: 1,
|
||||
selectable: true,
|
||||
lockMovementX: true,
|
||||
lockMovementY: true,
|
||||
lockRotation: true,
|
||||
lockScalingX: true,
|
||||
lockScalingY: true,
|
||||
strokeDashArray: [5, 2],
|
||||
opacity: 0.3,
|
||||
padding: 5,
|
||||
name: 'tempGrid',
|
||||
visible: isGridDisplay,
|
||||
direction: 'horizontal',
|
||||
})
|
||||
|
||||
canvas.add(tempGrid)
|
||||
|
||||
canvas.renderAll()
|
||||
}
|
||||
|
||||
const defaultKeyboardEvent = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
console.log('defaultKeyboardEvent')
|
||||
@ -314,6 +398,7 @@ export function useEvent() {
|
||||
removeDocumentEvent,
|
||||
removeMouseEvent,
|
||||
removeMouseLine,
|
||||
defaultMouseMoveEvent,
|
||||
initEvent,
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,12 +38,12 @@ export const useLine = () => {
|
||||
line.set({
|
||||
visible: false,
|
||||
})
|
||||
canvas
|
||||
?.getObjects()
|
||||
.find((obj) => obj.parentId === line.id)
|
||||
.set({
|
||||
const obj = canvas?.getObjects().find((obj) => obj.parentId === line.id)
|
||||
if (obj) {
|
||||
obj.set({
|
||||
visible: false,
|
||||
})
|
||||
}
|
||||
canvas?.renderAll()
|
||||
}
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ import {
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import { fabric } from 'fabric'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
import offsetPolygon from '@/util/qpolygon-utils'
|
||||
import offsetPolygon, { calculateAngle } from '@/util/qpolygon-utils'
|
||||
import { isObjectNotEmpty } from '@/util/common-utils'
|
||||
import * as turf from '@turf/turf'
|
||||
import { INPUT_TYPE, LINE_TYPE, Mode, POLYGON_TYPE } from '@/common/common'
|
||||
@ -1016,7 +1016,7 @@ export function useMode() {
|
||||
surfaceShapeMode: (o) => {},
|
||||
// 그림자 모드
|
||||
shadowMode: {
|
||||
rect: null,
|
||||
// rect: null,
|
||||
isDown: false,
|
||||
origX: 0,
|
||||
origY: 0,
|
||||
@ -1679,10 +1679,11 @@ export function useMode() {
|
||||
const offsetEdges = []
|
||||
|
||||
polygon.edges.forEach((edge, i) => {
|
||||
const offset =
|
||||
/* const offset =
|
||||
lines[i % lines.length].attributes.offset === undefined || lines[i % lines.length].attributes.offset === 0
|
||||
? 0.1
|
||||
: lines[i % lines.length].attributes.offset
|
||||
: lines[i % lines.length].attributes.offset*/
|
||||
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))
|
||||
@ -1717,10 +1718,12 @@ export function useMode() {
|
||||
const offsetEdges = []
|
||||
|
||||
polygon.edges.forEach((edge, i) => {
|
||||
const offset =
|
||||
/*const offset =
|
||||
lines[i % lines.length].attributes.offset === undefined || lines[i % lines.length].attributes.offset === 0
|
||||
? 0.1
|
||||
: lines[i % lines.length].attributes.offset
|
||||
: lines[i % lines.length].attributes.offset*/
|
||||
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))
|
||||
@ -1767,10 +1770,19 @@ export function useMode() {
|
||||
afterLine.push(line)
|
||||
}
|
||||
})
|
||||
|
||||
wall.lines = afterLine.concat(beforeLine)
|
||||
|
||||
//외벽선을 기준으로 polygon을 생성한다. 지붕선의 기준이 됨.
|
||||
const divWallLines = []
|
||||
wall.lines.forEach((currentWall, index) => {
|
||||
const nextWall = wall.lines[(index + 1) % wall.lines.length]
|
||||
const currentAngle = calculateAngle(currentWall.startPoint, currentWall.endPoint)
|
||||
const nextAngle = calculateAngle(nextWall.startPoint, nextWall.endPoint)
|
||||
if (currentAngle === nextAngle) {
|
||||
divWallLines.push({ currentWall: currentWall, nextWall: nextWall, index: index })
|
||||
}
|
||||
})
|
||||
|
||||
const polygon = createRoofPolygon(wall.points)
|
||||
const originPolygon = new QPolygon(wall.points, { fontSize: 0 })
|
||||
originPolygon.setViewLengthText(false)
|
||||
@ -1787,6 +1799,45 @@ export function useMode() {
|
||||
offsetPolygon = createPaddingPolygon(polygon, wall.lines).vertices
|
||||
}
|
||||
|
||||
if (divWallLines.length > 0) {
|
||||
/**
|
||||
* 외벽선을 분기한 횟수를 저장한다. 외벽선은 offset이 같지 않을때 분기한다.
|
||||
*/
|
||||
let addPoint = 0
|
||||
|
||||
divWallLines.forEach((line) => {
|
||||
const currentWall = line.currentWall
|
||||
const nextWall = line.nextWall
|
||||
const index = line.index + addPoint
|
||||
const xDiff = Big(currentWall.x1).minus(Big(nextWall.x1))
|
||||
const yDiff = Big(currentWall.y1).minus(Big(nextWall.y1))
|
||||
const offsetCurrentPoint = offsetPolygon[index]
|
||||
let offsetNextPoint = offsetPolygon[(index + 1) % offsetPolygon.length]
|
||||
line.index = index
|
||||
|
||||
if (currentWall.attributes.offset !== nextWall.attributes.offset) {
|
||||
const offsetPoint1 = {
|
||||
x: xDiff.eq(0) ? offsetCurrentPoint.x : nextWall.x1,
|
||||
y: yDiff.eq(0) ? offsetCurrentPoint.y : nextWall.y1,
|
||||
}
|
||||
const diffOffset = Big(nextWall.attributes.offset).minus(Big(currentWall.attributes.offset))
|
||||
const offsetPoint2 = {
|
||||
x: yDiff.eq(0) ? offsetPoint1.x : Big(offsetPoint1.x).plus(diffOffset).toNumber(),
|
||||
y: xDiff.eq(0) ? offsetPoint1.y : Big(offsetPoint1.y).plus(diffOffset).toNumber(),
|
||||
}
|
||||
const offsetPoint3 = {
|
||||
x: yDiff.eq(0) ? offsetNextPoint.x : Big(offsetNextPoint.x).plus(diffOffset).toNumber(),
|
||||
y: xDiff.eq(0) ? offsetNextPoint.y : Big(offsetNextPoint.y).plus(diffOffset).toNumber(),
|
||||
}
|
||||
offsetPolygon.splice(index + 1, 0, offsetPoint1, offsetPoint2)
|
||||
offsetNextPoint = offsetPoint3
|
||||
addPoint++
|
||||
} else {
|
||||
addPoint--
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const roof = makePolygon(
|
||||
offsetPolygon.map((point) => {
|
||||
return { x1: point.x, y1: point.y }
|
||||
@ -1806,6 +1857,8 @@ export function useMode() {
|
||||
roof.name = POLYGON_TYPE.ROOF
|
||||
roof.setWall(wall)
|
||||
|
||||
let roofWallIndex = 0
|
||||
|
||||
roof.lines.forEach((line, index) => {
|
||||
const x1 = Big(line.x1)
|
||||
const x2 = Big(line.x2)
|
||||
@ -1816,29 +1869,31 @@ export function useMode() {
|
||||
roofId: roof.id,
|
||||
planeSize: lineLength,
|
||||
actualSize: lineLength,
|
||||
wallLine: wall.lines[index].id,
|
||||
type: wall.lines[index].attributes.type,
|
||||
offset: wall.lines[index].attributes.offset,
|
||||
width: wall.lines[index].attributes.width,
|
||||
pitch: wall.lines[index].attributes.pitch,
|
||||
sleeve: wall.lines[index].attributes.sleeve || false,
|
||||
wallLine: wall.lines[roofWallIndex].id,
|
||||
type: wall.lines[roofWallIndex].attributes.type,
|
||||
offset: wall.lines[roofWallIndex].attributes.offset,
|
||||
width: wall.lines[roofWallIndex].attributes.width,
|
||||
pitch: wall.lines[roofWallIndex].attributes.pitch,
|
||||
sleeve: wall.lines[roofWallIndex].attributes.sleeve || false,
|
||||
}
|
||||
|
||||
const isDivLine = divWallLines.some((divLine) => divLine.index === index)
|
||||
if (!isDivLine) {
|
||||
roofWallIndex++
|
||||
}
|
||||
})
|
||||
|
||||
wall.set({
|
||||
// originX: 'center',
|
||||
// originY: 'center',
|
||||
attributes: {
|
||||
roofId: roof.id,
|
||||
},
|
||||
})
|
||||
|
||||
//외벽선 삭제
|
||||
canvas
|
||||
.getObjects()
|
||||
.filter((line) => line.attributes?.wallId === wall.id)
|
||||
.forEach((line) => canvas.remove(line))
|
||||
|
||||
/** 초기화*/
|
||||
roof.wall.baseLines.forEach((line, index) => {
|
||||
canvas.remove(line)
|
||||
})
|
||||
roof.wall.baseLines = []
|
||||
wall.lines.forEach((line, index) => {
|
||||
const x1 = Big(line.x1)
|
||||
const x2 = Big(line.x2)
|
||||
@ -1846,54 +1901,22 @@ export function useMode() {
|
||||
const y2 = Big(line.y2)
|
||||
const lineLength = x1.minus(x2).abs().pow(2).plus(y1.minus(y2).abs().pow(2)).sqrt().times(10).round().toNumber()
|
||||
line.attributes.roofId = roof.id
|
||||
line.attributes.currentRoofId = roof.lines[index].id
|
||||
line.attributes.wallId = wall.id
|
||||
// line.attributes.currentRoofId = roof.lines[index].id
|
||||
line.attributes.planeSize = lineLength
|
||||
line.attributes.actualSize = lineLength
|
||||
|
||||
let wallStroke, wallStrokeWidth
|
||||
switch (line.attributes.type) {
|
||||
case LINE_TYPE.WALLLINE.EAVES:
|
||||
wallStroke = '#45CD7D'
|
||||
wallStrokeWidth = 4
|
||||
break
|
||||
case LINE_TYPE.WALLLINE.HIPANDGABLE:
|
||||
wallStroke = '#45CD7D'
|
||||
wallStrokeWidth = 4
|
||||
break
|
||||
case LINE_TYPE.WALLLINE.GABLE:
|
||||
wallStroke = '#3FBAE6'
|
||||
wallStrokeWidth = 4
|
||||
break
|
||||
case LINE_TYPE.WALLLINE.JERKINHEAD:
|
||||
wallStroke = '#3FBAE6'
|
||||
wallStrokeWidth = 4
|
||||
break
|
||||
case LINE_TYPE.WALLLINE.SHED:
|
||||
wallStroke = '#000000'
|
||||
wallStrokeWidth = 4
|
||||
break
|
||||
case LINE_TYPE.WALLLINE.WALL:
|
||||
wallStroke = '#000000'
|
||||
wallStrokeWidth = 4
|
||||
break
|
||||
}
|
||||
|
||||
//외벽선의 색깔 표시를 위해 라인을 추가한다.
|
||||
const wallLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
|
||||
parentId: wall.id,
|
||||
name: 'wallLine',
|
||||
attributes: {
|
||||
wallId: wall.id,
|
||||
roofId: roof.id,
|
||||
type: line.attributes.type,
|
||||
currentRoofId: line.attributes.currentRoofId,
|
||||
currentWall: line.id,
|
||||
},
|
||||
stroke: wallStroke,
|
||||
strokeWidth: wallStrokeWidth,
|
||||
selectable: false,
|
||||
const baseLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
|
||||
visible: false,
|
||||
attributes: line.attributes,
|
||||
startPoint: line.startPoint,
|
||||
endPoint: line.endPoint,
|
||||
parentId: roof.id,
|
||||
name: 'baseLine',
|
||||
})
|
||||
canvas.add(wallLine)
|
||||
baseLine.attributes.originPoint = { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }
|
||||
roof.wall.baseLines.push(baseLine)
|
||||
canvas.add(baseLine)
|
||||
})
|
||||
|
||||
setRoof(roof)
|
||||
|
||||
@ -12,5 +12,17 @@ export function useObject() {
|
||||
canvas.remove(item)
|
||||
})
|
||||
}
|
||||
return { deleteObject }
|
||||
|
||||
const handleSelectableObjects = (targetNames = [], bool) => {
|
||||
if (!canvas) {
|
||||
return
|
||||
}
|
||||
const selectableObjects = canvas.getObjects().filter((obj) => targetNames.includes(obj.name))
|
||||
selectableObjects.forEach((obj) => {
|
||||
obj.selectable = bool
|
||||
})
|
||||
canvas.renderAll()
|
||||
}
|
||||
|
||||
return { deleteObject, handleSelectableObjects }
|
||||
}
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, pitchTextSelector } from '@/store/canvasAtom'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { fabric } from 'fabric'
|
||||
import { findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, isPointOnLine } from '@/util/canvas-util'
|
||||
import {
|
||||
distanceBetweenPoints,
|
||||
findAndRemoveClosestPoint,
|
||||
getDegreeByChon,
|
||||
getDegreeInOrientation,
|
||||
isPointOnLine,
|
||||
toFixedWithoutRounding,
|
||||
} from '@/util/canvas-util'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
import { isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils'
|
||||
import { flowDisplaySelector } from '@/store/settingAtom'
|
||||
@ -759,9 +766,9 @@ export const usePolygon = () => {
|
||||
|
||||
const splitPolygonWithLines = (polygon) => {
|
||||
polygon.set({ visible: false })
|
||||
let innerLines = [...polygon.innerLines]
|
||||
let innerLines = [...polygon.innerLines].filter((line) => line.visible)
|
||||
|
||||
// innerLine이 세팅이 안되어있는경우 찾아서 세팅한다.
|
||||
/*// innerLine이 세팅이 안되어있는경우 찾아서 세팅한다.
|
||||
if (!innerLines || innerLines.length === 0) {
|
||||
let innerLineTypes = Object.keys(LINE_TYPE.SUBLINE).map((key, value) => LINE_TYPE.SUBLINE[key])
|
||||
polygon.innerLines = canvas
|
||||
@ -775,8 +782,7 @@ export const usePolygon = () => {
|
||||
)
|
||||
|
||||
innerLines = [...polygon.innerLines]
|
||||
}
|
||||
|
||||
}*/
|
||||
canvas.renderAll()
|
||||
let polygonLines = [...polygon.lines]
|
||||
const roofs = []
|
||||
@ -827,86 +833,104 @@ export const usePolygon = () => {
|
||||
line.endPoint = endPoint
|
||||
})
|
||||
|
||||
polygonLines.forEach((line) => {
|
||||
/*polygonLines.forEach((line) => {
|
||||
line.set({ strokeWidth: 10 })
|
||||
canvas.add(line)
|
||||
})
|
||||
canvas.renderAll()
|
||||
canvas.renderAll()*/
|
||||
|
||||
polygonLines.forEach((line) => {
|
||||
/*const originStroke = line.stroke
|
||||
line.set({ stroke: 'red' })
|
||||
canvas.renderAll()*/
|
||||
const intersections = []
|
||||
innerLines.forEach((innerLine) => {
|
||||
/*const originInnerStroke = innerLine.stroke
|
||||
innerLine.set({ stroke: 'red' })
|
||||
canvas.renderAll()*/
|
||||
if (isPointOnLine(line, innerLine.startPoint)) {
|
||||
canvas.renderAll()
|
||||
if (isSamePoint(line.startPoint, innerLine.startPoint) || isSamePoint(line.endPoint, innerLine.startPoint)) {
|
||||
return
|
||||
}
|
||||
intersections.push(innerLine.startPoint)
|
||||
}
|
||||
if (isPointOnLine(line, innerLine.endPoint)) {
|
||||
canvas.renderAll()
|
||||
if (isSamePoint(line.startPoint, innerLine.endPoint) || isSamePoint(line.endPoint, innerLine.endPoint)) {
|
||||
return
|
||||
}
|
||||
intersections.push(innerLine.endPoint)
|
||||
}
|
||||
/*innerLine.set({ stroke: originInnerStroke })
|
||||
canvas.renderAll()*/
|
||||
})
|
||||
line.set({ intersections })
|
||||
|
||||
/*line.set({ stroke: originStroke })
|
||||
canvas.renderAll()*/
|
||||
})
|
||||
|
||||
const divideLines = polygonLines.filter((line) => line.intersections.length > 0)
|
||||
const divideLines = polygonLines.filter((line) => line.intersections?.length > 0)
|
||||
let newLines = []
|
||||
|
||||
divideLines.forEach((line) => {
|
||||
polygonLines = polygonLines.filter((line) => !line.intersections || line.intersections.length === 0)
|
||||
for (let i = divideLines.length - 1; i >= 0; i--) {
|
||||
const line = divideLines[i]
|
||||
const { intersections, startPoint, endPoint } = line
|
||||
|
||||
if (intersections.length === 1) {
|
||||
// 한 점만 교차하는 경우
|
||||
const newLinePoint1 = [line.x1, line.y1, intersections[0].x, intersections[0].y]
|
||||
const newLinePoint2 = [intersections[0].x, intersections[0].y, line.x2, line.y2]
|
||||
|
||||
const newLine1 = new QLine(newLinePoint1, {
|
||||
stroke: 'blue',
|
||||
strokeWidth: 3,
|
||||
fontSize: polygon.fontSize,
|
||||
attributes: line.attributes,
|
||||
name: 'newLine',
|
||||
})
|
||||
const newLine2 = new QLine(newLinePoint2, {
|
||||
stroke: 'blue',
|
||||
strokeWidth: 3,
|
||||
fontSize: polygon.fontSize,
|
||||
attributes: line.attributes,
|
||||
name: 'newLine',
|
||||
})
|
||||
|
||||
newLine1.attributes = {
|
||||
...line.attributes,
|
||||
planeSize: Math.round(Math.sqrt(Math.pow(newLine1.x1 - newLine1.x2, 2) + Math.pow(newLine1.y1 - newLine1.y2, 2))) * 10,
|
||||
actualSize: Math.round(Math.sqrt(Math.pow(newLine1.x1 - newLine1.x2, 2) + Math.pow(newLine1.y1 - newLine1.y2, 2))) * 10,
|
||||
planeSize: Math.round(Math.hypot(newLine1.x1 - newLine1.x2, newLine1.y1 - newLine1.y2)) * 10,
|
||||
actualSize: Math.round(Math.hypot(newLine1.x1 - newLine1.x2, newLine1.y1 - newLine1.y2)) * 10,
|
||||
}
|
||||
newLine2.attributes = {
|
||||
...line.attributes,
|
||||
planeSize: Math.round(Math.sqrt(Math.pow(newLine2.x1 - newLine2.x2, 2) + Math.pow(newLine2.y1 - newLine2.y2, 2))) * 10,
|
||||
actualSize: Math.round(Math.sqrt(Math.pow(newLine2.x1 - newLine2.x2, 2) + Math.pow(newLine2.y1 - newLine2.y2, 2))) * 10,
|
||||
planeSize: Math.round(Math.hypot(newLine2.x1 - newLine2.x2, newLine2.y1 - newLine2.y2)) * 10,
|
||||
actualSize: Math.round(Math.hypot(newLine2.x1 - newLine2.x2, newLine2.y1 - newLine2.y2)) * 10,
|
||||
}
|
||||
newLines.push(newLine1)
|
||||
newLines.push(newLine2)
|
||||
} else {
|
||||
// 두 점 이상 교차하는 경우
|
||||
//1. intersections중에 startPoint와 가장 가까운 점을 찾는다.
|
||||
//2. 가장 가까운 점을 기준으로 line을 나눈다.
|
||||
|
||||
newLines.push(newLine1, newLine2)
|
||||
divideLines.splice(i, 1) // 기존 line 제거
|
||||
} else {
|
||||
let currentPoint = startPoint
|
||||
|
||||
while (intersections.length !== 0) {
|
||||
const minDistancePoint = findAndRemoveClosestPoint(currentPoint, intersections)
|
||||
const newLinePoint = [currentPoint.x, currentPoint.y, minDistancePoint.x, minDistancePoint.y]
|
||||
|
||||
const newLine = new QLine(newLinePoint, {
|
||||
stroke: 'blue',
|
||||
strokeWidth: 3,
|
||||
fontSize: polygon.fontSize,
|
||||
attributes: line.attributes,
|
||||
name: 'newLine',
|
||||
})
|
||||
|
||||
newLine.attributes = {
|
||||
...line.attributes,
|
||||
planeSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10,
|
||||
actualSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10,
|
||||
planeSize: Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10,
|
||||
actualSize: Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10,
|
||||
}
|
||||
|
||||
newLines.push(newLine)
|
||||
currentPoint = minDistancePoint
|
||||
}
|
||||
@ -917,21 +941,66 @@ export const usePolygon = () => {
|
||||
strokeWidth: 3,
|
||||
fontSize: polygon.fontSize,
|
||||
attributes: line.attributes,
|
||||
name: 'newLine',
|
||||
})
|
||||
newLine.attributes = {
|
||||
...line.attributes,
|
||||
planeSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10,
|
||||
actualSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10,
|
||||
planeSize: Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10,
|
||||
actualSize: Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10,
|
||||
}
|
||||
|
||||
newLines.push(newLine)
|
||||
divideLines.splice(i, 1) // 기존 line 제거
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//polygonLines에서 divideLines를 제거하고 newLines를 추가한다.
|
||||
polygonLines = polygonLines.filter((line) => !divideLines.includes(line))
|
||||
newLines = newLines.filter((line) => !(Math.abs(line.startPoint.x - line.endPoint.x) < 1 && Math.abs(line.startPoint.y - line.endPoint.y) < 1))
|
||||
|
||||
polygonLines = [...polygonLines, ...newLines]
|
||||
|
||||
const allLines = [...polygonLines, ...innerLines]
|
||||
let allLines = [...polygonLines, ...innerLines]
|
||||
|
||||
/*allLines.forEach((line) => {
|
||||
const originColor = line.stroke
|
||||
|
||||
line.set('stroke', 'red')
|
||||
canvas.renderAll()
|
||||
|
||||
line.set('stroke', originColor)
|
||||
canvas.renderAll()
|
||||
})*/
|
||||
|
||||
const allPoints = []
|
||||
|
||||
// test용 좌표
|
||||
const polygonLinesPoints = polygonLines.map((line) => {
|
||||
return { startPoint: line.startPoint, endPoint: line.endPoint }
|
||||
})
|
||||
|
||||
const innerLinesPoints = innerLines.map((line) => {
|
||||
return { startPoint: line.startPoint, endPoint: line.endPoint }
|
||||
})
|
||||
|
||||
polygonLinesPoints.forEach(({ startPoint, endPoint }) => {
|
||||
allPoints.push(startPoint)
|
||||
allPoints.push(endPoint)
|
||||
})
|
||||
|
||||
innerLinesPoints.forEach(({ startPoint, endPoint }) => {
|
||||
allPoints.push(startPoint)
|
||||
allPoints.push(endPoint)
|
||||
})
|
||||
|
||||
// allPoints에서 중복을 제거한다.
|
||||
const uniquePoints = allPoints.filter((point, index, self) => {
|
||||
return (
|
||||
index ===
|
||||
self.findIndex((p) => {
|
||||
return isSamePoint(p, point)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// 2025-02-19 대각선은 케라바, 직선은 용마루로 세팅
|
||||
innerLines.forEach((innerLine) => {
|
||||
@ -940,14 +1009,14 @@ export const usePolygon = () => {
|
||||
|
||||
// startPoint와 endPoint의 각도가 0,90,180,270이면 직선으로 판단
|
||||
if (Math.abs(startPoint.x - endPoint.x) < 2 || Math.abs(startPoint.y - endPoint.y) < 2) {
|
||||
if (!innerLine.attributes || !innerLine.attributes.type) {
|
||||
if (!innerLine.attributes || !innerLine.attributes.type || innerLine.attributes.type === 'default') {
|
||||
innerLine.attributes = {
|
||||
...innerLine.attributes,
|
||||
type: LINE_TYPE.SUBLINE.RIDGE,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!innerLine.attributes || !innerLine.attributes.type) {
|
||||
if (!innerLine.attributes || !innerLine.attributes.type || innerLine.attributes.type === 'default') {
|
||||
innerLine.attributes = {
|
||||
...innerLine.attributes,
|
||||
type: LINE_TYPE.SUBLINE.GABLE,
|
||||
@ -956,6 +1025,13 @@ export const usePolygon = () => {
|
||||
}
|
||||
})
|
||||
|
||||
/*innerLines.forEach((line) => {
|
||||
const startPoint = line.startPoint
|
||||
const endPoint = line.endPoint
|
||||
/!*canvas.add(new fabric.Circle({ left: startPoint.x, top: startPoint.y + 10, radius: 5, fill: 'red' }))
|
||||
canvas.add(new fabric.Circle({ left: endPoint.x, top: endPoint.y - 10, radius: 5, fill: 'blue' }))*!/
|
||||
})*/
|
||||
|
||||
/**
|
||||
* 왼쪽 상단을 startPoint로 전부 변경
|
||||
*/
|
||||
@ -982,102 +1058,18 @@ export const usePolygon = () => {
|
||||
line.endPoint = endPoint
|
||||
})
|
||||
|
||||
// polygon line에서 각각 출발한다.
|
||||
polygonLines.forEach((line) => {
|
||||
/*line.set({ strokeWidth: 5, stroke: 'green' })
|
||||
canvas.add(line)
|
||||
canvas.renderAll()*/
|
||||
const startPoint = { ...line.startPoint } // 시작점
|
||||
let arrivalPoint = { ...line.endPoint } // 도착점
|
||||
|
||||
let currentPoint = startPoint
|
||||
let roofPoints = [startPoint]
|
||||
|
||||
let startLine = line
|
||||
let visitPoints = [startPoint]
|
||||
let visitLines = [startLine]
|
||||
let notVisitedLines = []
|
||||
let cnt = 0
|
||||
|
||||
while (!isSamePoint(currentPoint, arrivalPoint)) {
|
||||
//현재 점으로 부터 갈 수 있는 다른 라인을 찾는다.
|
||||
let nextLines = allLines.filter(
|
||||
(line2) =>
|
||||
(isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) &&
|
||||
line !== line2 &&
|
||||
innerLines.includes(line2) &&
|
||||
!visitLines.includes(line2),
|
||||
)
|
||||
|
||||
if (nextLines.length === 0) {
|
||||
nextLines = allLines.filter(
|
||||
(line2) =>
|
||||
(isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) &&
|
||||
line !== line2 &&
|
||||
!visitLines.includes(line2),
|
||||
)
|
||||
}
|
||||
|
||||
if (nextLines.length === 0) {
|
||||
//아직 안갔던 line중 0번째를 선택한다.
|
||||
if (notVisitedLines.length === 0) {
|
||||
break
|
||||
} else {
|
||||
let notVisitedLine = notVisitedLines.shift()
|
||||
roofPoints = [...notVisitedLine.roofPoints]
|
||||
currentPoint = { ...notVisitedLine.currentPoint }
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
let comparisonPoints = []
|
||||
|
||||
nextLines.forEach((nextLine) => {
|
||||
if (isSamePoint(nextLine.startPoint, currentPoint)) {
|
||||
comparisonPoints.push(nextLine.endPoint)
|
||||
} else {
|
||||
comparisonPoints.push(nextLine.startPoint)
|
||||
}
|
||||
//allLines에서 중복을 제거한다.
|
||||
allLines = allLines.filter((line, index, self) => {
|
||||
return (
|
||||
index ===
|
||||
self.findIndex((l) => {
|
||||
return isSamePoint(l.startPoint, line.startPoint) && isSamePoint(l.endPoint, line.endPoint)
|
||||
})
|
||||
|
||||
comparisonPoints = comparisonPoints.filter((point) => !visitPoints.some((visitPoint) => isSamePoint(visitPoint, point)))
|
||||
comparisonPoints = comparisonPoints.filter((point) => !isSamePoint(point, currentPoint))
|
||||
|
||||
const minDistancePoint = comparisonPoints.reduce((prev, current) => {
|
||||
const prevDistance = Math.sqrt(Math.pow(prev.x - arrivalPoint.x, 2) + Math.pow(prev.y - arrivalPoint.y, 2))
|
||||
const currentDistance = Math.sqrt(Math.pow(current.x - arrivalPoint.x, 2) + Math.pow(current.y - arrivalPoint.y, 2))
|
||||
|
||||
return prevDistance < currentDistance ? prev : current
|
||||
}, comparisonPoints[0])
|
||||
|
||||
nextLines.forEach((nextLine) => {
|
||||
if (isSamePoint(nextLine.startPoint, minDistancePoint) || isSamePoint(nextLine.endPoint, minDistancePoint)) {
|
||||
visitLines.push(nextLine)
|
||||
} else {
|
||||
notVisitedLines.push({
|
||||
line: nextLine,
|
||||
endPoint: nextLine.endPoint,
|
||||
startPoint: nextLine.startPoint,
|
||||
currentPoint: { ...currentPoint },
|
||||
roofPoints: [...roofPoints],
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
currentPoint = { ...minDistancePoint }
|
||||
roofPoints.push(currentPoint)
|
||||
cnt++
|
||||
|
||||
if (cnt > 100) {
|
||||
break
|
||||
}
|
||||
}
|
||||
roofs.push(roofPoints)
|
||||
canvas.remove(line)
|
||||
canvas.renderAll()
|
||||
)
|
||||
})
|
||||
|
||||
const newRoofs = removeDuplicatePolygons(roofs.filter((roof) => roof.length < 100))
|
||||
// 나눠서 중복 제거된 roof return
|
||||
const newRoofs = getSplitRoofsPoints(allLines)
|
||||
|
||||
newRoofs.forEach((roofPoint, index) => {
|
||||
let defense, pitch
|
||||
@ -1108,7 +1100,7 @@ export const usePolygon = () => {
|
||||
})
|
||||
|
||||
// blue로 생성된 것들은 대표라인이 될 수 없음.
|
||||
representLines = representLines.filter((line) => line.stroke !== 'blue')
|
||||
// representLines = representLines.filter((line) => line.stroke !== 'blue')
|
||||
// representLines중 가장 긴 line을 찾는다.
|
||||
representLines.forEach((line) => {
|
||||
if (!representLine) {
|
||||
@ -1119,7 +1111,7 @@ export const usePolygon = () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
const direction = polygon.direction ?? representLine.direction
|
||||
const direction = polygon.direction ?? representLine?.direction
|
||||
const polygonDirection = polygon.direction
|
||||
|
||||
switch (direction) {
|
||||
@ -1135,8 +1127,11 @@ export const usePolygon = () => {
|
||||
case 'left':
|
||||
defense = 'north'
|
||||
break
|
||||
default:
|
||||
defense = 'south'
|
||||
break
|
||||
}
|
||||
pitch = polygon.lines[index].attributes?.pitch ?? 0
|
||||
pitch = polygon.lines[index]?.attributes?.pitch ?? 0
|
||||
|
||||
const roof = new QPolygon(roofPoint, {
|
||||
fontSize: polygon.fontSize,
|
||||
@ -1154,7 +1149,7 @@ export const usePolygon = () => {
|
||||
|
||||
//allLines중 생성된 roof와 관련있는 line을 찾는다.
|
||||
|
||||
roof.lines = [...polygonLines, ...polygon.innerLines].filter((line) => {
|
||||
const roofLines = [...polygonLines, ...polygon.innerLines].filter((line) => {
|
||||
let startFlag = false
|
||||
let endFlag = false
|
||||
const startPoint = line.startPoint
|
||||
@ -1171,11 +1166,156 @@ export const usePolygon = () => {
|
||||
return startFlag && endFlag
|
||||
})
|
||||
|
||||
roofLines.forEach((line) => {
|
||||
roof.lines.forEach((roofLine) => {
|
||||
if (
|
||||
(isSamePoint(line.startPoint, roofLine.startPoint) && isSamePoint(line.endPoint, roofLine.endPoint)) ||
|
||||
(isSamePoint(line.startPoint, roofLine.endPoint) && isSamePoint(line.endPoint, roofLine.startPoint))
|
||||
) {
|
||||
roofLine.attributes = { ...line.attributes }
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
canvas.add(roof)
|
||||
addLengthText(roof)
|
||||
// addLengthText(roof)
|
||||
canvas.remove(polygon)
|
||||
canvas.renderAll()
|
||||
})
|
||||
|
||||
//지붕 완료 후 보조선을 전부 제거한다.
|
||||
const auxiliaryLines = canvas.getObjects().filter((obj) => obj.name === 'auxiliaryLine')
|
||||
|
||||
auxiliaryLines.forEach((line) => {
|
||||
canvas.remove(line)
|
||||
})
|
||||
canvas.renderAll()
|
||||
canvas.discardActiveObject()
|
||||
}
|
||||
|
||||
const getSplitRoofsPoints = (allLines) => {
|
||||
// ==== Utility functions ====
|
||||
|
||||
function isSamePoint(p1, p2, epsilon = 1) {
|
||||
return Math.abs(p1.x - p2.x) <= 2 && Math.abs(p1.y - p2.y) <= 2
|
||||
}
|
||||
|
||||
function normalizePoint(p, epsilon = 1) {
|
||||
return {
|
||||
x: Math.round(p.x / epsilon) * epsilon,
|
||||
y: Math.round(p.y / epsilon) * epsilon,
|
||||
}
|
||||
}
|
||||
|
||||
function pointToKey(p, epsilon = 1) {
|
||||
const norm = normalizePoint(p, epsilon)
|
||||
return `${norm.x},${norm.y}`
|
||||
}
|
||||
|
||||
// 거리 계산
|
||||
function calcDistance(p1, p2) {
|
||||
return Math.hypot(p2.x - p1.x, p2.y - p1.y)
|
||||
}
|
||||
|
||||
// ==== Direct edge check ====
|
||||
|
||||
function isDirectlyConnected(start, end, graph, epsilon = 1) {
|
||||
const startKey = pointToKey(start, epsilon)
|
||||
return (graph[startKey] || []).some((neighbor) => isSamePoint(neighbor.point, end, epsilon))
|
||||
}
|
||||
// ==== Dijkstra pathfinding ====
|
||||
|
||||
function findShortestPath(start, end, graph, epsilon = 1) {
|
||||
const startKey = pointToKey(start, epsilon)
|
||||
const endKey = pointToKey(end, epsilon)
|
||||
|
||||
const distances = {}
|
||||
const previous = {}
|
||||
const visited = new Set()
|
||||
const queue = [{ key: startKey, dist: 0 }]
|
||||
|
||||
for (const key in graph) distances[key] = Infinity
|
||||
distances[startKey] = 0
|
||||
|
||||
while (queue.length > 0) {
|
||||
queue.sort((a, b) => a.dist - b.dist)
|
||||
const { key } = queue.shift()
|
||||
if (visited.has(key)) continue
|
||||
visited.add(key)
|
||||
|
||||
for (const neighbor of graph[key] || []) {
|
||||
const neighborKey = pointToKey(neighbor.point, epsilon)
|
||||
const alt = distances[key] + neighbor.distance
|
||||
if (alt < distances[neighborKey]) {
|
||||
distances[neighborKey] = alt
|
||||
previous[neighborKey] = key
|
||||
queue.push({ key: neighborKey, dist: alt })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const path = []
|
||||
let currentKey = endKey
|
||||
|
||||
if (!previous[currentKey]) return null
|
||||
|
||||
while (currentKey !== startKey) {
|
||||
const [x, y] = currentKey.split(',').map(Number)
|
||||
path.unshift({ x, y })
|
||||
currentKey = previous[currentKey]
|
||||
}
|
||||
|
||||
const [sx, sy] = startKey.split(',').map(Number)
|
||||
path.unshift({ x: sx, y: sy })
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// 최종 함수
|
||||
function getPath(start, end, graph, epsilon = 1) {
|
||||
if (isDirectlyConnected(start, end, graph, epsilon)) {
|
||||
console.log('직선 연결 있음. 무시.')
|
||||
return []
|
||||
}
|
||||
|
||||
const path = findShortestPath(start, end, graph, epsilon)
|
||||
if (!path || path.length < 3) {
|
||||
console.log('경로 존재하나 3개 미만 좌표. 무시.')
|
||||
return []
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
const roofs = []
|
||||
|
||||
allLines.forEach((line) => {
|
||||
// 그래프 생성
|
||||
const graph = {}
|
||||
const edges = allLines
|
||||
.filter((line2) => line !== line2)
|
||||
.map((line) => {
|
||||
return [line.startPoint, line.endPoint]
|
||||
})
|
||||
|
||||
for (const [p1, p2] of edges) {
|
||||
const key1 = pointToKey(p1)
|
||||
const key2 = pointToKey(p2)
|
||||
const distance = calcDistance(p1, p2)
|
||||
|
||||
if (!graph[key1]) graph[key1] = []
|
||||
if (!graph[key2]) graph[key2] = []
|
||||
|
||||
graph[key1].push({ point: p2, distance })
|
||||
graph[key2].push({ point: p1, distance })
|
||||
}
|
||||
|
||||
const startPoint = { ...line.startPoint } // 시작점
|
||||
let arrivalPoint = { ...line.endPoint } // 도착점
|
||||
roofs.push(getPath(startPoint, arrivalPoint, graph))
|
||||
})
|
||||
|
||||
return removeDuplicatePolygons(roofs.filter((roof) => roof.length < 100))
|
||||
}
|
||||
|
||||
const splitPolygonWithSeparate = (separates) => {
|
||||
|
||||
@ -7,8 +7,9 @@ const GRID_PADDING = 5
|
||||
export function useTempGrid() {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const gridColor = useRecoilValue(gridColorState)
|
||||
const [tempGridMode, setTempGridMode] = useRecoilState(tempGridModeState)
|
||||
const isGridDisplay = useRecoilValue(gridDisplaySelector)
|
||||
const [tempGridMode, setTempGridMode] = useRecoilState(tempGridModeState)
|
||||
|
||||
const tempGridModeStateLeftClickEvent = (e) => {
|
||||
//임의 그리드 모드일 경우
|
||||
let pointer = canvas.getPointer(e.e)
|
||||
@ -35,37 +36,8 @@ export function useTempGrid() {
|
||||
canvas.renderAll()
|
||||
}
|
||||
|
||||
const tempGridRightClickEvent = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
//임의 그리드 모드일 경우
|
||||
let pointer = { x: e.offsetX, y: e.offsetY }
|
||||
|
||||
const tempGrid = new fabric.Line([-1500, pointer.y, 2500, pointer.y], {
|
||||
stroke: gridColor,
|
||||
strokeWidth: 1,
|
||||
selectable: true,
|
||||
lockMovementX: true,
|
||||
lockMovementY: true,
|
||||
lockRotation: true,
|
||||
lockScalingX: true,
|
||||
lockScalingY: true,
|
||||
strokeDashArray: [5, 2],
|
||||
opacity: 0.3,
|
||||
padding: GRID_PADDING,
|
||||
name: 'tempGrid',
|
||||
visible: isGridDisplay,
|
||||
direction: 'horizontal',
|
||||
})
|
||||
|
||||
canvas.add(tempGrid)
|
||||
|
||||
canvas.renderAll()
|
||||
}
|
||||
|
||||
return {
|
||||
tempGridModeStateLeftClickEvent,
|
||||
tempGridRightClickEvent,
|
||||
tempGridMode,
|
||||
setTempGridMode,
|
||||
}
|
||||
|
||||
@ -125,9 +125,9 @@
|
||||
"modal.module.basic.settting.module.error6": "垂木の間隔を入力してください。\n(屋根材: {0})",
|
||||
"modal.module.basic.settting.module.error7": "下在ビーチを入力してください。\n(屋根材: {0})",
|
||||
"modal.module.basic.settting.module.error8": "モジュール配置領域の値を入力してください。\n(屋根材: {0})",
|
||||
"modal.module.basic.settting.module.error9": "軒側の値は{0} mm以上でなければなりません。\n(屋根材: {1})",
|
||||
"modal.module.basic.settting.module.error10": "吊下側の値は{0} mm以上でなければなりません。\n(屋根材: {1})",
|
||||
"modal.module.basic.settting.module.error11": "ケラバ側の値は{0} mm以上でなければなりません。\n(屋根材: {1})",
|
||||
"modal.module.basic.settting.module.error9": "軒側の配置領域の値を{0} mm以上に変更してください。\n(屋根材: {1})",
|
||||
"modal.module.basic.settting.module.error10": "棟側の配置領域の値を{0} mm以上に変更してください。\n(屋根材: {1})",
|
||||
"modal.module.basic.settting.module.error11": "ケラバ側の配置領域の値を{0} mm以上に変更してください。\n(屋根材: {1})",
|
||||
"modal.module.basic.settting.module.error12": "施工方法を選択してください。\n(屋根材: {0})",
|
||||
"modal.module.basic.setting.module.placement": "モジュールの配置",
|
||||
"modal.module.basic.setting.module.placement.select.fitting.type": "設置形態を選択してください。",
|
||||
@ -143,6 +143,7 @@
|
||||
"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.module.placement.margin.check1": "架台メーカーを選択してください。",
|
||||
"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": "指定した辺を基準に設置する",
|
||||
@ -596,6 +597,29 @@
|
||||
"board.sub.updDt": "更新",
|
||||
"board.sub.btn.close": "閉じる",
|
||||
"board.uptDt": "更新",
|
||||
"qna.sub.btn.inquiry": "お問い合わせ登録",
|
||||
"qna.title": "お問合せ",
|
||||
"qna.detail.sub.answer": "回答",
|
||||
"qna.detail.sub.fileList": "添付ファイル",
|
||||
"qna.sub.title": "お問合せリスト",
|
||||
"qna.reg.header.regDt": "お問い合わせ登録日",
|
||||
"qna.reg.header.regUserNm": "名前",
|
||||
"qna.reg.header.regUserTelNo": "お問い合わせ",
|
||||
"qna.reg.header.type": "お問い合わせ区分",
|
||||
"qna.reg.header.title": "お問い合わせタイトル",
|
||||
"qna.reg.header.contents": "お問い合わせ内容",
|
||||
"qna.reg.header.fileList": "ファイル添付",
|
||||
"qna.reg.header.save": "保存",
|
||||
"qna.reg.alert.require.regUserNm": "名前を入力してください。",
|
||||
"qna.reg.alert.select.type": "お問い合わせ区分を選択してください。",
|
||||
"qna.reg.alert.require.title": "タイトルを入力してください。",
|
||||
"qna.reg.alert.require.contents": "内容を入力してください。",
|
||||
"qna.reg.confirm.save": "1:1お問い合わせを登録しますか? <br/>Hanwha Japan 担当者にお問い合わせメールが送信されます。",
|
||||
"qna.reg.alert.save": "保存されました。",
|
||||
"qna.reg.alert.saveFail": "保存に失敗しました。",
|
||||
"qna.list.header.regNm": "登録者",
|
||||
"qna.list.header.answer.yes": "回答完了",
|
||||
"qna.list.header.answer.no": "未回答",
|
||||
"myinfo.title": "社員情報",
|
||||
"myinfo.info.userId": "ユーザーID",
|
||||
"myinfo.info.nameKana": "担当者名日本語",
|
||||
@ -732,7 +756,7 @@
|
||||
"stuff.detail.tooltip.surfaceType": "塩害地域の定義は各メーカーの設置マニュアルをご確認ください",
|
||||
"stuff.detail.tempSave.message0": "一時保存されました。 物件番号を取得するには、保存ボタンを押して下さい。,",
|
||||
"stuff.detail.tempSave.message1": "一時保存されました。物件番号を取得するには、必須項目をすべて入力してください。",
|
||||
"stuff.detail.tempSave.message2": "担当者名は10桁以下で入力してください。",
|
||||
"stuff.detail.tempSave.message2": "担当者名は全角20文字(半角40文字)以下で入力してください.",
|
||||
"stuff.detail.tempSave.message3": "二次販売店を選択してください。",
|
||||
"stuff.detail.confirm.message1": "販売店情報を変更すると、設計依頼文書番号が削除されます。変更しますか?",
|
||||
"stuff.detail.delete.message1": "仕様が確定したものは削除できません。",
|
||||
@ -786,7 +810,7 @@
|
||||
"stuff.search.schObjectNo": "物件番号",
|
||||
"stuff.search.schSaleStoreName": "販売代理店名",
|
||||
"stuff.search.schAddress": "商品アドレス",
|
||||
"stuff.search.schObjectName": "商品名",
|
||||
"stuff.search.schObjectName": "物件名",
|
||||
"stuff.search.schDispCompanyName": "見積先",
|
||||
"stuff.search.schSelSaleStoreId": "販売代理店選択",
|
||||
"stuff.search.schReceiveUser": "担当者",
|
||||
|
||||
@ -597,6 +597,29 @@
|
||||
"board.sub.updDt": "업데이트",
|
||||
"board.sub.btn.close": "닫기",
|
||||
"board.uptDt": "갱신",
|
||||
"qna.sub.btn.inquiry": "문의등록",
|
||||
"qna.title": "문의",
|
||||
"qna.sub.title": "문의 목록",
|
||||
"qna.detail.sub.answer": "답변",
|
||||
"qna.detail.sub.fileList": "첨부파일",
|
||||
"qna.reg.header.regDt": "문의등록일",
|
||||
"qna.reg.header.regUserNm": "이름",
|
||||
"qna.reg.header.regUserTelNo": "연락처",
|
||||
"qna.reg.header.type": "문의구분",
|
||||
"qna.reg.header.title": "문의제목",
|
||||
"qna.reg.header.contents": "문의정보",
|
||||
"qna.reg.header.fileList": "파일첨부",
|
||||
"qna.reg.header.save": "저장",
|
||||
"qna.reg.alert.require.regUserNm": "이름을 입력하세요.",
|
||||
"qna.reg.alert.select.type": "문의구분을 선택하세요.",
|
||||
"qna.reg.alert.require.title": "제목을 입력하세요.",
|
||||
"qna.reg.alert.require.contents": "내용을 입력하세요.",
|
||||
"qna.reg.confirm.save": "1:1문의를 등록하시겠습니까? <br/> Hanwha Japan 담당자에게 문의메일이 발송됩니다.",
|
||||
"qna.reg.alert.save": "저장 되었습니다.",
|
||||
"qna.reg.alert.saveFail": "저장에 실패하였습니다.",
|
||||
"qna.list.header.regNm": "등록자",
|
||||
"qna.list.header.answer.yes": "답변완료",
|
||||
"qna.list.header.answer.no": "미답변",
|
||||
"myinfo.title": "내정보",
|
||||
"myinfo.info.userId": "사용자ID",
|
||||
"myinfo.info.nameKana": "담당자명 일본어",
|
||||
@ -733,7 +756,7 @@
|
||||
"stuff.detail.tooltip.surfaceType": "염해지역 정의는 각 메이커의 설치 매뉴얼을 확인해주십시오",
|
||||
"stuff.detail.tempSave.message0": "임시저장 되었습니다. 물건번호를 획득하려면 저장버튼을 눌러주십시오.",
|
||||
"stuff.detail.tempSave.message1": "임시저장 되었습니다. 물건번호를 획득하려면 필수 항목을 모두 입력해 주십시오.",
|
||||
"stuff.detail.tempSave.message2": "담당자이름은 10자리 이하로 입력해 주십시오.",
|
||||
"stuff.detail.tempSave.message2": "담당자이름은 전각20자(반각40자) 이하로 입력해 주십시오.",
|
||||
"stuff.detail.tempSave.message3": "2차 판매점을 선택해주세요.",
|
||||
"stuff.detail.confirm.message1": "판매점 정보를 변경하면 설계의뢰 문서번호가 삭제됩니다. 변경하시겠습니까?",
|
||||
"stuff.detail.delete.message1": "사양이 확정된 물건은 삭제할 수 없습니다.",
|
||||
@ -1052,6 +1075,7 @@
|
||||
"module.not.found": "모듈을 선택하세요.",
|
||||
"module.circuit.minimun.error": "회로번호는 1 이상입력해주세요.",
|
||||
"module.already.exist.error": "회로번호가 같은 다른 파워 컨디셔너 모듈이 있습니다. 다른 회로번호를 설정하십시오.",
|
||||
"module.circuit.fix.not.same.roof.error": "다른 지붕면의 모듈이 선택되어 있습니다. 모듈 선택을 다시 하세요.",
|
||||
"construction.length.difference": "지붕면 공법을 모두 선택하십시오.",
|
||||
"menu.validation.canvas.roof": "패널을 배치하려면 지붕면을 입력해야 합니다.",
|
||||
"batch.object.outside.roof": "오브젝트는 지붕내에 설치해야 합니다.",
|
||||
|
||||
@ -151,6 +151,7 @@ button{
|
||||
.al-r{text-align: right !important;}
|
||||
.al-c{text-align: center !important;}
|
||||
|
||||
.org{color: #f86a56 !important;}
|
||||
|
||||
// button
|
||||
.btn-frame{
|
||||
|
||||
@ -388,6 +388,10 @@
|
||||
border: 1px solid #eee;
|
||||
.drag-file-area{
|
||||
margin-top: 0;
|
||||
.file-list {
|
||||
overflow-y: auto;
|
||||
max-height: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -360,16 +360,16 @@ export const calculateIntersection = (line1, line2) => {
|
||||
|
||||
// Check if the intersection X and Y are within the range of both lines
|
||||
if (
|
||||
result[0] >= line1MinX &&
|
||||
result[0] <= line1MaxX &&
|
||||
result[0] >= line2MinX &&
|
||||
result[0] <= line2MaxX &&
|
||||
result[1] >= line1MinY &&
|
||||
result[1] <= line1MaxY &&
|
||||
result[1] >= line2MinY &&
|
||||
result[1] <= line2MaxY
|
||||
result[0] >= line1MinX - 1 &&
|
||||
result[0] <= line1MaxX + 1 &&
|
||||
result[0] >= line2MinX - 1 &&
|
||||
result[0] <= line2MaxX + 1 &&
|
||||
result[1] >= line1MinY - 1 &&
|
||||
result[1] <= line1MaxY + 1 &&
|
||||
result[1] >= line2MinY - 1 &&
|
||||
result[1] <= line2MaxY + 1
|
||||
) {
|
||||
return { x: Math.round(result[0]), y: Math.round(result[1]) }
|
||||
return { x: result[0], y: result[1] }
|
||||
} else {
|
||||
return null // Intersection is out of range
|
||||
}
|
||||
@ -515,24 +515,39 @@ export const sortedPointLessEightPoint = (points) => {
|
||||
* @param line
|
||||
* @param point
|
||||
* @returns {boolean}
|
||||
* @param epsilon
|
||||
*/
|
||||
// 직선의 방정식.
|
||||
// 방정식은 ax + by + c = 0이며, 점의 좌표를 대입하여 계산된 값은 직선과 점 사이의 관계를 나타낸다.
|
||||
export function isPointOnLine({ x1, y1, x2, y2 }, { x, y }) {
|
||||
/*const a = line.y2 - line.y1
|
||||
export function isPointOnLine({ x1, y1, x2, y2 }, { x, y }, epsilon = 2) {
|
||||
/*const a = y2 - y1
|
||||
const b = x1 - x2
|
||||
const c = x2 * y1 - x1 * y2
|
||||
return Math.abs(a * x + b * y + c) < 1000*/
|
||||
/*/!*const a = line.y2 - line.y1
|
||||
const b = line.x1 - line.x2
|
||||
const c = line.x2 * line.y1 - line.x1 * line.y2
|
||||
const result = Math.abs(a * point.x + b * point.y + c) / 100
|
||||
|
||||
// 점이 선 위에 있는지 확인
|
||||
return result <= 10*/
|
||||
return result <= 10*!*/
|
||||
// 직선 방정식 만족 여부 확인
|
||||
const crossProduct = (y - y1) * (x2 - x1) - (x - x1) * (y2 - y1)
|
||||
if (Math.abs(crossProduct) > 5) return false // 작은 오차 허용
|
||||
if (Math.abs(crossProduct) > 1000) return false // 작은 오차 허용
|
||||
|
||||
const isSameX = Math.abs(x1 - x2) < 2
|
||||
const isSameY = Math.abs(y1 - y2) < 2
|
||||
|
||||
// 점이 선분의 범위 내에 있는지 확인
|
||||
const withinXRange = Math.min(x1, x2) <= x && x <= Math.max(x1, x2)
|
||||
const withinYRange = Math.min(y1, y2) <= y && y <= Math.max(y1, y2)
|
||||
let withinXRange = Math.min(x1, x2) - x <= 2
|
||||
if (!isSameX) {
|
||||
withinXRange = withinXRange && 2 <= Math.max(x1, x2) - x
|
||||
}
|
||||
|
||||
let withinYRange = Math.min(y1, y2) - y <= 2
|
||||
if (!isSameY) {
|
||||
withinYRange = withinYRange && 2 <= Math.max(y1, y2) - y
|
||||
}
|
||||
|
||||
return withinXRange && withinYRange
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user