diff --git a/.env b/.env new file mode 100644 index 00000000..50ab0d74 --- /dev/null +++ b/.env @@ -0,0 +1,12 @@ +# Environment variables declared in this file are automatically made available to Prisma. +# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema + +# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. +# See the documentation for all the connection string options: https://pris.ly/d/connection-strings + +# DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public" +# DATABASE_URL="mongodb://yoo32767:GuCtswjLGqUaNL0G@cluster0.vsdtcnb.mongodb.net/sample_mflix?retryWrites=true&w=majority" +#DATABASE_URL = "mongodb%2Bsrv%3A%2F%2Fyoo32767%3AGuCtswjLGqUaNL0G%40cluster0.vsdtcnb.mongodb.net%2F%3FretryWrites%3Dtrue%26w%3Dmajority%26appName%3DCluster0" +# DATABASE_URL = "mongodb+srv://yoo32767:GuCtswjLGqUaNL0G@cluster0.vsdtcnb.mongodb.net/Cluster0?retryWrites=true&w=majority" +# DATABASE_URL="mongodb://yoo32767:GuCtswjLGqUaNL0G@cluster0.vsdtcnb.mongodb.net/sample_mflix?retryWrites=true&w=majority" +DATABASE_URL="mongodb+srv://yoo32767:GuCtswjLGqUaNL0G@cluster0.vsdtcnb.mongodb.net/mytest" \ No newline at end of file diff --git a/package.json b/package.json index f7fa513c..9c745fdf 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,11 @@ }, "dependencies": { "@nextui-org/react": "^2.4.2", + "@prisma/client": "^5.17.0", "fabric": "^5.3.0", "framer-motion": "^11.2.13", "mathjs": "^13.0.2", + "mongodb": "^6.8.0", "next": "14.2.3", "react": "^18", "react-dom": "^18", @@ -21,6 +23,7 @@ }, "devDependencies": { "postcss": "^8", + "prisma": "^5.17.0", "tailwindcss": "^3.4.1" } } diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 00000000..327b757a --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,19 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +model test { + id String @id @default(auto()) @map("_id") @db.ObjectId + content String +} + +model canvas { + id String @id @default(auto()) @map("_id") @db.ObjectId + loginId String + canvas Json +} \ No newline at end of file diff --git a/src/components/Roof2.jsx b/src/components/Roof2.jsx index 787ea08d..96d3a57e 100644 --- a/src/components/Roof2.jsx +++ b/src/components/Roof2.jsx @@ -6,12 +6,14 @@ import QRect from '@/components/fabric/QRect' import QPolygon from '@/components/fabric/QPolygon' import RangeSlider from './ui/RangeSlider' -import { useRecoilState } from 'recoil' -import { canvasSizeState, fontSizeState, sortedPolygonArray } from '@/store/canvasAtom' +import { useRecoilState, useRecoilValue } from 'recoil' +import { canvasAtom, canvasListState, canvasSizeState, fontSizeState, sortedPolygonArray } from '@/store/canvasAtom' import { QLine } from '@/components/fabric/QLine' +import { getTests, getCanvasState, insertCanvasState } from '@/lib/canvas' +import { calculateIntersection2 } from '@/util/canvas-util' export default function Roof2() { - const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage } = useCanvas('canvas') + const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas') //canvas 기본 사이즈 const [canvasSize, setCanvasSize] = useRecoilState(canvasSizeState) @@ -38,11 +40,14 @@ export default function Roof2() { zoomOut, zoom, togglePolygonLine, + handleOuterlinesTest, handleOuterlinesTest2, applyTemplateB, makeRoofPatternPolygon, } = useMode() + // const [canvasState, setCanvasState] = useRecoilState(canvasAtom) + useEffect(() => { if (!canvas) { return @@ -190,6 +195,18 @@ export default function Roof2() { { x: 740, y: 280 }, { x: 740, y: 130 }, ] + + const eightPoint2 = [ + { x: 197, y: 215 }, + { x: 197, y: 815 }, + { x: 397, y: 815 }, + { x: 397, y: 1115 }, + { x: 697, y: 1115 }, + { x: 697, y: 815 }, + { x: 897, y: 815 }, + { x: 897, y: 215 }, + ] + if (canvas) { const polygon = new QPolygon(eightPoint, { fill: 'transparent', @@ -202,6 +219,8 @@ export default function Roof2() { canvas?.add(polygon) + console.log(polygon) + handleOuterlinesTest2(polygon) // const lines = togglePolygonLine(polygon) @@ -223,16 +242,44 @@ export default function Roof2() { const makeQLine = () => { if (canvas) { const line = new QLine( - [50, 50, 200, 50], + [50, 250, 900, 250], { stroke: 'black', - strokeWidth: 1, + strokeWidth: 5, fontSize: fontSize, + selectable: true, + }, + 50, + ) + + const line2 = new QLine( + [450, 450, 821, 78], + { + stroke: 'black', + strokeWidth: 5, + fontSize: fontSize, + selectable: true, }, 50, ) canvas?.add(line) + canvas?.add(line2) + + const interSectionPoint = calculateIntersection2(line, line2) + + if (interSectionPoint) { + console.log(interSectionPoint) + + const circle = new fabric.Circle({ + radius: 5, + fill: 'red', + left: interSectionPoint.x - 5, + top: interSectionPoint.y - 5, + }) + + canvas?.add(circle) + } } } @@ -258,6 +305,36 @@ export default function Roof2() { const lines = togglePolygonLine(polygon) } + /** + * canvas 내용 저장하기 + */ + const handleSaveCanvas = async () => { + // const jsonStr = JSON.stringify(canvas?.toDatalessJSON(['type', 'fontSize'])) + const jsonObj = JSON.stringify(canvas?.toDatalessJSON(['type', 'fontSize', 'lines'])) + console.log(jsonObj) + + const param = { + loginId: 'test', + canvas: jsonObj, + } + console.log(param) + + await insertCanvasState(param) + handleClear() + } + + /** + * canvas 내용 불러오기 + */ + const handleLoadCanvas = async () => { + const canvasStates = await getCanvasState() + console.log(JSON.parse(canvasStates.canvas)) + canvas?.loadFromJSON(JSON.parse(canvasStates.canvas)) + } + + /** + * 컨트롤러 보이기/숨기기 + */ const handleShowController = () => { setShowControl(!showControl) } @@ -373,6 +450,15 @@ export default function Roof2() { + {/* + */} + diff --git a/src/components/fabric/QLine.js b/src/components/fabric/QLine.js index 7796e66e..98948ac4 100644 --- a/src/components/fabric/QLine.js +++ b/src/components/fabric/QLine.js @@ -1,4 +1,5 @@ import { fabric } from 'fabric' +import { getDirection, getDirectionByPoint } from '@/util/canvas-util' export class QLine extends fabric.Group { line @@ -16,7 +17,11 @@ export class QLine extends fabric.Group { isAlreadyInterSection = false interSectionPoints = [] - #lengthTxt = 0 + lengthTxt = 0 + + initPoints + initOption + initLengthTxt constructor(points, option = { isActiveLengthText: true }, lengthTxt) { // 소수점 전부 제거 @@ -33,38 +38,43 @@ export class QLine extends fabric.Group { const line = new fabric.Line(points, { ...option, strokeWidth: 1 }) super([line], {}) + this.initPoints = points + this.initOption = option + this.initLengthTxt = lengthTxt + this.x1 = x1 this.y1 = y1 this.x2 = x2 this.y2 = y2 this.line = line this.fontSize = option.fontSize - this.direction = option.direction + this.direction = option.direction ?? getDirectionByPoint({ x: x1, y: y1 }, { x: x2, y: y2 }) this.parent = option.parent this.idx = option.idx if (lengthTxt > 0) { - this.#lengthTxt = Number(lengthTxt) + this.lengthTxt = Number(lengthTxt) } - option.isActiveLengthText ?? this.#init() - this.#addControl() + option.isActiveLengthText ?? this.init() + this.addControl() } - #init() { - this.#addLengthText(true) + init() { + this.addLengthText(true) } - #addControl() { + addControl() { this.on('moving', () => { - this.#addLengthText(false) + this.addLengthText(false) }) this.on('modified', (e) => { - this.#addLengthText(false) + this.addLengthText(false) }) this.on('selected', () => { + console.log(this) Object.keys(this.controls).forEach((controlKey) => { if (controlKey !== 'ml' && controlKey !== 'mr') { this.setControlVisible(controlKey, false) @@ -73,19 +83,19 @@ export class QLine extends fabric.Group { }) } - #addLengthText(isFirst) { + addLengthText(isFirst) { if (this.text) { this.removeWithUpdate(this.text) this.text = null } - if (isFirst && this.#lengthTxt > 0) { - const text = new fabric.Textbox(this.#lengthTxt.toFixed(0).toString(), { + if (isFirst && this.lengthTxt > 0) { + const text = new fabric.Textbox(this.lengthTxt.toFixed(0).toString(), { left: (this.x1 + this.x2) / 2, top: (this.y1 + this.y2) / 2, fontSize: this.fontSize, }) - this.length = this.#lengthTxt + this.length = this.lengthTxt this.text = text this.addWithUpdate(text) return @@ -115,4 +125,41 @@ export class QLine extends fabric.Group { this.text.set({ fontSize }) this.addWithUpdate() } + + fromObject(object, callback) { + console.log('fromObject', object, callback) + } + + async = true + + toObject(propertiesToInclude) { + return fabric.util.object.extend(this.callSuper('toObject'), { + length: this.length, + line: this.line, + text: this.text, + fontSize: this.fontSize, + x1: this.x1, + y1: this.y1, + x2: this.x2, + y2: this.y2, + direction: this.direction, + idx: this.idx, + type: this.type, + parent: this.parent, + isAlreadyInterSection: this.isAlreadyInterSection, + interSectionPoints: this.interSectionPoints, + lengthTxt: this.lengthTxt, + setFontSize: this.setFontSize, + addLengthText: this.addLengthText, + init: this.init, + addControl: this.addControl, + initPoints: this.initPoints, + initOption: this.initOption, + initLengthTxt: this.initLengthTxt, + }) + } + + _set(key, value) { + this.callSuper('_set', key, value) + } } diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index 5d7c796e..f5f1ee96 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -12,6 +12,7 @@ import { sortedPoints, } from '@/util/canvas-util' import { QLine } from '@/components/fabric/QLine' +import { drawHelpLineInHexagon2 } from '@/util/qpolygon-utils' export default class QPolygon extends fabric.Group { type = 'QPolygon' @@ -28,8 +29,9 @@ export default class QPolygon extends fabric.Group { helpLines = [] wall - ridges = [] - hips = [] + + initPoints + initOption constructor(points, options, canvas) { /*if (points.length !== 4 && points.length !== 6) { @@ -48,19 +50,23 @@ export default class QPolygon extends fabric.Group { const sortPoints = sortedPoints(points) const polygon = new fabric.Polygon(sortPoints, options) - super([polygon], {}) + super([polygon], { selectable: false }) this.fontSize = options.fontSize this.points = sortPoints this.polygon = polygon this.name = options.name - this.#init() - this.#addEvent() - this.#initLines() + + this.initPoints = points + this.initOption = options + + this.init() + this.addEvent() + this.initLines() this.setShape() } - #initLines() { + initLines() { this.points.forEach((point, i) => { const nextPoint = this.points[(i + 1) % this.points.length] const line = new QLine([point.x, point.y, nextPoint.x, nextPoint.y], { @@ -74,13 +80,13 @@ export default class QPolygon extends fabric.Group { }) } - #init() { - this.#addLengthText() + init() { + this.addLengthText() } - #addEvent() { + addEvent() { this.on('scaling', (e) => { - this.#updateLengthText() + this.updateLengthText() }) this.on('selected', function() { @@ -110,10 +116,10 @@ export default class QPolygon extends fabric.Group { } }) - this.addWithUpdate() + this.canvas.add() } - #addLengthText() { + addLengthText() { if (this.texts.length > 0) { this.texts.forEach((text) => { this.canvas.remove(text) @@ -139,13 +145,13 @@ export default class QPolygon extends fabric.Group { }) this.texts.push(text) - this.addWithUpdate(text) + this.canvas.add(text) }) this.canvas.renderAll() } - #updateLengthText() { + updateLengthText() { const points = this.getCurrentPoints() points.forEach((start, i) => { @@ -191,10 +197,10 @@ export default class QPolygon extends fabric.Group { new fabric.Point(rect.left + rect.width, rect.top + rect.height), ] - const isInside = rectPoints.every((rectPoint) => this.inPolygon(rectPoint) && this.#distanceFromEdge(rectPoint) >= cell.padding) + const isInside = rectPoints.every((rectPoint) => this.inPolygon(rectPoint) && this.distanceFromEdge(rectPoint) >= cell.padding) if (isInside) { - this.addWithUpdate(rect) + this.canvas.add(rect) } } } @@ -248,7 +254,7 @@ export default class QPolygon extends fabric.Group { return intersects % 2 === 1 } - #distanceFromEdge(point) { + distanceFromEdge(point) { const vertices = this.getCurrentPoints() let minDistance = Infinity @@ -375,7 +381,7 @@ export default class QPolygon extends fabric.Group { return this.shape } - #drawHelpLineInRect(chon) { + drawHelpLineInRect(chon) { let type = 1 let smallestLength = Infinity let maxLength = 0 @@ -520,19 +526,18 @@ export default class QPolygon extends fabric.Group { strokeWidth: 1, }) - this.addWithUpdate(realLine1) - this.addWithUpdate(realLine2) - this.addWithUpdate(realLine3) - this.addWithUpdate(realLine4) - this.addWithUpdate(realLine5) - this.addWithUpdate(realLine6) + this.canvas.add(realLine1) + this.canvas.add(realLine2) + this.canvas.add(realLine3) + this.canvas.add(realLine4) + this.canvas.add(realLine5) + this.canvas.add(realLine6) if (smallestLength !== maxLength) { // 정사각형이 아닌경우에만 용마루를 추가한다. this.canvas.add(ridge) } } - - #drawHelpLineInHexagon2(chon) { + drawHelpLineInHexagon2(chon) { const oneSideLines = [...this.lines].map((line) => { let newX1, newY1, newX2, newY2 if (line.direction === 'top') { @@ -748,8 +753,7 @@ export default class QPolygon extends fabric.Group { this.canvas.renderAll() }) } - - #drawHelpLineInHexagon(chon) { + drawHelpLineInHexagon(chon) { const historyLines = [] const helpPoints = [] const notInterSectionLines = [] @@ -762,133 +766,146 @@ export default class QPolygon extends fabric.Group { const y1 = point.y const x2 = wallPoint.x const y2 = wallPoint.y + const historyLines = [] + const helpPoints = [] + const notInterSectionLines = [] + const ridge = [] + const maxLength = this.lines.reduce((max, obj) => Math.max(max, obj.length), 0) + this.points.forEach((point, index) => { + const wallPoint = this.wall.points[index] + // 두 점의 좌표 + const x1 = point.x + const y1 = point.y + const x2 = wallPoint.x + const y2 = wallPoint.y - let newX2, newY2 + let newX2, newY2 - // x1, y1을 기준으로 x2, y2와의 거리를 유지한 새로운 직선 생성 - const angle = Math.atan2(y2 - y1, x2 - x1) + // x1, y1을 기준으로 x2, y2와의 거리를 유지한 새로운 직선 생성 + const angle = Math.atan2(y2 - y1, x2 - x1) - newX2 = x1 + (maxLength / 2) * Math.cos(angle) - newY2 = y1 + (maxLength / 2) * Math.sin(angle) + newX2 = x1 + (maxLength / 2) * Math.cos(angle) + newY2 = y1 + (maxLength / 2) * Math.sin(angle) - const line = new QLine([x1, y1, newX2, newY2], { - fontSize: this.fontSize, - stroke: 'blue', - idx: index, + const line = new QLine([x1, y1, newX2, newY2], { + fontSize: this.fontSize, + stroke: 'blue', + idx: index, + }) + historyLines.push(line) + this.canvas.add(line) + + this.canvas.renderAll() }) - historyLines.push(line) - this.addWithUpdate(line) + + /** + * 삼각 지붕 + */ + historyLines.forEach((line, index) => { + const prevLine = historyLines[(index - 1 + historyLines.length) % historyLines.length] + + let intersectionPoint = calculateIntersection(line, prevLine) + + if (!intersectionPoint) { + notInterSectionLines.push(line) + return + } + + const helpLine1 = new QLine([prevLine.x1, prevLine.y1, intersectionPoint.x, intersectionPoint.y], { + fontSize: this.fontSize, + stroke: 'red', + }) + + const helpLine2 = new QLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], { + fontSize: this.fontSize, + stroke: 'red', + }) + notInterSectionLines.pop() + helpPoints.push(intersectionPoint) + + this.canvas.add(helpLine1) + this.canvas.add(helpLine2) + this.canvas.remove(prevLine) + this.canvas.remove(line) + this.canvas.renderAll() + }) + // 용마루 + + const ridgePoint = [] + + helpPoints.forEach((helpPoint, index) => { + const closestLine = findClosestLineToPoint(helpPoint, notInterSectionLines) + + // 가까운 선의 중심점 + const centerClosestLinePoint = { + x: (closestLine.x1 + closestLine.x2) / 2, + y: (closestLine.y1 + closestLine.y2) / 2, + } + + const direction = getDirectionByPoint(helpPoint, centerClosestLinePoint) + + let newX, newY + + switch (direction) { + case 'left': { + newX = ((closestLine.x2 - closestLine.x1) * (helpPoint.y - closestLine.y1)) / (closestLine.y2 - closestLine.y1) + closestLine.x1 + newY = helpPoint.y + break + } + case 'right': { + newX = ((closestLine.x2 - closestLine.x1) * (helpPoint.y - closestLine.y1)) / (closestLine.y2 - closestLine.y1) + closestLine.x1 + newY = helpPoint.y + break + } + case 'top': { + newX = helpPoint.x + newY = ((closestLine.y2 - closestLine.y1) * (helpPoint.x - closestLine.x1)) / (closestLine.x2 - closestLine.x1) + closestLine.y1 + break + } + case 'bottom': { + newX = helpPoint.x + newY = ((closestLine.y2 - closestLine.y1) * (helpPoint.x - closestLine.x1)) / (closestLine.x2 - closestLine.x1) + closestLine.y1 + break + } + } + + const ridgeHelpLine = new QLine([closestLine.x1, closestLine.y1, newX, newY], { + fontSize: this.fontSize, + stroke: 'purple', + strokeWidth: 5, + }) + + ridgePoint.push({ x: newX, y: newY }) + + const ridge = new QLine([helpPoint.x, helpPoint.y, newX, newY], { + fontSize: this.fontSize, + stroke: 'skyblue', + strokeWidth: 5, + }) + + this.canvas.add(ridge) + this.canvas.renderAll() + + this.canvas.add(ridgeHelpLine) + this.canvas.remove(closestLine) + this.canvas.renderAll() + }) + + // 용마루 끼리 연결 + for (let i = 0; i < ridgePoint.length; i = i + 2) { + const currentRidgeEndPoint = ridgePoint[i] + const nextRidgeEndPoint = ridgePoint[(i + 1) % ridgePoint.length] + const ridgeConnectLine = new QLine([currentRidgeEndPoint.x, currentRidgeEndPoint.y, nextRidgeEndPoint.x, nextRidgeEndPoint.y], { + fontSize: this.fontSize, + stroke: 'green', + strokeWidth: 5, + }) + this.canvas.add(ridgeConnectLine) + this.canvas.renderAll() + } this.canvas.renderAll() }) - - /** - * 삼각 지붕 - */ - historyLines.forEach((line, index) => { - const prevLine = historyLines[(index - 1 + historyLines.length) % historyLines.length] - - let intersectionPoint = calculateIntersection(line, prevLine) - - if (!intersectionPoint) { - notInterSectionLines.push(line) - return - } - - const helpLine1 = new QLine([prevLine.x1, prevLine.y1, intersectionPoint.x, intersectionPoint.y], { - fontSize: this.fontSize, - stroke: 'red', - }) - - const helpLine2 = new QLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], { - fontSize: this.fontSize, - stroke: 'red', - }) - notInterSectionLines.pop() - helpPoints.push(intersectionPoint) - - this.addWithUpdate(helpLine1) - this.addWithUpdate(helpLine2) - this.removeWithUpdate(prevLine) - this.removeWithUpdate(line) - this.canvas.renderAll() - }) - // 용마루 - - const ridgePoint = [] - - helpPoints.forEach((helpPoint, index) => { - const closestLine = findClosestLineToPoint(helpPoint, notInterSectionLines) - - // 가까운 선의 중심점 - const centerClosestLinePoint = { - x: (closestLine.x1 + closestLine.x2) / 2, - y: (closestLine.y1 + closestLine.y2) / 2, - } - - const direction = getDirectionByPoint(helpPoint, centerClosestLinePoint) - - let newX, newY - - switch (direction) { - case 'left': { - newX = ((closestLine.x2 - closestLine.x1) * (helpPoint.y - closestLine.y1)) / (closestLine.y2 - closestLine.y1) + closestLine.x1 - newY = helpPoint.y - break - } - case 'right': { - newX = ((closestLine.x2 - closestLine.x1) * (helpPoint.y - closestLine.y1)) / (closestLine.y2 - closestLine.y1) + closestLine.x1 - newY = helpPoint.y - break - } - case 'top': { - newX = helpPoint.x - newY = ((closestLine.y2 - closestLine.y1) * (helpPoint.x - closestLine.x1)) / (closestLine.x2 - closestLine.x1) + closestLine.y1 - break - } - case 'bottom': { - newX = helpPoint.x - newY = ((closestLine.y2 - closestLine.y1) * (helpPoint.x - closestLine.x1)) / (closestLine.x2 - closestLine.x1) + closestLine.y1 - break - } - } - - const ridgeHelpLine = new QLine([closestLine.x1, closestLine.y1, newX, newY], { - fontSize: this.fontSize, - stroke: 'purple', - strokeWidth: 5, - }) - - ridgePoint.push({ x: newX, y: newY }) - - const ridge = new QLine([helpPoint.x, helpPoint.y, newX, newY], { - fontSize: this.fontSize, - stroke: 'skyblue', - strokeWidth: 5, - }) - - this.addWithUpdate(ridge) - this.canvas.renderAll() - - this.addWithUpdate(ridgeHelpLine) - this.removeWithUpdate(closestLine) - this.canvas.renderAll() - }) - - // 용마루 끼리 연결 - for (let i = 0; i < ridgePoint.length; i = i + 2) { - const currentRidgeEndPoint = ridgePoint[i] - const nextRidgeEndPoint = ridgePoint[(i + 1) % ridgePoint.length] - const ridgeConnectLine = new QLine([currentRidgeEndPoint.x, currentRidgeEndPoint.y, nextRidgeEndPoint.x, nextRidgeEndPoint.y], { - fontSize: this.fontSize, - stroke: 'green', - strokeWidth: 5, - }) - this.addWithUpdate(ridgeConnectLine) - this.canvas.renderAll() - } - - this.canvas.renderAll() } #drawHelpLineInOctagon(chon) { diff --git a/src/hooks/useCanvas.js b/src/hooks/useCanvas.js index 5f44060a..44ed17ce 100644 --- a/src/hooks/useCanvas.js +++ b/src/hooks/useCanvas.js @@ -1,10 +1,6 @@ import { useEffect, useRef, useState } from 'react' import { fabric } from 'fabric' -import { - actionHandler, - anchorWrapper, - polygonPositionHandler, -} from '@/util/canvas-util' +import { actionHandler, anchorWrapper, polygonPositionHandler } from '@/util/canvas-util' import { useRecoilState } from 'recoil' import { canvasSizeState, fontSizeState } from '@/store/canvasAtom' @@ -31,17 +27,6 @@ export function useCanvas(id) { selection: false, }) - // settings for all canvas in the app - fabric.Object.prototype.transparentCorners = false - fabric.Object.prototype.cornerColor = '#2BEBC8' - fabric.Object.prototype.cornerStyle = 'rect' - fabric.Object.prototype.cornerStrokeColor = '#2BEBC8' - fabric.Object.prototype.cornerSize = 6 - - QPolygon.prototype.canvas = c - QLine.prototype.canvas = c - QRect.prototype.canvas = c - setCanvas(c) return () => { c.dispose() @@ -57,24 +42,14 @@ export function useCanvas(id) { useEffect(() => { canvas ?.getObjects() - .filter( - (obj) => - obj.type === 'textbox' || - obj.type === 'text' || - obj.type === 'i-text', - ) + .filter((obj) => obj.type === 'textbox' || obj.type === 'text' || obj.type === 'i-text') .forEach((obj) => { obj.set({ fontSize: fontSize }) }) canvas ?.getObjects() - .filter( - (obj) => - obj.type === 'QLine' || - obj.type === 'QPolygon' || - obj.type === 'QRect', - ) + .filter((obj) => obj.type === 'QLine' || obj.type === 'QPolygon' || obj.type === 'QRect') .forEach((obj) => { obj.setFontSize(fontSize) }) @@ -121,26 +96,43 @@ export function useCanvas(id) { */ const removeMouseLines = () => { if (canvas?._objects.length > 0) { - const mouseLines = canvas?._objects.filter( - (obj) => obj.name === 'mouseLine', - ) + const mouseLines = canvas?._objects.filter((obj) => obj.name === 'mouseLine') mouseLines.forEach((item) => canvas?.remove(item)) } canvas?.renderAll() } - /** - * 눈금 그리기 - */ const initialize = () => { canvas?.clear() + // settings for all canvas in the app + fabric.Object.prototype.transparentCorners = false + fabric.Object.prototype.cornerColor = '#2BEBC8' + fabric.Object.prototype.cornerStyle = 'rect' + fabric.Object.prototype.cornerStrokeColor = '#2BEBC8' + fabric.Object.prototype.cornerSize = 6 - // 기존 이벤트가 있을 경우 제거한다. - // removeEventOnCanvas() + QPolygon.prototype.canvas = canvas + QLine.prototype.canvas = canvas + QRect.prototype.canvas = canvas - // 작업 후에 event를 추가해준다. + fabric.QLine = fabric.util.createClass(fabric.Group, {}) + fabric.QPolygon = fabric.util.createClass(fabric.Group, {}) - // addEventOnCanvas() + // fromObject 메서드를 QLine 클래스에 직접 추가 + fabric.QLine.fromObject = function (object, callback) { + const { initOption, initPoints, initLengthTxt } = object + fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) { + return callback(new QLine(initPoints, initOption, initLengthTxt)) + }) + } + + // fromObject 메서드를 QLine 클래스에 직접 추가 + fabric.QPolygon.fromObject = function (object, callback) { + const { initOption, initPoints, initLengthTxt } = object + fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) { + return callback(new QPolygon(initPoints, initOption, initLengthTxt)) + }) + } } /** @@ -176,26 +168,20 @@ export function useCanvas(id) { } // 가로선을 그립니다. - const horizontalLine = new fabric.Line( - [0, pointer.y, canvasSize.horizontal, pointer.y], - { - stroke: 'black', - strokeWidth: 1, - selectable: false, - name: 'mouseLine', - }, - ) + const horizontalLine = new fabric.Line([0, pointer.y, canvasSize.horizontal, pointer.y], { + stroke: 'black', + strokeWidth: 1, + selectable: false, + name: 'mouseLine', + }) // 세로선을 그립니다. - const verticalLine = new fabric.Line( - [pointer.x, 0, pointer.x, canvasSize.vertical], - { - stroke: 'black', - strokeWidth: 1, - selectable: false, - name: 'mouseLine', - }, - ) + const verticalLine = new fabric.Line([pointer.x, 0, pointer.x, canvasSize.vertical], { + stroke: 'black', + strokeWidth: 1, + selectable: false, + name: 'mouseLine', + }) // 선들을 캔버스에 추가합니다. canvas?.add(horizontalLine, verticalLine) @@ -325,7 +311,6 @@ export function useCanvas(id) { } const jsonStr = JSON.stringify(canvas) localStorage.setItem('canvas', jsonStr) - handleClear() } /** @@ -479,10 +464,7 @@ export function useCanvas(id) { poly.controls = poly.points.reduce(function (acc, point, index) { acc['p' + index] = new fabric.Control({ positionHandler: polygonPositionHandler, - actionHandler: anchorWrapper( - index > 0 ? index - 1 : lastControl, - actionHandler, - ), + actionHandler: anchorWrapper(index > 0 ? index - 1 : lastControl, actionHandler), actionName: 'modifyPolygon', pointIndex: index, }) @@ -571,6 +553,54 @@ export function useCanvas(id) { }) } + const addCanvas = () => { + // const canvasState = canvas + + const objs = canvas + + console.log(objs) + const str = JSON.stringify(objs) + + canvas?.clear() + + console.log(str) + console.log(JSON.parse(str)) + + // 역직렬화하여 캔버스에 객체를 다시 추가합니다. + setTimeout(() => { + canvas?.loadFromJSON( + JSON.parse(str), + function () { + // 모든 객체가 로드되고 캔버스에 추가된 후 호출됩니다. + canvas?.renderAll() // 캔버스를 다시 그립니다. + console.log('Objects are reloaded and rendered on canvas.') + }, + function (o, object) { + // 각 객체가 로드될 때마다 호출됩니다. + console.log('Object loaded: ', o, object) + + canvas?.add(object) + canvas?.renderAll() + }, + ) + }, 1000) + + /*canvas?.loadFromJSON(JSON.parse(str), () => { + console.log('load done') + })*/ + + /*const stateArr = canvasState.map((state) => state.getObject().getObjects()) + + const newCanvasList = [...canvasList, JSON.stringify(stateArr)] + + setCanvasList(newCanvasList)*/ + } + + const changeCanvas = (idx) => { + canvas?.clear() + const canvasState = JSON.parse(canvasList[idx]) + } + return { canvas, addShape, @@ -585,5 +615,7 @@ export function useCanvas(id) { saveImage, handleFlip, setCanvasBackgroundWithDots, + addCanvas, + changeCanvas, } } diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index e7c84a6d..dab596c2 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1,9 +1,10 @@ -import { useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import QRect from '@/components/fabric/QRect' import QPolygon from '@/components/fabric/QPolygon' import { findTopTwoIndexesByDistance, getCenterPoint, getDirection, getStartIndex, rearrangeArray } from '@/util/canvas-util' import { useRecoilState } from 'recoil' -import { fontSizeState, roofState, sortedPolygonArray, roofPolygonPatternArrayState, wallState } from '@/store/canvasAtom' + +import { fontSizeState, roofPolygonPatternArrayState, roofState, sortedPolygonArray, wallState } from '@/store/canvasAtom' import { QLine } from '@/components/fabric/QLine' export const Mode = { @@ -30,8 +31,22 @@ export function useMode() { const [sortedArray, setSortedArray] = useRecoilState(sortedPolygonArray) const [roof, setRoof] = useRecoilState(roofState) const [wall, setWall] = useRecoilState(wallState) + const [roofPolygonPattern, setRoofPolygonPattern] = useRecoilState(roofPolygonPatternArrayState) + useEffect(() => { + // 이벤트 리스너 추가 + if (!canvas) { + return + } + document.addEventListener('keydown', handleKeyDown) + + // 컴포넌트가 언마운트될 때 이벤트 리스너 제거 + return () => { + document.removeEventListener('keydown', handleKeyDown) + } + }, [canvas]) // 빈 배열을 전달하여 컴포넌트가 마운트될 때만 실행되도록 함 + const addEvent = (mode) => { switch (mode) { case 'default': @@ -64,6 +79,147 @@ export function useMode() { } } + const keyValid = () => { + if (points.current.length === 0) { + alert('시작점을 선택해주세요') + return false + } + return true + } + + const drawCircleAndLine = (verticalLength, horizontalLength) => { + const circle = new fabric.Circle({ + radius: 5, + fill: 'transparent', // 원 안을 비웁니다. + stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다. + left: points.current[points.current.length - 1].left + horizontalLength - 5, + top: points.current[points.current.length - 1].top + verticalLength - 5, + originX: 'center', + originY: 'center', + selectable: false, + }) + historyPoints.current.push(circle) + points.current.push(circle) + canvas?.add(circle) + canvas?.renderAll() + + // length 값이 숫자가 아닌 경우 + if (isNaN(length) || Math.max(Math.abs(verticalLength), Math.abs(horizontalLength)) === 0) { + //마지막 추가 된 points 제거합니다. + + const lastPoint = historyPoints.current[historyPoints.current.length - 1] + + canvas?.remove(lastPoint) + + historyPoints.current.pop() + points.current.pop() + return + } + + const line = new QLine( + [points.current[0].left, points.current[0].top, points.current[0].left + horizontalLength, points.current[0].top + verticalLength], + { + stroke: 'black', + strokeWidth: 2, + selectable: false, + viewLengthText: true, + direction: getDirection(points.current[0], points.current[1]), + fontSize: fontSize, + }, + ) + + pushHistoryLine(line) + + // 라인의 끝에 점을 추가합니다. + const endPointCircle = new fabric.Circle({ + radius: 1, + fill: 'transparent', // 원 안을 비웁니다. + stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다. + left: points.current[0].left + horizontalLength, + top: points.current[0].top + verticalLength, + originX: 'center', + originY: 'center', + selectable: false, + }) + + canvas?.add(endPointCircle) + + historyPoints.current.push(endPointCircle) + + points.current.forEach((point) => { + canvas?.remove(point) + }) + points.current = [endPointCircle] + + canvas.renderAll() + } + + const handleKeyDown = (e) => { + switch (e.key) { + case 'ArrowDown': { + if (!keyValid()) { + return + } + const verticalLength = Number(prompt('길이를 입력하세요:')) + const horizontalLength = 0 + + drawCircleAndLine(verticalLength, horizontalLength) + + break + } + case 'ArrowUp': { + if (!keyValid()) { + return + } + const verticalLength = -Number(prompt('길이를 입력하세요:')) + const horizontalLength = 0 + + drawCircleAndLine(verticalLength, horizontalLength) + + break + } + case 'ArrowLeft': { + if (!keyValid()) { + return + } + const verticalLength = 0 + const horizontalLength = -Number(prompt('길이를 입력하세요:')) + + drawCircleAndLine(verticalLength, horizontalLength) + + break + } + case 'ArrowRight': { + if (!keyValid()) { + return + } + + const verticalLength = 0 + const horizontalLength = Number(prompt('길이를 입력하세요:')) + + drawCircleAndLine(verticalLength, horizontalLength) + + break + } + + case 'Enter': { + const result = prompt('입력하세요 (a(A패턴),b(B패턴),t(지붕))') + + switch (result) { + case 'a': + applyTemplateA() + break + case 'b': + applyTemplateB() + break + case 't': + templateMode() + break + } + } + } + } + const changeMode = (canvas, mode) => { setMode(mode) // mode변경 시 이전 이벤트 제거 @@ -720,10 +876,17 @@ export function useMode() { /** *벽 지붕 외곽선 생성 */ - const handleOuterlinesTest = (polygon, offset = 71) => { - var offsetPoints = [] + const handleOuterlinesTest = (offsetInputX, offsetInputY = 0) => { + const polygon = drawWallPolygon() - debugger + let offsetPoints = [] + const originalMax = 71 + const transformedMax = 100 + + offsetInputY = offsetInputY !== 0 ? offsetInputY : offsetInputX + + const offsetX = (offsetInputX / transformedMax) * originalMax * 2 + const offsetY = (offsetInputY / transformedMax) * originalMax * 2 const sortedIndex = getStartIndex(polygon.lines) let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex) @@ -776,17 +939,14 @@ export function useMode() { // 오프셋 적용 var offsetPoint = { - x1: current.x + unitNormal.x * offset, - y1: current.y + unitNormal.y * offset, + x1: current.x + unitNormal.x * offsetX, + y1: current.y + unitNormal.y * offsetY, } offsetPoints.push(offsetPoint) } - const roof = makePolygon(offsetPoints) - setRoof(roof) - - return roof + makePolygon(offsetPoints) } /** @@ -855,6 +1015,7 @@ export function useMode() { const roof = makePolygon(offsetPoints) roof.setWall(polygon) setRoof(roof) + roof.drawHelpLine() } @@ -905,6 +1066,8 @@ export function useMode() { } else if (polygon.lines.length === 6) { //6각형 handleOuterLineTemplateA6Points(polygon) + } else if (polygon.lines.length === 8) { + handleOuterLineTemplateA8Points(polygon) } } @@ -1721,6 +1884,214 @@ export function useMode() { canvas.renderAll() } + const handleOuterLineTemplateA8Points = (polygon, offsetInputX = 50, offsetInputY = 20) => { + let offsetPoints = [] + + const originalMax = 71 + const transformedMax = 100 + + let lines = [] //내각라인 + let outLines = [] //아웃라인 + let halfLength = 0 //선길이 + + const dashedCenterLineOpt = { + stroke: 'black', + strokeWidth: 4, + property: 'centerLine', + strokeDashArray: [5, 5], + fontSize: 14, + } + + const centerLineOpt = { + stroke: 'blue', + strokeWidth: 5, + property: 'bigHoriCenter', + fontSize: 14, + } + + // 폴리곤의 각 변을 선으로 생성 + for (let i = 0; i < polygon.points.length; i++) { + const start = polygon.points[i] + const end = polygon.points[(i + 1) % polygon.points.length] // 다음 점, 마지막 점의 경우 첫 점으로 + + const color = i % 2 === 0 ? '#A0D468' : 'skyblue' + + const line = new QLine([start.x, start.y, end.x, end.y], { + stroke: color, + strokeWidth: 2, + property: 'normal', + fontSize: 14, + }) + + // 선을 배열에 추가 + lines.push(line) + canvas.add(line) + } + + offsetInputY = offsetInputY !== 0 ? offsetInputY : offsetInputX + + const sortedIndex = getStartIndex(polygon.lines) + let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex) + + if (tmpArraySorted[0].direction === 'right') { + //시계방향 + tmpArraySorted = tmpArraySorted.reverse() //그럼 배열을 거꾸로 만들어서 무조건 반시계방향으로 배열 보정 + } + + setSortedArray(tmpArraySorted) //recoil에 넣음 + + const points = tmpArraySorted.map((line) => ({ + x: line.x1, + y: line.y1, + })) + + // 외적을 계산하는 함수 + function crossProduct(p1, p2, p3) { + const dx1 = p2.x - p1.x + const dy1 = p2.y - p1.y + const dx2 = p3.x - p2.x + const dy2 = p3.y - p2.y + return dx1 * dy2 - dy1 * dx2 + } + + let concaveIndices = [] //볼록한 부분 인덱스 배열 + let concavePointIndices = [] //오목한 부분 인덱스 배열 + + // 오목한 부분 찾기 + function findConcavePointIndices(points) { + let concaveIndices = [] + for (let i = 0; i < points.length; i++) { + const p1 = points[i] + const p2 = points[(i + 1) % points.length] + const p3 = points[(i + 2) % points.length] + const cross = crossProduct(p1, p2, p3) + if (cross < 0) { + concaveIndices.push((i + 1) % points.length) + } else { + concavePointIndices.push((i + 1) % points.length) + } + } + return concaveIndices + } + + // 오목한 부분 인덱스 찾기 + concaveIndices = findConcavePointIndices(points) //오목한 부분을 제외한 인덱스 + const concavePoints = concaveIndices.map((index) => points[index]) + + for (var i = 0; i < points.length; i++) { + var prev = points[(i - 1 + points.length) % points.length] + var current = points[i] + var next = points[(i + 1) % points.length] + + // 두 벡터 계산 (prev -> current, current -> next) + var vector1 = { x: current.x - prev.x, y: current.y - prev.y } + var vector2 = { x: next.x - current.x, y: next.y - current.y } + + // 벡터의 길이 계산 + var length1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y) + var length2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y) + + // 벡터를 단위 벡터로 정규화 + var unitVector1 = { x: vector1.x / length1, y: vector1.y / length1 } + var unitVector2 = { x: vector2.x / length2, y: vector2.y / length2 } + + // 법선 벡터 계산 (왼쪽 방향) + var normal1 = { x: -unitVector1.y, y: unitVector1.x } + var normal2 = { x: -unitVector2.y, y: unitVector2.x } + + // 법선 벡터 평균 계산 + var averageNormal = { + x: (normal1.x + normal2.x) / 2, + y: (normal1.y + normal2.y) / 2, + } + + // 평균 법선 벡터를 단위 벡터로 정규화 + var lengthNormal = Math.sqrt(averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y) + var unitNormal = { + x: averageNormal.x / lengthNormal, + y: averageNormal.y / lengthNormal, + } + + let offsetX + let offsetY + + if (concavePointIndices[0] === i || concavePointIndices[1] === i) { + // 인덱스가 배열이랑 같으면 + if ((concavePointIndices[0] === 4 && concavePointIndices[1] === 5) || (concavePointIndices[0] === 2 && concavePointIndices[1] === 3)) { + offsetX = 1 + offsetY = (offsetInputY / transformedMax) * originalMax * 2 + } else { + offsetX = (offsetInputX / transformedMax) * originalMax * 2 + offsetY = 1 + } + } else { + offsetX = (offsetInputX / transformedMax) * originalMax * 2 + offsetY = (offsetInputY / transformedMax) * originalMax * 2 + } + + // 오프셋 적용 + var offsetPoint = { + x1: current.x + unitNormal.x * offsetX, + y1: current.y + unitNormal.y * offsetY, + } + + offsetPoints.push(offsetPoint) + } + + const outlinePolygon = makePolygon(offsetPoints) + + // 아웃라인 폴리곤의 각 변을 선으로 생성 + for (let i = 0; i < outlinePolygon.points.length; i++) { + const start = outlinePolygon.points[i] + const end = outlinePolygon.points[(i + 1) % outlinePolygon.points.length] // 다음 점, 마지막 점의 경우 첫 점으로 + + const line = new QLine([start.x, start.y, end.x, end.y], { + stroke: 'blue', + strokeWidth: 2, + property: 'normal', + fontSize: 14, + }) + + // 선을 배열에 추가 + outLines.push(line) + canvas.add(line) + } + + canvas?.remove(outlinePolygon) //임시 폴리곤을 삭제 + canvas?.remove(outLines[concavePointIndices[0]]) //가운데 제외되는 선을 지운다 + + //라인들을 좌측에서 -> 우측으로 그리는거처럼 데이터 보정 + outLines.forEach((outline, index) => { + let minX, minY, maxX, maxY + if (outline.x2 < outline.x1 || outline.y2 < outline.y1) { + outLines[index].x1 = outline.x2 + outLines[index].y1 = outline.y2 + outLines[index].x2 = outline.x1 + outLines[index].y2 = outline.y1 + outLines[index].line.x1 = minX + outLines[index].line.y1 = minY + outLines[index].line.x2 = maxX + outLines[index].line.y2 = maxY + } + }) + + // for (let i = 0; i < outLines.length; i++) { + // if (!i % 2 === 0) { + // if (i === concavePointIndices[0] - 1 || i === concavePointIndices[1] + 1) { + // //배열 3번이나 5번일때 + // } else { + // } + // } + // } + + let parallelLines = concavePointIndices[0] + 4 //들어간선에 무조건 평행하는 선 찾기 + if (parallelLines > outLines.length) { + parallelLines = outLines[concavePointIndices[0] + 4 - outLines.length - 1] + } + + canvas.renderAll() + } + /** * 템플릿 B 적용 */ @@ -2217,6 +2588,7 @@ export function useMode() { zoomOut, zoom, togglePolygonLine, + handleOuterlinesTest, handleOuterlinesTest2, makeRoofPatternPolygon, } diff --git a/src/lib/canvas.js b/src/lib/canvas.js new file mode 100644 index 00000000..c2dd207f --- /dev/null +++ b/src/lib/canvas.js @@ -0,0 +1,38 @@ +'use server' + +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient() + +export const getTests = () => { + return prisma.test.findMany() +} + +export const insertTest = async (param) => { + return prisma.test.create({ + data: { + content: param, + }, + }) +} + +export const getCanvasStateAll = () => { + return prisma.canvas.findMany() +} + +export const getCanvasState = () => { + return prisma.canvas.findFirst({ + where: { + loginId: 'test', + }, + orderBy: { + id: 'desc', + }, + }) +} + +export const insertCanvasState = (param) => { + return prisma.canvas.create({ + data: param, + }) +} diff --git a/src/lib/prisma.js b/src/lib/prisma.js new file mode 100644 index 00000000..aa946098 --- /dev/null +++ b/src/lib/prisma.js @@ -0,0 +1,14 @@ +import { PrismaClient } from '@prisma/client' + +let prisma + +if (process.env.NODE_ENV === 'production') { + prisma = new PrismaClient() +} else { + if (!global.prisma) { + global.prisma = new PrismaClient() + } + prisma = global.prisma +} + +export default prisma diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index aa9c7315..072f7bab 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -312,21 +312,109 @@ export function calculateIntersection(line1, line2) { if (t >= 0 && t <= 1 && u >= 0 && u <= 1) { const intersectionX = x1_1 + t * (x2_1 - x1_1) const intersectionY = y1_1 + t * (y2_1 - y1_1) - return { x: Math.round(intersectionX), y: Math.round(intersectionY) } + + // Determine the min and max for line1 and line2 for both x and y + const line1MinX = Math.min(line1.x1, line1.x2) + const line1MaxX = Math.max(line1.x1, line1.x2) + const line2MinX = Math.min(line2.x1, line2.x2) + const line2MaxX = Math.max(line2.x1, line2.x2) + + const line1MinY = Math.min(line1.y1, line1.y2) + const line1MaxY = Math.max(line1.y1, line1.y2) + const line2MinY = Math.min(line2.y1, line2.y2) + const line2MaxY = Math.max(line2.y1, line2.y2) + + // 교차점이 선분의 범위 내에 있는지 확인 + if ( + intersectionX >= line1MinX && + intersectionX <= line1MaxX && + intersectionX >= line2MinX && + intersectionX <= line2MaxX && + intersectionY >= line1MinY && + intersectionY <= line1MaxY && + intersectionY >= line2MinY && + intersectionY <= line2MaxY + ) { + return { x: Math.round(intersectionX), y: Math.round(intersectionY) } + } } return null // 교차점이 선분의 범위 내에 없음 } -export const calculateIntersection2 = (line1, line2) => { - const result = intersect([line1.x1, line1.y1], [line1.x2, line1.y2], [line2.x1, line2.y1], [line2.x2, line2.y2]) - return { x: Math.round(result[0]), y: Math.round(result[1]) } +export const findIntersection1 = (line1, line2) => { + const { x1, y1, x2, y2 } = line1 // 첫 번째 선의 두 점 + + const x3 = line2.x1 + const y3 = line2.y1 + const x4 = line2.x2 + const y4 = line2.y2 + + // 선의 방정식의 계수 계산 + const A1 = y2 - y1 + const B1 = x1 - x2 + const C1 = A1 * x1 + B1 * y1 + + const A2 = y4 - y3 + const B2 = x3 - x4 + const C2 = A2 * x3 + B2 * y3 + + const determinant = A1 * B2 - A2 * B1 + + if (determinant === 0) { + // 두 선이 평행하거나 일직선일 경우 + return null + } + + const x = (B1 * C2 - B2 * C1) / determinant + const y = (A1 * C2 - A2 * C1) / determinant + + return { x, y } } -function findOrthogonalPoint(x1, y1, x2, y2, x3, y3, x4, y4) { +export const calculateIntersection2 = (line1, line2) => { + const result = intersect([line1.x1, line1.y1], [line1.x2, line1.y2], [line2.x1, line2.y1], [line2.x2, line2.y2]) + + if (!result) { + return null + } + + // Determine the min and max for line1 and line2 for both x and y + const line1MinX = Math.min(line1.x1, line1.x2) + const line1MaxX = Math.max(line1.x1, line1.x2) + const line2MinX = Math.min(line2.x1, line2.x2) + const line2MaxX = Math.max(line2.x1, line2.x2) + + const line1MinY = Math.min(line1.y1, line1.y2) + const line1MaxY = Math.max(line1.y1, line1.y2) + const line2MinY = Math.min(line2.y1, line2.y2) + const line2MaxY = Math.max(line2.y1, line2.y2) + + // Check if the intersection X and Y are within the range of both lines + if ( + result[0] >= line1MinX && + result[0] <= line1MaxX && + result[0] >= line2MinX && + result[0] <= line2MaxX && + result[1] >= line1MinY && + result[1] <= line1MaxY && + result[1] >= line2MinY && + result[1] <= line2MaxY + ) { + return { x: Math.round(result[0]), y: Math.round(result[1]) } + } else { + return null // Intersection is out of range + } +} + +export function findOrthogonalPoint(line1, line2) { // Calculate the intersection point of two lines - const intersectionX = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)) - const intersectionY = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)) + const intersectionX = + ((line1.x1 * line2.y1 - line1.y1 * line2.x1) * (line2.x2 - line2.x1) - (line1.x1 - line1.x2) * (line2.x2 * line2.y2 - line2.y1 * line2.x1)) / + ((line1.x1 - line1.x2) * (line2.y1 - line2.y2) - (line1.y1 - line1.y2) * (line2.x2 - line2.x1)) + const intersectionY = + ((line1.x1 * line2.y1 - line1.y1 * line2.x1) * (line2.y2 - line2.y1) - (line1.y1 - line1.y2) * (line2.x2 * line2.y2 - line2.y1 * line2.x1)) / + ((line1.x1 - line1.x2) * (line2.y1 - line2.y2) - (line1.y1 - line1.y2) * (line2.x2 - line2.x1)) return { x: intersectionX, y: intersectionY } } @@ -338,7 +426,6 @@ function findOrthogonalPoint(x1, y1, x2, y2, x3, y3, x4, y4) { export const sortedPoints = (points) => { const copyPoints = [...points] //points를 x,y좌표를 기준으로 정렬합니다. - copyPoints.sort((a, b) => { if (a.x === b.x) { return a.y - b.y @@ -354,7 +441,6 @@ export const sortedPoints = (points) => { let index = 1 let currentPoint = { ...copyPoints[0] } copyPoints.splice(0, 1) - while (index < points.length) { if (index === points.length - 1) { resultPoints.push(copyPoints[0]) @@ -364,9 +450,15 @@ export const sortedPoints = (points) => { // 짝수번째는 y값이 같은 점을 찾는다. for (let i = 0; i < copyPoints.length; i++) { // y값이 같은 point가 많은 경우 그 중 x값이 가장 큰걸 찾는다. - const temp = copyPoints.filter((point) => point.y === currentPoint.y) - // temp중 x값이 가장 큰 값 copyPoint보다 큰값 - const min = temp.reduce((prev, current) => (prev.x <= current.x ? prev : current)) + let temp = copyPoints.filter((point) => point.y === currentPoint.y) + if (temp.length === 0) { + // temp가 비어있을 경우 copyPoints에서 가장 가까운 점을 찾는다. + temp = Array.of(findClosestPointByY(currentPoint, copyPoints)) + } + // temp중 x값이 가장 가까운 값 + + const min = temp.reduce((prev, current) => (Math.abs(current.x - currentPoint.x) <= Math.abs(prev.x - currentPoint.x) ? current : prev)) + resultPoints.push(min) currentPoint = min copyPoints.splice(copyPoints.indexOf(min), 1) @@ -377,9 +469,14 @@ export const sortedPoints = (points) => { // 홀수번째는 x값이 같은 점을 찾는다. for (let i = 0; i < copyPoints.length; i++) { // x값이 같은 point가 많은 경우 그 중 y값이 가장 큰걸 찾는다. - const temp = copyPoints.filter((point) => point.x === currentPoint.x) - // temp중 y값이 가장 작은 값 - const min = temp.reduce((prev, current) => (prev.y <= current.y ? prev : current)) + let temp = copyPoints.filter((point) => point.x === currentPoint.x) + if (temp.length === 0) { + // temp가 비어있을 경우 copyPoints에서 가장 가까운 점을 찾는다. + + temp = Array.of(findClosestPointByX(currentPoint, copyPoints)) + } + // temp중 y값이 가장 가까운 값 + const min = temp.reduce((prev, current) => (Math.abs(current.y - currentPoint.y) <= Math.abs(prev.y - currentPoint.y) ? current : prev)) resultPoints.push(min) currentPoint = min @@ -389,7 +486,6 @@ export const sortedPoints = (points) => { } } } - return resultPoints } @@ -430,6 +526,81 @@ export function findClosestLineToPoint(point, lines) { return closestLine } +/** + * x값이 가장 가까운 점 + * @param targetPoint + * @param points + * @returns {*|null} + */ +function findClosestPointByX(targetPoint, points) { + if (points.length === 0) { + return null // Return null if the points array is empty + } + + let closestPoint = points[0] + let smallestDistance = Math.abs(targetPoint.x - points[0].x) + + for (let i = 1; i < points.length; i++) { + const currentDistance = Math.abs(targetPoint.x - points[i].x) + if (currentDistance < smallestDistance) { + smallestDistance = currentDistance + closestPoint = points[i] + } + } + + return closestPoint +} + +/** + * y값이 가장 가까운 점 + * @param targetPoint + * @param points + * @returns {*|null} + */ +function findClosestPointByY(targetPoint, points) { + if (points.length === 0) { + return null // Return null if the points array is empty + } + + let closestPoint = points[0] + let smallestDistance = Math.abs(targetPoint.y - points[0].y) + + for (let i = 1; i < points.length; i++) { + const currentDistance = Math.abs(targetPoint.y - points[i].y) + if (currentDistance < smallestDistance) { + smallestDistance = currentDistance + closestPoint = points[i] + } + } + + return closestPoint +} + +/** + * 주어진 점에서 points 배열 중 가장 가까운 점을 찾는 함수입니다. + * @param {Object} targetPoint - { x: number, y: number } 형태의 대상 점 객체 + * @param {Array} points - { x: number, y: number } 형태의 점 객체들의 배열 + * @returns {Object} targetPoint에 가장 가까운 점 객체 + */ +export function findClosestPoint(targetPoint, points) { + if (points.length === 0) { + return null // points 배열이 비어있으면 null 반환 + } + + let closestPoint = points[0] + let smallestDistance = calculateDistancePoint(targetPoint, closestPoint) + + for (let i = 1; i < points.length; i++) { + const currentDistance = calculateDistancePoint(targetPoint, points[i]) + if (currentDistance < smallestDistance) { + smallestDistance = currentDistance + closestPoint = points[i] + } + } + + return closestPoint +} + /** * 점과 직선사이의 최단 거리 * @param point @@ -448,3 +619,33 @@ export function calculateDistance(point, line) { const denominator = Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)) return numerator / denominator } + +/** + * 두 점 사이의 거리를 계산하는 함수입니다. + * @param {Object} point1 - 첫 번째 점 { x: number, y: number } + * @param {Object} point2 - 두 번째 점 { x: number, y: number } + * @returns {number} 두 점 사이의 거리 + */ +function calculateDistancePoint(point1, point2) { + return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2)) +} + +/** + * {x, y} 형태의 배열을 받아 중복된 점을 제거하는 함수 + * @param points + * @returns {*[]} + */ +export function removeDuplicatePoints(points) { + const uniquePoints = [] + const seen = new Set() + + points.forEach((point) => { + const identifier = `${point.x}:${point.y}` + if (!seen.has(identifier)) { + seen.add(identifier) + uniquePoints.push(point) + } + }) + + return uniquePoints +} diff --git a/src/util/qline-utils.js b/src/util/qline-utils.js new file mode 100644 index 00000000..4cbcb118 --- /dev/null +++ b/src/util/qline-utils.js @@ -0,0 +1,12 @@ +import { fabric } from 'fabric' +import { QLine } from '@/components/fabric/QLine' + +export const defineQLine = () => { + fabric.QLine = fabric.util.createClass(fabric.Group, {}) + fabric.QLine.fromObject = function (object, callback) { + const { initOption, initPoints, initLengthTxt } = object + fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) { + return callback(new QLine(initPoints, initOption, initLengthTxt)) + }) + } +} diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js new file mode 100644 index 00000000..e683e4be --- /dev/null +++ b/src/util/qpolygon-utils.js @@ -0,0 +1,329 @@ +import { fabric } from 'fabric' +import QPolygon from '@/components/fabric/QPolygon' +import { QLine } from '@/components/fabric/QLine' +import { calculateIntersection, calculateIntersection2, distanceBetweenPoints, findIntersection1, removeDuplicatePoints } from '@/util/canvas-util' + +export const defineQPloygon = () => { + fabric.QPolygon = fabric.util.createClass(fabric.Group, {}) + // fromObject 메서드를 QLine 클래스에 직접 추가 + fabric.QPolygon.fromObject = function (object, callback) { + const { initOption, initPoints, initLengthTxt } = object + fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) { + return callback(new QPolygon(initPoints, initOption, initLengthTxt)) + }) + } +} + +export const drawHelpLineInHexagon2 = (polygon, chon) => { + const oneSideLines = [...polygon.lines].map((line) => { + let newX1, newY1, newX2, newY2 + if (line.direction === 'top') { + newX1 = line.x2 + newY1 = line.y2 + newX2 = line.x1 + newY2 = line.y1 + + line.x1 = newX1 + line.y1 = newY1 + line.x2 = newX2 + line.y2 = newY2 + line.direction = 'bottom' + } else if (line.direction === 'left') { + newX1 = line.x2 + newY1 = line.y2 + newX2 = line.x1 + newY2 = line.y1 + + line.x1 = newX1 + line.y1 = newY1 + line.x2 = newX2 + line.y2 = newY2 + line.direction = 'right' + } + return line + }) + + const centerLines = [] + const helpLines = [] + const ridgeStartPoints = [] + + const horizontalLines = oneSideLines.filter((line) => line.direction === 'right') + const verticalLines = oneSideLines.filter((line) => line.direction === 'bottom') + // horizontalLines 를 y1 좌표 기준으로 정렬한다. + horizontalLines.sort((a, b) => a.y1 - b.y1) + // verticalLines 를 x1 좌표 기준으로 정렬한다. + verticalLines.sort((a, b) => a.x1 - b.x1) + + const maxHorizontalLineLength = horizontalLines.reduce((prev, current) => (prev.length > current.length ? prev.length : current.length)) + const maxVerticalLineLength = verticalLines.reduce((prev, current) => (prev.length > current.length ? prev.length : current.length)) + + // 모든 가로선의 중심선을 긋는다. + horizontalLines.forEach((line, index) => { + const nextLine = horizontalLines[(index + 1) % horizontalLines.length] + + line.line.set({ strokeWidth: 5 }) + nextLine.line.set({ strokeWidth: 5 }) + + polygon.canvas.renderAll() + + const startCenterX = Math.min(line.x1, nextLine.x1) + const startCenterY = (line.y1 + nextLine.y1) / 2 + + const endCenterX = line.length >= nextLine.length ? startCenterX + line.length : startCenterX + nextLine.length + const endCenterY = startCenterY + + const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], { + fontSize: polygon.fontSize, + stroke: 'red', + strokeWidth: 1, + direction: 'horizontal', + }) + + /*polygon.canvas.add(centerLine) + polygon.canvas.renderAll()*/ + + centerLines.push(centerLine) + }) + + // 모든 세로선의 중심선을 긋는다. + verticalLines.forEach((line, index) => { + const nextLine = verticalLines[(index + 1) % verticalLines.length] + + const startCenterX = (line.x1 + nextLine.x1) / 2 + const startCenterY = Math.min(line.y1, nextLine.y1) + + const endCenterX = startCenterX + let endCenterY = line.length >= nextLine.length ? startCenterY + line.length : startCenterY + nextLine.length + + const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], { + fontSize: polygon.fontSize, + stroke: 'blue', + strokeWidth: 1, + direction: 'vertical', + }) + + /*polygon.canvas.add(centerLine) + polygon.canvas.renderAll()*/ + centerLines.push(centerLine) + }) + + polygon.points.forEach((point, index) => { + const wallPoint = polygon.wall.points[index] + // 두 점의 좌표 + const x1 = point.x + const y1 = point.y + const x2 = wallPoint.x + const y2 = wallPoint.y + + let newX2, newY2 + + // x1, y1을 기준으로 x2, y2와의 거리를 유지한 새로운 직선 생성 + const angle = Math.atan2(y2 - y1, x2 - x1) + + let previousIndex = index === 0 ? polygon.lines.length - 1 : index - 1 + const maxLength = Math.max(polygon.lines[index].length, polygon.lines[previousIndex].length) + + newX2 = Math.floor(x1 + (maxLength / 2 + polygon.points.length * 10) * Math.cos(angle)) + newY2 = Math.floor(y1 + (maxLength / 2 + polygon.points.length * 10) * Math.sin(angle)) + + const line = new QLine([x1, y1, newX2, newY2], { + fontSize: polygon.fontSize, + stroke: 'green', + idx: index, + }) + line.set({ degree: fabric.util.radiansToDegrees(angle) }) + polygon.canvas.add(line) + helpLines.push(line) + polygon.canvas.renderAll() + }) + + helpLines.forEach((line, index) => { + for (let i = index + 1; i < helpLines.length; i++) { + const nextLine = helpLines[i] + + if (line.isAlreadyInterSection || nextLine.isAlreadyInterSection) { + continue + } + + let intersectionPoint = calculateIntersection(line, nextLine) + + if (!intersectionPoint) { + continue + } + + const circle = new fabric.Circle({ + radius: 3, + fill: 'red', + left: intersectionPoint.x - 3, + top: intersectionPoint.y - 3, + }) + + polygon.canvas.add(circle) + + line.set({ isAlreadyInterSection: true }) + nextLine.set({ isAlreadyInterSection: true }) + + const helpLine1 = new QLine([nextLine.x1, nextLine.y1, intersectionPoint.x, intersectionPoint.y], { + fontSize: polygon.fontSize, + stroke: 'skyblue', + }) + + const helpLine2 = new QLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], { + fontSize: polygon.fontSize, + stroke: 'skyblue', + }) + + ridgeStartPoints.push(intersectionPoint) + polygon.canvas.add(helpLine1) + polygon.canvas.add(helpLine2) + polygon.canvas.remove(nextLine) + polygon.canvas.remove(line) + polygon.canvas.renderAll() + } + }) + + // 안만나는 선들 + const notInterSectionLines = helpLines.filter((line) => !line.isAlreadyInterSection) + const ridgeEndPoints = [] + let interSectionPoints = [] + + notInterSectionLines.forEach((line, index) => { + let subCenterLines + if (Math.abs(line.degree) < 90) { + subCenterLines = centerLines.filter((centerLine) => centerLine.direction === 'vertical') + } else { + subCenterLines = centerLines.filter((centerLine) => centerLine.direction === 'horizontal') + } + + centerLines.forEach((centerLine) => { + const interSectionPoint = calculateIntersection2(line, centerLine) + + if (!interSectionPoint) { + return + } + + ridgeStartPoints.forEach((point) => { + line.interSectionPoints.push(interSectionPoint) + interSectionPoints.push(interSectionPoint) + + const newLine = new QLine([line.x1, line.y1, interSectionPoint.x, interSectionPoint.y], { + stroke: 'black', + fontSize: polygon.fontSize, + }) + + const circle = new fabric.Circle({ + radius: 3, + fill: 'blue', + left: interSectionPoint.x - 3, + top: interSectionPoint.y - 3, + }) + polygon.canvas.add(circle) + polygon.canvas.add(newLine) + + polygon.canvas.remove(line) + + line.set({ isAlreadyInterSection: true }) + }) + }) + }) + + interSectionPoints = removeDuplicatePoints(interSectionPoints) + + const startRidgePoint = ridgeStartPoints[0] + + const endRidgePoint = ridgeStartPoints[ridgeStartPoints.length - 1] + + let step = 0 + + while (true) { + if (step % 2 === 0) { + const nextPoint = interSectionPoints.find((point) => point.x === startRidgePoint.x || point.y === startRidgePoint.y) + + if (nextPoint) { + const ridge = new QLine([startRidgePoint.x, startRidgePoint.y, nextPoint.x, nextPoint.y], { + stroke: 'green', + fontSize: polygon.fontSize, + }) + polygon.canvas.add(ridge) + polygon.canvas.renderAll() + } + + console.log('nextPoint', nextPoint) + console.log('startRidgePoint', startRidgePoint) + } else { + } + + step++ + + break + } + + /*ridgeStartPoints.forEach((point, index) => { + for (let i = index + 1; i < ridgeStartPoints.length; i++) { + const currentPoint = ridgeStartPoints[index] + const nextPoint = ridgeStartPoints[i] + + if (currentPoint.x === nextPoint.x || currentPoint.y === nextPoint.y) { + const ridge = new QLine([currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y], { + stroke: 'black', + fontSize: polygon.fontSize, + }) + polygon.canvas.add(ridge) + polygon.canvas.renderAll() + break + } + } + })*/ + + /*ridgeStartPoints.forEach((point, index) => { + let arrivalPoint + let distance = Infinity + let startPoint + interSectionPoints.forEach((interSectionPoint) => { + if (Math.abs(point.x - interSectionPoint.x) < 1 || Math.abs(point.y - interSectionPoint.y) < 1) { + if (distanceBetweenPoints(point, interSectionPoint) < distance) { + startPoint = point + distance = distanceBetweenPoints(point, interSectionPoint) + arrivalPoint = interSectionPoint + } + } + }) + + if (arrivalPoint) { + const line = notInterSectionLines.filter((line) => line.interSectionPoints.includes(arrivalPoint))[0] + + const ridge = new QLine([startPoint.x, startPoint.y, arrivalPoint.x, arrivalPoint.y], { + stroke: 'black', + fontSize: polygon.fontSize, + }) + + const helpLine = new QLine([line.x1, line.y1, arrivalPoint.x, arrivalPoint.y], { + stroke: 'red', + fontSize: polygon.fontSize, + }) + + ridgeEndPoints.push(arrivalPoint) + polygon.canvas.add(ridge) + polygon.canvas.add(helpLine) + // polygon.canvas.remove(line) + polygon.canvas.renderAll() + debugger + } + })*/ + + /*for (let i = 0; i < ridgeEndPoints.length; i = i + 2) { + const currentRidgeEndPoint = ridgeEndPoints[i] + const nextRidgeEndPoint = ridgeEndPoints[(i + 1) % ridgeEndPoints.length] + const ridgeConnectLine = new QLine([currentRidgeEndPoint.x, currentRidgeEndPoint.y, nextRidgeEndPoint.x, nextRidgeEndPoint.y], { + fontSize: polygon.fontSize, + stroke: 'green', + }) + + polygon.canvas.add(ridgeConnectLine) + polygon.canvas.renderAll() + }*/ +} + +export const drawHelpLineInHexagon = (polygon, chon) => { + // 가장 긴라인을 기준으로 centerLine을 그린다. +} diff --git a/yarn.lock b/yarn.lock index bd27ddfe..94bdd351 100644 --- a/yarn.lock +++ b/yarn.lock @@ -148,6 +148,13 @@ semver "^7.3.5" tar "^6.1.11" +"@mongodb-js/saslprep@^1.1.5": + version "1.1.8" + resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.8.tgz#d39744540be8800d17749990b0da95b4271840d1" + integrity sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ== + dependencies: + sparse-bitfield "^3.0.3" + "@next/env@14.2.3": version "14.2.3" resolved "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz" @@ -1143,6 +1150,47 @@ resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@prisma/client@^5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.17.0.tgz#9079947bd749689c2dabfb9ecc70a24ebefb1f43" + integrity sha512-N2tnyKayT0Zf7mHjwEyE8iG7FwTmXDHFZ1GnNhQp0pJUObsuel4ZZ1XwfuAYkq5mRIiC/Kot0kt0tGCfLJ70Jw== + +"@prisma/debug@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.17.0.tgz#a765105848993984535b6066f8ebc6e6ead26533" + integrity sha512-l7+AteR3P8FXiYyo496zkuoiJ5r9jLQEdUuxIxNCN1ud8rdbH3GTxm+f+dCyaSv9l9WY+29L9czaVRXz9mULfg== + +"@prisma/engines-version@5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053": + version "5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053.tgz#3c7cc1ef3ebc34cbd069e5873b9982f2aabf5acd" + integrity sha512-tUuxZZysZDcrk5oaNOdrBnnkoTtmNQPkzINFDjz7eG6vcs9AVDmA/F6K5Plsb2aQc/l5M2EnFqn3htng9FA4hg== + +"@prisma/engines@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.17.0.tgz#74dd1aabb22675892760b3cf69a448e3aef4616b" + integrity sha512-+r+Nf+JP210Jur+/X8SIPLtz+uW9YA4QO5IXA+KcSOBe/shT47bCcRMTYCbOESw3FFYFTwe7vU6KTWHKPiwvtg== + dependencies: + "@prisma/debug" "5.17.0" + "@prisma/engines-version" "5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053" + "@prisma/fetch-engine" "5.17.0" + "@prisma/get-platform" "5.17.0" + +"@prisma/fetch-engine@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.17.0.tgz#f718dc7426411d1ebeeee53e2d0d38652387f87c" + integrity sha512-ESxiOaHuC488ilLPnrv/tM2KrPhQB5TRris/IeIV4ZvUuKeaicCl4Xj/JCQeG9IlxqOgf1cCg5h5vAzlewN91Q== + dependencies: + "@prisma/debug" "5.17.0" + "@prisma/engines-version" "5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053" + "@prisma/get-platform" "5.17.0" + +"@prisma/get-platform@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.17.0.tgz#89fdcae2adddebbbf0e7bd0474a6c49d6023519b" + integrity sha512-UlDgbRozCP1rfJ5Tlkf3Cnftb6srGrEQ4Nm3og+1Se2gWmCZ0hmPIi+tQikGDUVLlvOWx3Gyi9LzgRP+HTXV9w== + dependencies: + "@prisma/debug" "5.17.0" + "@react-aria/breadcrumbs@3.5.13": version "3.5.13" resolved "https://registry.yarnpkg.com/@react-aria/breadcrumbs/-/breadcrumbs-3.5.13.tgz#2686f7f460f20d67fe5cdfe185e32e3e78186962" @@ -2023,6 +2071,18 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.6.tgz#193ced6a40c8006cfc1ca3f4553444fb38f0e543" integrity sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA== +"@types/webidl-conversions@*": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz#1306dbfa53768bcbcfc95a1c8cde367975581859" + integrity sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA== + +"@types/whatwg-url@^11.0.2": + version "11.0.5" + resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-11.0.5.tgz#aaa2546e60f0c99209ca13360c32c78caf2c409f" + integrity sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ== + dependencies: + "@types/webidl-conversions" "*" + abab@^2.0.5, abab@^2.0.6: version "2.0.6" resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz" @@ -2158,6 +2218,11 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== +bson@^6.7.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/bson/-/bson-6.8.0.tgz#5063c41ba2437c2b8ff851b50d9e36cb7aaa7525" + integrity sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ== + busboy@1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz" @@ -2852,6 +2917,11 @@ mathjs@^13.0.2: tiny-emitter "^2.1.0" typed-function "^4.2.1" +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + merge2@^1.3.0: version "1.4.1" resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" @@ -2926,6 +2996,23 @@ mkdirp@^1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mongodb-connection-string-url@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz#c13e6ac284ae401752ebafdb8cd7f16c6723b141" + integrity sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg== + dependencies: + "@types/whatwg-url" "^11.0.2" + whatwg-url "^13.0.0" + +mongodb@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.8.0.tgz#680450f113cdea6d2d9f7121fe57cd29111fd2ce" + integrity sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw== + dependencies: + "@mongodb-js/saslprep" "^1.1.5" + bson "^6.7.0" + mongodb-connection-string-url "^3.0.0" + ms@2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" @@ -3134,12 +3221,19 @@ postcss@^8, postcss@^8.4.23: picocolors "^1.0.0" source-map-js "^1.2.0" +prisma@^5.17.0: + version "5.17.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.17.0.tgz#267b43921ab94805b010537cffa5ccaf530fa066" + integrity sha512-m4UWkN5lBE6yevqeOxEvmepnL5cNPEjzMw2IqDB59AcEV6w7D8vGljDLd1gPFH+W6gUxw9x7/RmN5dCS/WTPxA== + dependencies: + "@prisma/engines" "5.17.0" + psl@^1.1.33: version "1.9.0" resolved "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== -punycode@^2.1.1: +punycode@^2.1.1, punycode@^2.3.0: version "2.3.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -3378,6 +3472,13 @@ source-map@~0.6.1: resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ== + dependencies: + memory-pager "^1.0.2" + streamsearch@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" @@ -3563,6 +3664,13 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" +tr46@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" + integrity sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw== + dependencies: + punycode "^2.3.0" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -3690,6 +3798,14 @@ whatwg-url@^11.0.0: tr46 "^3.0.0" webidl-conversions "^7.0.0" +whatwg-url@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-13.0.0.tgz#b7b536aca48306394a34e44bda8e99f332410f8f" + integrity sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig== + dependencies: + tr46 "^4.1.1" + webidl-conversions "^7.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"