Merge branch 'dev' into feature/test-jy
# Conflicts: # src/components/fabric/QPolygon.js
This commit is contained in:
commit
11e84ed614
4320
package-lock.json
generated
4320
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -24,10 +24,12 @@
|
||||
"react-datepicker": "^7.3.0",
|
||||
"react-dom": "^18",
|
||||
"react-responsive-modal": "^6.4.2",
|
||||
"react-toastify": "^10.0.5",
|
||||
"recoil": "^0.7.7",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@turf/turf": "^7.0.0",
|
||||
"postcss": "^8",
|
||||
"prettier": "^3.3.3",
|
||||
"prisma": "^5.18.0",
|
||||
|
||||
@ -1,16 +1,26 @@
|
||||
'use client'
|
||||
|
||||
import { Button, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from '@nextui-org/react'
|
||||
|
||||
import { AxiosType, useAxios } from '@/hooks/useAxios'
|
||||
// import { get } from '@/lib/Axios'
|
||||
|
||||
import QSelect from '@/components/ui/QSelect'
|
||||
|
||||
import styles from './changelog.module.css'
|
||||
import { get } from '@/lib/Axios'
|
||||
|
||||
export default function changelogPage() {
|
||||
const { get } = useAxios()
|
||||
const testVar = process.env.NEXT_PUBLIC_TEST
|
||||
|
||||
const handleUsers = async () => {
|
||||
const users = await get('/api/user/find-all')
|
||||
console.log(users)
|
||||
// const users = await get('/api/user/find-all')
|
||||
const params = {
|
||||
type: AxiosType.INTERNAL,
|
||||
url: '/api/user/find-all',
|
||||
}
|
||||
const users = await get(params)
|
||||
console.log('users', users)
|
||||
}
|
||||
|
||||
const data = [
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
import { Inter } from 'next/font/google'
|
||||
import './globals.css'
|
||||
import '../styles/style.scss'
|
||||
import Headers from '@/components/Headers'
|
||||
|
||||
import RecoilRootWrapper from './RecoilWrapper'
|
||||
import UIProvider from './UIProvider'
|
||||
import { headers } from 'next/headers'
|
||||
import Headers from '@/components/Headers'
|
||||
|
||||
import { ToastContainer } from 'react-toastify'
|
||||
import QModal from '@/components/common/modal/QModal'
|
||||
|
||||
import './globals.css'
|
||||
import '../styles/style.scss'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
@ -24,6 +29,8 @@ export default function RootLayout({ children }) {
|
||||
{headerPathname !== '/login' && <Headers />}
|
||||
<RecoilRootWrapper>
|
||||
<UIProvider>{children}</UIProvider>
|
||||
<QModal />
|
||||
<ToastContainer />
|
||||
</RecoilRootWrapper>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -4,15 +4,22 @@ import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import Link from 'next/link'
|
||||
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { modalContent, modalState } from '@/store/modalAtom'
|
||||
|
||||
import { AxiosType, useAxios } from '@/hooks/useAxios'
|
||||
|
||||
import { Button } from '@nextui-org/react'
|
||||
|
||||
import SingleDatePicker from './common/datepicker/SingleDatePicker'
|
||||
import RangeDatePicker from './common/datepicker/RangeDatePicker'
|
||||
import QGrid from './common/grid/QGrid'
|
||||
import QModal from './common/modal/QModal'
|
||||
import { QToast } from '@/hooks/useToast'
|
||||
|
||||
export default function Intro() {
|
||||
const [open, setOpen] = useState(false)
|
||||
const { get } = useAxios()
|
||||
|
||||
// const [open, setOpen] = useState(false)
|
||||
const [startDate, setStartDate] = useState(new Date())
|
||||
const singleDatePickerProps = {
|
||||
startDate,
|
||||
@ -35,6 +42,9 @@ export default function Intro() {
|
||||
isPageable: false,
|
||||
})
|
||||
|
||||
const [open, setOpen] = useRecoilState(modalState)
|
||||
const [contents, setContent] = useRecoilState(modalContent)
|
||||
|
||||
const modelProps = {
|
||||
open,
|
||||
setOpen,
|
||||
@ -60,8 +70,9 @@ export default function Intro() {
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
const response = await fetch('https://www.ag-grid.com/example-assets/space-mission-data.json')
|
||||
const data = await response.json()
|
||||
// const response = await fetch('https://www.ag-grid.com/example-assets/space-mission-data.json')
|
||||
// const data = await response.json()
|
||||
const data = await get({ type: AxiosType.EXTERNAL, url: 'https://www.ag-grid.com/example-assets/space-mission-data.json' })
|
||||
setGridProps({ ...gridProps, gridData: data })
|
||||
}
|
||||
fetchData()
|
||||
@ -95,10 +106,35 @@ export default function Intro() {
|
||||
<div className="my-2">
|
||||
<div className="text-2xl">QModal</div>
|
||||
<div>
|
||||
<Button color="primary" onClick={() => setOpen(true)}>
|
||||
{/* <Button color="primary" onClick={() => setOpen(true)}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<QModal {...modelProps}>{ipsum}</QModal>
|
||||
<QModal {...modelProps}>{ipsum}</QModal> */}
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setContent(ipsum)
|
||||
setOpen(true)
|
||||
}}
|
||||
>
|
||||
Open Modal
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-2">
|
||||
<div className="text-2xl">QToast</div>
|
||||
<div>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
QToast({
|
||||
message: 'This is a toast message',
|
||||
type: 'success',
|
||||
})
|
||||
}}
|
||||
>
|
||||
Open Toast
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -2,14 +2,15 @@ import { useCanvas } from '@/hooks/useCanvas'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Mode, useMode } from '@/hooks/useMode'
|
||||
import { Button } from '@nextui-org/react'
|
||||
|
||||
import RangeSlider from './ui/RangeSlider'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import { canvasSizeState, fontSizeState, roofMaterialState, sortedPolygonArray, templateTypeState } from '@/store/canvasAtom'
|
||||
import { canvasSizeState, fontSizeState, roofMaterialState, sortedPolygonArray, templateTypeState, compassState } from '@/store/canvasAtom'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import { getCanvasState, insertCanvasState } from '@/lib/canvas'
|
||||
import { calculateIntersection } from '@/util/canvas-util'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
import * as turf from '@turf/turf'
|
||||
import { toGeoJSON } from '@/util/qpolygon-utils'
|
||||
|
||||
export default function Roof2() {
|
||||
const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas')
|
||||
@ -35,6 +36,8 @@ export default function Roof2() {
|
||||
|
||||
const [templateType, setTemplateType] = useRecoilState(templateTypeState)
|
||||
|
||||
const [compass, setCompass] = useRecoilState(compassState)
|
||||
|
||||
const {
|
||||
mode,
|
||||
setMode,
|
||||
@ -51,6 +54,7 @@ export default function Roof2() {
|
||||
makeRoofPatternPolygon,
|
||||
createRoofRack,
|
||||
drawRoofPolygon,
|
||||
drawCellInTrestle,
|
||||
} = useMode()
|
||||
|
||||
// const [canvasState, setCanvasState] = useRecoilState(canvasAtom)
|
||||
@ -492,6 +496,10 @@ export default function Roof2() {
|
||||
})
|
||||
}
|
||||
|
||||
const setCompassState = (degree) => {
|
||||
setCompass(degree)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{canvas && (
|
||||
@ -521,12 +529,27 @@ export default function Roof2() {
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(2)}>
|
||||
지붕패턴2
|
||||
</Button>
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(90)}>
|
||||
방위표◀
|
||||
</Button>
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(270)}>
|
||||
방위표▶
|
||||
</Button>
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(180)}>
|
||||
방위표▲
|
||||
</Button>
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(0)}>
|
||||
방위표▼
|
||||
</Button>
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.ROOF_TRESTLE)}>
|
||||
지붕가대생성
|
||||
</Button>
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.FILL_CELLS)}>
|
||||
태양광셀생성
|
||||
</Button>
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.CELL_POWERCON)}>
|
||||
파워콘설치
|
||||
</Button>
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.TEXTBOX ? 'primary' : 'default'}`} onClick={() => setMode(Mode.TEXTBOX)}>
|
||||
텍스트박스 모드
|
||||
</Button>
|
||||
@ -542,9 +565,10 @@ export default function Roof2() {
|
||||
{/*<Button className="m-1 p-2" onClick={zoomIn}>
|
||||
확대
|
||||
</Button>
|
||||
*/}
|
||||
<Button className="m-1 p-2" onClick={zoomOut}>
|
||||
축소
|
||||
</Button>*/}
|
||||
</Button>
|
||||
현재 줌 : {zoom}%
|
||||
<Button className="m-1 p-2" onClick={makeLine}>
|
||||
선 추가
|
||||
@ -596,7 +620,10 @@ export default function Roof2() {
|
||||
지붕타입 지붕재
|
||||
</Button>
|
||||
<Button className="m-1 p-2" onClick={createRoofRack}>
|
||||
지붕가대
|
||||
지붕가대설치
|
||||
</Button>
|
||||
<Button className="m-1 p-2" onClick={drawCellInTrestle}>
|
||||
선택한 가대 셀채우기
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -1,8 +1,16 @@
|
||||
'use client'
|
||||
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
|
||||
import { Modal } from 'react-responsive-modal'
|
||||
|
||||
import { modalContent, modalState } from '@/store/modalAtom'
|
||||
|
||||
import 'react-responsive-modal/styles.css'
|
||||
|
||||
export default function QModal(props) {
|
||||
const { open, setOpen, children } = props
|
||||
export default function QModal() {
|
||||
const [open, setOpen] = useRecoilState(modalState)
|
||||
const children = useRecoilValue(modalContent)
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={() => setOpen(false)} center>
|
||||
|
||||
@ -2,7 +2,8 @@ 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, drawHippedRoof } from '@/util/qpolygon-utils'
|
||||
import { calculateAngle, drawHippedRoof, inPolygon, lineIntersect, splitPolygonWithLines, toGeoJSON } from '@/util/qpolygon-utils'
|
||||
import * as turf from '@turf/turf'
|
||||
|
||||
export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
type: 'QPolygon',
|
||||
@ -238,14 +239,12 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
|
||||
const rectPoints = [
|
||||
{ x: rectLeft, y: rectTop },
|
||||
{ x: rectLeft + rectWidth, y: rectTop },
|
||||
{ x: rectLeft, y: rectTop + rectHeight },
|
||||
{ x: rectLeft + rectWidth, y: rectTop + rectHeight },
|
||||
{ x: rectLeft + rectWidth, y: rectTop },
|
||||
]
|
||||
|
||||
const allPointsInside = rectPoints.every((point) => this.inPolygon(point))
|
||||
|
||||
if (allPointsInside) {
|
||||
if (inPolygon(this.points, rectPoints)) {
|
||||
const rect = new fabric.Rect({
|
||||
left: rectLeft,
|
||||
top: rectTop,
|
||||
@ -275,7 +274,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
this.cells = drawCellsArray
|
||||
return drawCellsArray
|
||||
},
|
||||
fillCellABTemplate(cell = { width: 50, height: 100, padding: 5, parallelPoint: undefined, startX: 0, startY: 0 }) {
|
||||
fillCellABType(cell = { width: 50, height: 100, padding: 5, wallDirection: 'left', referenceDirection: 'none', startIndex: -1 }) {
|
||||
const points = this.points
|
||||
|
||||
const minX = Math.min(...points.map((p) => p.x)) //왼쪽
|
||||
@ -293,58 +292,226 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
const rows = Math.floor((boundingBoxHeight + cell.padding) / (rectHeight + cell.padding))
|
||||
|
||||
//전체 높이에서 패딩을 포함하고 rows를 곱해서 여백길이를 계산 후에 2로 나누면 반높이를 넣어서 중간으로 정렬
|
||||
const tmpHeight = (boundingBoxHeight - (rectHeight + cell.padding) * rows) / 2
|
||||
//센터 정렬시에 쓴다 체크박스가 존재함 TODO: if문 추가해서 정렬해야함
|
||||
const tmpWidth = (boundingBoxWidth - (rectWidth + cell.padding) * cols) / 2
|
||||
|
||||
let centerHeight = rows > 1 ? (boundingBoxHeight - (rectHeight + cell.padding / 2) * rows) / 2 : (boundingBoxHeight - rectHeight * rows) / 2 //rows 1개 이상이면 cell 을 반 나눠서 중간을 맞춘다
|
||||
let centerWidth = cols > 1 ? (boundingBoxWidth - (rectWidth + cell.padding / 2) * cols) / 2 : (boundingBoxWidth - rectWidth * cols) / 2
|
||||
|
||||
const drawCellsArray = [] //그려진 셀의 배열
|
||||
|
||||
let idx = 1
|
||||
|
||||
if (cell.parallelPoint > -1) {
|
||||
//4각형 이상이면 각 꼭지점에서 위, 아래로 이동하면서 채움
|
||||
//앞에서 -1로 선언함
|
||||
if (cell.parallelPoint === 1 || cell.parallelPoint === 2) {
|
||||
//ㄴ자 역ㄱ자
|
||||
//밑에서 위로 올라가야하면
|
||||
cell.startY = cell.startY - (rectHeight + cell.padding) * rows
|
||||
if (cell.parallelPoint === 2) {
|
||||
cell.startX = cell.startX - (rectWidth + cell.padding) * cols
|
||||
}
|
||||
} else {
|
||||
if (cell.parallelPoint === 5) {
|
||||
cell.startX = cell.startX - (rectWidth + cell.padding) * cols
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let startXPos, startYPos
|
||||
|
||||
for (let i = 0; i < cols; i++) {
|
||||
for (let j = 0; j < rows; j++) {
|
||||
if (cell.parallelPoint > -1) {
|
||||
startXPos = cell.startX + i * (rectWidth + cell.padding)
|
||||
startYPos = cell.startY + j * (rectHeight + cell.padding)
|
||||
} else {
|
||||
startXPos = minX + i * (rectWidth + cell.padding)
|
||||
startYPos = minY + j * (rectHeight + cell.padding)
|
||||
}
|
||||
|
||||
const rectPoints = []
|
||||
|
||||
rectPoints.push(
|
||||
{ x: startXPos, y: startYPos },
|
||||
{ x: startXPos + rectWidth, y: startYPos },
|
||||
{ x: startXPos, y: startYPos + rectHeight },
|
||||
{ x: startXPos + rectWidth, y: startYPos + rectHeight },
|
||||
)
|
||||
if (cell.referenceDirection !== 'none') {
|
||||
//4각형은 기준점이 없다
|
||||
|
||||
const allPointsInside = rectPoints.every((point) => this.inPolygon(point))
|
||||
if (cell.referenceDirection === 'top') {
|
||||
//top, bottom은 A패턴만
|
||||
if (cell.wallDirection === 'left') {
|
||||
startXPos = minX + i * rectWidth
|
||||
startYPos = minY + j * rectHeight
|
||||
|
||||
if (i > 0) {
|
||||
startXPos = startXPos + i * cell.padding //옆으로 패딩
|
||||
}
|
||||
} else {
|
||||
startXPos = maxX - (1 + i) * rectWidth - 0.01
|
||||
startYPos = minY + j * rectHeight + 0.01
|
||||
|
||||
if (i > 0) {
|
||||
startXPos = startXPos - i * cell.padding //옆으로 패딩
|
||||
}
|
||||
}
|
||||
|
||||
if (j > 0) {
|
||||
startYPos = startYPos + j * cell.padding
|
||||
}
|
||||
|
||||
rectPoints.push(
|
||||
{ x: startXPos, y: startYPos },
|
||||
{ x: startXPos + rectWidth, y: startYPos },
|
||||
{ x: startXPos, y: startYPos + rectHeight },
|
||||
{ x: startXPos + rectWidth, y: startYPos + rectHeight },
|
||||
)
|
||||
} else if (cell.referenceDirection === 'bottom') {
|
||||
if (cell.wallDirection === 'left') {
|
||||
startXPos = minX + i * rectWidth
|
||||
startYPos = maxY - j * rectHeight - 0.01
|
||||
|
||||
if (i > 0) {
|
||||
startXPos = startXPos + i * cell.padding
|
||||
}
|
||||
|
||||
rectPoints.push(
|
||||
{ x: startXPos, y: startYPos },
|
||||
{ x: startXPos + rectWidth, y: startYPos },
|
||||
{ x: startXPos, y: startYPos - rectHeight },
|
||||
{ x: startXPos + rectWidth, y: startYPos - rectHeight },
|
||||
)
|
||||
} else {
|
||||
startXPos = maxX - i * rectWidth - 0.01
|
||||
startYPos = maxY - j * rectHeight - 0.01
|
||||
|
||||
if (i > 0) {
|
||||
startXPos = startXPos - i * cell.padding
|
||||
}
|
||||
|
||||
rectPoints.push(
|
||||
{ x: startXPos, y: startYPos },
|
||||
{ x: startXPos - rectWidth, y: startYPos },
|
||||
{ x: startXPos, y: startYPos - rectHeight },
|
||||
{ x: startXPos - rectWidth, y: startYPos - rectHeight },
|
||||
)
|
||||
startXPos = startXPos - rectWidth //우 -> 좌 들어가야해서 마이너스 처리
|
||||
}
|
||||
startYPos = startYPos - rectHeight //밑에서 위로 올라가는거라 마이너스 처리
|
||||
|
||||
if (j > 0) {
|
||||
startYPos = startYPos - j * cell.padding
|
||||
}
|
||||
} else if (cell.referenceDirection === 'left') {
|
||||
//여기서부턴 B패턴임
|
||||
if (cell.wallDirection === 'top') {
|
||||
startXPos = minX + i * rectWidth
|
||||
startYPos = minY + j * rectHeight
|
||||
|
||||
if (i > 0) {
|
||||
startXPos = startXPos + i * cell.padding //밑으로
|
||||
}
|
||||
if (j > 0) {
|
||||
startYPos = startYPos + j * cell.padding //옆으로 패딩
|
||||
}
|
||||
|
||||
rectPoints.push(
|
||||
{ x: startXPos, y: startYPos },
|
||||
{ x: startXPos + rectWidth, y: startYPos },
|
||||
{ x: startXPos, y: startYPos + rectHeight },
|
||||
{ x: startXPos + rectWidth, y: startYPos + rectHeight },
|
||||
)
|
||||
} else {
|
||||
startXPos = minX + i * rectWidth
|
||||
startYPos = maxY - j * rectHeight - 0.01
|
||||
|
||||
if (i > 0) {
|
||||
startXPos = startXPos + i * cell.padding
|
||||
}
|
||||
|
||||
if (j > 0) {
|
||||
startYPos = startYPos - j * cell.padding
|
||||
}
|
||||
|
||||
rectPoints.push(
|
||||
{ x: startXPos, y: startYPos },
|
||||
{ x: startXPos + rectWidth, y: startYPos },
|
||||
{ x: startXPos, y: startYPos - rectHeight },
|
||||
{ x: startXPos + rectWidth, y: startYPos - rectHeight },
|
||||
)
|
||||
|
||||
startYPos = startYPos - rectHeight //밑에서 위로 올라가는거라 마이너스 처리
|
||||
}
|
||||
} else if (cell.referenceDirection === 'right') {
|
||||
if (cell.wallDirection === 'top') {
|
||||
startXPos = maxX - i * rectWidth - 0.01
|
||||
startYPos = minY + j * rectHeight + 0.01
|
||||
|
||||
if (j > 0) {
|
||||
startYPos = startYPos + j * cell.padding //위에서 밑으로라 +
|
||||
}
|
||||
|
||||
rectPoints.push(
|
||||
{ x: startXPos, y: startYPos },
|
||||
{ x: startXPos - rectWidth, y: startYPos },
|
||||
{ x: startXPos, y: startYPos + rectHeight },
|
||||
{ x: startXPos - rectWidth, y: startYPos + rectHeight },
|
||||
)
|
||||
} else {
|
||||
startXPos = maxX - i * rectWidth - 0.01
|
||||
startYPos = maxY - j * rectHeight - 0.01
|
||||
|
||||
if (j > 0) {
|
||||
startYPos = startYPos - j * cell.padding
|
||||
}
|
||||
rectPoints.push(
|
||||
{ x: startXPos, y: startYPos },
|
||||
{ x: startXPos - rectWidth, y: startYPos },
|
||||
{ x: startXPos, y: startYPos - rectHeight },
|
||||
{ x: startXPos - rectWidth, y: startYPos - rectHeight },
|
||||
)
|
||||
startYPos = startYPos - rectHeight //밑에서 위로 올라가는거라 마이너스 처리
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
startXPos = startXPos - i * cell.padding //옆으로 패딩
|
||||
}
|
||||
startXPos = startXPos - rectWidth // 우측에서 -> 좌측으로 그려짐
|
||||
}
|
||||
} else {
|
||||
// centerWidth = 0 //나중에 중간 정렬 이면 어쩌구 함수 만들어서 넣음
|
||||
if (['left', 'right'].includes(cell.wallDirection)) {
|
||||
centerWidth = 0
|
||||
} else if (['top', 'bottom'].includes(cell.wallDirection)) {
|
||||
centerHeight = 0
|
||||
}
|
||||
|
||||
if (cell.wallDirection === 'left') {
|
||||
startXPos = minX + i * rectWidth + centerWidth
|
||||
startYPos = minY + j * rectHeight + centerHeight
|
||||
if (i > 0) {
|
||||
startXPos = startXPos + i * cell.padding
|
||||
}
|
||||
if (j > 0 && j < rows) {
|
||||
startYPos = startYPos + j * cell.padding
|
||||
}
|
||||
} else if (cell.wallDirection === 'right') {
|
||||
startXPos = maxX - (1 + i) * rectWidth - 0.01 - centerWidth
|
||||
startYPos = minY + j * rectHeight + 0.01 + centerHeight
|
||||
if (i > 0) {
|
||||
startXPos = startXPos - i * cell.padding
|
||||
}
|
||||
if (j > 0 && j < rows) {
|
||||
startYPos = startYPos + j * cell.padding
|
||||
}
|
||||
} else if (cell.wallDirection === 'top') {
|
||||
startXPos = minX + i * rectWidth - 0.01 + centerWidth
|
||||
startYPos = minY + j * rectHeight + 0.01 + centerHeight
|
||||
|
||||
if (i > 0) {
|
||||
startXPos = startXPos + i * cell.padding
|
||||
}
|
||||
if (j > 0 && j < rows) {
|
||||
startYPos = startYPos + j * cell.padding
|
||||
}
|
||||
} else if (cell.wallDirection === 'bottom') {
|
||||
startXPos = minX + i * rectWidth + 0.01 + centerWidth
|
||||
startYPos = maxY - (j + 1) * rectHeight - 0.01 - centerHeight
|
||||
|
||||
if (i > 0) {
|
||||
startXPos = startXPos + i * cell.padding
|
||||
}
|
||||
|
||||
if (j > 0 && j < rows) {
|
||||
startYPos = startYPos - j * cell.padding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const allPointsInside = rectPoints.every((point) => this.inPolygonABType(point.x, point.y, points))
|
||||
|
||||
if (allPointsInside) {
|
||||
//먼저 그룹화를 시켜놓고 뒤에서 글씨를 넣어서 변경한다
|
||||
const text = new fabric.Text(``, {
|
||||
fontFamily: 'serif',
|
||||
fontSize: 30,
|
||||
fill: 'black',
|
||||
type: 'cellText',
|
||||
})
|
||||
|
||||
const rect = new fabric.Rect({
|
||||
left: startXPos,
|
||||
top: startYPos,
|
||||
// left: startXPos,
|
||||
// top: startYPos,
|
||||
width: rectWidth,
|
||||
height: rectHeight,
|
||||
fill: '#BFFD9F',
|
||||
@ -358,15 +525,22 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
opacity: 0.8,
|
||||
name: 'cell',
|
||||
idx: idx,
|
||||
type: 'cellRect',
|
||||
})
|
||||
|
||||
var group = new fabric.Group([rect, text], {
|
||||
left: startXPos,
|
||||
top: startYPos,
|
||||
})
|
||||
|
||||
idx++
|
||||
drawCellsArray.push(rect) //배열에 넣어서 반환한다
|
||||
this.canvas.add(rect)
|
||||
drawCellsArray.push(group) //배열에 넣어서 반환한다
|
||||
this.canvas.add(group)
|
||||
this.canvas?.renderAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
this.canvas?.renderAll()
|
||||
|
||||
this.cells = drawCellsArray
|
||||
return drawCellsArray
|
||||
},
|
||||
@ -402,6 +576,57 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
return intersects % 2 === 1
|
||||
},
|
||||
|
||||
inPolygonABType(x, y, polygon) {
|
||||
let inside = false
|
||||
let n = polygon.length
|
||||
|
||||
for (let i = 0, j = n - 1; i < n; j = i++) {
|
||||
let xi = polygon[i].x
|
||||
let yi = polygon[i].y
|
||||
let xj = polygon[j].x
|
||||
let yj = polygon[j].y
|
||||
|
||||
// console.log('xi : ', xi, 'yi : ', yi, 'xj : ', xj, 'yj : ', yj)
|
||||
|
||||
let intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi
|
||||
if (intersect) inside = !inside
|
||||
}
|
||||
|
||||
return inside
|
||||
},
|
||||
|
||||
inPolygon2(rectPoints) {
|
||||
const polygonCoords = toGeoJSON(this.points)
|
||||
const rectCoords = toGeoJSON(rectPoints)
|
||||
|
||||
const outerPolygon = turf.polygon([polygonCoords])
|
||||
const innerPolygon = turf.polygon([rectCoords])
|
||||
// 각 점이 다각형 내부에 있는지 확인
|
||||
const allPointsInside = rectCoords.every((coord) => {
|
||||
const point = turf.point(coord)
|
||||
return turf.booleanPointInPolygon(point, outerPolygon)
|
||||
})
|
||||
|
||||
// 사각형의 변 정의
|
||||
const rectEdges = [
|
||||
[rectCoords[0], rectCoords[1]],
|
||||
[rectCoords[1], rectCoords[2]],
|
||||
[rectCoords[2], rectCoords[3]],
|
||||
[rectCoords[3], rectCoords[0]],
|
||||
]
|
||||
|
||||
// 다각형의 변 정의
|
||||
const outerEdges = turf.lineString(outerPolygon.geometry.coordinates[0])
|
||||
|
||||
// 사각형의 변들이 다각형의 변과 교차하는지 확인
|
||||
const noEdgesIntersect = rectEdges.every((edge) => {
|
||||
const line = turf.lineString(edge)
|
||||
const intersects = turf.lineIntersect(line, outerEdges)
|
||||
return intersects.features.length === 0
|
||||
})
|
||||
|
||||
return allPointsInside && noEdgesIntersect
|
||||
},
|
||||
distanceFromEdge(point) {
|
||||
const vertices = this.getCurrentPoints()
|
||||
let minDistance = Infinity
|
||||
|
||||
67
src/hooks/useAxios.js
Normal file
67
src/hooks/useAxios.js
Normal file
@ -0,0 +1,67 @@
|
||||
import axios from 'axios'
|
||||
|
||||
export const AxiosType = {
|
||||
INTERNAL: 'Internal',
|
||||
EXTERNAL: 'External',
|
||||
}
|
||||
|
||||
export function useAxios() {
|
||||
const getInstances = (type) => {
|
||||
return axios.create({
|
||||
baseURL: type === AxiosType.INTERNAL ? process.env.NEXT_PUBLIC_API_SERVER_PATH : '',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
// config['Authorization'] = localStorage.getItem('token')
|
||||
//TODO: 인터셉터에서 추가 로직 구현
|
||||
return config
|
||||
})
|
||||
|
||||
axios.interceptors.request.use(undefined, (error) => {
|
||||
//TODO: 인터셉터에서 에러 처리 로직 구현
|
||||
// if (error.isAxiosError && e.response?.status === 401) {
|
||||
// localStorage.removeItem('token')
|
||||
// }
|
||||
})
|
||||
|
||||
const get = async ({ type, url }) => {
|
||||
return await getInstances(type)
|
||||
.get(url)
|
||||
.then((res) => res.data)
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
const post = async ({ type, url, data }) => {
|
||||
return await getInstances(type)
|
||||
.post(url, data)
|
||||
.then((res) => res.data)
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
const put = async ({ type, url, data }) => {
|
||||
return await getInstances(type)
|
||||
.put(url, data)
|
||||
.then((res) => res.data)
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
const patch = async ({ type, url, data }) => {
|
||||
return await getInstances(type)
|
||||
.patch(url, data)
|
||||
.then((res) => res.data)
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
const del = async ({ type, url }) => {
|
||||
return await getInstances(type)
|
||||
.delete(url)
|
||||
.then((res) => res.data)
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
return { get, post, put, patch, del }
|
||||
}
|
||||
@ -89,8 +89,8 @@ export function useCanvas(id) {
|
||||
canvas?.off('object:modified')
|
||||
canvas?.off('object:removed')
|
||||
canvas?.off('object:added')
|
||||
canvas?.off('mouse:move', drawMouseLines)
|
||||
canvas?.off('mouse:down', handleMouseDown)
|
||||
canvas?.off('mouse:move')
|
||||
canvas?.off('mouse:down')
|
||||
}
|
||||
|
||||
const addEventOnObject = (e) => {
|
||||
@ -112,9 +112,11 @@ export function useCanvas(id) {
|
||||
target.on('mousedown', () => {
|
||||
if (target.get('selected')) {
|
||||
target.set({ strokeWidth: 1 })
|
||||
target.set({ strokeDashArray: [5, 5] })
|
||||
target.set({ selected: false })
|
||||
} else {
|
||||
target.set({ strokeWidth: 5 })
|
||||
target.set({ strokeDashArray: [0, 0] })
|
||||
target.set({ selected: true })
|
||||
}
|
||||
canvas?.renderAll()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
35
src/hooks/useToast.js
Normal file
35
src/hooks/useToast.js
Normal file
@ -0,0 +1,35 @@
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
const toastDefaultOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 3000,
|
||||
draggable: false,
|
||||
hideProgressBar: false,
|
||||
rtl: false,
|
||||
pauseOnFocusLoss: true,
|
||||
pauseOnHover: true,
|
||||
theme: 'light',
|
||||
limit: 2,
|
||||
closeOnClick: true,
|
||||
}
|
||||
|
||||
const QToast = (props) => {
|
||||
// type TypeOptions = 'info' | 'success' | 'warning' | 'error' | 'default'
|
||||
const { message, type = 'info', options } = props
|
||||
const customOptions = { ...toastDefaultOptions, ...options }
|
||||
|
||||
switch (type) {
|
||||
case 'info':
|
||||
return toast.info(message, customOptions)
|
||||
case 'success':
|
||||
return toast.success(message, customOptions)
|
||||
case 'warning':
|
||||
return toast.warn(message, customOptions)
|
||||
case 'error':
|
||||
return toast.error(message, customOptions)
|
||||
default:
|
||||
return toast(message, customOptions)
|
||||
}
|
||||
}
|
||||
|
||||
export { QToast }
|
||||
@ -67,3 +67,9 @@ export const roofMaterialState = atom({
|
||||
default: { width: 20, height: 10, rafter: 0, roofStyle: 2 },
|
||||
dangerouslyAllowMutability: true,
|
||||
})
|
||||
|
||||
export const compassState = atom({
|
||||
key: 'compass',
|
||||
default: undefined,
|
||||
dangerouslyAllowMutability: true,
|
||||
})
|
||||
|
||||
15
src/store/modalAtom.js
Normal file
15
src/store/modalAtom.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { atom } from 'recoil'
|
||||
|
||||
export const modalState = atom({
|
||||
key: 'modalState',
|
||||
default: false,
|
||||
})
|
||||
|
||||
export const modalContent = atom({
|
||||
key: 'modalContent',
|
||||
default: (
|
||||
<>
|
||||
<div>test</div>
|
||||
</>
|
||||
),
|
||||
})
|
||||
@ -1 +1,2 @@
|
||||
@import '_test.scss';
|
||||
@import '_test.scss';
|
||||
@import 'react-toastify/dist/ReactToastify.css';
|
||||
@ -9,6 +9,7 @@ import {
|
||||
getRoofHypotenuse,
|
||||
} from '@/util/canvas-util'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
import * as turf from '@turf/turf'
|
||||
|
||||
const TWO_PI = Math.PI * 2
|
||||
|
||||
@ -983,6 +984,43 @@ export const splitPolygonWithLines = (polygon) => {
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* 좌표 테스트용
|
||||
*/
|
||||
allLines.forEach((line) => {
|
||||
const text = new fabric.Text(`(${line.startPoint.x},${line.startPoint.y})`, {
|
||||
left: line.startPoint.x,
|
||||
top: line.startPoint.y,
|
||||
fontSize: 15,
|
||||
})
|
||||
|
||||
polygon.canvas.add(text)
|
||||
polygon.canvas.renderAll()
|
||||
|
||||
const text2 = new fabric.Text(`(${line.endPoint.x},${line.endPoint.y})`, {
|
||||
left: line.endPoint.x,
|
||||
top: line.endPoint.y,
|
||||
fontSize: 15,
|
||||
})
|
||||
|
||||
polygon.canvas.add(text2)
|
||||
polygon.canvas.renderAll()
|
||||
})
|
||||
|
||||
polygon.points.forEach((point, index) => {
|
||||
const text = new fabric.Text(`(${point.x},${point.y})`, {
|
||||
left: point.x,
|
||||
top: point.y,
|
||||
fontSize: 15,
|
||||
})
|
||||
|
||||
polygon.canvas.add(text)
|
||||
polygon.canvas.renderAll()
|
||||
})
|
||||
/**
|
||||
* 좌표 테스트용 끝
|
||||
*/
|
||||
|
||||
polygon.points.forEach((point, index) => {
|
||||
allLines.forEach((line) => {
|
||||
if (line.endPoint.x === point.x && line.endPoint.y === point.y) {
|
||||
@ -1007,22 +1045,39 @@ export const splitPolygonWithLines = (polygon) => {
|
||||
const arrivalPoint = endLine.endPoint
|
||||
routes.push(startLine.startPoint)
|
||||
routes.push(startLine.endPoint)
|
||||
|
||||
//hip끼리 만나는 경우는 아무것도 안해도됨
|
||||
let count = 0
|
||||
if (!isSamePoint(startLine.endPoint, arrivalPoint)) {
|
||||
// polygon line까지 추가
|
||||
const allLinesCopy = [...allLines, ...polygon.lines]
|
||||
// hip이 만나지 않는 경우 갈 수 있는 길을 다 돌아야함
|
||||
let currentPoint = startLine.endPoint
|
||||
let currentLine = startLine
|
||||
|
||||
while (!isSamePoint(currentPoint, arrivalPoint) && count <= polygon.points.length) {
|
||||
count++
|
||||
let movedLines = []
|
||||
let subMovedLines = []
|
||||
while (!isSamePoint(currentPoint, arrivalPoint)) {
|
||||
// startHip에서 만나는 출발선 두개. 두개의 선을 출발하여 arrivalPoint에 도착할 때 까지 count를 세고, 더 낮은 count를 가진 길을 선택한다.
|
||||
let connectedLines = allLinesCopy.filter((line) => isSamePoint(line.startPoint, currentPoint) || isSamePoint(line.endPoint, currentPoint))
|
||||
|
||||
connectedLines = connectedLines.filter((line) => line !== currentLine)
|
||||
|
||||
connectedLines = connectedLines.filter((line) => !subMovedLines.includes(line))
|
||||
|
||||
//마지막 선이 endLine의 startPoint와 같은경우 그 전까지 movedLine을 제거한다.
|
||||
const endLineMeetLineCnt = connectedLines.filter((line) => {
|
||||
return isSamePoint(line.endPoint, endLine.startPoint) || isSamePoint(line.startPoint, endLine.startPoint)
|
||||
}).length
|
||||
|
||||
if (endLineMeetLineCnt !== 0) {
|
||||
movedLines.push(subMovedLines)
|
||||
|
||||
console.log(movedLines, index)
|
||||
}
|
||||
|
||||
connectedLines = connectedLines.filter((line) => {
|
||||
return !isSamePoint(line.endPoint, endLine.startPoint) && !isSamePoint(line.startPoint, endLine.startPoint)
|
||||
})
|
||||
|
||||
if (connectedLines.length === 0) {
|
||||
return
|
||||
}
|
||||
@ -1050,14 +1105,15 @@ export const splitPolygonWithLines = (polygon) => {
|
||||
|
||||
currentPoint = tempPoints[minIndex].point
|
||||
currentLine = tempPoints[minIndex].line
|
||||
if (currentLine !== startLine) {
|
||||
subMovedLines.push(currentLine)
|
||||
}
|
||||
routes.push(currentPoint)
|
||||
}
|
||||
}
|
||||
|
||||
if (count <= polygon.points.length - 1) {
|
||||
routes.push(endLine.startPoint)
|
||||
roofs.push(routes)
|
||||
}
|
||||
routes.push(endLine.startPoint)
|
||||
roofs.push(routes)
|
||||
})
|
||||
|
||||
// 중복 제거
|
||||
@ -1078,6 +1134,7 @@ export const splitPolygonWithLines = (polygon) => {
|
||||
fill: 'transparent',
|
||||
strokeWidth: 3,
|
||||
name: 'roof',
|
||||
selectable: false,
|
||||
})
|
||||
|
||||
polygon.canvas.add(roof)
|
||||
@ -2737,3 +2794,35 @@ function arraysHaveSamePoints(array1, array2) {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export const toGeoJSON = (pointsArray) => {
|
||||
// 객체 배열을 GeoJSON 형식의 좌표 배열로 변환
|
||||
const coordinates = pointsArray.map((point) => [point.x, point.y])
|
||||
|
||||
// 닫힌 다각형을 만들기 위해 첫 번째 점을 마지막에 추가
|
||||
coordinates.push([pointsArray[0].x, pointsArray[0].y])
|
||||
|
||||
return coordinates
|
||||
}
|
||||
|
||||
export const inPolygon = (polygonPoints, rectPoints) => {
|
||||
const polygonCoordinates = toGeoJSON(polygonPoints)
|
||||
const rectCoordinates = toGeoJSON(rectPoints)
|
||||
|
||||
const polygonFeature = turf.polygon([polygonCoordinates])
|
||||
const rectFeature = turf.polygon([rectCoordinates])
|
||||
|
||||
// 사각형의 모든 꼭짓점이 다각형 내부에 있는지 확인
|
||||
const allPointsInsidePolygon = rectCoordinates.every((coord) => {
|
||||
const point = turf.point(coord)
|
||||
return turf.booleanPointInPolygon(point, polygonFeature)
|
||||
})
|
||||
|
||||
// 다각형의 모든 점이 사각형 내부에 있지 않은지 확인
|
||||
const noPolygonPointsInsideRect = polygonCoordinates.every((coord) => {
|
||||
const point = turf.point(coord)
|
||||
return !turf.booleanPointInPolygon(point, rectFeature)
|
||||
})
|
||||
|
||||
return allPointsInsidePolygon && noPolygonPointsInsideRect
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user