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:
commit
7e1c8c6710
3
.env.development
Normal file
3
.env.development
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
NEXT_PUBLIC_TEST="테스트변수입니다. development"
|
||||||
|
|
||||||
|
NEXT_PUBLIC_API_SERVER_PATH="http://localhost:8080"
|
||||||
3
.env.production
Normal file
3
.env.production
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
NEXT_PUBLIC_TEST="테스트변수입니다. production"
|
||||||
|
|
||||||
|
NEXT_PUBLIC_API_SERVER_PATH="http://localhost:8080"
|
||||||
BIN
Nextjs 14 컴포넌트에 대해서....pdf
Normal file
BIN
Nextjs 14 컴포넌트에 대해서....pdf
Normal file
Binary file not shown.
BIN
Qcast coding convention.pdf
Normal file
BIN
Qcast coding convention.pdf
Normal file
Binary file not shown.
BIN
Qcast development guilde.pdf
Normal file
BIN
Qcast development guilde.pdf
Normal file
Binary file not shown.
37
README.md
37
README.md
@ -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:
|
||||||
|
|
||||||

|
```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.
|
||||||
|
|
||||||

|
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.
|
||||||
|
|
||||||

|
## Learn More
|
||||||
|
|
||||||
### type4
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||

|
- [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.
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,6 @@ const config = {
|
|||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
export default config;
|
export default config
|
||||||
|
|||||||
19
shape-type.md
Normal file
19
shape-type.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 점 갯수 별 타입
|
||||||
|
|
||||||
|
## 점 6개
|
||||||
|
|
||||||
|
### type1
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### type2
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### type3
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### type4
|
||||||
|
|
||||||
|

|
||||||
11
src/app/[locale]/LocaleProvider.js
Normal file
11
src/app/[locale]/LocaleProvider.js
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
4
src/app/[locale]/changelog copy/changelog.module.css
Normal file
4
src/app/[locale]/changelog copy/changelog.module.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.test {
|
||||||
|
@apply bg-red-500;
|
||||||
|
@apply text-2xl;
|
||||||
|
}
|
||||||
73
src/app/[locale]/changelog copy/page.jsx
Normal file
73
src/app/[locale]/changelog copy/page.jsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
4
src/app/[locale]/changelog/changelog.module.css
Normal file
4
src/app/[locale]/changelog/changelog.module.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.test {
|
||||||
|
@apply bg-red-500;
|
||||||
|
@apply text-2xl;
|
||||||
|
}
|
||||||
73
src/app/[locale]/changelog/page.jsx
Normal file
73
src/app/[locale]/changelog/page.jsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
15
src/app/[locale]/community/archive/page.jsx
Normal file
15
src/app/[locale]/community/archive/page.jsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -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>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
15
src/app/[locale]/community/notice/page.jsx
Normal file
15
src/app/[locale]/community/notice/page.jsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
15
src/app/[locale]/error.jsx
Normal file
15
src/app/[locale]/error.jsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
14
src/app/[locale]/intro/page.jsx
Normal file
14
src/app/[locale]/intro/page.jsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
13
src/app/[locale]/layout.js
Normal file
13
src/app/[locale]/layout.js
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
15
src/app/[locale]/management/plan/page.jsx
Normal file
15
src/app/[locale]/management/plan/page.jsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
15
src/app/[locale]/management/stuff/page.jsx
Normal file
15
src/app/[locale]/management/stuff/page.jsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
15
src/app/[locale]/master/company/page.jsx
Normal file
15
src/app/[locale]/master/company/page.jsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
15
src/app/[locale]/master/price/page.jsx
Normal file
15
src/app/[locale]/master/price/page.jsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
25
src/app/[locale]/not-found.jsx
Normal file
25
src/app/[locale]/not-found.jsx
Normal 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
19
src/app/[locale]/page.js
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -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>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@ -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'
|
||||||
|
|||||||
@ -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 />
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
27
src/components/Main.jsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -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,94 +496,87 @@ 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>
|
||||||
<Button
|
{templateType === 0 && (
|
||||||
className="m-1 p-2"
|
<>
|
||||||
onClick={() => {
|
<Button
|
||||||
setCanvasBackgroundWithDots(canvas, 10)
|
className="m-1 p-2"
|
||||||
}}
|
onClick={() => {
|
||||||
>
|
setCanvasBackgroundWithDots(canvas, 10)
|
||||||
점선 추가
|
}}
|
||||||
</Button>
|
>
|
||||||
<Button
|
점선 추가
|
||||||
className="m-1 p-2"
|
</Button>
|
||||||
onClick={() => {
|
<Button
|
||||||
setCanvasBackgroundWithDots(canvas, 20)
|
className="m-1 p-2"
|
||||||
}}
|
onClick={() => {
|
||||||
>
|
setCanvasBackgroundWithDots(canvas, 20)
|
||||||
점선 추가
|
}}
|
||||||
</Button>
|
>
|
||||||
|
점선 추가
|
||||||
|
</Button>
|
||||||
|
<Button className="m-1 p-2" onClick={makeQPolygon}>
|
||||||
|
QPolygon
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Button className="m-1 p-2" onClick={saveImage}>
|
<Button className="m-1 p-2" onClick={saveImage}>
|
||||||
저장
|
저장
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="m-1 p-2" onClick={makeQPolygon}>
|
{/*<Button className="m-1 p-2" onClick={rotateShape}>
|
||||||
QPolygon
|
|
||||||
</Button>
|
|
||||||
<Button className="m-1 p-2" onClick={rotateShape}>
|
|
||||||
회전
|
회전
|
||||||
</Button>
|
</Button>*/}
|
||||||
<Button className="m-1 p-2" onClick={makeQLine}>
|
{/*<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 ? '숨기기' : '보이기'}`}
|
||||||
|
|||||||
7
src/components/community/Archive.jsx
Normal file
7
src/components/community/Archive.jsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default function Archive() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Community Archive</h1>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
7
src/components/community/Faq.jsx
Normal file
7
src/components/community/Faq.jsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default function Faq() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Community FAQ</h1>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
7
src/components/community/Notice.jsx
Normal file
7
src/components/community/Notice.jsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default function Notice() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Community Notice</h1>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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,10 +22,27 @@ 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 {
|
||||||
points = sortedPoints(points)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.callSuper('initialize', points, options)
|
this.callSuper('initialize', points, options)
|
||||||
@ -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,24 +337,15 @@ 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) {
|
||||||
this.wall = wall
|
this.wall = 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)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7
src/components/management/Plan.jsx
Normal file
7
src/components/management/Plan.jsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default function Plan() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Management Plan</h1>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
7
src/components/management/Stuff.jsx
Normal file
7
src/components/management/Stuff.jsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default function Stuff() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Management Stuff</h1>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
7
src/components/master/Company.jsx
Normal file
7
src/components/master/Company.jsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default function Company() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Master Company</h1>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
7
src/components/master/Price.jsx
Normal file
7
src/components/master/Price.jsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default function Price() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Master Price</h1>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
33
src/components/ui/QSelect.jsx
Normal file
33
src/components/ui/QSelect.jsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
3
src/components/ui/QSelect.module.css
Normal file
3
src/components/ui/QSelect.module.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.test {
|
||||||
|
@apply bg-blue-500;
|
||||||
|
}
|
||||||
19
src/components/ui/datepicker/RangeDatePicker.jsx
Normal file
19
src/components/ui/datepicker/RangeDatePicker.jsx
Normal 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}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
8
src/components/ui/datepicker/SingleDatePicker.jsx
Normal file
8
src/components/ui/datepicker/SingleDatePicker.jsx
Normal 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)} />
|
||||||
|
}
|
||||||
@ -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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
56
src/lib/Axios.js
Normal 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
18
src/locales/client.js
Normal 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
7
src/locales/ja.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
console.log('Loaded JA')
|
||||||
|
|
||||||
|
export default {
|
||||||
|
hello: 'こんにちは',
|
||||||
|
welcome: 'こんにちは {name}!',
|
||||||
|
locale: '現在のロケールは {locale} です。',
|
||||||
|
}
|
||||||
7
src/locales/ko.js
Normal file
7
src/locales/ko.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
console.log('Loaded KO')
|
||||||
|
|
||||||
|
export default {
|
||||||
|
hello: '안녕',
|
||||||
|
welcome: '안녕 {name}!',
|
||||||
|
locale: '현재 로케일은 {locale}입니다.',
|
||||||
|
}
|
||||||
14
src/locales/server.js
Normal file
14
src/locales/server.js
Normal 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,
|
||||||
|
},
|
||||||
|
)
|
||||||
@ -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,
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|||||||
@ -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
3
src/styles/_test.scss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.test {
|
||||||
|
background-color: #121212;
|
||||||
|
}
|
||||||
1
src/styles/style.scss
Normal file
1
src/styles/style.scss
Normal file
@ -0,0 +1 @@
|
|||||||
|
@import '_test.scss';
|
||||||
@ -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')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,53 +216,152 @@ 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)
|
||||||
remainingPoints.forEach((ridgePoint) => {
|
|
||||||
polygon.points.forEach((point) => {
|
|
||||||
const degree = calculateAngle(ridgePoint, point)
|
|
||||||
|
|
||||||
if (Math.abs(degree) % 45 < 1) {
|
|
||||||
const line = new QLine([ridgePoint.x, ridgePoint.y, point.x, point.y], {
|
|
||||||
stroke: 'purple',
|
|
||||||
fontSize: polygon.fontSize,
|
|
||||||
name: 'hip',
|
|
||||||
})
|
|
||||||
|
|
||||||
polygon.hips.push(line)
|
|
||||||
polygon.canvas.add(line)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
while (remainingPoints.length > 0) {
|
// ridgeEndPoints끼리 이어준다.
|
||||||
const point = remainingPoints.shift()
|
const remainingPoints = [...ridgeEndPoints]
|
||||||
const closestPoint = findClosestPoint(point, remainingPoints)
|
|
||||||
if (!closestPoint) continue
|
|
||||||
// 마루끼리 연결
|
|
||||||
const line = new QLine([point.x, point.y, closestPoint.x, closestPoint.y], {
|
|
||||||
stroke: 'purple',
|
|
||||||
fontSize: polygon.fontSize,
|
|
||||||
name: 'connectRidge',
|
|
||||||
})
|
|
||||||
polygon.connectRidges.push(line)
|
|
||||||
|
|
||||||
polygon.canvas.add(line)
|
// 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)
|
||||||
|
|
||||||
|
polygon.points.forEach((point) => {
|
||||||
|
const degree = calculateAngle(ridgePoint, point)
|
||||||
|
|
||||||
|
if (Math.abs(degree) % 45 < 1) {
|
||||||
|
const line = new QLine([ridgePoint.x, ridgePoint.y, point.x, point.y], {
|
||||||
|
stroke: 'purple',
|
||||||
|
fontSize: polygon.fontSize,
|
||||||
|
name: 'hip',
|
||||||
|
})
|
||||||
|
|
||||||
|
polygon.hips.push(line)
|
||||||
|
polygon.canvas.add(line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// ridgeEndPoint끼리 연결한다.
|
||||||
|
while (remainingPoints.length > 1) {
|
||||||
|
const startPoint = remainingPoints.shift()
|
||||||
|
const endPoint = remainingPoints.shift()
|
||||||
|
|
||||||
|
if (!(startPoint.x === endPoint.x && startPoint.y === endPoint.y)) {
|
||||||
|
const line = new QLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], {
|
||||||
|
stroke: 'purple',
|
||||||
|
fontSize: polygon.fontSize,
|
||||||
|
name: 'connectRidge',
|
||||||
|
})
|
||||||
|
|
||||||
|
line.startPoint = startPoint
|
||||||
|
line.endPoint = endPoint
|
||||||
|
|
||||||
|
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.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
138
yarn.lock
@ -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"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user