Merge branch 'dev' into feature/test-jy

# Conflicts:
#	src/components/Roof2.jsx
#	src/components/fabric/QPolygon.js
#	src/hooks/useMode.js
#	src/util/qpolygon-utils.js
This commit is contained in:
Jaeyoung Lee 2024-08-06 10:55:06 +09:00
commit 7e1c8c6710
67 changed files with 2021 additions and 575 deletions

3
.env.development Normal file
View File

@ -0,0 +1,3 @@
NEXT_PUBLIC_TEST="테스트변수입니다. development"
NEXT_PUBLIC_API_SERVER_PATH="http://localhost:8080"

3
.env.production Normal file
View File

@ -0,0 +1,3 @@
NEXT_PUBLIC_TEST="테스트변수입니다. production"
NEXT_PUBLIC_API_SERVER_PATH="http://localhost:8080"

Binary file not shown.

BIN
Qcast coding convention.pdf Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,19 +1,36 @@
# 점 갯수 별 타입 This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## 점 6개 ## Getting Started
### type1 First, run the development server:
![type1](https://devgrr-bucket.s3.ap-northeast-2.amazonaws.com/qcast-type/point6+-+type1.png) ```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
### type2 Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
![type2](https://devgrr-bucket.s3.ap-northeast-2.amazonaws.com/qcast-type/point6-+type2.png) You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
### type3 This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
![type3](https://devgrr-bucket.s3.ap-northeast-2.amazonaws.com/qcast-type/point6-type3.png) ## Learn More
### type4 To learn more about Next.js, take a look at the following resources:
![type4](https://devgrr-bucket.s3.ap-northeast-2.amazonaws.com/qcast-type/point6-type4.png) - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

@ -1,15 +1,18 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
reactStrictMode: false, reactStrictMode: true,
webpack: (config) => { webpack: (config) => {
config.externals.push({ config.externals.push({
"utf-8-validate": "commonjs utf-8-validate", 'utf-8-validate': 'commonjs utf-8-validate',
bufferutil: "commonjs bufferutil", bufferutil: 'commonjs bufferutil',
canvas: "commonjs canvas", canvas: 'commonjs canvas',
}); })
// config.infrastructureLogging = { debug: /PackFileCache/ }; // config.infrastructureLogging = { debug: /PackFileCache/ };
return config; return config
}, },
}; sassOptions: {
includePaths: ['./src/styles'],
},
}
export default nextConfig; export default nextConfig

View File

@ -11,12 +11,15 @@
"dependencies": { "dependencies": {
"@nextui-org/react": "^2.4.2", "@nextui-org/react": "^2.4.2",
"@prisma/client": "^5.17.0", "@prisma/client": "^5.17.0",
"axios": "^1.7.3",
"fabric": "^5.3.0", "fabric": "^5.3.0",
"framer-motion": "^11.2.13", "framer-motion": "^11.2.13",
"mathjs": "^13.0.2", "mathjs": "^13.0.2",
"mongodb": "^6.8.0", "mongodb": "^6.8.0",
"next": "14.2.3", "next": "14.2.3",
"next-international": "^1.2.4",
"react": "^18", "react": "^18",
"react-datepicker": "^7.3.0",
"react-dom": "^18", "react-dom": "^18",
"recoil": "^0.7.7", "recoil": "^0.7.7",
"uuid": "^9.0.1" "uuid": "^9.0.1"
@ -25,6 +28,7 @@
"postcss": "^8", "postcss": "^8",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prisma": "^5.17.0", "prisma": "^5.17.0",
"sass": "^1.77.8",
"tailwindcss": "^3.4.1" "tailwindcss": "^3.4.1"
} }
} }

View File

@ -3,6 +3,6 @@ const config = {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
}, },
}; }
export default config; export default config

19
shape-type.md Normal file
View File

@ -0,0 +1,19 @@
# 점 갯수 별 타입
## 점 6개
### type1
![type1](https://devgrr-bucket.s3.ap-northeast-2.amazonaws.com/qcast-type/point6+-+type1.png)
### type2
![type2](https://devgrr-bucket.s3.ap-northeast-2.amazonaws.com/qcast-type/point6-+type2.png)
### type3
![type3](https://devgrr-bucket.s3.ap-northeast-2.amazonaws.com/qcast-type/point6-type3.png)
### type4
![type4](https://devgrr-bucket.s3.ap-northeast-2.amazonaws.com/qcast-type/point6-type4.png)

View File

@ -0,0 +1,11 @@
'use client'
import { I18nProviderClient } from '@/locales/client'
export function LocaleProvider({ locale, children }) {
return (
<I18nProviderClient locale={locale} fallback={<p>Loading...</p>}>
{children}
</I18nProviderClient>
)
}

View File

@ -0,0 +1,4 @@
.test {
@apply bg-red-500;
@apply text-2xl;
}

View File

@ -0,0 +1,73 @@
'use client'
import { Button, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from '@nextui-org/react'
import Hero from '@/components/Hero'
import QSelect from '@/components/ui/QSelect'
import styles from './changelog.module.css'
import { get } from '@/lib/Axios'
export default function changelogPage() {
const testVar = process.env.NEXT_PUBLIC_TEST
const handleUsers = async () => {
const users = await get('/api/user/find-all')
console.log(users)
}
const data = [
{
id: 1,
author: 'SWYOO',
contents: '버튼 정리(템플릿 적용)',
date: '2024.07.16',
},
{
id: 2,
author: 'SWYOO',
contents: 'README.md 파일 이미지 경로 수정',
date: '2024.07.17',
},
{
id: 3,
author: 'SWYOO',
contents: '',
date: '',
},
]
return (
<>
<Hero title="Change log" />
<div className={styles.test}> 영역은 테스트입니다.</div>
<div>
<Table isStriped>
<TableHeader>
<TableColumn>DATE</TableColumn>
<TableColumn>NAME</TableColumn>
<TableColumn>CONTENTS</TableColumn>
</TableHeader>
<TableBody>
{data.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.date}</TableCell>
<TableCell>{item.author}</TableCell>
<TableCell>{item.contents}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="m-2">
<QSelect />
</div>
<div className="w-full bg-orange-300 m-2">{testVar}</div>
<div>
<div className="m-2">
<Button onClick={handleUsers}>Button</Button>
</div>
</div>
<div className="test">
<p className="text-white">Sass 테스트입니다.</p>
</div>
</>
)
}

View File

@ -0,0 +1,4 @@
.test {
@apply bg-red-500;
@apply text-2xl;
}

View File

@ -0,0 +1,73 @@
'use client'
import { Button, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from '@nextui-org/react'
import Hero from '@/components/Hero'
import QSelect from '@/components/ui/QSelect'
import styles from './changelog.module.css'
import { get } from '@/lib/Axios'
export default function changelogPage() {
const testVar = process.env.NEXT_PUBLIC_TEST
const handleUsers = async () => {
const users = await get('/api/user/find-all')
console.log(users)
}
const data = [
{
id: 1,
author: 'SWYOO',
contents: '버튼 정리(템플릿 적용)',
date: '2024.07.16',
},
{
id: 2,
author: 'SWYOO',
contents: 'README.md 파일 이미지 경로 수정',
date: '2024.07.17',
},
{
id: 3,
author: 'SWYOO',
contents: '',
date: '',
},
]
return (
<>
<Hero title="Change log" />
<div className={styles.test}> 영역은 테스트입니다.</div>
<div>
<Table isStriped>
<TableHeader>
<TableColumn>DATE</TableColumn>
<TableColumn>NAME</TableColumn>
<TableColumn>CONTENTS</TableColumn>
</TableHeader>
<TableBody>
{data.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.date}</TableCell>
<TableCell>{item.author}</TableCell>
<TableCell>{item.contents}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="m-2">
<QSelect />
</div>
<div className="w-full bg-orange-300 m-2">{testVar}</div>
<div>
<div className="m-2">
<Button onClick={handleUsers}>Button</Button>
</div>
</div>
<div className="test">
<p className="text-white">Sass 테스트입니다.</p>
</div>
</>
)
}

View File

@ -0,0 +1,15 @@
'use client'
import Hero from '@/components/Hero'
import Archive from '@/components/community/Archive'
export default function CommunityArchivePage() {
return (
<>
<Hero title="자료 다운로드"/>
<div className="container flex flex-wrap items-center justify-between mx-auto p-4 m-4 border">
<Archive/>
</div>
</>
)
}

View File

@ -1,14 +1,14 @@
'use client' 'use client'
import Hero from '@/components/Hero' import Hero from '@/components/Hero'
import Intro from '@/components/Intro' import Faq from '@/components/community/Faq'
export default function IntroPage() { export default function CommunityFaqPage() {
return ( return (
<> <>
<Hero title="Drawing on canvas 2D Intro" /> <Hero title="FAQ"/>
<div className="container flex flex-wrap items-center justify-between mx-auto p-4 m-4 border"> <div className="container flex flex-wrap items-center justify-between mx-auto p-4 m-4 border">
<Intro /> <Faq/>
</div> </div>
</> </>
) )

View File

@ -0,0 +1,15 @@
'use client'
import Hero from '@/components/Hero'
import Notice from '@/components/community/Notice'
export default function CommunityNoticePage() {
return (
<>
<Hero title="공지사항"/>
<div className="container flex flex-wrap items-center justify-between mx-auto p-4 m-4 border">
<Notice/>
</div>
</>
)
}

View File

@ -0,0 +1,15 @@
'use client'
export default function ServerError() {
return (
<section className="bg-white dark:bg-gray-900">
<div className="py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6">
<div className="mx-auto max-w-screen-sm text-center">
<h1 className="mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-primary-600 dark:text-primary-500">500</h1>
<p className="mb-4 text-3xl tracking-tight font-bold text-gray-900 md:text-4xl dark:text-white">Internal Server Error.</p>
<p className="mb-4 text-lg font-light text-gray-500 dark:text-gray-400">We are already working to solve the problem. </p>
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,14 @@
'use client'
import Hero from '@/components/Hero'
import Intro from '@/components/Intro'
export default function IntroPage() {
return (
<>
<div className="container mx-auto p-4 m-4 border">
<Intro />
</div>
</>
)
}

View File

@ -0,0 +1,13 @@
'use client'
import { useCurrentLocale } from '@/locales/client'
import { LocaleProvider } from './LocaleProvider'
export default function LocaleLayout({ children }) {
const locale = useCurrentLocale()
return (
<>
<LocaleProvider locale={locale}>{children}</LocaleProvider>
</>
)
}

View File

@ -0,0 +1,15 @@
'use client'
import Hero from '@/components/Hero'
import Plan from '@/components/management/Plan'
export default function ManagementPlanPage() {
return (
<>
<Hero title="도면관리"/>
<div className="container flex flex-wrap items-center justify-between mx-auto p-4 m-4 border">
<Plan/>
</div>
</>
)
}

View File

@ -0,0 +1,15 @@
'use client'
import Hero from '@/components/Hero'
import Stuff from '@/components/management/Stuff'
export default function ManagementStuffPage() {
return (
<>
<Hero title="물건관리" />
<div className="container flex flex-wrap items-center justify-between mx-auto p-4 m-4 border">
<Stuff/>
</div>
</>
)
}

View File

@ -0,0 +1,15 @@
'use client'
import Hero from '@/components/Hero'
import Company from '@/components/master/Company'
export default function MasterCompanyPage() {
return (
<>
<Hero title="회사정보 조회"/>
<div className="container flex flex-wrap items-center justify-between mx-auto p-4 m-4 border">
<Company/>
</div>
</>
)
}

View File

@ -0,0 +1,15 @@
'use client'
import Hero from '@/components/Hero'
import Price from '@/components/master/Price'
export default function MasterPricePage() {
return (
<>
<Hero title="가격 마스터 조회" />
<div className="container flex flex-wrap items-center justify-between mx-auto p-4 m-4 border">
<Price />
</div>
</>
)
}

View File

@ -0,0 +1,25 @@
'use client'
import Link from 'next/link'
export default function NotFound() {
return (
<section className="bg-white dark:bg-gray-900">
<div className="py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6">
<div className="mx-auto max-w-screen-sm text-center">
<h1 className="mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-primary-600 dark:text-primary-500">404</h1>
<p className="mb-4 text-3xl tracking-tight font-bold text-gray-900 md:text-4xl dark:text-white">Something's missing.</p>
<p className="mb-4 text-lg font-light text-gray-500 dark:text-gray-400">
Sorry, we can't find that page. You'll find lots to explore on the home page.{' '}
</p>
<Link
href="/"
className="inline-flex text-white bg-primary-600 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:focus:ring-primary-900 my-4"
>
Back to Homepage
</Link>
</div>
</div>
</section>
)
}

19
src/app/[locale]/page.js Normal file
View File

@ -0,0 +1,19 @@
import MainPage from '@/components/Main'
import { getCurrentLocale } from '@/locales/server'
export default function page() {
const currentLocale = getCurrentLocale()
const mainPageProps = {
currentLocale,
}
return (
<>
<div className="m-4">
<h3>Main</h3>
<MainPage {...mainPageProps} />
</div>
</>
)
}

View File

@ -1,40 +0,0 @@
'use client'
import Hero from '@/components/Hero'
import {
Table,
TableBody,
TableCell,
TableColumn,
TableHeader,
TableRow,
} from '@nextui-org/react'
export default function changelogPage() {
return (
<>
<Hero title="Change log" />
<div>
<Table isStriped>
<TableHeader>
<TableColumn>DATE</TableColumn>
<TableColumn>NAME</TableColumn>
<TableColumn>CONTENTS</TableColumn>
</TableHeader>
<TableBody>
<TableRow key="2">
<TableCell>2024.07.17</TableCell>
<TableCell>SWYOO</TableCell>
<TableCell>` * README.md 파일 이미지 경로 수정 `</TableCell>
</TableRow>
<TableRow key="1">
<TableCell>2024.07.16</TableCell>
<TableCell>SWYOO</TableCell>
<TableCell>` * 버튼 정리(템플릿 적용) `</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</>
)
}

View File

@ -30,10 +30,11 @@ body {
.text-balance { .text-balance {
text-wrap: balance; text-wrap: balance;
} }
} */ }
.archivo-black-regular { .archivo-black-regular {
font-family: 'Archivo Black', sans-serif; font-family: 'Archivo Black', sans-serif;
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
} }
*/

View File

@ -1,5 +1,6 @@
import { Inter } from 'next/font/google' import { Inter } from 'next/font/google'
import './globals.css' import './globals.css'
import '../styles/style.scss'
import Headers from '@/components/Headers' import Headers from '@/components/Headers'
import RecoilRootWrapper from './RecoilWrapper' import RecoilRootWrapper from './RecoilWrapper'
import UIProvider from './UIProvider' import UIProvider from './UIProvider'

View File

@ -1,5 +1,5 @@
import Hero from '@/components/Hero' import Main from '@/components/Main'
export default function Home() { export default function Home() {
return <Hero title="Q.CAST III - Prototype" /> return <Main />
} }

View File

@ -2,7 +2,7 @@ import Link from 'next/link'
export default function Headers() { export default function Headers() {
return ( return (
<div className="w-full absolute z-10"> <div className="w-full">
<nav className="container relative flex flex-wrap items-center justify-between mx-auto p-8"> <nav className="container relative flex flex-wrap items-center justify-between mx-auto p-8">
<Link href="/" className="font-bold text-3xl"> <Link href="/" className="font-bold text-3xl">
Home Home

View File

@ -1,7 +1,7 @@
export default function Hero(props) { export default function Hero(props) {
return ( return (
<div className="pt-48 flex justify-center"> <div className="pt-48 flex justify-center">
<h1 className="text-6xl archivo-black-regular">{props.title}</h1> <h1 className="text-4xl archivo-black-regular">{props.title}</h1>
</div> </div>
) )
} }

View File

@ -1,8 +1,38 @@
'use client'
import { useState } from 'react'
import SingleDatePicker from './ui/datepicker/SingleDatePicker'
import RangeDatePicker from './ui/datepicker/RangeDatePicker'
export default function Intro() { export default function Intro() {
const [startDate, setStartDate] = useState(new Date())
const singleDatePickerProps = {
startDate,
setStartDate,
}
const [dateRange, setDateRange] = useState([null, null])
const [startRangeDate, endRangeDate] = dateRange
const rangeDatePickerProps = {
startRangeDate,
endRangeDate,
setDateRange,
}
return ( return (
<> <>
<h1>Intro</h1> <div className="my-2">
<p>Drawing on canvas 2D is a simple way to create graphics on the web.</p> <h4>Single Date Picker</h4>
<div>
<SingleDatePicker {...singleDatePickerProps} />
</div>
</div>
<div className="my-2">
<h4>Range Date Picker</h4>
<div>
<RangeDatePicker {...rangeDatePickerProps} />
</div>
</div>
</> </>
) )
} }

27
src/components/Main.jsx Normal file
View File

@ -0,0 +1,27 @@
'use client'
import { useChangeLocale, useI18n } from '@/locales/client'
import { Button, Chip } from '@nextui-org/react'
export default function MainPage(props) {
const { currentLocale } = props
const t = useI18n()
const changeLocale = useChangeLocale()
const handleChangeLocale = () => {
currentLocale === 'ja' ? changeLocale('ko') : changeLocale('ja')
}
console.log('MainPage', currentLocale)
return (
<>
<h3>{t('locale', { locale: <Chip color="primary">{currentLocale}</Chip> })}</h3>
<div>{t('hello')}</div>
<div>{t('welcome', { name: '효준' })}</div>
<div>
<Button onClick={handleChangeLocale}>Change Locale</Button>
</div>
</>
)
}

View File

@ -2,15 +2,15 @@ import { useCanvas } from '@/hooks/useCanvas'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Mode, useMode } from '@/hooks/useMode' import { Mode, useMode } from '@/hooks/useMode'
import { Button } from '@nextui-org/react' import { Button } from '@nextui-org/react'
import QRect from '@/components/fabric/QRect'
import RangeSlider from './ui/RangeSlider' import RangeSlider from './ui/RangeSlider'
import { useRecoilState } from 'recoil' import { useRecoilState, useRecoilValue } from 'recoil'
import { canvasSizeState, fontSizeState, sortedPolygonArray } from '@/store/canvasAtom' import { canvasSizeState, fontSizeState, roofMaterialState, sortedPolygonArray, templateTypeState } from '@/store/canvasAtom'
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
import { getCanvasState, insertCanvasState } from '@/lib/canvas' import { getCanvasState, insertCanvasState } from '@/lib/canvas'
import { calculateIntersection } from '@/util/canvas-util' import { calculateIntersection } from '@/util/canvas-util'
import { QPolygon } from '@/components/fabric/QPolygon' import { QPolygon } from '@/components/fabric/QPolygon'
import offsetPolygon from '@/util/qpolygon-utils'
export default function Roof2() { export default function Roof2() {
const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas') const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas')
@ -31,8 +31,14 @@ export default function Roof2() {
const [showControl, setShowControl] = useState(false) const [showControl, setShowControl] = useState(false)
//
const roofMaterial = useRecoilValue(roofMaterialState)
const [templateType, setTemplateType] = useRecoilState(templateTypeState)
const { const {
mode, mode,
setMode,
changeMode, changeMode,
handleClear, handleClear,
fillCellInPolygon, fillCellInPolygon,
@ -44,6 +50,7 @@ export default function Roof2() {
handleOuterlinesTest2, handleOuterlinesTest2,
applyTemplateB, applyTemplateB,
makeRoofPatternPolygon, makeRoofPatternPolygon,
createRoofRack,
drawRoofPolygon, drawRoofPolygon,
} = useMode() } = useMode()
@ -56,22 +63,6 @@ export default function Roof2() {
changeMode(canvas, mode) changeMode(canvas, mode)
}, [canvas, mode]) }, [canvas, mode])
const makeRect = () => {
if (canvas) {
const rect = new QRect({
left: 100,
top: 100,
fill: 'transparent',
stroke: 'black',
width: 400,
height: 100,
fontSize: fontSize,
})
canvas?.add(rect)
}
}
const makeLine = () => { const makeLine = () => {
if (canvas) { if (canvas) {
const line = new QLine([50, 50, 200, 50], { const line = new QLine([50, 50, 200, 50], {
@ -108,7 +99,6 @@ export default function Roof2() {
} }
useEffect(() => { useEffect(() => {
console.log('horizontalSize', horizontalSize)
setCanvasSize({ ...canvasSize, vertical: parseInt(verticalSize), horizontal: parseInt(horizontalSize) }) setCanvasSize({ ...canvasSize, vertical: parseInt(verticalSize), horizontal: parseInt(horizontalSize) })
}, [verticalSize, horizontalSize]) }, [verticalSize, horizontalSize])
@ -120,13 +110,10 @@ export default function Roof2() {
// }, [verticalSize, horizontalSize]) // }, [verticalSize, horizontalSize])
useEffect(() => { useEffect(() => {
const { vertical, horizontal } = canvasSize const { vertical, horizontal } = canvasSize
console.log('canvasSize', canvasSize)
console.log('horizontalSize', horizontalSize)
if (vertical !== verticalSize || horizontal !== horizontalSize) { if (vertical !== verticalSize || horizontal !== horizontalSize) {
console.log('setCanvasSize!!') canvas?.setWidth(horizontalSize)
canvas.setWidth(horizontalSize) canvas?.setHeight(verticalSize)
canvas.setHeight(verticalSize) canvas?.renderAll()
canvas.renderAll()
} }
}, [canvasSize, canvas]) }, [canvasSize, canvas])
@ -140,12 +127,12 @@ export default function Roof2() {
{ x: 100, y: 400 }, { x: 100, y: 400 },
] ]
const type2 = [ const type2 = [
{ x: 100, y: 100 }, { x: 200, y: 100 },
{ x: 100, y: 1000 }, { x: 200, y: 1000 },
{ x: 1000, y: 1000 }, { x: 1100, y: 1000 },
{ x: 1000, y: 600 }, { x: 1100, y: 600 },
{ x: 550, y: 600 }, { x: 650, y: 600 },
{ x: 550, y: 100 }, { x: 650, y: 100 },
] ]
const type3 = [ const type3 = [
@ -228,13 +215,19 @@ export default function Roof2() {
{ x: 1100, y: 200 }, { x: 1100, y: 200 },
] ]
const octaType1 = [ const twelvePoint = [
{ x: 100, y: 100 }, { x: 195, y: 166 },
{ x: 100, y: 600 }, { x: 195, y: 466 },
{ x: 650, y: 600 }, { x: 395, y: 466 },
{ x: 650, y: 400 }, { x: 395, y: 766 },
{ x: 350, y: 400 }, { x: 545, y: 766 },
{ x: 350, y: 100 }, { x: 545, y: 466 },
{ x: 695, y: 466 },
{ x: 695, y: 666 },
{ x: 845, y: 666 },
{ x: 845, y: 466 },
{ x: 995, y: 466 },
{ x: 995, y: 166 },
] ]
const complicatedType = [ const complicatedType = [
@ -275,39 +268,26 @@ export default function Roof2() {
{ x: 600, y: 100 }, { x: 600, y: 100 },
] ]
const twelvePoint = [
{ x: 195, y: 166 },
{ x: 195, y: 466 },
{ x: 395, y: 466 },
{ x: 395, y: 766 },
{ x: 545, y: 766 },
{ x: 545, y: 466 },
{ x: 695, y: 466 },
{ x: 695, y: 666 },
{ x: 845, y: 666 },
{ x: 845, y: 466 },
{ x: 995, y: 466 },
{ x: 995, y: 166 },
]
const types = [type1, type2, type3, type4, type1A, type1B, eightPoint, eightPoint2, eightPoint3, eightPoint4, twelvePoint] const types = [type1, type2, type3, type4, type1A, type1B, eightPoint, eightPoint2, eightPoint3, eightPoint4, twelvePoint]
const newP = [
{ x: 450, y: 450 },
{ x: 650, y: 250 },
{ x: 675, y: 275 },
{ x: 450, y: 850 },
]
const polygon = new QPolygon(eightPoint3, { const polygon = new QPolygon(eightPoint3, {
fill: 'transparent', fill: 'transparent',
stroke: 'green', stroke: 'green',
strokeWidth: 1, strokeWidth: 1,
selectable: true, selectable: false,
fontSize: fontSize, fontSize: fontSize,
name: 'QPolygon1', name: 'wall',
}) })
canvas?.add(polygon) canvas?.add(polygon)
// drawRoofPolygon(polygon) handleOuterlinesTest2(polygon, 50)
setTemplateType(1)
handleOuterlinesTest2(polygon)
// const lines = togglePolygonLine(polygon)
// togglePolygonLine(lines[0])
} }
const rotateShape = () => { const rotateShape = () => {
@ -395,6 +375,70 @@ export default function Roof2() {
handleClear() handleClear()
} }
const drawRoofMaterial = () => {
const { width, height, roofStyle } = roofMaterial
const wallPolygon = canvas?.getObjects().find((obj) => obj.name === 'wall')
wallPolygon.set('strokeDashArray', [10, 5, 2, 5])
wallPolygon.set('stroke', 'blue')
wallPolygon.set('strokeWidth', 1)
const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roof')
roofs.forEach((roof) => {
let maxLengthLine = roof.lines.reduce((acc, cur) => {
return acc.length > cur.length ? acc : cur
})
const roofRatio = window.devicePixelRatio || 1
//
const patternSourceCanvas = document.createElement('canvas')
if (roofStyle === 1) {
if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
patternSourceCanvas.width = width * roofRatio
patternSourceCanvas.height = height * roofRatio
} else {
patternSourceCanvas.width = height * roofRatio
patternSourceCanvas.height = width * roofRatio
}
} else if (roofStyle === 2) {
if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
patternSourceCanvas.width = width * 2
patternSourceCanvas.height = height * 2
} else {
patternSourceCanvas.width = height * 2
patternSourceCanvas.height = width * 2
}
}
const ctx = patternSourceCanvas.getContext('2d')
ctx.scale(roofRatio, roofRatio)
ctx.strokeStyle = 'green'
ctx.lineWidth = 0.4
//
if (roofStyle === 1) {
ctx.strokeRect(0, 0, 50, 30)
} else if (roofStyle === 2) {
//
ctx.strokeRect(0, 0, 200, 100)
ctx.strokeRect(100, 100, 200, 100)
}
//
const pattern = new fabric.Pattern({
source: patternSourceCanvas,
repeat: 'repeat',
})
roof.set('fill', null)
roof.set('fill', pattern)
canvas?.renderAll()
})
}
/** /**
* canvas 내용 불러오기 * canvas 내용 불러오기
*/ */
@ -415,6 +459,14 @@ export default function Roof2() {
makeRoofPatternPolygon(roofStyle) makeRoofPatternPolygon(roofStyle)
} }
const deleteCell = () => {
const selectedCells = canvas?.getObjects().filter((obj) => obj.name === 'cell' && obj.selected)
selectedCells.forEach((cell) => {
canvas?.remove(cell)
})
}
return ( return (
<> <>
{canvas && ( {canvas && (
@ -423,23 +475,19 @@ export default function Roof2() {
<Button className="m-1 p-2" color={`${mode === Mode.DEFAULT ? 'primary' : 'default'}`} onClick={fillCellInPolygon}> <Button className="m-1 p-2" color={`${mode === Mode.DEFAULT ? 'primary' : 'default'}`} onClick={fillCellInPolygon}>
모드 DEFAULT 모드 DEFAULT
</Button> </Button>
<Button <Button className="m-1 p-2" color={`${mode === Mode.DRAW_LINE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.DRAW_LINE)}>
className="m-1 p-2"
color={`${mode === Mode.DRAW_LINE ? 'primary' : 'default'}`}
onClick={() => changeMode(canvas, Mode.DRAW_LINE)}
>
기준선 긋기 모드 기준선 긋기 모드
</Button> </Button>
<Button className="m-1 p-2" color={`${mode === Mode.EDIT ? 'primary' : 'default'}`} onClick={() => changeMode(canvas, Mode.EDIT)}> <Button className="m-1 p-2" color={`${mode === Mode.EDIT ? 'primary' : 'default'}`} onClick={() => setMode(Mode.EDIT)}>
에디팅모드 에디팅모드
</Button> </Button>
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => changeMode(canvas, Mode.TEMPLATE)}> <Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.TEMPLATE)}>
템플릿(기둥) 템플릿(기둥)
</Button> </Button>
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => changeMode(canvas, Mode.PATTERNA)}> <Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.PATTERNA)}>
템플릿(A 패턴) 템플릿(A 패턴)
</Button> </Button>
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => changeMode(canvas, Mode.PATTERNB)}> <Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.PATTERNB)}>
템플릿(B 패턴) 템플릿(B 패턴)
</Button> </Button>
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(1)}> <Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(1)}>
@ -448,55 +496,39 @@ export default function Roof2() {
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(2)}> <Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(2)}>
지붕패턴2 지붕패턴2
</Button> </Button>
<Button <Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.ROOF_TRESTLE)}>
className="m-1 p-2"
color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`}
onClick={() => changeMode(canvas, Mode.ROOF_TRESTLE)}
>
지붕가대생성 지붕가대생성
</Button> </Button>
<Button <Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.FILL_CELLS)}>
className="m-1 p-2"
color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`}
onClick={() => changeMode(canvas, Mode.FILL_CELLS)}
>
태양광셀생성 태양광셀생성
</Button> </Button>
<Button className="m-1 p-2" color={`${mode === Mode.TEXTBOX ? 'primary' : 'default'}`} onClick={() => changeMode(canvas, Mode.TEXTBOX)}> <Button className="m-1 p-2" color={`${mode === Mode.TEXTBOX ? 'primary' : 'default'}`} onClick={() => setMode(Mode.TEXTBOX)}>
텍스트박스 모드 텍스트박스 모드
</Button> </Button>
<Button {/*<Button className="m-1 p-2" onClick={handleUndo}>
className="m-1 p-2"
color={`${mode === Mode.DRAW_RECT ? 'primary' : 'default'}`}
onClick={() => changeMode(canvas, Mode.DRAW_RECT)}
>
사각형 생성 모드
</Button>
<Button className="m-1 p-2" onClick={handleUndo}>
Undo Undo
</Button> </Button>
<Button className="m-1 p-2" onClick={handleRedo}> <Button className="m-1 p-2" onClick={handleRedo}>
Redo Redo
</Button> </Button>*/}
<Button className="m-1 p-2" onClick={handleClear}> <Button className="m-1 p-2" onClick={handleClear}>
clear clear
</Button> </Button>
<Button className="m-1 p-2" onClick={zoomIn}> {/*<Button className="m-1 p-2" onClick={zoomIn}>
확대 확대
</Button> </Button>
<Button className="m-1 p-2" onClick={zoomOut}> <Button className="m-1 p-2" onClick={zoomOut}>
축소 축소
</Button> </Button>*/}
현재 : {zoom}% 현재 : {zoom}%
<Button className="m-1 p-2" onClick={makeRect}>
사각형만들기
</Button>
<Button className="m-1 p-2" onClick={makeLine}> <Button className="m-1 p-2" onClick={makeLine}>
추가 추가
</Button> </Button>
<Button className="m-1 p-2" onClick={makePolygon}> <Button className="m-1 p-2" onClick={makePolygon}>
다각형 추가 다각형 추가
</Button> </Button>
{templateType === 0 && (
<>
<Button <Button
className="m-1 p-2" className="m-1 p-2"
onClick={() => { onClick={() => {
@ -513,29 +545,38 @@ export default function Roof2() {
> >
점선 추가 점선 추가
</Button> </Button>
<Button className="m-1 p-2" onClick={saveImage}>
저장
</Button>
<Button className="m-1 p-2" onClick={makeQPolygon}> <Button className="m-1 p-2" onClick={makeQPolygon}>
QPolygon QPolygon
</Button> </Button>
<Button className="m-1 p-2" onClick={rotateShape}> </>
회전 )}
<Button className="m-1 p-2" onClick={saveImage}>
저장
</Button> </Button>
<Button className="m-1 p-2" onClick={makeQLine}> {/*<Button className="m-1 p-2" onClick={rotateShape}>
회전
</Button>*/}
{/*<Button className="m-1 p-2" onClick={makeQLine}>
QLine QLine
</Button> </Button>
<Button className="m-1 p-2" onClick={PolygonToLine}> <Button className="m-1 p-2" onClick={PolygonToLine}>
PolygonToLine PolygonToLine
</Button> </Button>
{/*<Button className="m-1 p-2" onClick={handleSaveCanvas}>
canvas 내용 저장하기
</Button>
<Button className="m-1 p-2" onClick={handleLoadCanvas}>
canvas 내용 불러오기
</Button>*/}
<Button className="m-1 p-2" onClick={addCanvas}> <Button className="m-1 p-2" onClick={addCanvas}>
캔버스 추가 캔버스 추가
</Button>*/}
{templateType === 1 && (
<>
<Button className="m-1 p-2" onClick={drawRoofMaterial}>
지붕타입 지붕재
</Button>
<Button className="m-1 p-2" onClick={createRoofRack}>
지붕가대
</Button>
</>
)}
<Button className="m-1 p-2" onClick={deleteCell}>
선택 지우기
</Button> </Button>
<Button className="m-1 p-2" color={`${showControl ? 'primary' : 'default'}`} onClick={handleShowController}> <Button className="m-1 p-2" color={`${showControl ? 'primary' : 'default'}`} onClick={handleShowController}>
canvas 컨트롤러 {`${showControl ? '숨기기' : '보이기'}`} canvas 컨트롤러 {`${showControl ? '숨기기' : '보이기'}`}

View File

@ -0,0 +1,7 @@
export default function Archive() {
return (
<>
<h1>Community Archive</h1>
</>
)
}

View File

@ -0,0 +1,7 @@
export default function Faq() {
return (
<>
<h1>Community FAQ</h1>
</>
)
}

View File

@ -0,0 +1,7 @@
export default function Notice() {
return (
<>
<h1>Community Notice</h1>
</>
)
}

View File

@ -10,8 +10,9 @@ export const QLine = fabric.util.createClass(fabric.Line, {
length: 0, length: 0,
direction: null, direction: null,
idx: 0, idx: 0,
area: 0,
initialize: function (points, options, canvas) { initialize: function (points, options, canvas) {
this.callSuper('initialize', points, options) this.callSuper('initialize', points, { ...options, selectable: options.selectable ?? false })
if (options.id) { if (options.id) {
this.id = options.id this.id = options.id
} else { } else {
@ -23,16 +24,9 @@ export const QLine = fabric.util.createClass(fabric.Line, {
point = Math.round(point) point = Math.round(point)
}) })
const scaleX = this.scaleX
const scaleY = this.scaleY
const x1 = this.left
const y1 = this.top
const x2 = this.left + this.width * scaleX
const y2 = this.top + this.height * scaleY
this.idx = options.idx ?? 0 this.idx = options.idx ?? 0
const dx = x2 - x1
const dy = y2 - y1 this.setLength()
this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(0))
this.direction = options.direction ?? getDirection({ x: this.x1, y: this.y1 }, { x: this.x2, y: this.y2 }) this.direction = options.direction ?? getDirection({ x: this.x1, y: this.y1 }, { x: this.x2, y: this.y2 })
@ -67,9 +61,22 @@ export const QLine = fabric.util.createClass(fabric.Line, {
}) })
}, },
setLength() {
const scaleX = this.scaleX
const scaleY = this.scaleY
const x1 = this.left
const y1 = this.top
const x2 = this.left + this.width * scaleX
const y2 = this.top + this.height * scaleY
const dx = x2 - x1
const dy = y2 - y1
this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(0))
},
addLengthText() { addLengthText() {
const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id) const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
this.setLength()
const scaleX = this.scaleX const scaleX = this.scaleX
const scaleY = this.scaleY const scaleY = this.scaleY
const x1 = this.left const x1 = this.left
@ -79,13 +86,10 @@ export const QLine = fabric.util.createClass(fabric.Line, {
if (thisText) { if (thisText) {
thisText.set({ text: this.length.toFixed(0).toString(), left: (x1 + x2) / 2, top: (y1 + y2) / 2 }) thisText.set({ text: this.length.toFixed(0).toString(), left: (x1 + x2) / 2, top: (y1 + y2) / 2 })
this.text = thisText
return return
} }
const dx = x2 - x1
const dy = y2 - y1
this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(0))
const text = new fabric.Textbox(this.length.toFixed(0).toString(), { const text = new fabric.Textbox(this.length.toFixed(0).toString(), {
left: (x1 + x2) / 2, left: (x1 + x2) / 2,
top: (y1 + y2) / 2, top: (y1 + y2) / 2,
@ -113,4 +117,12 @@ export const QLine = fabric.util.createClass(fabric.Line, {
setCanvas(canvas) { setCanvas(canvas) {
this.canvas = canvas this.canvas = canvas
}, },
setViewLengthText(bool) {
const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
if (thisText) {
thisText.set({ visible: bool })
}
return this
},
}) })

View File

@ -2,8 +2,7 @@ import { fabric } from 'fabric'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util' import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util'
import { drawHelpLineInHexagon } from '@/util/qpolygon-utils' import { calculateAngle, dividePolygon, drawHelpLineInHexagon,drawHippedRoof } from '@/util/qpolygon-utils'
import { drawHippedRoof } from '@/util/qpolygon-utils'
export const QPolygon = fabric.util.createClass(fabric.Polygon, { export const QPolygon = fabric.util.createClass(fabric.Polygon, {
type: 'QPolygon', type: 'QPolygon',
@ -14,6 +13,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
hips: [], hips: [],
ridges: [], ridges: [],
connectRidges: [], connectRidges: [],
cells: [],
parentId: null,
innerLines: [], innerLines: [],
initialize: function (points, options, canvas) { initialize: function (points, options, canvas) {
// 소수점 전부 제거 // 소수점 전부 제거
@ -21,11 +22,28 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
point.x = Math.round(point.x) point.x = Math.round(point.x)
point.y = Math.round(point.y) point.y = Math.round(point.y)
}) })
if (points.length <= 8) { options.sort = options.sort ?? true
options.parentId = options.parentId ?? null
if (!options.sort && points.length <= 8) {
points = sortedPointLessEightPoint(points) points = sortedPointLessEightPoint(points)
} else { } else {
let isDiagonal = false
points.forEach((point, i) => {
if (isDiagonal) {
return
}
const nextPoint = points[(i + 1) % points.length]
const angle = calculateAngle(point, nextPoint)
if (!(Math.abs(angle) === 0 || Math.abs(angle) === 180)) {
isDiagonal = true
}
})
if (!isDiagonal) {
points = sortedPoints(points) points = sortedPoints(points)
} }
}
this.callSuper('initialize', points, options) this.callSuper('initialize', points, options)
if (options.id) { if (options.id) {
@ -79,6 +97,9 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), { return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), {
type: this.type, type: this.type,
text: this.text, text: this.text,
hips: this.hips,
ridges: this.ridges,
connectRidges: this.connectRidges,
}) })
}, },
init: function () { init: function () {
@ -121,6 +142,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
direction: getDirectionByPoint(point, nextPoint), direction: getDirectionByPoint(point, nextPoint),
idx: i, idx: i,
}) })
line.startPoint = point
line.endPoint = nextPoint
this.lines.push(line) this.lines.push(line)
}) })
}, },
@ -132,7 +155,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
}, },
addLengthText() { addLengthText() {
let points = this.points let points = this.getCurrentPoints()
points.forEach((start, i) => { points.forEach((start, i) => {
const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id && obj.idx === i) const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id && obj.idx === i)
@ -183,7 +206,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.canvas = canvas this.canvas = canvas
}, },
fillCell(cell = { width: 50, height: 100, padding: 10 }) { fillCell(cell = { width: 50, height: 100, padding: 10 }) {
const points = this.points const points = this.getCurrentPoints()
const minX = Math.min(...points.map((p) => p.x)) const minX = Math.min(...points.map((p) => p.x))
const maxX = Math.max(...points.map((p) => p.x)) const maxX = Math.max(...points.map((p) => p.x))
@ -204,6 +227,10 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
//센터 정렬시에 쓴다 체크박스가 존재함 TODO: if문 추가해서 정렬해야함 //센터 정렬시에 쓴다 체크박스가 존재함 TODO: if문 추가해서 정렬해야함
let tmpWidth = (boundingBoxWidth - (rectWidth + cell.padding) * cols) / 2 let tmpWidth = (boundingBoxWidth - (rectWidth + cell.padding) * cols) / 2
const drawCellsArray = [] //그려진 셀의 배열
let idx = 1
for (let i = 0; i < cols; i++) { for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) { for (let j = 0; j < rows; j++) {
const rectLeft = minX + i * (rectWidth + cell.padding) + tmpWidth const rectLeft = minX + i * (rectWidth + cell.padding) + tmpWidth
@ -225,24 +252,31 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
width: rectWidth, width: rectWidth,
height: rectHeight, height: rectHeight,
fill: '#BFFD9F', fill: '#BFFD9F',
stroke: 'black',
selectable: true, // 선택 가능하게 설정 selectable: true, // 선택 가능하게 설정
lockMovementX: true, // X 축 이동 잠금 lockMovementX: true, // X 축 이동 잠금
lockMovementY: true, // Y 축 이동 잠금 lockMovementY: true, // Y 축 이동 잠금
lockRotation: true, // 회전 잠금 lockRotation: true, // 회전 잠금
lockScalingX: true, // X 축 크기 조정 잠금 lockScalingX: true, // X 축 크기 조정 잠금
lockScalingY: true, // Y 축 크기 조정 잠금 lockScalingY: true, // Y 축 크기 조정 잠금
opacity: 0.6, opacity: 0.8,
name: 'cell',
idx: idx,
parentId: this.id,
}) })
idx++
drawCellsArray.push(rect) //배열에 넣어서 반환한다
this.canvas.add(rect) this.canvas.add(rect)
} }
} }
} }
this.canvas?.renderAll()
this.canvas.renderAll() this.cells = drawCellsArray
return drawCellsArray
}, },
inPolygon(point) { inPolygon(point) {
const vertices = this.getCurrentPoints() const vertices = this.points
let intersects = 0 let intersects = 0
for (let i = 0; i < vertices.length; i++) { for (let i = 0; i < vertices.length; i++) {
@ -303,23 +337,14 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
return minDistance return minDistance
}, },
getCurrentPoints() { getCurrentPoints() {
const scaleX = this.scaleX const pathOffset = this.get('pathOffset')
const scaleY = this.scaleY const matrix = this.calcTransformMatrix()
return this.get('points')
const left = this.left .map(function (p) {
const top = this.top return new fabric.Point(p.x - pathOffset.x, p.y - pathOffset.y)
})
// 시작점 .map(function (p) {
const point = this.points[0] return fabric.util.transformPoint(p, matrix)
const movingX = left - point.x * scaleX
const movingY = top - point.y * scaleY
return this.points.map((point) => {
return {
x: point.x * scaleX + movingX,
y: point.y * scaleY + movingY,
}
}) })
}, },
setWall: function (wall) { setWall: function (wall) {
@ -330,5 +355,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
text.set({ visible: isView }) text.set({ visible: isView })
}) })
}, },
divideLine() {}, divideLine() {
dividePolygon(this)
},
}) })

View File

@ -1,98 +0,0 @@
import { fabric } from 'fabric'
export default class QRect extends fabric.Rect {
#text = []
#viewLengthText
#fontSize
type = 'QRect'
constructor(option) {
if (!option.fontSize) {
throw new Error('Font size is required.')
}
super(option)
this.#fontSize = option.fontSize
this.#init(option)
this.#addControl()
}
#init(option) {
this.#viewLengthText = option.viewLengthText ?? true
}
setViewLengthText(bool) {
this.#viewLengthText = bool
this.#addLengthText()
}
setFontSize(fontSize) {
this.#fontSize = fontSize
this.#addLengthText()
}
#addControl() {
this.on('removed', () => {
if (this.#text.length > 0) {
this.#text.forEach((text) => {
this.canvas.remove(text)
})
this.#text = []
}
})
this.on('added', () => {
this.#addLengthText()
})
this.on('modified', (e) => {
this.#addLengthText()
})
this.on('scaling', (e) => {
this.#addLengthText()
})
this.on('moving', () => {
this.#addLengthText()
})
}
#addLengthText() {
if (this.#text.length > 0) {
this.#text.forEach((text) => {
this.canvas.remove(text)
})
this.#text = []
}
if (!this.#viewLengthText) {
return
}
const scaleX = this.scaleX
const scaleY = this.scaleY
const lines = [
{
start: { x: this.left, y: this.top },
end: { x: this.left + this.width * scaleX, y: this.top },
},
{
start: { x: this.left, y: this.top + this.height * scaleY },
end: { x: this.left, y: this.top },
},
]
lines.forEach((line) => {
const dx = line.end.x - line.start.x
const dy = line.end.y - line.start.y
const length = Math.sqrt(dx * dx + dy * dy)
const text = new fabric.Text(length.toFixed(0), {
left: (line.start.x + line.end.x) / 2,
top: (line.start.y + line.end.y) / 2,
fontSize: this.#fontSize,
selectable: false,
})
this.#text.push(text)
this.canvas.add(text)
})
}
}

View File

@ -0,0 +1,7 @@
export default function Plan() {
return (
<>
<h1>Management Plan</h1>
</>
)
}

View File

@ -0,0 +1,7 @@
export default function Stuff() {
return (
<>
<h1>Management Stuff</h1>
</>
)
}

View File

@ -0,0 +1,7 @@
export default function Company() {
return (
<>
<h1>Master Company</h1>
</>
)
}

View File

@ -0,0 +1,7 @@
export default function Price() {
return (
<>
<h1>Master Price</h1>
</>
)
}

View File

@ -0,0 +1,33 @@
import { Select, SelectItem } from '@nextui-org/react'
import styles from './QSelect.module.css'
const animals = [
{ key: 'cat', label: 'Cat' },
{ key: 'dog', label: 'Dog' },
{ key: 'elephant', label: 'Elephant' },
{ key: 'lion', label: 'Lion' },
{ key: 'tiger', label: 'Tiger' },
{ key: 'giraffe', label: 'Giraffe' },
{ key: 'dolphin', label: 'Dolphin' },
{ key: 'penguin', label: 'Penguin' },
{ key: 'zebra', label: 'Zebra' },
{ key: 'shark', label: 'Shark' },
{ key: 'whale', label: 'Whale' },
{ key: 'otter', label: 'Otter' },
{ key: 'crocodile', label: 'Crocodile' },
]
export default function QSelect() {
return (
<>
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
<Select label="Select an animal" className="max-w-xs">
{animals.map((animal) => (
<SelectItem key={animal.key}>{animal.label}</SelectItem>
))}
</Select>
</div>
<div className={styles.test}>test</div>
</>
)
}

View File

@ -0,0 +1,3 @@
.test {
@apply bg-blue-500;
}

View File

@ -0,0 +1,19 @@
import DatePicker from 'react-datepicker'
import 'react-datepicker/dist/react-datepicker.css'
export default function RangeDatePicker(props) {
const { startRangeDate, endRangeDate, setDateRange } = props
return (
<DatePicker
dateFormat={`yyyy-MM-dd`}
selectsRange={true}
startDate={startRangeDate}
endDate={endRangeDate}
onChange={(update) => {
setDateRange(update)
}}
isClearable={true}
/>
)
}

View File

@ -0,0 +1,8 @@
import DatePicker from 'react-datepicker'
import 'react-datepicker/dist/react-datepicker.css'
export default function SingleDatePicker(props) {
const { startDate, setStartDate } = props
return <DatePicker showIcon dateFormat={`yyyy-MM-dd`} selected={startDate} onChange={(date) => setStartDate(date)} />
}

View File

@ -5,8 +5,9 @@ import { actionHandler, anchorWrapper, polygonPositionHandler } from '@/util/can
import { useRecoilState } from 'recoil' import { useRecoilState } from 'recoil'
import { canvasSizeState, fontSizeState } from '@/store/canvasAtom' import { canvasSizeState, fontSizeState } from '@/store/canvasAtom'
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
import QRect from '@/components/fabric/QRect'
import { QPolygon } from '@/components/fabric/QPolygon' import { QPolygon } from '@/components/fabric/QPolygon'
import { defineQLine } from '@/util/qline-utils'
import { defineQPloygon } from '@/util/qpolygon-utils'
export function useCanvas(id) { export function useCanvas(id) {
const [canvas, setCanvas] = useState() const [canvas, setCanvas] = useState()
@ -63,6 +64,7 @@ export function useCanvas(id) {
if (canvas) { if (canvas) {
initialize() initialize()
canvas?.on('object:added', onChange) canvas?.on('object:added', onChange)
canvas?.on('object:added', addEventOnObject)
canvas?.on('object:modified', onChange) canvas?.on('object:modified', onChange)
canvas?.on('object:removed', onChange) canvas?.on('object:removed', onChange)
canvas?.on('mouse:move', drawMouseLines) canvas?.on('mouse:move', drawMouseLines)
@ -91,6 +93,35 @@ export function useCanvas(id) {
canvas?.off('mouse:down', handleMouseDown) canvas?.off('mouse:down', handleMouseDown)
} }
const addEventOnObject = (e) => {
const target = e.target
if (target.name === 'cell') {
target.on('mousedown', () => {
if (target.get('selected')) {
target.set({ selected: false })
target.set({ fill: '#BFFD9F' })
} else {
target.set({ selected: true })
target.set({ fill: 'red' })
}
canvas?.renderAll()
})
}
if (target.name === 'trestle') {
target.on('mousedown', () => {
if (target.get('selected')) {
target.set({ strokeWidth: 1 })
target.set({ selected: false })
} else {
target.set({ strokeWidth: 5 })
target.set({ selected: true })
}
canvas?.renderAll()
})
}
}
/** /**
* 마우스 포인터의 가이드라인을 제거합니다. * 마우스 포인터의 가이드라인을 제거합니다.
*/ */
@ -114,22 +145,8 @@ export function useCanvas(id) {
fabric.QPolygon = QPolygon fabric.QPolygon = QPolygon
QPolygon.prototype.canvas = canvas QPolygon.prototype.canvas = canvas
QLine.prototype.canvas = canvas QLine.prototype.canvas = canvas
QRect.prototype.canvas = canvas defineQLine()
defineQPloygon()
fabric.QLine.fromObject = function (object, callback) {
function _callback(instance) {
delete instance.points
callback && callback(instance)
}
const options = fabric.util.object.clone(object, true)
options.points = [object.x1, object.y1, object.x2, object.y2]
fabric.Object._fromObject('QLine', options, _callback, 'points')
}
fabric.QPolygon.fromObject = function (object, callback) {
fabric.Object._fromObject('QPolygon', object, callback, 'points')
}
} }
/** /**
@ -336,8 +353,8 @@ export function useCanvas(id) {
let top = targetObj.top + 10 let top = targetObj.top + 10
if (top > CANVAS.HEIGHT) { if (top > canvasSize.vertical) {
top = CANVAS.HEIGHT top = canvasSize.vertical
} }
targetObj.set({ top: top }) targetObj.set({ top: top })
@ -384,8 +401,8 @@ export function useCanvas(id) {
let left = targetObj.left + 10 let left = targetObj.left + 10
if (left > CANVAS.WIDTH) { if (left > canvasSize.horizontal) {
left = CANVAS.WIDTH left = canvasSize.horizontal
} }
targetObj.set({ left: left }) targetObj.set({ left: left })
@ -526,7 +543,7 @@ export function useCanvas(id) {
} }
} }
canvas.renderAll() canvas?.renderAll()
} }
const setCanvasBackgroundWithDots = (canvas, gap) => { const setCanvasBackgroundWithDots = (canvas, gap) => {
@ -553,7 +570,24 @@ export function useCanvas(id) {
const addCanvas = () => { const addCanvas = () => {
// const canvasState = canvas // const canvasState = canvas
const objs = canvas?.toJSON(['selectable', 'name', 'parentId', 'id', 'length', 'idx', 'direction', 'lines', 'points']) const objs = canvas?.toJSON([
'selectable',
'name',
'parentId',
'id',
'length',
'idx',
'direction',
'lines',
'points',
'lockMovementX',
'lockMovementY',
'lockRotation',
'lockScalingX',
'lockScalingY',
'opacity',
'cells',
])
const str = JSON.stringify(objs) const str = JSON.stringify(objs)
@ -563,16 +597,12 @@ export function useCanvas(id) {
// 역직렬화하여 캔버스에 객체를 다시 추가합니다. // 역직렬화하여 캔버스에 객체를 다시 추가합니다.
canvas?.loadFromJSON(JSON.parse(str), function () { canvas?.loadFromJSON(JSON.parse(str), function () {
// 모든 객체가 로드되고 캔버스에 추가된 후 호출됩니다. // 모든 객체가 로드되고 캔버스에 추가된 후 호출됩니다.
console.log(canvas?.getObjects().filter((obj) => obj.name === 'roof'))
canvas?.renderAll() // 캔버스를 다시 그립니다. canvas?.renderAll() // 캔버스를 다시 그립니다.
}) })
}, 1000) }, 1000)
} }
const changeCanvas = (idx) => {
canvas?.clear()
const canvasState = JSON.parse(canvasList[idx])
}
return { return {
canvas, canvas,
addShape, addShape,
@ -588,6 +618,5 @@ export function useCanvas(id) {
handleFlip, handleFlip,
setCanvasBackgroundWithDots, setCanvasBackgroundWithDots,
addCanvas, addCanvas,
changeCanvas,
} }
} }

View File

@ -1,10 +1,10 @@
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import QRect from '@/components/fabric/QRect' import { findTopTwoIndexesByDistance, getCenterPoint, getDirection, getStartIndex, rearrangeArray } from '@/util/canvas-util'
import { findTopTwoIndexesByDistance, getCenterPoint, getDirection, getDirectionByPoint, getStartIndex, rearrangeArray } from '@/util/canvas-util'
import { useRecoilState } from 'recoil' import { useRecoilState } from 'recoil'
import { import {
canvasSizeState, canvasSizeState,
drewRoofCellsState,
fontSizeState, fontSizeState,
roofPolygonArrayState, roofPolygonArrayState,
roofPolygonPatternArrayState, roofPolygonPatternArrayState,
@ -16,19 +16,19 @@ import {
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
import { fabric } from 'fabric' import { fabric } from 'fabric'
import { QPolygon } from '@/components/fabric/QPolygon' import { QPolygon } from '@/components/fabric/QPolygon'
import offsetPolygon from '@/util/qpolygon-utils'
export const Mode = { export const Mode = {
DRAW_LINE: 'drawLine', // 기준선 긋기모드 DRAW_LINE: 'drawLine', // 기준선 긋기모드`
EDIT: 'edit', EDIT: 'edit',
TEMPLATE: 'template', TEMPLATE: 'template',
PATTERNA: 'patterna', PATTERNA: 'patterna',
PATTERNB: 'patternb', PATTERNB: 'patternb',
TEXTBOX: 'textbox', TEXTBOX: 'textbox',
DRAW_RECT: 'drawRect', DRAW_RECT: 'drawRect',
ROOF_PATTERN: 'roofPattern', ROOF_PATTERN: 'roofPattern', //지붕패턴 모드
MODULE: 'module', ROOF_TRESTLE: 'roofTrestle', //지붕가대 모드
ROOF_TRESTLE: 'roofTrestle', FILL_CELLS: 'fillCells', //태양광셀 모드
FILL_CELLS: 'fillCells',
DEFAULT: 'default', DEFAULT: 'default',
} }
@ -57,6 +57,8 @@ export function useMode() {
const [canvasSize] = useRecoilState(canvasSizeState) const [canvasSize] = useRecoilState(canvasSizeState)
const [selectedCellRoofArray, setSelectedCellRoofArray] = useState([]) const [selectedCellRoofArray, setSelectedCellRoofArray] = useState([])
const [drewRoofCells, setDrewRoofCells] = useRecoilState(drewRoofCellsState)
const [roofStyle, setRoofStyle] = useState(1) //기본 지붕 패턴
useEffect(() => { useEffect(() => {
// 이벤트 리스너 추가 // 이벤트 리스너 추가
@ -214,9 +216,6 @@ export function useMode() {
const addEvent = (mode) => { const addEvent = (mode) => {
switch (mode) { switch (mode) {
case 'default':
canvas?.off('mouse:down')
break
case 'drawLine': case 'drawLine':
drawLineMode() drawLineMode()
break break
@ -247,6 +246,9 @@ export function useMode() {
case 'fillCells': case 'fillCells':
makeRoofFillCells() makeRoofFillCells()
break break
case 'default':
canvas?.off('mouse:down')
break
} }
} }
@ -327,7 +329,7 @@ export function useMode() {
points.current = [endPointCircle] points.current = [endPointCircle]
canvas.renderAll() canvas?.renderAll()
} }
const handleKeyDown = (e) => { const handleKeyDown = (e) => {
@ -520,6 +522,7 @@ export function useMode() {
// handleOuterlines() // handleOuterlines()
const wall = makePolygon() const wall = makePolygon()
wall.set({ name: 'wall' })
setWall(wall) setWall(wall)
return wall return wall
@ -531,8 +534,7 @@ export function useMode() {
if (historyPoints.current.length >= 4) { if (historyPoints.current.length >= 4) {
const wall = drawWallPolygon() const wall = drawWallPolygon()
handleOuterlinesTest2(wall) handleOuterlinesTest2(wall)
/*setWall(wall) setTemplateType(1)
roof.drawHelpLine()*/
} }
} }
@ -613,27 +615,6 @@ export function useMode() {
rect.set({ width: Math.abs(origX - pointer.x) }) rect.set({ width: Math.abs(origX - pointer.x) })
rect.set({ height: Math.abs(origY - pointer.y) }) rect.set({ height: Math.abs(origY - pointer.y) })
}) })
canvas.on('mouse:up', function (o) {
const pointer = canvas.getPointer(o.e)
const qRect = new QRect({
left: origX,
top: origY,
originX: 'left',
originY: 'top',
width: pointer.x - origX,
height: pointer.y - origY,
angle: 0,
viewLengthText: true,
fill: 'transparent',
stroke: 'black',
transparentCorners: false,
fontSize: fontSize,
})
canvas.remove(rect)
canvas.add(qRect)
isDown = false
})
} }
/** /**
@ -641,6 +622,9 @@ export function useMode() {
* a : 시작점, b : 끝점 * a : 시작점, b : 끝점
*/ */
const drawLineWithLength = (a, b) => { const drawLineWithLength = (a, b) => {
if (!a || !b) {
return
}
const line = new QLine([a.left, a.top, b.left, b.top], { const line = new QLine([a.left, a.top, b.left, b.top], {
stroke: 'black', stroke: 'black',
strokeWidth: 2, strokeWidth: 2,
@ -700,7 +684,6 @@ export function useMode() {
stroke: 'black', stroke: 'black',
fill: 'transparent', fill: 'transparent',
viewLengthText: true, viewLengthText: true,
selectable: true,
fontSize: fontSize, fontSize: fontSize,
}, },
canvas, canvas,
@ -712,7 +695,7 @@ export function useMode() {
// 캔버스를 다시 그립니다. // 캔버스를 다시 그립니다.
if (!otherLines) { if (!otherLines) {
// polygon.fillCell() // polygon.fillCell()
canvas.renderAll() canvas?.renderAll()
// polygon.setViewLengthText(false) // polygon.setViewLengthText(false)
setMode(Mode.DEFAULT) setMode(Mode.DEFAULT)
} }
@ -727,6 +710,7 @@ export function useMode() {
canvas?.clear() canvas?.clear()
startPoint.current = null startPoint.current = null
setEndPoint(null) setEndPoint(null)
setTemplateType(0)
points.current = [] points.current = []
historyPoints.current = [] historyPoints.current = []
historyLines.current = [] historyLines.current = []
@ -1109,71 +1093,19 @@ export function useMode() {
/** /**
* 지붕 외곽선 생성 polygon을 입력받아 만들기 * 지붕 외곽선 생성 polygon을 입력받아 만들기
*/ */
const handleOuterlinesTest2 = (polygon, offset = 71) => { const handleOuterlinesTest2 = (polygon, offset = 50) => {
const offsetPoints = [] const offsetPoints = offsetPolygon(polygon.points, offset)
const sortedIndex = getStartIndex(polygon.lines)
let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex)
if (tmpArraySorted[0].direction === 'right') { const roof = makePolygon(
//시계방향 offsetPoints.map((point) => {
tmpArraySorted = tmpArraySorted.reverse() //그럼 배열을 거꾸로 만들어서 무조건 반시계방향으로 배열 보정 return { x1: point.x, y1: point.y }
} }),
)
setSortedArray(tmpArraySorted) //recoil에 넣음
const points = tmpArraySorted.map((line) => ({
x: line.x1,
y: line.y1,
}))
for (let i = 0; i < points.length; i++) {
const prev = points[(i - 1 + points.length) % points.length]
const current = points[i]
const next = points[(i + 1) % points.length]
// 두 벡터 계산 (prev -> current, current -> next)
const vector1 = { x: current.x - prev.x, y: current.y - prev.y }
const vector2 = { x: next.x - current.x, y: next.y - current.y }
// 벡터의 길이 계산
const length1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y)
const length2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y)
// 벡터를 단위 벡터로 정규화
const unitVector1 = { x: vector1.x / length1, y: vector1.y / length1 }
const unitVector2 = { x: vector2.x / length2, y: vector2.y / length2 }
// 법선 벡터 계산 (왼쪽 방향)
const normal1 = { x: -unitVector1.y, y: unitVector1.x }
const normal2 = { x: -unitVector2.y, y: unitVector2.x }
// 법선 벡터 평균 계산
const averageNormal = {
x: (normal1.x + normal2.x) / 2,
y: (normal1.y + normal2.y) / 2,
}
// 평균 법선 벡터를 단위 벡터로 정규화
const lengthNormal = Math.sqrt(averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y)
const unitNormal = {
x: averageNormal.x / lengthNormal,
y: averageNormal.y / lengthNormal,
}
// 오프셋 적용
const offsetPoint = {
x1: current.x + unitNormal.x * offset,
y1: current.y + unitNormal.y * offset,
}
offsetPoints.push(offsetPoint)
}
const roof = makePolygon(offsetPoints)
roof.setWall(polygon) roof.setWall(polygon)
setRoof(roof) setRoof(roof)
roof.drawHelpLine() roof.drawHelpLine()
roof.divideLine()
} }
const drawRoofPolygon = (wall, offset = 50) => { const drawRoofPolygon = (wall, offset = 50) => {
@ -1579,7 +1511,7 @@ export function useMode() {
rtnLines.push(line) rtnLines.push(line)
}) })
canvas.renderAll() canvas?.renderAll()
} }
if (obj.type === 'QLine') { if (obj.type === 'QLine') {
const parent = obj.parent const parent = obj.parent
@ -1592,7 +1524,7 @@ export function useMode() {
}) })
parent.visible = true parent.visible = true
canvas.renderAll() canvas?.renderAll()
} }
return rtnLines return rtnLines
} }
@ -1614,15 +1546,15 @@ export function useMode() {
setTemplateType(2) setTemplateType(2)
} }
const handleOuterLineTemplateA4Points = (polygon) => { const handleOuterLineTemplateA4Points = (polygon, offsetInputX = 20, offsetInputY = 50) => {
const edge = 20 const edge = offsetInputX
const eaves = 50 const eaves = offsetInputY
// 폴리곤의 각 변을 선으로 생성 // 폴리곤의 각 변을 선으로 생성
const createLine = (start, end, stroke, property) => const createLine = (start, end, stroke, property) =>
new QLine([start.x, start.y, end.x, end.y], { new QLine([start.x, start.y, end.x, end.y], {
stroke, stroke,
strokeWidth: 5, strokeWidth: 1,
property, property,
fontSize: 14, fontSize: 14,
}) })
@ -1660,7 +1592,7 @@ export function useMode() {
strokeDashArray: dashArray, strokeDashArray: dashArray,
}) })
const vertCenterLine = createCenterLine(centerPointX, lines[0].y1 - edge, centerPointX, lines[0].y2 + edge, 'blue', 4, 'center') const vertCenterLine = createCenterLine(centerPointX, lines[0].y1 - edge, centerPointX, lines[0].y2 + edge, 'blue', 1, 'center')
canvas.add(vertCenterLine) canvas.add(vertCenterLine)
const horiCenterLineLeft = createCenterLine( const horiCenterLineLeft = createCenterLine(
@ -1722,6 +1654,31 @@ export function useMode() {
const outLine = createLine({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }, 'blue', 'normal') const outLine = createLine({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }, 'blue', 'normal')
canvas.add(outLine) canvas.add(outLine)
}) })
const roofPatternPolygonArray = []
const leftLine = drawArray[0]
const rightLine = drawArray[3]
//사각형 왼쪽 지붕 패턴 생성 배열
const leftPolygon = [
{ x: leftLine.x1, y: leftLine.y1 },
{ x: leftLine.x2, y: leftLine.y2 },
{ x: vertCenterLine.x1, y: vertCenterLine.y1 },
{ x: vertCenterLine.x2, y: vertCenterLine.y2 },
]
roofPatternPolygonArray.push(leftPolygon)
//사각형 오른쪽 지붕 패턴 생성 배열
const rightPolygon = [
{ x: vertCenterLine.x1, y: vertCenterLine.y1 },
{ x: vertCenterLine.x2, y: vertCenterLine.y2 },
{ x: rightLine.x1, y: rightLine.y1 },
{ x: rightLine.x2, y: rightLine.y2 },
]
roofPatternPolygonArray.push(rightPolygon)
setRoofPolygonPattern({ roofPatternPolygonArray, lines }) //모든 행을 저장
} }
//탬플릿A 적용 //탬플릿A 적용
@ -2428,7 +2385,7 @@ export function useMode() {
roofPatternPolygonArray.push(smallRoofPolygon) //작은지붕폴리곤 roofPatternPolygonArray.push(smallRoofPolygon) //작은지붕폴리곤
setRoofPolygonPattern({ roofPatternPolygonArray, lines }) //모든 행을 저장 setRoofPolygonPattern({ roofPatternPolygonArray, lines }) //모든 행을 저장
canvas.renderAll() canvas?.renderAll()
} }
const handleOuterLineTemplateA8Points = (polygon, offsetInputX = 20, offsetInputY = 50) => { const handleOuterLineTemplateA8Points = (polygon, offsetInputX = 20, offsetInputY = 50) => {
@ -2760,8 +2717,6 @@ export function useMode() {
let tmpArray = [] let tmpArray = []
let tmpBigArray = [] let tmpBigArray = []
console.log('tmpVertCenterLine', tmpVertCenterLine)
const lastCenterLine = tmpVertCenterLine[tmpVertCenterLine.length - 1] //마지막 센터라인을 정의 const lastCenterLine = tmpVertCenterLine[tmpVertCenterLine.length - 1] //마지막 센터라인을 정의
for (let i = 0; i < tmpVertCenterLine.length - 1; i++) { for (let i = 0; i < tmpVertCenterLine.length - 1; i++) {
@ -2836,8 +2791,6 @@ export function useMode() {
} }
} }
console.log('roofPatternPolygonArray', roofPatternPolygonArray)
setRoofPolygonPattern({ roofPatternPolygonArray, lines }) setRoofPolygonPattern({ roofPatternPolygonArray, lines })
} else { } else {
// 오목한 부분이 세로선일때 아래ㄷ, 위ㄷ // 오목한 부분이 세로선일때 아래ㄷ, 위ㄷ
@ -3031,7 +2984,7 @@ export function useMode() {
} }
setRoofPolygonPattern({ roofPatternPolygonArray, lines }) setRoofPolygonPattern({ roofPatternPolygonArray, lines })
} }
canvas.renderAll() canvas?.renderAll()
} }
/** /**
@ -3142,7 +3095,7 @@ export function useMode() {
setRoofPolygonPattern({ roofPatternPolygonArray, lines: polygon.lines }) setRoofPolygonPattern({ roofPatternPolygonArray, lines: polygon.lines })
} }
canvas.renderAll() canvas?.renderAll()
} }
/** /**
@ -3479,7 +3432,7 @@ export function useMode() {
setRoofPolygonPattern({ roofPatternPolygonArray, lines: polygon.lines }) setRoofPolygonPattern({ roofPatternPolygonArray, lines: polygon.lines })
// } // }
canvas.renderAll() canvas?.renderAll()
} }
/** /**
@ -3557,26 +3510,14 @@ export function useMode() {
}) })
canvas.add(overLine) canvas.add(overLine)
}) })
canvas.renderAll() canvas?.renderAll()
} }
const makeRoofPatternPolygon = (roofStyle) => { const getRoofPattern = (roofStyle, mode = 'normal') => {
if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) { const ratio = window.devicePixelRatio || 1
alert('객체가 비어있습니다.')
return
}
//내부 선 점선으로 변경 const inputPatternSize = { width: 30, height: 20 } //임시 사이즈
roofPolygonPattern.lines.forEach((line, index) => { const patternSize = { ...inputPatternSize } // 입력된 값을 뒤집기 위해
line.line.set('strokeDashArray', [10, 5, 2, 5])
line.line.set('stroke', 'blue')
line.line.set('strokeWidth', 1)
})
var ratio = window.devicePixelRatio || 1
let inputPatternSize = { width: 30, height: 20 } //임시 사이즈
let patternSize = { ...inputPatternSize } // 입력된 값을 뒤집기 위해
if (templateType === 2) { if (templateType === 2) {
//세로형이면 width height를 바꿈 //세로형이면 width height를 바꿈
@ -3585,17 +3526,26 @@ export function useMode() {
// 패턴 소스를 위한 임시 캔버스 생성 // 패턴 소스를 위한 임시 캔버스 생성
const patternSourceCanvas = document.createElement('canvas') const patternSourceCanvas = document.createElement('canvas')
patternSourceCanvas.width = patternSize.width * ratio patternSourceCanvas.width = roofStyle === 2 ? patternSize.width * 2 * ratio : patternSize.width * ratio
patternSourceCanvas.height = patternSize.height * ratio patternSourceCanvas.height = roofStyle === 2 ? patternSize.height * 2 * ratio : patternSize.height * ratio
const ctx = patternSourceCanvas.getContext('2d') const ctx = patternSourceCanvas.getContext('2d')
// 벽돌 패턴 그리기 // 벽돌 패턴 그리기
ctx.scale(ratio, ratio) ctx.scale(ratio, ratio)
if (mode === 'cell') {
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'
ctx.fillRect(0, 0, patternSize.width * 2, patternSize.height * 2)
}
ctx.strokeStyle = 'green' ctx.strokeStyle = 'green'
ctx.lineWidth = 0.4 ctx.lineWidth = 0.4
// 첫 번째 행 벽돌 // 첫 번째 행 벽돌
if (roofStyle === 2) { if (roofStyle === 2) {
patternSize.width = patternSize.width * 2
patternSize.height = patternSize.height * 2
//지그재그 //지그재그
// // 두 번째 행 벽돌 // // 두 번째 행 벽돌
if (templateType === 2) { if (templateType === 2) {
@ -3615,10 +3565,35 @@ export function useMode() {
repeat: 'repeat', repeat: 'repeat',
}) })
return pattern
}
/**
* 지붕 패턴 생성 로직
* @param roofStyle
*/
const makeRoofPatternPolygon = (roofStyle) => {
if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) {
alert('객체가 비어있습니다.')
return
}
setRoofStyle(roofStyle) //클릭한 지붕패턴을 저장
//내부 선 점선으로 변경 추후에 다시 되돌리는 로직 필요
roofPolygonPattern.lines.forEach((line, index) => {
line.line.set('strokeDashArray', [10, 5, 2, 5])
line.line.set('stroke', 'blue')
line.line.set('strokeWidth', 1)
})
const pattern = getRoofPattern(roofStyle)
const commonOption = { const commonOption = {
fill: pattern, fill: pattern,
selectable: false, selectable: false,
fontSize: 15, // fontSize는 필요에 따라 조정 fontSize: 15, // fontSize는 필요에 따라 조정
sort: false,
lockMovementX: true, lockMovementX: true,
lockMovementY: true, lockMovementY: true,
lockRotation: true, lockRotation: true,
@ -3628,6 +3603,7 @@ export function useMode() {
let polygonArray = [] let polygonArray = []
//패턴 폴리곤을 생성 후 배열에 담음
roofPolygonPattern.roofPatternPolygonArray.forEach((patternPolygon, index) => { roofPolygonPattern.roofPatternPolygonArray.forEach((patternPolygon, index) => {
const drawPolygon = new QPolygon(patternPolygon, commonOption) const drawPolygon = new QPolygon(patternPolygon, commonOption)
canvas.add(drawPolygon) canvas.add(drawPolygon)
@ -3635,11 +3611,15 @@ export function useMode() {
drawPolygon.set('customIndex', index) drawPolygon.set('customIndex', index)
polygonArray.push(drawPolygon) polygonArray.push(drawPolygon)
}) })
canvas.renderAll() canvas?.renderAll()
//지붕 폴리곤 recoil에 담음
setRoofPolygonArray(polygonArray) setRoofPolygonArray(polygonArray)
} }
/**
* 가대 생성 로직
*/
const makeRoofTrestle = () => { const makeRoofTrestle = () => {
if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) { if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) {
alert('객체가 비어있습니다.') alert('객체가 비어있습니다.')
@ -3660,8 +3640,13 @@ export function useMode() {
strokeWidth: 3, strokeWidth: 3,
} }
/**
* 지붕가대 생성 가대 선택 이벤트를 추가하는 로직
* @param polygon
*/
function toggleSelection(polygon) { function toggleSelection(polygon) {
if (polygon.strokeWidth === defualtStrokeStyle.strokeWidth) { if (polygon.strokeWidth === defualtStrokeStyle.strokeWidth) {
//기본 선택이랑 스트로크 굵기가 같으면 선택 안됨으로 봄
polygon.set({ polygon.set({
stroke: selectedStrokeStyle.stroke, stroke: selectedStrokeStyle.stroke,
strokeWidth: selectedStrokeStyle.strokeWidth, strokeWidth: selectedStrokeStyle.strokeWidth,
@ -3670,6 +3655,7 @@ export function useMode() {
canvas.discardActiveObject() // 객체의 활성 상태 해제 canvas.discardActiveObject() // 객체의 활성 상태 해제
selectedAreaArray.push(polygon) selectedAreaArray.push(polygon)
} else { } else {
//선택후 재선택하면 선택안됨으로 변경
polygon.set({ polygon.set({
stroke: defualtStrokeStyle.stroke, stroke: defualtStrokeStyle.stroke,
strokeWidth: defualtStrokeStyle.strokeWidth, strokeWidth: defualtStrokeStyle.strokeWidth,
@ -3677,13 +3663,17 @@ export function useMode() {
}) })
canvas.discardActiveObject() // 객체의 활성 상태 해제 canvas.discardActiveObject() // 객체의 활성 상태 해제
const removeIndex = polygon.customIndex //폴리곤에 커스텀 인덱스를 가지고 해당 배열 인덱스를 찾아 삭제함
const removeArrayIndex = selectedAreaArray.findIndex((x) => x.customIndex === removeIndex) const removeIndex = polygon.idx
const removeArrayIndex = selectedAreaArray.findIndex((x) => x.idx === removeIndex)
selectedAreaArray.splice(removeArrayIndex, 1) selectedAreaArray.splice(removeArrayIndex, 1)
} }
canvas.renderAll() canvas?.renderAll()
} }
const pattern = getRoofPattern(roofStyle, 'cell')
// 외각선을 안쪽으로 그려 가대선을 그린다.
polygons.forEach((polygon, index) => { polygons.forEach((polygon, index) => {
const trestlePolygon = handleOuterlinesTest(polygon, -12) const trestlePolygon = handleOuterlinesTest(polygon, -12)
trestlePolygon.setViewLengthText(false) //얘는 set으로 안먹는다... trestlePolygon.setViewLengthText(false) //얘는 set으로 안먹는다...
@ -3697,37 +3687,118 @@ export function useMode() {
lockScalingX: true, lockScalingX: true,
lockScalingY: true, lockScalingY: true,
bringToFront: true, bringToFront: true,
customIndex: polygon.customIndex, idx: polygon.customIndex, //가대 폴리곤의 임시 인덱스를 넣어줌
name: 'trestlePolygon',
}) })
/**
* 가대 선택 이벤트
*/
trestlePolygon.on('mousedown', function () { trestlePolygon.on('mousedown', function () {
const customIndex = polygon.get('customIndex')
toggleSelection(trestlePolygon) toggleSelection(trestlePolygon)
}) })
console.log('polygon', polygon)
polygon.set({ fill: pattern })
}) })
setSelectedCellRoofArray(selectedAreaArray) setSelectedCellRoofArray(selectedAreaArray)
canvas?.renderAll()
setMode(Mode.DEFAULT) //default 모드로 변경
} }
/**
* 가대 선택 채우기
*/
const makeRoofFillCells = () => { const makeRoofFillCells = () => {
const selectedCellRoofs = selectedCellRoofArray const drawCellsArray = []
if (selectedCellRoofs.length === 0) {
if (selectedCellRoofArray.length === 0) {
//배열에 선택된 가대 셀이 없으면 리턴
alert('선택된 영역이 없습니다.') alert('선택된 영역이 없습니다.')
setMode(Mode.DEFAULT) //default 모드로 변경
return return
} }
let inputCellSize = { width: 172, height: 113 }
let cellSize = { ...inputCellSize } //기본으로 가로형으로 넣고 if (drewRoofCells.length > 0) {
//리코일에
if (confirm('패널이 초기화 됩니다.')) {
drewRoofCells.forEach((cells, index) => {
cells.drawCells.forEach((cell) => {
canvas?.remove(cell)
})
})
setDrewRoofCells([])
canvas?.renderAll()
}
}
const inputCellSize = { width: 172, height: 113 }
const cellSize = { ...inputCellSize } //기본으로 가로형으로 넣고
if (templateType === 2) { if (templateType === 2) {
;[cellSize.width, cellSize.height] = [cellSize.height, cellSize.width] ;[cellSize.width, cellSize.height] = [cellSize.height, cellSize.width]
} }
selectedCellRoofs.forEach((polygon, index) => { selectedCellRoofArray.forEach((polygon, index) => {
polygon.fillCell({ width: cellSize.width, height: cellSize.height, padding: 10 }) const drawCells = polygon.fillCell({ width: cellSize.width, height: cellSize.height, padding: 10 })
drawCellsArray.push({ roofIndex: polygon.customIndex, drawCells: drawCells })
}) })
setDrewRoofCells(drawCellsArray)
setMode(Mode.DEFAULT) //default 모드로 변경
}
const createRoofRack = () => {
const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roof')
let roofCells = [] // roof에 적재된 cell들
roofs.forEach((roof, index) => {
let maxLengthLine = roof.lines.reduce((acc, cur) => {
return acc.length > cur.length ? acc : cur
})
const offsetPolygonPoint = offsetPolygon(roof.points, -20)
const trestlePoly = new QPolygon(offsetPolygonPoint, {
fill: 'transparent',
stroke: 'red',
strokeDashArray: [5, 5],
strokeWidth: 1,
selectable: true,
fontSize: fontSize,
name: 'trestle',
lockMovementX: true, // X 축 이동 잠금
lockMovementY: true, // Y 축 이동 잠금
lockRotation: true, // 회전 잠금
lockScalingX: true, // X 축 크기 조정 잠금
lockScalingY: true, // Y 축 크기 조정 잠금
idx: index,
parentId: roof.id,
})
canvas?.add(trestlePoly)
let drawRoofCells
if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
drawRoofCells = trestlePoly.fillCell({ width: 100, height: 50, padding: 10 })
trestlePoly.direction = 'south'
} else {
drawRoofCells = trestlePoly.fillCell({ width: 50, height: 100, padding: 10 })
trestlePoly.direction = 'east'
}
drawRoofCells.forEach((cell) => {
roofCells.push(cell)
})
})
setDrewRoofCells(roofCells)
} }
return { return {
mode, mode,
setMode,
changeMode, changeMode,
setCanvas, setCanvas,
handleClear, handleClear,
@ -3739,6 +3810,7 @@ export function useMode() {
handleOuterlinesTest2, handleOuterlinesTest2,
makeRoofPatternPolygon, makeRoofPatternPolygon,
makeRoofTrestle, makeRoofTrestle,
createRoofRack,
drawRoofPolygon, drawRoofPolygon,
} }
} }

56
src/lib/Axios.js Normal file
View File

@ -0,0 +1,56 @@
'use client'
import axios from 'axios'
axios.defaults.baseURL = process.env.NEXT_PUBLIC_API_SERVER_PATH
const axiosInstance = axios.create({
// baseURL: process.env.API_SERVER_URL,
headers: {
Accept: 'application/json',
},
})
axiosInstance.interceptors.request.use((config) => {
// config['Authorization'] = localStorage.getItem('token')
//TODO: 인터셉터에서 추가 로직 구현
return config
})
axiosInstance.interceptors.request.use(undefined, (error) => {
//TODO: 인터셉터에서 에러 처리 로직 구현
// if (error.isAxiosError && e.response?.status === 401) {
// localStorage.removeItem('token')
// }
})
export const get = (url) =>
axiosInstance
.get(url)
.then((res) => res.data)
.catch(console.error)
export const post = (url, data) =>
axiosInstance
.post(url, data)
.then((res) => res.data)
.catch(console.error)
export const put = (url, data) =>
axiosInstance
.put(url, data)
.then((res) => res.data)
.catch(console.error)
export const patch = (url, data) =>
axiosInstance
.patch(url, data)
.then((res) => res.data)
.catch(console.error)
export const del = (url) =>
axiosInstance
.delete(url)
.then((res) => res.data)
.catch(console.error)

18
src/locales/client.js Normal file
View File

@ -0,0 +1,18 @@
'use client'
import { createI18nClient } from 'next-international/client'
export const { useI18n, useScopedI18n, I18nProviderClient, useChangeLocale, defineLocale, useCurrentLocale } = createI18nClient(
{
ko: () => import('./ko'),
ja: () => import('./ja'),
},
{
// Uncomment to set base path
// basePath: '/base',
// Uncomment to use custom segment name
// segmentName: 'locale',
// Uncomment to set fallback locale
// fallbackLocale: en,
},
)

7
src/locales/ja.js Normal file
View File

@ -0,0 +1,7 @@
console.log('Loaded JA')
export default {
hello: 'こんにちは',
welcome: 'こんにちは {name}!',
locale: '現在のロケールは {locale} です。',
}

7
src/locales/ko.js Normal file
View File

@ -0,0 +1,7 @@
console.log('Loaded KO')
export default {
hello: '안녕',
welcome: '안녕 {name}!',
locale: '현재 로케일은 {locale}입니다.',
}

14
src/locales/server.js Normal file
View File

@ -0,0 +1,14 @@
import { createI18nServer } from 'next-international/server'
export const { getI18n, getScopedI18n, getCurrentLocale, getStaticParams } = createI18nServer(
{
ko: () => import('./ko'),
ja: () => import('./ja'),
},
{
// Uncomment to use custom segment name
// segmentName: 'locale',
// Uncomment to set fallback locale
// fallbackLocale: en,
},
)

View File

@ -1,12 +1,27 @@
import { NextRequest, NextResponse } from 'next/server' import { createI18nMiddleware } from 'next-international/middleware'
const I18nMiddleware = createI18nMiddleware({
locales: ['ko', 'ja'],
defaultLocale: 'ko',
})
export function middleware(request) { export function middleware(request) {
const requestHeaders = new Headers(request.headers) return I18nMiddleware(request)
requestHeaders.set('x-pathname', request.nextUrl.pathname)
return NextResponse.next({
request: {
headers: requestHeaders,
},
})
} }
export const config = {
matcher: ['/((?!api|static|.*\\..*|_next|favicon.ico|robots.txt).*)'],
}
// import { NextRequest, NextResponse } from 'next/server'
// export function middleware(request) {
// const requestHeaders = new Headers(request.headers)
// requestHeaders.set('x-pathname', request.nextUrl.pathname)
// return NextResponse.next({
// request: {
// headers: requestHeaders,
// },
// })
// }

View File

@ -50,6 +50,20 @@ export const roofPolygonArrayState = atom({
export const templateTypeState = atom({ export const templateTypeState = atom({
key: 'templateType', key: 'templateType',
default: 1, //1:모임지붕, 2:A타입, 3:B타입 default: 0, //1:모임지붕, 2:A타입, 3:B타입
dangerouslyAllowMutability: true,
})
//셀 그린 이후에 생성하는 state
export const drewRoofCellsState = atom({
key: 'drewRoofCells',
default: [],
dangerouslyAllowMutability: true,
})
// 지붕재 width, height, rafter(서까래), roofStyle을 갖고있고 roofStyle 1은 정방향, 2는 지그재그
export const roofMaterialState = atom({
key: 'roofMaterial',
default: { width: 20, height: 10, rafter: 0, roofStyle: 2 },
dangerouslyAllowMutability: true, dangerouslyAllowMutability: true,
}) })

3
src/styles/_test.scss Normal file
View File

@ -0,0 +1,3 @@
.test {
background-color: #121212;
}

1
src/styles/style.scss Normal file
View File

@ -0,0 +1 @@
@import '_test.scss';

View File

@ -2,8 +2,14 @@ import { fabric } from 'fabric'
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
export const defineQLine = () => { export const defineQLine = () => {
/*fabric.QLine = QLine fabric.QLine.fromObject = function (object, callback) {
fabric.QLine.fromObject = (object, callback) => { function _callback(instance) {
return new fabric.QLine([object.x1, object.y1, object.x2, object.y2], object) delete instance.points
}*/ callback && callback(instance)
}
const options = fabric.util.object.clone(object, true)
options.points = [object.x1, object.y1, object.x2, object.y2]
fabric.Object._fromObject('QLine', options, _callback, 'points')
}
} }

View File

@ -1,16 +1,13 @@
import { fabric } from 'fabric' import { fabric } from 'fabric'
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
import { distanceBetweenPoints, findClosestPoint, getAdjacent, getRoofHypotenuse } from '@/util/canvas-util' import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDirectionByPoint, getAdjacent, getRoofHypotenuse } from '@/util/canvas-util'
import { QPolygon } from '@/components/fabric/QPolygon'
const TWO_PI = Math.PI * 2
export const defineQPloygon = () => { export const defineQPloygon = () => {
fabric.QPolygon = fabric.util.createClass(fabric.Group, {})
// fromObject 메서드를 QLine 클래스에 직접 추가
fabric.QPolygon.fromObject = function (object, callback) { fabric.QPolygon.fromObject = function (object, callback) {
const { initOption, initPoints, initLengthTxt } = object fabric.Object._fromObject('QPolygon', object, callback, 'points')
fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) {
return callback(new QPolygon(initPoints, object, initLengthTxt))
})
} }
} }
@ -25,7 +22,7 @@ export const drawHelpLineInHexagon = (polygon, chon) => {
const ridgeStartPoints = [] const ridgeStartPoints = []
const ridgeEndPoints = [] const ridgeEndPoints = []
const centerInterSectionPoints = [] let centerInterSectionPoints = []
// polygon.lines = polygon.lines.sort((a, b) => a.length - b.length) // polygon.lines = polygon.lines.sort((a, b) => a.length - b.length)
polygon.wall.lines = getOneSideLines(polygon.wall) polygon.wall.lines = getOneSideLines(polygon.wall)
@ -107,9 +104,9 @@ export const drawHelpLineInHexagon = (polygon, chon) => {
: nextLine.startPoint : nextLine.startPoint
line.connectedPoint = { interSectionPoint, area, startPoint, endPoint } line.connectedPoint = { interSectionPoint, area, startPoint, endPoint }
line.connectedPoints.push(interSectionPoint) line.connectedPoints.push({ interSectionPoint, area, startPoint, endPoint })
nextLine.connectedPoint = { interSectionPoint, area, startPoint, endPoint } nextLine.connectedPoint = { interSectionPoint, area, startPoint, endPoint }
nextLine.connectedPoints.push(interSectionPoint) nextLine.connectedPoints.push({ interSectionPoint, area, startPoint, endPoint })
} }
} }
}) })
@ -156,6 +153,12 @@ export const drawHelpLineInHexagon = (polygon, chon) => {
name: 'hip', name: 'hip',
}) })
line.startPoint = point.startPoint
line.endPoint = point.interSectionPoint
line2.startPoint = point.endPoint
line2.endPoint = point.interSectionPoint
polygon.hips.push(line) polygon.hips.push(line)
polygon.hips.push(line2) polygon.hips.push(line2)
@ -171,20 +174,26 @@ export const drawHelpLineInHexagon = (polygon, chon) => {
uniqueInterSectionPoints.forEach((point) => { uniqueInterSectionPoints.forEach((point) => {
const interSectionPoint = point.interSectionPoint const interSectionPoint = point.interSectionPoint
if (connectedPoint.x === interSectionPoint.x && connectedPoint.y === interSectionPoint.y) { if (connectedPoint.interSectionPoint.x === interSectionPoint.x && connectedPoint.interSectionPoint.y === interSectionPoint.y) {
removedIdx.push(line.idx) removedIdx.push(line.idx)
} }
}) })
}) })
}) })
const notIntersectedLines = helpLines.filter((line) => !removedIdx.includes(line.idx)) let notIntersectedLines = helpLines.filter((line) => !removedIdx.includes(line.idx))
notIntersectedLines = notIntersectedLines.map((line) => {
return { ...line, centerInterSectionPoints: [] }
})
notIntersectedLines.forEach((line) => { notIntersectedLines.forEach((line) => {
centerLines.forEach((centerLine) => { centerLines.forEach((centerLine) => {
const interSectionPoint = calculateIntersection(line, centerLine) const interSectionPoint = calculateIntersection(line, centerLine)
if (interSectionPoint && polygon.inPolygon(interSectionPoint) && polygon.wall.inPolygon(interSectionPoint)) { if (interSectionPoint && polygon.inPolygon(interSectionPoint) && polygon.wall.inPolygon(interSectionPoint)) {
line.centerInterSectionPoints.push(interSectionPoint)
interSectionPoint.lineIdx = line.idx
centerInterSectionPoints.push(interSectionPoint) centerInterSectionPoints.push(interSectionPoint)
} }
}) })
@ -193,11 +202,12 @@ export const drawHelpLineInHexagon = (polygon, chon) => {
// centerInterSectionPoints에서 ridgeStartPoints와 x가 같거나 y가 같은것중 가장 가까운 점들을 찾는다. // centerInterSectionPoints에서 ridgeStartPoints와 x가 같거나 y가 같은것중 가장 가까운 점들을 찾는다.
ridgeStartPoints.forEach((point) => { ridgeStartPoints.forEach((point) => {
const xPoints = centerInterSectionPoints.filter((centerPoint) => Math.abs(centerPoint.x - point.x) < 2) const xPoints = centerInterSectionPoints.filter((centerPoint) => Math.abs(centerPoint.x - point.x) < 2)
const yPoints = centerInterSectionPoints.filter((centerPoint) => Math.abs(centerPoint.y - point.y) < 2) const yPoints = centerInterSectionPoints.filter((centerPoint) => Math.abs(centerPoint.y - point.y) < 2)
let closestPoint let closestPoint
if (xPoints.length === 0) { if (xPoints.length === 0) {
closestPoint = findClosestPoint(point, yPoints) closestPoint = findClosestPoint(point, yPoints)
} else { } else if (yPoints.length === 0) {
closestPoint = findClosestPoint(point, xPoints) closestPoint = findClosestPoint(point, xPoints)
} }
@ -206,17 +216,55 @@ export const drawHelpLineInHexagon = (polygon, chon) => {
stroke: 'purple', stroke: 'purple',
fontSize: polygon.fontSize, fontSize: polygon.fontSize,
name: 'ridge', name: 'ridge',
direction: getDirectionByPoint(point, closestPoint),
}) })
line.startPoint = point
line.endPoint = closestPoint
polygon.ridges.push(line) polygon.ridges.push(line)
polygon.canvas.add(line) polygon.canvas.add(line)
ridgeEndPoints.push(closestPoint) ridgeEndPoints.push(closestPoint)
notIntersectedLines = notIntersectedLines.filter((line) => line.idx !== closestPoint.lineIdx)
} }
}) })
// ridgeEndPoints끼리 이어준다. centerInterSectionPoints = []
const remainingPoints = ridgeEndPoints notIntersectedLines.forEach((line) => {
centerInterSectionPoints.push(...line.centerInterSectionPoints)
})
// ridgeEndPoints끼리 이어준다.
const remainingPoints = [...ridgeEndPoints]
// ridgeEndPoint에서 centerInterSectionPoints와 45도인 점을 찾아 이어준다.
ridgeEndPoints.forEach((ridgePoint) => {
const filteredCenterInterSectionPoints = centerInterSectionPoints.filter((centerPoint) => {
const degree = calculateAngle(ridgePoint, centerPoint)
return Math.abs(degree) === 45 || Math.abs(degree) === 135
})[0]
if (filteredCenterInterSectionPoints) {
const line = new QLine([ridgePoint.x, ridgePoint.y, filteredCenterInterSectionPoints.x, filteredCenterInterSectionPoints.y], {
stroke: 'purple',
fontSize: polygon.fontSize,
name: 'hip',
})
if (line.length === 0) {
return
}
line.startPoint = ridgePoint
line.endPoint = filteredCenterInterSectionPoints
polygon.hips.push(line)
polygon.canvas.add(line)
ridgeStartPoints.push(filteredCenterInterSectionPoints)
remainingPoints.forEach((ridgePoint) => {
polygon.points.forEach((point) => { polygon.points.forEach((point) => {
const degree = calculateAngle(ridgePoint, point) const degree = calculateAngle(ridgePoint, point)
@ -231,28 +279,89 @@ export const drawHelpLineInHexagon = (polygon, chon) => {
polygon.canvas.add(line) polygon.canvas.add(line)
} }
}) })
}
}) })
while (remainingPoints.length > 0) { // ridgeEndPoint끼리 연결한다.
const point = remainingPoints.shift() while (remainingPoints.length > 1) {
const closestPoint = findClosestPoint(point, remainingPoints) const startPoint = remainingPoints.shift()
if (!closestPoint) continue const endPoint = remainingPoints.shift()
// 마루끼리 연결
const line = new QLine([point.x, point.y, closestPoint.x, closestPoint.y], { if (!(startPoint.x === endPoint.x && startPoint.y === endPoint.y)) {
const line = new QLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], {
stroke: 'purple', stroke: 'purple',
fontSize: polygon.fontSize, fontSize: polygon.fontSize,
name: 'connectRidge', name: 'connectRidge',
}) })
line.startPoint = startPoint
line.endPoint = endPoint
polygon.connectRidges.push(line) polygon.connectRidges.push(line)
polygon.points.forEach((point) => {
const degree = calculateAngle(startPoint, point)
if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
const line = new QLine([startPoint.x, startPoint.y, point.x, point.y], {
stroke: 'purple',
fontSize: polygon.fontSize,
name: 'hip',
})
line.startPoint = startPoint
line.endPoint = point
polygon.hips.push(line)
polygon.canvas.add(line) polygon.canvas.add(line)
} }
})
polygon.points.forEach((point) => {
const degree = calculateAngle(endPoint, point)
if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
const line = new QLine([endPoint.x, endPoint.y, point.x, point.y], {
stroke: 'purple',
fontSize: polygon.fontSize,
name: 'hip',
})
line.startPoint = endPoint
line.endPoint = point
polygon.hips.push(line)
polygon.canvas.add(line)
}
})
polygon.canvas.add(line)
} else {
polygon.points.forEach((point) => {
const degree = calculateAngle(startPoint, point)
if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
const line = new QLine([startPoint.x, startPoint.y, point.x, point.y], {
stroke: 'purple',
fontSize: polygon.fontSize,
name: 'hip',
})
line.startPoint = startPoint
line.endPoint = point
polygon.hips.push(line)
polygon.canvas.add(line)
}
})
}
}
} }
export const drawCenterLines = (polygon) => { export const drawCenterLines = (polygon) => {
const centerLines = [] const centerLines = []
const oneSideLines = getOneSideLines(polygon) const oneSideLines = polygon.lines.map((line) => getOneSideLine(line))
const horizontalLines = oneSideLines.filter((line) => line.direction === 'right') const horizontalLines = oneSideLines.filter((line) => line.direction === 'right')
const verticalLines = oneSideLines.filter((line) => line.direction === 'bottom') const verticalLines = oneSideLines.filter((line) => line.direction === 'bottom')
@ -267,9 +376,6 @@ export const drawCenterLines = (polygon) => {
horizontalLines.forEach((line, index) => { horizontalLines.forEach((line, index) => {
const nextLine = horizontalLines[(index + 1) % horizontalLines.length] const nextLine = horizontalLines[(index + 1) % horizontalLines.length]
line.set({ strokeWidth: 5 })
nextLine.set({ strokeWidth: 5 })
polygon.canvas.renderAll() polygon.canvas.renderAll()
const startCenterX = Math.min(line.x1, nextLine.x1) const startCenterX = Math.min(line.x1, nextLine.x1)
@ -325,6 +431,8 @@ const getOneSideLines = (polygon) => {
line.x2 = newX2 line.x2 = newX2
line.y2 = newY2 line.y2 = newY2
line.direction = 'bottom' line.direction = 'bottom'
line.startPoint = { x: newX1, y: newY1 }
line.endPoint = { x: newX2, y: newY2 }
} else if (line.direction === 'left') { } else if (line.direction === 'left') {
newX1 = line.x2 newX1 = line.x2
newY1 = line.y2 newY1 = line.y2
@ -336,11 +444,13 @@ const getOneSideLines = (polygon) => {
line.x2 = newX2 line.x2 = newX2
line.y2 = newY2 line.y2 = newY2
line.direction = 'right' line.direction = 'right'
line.startPoint = { x: newX1, y: newY1 }
line.endPoint = { x: newX2, y: newY2 }
} }
return line return line
}) })
} }
const calculateAngle = (point1, point2) => { export const calculateAngle = (point1, point2) => {
const deltaX = point2.x - point1.x const deltaX = point2.x - point1.x
const deltaY = point2.y - point1.y const deltaY = point2.y - point1.y
const angleInRadians = Math.atan2(deltaY, deltaX) const angleInRadians = Math.atan2(deltaY, deltaX)
@ -382,6 +492,469 @@ const calculateTriangleArea = (point1, point2, point3) => {
return Math.abs(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2 return Math.abs(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2
} }
// polygon을 나눈다.
export const dividePolygon = (polygon) => {
let hips = polygon.hips
const ridges = polygon.ridges.map((ridge) => getOneSideLine(ridge))
const connectRidges = polygon.connectRidges
const polygonLines = polygon.lines
hips.forEach((hip) => {
// hips의 startPoint와 endPoint를 polygon의 points와 비교하여 같은 점이 endPoint일 경우 startPoint로 변경한다.
const startPoint = polygon.points.find((point) => point.x === hip.endPoint.x && point.y === hip.endPoint.y)
if (startPoint) {
const temp = hip.startPoint
hip.startPoint = hip.endPoint
hip.endPoint = temp
}
})
hips = [...hips, ...connectRidges]
polygonLines.forEach((line, index) => {
let ridge
const startPoint = line.startPoint
const endPoint = line.endPoint
let polygonPoints = []
polygonPoints.push(startPoint)
polygonPoints.push(endPoint)
const startHip = hips.find((hip) => hip.startPoint.x === startPoint.x && hip.startPoint.y === startPoint.y)
const endHip = hips.find((hip) => hip.startPoint.x === endPoint.x && hip.startPoint.y === endPoint.y)
if (!startHip || !endHip) {
return
}
if (startHip && endHip && startHip.endPoint.x === endHip.endPoint.x && startHip.endPoint.y === endHip.endPoint.y) {
polygonPoints.push(startHip.endPoint)
const newPolygon = new QPolygon(polygonPoints, {
fontSize: polygon.fontSize,
parentId: polygon.id,
name: 'roof',
selectable: false,
stroke: 'black',
fill: 'transparent',
strokeWidth: 3,
})
polygon.canvas.add(newPolygon)
return
}
let connectedRidge
const restRidgeConnection = connectRidges[0]
if (!restRidgeConnection || restRidgeConnection.length === 0) {
connectedRidge = ridges.find(
(ridge) =>
(ridge.startPoint.x === startHip.endPoint.x &&
ridge.startPoint.y === startHip.endPoint.y &&
ridge.endPoint.x === endHip.endPoint.x &&
ridge.endPoint.y === endHip.endPoint.y) ||
(ridge.startPoint.x === endHip.endPoint.x &&
ridge.startPoint.y === endHip.endPoint.y &&
ridge.endPoint.x === startHip.endPoint.x &&
ridge.endPoint.y === startHip.endPoint.y),
)
} else {
connectedRidge = ridges.find(
(ridge) =>
(ridge.startPoint.x === startHip.endPoint.x && ridge.startPoint.y === startHip.endPoint.y) ||
(ridge.endPoint.x === startHip.endPoint.x && ridge.endPoint.y === startHip.endPoint.y),
)
}
const hipStartPoint = startHip.endPoint
const hipEndPoint = endHip.endPoint
if (connectedRidge.startPoint.x === hipStartPoint.x && connectedRidge.startPoint.y === hipStartPoint.y) {
if (connectedRidge.endPoint.x === hipEndPoint.x && connectedRidge.endPoint.y === hipEndPoint.y) {
polygonPoints.push(connectedRidge.endPoint)
polygonPoints.push(connectedRidge.startPoint)
const newPolygon = new QPolygon(polygonPoints, {
fontSize: polygon.fontSize,
parentId: polygon.id,
name: 'roof',
selectable: false,
stroke: 'black',
fill: 'transparent',
strokeWidth: 3,
})
polygon.canvas.add(newPolygon)
return
}
} else if (connectedRidge.endPoint.x === hipStartPoint.x && connectedRidge.endPoint.y === hipStartPoint.y) {
if (connectedRidge.startPoint.x === hipEndPoint.x && connectedRidge.startPoint.y === hipEndPoint.y) {
polygonPoints.push(connectedRidge.startPoint)
polygonPoints.push(connectedRidge.endPoint)
const newPolygon = new QPolygon(polygonPoints, {
fontSize: polygon.fontSize,
parentId: polygon.id,
name: 'roof',
selectable: false,
stroke: 'black',
fill: 'transparent',
strokeWidth: 3,
sort: true,
})
polygon.canvas.add(newPolygon)
return
}
}
// 지붕이 꺾여있는 경우
if (
(restRidgeConnection.startPoint.x === startHip.endPoint.x && restRidgeConnection.startPoint.y === startHip.endPoint.y) ||
(restRidgeConnection.endPoint.x === startHip.endPoint.x && restRidgeConnection.endPoint.y === startHip.endPoint.y)
) {
polygonPoints = [startPoint, startHip.endPoint]
let lastPoint
if (restRidgeConnection.startPoint.x === startHip.endPoint.x && restRidgeConnection.startPoint.y === startHip.endPoint.y) {
lastPoint = restRidgeConnection.endPoint
polygonPoints.push(restRidgeConnection.endPoint)
} else {
lastPoint = restRidgeConnection.startPoint
polygonPoints.push(restRidgeConnection.startPoint)
}
connectedRidge = ridges.find(
(ridge) =>
(ridge.startPoint.x === lastPoint.x && ridge.startPoint.y === lastPoint.y) ||
(ridge.endPoint.x === lastPoint.x && ridge.endPoint.y === lastPoint.y),
)
if (connectedRidge.startPoint.x === lastPoint.x && connectedRidge.startPoint.y === lastPoint.y) {
polygonPoints.push(connectedRidge.endPoint)
} else {
polygonPoints.push(connectedRidge.startPoint)
}
polygonPoints.push(endPoint)
} else {
polygonPoints = [endPoint, endHip.endPoint]
let lastPoint
if (restRidgeConnection.startPoint.x === endHip.endPoint.x && restRidgeConnection.startPoint.y === endHip.endPoint.y) {
lastPoint = restRidgeConnection.endPoint
polygonPoints.push(restRidgeConnection.endPoint)
} else {
lastPoint = restRidgeConnection.startPoint
polygonPoints.push(restRidgeConnection.startPoint)
}
connectedRidge = ridges.find(
(ridge) =>
(ridge.startPoint.x === lastPoint.x && ridge.startPoint.y === lastPoint.y) ||
(ridge.endPoint.x === lastPoint.x && ridge.endPoint.y === lastPoint.y),
)
if (connectedRidge.startPoint.x === startHip.endPoint.x && connectedRidge.startPoint.y === startHip.endPoint.y) {
lastPoint = connectedRidge.startPoint
polygonPoints.push(connectedRidge.startPoint)
} else {
lastPoint = connectedRidge.endPoint
polygonPoints.push(connectedRidge.endPoint)
}
polygonPoints.push(startPoint)
}
const newPolygon = new QPolygon(polygonPoints, {
fontSize: polygon.fontSize,
parentId: polygon.id,
name: 'roof',
selectable: false,
stroke: 'black',
fill: 'transparent',
strokeWidth: 3,
})
polygon.canvas.add(newPolygon)
})
}
const getOneSideLine = (line) => {
// left, top 방향의 line은 right, bottom 방향의 line으로 변경한다.
const newLine = { ...line }
let newX1, newY1, newX2, newY2
if (newLine.direction === 'top') {
newX1 = newLine.x2
newY1 = newLine.y2
newX2 = newLine.x1
newY2 = newLine.y1
newLine.x1 = newX1
newLine.y1 = newY1
newLine.x2 = newX2
newLine.y2 = newY2
newLine.direction = 'bottom'
newLine.startPoint = { x: newX1, y: newY1 }
newLine.endPoint = { x: newX2, y: newY2 }
} else if (line.direction === 'left') {
newX1 = newLine.x2
newY1 = newLine.y2
newX2 = newLine.x1
newY2 = newLine.y1
newLine.x1 = newX1
newLine.y1 = newY1
newLine.x2 = newX2
newLine.y2 = newY2
newLine.direction = 'right'
newLine.startPoint = { x: newX1, y: newY1 }
newLine.endPoint = { x: newX2, y: newY2 }
}
return newLine
}
function inwardEdgeNormal(vertex1, vertex2) {
// Assuming that polygon vertices are in clockwise order
const dx = vertex2.x - vertex1.x
const dy = vertex2.y - vertex1.y
const edgeLength = Math.sqrt(dx * dx + dy * dy)
return {
x: -dy / edgeLength,
y: dx / edgeLength,
}
}
function outwardEdgeNormal(vertex1, vertex2) {
var n = inwardEdgeNormal(vertex1, vertex2)
return {
x: -n.x,
y: -n.y,
}
}
function createPolygon(vertices) {
const edges = []
let minX = vertices.length > 0 ? vertices[0].x : undefined
let minY = vertices.length > 0 ? vertices[0].y : undefined
let maxX = minX
let maxY = minY
for (let i = 0; i < vertices.length; i++) {
const vertex1 = vertices[i]
const vertex2 = vertices[(i + 1) % vertices.length]
const outwardNormal = outwardEdgeNormal(vertex1, vertex2)
const inwardNormal = inwardEdgeNormal(vertex1, vertex2)
const edge = {
vertex1,
vertex2,
index: i,
outwardNormal,
inwardNormal,
}
edges.push(edge)
const x = vertices[i].x
const y = vertices[i].y
minX = Math.min(x, minX)
minY = Math.min(y, minY)
maxX = Math.max(x, maxX)
maxY = Math.max(y, maxY)
}
return {
vertices,
edges,
minX,
minY,
maxX,
maxY,
}
}
// based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/, edgeA => "line a", edgeB => "line b"
function edgesIntersection(edgeA, edgeB) {
const den =
(edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) -
(edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y)
if (den == 0) {
return null // lines are parallel or coincident
}
const ua =
((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
(edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
den
const ub =
((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
(edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
den
// Edges are not intersecting but the lines defined by them are
const isIntersectionOutside = ua < 0 || ub < 0 || ua > 1 || ub > 1
return {
x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x),
y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y),
isIntersectionOutside,
}
}
function appendArc(arcSegments, vertices, center, radius, startVertex, endVertex, isPaddingBoundary) {
var startAngle = Math.atan2(startVertex.y - center.y, startVertex.x - center.x)
var endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x)
if (startAngle < 0) {
startAngle += TWO_PI
}
if (endAngle < 0) {
endAngle += TWO_PI
}
const angle = startAngle > endAngle ? startAngle - endAngle : startAngle + TWO_PI - endAngle
const angleStep = (isPaddingBoundary ? -angle : TWO_PI - angle) / arcSegments
vertices.push(startVertex)
for (let i = 1; i < arcSegments; ++i) {
const angle = startAngle + angleStep * i
const vertex = {
x: center.x + Math.cos(angle) * radius,
y: center.y + Math.sin(angle) * radius,
}
vertices.push(vertex)
}
vertices.push(endVertex)
}
function createOffsetEdge(edge, dx, dy) {
return {
vertex1: {
x: edge.vertex1.x + dx,
y: edge.vertex1.y + dy,
},
vertex2: {
x: edge.vertex2.x + dx,
y: edge.vertex2.y + dy,
},
}
}
function createMarginPolygon(polygon, offset, arcSegments = 0) {
const offsetEdges = []
for (let i = 0; i < polygon.edges.length; i++) {
const edge = polygon.edges[i]
const dx = edge.outwardNormal.x * offset
const dy = edge.outwardNormal.y * offset
offsetEdges.push(createOffsetEdge(edge, dx, dy))
}
const vertices = []
for (let i = 0; i < offsetEdges.length; i++) {
const thisEdge = offsetEdges[i]
const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
const vertex = edgesIntersection(prevEdge, thisEdge)
if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
vertices.push({
x: vertex.x,
y: vertex.y,
})
} else {
const arcCenter = polygon.edges[i].vertex1
appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, false)
}
}
const marginPolygon = createPolygon(vertices)
marginPolygon.offsetEdges = offsetEdges
return marginPolygon
}
function createPaddingPolygon(polygon, offset, arcSegments = 0) {
const offsetEdges = []
for (let i = 0; i < polygon.edges.length; i++) {
const edge = polygon.edges[i]
const dx = edge.inwardNormal.x * offset
const dy = edge.inwardNormal.y * offset
offsetEdges.push(createOffsetEdge(edge, dx, dy))
}
const vertices = []
for (let i = 0; i < offsetEdges.length; i++) {
const thisEdge = offsetEdges[i]
const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
const vertex = edgesIntersection(prevEdge, thisEdge)
if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
vertices.push({
x: vertex.x,
y: vertex.y,
})
} else {
const arcCenter = polygon.edges[i].vertex1
appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, true)
}
}
const paddingPolygon = createPolygon(vertices)
paddingPolygon.offsetEdges = offsetEdges
return paddingPolygon
}
export default function offsetPolygon(vertices, offset) {
const polygon = createPolygon(vertices)
const arcSegments = 0
const originPolygon = new QPolygon(vertices, { fontSize: 0 })
if (offset > 0) {
let result = createMarginPolygon(polygon, offset, arcSegments).vertices
const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point))
if (allPointsOutside) {
return createMarginPolygon(polygon, offset, arcSegments).vertices
} else {
return createPaddingPolygon(polygon, offset, arcSegments).vertices
}
} else {
let result = createPaddingPolygon(polygon, offset, arcSegments).vertices
const allPointsInside = result.every((point) => originPolygon.inPolygon(point))
if (allPointsInside) {
return createPaddingPolygon(polygon, offset, arcSegments).vertices
} else {
return createMarginPolygon(polygon, offset, arcSegments).vertices
}
}
}
export const drawHippedRoof = (polygon, chon) => { export const drawHippedRoof = (polygon, chon) => {
drawRoofRidge(polygon) drawRoofRidge(polygon)
// drawHips(polygon) // drawHips(polygon)

138
yarn.lock
View File

@ -21,6 +21,42 @@
dependencies: dependencies:
regenerator-runtime "^0.14.0" regenerator-runtime "^0.14.0"
"@floating-ui/core@^1.6.0":
version "1.6.5"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.5.tgz#102335cac0d22035b04d70ca5ff092d2d1a26f2b"
integrity sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA==
dependencies:
"@floating-ui/utils" "^0.2.5"
"@floating-ui/dom@^1.0.0":
version "1.6.8"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.8.tgz#45e20532b6d8a061b356a4fb336022cf2609754d"
integrity sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q==
dependencies:
"@floating-ui/core" "^1.6.0"
"@floating-ui/utils" "^0.2.5"
"@floating-ui/react-dom@^2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.1.tgz#cca58b6b04fc92b4c39288252e285e0422291fb0"
integrity sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==
dependencies:
"@floating-ui/dom" "^1.0.0"
"@floating-ui/react@^0.26.2":
version "0.26.20"
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.20.tgz#49ae23347666626db8671c2aa2df469bbec7db71"
integrity sha512-RixKJJG92fcIsVoqrFr4Onpzh7hlOx4U7NV4aLhMLmtvjZ5oTB/WzXaANYUZATKqXvvW7t9sCxtzejip26N5Ag==
dependencies:
"@floating-ui/react-dom" "^2.1.1"
"@floating-ui/utils" "^0.2.5"
tabbable "^6.0.0"
"@floating-ui/utils@^0.2.5":
version "0.2.5"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.5.tgz#105c37d9d9620ce69b7f692a20c821bf1ad2cbf9"
integrity sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==
"@formatjs/ecma402-abstract@2.0.0": "@formatjs/ecma402-abstract@2.0.0":
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz#39197ab90b1c78b7342b129a56a7acdb8f512e17" resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz#39197ab90b1c78b7342b129a56a7acdb8f512e17"
@ -2181,6 +2217,15 @@ asynckit@^0.4.0:
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
axios@^1.7.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.3.tgz#a1125f2faf702bc8e8f2104ec3a76fab40257d85"
integrity sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
balanced-match@^1.0.0: balanced-match@^1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
@ -2249,7 +2294,7 @@ canvas@^2.8.0:
nan "^2.17.0" nan "^2.17.0"
simple-get "^3.0.3" simple-get "^3.0.3"
chokidar@^3.5.3: "chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
version "3.6.0" version "3.6.0"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
@ -2269,7 +2314,7 @@ chownr@^2.0.0:
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
client-only@0.0.1: client-only@0.0.1, client-only@^0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz" resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
@ -2279,7 +2324,7 @@ clsx@^1.2.1:
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
clsx@^2.0.0: clsx@^2.0.0, clsx@^2.1.0:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
@ -2394,6 +2439,11 @@ data-urls@^3.0.1:
whatwg-mimetype "^3.0.0" whatwg-mimetype "^3.0.0"
whatwg-url "^11.0.0" whatwg-url "^11.0.0"
date-fns@^3.3.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.6.0.tgz#f20ca4fe94f8b754951b24240676e8618c0206bf"
integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==
debug@4: debug@4:
version "4.3.5" version "4.3.5"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz" resolved "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz"
@ -2539,6 +2589,11 @@ flat@^5.0.2:
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
follow-redirects@^1.15.6:
version "1.15.6"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
foreground-child@^3.1.0: foreground-child@^3.1.0:
version "3.2.1" version "3.2.1"
resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz" resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz"
@ -2700,6 +2755,11 @@ iconv-lite@0.6.3:
dependencies: dependencies:
safer-buffer ">= 2.1.2 < 3.0.0" safer-buffer ">= 2.1.2 < 3.0.0"
immutable@^4.0.0:
version "4.3.7"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381"
integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==
inflight@^1.0.4: inflight@^1.0.4:
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@ -2713,6 +2773,11 @@ inherits@2, inherits@^2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
international-types@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/international-types/-/international-types-0.8.1.tgz#c0e593d9911c1a23f64bbd6eb1abb2941fe2353f"
integrity sha512-tajBCAHo4I0LIFlmQ9ZWfjMWVyRffzuvfbXCd6ssFt5u1Zw15DN0UBpVTItXdNa1ls+cpQt3Yw8+TxsfGF8JcA==
intl-messageformat@^10.1.0: intl-messageformat@^10.1.0:
version "10.5.14" version "10.5.14"
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.14.tgz#e5bb373f8a37b88fbe647d7b941f3ab2a37ed00a" resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.14.tgz#e5bb373f8a37b88fbe647d7b941f3ab2a37ed00a"
@ -2883,7 +2948,7 @@ lodash.omit@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60"
integrity sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg== integrity sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==
loose-envify@^1.0.0, loose-envify@^1.1.0: loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -3037,6 +3102,15 @@ nanoid@^3.3.6, nanoid@^3.3.7:
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
next-international@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/next-international/-/next-international-1.2.4.tgz#abe50b2aa3ba7ecf92d41f87537796a4b2dd0ba3"
integrity sha512-JQvp+h2iSgA/t8hu5S/Lwow1ZErJutQRdpnplxjv4VTlCiND8T95fYih8BjkHcVhQbtM+Wu9Mb1CM32wD9hlWQ==
dependencies:
client-only "^0.0.1"
international-types "^0.8.1"
server-only "^0.0.1"
next@14.2.3: next@14.2.3:
version "14.2.3" version "14.2.3"
resolved "https://registry.npmjs.org/next/-/next-14.2.3.tgz" resolved "https://registry.npmjs.org/next/-/next-14.2.3.tgz"
@ -3233,6 +3307,20 @@ prisma@^5.17.0:
dependencies: dependencies:
"@prisma/engines" "5.17.0" "@prisma/engines" "5.17.0"
prop-types@^15.7.2:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
psl@^1.1.33: psl@^1.1.33:
version "1.9.0" version "1.9.0"
resolved "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz" resolved "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz"
@ -3253,6 +3341,17 @@ queue-microtask@^1.2.2:
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
react-datepicker@^7.3.0:
version "7.3.0"
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-7.3.0.tgz#666664a609d4b57b095083fe29d080943fa7b3ed"
integrity sha512-EqRKLAtLZUTztiq6a+tjSjQX9ES0Xd229JPckAtyZZ4GoY3rtvNWAzkYZnQUf6zTWT50Ki0+t+W9VRQIkSJLfg==
dependencies:
"@floating-ui/react" "^0.26.2"
clsx "^2.1.0"
date-fns "^3.3.1"
prop-types "^15.7.2"
react-onclickoutside "^6.13.0"
react-dom@^18: react-dom@^18:
version "18.3.1" version "18.3.1"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
@ -3261,6 +3360,16 @@ react-dom@^18:
loose-envify "^1.1.0" loose-envify "^1.1.0"
scheduler "^0.23.2" scheduler "^0.23.2"
react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-onclickoutside@^6.13.0:
version "6.13.1"
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.13.1.tgz#1f5e0241c08784b6e65745d91aca0d700c548a89"
integrity sha512-LdrrxK/Yh9zbBQdFbMTXPp3dTSN9B+9YJQucdDu3JNKRrbdU+H+/TVONJoWtOwy4II8Sqf1y/DTI6w/vGPYW0w==
react-remove-scroll-bar@^2.3.6: react-remove-scroll-bar@^2.3.6:
version "2.3.6" version "2.3.6"
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c"
@ -3383,6 +3492,15 @@ safe-buffer@~5.2.0:
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sass@^1.77.8:
version "1.77.8"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.8.tgz#9f18b449ea401759ef7ec1752a16373e296b52bd"
integrity sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
saxes@^5.0.1: saxes@^5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz" resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz"
@ -3419,6 +3537,11 @@ semver@^7.3.5:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13"
integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==
server-only@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/server-only/-/server-only-0.0.1.tgz#0f366bb6afb618c37c9255a314535dc412cd1c9e"
integrity sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==
set-blocking@^2.0.0: set-blocking@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@ -3467,7 +3590,7 @@ simple-swizzle@^0.2.2:
dependencies: dependencies:
is-arrayish "^0.3.1" is-arrayish "^0.3.1"
source-map-js@^1.0.2, source-map-js@^1.2.0: "source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2, source-map-js@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz"
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
@ -3574,6 +3697,11 @@ symbol-tree@^3.2.4:
resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz" resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
tabbable@^6.0.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
tailwind-merge@^1.14.0: tailwind-merge@^1.14.0:
version "1.14.0" version "1.14.0"
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-1.14.0.tgz#e677f55d864edc6794562c63f5001f45093cdb8b" resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-1.14.0.tgz#e677f55d864edc6794562c63f5001f45093cdb8b"