From f7dffdf486c76167057b6f02c0ac45a09df5b0fd Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Fri, 18 Aug 2023 22:16:22 +0900 Subject: [PATCH 01/27] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20chore:=20remove?= =?UTF-8?q?=20debug=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/http.ts | 1 - utils/whiteboardHelper.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/utils/http.ts b/utils/http.ts index c68c4d1..0bcc35a 100644 --- a/utils/http.ts +++ b/utils/http.ts @@ -38,7 +38,6 @@ export async function httpDelete(url: string, retry: boolean = true) { function createHeaders() { const accessToken = useUser.getState().accessToken; const headers = new Headers(); - headers.set('ngrok-skip-browser-warning', 'fuck you'); if (accessToken.length > 0) headers.set('Authorization', accessToken); return headers; } diff --git a/utils/whiteboardHelper.ts b/utils/whiteboardHelper.ts index 03a53b6..71ef3a2 100644 --- a/utils/whiteboardHelper.ts +++ b/utils/whiteboardHelper.ts @@ -58,7 +58,6 @@ export function genDepth(objA?: Obj, objB?: Obj): number { * @return {number} Generated depth value. */ export function topDepth(): number { - console.log(useWhiteBoard.getState().objTree); const rootObjNode = useWhiteBoard.getState().objTree; if (rootObjNode.childNodes.length === 0) { return 0.0001; From 52b35b936f55294a5e255ebf8324e8f019e6177b Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Fri, 18 Aug 2023 22:57:48 +0900 Subject: [PATCH 02/27] =?UTF-8?q?=E2=9C=A8=20feat:=20CircleObj?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/WhiteBoard/CircleRenderer.tsx | 23 ++++++++++ components/WhiteBoard/MouseHandler.tsx | 46 +++++++++++++++++++ components/WhiteBoard/NodeRenderer.tsx | 3 ++ .../WhiteBoard/ObjectPropertyEditor/index.tsx | 27 +++++++++++ components/WhiteBoard/ToolSelector/index.tsx | 1 + components/WhiteBoard/TreeViewer/index.tsx | 2 + global.d.ts | 13 +++++- 7 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 components/WhiteBoard/CircleRenderer.tsx diff --git a/components/WhiteBoard/CircleRenderer.tsx b/components/WhiteBoard/CircleRenderer.tsx new file mode 100644 index 0000000..822f21e --- /dev/null +++ b/components/WhiteBoard/CircleRenderer.tsx @@ -0,0 +1,23 @@ +'use client'; +import { MutableRefObject } from 'react'; +import { useWhiteBoard } from '../../states/whiteboard'; +import { Circle } from '@react-three/drei'; + +interface CircleRendererProps { + objId: string; + dimensionsRef: MutableRefObject; +} +export default function CircleRenderer({ objId, dimensionsRef }: CircleRendererProps) { + const obj = useWhiteBoard((state) => state.objMap.get(objId))! as CircleObj; + + dimensionsRef.current.x = obj.x; + dimensionsRef.current.y = obj.y; + dimensionsRef.current.w = obj.r * 2; + dimensionsRef.current.h = obj.r * 2; + + return ( + + + + ); +} diff --git a/components/WhiteBoard/MouseHandler.tsx b/components/WhiteBoard/MouseHandler.tsx index 378ebc1..524a520 100644 --- a/components/WhiteBoard/MouseHandler.tsx +++ b/components/WhiteBoard/MouseHandler.tsx @@ -5,6 +5,7 @@ import { SELECT_DEPTH, SELECT_HIGHLIGHT, boundNumber, + createCircle, createLine, createRect, createText, @@ -153,6 +154,7 @@ export default function MouseHandler() { switch (currentTool) { case 'SELECT': case 'RECT': + case 'CIRCLE': case 'TEXT': if (drag || !selection) return null; return ( @@ -248,6 +250,7 @@ const usePointerUp = ( break; case 'RECT': case 'LINE': + case 'CIRCLE': case 'TEXT': if (e.button == 0) { setUpPos(newPos); @@ -328,6 +331,7 @@ const usePointerDown = ( case 'RECT': case 'LINE': case 'TEXT': + case 'CIRCLE': setDraw(false); e.stopPropagation(); // fall through case 'SELECT': // selection box on left click @@ -460,6 +464,8 @@ const useDraw = ( return createLine(downPos.x, downPos.y, upPos.x, upPos.y); case 'TEXT': return createText(newObjPos.x, newObjPos.y, newObjSize.x); + case 'CIRCLE': + return createCircle(newObjPos.x, newObjPos.y, Math.min(newObjSize.x, newObjSize.y) / 2); } return null; })(); @@ -504,6 +510,8 @@ function handleDrag(obj: Obj, newPos: Coord, drag: DragData, updateObj: (obj: Ob break; case 'LINE': return handleLineDrag(obj as LineObj, newPos, drag, updateObj); + case 'CIRCLE': + return handleCircleDrag(obj as CircleObj, newPos, drag, updateObj); } } @@ -545,6 +553,44 @@ function handleRectDrag( } } +function handleCircleDrag( + obj: CircleObj, + newPos: Coord, + drag: DragData, + updateObj: (obj: Obj) => void, +) { + switch (drag.mode) { + case 'move': + return updateObj({ + ...obj, + x: validateValue(newPos.x + drag.prevObj.x - drag.mousePos.x), + y: validateValue(newPos.y + drag.prevObj.y - drag.mousePos.y), + } as Obj); + case 'n': + return updateObj({ + ...obj, + r: validateValue((newPos.y - drag.prevObj.y) / 2, true), + } as Obj); + case 'e': + return updateObj({ + ...obj, + r: validateValue((newPos.x - drag.prevObj.x) / 2, true), + } as Obj); + case 'w': + return updateObj({ + ...obj, + r: validateValue(drag.prevObj.x + (drag.prevObj as CircleObj).r * 2 - newPos.x, true) / 2, + x: validateValue(newPos.x), + } as Obj); + case 's': + return updateObj({ + ...obj, + r: validateValue(drag.prevObj.y + (drag.prevObj as CircleObj).r * 2 - newPos.y, true) / 2, + y: validateValue(newPos.y), + } as Obj); + } +} + function handleTextDrag( obj: TextObj, newPos: Coord, diff --git a/components/WhiteBoard/NodeRenderer.tsx b/components/WhiteBoard/NodeRenderer.tsx index 9d31f01..7433afc 100644 --- a/components/WhiteBoard/NodeRenderer.tsx +++ b/components/WhiteBoard/NodeRenderer.tsx @@ -7,6 +7,7 @@ import { getPos } from '@/utils/whiteboardHelper'; import SelectionRenderer from './SelectionRenderer'; import { MutableRefObject, memo, useRef } from 'react'; import LineRenderer from './LineRenderer'; +import CircleRenderer from './CircleRenderer'; interface NodeRendererProps { node: ObjNode; @@ -39,6 +40,8 @@ function RenderWrapper({ objId, type, dimensionsRef }: RenderWrapperProps) { return ; case 'LINE': return ; + case 'CIRCLE': + return ; } } diff --git a/components/WhiteBoard/ObjectPropertyEditor/index.tsx b/components/WhiteBoard/ObjectPropertyEditor/index.tsx index 41d7f8e..5e25010 100644 --- a/components/WhiteBoard/ObjectPropertyEditor/index.tsx +++ b/components/WhiteBoard/ObjectPropertyEditor/index.tsx @@ -45,6 +45,12 @@ export default function ObjectPropertyEditor({ targetObjId }: ObjectPropertyEdit panels.push( , ); + break; + case 'CIRCLE': + panels.push( + , + ); + break; case 'ROOT': } @@ -212,6 +218,27 @@ function LinePanel({ obj, onChange }: LinePanelProps) { ); } +interface CirclePanelProps { + obj: CircleObj; + onChange: (key: string, val: any) => void; +} + +function CirclePanel({ obj, onChange }: CirclePanelProps) { + return ( +
+ + +
+ ); +} + interface PropertyProps { propKey: string; label?: string; diff --git a/components/WhiteBoard/ToolSelector/index.tsx b/components/WhiteBoard/ToolSelector/index.tsx index 43ee312..9aa41df 100644 --- a/components/WhiteBoard/ToolSelector/index.tsx +++ b/components/WhiteBoard/ToolSelector/index.tsx @@ -11,6 +11,7 @@ export default function ToolSelector() { + ); } diff --git a/components/WhiteBoard/TreeViewer/index.tsx b/components/WhiteBoard/TreeViewer/index.tsx index a245044..2cd8c3f 100644 --- a/components/WhiteBoard/TreeViewer/index.tsx +++ b/components/WhiteBoard/TreeViewer/index.tsx @@ -85,5 +85,7 @@ function typeToIcon(type: ObjType) { return 'dashboard'; case 'LINE': return 'linear_scale'; + case 'CIRCLE': + return 'circle'; } } diff --git a/global.d.ts b/global.d.ts index 6221475..7ae82da 100644 --- a/global.d.ts +++ b/global.d.ts @@ -4,7 +4,7 @@ interface ObjNode { depth: number; } -type ObjType = 'RECT' | 'TEXT' | 'ROOT' | 'LINE'; +type ObjType = 'RECT' | 'TEXT' | 'ROOT' | 'LINE' | 'CIRCLE'; interface Obj { objId: string; @@ -22,6 +22,15 @@ interface ObjDimensions { h: number; } +interface CircleObj extends Obj { + r: number; + color: string; +} + +interface CircleOptions { + color?: string; +} + interface RectObj extends Obj { w: number; h: number; @@ -64,7 +73,7 @@ interface Coord { y: number; } -type Tool = 'HAND' | 'SELECT' | 'RECT' | 'TEXT' | 'LINE' | 'BUNDLE'; +type Tool = 'HAND' | 'SELECT' | 'RECT' | 'TEXT' | 'LINE' | 'BUNDLE' | 'CIRCLE'; interface WBDocumentMetadata { documentId: number; From 27dd6eaa299a0ad4e7582a333f86543c6540a3ff Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Sat, 19 Aug 2023 00:26:05 +0900 Subject: [PATCH 03/27] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20graph=20->=20tr?= =?UTF-8?q?ee=20and=20preview=20renderer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mindmap response 저장해 뒀다가 preview시 렌더러 선택 가능 preview는 글로벌 맵에 요소 복사했다가 종료할때 다시 삭제해줌 --- .../components/GenerateDialogue.tsx | 184 +++++++++++++++--- components/WhiteBoard/MouseHandler.tsx | 8 +- states/whiteboard.ts | 38 ++-- utils/whiteboardHelper.ts | 122 ++++++++++-- 4 files changed, 296 insertions(+), 56 deletions(-) diff --git a/app/document/[documentId]/components/GenerateDialogue.tsx b/app/document/[documentId]/components/GenerateDialogue.tsx index 1d7e424..a392c8f 100644 --- a/app/document/[documentId]/components/GenerateDialogue.tsx +++ b/app/document/[documentId]/components/GenerateDialogue.tsx @@ -2,27 +2,43 @@ import Dialogue from '@/components/Dialogue'; import TextInput from '@/components/Dialogue/Input/TextInput'; +import MouseHandler from '@/components/WhiteBoard/MouseHandler'; +import NodeRenderer from '@/components/WhiteBoard/NodeRenderer'; import { useToast } from '@/states/toast'; import { useWhiteBoard } from '@/states/whiteboard'; import { generateGraph } from '@/utils/api'; -import { createForceBundleFromMindmap } from '@/utils/whiteboardHelper'; -import { useState } from 'react'; +import { + createForceBundleFromMindmap, + createTreeFromMindmap, + objNodeSorter, +} from '@/utils/whiteboardHelper'; +import { Canvas } from '@react-three/fiber'; +import { Suspense, useEffect, useState } from 'react'; interface GenerateDialogueProps { onCancel: () => void; } +type ScreenState = 'load' | 'preview' | 'input'; +type PreviewMode = 'tree' | 'force'; + export default function GenerateDialouge({ onCancel }: GenerateDialogueProps) { const [text, setText] = useState(''); - const [load, setLoad] = useState(false); - const { setCurrentTool, setBundle } = useWhiteBoard((state) => ({ + const [state, setState] = useState('input'); + const [rawGraphData, setRawGraphData] = useState(null); + const [previewMode, setPreviewMode] = useState('tree'); + const [previewBundle, setPreviewBundle] = useState(null); + const [previewTree, setPreviewTree] = useState(null); + const { setCurrentTool, setBundle, addObj, removeObj } = useWhiteBoard((state) => ({ setCurrentTool: state.setCurrentTool, setBundle: state.setBundle, + addObj: state.addObj, + removeObj: state.removeObj, })); const pushToast = useToast((state) => state.pushToast); - const onSubmit = () => { - setLoad(true); + const onGenerate = () => { + setState('load'); // request generation (async () => { const res = await generateGraph({ text }); @@ -32,43 +48,155 @@ export default function GenerateDialouge({ onCancel }: GenerateDialogueProps) { duraton: 3000, msg: '작업을 실패했습니다.', }); - return setLoad(false); + return setState('input'); } - // generate bundle - const bundle = createForceBundleFromMindmap(res); + setRawGraphData(res); + setState('preview'); + })(); + }; - setBundle(bundle); - setCurrentTool('BUNDLE'); + useEffect(() => { + // generate bundle + if (rawGraphData === null) return; + setPreviewBundle(() => { + switch (previewMode) { + case 'tree': + return createTreeFromMindmap(rawGraphData); + case 'force': + return createForceBundleFromMindmap(rawGraphData); + } + }); + }, [previewMode, rawGraphData]); + + useEffect(() => { + if (previewBundle === null) return; + // prepare preview + const children: ObjNode[] = []; + for (const obj of previewBundle.objs) { + addObj(obj, false); + children.push({ + objId: obj.objId, + childNodes: [], + depth: obj.depth, + }); + } + setPreviewTree({ objId: 'ROOT', depth: 0, childNodes: children.sort(objNodeSorter) }); + return () => { + // cleanup + for (const obj of previewBundle.objs) { + removeObj(obj, false); + setPreviewTree(null); + } + }; + }, [addObj, previewBundle, removeObj]); + const onSubmit = () => { + // set bundle and exit + if (previewBundle === null) return; + setState('load'); + (async () => { + setBundle(previewBundle); + setCurrentTool('BUNDLE'); pushToast({ id: new Date().getTime(), duraton: 5000, msg: '그래프 생성이 완료되었습니다!\n클릭해서 원하는 위치에 붙여넣어 주세요', }); + cleanUp(); onCancel(); })(); }; + const cleanUp = () => { + if (previewBundle === null) return; + // remove previewBundle objs from objmap + for (const obj of previewBundle.objs) { + removeObj(obj); + } + }; + return (
- - {load ? ( -
-

잠시만 기다려 주세요

-
-
- ) : ( - setText(e.target.value)} - label={'생성에 사용될 글'} - multiline={true} - /> - )} -
+ {(() => { + switch (state) { + case 'load': + return ( + +
+

잠시만 기다려 주세요

+
+
+
+ ); + case 'preview': + if (previewTree === null) return null; + return ( + { + cleanUp(); + onCancel(); + }} + onSubmit={onSubmit} + enabled={true} + > +

렌더러

+ + + + +
+ ); + case 'input': + return ( + + setText(e.target.value)} + label={'생성에 사용될 글'} + multiline={true} + /> + + ); + } + })()}
); } + +function PreviewRenderer({ root }: { root: ObjNode }) { + return ( + + + + + + ); +} diff --git a/components/WhiteBoard/MouseHandler.tsx b/components/WhiteBoard/MouseHandler.tsx index 524a520..6569977 100644 --- a/components/WhiteBoard/MouseHandler.tsx +++ b/components/WhiteBoard/MouseHandler.tsx @@ -23,7 +23,11 @@ const PAN_MAX_DELTA = 100; const MAX_ZOOM = 10000; const MIN_ZOOM = 0.1; -export default function MouseHandler() { +interface MouseHandlerProps { + forceTool?: Tool; +} + +export default function MouseHandler({ forceTool }: MouseHandlerProps) { const { invalidate, mouse, @@ -45,7 +49,7 @@ export default function MouseHandler() { setDrag, } = useWhiteBoard((state) => ({ bundle: state.bundle, - currentTool: state.currentTool, + currentTool: forceTool ?? state.currentTool, setCurrentTool: state.setCurrentTool, objMap: state.objMap, addObj: state.addObj, diff --git a/states/whiteboard.ts b/states/whiteboard.ts index 5ebc653..2c2d3f5 100644 --- a/states/whiteboard.ts +++ b/states/whiteboard.ts @@ -12,13 +12,14 @@ interface WhiteBoardState { interface WhiteBoardActions { addBundle: (offset: Coord) => void; - addObj: (obj: Obj) => void; + addObj: (obj: Obj, global?: boolean) => void; updateObj: (obj: Obj) => void; setCurrentTool: (tool: Tool) => void; setCurrentObj: (obj: string | null) => void; setDrag: (drag: DragData | null) => void; loadDocument: (document: WBDocument) => void; setBundle: (bundle: ObjBundle) => void; + removeObj: (obj: Obj, global?: boolean) => void; resetWhiteBoard: () => void; } @@ -38,18 +39,23 @@ const initialState = { export const useWhiteBoard = create()((set, get) => ({ ...initialState, - addObj: (obj: Obj) => + addObj: (obj: Obj, global: boolean = true) => { set((state) => ({ objMap: state.objMap.set(obj.objId, obj), - objTree: { - ...state.objTree, - childNodes: [ - { objId: obj.objId, childNodes: [], depth: obj.depth }, - ...state.objTree.childNodes, - ], - }, - currentObj: obj.objId, - })), + })); + if (global) { + set((state) => ({ + objTree: { + ...state.objTree, + childNodes: [ + { objId: obj.objId, childNodes: [], depth: obj.depth }, + ...state.objTree.childNodes, + ], + }, + currentObj: obj.objId, + })); + } + }, updateObj: (obj: Obj) => set((state) => ({ objMap: state.objMap.set(obj.objId, obj) })), setCurrentTool: (tool: Tool) => set(() => ({ currentTool: tool })), setCurrentObj: (obj: string | null) => set(() => ({ currentObj: obj })), @@ -78,4 +84,14 @@ export const useWhiteBoard = create()((set, bundle: null, })); }, + removeObj: (obj: Obj, global: boolean = true) => { + set((state) => { + const map = new Map(state.objMap); + map.delete(obj.objId); + return { objMap: map }; + }); + if (global) { + // TODO: remove from objTree + } + }, })); diff --git a/utils/whiteboardHelper.ts b/utils/whiteboardHelper.ts index 71ef3a2..8805c86 100644 --- a/utils/whiteboardHelper.ts +++ b/utils/whiteboardHelper.ts @@ -4,8 +4,9 @@ import { forceCollide, forceLink, forceManyBody, - forceRadial, forceSimulation, + hierarchy, + tree, } from 'd3'; import { Camera, Vector2, Vector3 } from 'three'; @@ -143,6 +144,25 @@ export function createText( } as TextObj); } +export function createCircle( + x: number, + y: number, + r: number, + depth: number = genDepth(), + options?: CircleOptions, +): CircleObj { + return validateCircleObj({ + objId: genId(), + type: 'CIRCLE', + x: x, + y: y, + r: r, + depth: depth, + parentId: 'ROOT', + color: options?.color ?? genColor(), + } as CircleObj); +} + /** * Create Line Object based on input parameter * @@ -295,6 +315,13 @@ function validateLineObj(obj: LineObj): LineObj { return obj; } +function validateCircleObj(obj: CircleObj): CircleObj { + obj.x = validateValue(obj.x); + obj.y = validateValue(obj.y); + obj.r = validateValue(obj.r); + return obj; +} + /** * validates input text obj * @@ -335,11 +362,76 @@ interface LinkData { target: number; } +export function createTreeFromMindmap(response: MindmapResponse): ObjBundle { + let top = topDepth(); + const fontSize = 30; + const edgeColor = '#dddddd'; + const fontColor = '#000000'; + const nodeColor = '#000000'; + const unit = 0.0001; + const result: Obj[] = []; + const bounds = { minX: NaN, maxX: NaN, minY: NaN, maxY: NaN }; + + const updateBounds = (obj: Obj) => { + if (isNaN(bounds.minX)) { + bounds.minX = obj.x; + bounds.maxX = obj.x; + bounds.minY = obj.y; + bounds.maxY = obj.y; + return; + } + bounds.minX = Math.min(bounds.minX, obj.x); + bounds.maxX = Math.max(bounds.maxX, obj.x); + bounds.minY = Math.min(bounds.minY, obj.y); + bounds.maxY = Math.max(bounds.maxY, obj.y); + }; + + const appendObj = (obj: Obj) => { + updateBounds(obj); + result.push(obj); + top += unit; + }; + + const graph = new Map(Object.entries(response.graph)); + const root = hierarchy(response.root as unknown, (d) => { + return graph.get((d as number).toString()); + }); + + const pointRadius = 5; + + const layout = tree().nodeSize([60, 300]); + const data = layout(root); + + data.links().map((v) => { + appendObj( + createLine(v.source.y, v.source.x, v.target.y, v.target.x, top, { color: edgeColor }), + ); + }); + data.descendants().map((v) => { + appendObj( + createText(v.y - 50, v.x + pointRadius * 2 + 5, 100, top, { + color: fontColor, + fontSize: fontSize, + text: response.keywords[v.data as number], + textAlign: 'center', + }), + ); + appendObj( + createCircle(v.y - pointRadius, v.x - pointRadius, pointRadius, top, { color: nodeColor }), + ); + }); + return { + x: validateValue((bounds.maxX + bounds.minX) / 2), + y: validateValue((bounds.maxY + bounds.minY) / 2), + w: bounds.maxX - bounds.minX, + h: bounds.maxY - bounds.minY, + objs: result, + }; +} + export function createForceBundleFromMindmap(response: MindmapResponse): ObjBundle { const fontSize = 20; - const nodeWidth = fontSize * 8; - const nodeHeight = fontSize * 2; - const radius = nodeWidth / 2 + 10; + const radius = 50; const edgeWidth = 2; const edgeColor = '#31493C'; const fontColor = '#FFFFFF'; @@ -395,13 +487,11 @@ export function createForceBundleFromMindmap(response: MindmapResponse): ObjBund 'link', forceLink(links) .id((d) => d.id) - .strength(2) - .distance(10), + .strength(2), ) .force('charge', forceManyBody().strength(-100)) .force('center', forceCenter(0, 0).strength(1)) - .force('collision', forceCollide(radius)) - .force('radial', forceRadial(0, 0, 100)) + .force('collision', forceCollide(radius * 2)) .tick(300); for (const link of links) { @@ -417,21 +507,22 @@ export function createForceBundleFromMindmap(response: MindmapResponse): ObjBund for (const node of nodes) { appendObj( - createRect( - (node.x ?? 0) - nodeWidth / 2, - (node.y ?? 0) - nodeHeight / 2, - nodeWidth, - nodeHeight, - nodeColor, + createCircle( + (node.x ?? 0) - radius, + (node.y ?? 0) - radius, + radius, + top, + { color: nodeColor }, ), ); appendObj( - createText((node.x ?? 0) - nodeWidth / 2, (node.y ?? 0) - fontSize / 2, nodeWidth, top, { + createText((node.x ?? 0) - radius, (node.y ?? 0) - fontSize / 2, radius * 2, top, { textAlign: 'center', fontSize: fontSize, text: node.label, color: fontColor, + overflow: 'break-word', }), ); } @@ -546,6 +637,7 @@ export function translateObj(obj: Obj, offset: Coord): Obj { (obj as LineObj).y2 = validateValue((obj as LineObj).y2 + offset.y); case 'RECT': case 'TEXT': + case 'CIRCLE': obj.x = validateValue(obj.x + offset.x); obj.y = validateValue(obj.y + offset.y); } From 504aa181e17c64e8dbf34514f04089eee0231710 Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Wed, 6 Sep 2023 13:30:26 +0900 Subject: [PATCH 04/27] =?UTF-8?q?=E2=9C=A8=20feat:=20renderer=20refactor,?= =?UTF-8?q?=20create=20graph=20renderer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/WhiteBoard/CircleObjRenderer.tsx | 20 ++++++++++ components/WhiteBoard/CircleRenderer.tsx | 23 ----------- components/WhiteBoard/GraphObjRenderer.tsx | 7 ++++ .../{LineRenderer.tsx => LineObjRenderer.tsx} | 24 +++++------ components/WhiteBoard/MouseHandler.tsx | 2 +- components/WhiteBoard/NodeRenderer.tsx | 40 +++++++++++-------- .../WhiteBoard/RectangleObjRenderer.tsx | 21 ++++++++++ components/WhiteBoard/RectangleRenderer.tsx | 24 ----------- components/WhiteBoard/SelectionRenderer.tsx | 2 +- .../{TextRenderer.tsx => TextObjRenderer.tsx} | 21 +++++----- global.d.ts | 11 ++++- 11 files changed, 103 insertions(+), 92 deletions(-) create mode 100644 components/WhiteBoard/CircleObjRenderer.tsx delete mode 100644 components/WhiteBoard/CircleRenderer.tsx create mode 100644 components/WhiteBoard/GraphObjRenderer.tsx rename components/WhiteBoard/{LineRenderer.tsx => LineObjRenderer.tsx} (74%) create mode 100644 components/WhiteBoard/RectangleObjRenderer.tsx delete mode 100644 components/WhiteBoard/RectangleRenderer.tsx rename components/WhiteBoard/{TextRenderer.tsx => TextObjRenderer.tsx} (73%) diff --git a/components/WhiteBoard/CircleObjRenderer.tsx b/components/WhiteBoard/CircleObjRenderer.tsx new file mode 100644 index 0000000..6344779 --- /dev/null +++ b/components/WhiteBoard/CircleObjRenderer.tsx @@ -0,0 +1,20 @@ +'use client'; +import { Circle } from '@react-three/drei'; +import { ObjRendererProps } from './NodeRenderer'; + +export default function CircleObjRenderer({ rawObj, dimensionsRef }: ObjRendererProps) { + const obj = rawObj as CircleObj; + + if (dimensionsRef !== undefined) { + dimensionsRef.current.x = obj.x; + dimensionsRef.current.y = obj.y; + dimensionsRef.current.w = obj.r * 2; + dimensionsRef.current.h = obj.r * 2; + } + + return ( + + + + ); +} diff --git a/components/WhiteBoard/CircleRenderer.tsx b/components/WhiteBoard/CircleRenderer.tsx deleted file mode 100644 index 822f21e..0000000 --- a/components/WhiteBoard/CircleRenderer.tsx +++ /dev/null @@ -1,23 +0,0 @@ -'use client'; -import { MutableRefObject } from 'react'; -import { useWhiteBoard } from '../../states/whiteboard'; -import { Circle } from '@react-three/drei'; - -interface CircleRendererProps { - objId: string; - dimensionsRef: MutableRefObject; -} -export default function CircleRenderer({ objId, dimensionsRef }: CircleRendererProps) { - const obj = useWhiteBoard((state) => state.objMap.get(objId))! as CircleObj; - - dimensionsRef.current.x = obj.x; - dimensionsRef.current.y = obj.y; - dimensionsRef.current.w = obj.r * 2; - dimensionsRef.current.h = obj.r * 2; - - return ( - - - - ); -} diff --git a/components/WhiteBoard/GraphObjRenderer.tsx b/components/WhiteBoard/GraphObjRenderer.tsx new file mode 100644 index 0000000..209aa4d --- /dev/null +++ b/components/WhiteBoard/GraphObjRenderer.tsx @@ -0,0 +1,7 @@ +import LineObjRenderer from './LineObjRenderer'; +import { ObjRendererProps } from './NodeRenderer'; + +export default function GraphObjRenderer({ rawObj, dimensionsRef }: ObjRendererProps) { + const obj = rawObj as GraphObj; + return <>; +} diff --git a/components/WhiteBoard/LineRenderer.tsx b/components/WhiteBoard/LineObjRenderer.tsx similarity index 74% rename from components/WhiteBoard/LineRenderer.tsx rename to components/WhiteBoard/LineObjRenderer.tsx index 7adb192..0e08461 100644 --- a/components/WhiteBoard/LineRenderer.tsx +++ b/components/WhiteBoard/LineObjRenderer.tsx @@ -1,21 +1,17 @@ 'use client'; import { ThreeEvent } from '@react-three/fiber'; -import { MutableRefObject, forwardRef, useImperativeHandle, useMemo, useRef } from 'react'; -import { useWhiteBoard } from '../../states/whiteboard'; +import { forwardRef, useImperativeHandle, useMemo, useRef } from 'react'; import { Euler, Mesh } from 'three'; +import { ObjRendererProps } from './NodeRenderer'; -interface LineRendererProps { - objId: string; - dimensionsRef: MutableRefObject; -} -export default function LineRenderer({ objId, dimensionsRef }: LineRendererProps) { - const obj = useWhiteBoard((state) => state.objMap.get(objId))! as LineObj; - - dimensionsRef.current.x = Math.min(obj.x, obj.x2); - dimensionsRef.current.y = Math.min(obj.y, obj.y2); - dimensionsRef.current.w = Math.abs(obj.x - obj.x2); - dimensionsRef.current.h = Math.abs(obj.y - obj.y2); - +export default function LineObjRenderer({ rawObj, dimensionsRef }: ObjRendererProps) { + const obj = rawObj as LineObj; + if (dimensionsRef !== undefined) { + dimensionsRef.current.x = Math.min(obj.x, obj.x2); + dimensionsRef.current.y = Math.min(obj.y, obj.y2); + dimensionsRef.current.w = Math.abs(obj.x - obj.x2); + dimensionsRef.current.h = Math.abs(obj.y - obj.y2); + } return ( ; +} interface NodeRendererProps { node: ObjNode; @@ -27,27 +33,28 @@ export default function NodeRenderer({ node }: NodeRendererProps) { } interface RenderWrapperProps { - objId: string; - type: ObjType; + obj: Obj; dimensionsRef: MutableRefObject; } -function RenderWrapper({ objId, type, dimensionsRef }: RenderWrapperProps) { - switch (type) { +function RenderWrapper({ obj, dimensionsRef }: RenderWrapperProps) { + switch (obj.type) { case 'RECT': - return ; + return ; case 'TEXT': - return ; + return ; case 'LINE': - return ; + return ; case 'CIRCLE': - return ; + return ; + case 'GRAPH': + return ; } } const MemoizedRenderWrapper = memo( RenderWrapper, - (prev, next) => prev.objId === next.objId && prev.dimensionsRef === next.dimensionsRef, + (prev, next) => prev.obj.objId === next.obj.objId && prev.dimensionsRef === next.dimensionsRef, ); interface ObjectWrapperProps { @@ -64,14 +71,15 @@ function ObjectWrapper({ objId }: ObjectWrapperProps) { currentTool: state.currentTool, })); const { mouse, camera } = useThree(); - const selection = currentObj === obj.objId; + const selection = currentObj === objId; + return ( { // 첫번째 클릭만 여기서 처리, selection 이후는 selectionRenderer에서 처리 e.stopPropagation(); if (e.button === 0) { - setCurrentObj(obj.objId); + setCurrentObj(objId); if (selection) return; if (currentTool === 'SELECT') { const mousePos = getPos(mouse, camera); @@ -84,7 +92,7 @@ function ObjectWrapper({ objId }: ObjectWrapperProps) { } }} > - + {selection ? : <>} ); diff --git a/components/WhiteBoard/RectangleObjRenderer.tsx b/components/WhiteBoard/RectangleObjRenderer.tsx new file mode 100644 index 0000000..6fd9b38 --- /dev/null +++ b/components/WhiteBoard/RectangleObjRenderer.tsx @@ -0,0 +1,21 @@ +'use client'; +import { ObjRendererProps } from './NodeRenderer'; + +// renders given rectangle object, sets selection onClick +export default function RectangleObjRenderer({ rawObj, dimensionsRef }: ObjRendererProps) { + const obj = rawObj as RectObj; + + if (dimensionsRef !== undefined) { + dimensionsRef.current.x = obj.x; + dimensionsRef.current.y = obj.y; + dimensionsRef.current.w = obj.w; + dimensionsRef.current.h = obj.h; + } + + return ( + + + + + ); +} diff --git a/components/WhiteBoard/RectangleRenderer.tsx b/components/WhiteBoard/RectangleRenderer.tsx deleted file mode 100644 index 957b214..0000000 --- a/components/WhiteBoard/RectangleRenderer.tsx +++ /dev/null @@ -1,24 +0,0 @@ -'use client'; - -import { MutableRefObject } from 'react'; -import { useWhiteBoard } from '../../states/whiteboard'; - -interface Props { - objId: string; - dimensionsRef: MutableRefObject; -} - -// renders given rectangle object, sets selection onClick -export default function RectangleRenderer({ objId, dimensionsRef }: Props) { - const obj = useWhiteBoard((state) => state.objMap.get(objId))! as RectObj; - dimensionsRef.current.x = obj.x; - dimensionsRef.current.y = obj.y; - dimensionsRef.current.w = obj.w; - dimensionsRef.current.h = obj.h; - return ( - - - - - ); -} diff --git a/components/WhiteBoard/SelectionRenderer.tsx b/components/WhiteBoard/SelectionRenderer.tsx index d27b26a..5f5fbe8 100644 --- a/components/WhiteBoard/SelectionRenderer.tsx +++ b/components/WhiteBoard/SelectionRenderer.tsx @@ -11,7 +11,7 @@ import { import { Circle } from '@react-three/drei'; import { ThreeEvent, useFrame, useThree } from '@react-three/fiber'; import { MutableRefObject, forwardRef, useImperativeHandle, useRef } from 'react'; -import { FlatLine, FlatLineRef } from './LineRenderer'; +import { FlatLine, FlatLineRef } from './LineObjRenderer'; import { BufferGeometry, Mesh, NormalBufferAttributes } from 'three'; interface SelectionRendererProps { diff --git a/components/WhiteBoard/TextRenderer.tsx b/components/WhiteBoard/TextObjRenderer.tsx similarity index 73% rename from components/WhiteBoard/TextRenderer.tsx rename to components/WhiteBoard/TextObjRenderer.tsx index e1e8140..264723d 100644 --- a/components/WhiteBoard/TextRenderer.tsx +++ b/components/WhiteBoard/TextObjRenderer.tsx @@ -1,21 +1,17 @@ 'use client'; import { Text } from '@react-three/drei'; -import { MutableRefObject } from 'react'; -import { useWhiteBoard } from '../../states/whiteboard'; import { useThree } from '@react-three/fiber'; +import { ObjRendererProps } from './NodeRenderer'; -interface TextViewProps { - objId: string; - dimensionsRef: MutableRefObject; -} - -export default function TextRenderer({ objId, dimensionsRef }: TextViewProps) { - const obj = useWhiteBoard((state) => state.objMap.get(objId))! as TextObj; +export default function TextObjRenderer({ rawObj, dimensionsRef }: ObjRendererProps) { + const obj = rawObj as TextObj; const { invalidate } = useThree(); - dimensionsRef.current.w = obj.w; - dimensionsRef.current.x = obj.x; - dimensionsRef.current.y = obj.y; + if (dimensionsRef !== undefined) { + dimensionsRef.current.w = obj.w; + dimensionsRef.current.x = obj.x; + dimensionsRef.current.y = obj.y; + } const position = calculatePosition(obj); @@ -34,6 +30,7 @@ export default function TextRenderer({ objId, dimensionsRef }: TextViewProps) { onAfterRender={(_renderer, _scene, _camera, geometry) => { if (geometry.boundingBox === null) return; const newHeight = geometry.boundingBox.max.y - geometry.boundingBox.min.y; + if (dimensionsRef === undefined) return; if (dimensionsRef.current.h !== newHeight) { dimensionsRef.current.h = newHeight; invalidate(); diff --git a/global.d.ts b/global.d.ts index 7ae82da..4b0f0dd 100644 --- a/global.d.ts +++ b/global.d.ts @@ -4,7 +4,7 @@ interface ObjNode { depth: number; } -type ObjType = 'RECT' | 'TEXT' | 'ROOT' | 'LINE' | 'CIRCLE'; +type ObjType = 'RECT' | 'TEXT' | 'ROOT' | 'LINE' | 'CIRCLE' | 'GRAPH'; interface Obj { objId: string; @@ -22,7 +22,13 @@ interface ObjDimensions { h: number; } +interface GraphObj extends Obj { + type: 'GRAPH'; + data: MindmapResponse; +} + interface CircleObj extends Obj { + type: 'CIRCLE'; r: number; color: string; } @@ -32,6 +38,7 @@ interface CircleOptions { } interface RectObj extends Obj { + type: 'RECT'; w: number; h: number; color: string; @@ -40,6 +47,7 @@ interface RectObj extends Obj { type OverflowType = 'normal' | 'break-word'; type TextAlgin = 'center' | 'left' | 'right'; interface TextObj extends Obj { + type: 'TEXT'; w: number; fontSize: number; overflow: OverflowType; @@ -57,6 +65,7 @@ interface TextOptions { } interface LineObj extends Obj { + type: 'LINE'; x2: number; y2: number; color: string; From 5c44509fed8917c2c2bdf573436ff3ae07e5b1dc Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Mon, 25 Sep 2023 15:19:42 +0900 Subject: [PATCH 05/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20default?= =?UTF-8?q?=20font=20variable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/WhiteBoard/TextObjRenderer.tsx | 2 +- utils/whiteboardHelper.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/components/WhiteBoard/TextObjRenderer.tsx b/components/WhiteBoard/TextObjRenderer.tsx index 264723d..2358a4e 100644 --- a/components/WhiteBoard/TextObjRenderer.tsx +++ b/components/WhiteBoard/TextObjRenderer.tsx @@ -17,7 +17,7 @@ export default function TextObjRenderer({ rawObj, dimensionsRef }: ObjRendererPr return ( Date: Mon, 25 Sep 2023 15:20:34 +0900 Subject: [PATCH 06/27] =?UTF-8?q?=E2=9E=95=20chore:=20add=20dependencies?= =?UTF-8?q?=20for=20graph=20generation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + yarn.lock | 210 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 158 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 3b86252..2048132 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@types/react-dom": "18.2.7", "@types/three": "^0.154.0", "d3": "^7.8.5", + "d3-force-3d": "^3.0.5", "eslint": "8.44.0", "eslint-config-next": "13.4.9", "material-symbols": "^0.10.1", @@ -24,6 +25,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "three": "^0.154.0", + "three-forcegraph": "^1.41.10", "typescript": "5.1.6", "zustand": "^4.4.0" }, diff --git a/yarn.lock b/yarn.lock index f5bfc28..edff4bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -292,28 +292,28 @@ integrity sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ== "@types/d3-array@*": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.5.tgz#857c1afffd3f51319bbc5b301956aca68acaa7b8" - integrity sha512-Qk7fpJ6qFp+26VeQ47WY0mkwXaiq8+76RJcncDEfMc2ocRzXLO67bLFRNI4OX1aGBoPzsM5Y2T+/m1pldOgD+A== + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.7.tgz#b128a0c0b0d9481d3281df47de0955730db384a1" + integrity sha512-4/Q0FckQ8TBjsB0VdGFemJOG8BLXUB2KKlL0VmZ+eOYeOnTb/wDRQqYWpBmQ6IlvWkXwkYiot+n9Px2aTJ7zGQ== "@types/d3-axis@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.2.tgz#96e11d51256baf5bdb2fa73a17d302993e79df07" - integrity sha512-uGC7DBh0TZrU/LY43Fd8Qr+2ja1FKmH07q2FoZFHo1eYl8aj87GhfVoY1saJVJiq24rp1+wpI6BvQJMKgQm8oA== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.3.tgz#e9ca5d1dd7b1da4023ab0f9e921c3f6e86b8c06d" + integrity sha512-SE3x/pLO/+GIHH17mvs1uUVPkZ3bHquGzvZpPAh4yadRy71J93MJBpgK/xY8l9gT28yTN1g9v3HfGSFeBMmwZw== dependencies: "@types/d3-selection" "*" "@types/d3-brush@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.2.tgz#a610aad5a1e76c375be63e11c5eee1ed9fd2fb40" - integrity sha512-2TEm8KzUG3N7z0TrSKPmbxByBx54M+S9lHoP2J55QuLU0VSQ9mE96EJSAOVNEqd1bbynMjeTS9VHmz8/bSw8rA== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.3.tgz#c5de3fd8efad6d85507fa74992540060aba38c25" + integrity sha512-MQ1/M/B5ifTScHSe5koNkhxn2mhUPqXjGuKjjVYckplAPjP9t2I2sZafb/YVHDwhoXWZoSav+Q726eIbN3qprA== dependencies: "@types/d3-selection" "*" "@types/d3-chord@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.2.tgz#cf6f05ad2d8faaad524e9e6f454b4fd06b200930" - integrity sha512-abT/iLHD3sGZwqMTX1TYCMEulr+wBd0SzyOQnjYNLp7sngdOHYtNkMRI5v3w5thoN+BWtlHVDx2Osvq6fxhZWw== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.3.tgz#cd1dc38ac7cb390fe06abc09d30ddf0cd2ff350a" + integrity sha512-keuSRwO02c7PBV3JMWuctIfdeJrVFI7RpzouehvBWL4/GGUB3PBNg/9ZKPZAgJphzmS2v2+7vr7BGDQw1CAulw== "@types/d3-color@*": version "3.1.0" @@ -321,9 +321,9 @@ integrity sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA== "@types/d3-contour@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.2.tgz#d8a0e4d12ec14f7d2bb6e59f3fbc1a527457d0b2" - integrity sha512-k6/bGDoAGJZnZWaKzeB+9glgXCYGvh6YlluxzBREiVo8f/X2vpTEdgPy9DN7Z2i42PZOZ4JDhVdlTSTSkLDPlQ== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.3.tgz#16255aeb85557488bdf84c0a7988c428c0d2939b" + integrity sha512-x7G/tdDZt4m09XZnG2SutbIuQqmkNYqR9uhDMdPlpJbcwepkEjEWG29euFcgVA1k6cn92CHdDL9Z+fOnxnbVQw== dependencies: "@types/d3-array" "*" "@types/geojson" "*" @@ -334,21 +334,21 @@ integrity sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ== "@types/d3-dispatch@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.2.tgz#b2fa80bab3bcead68680766e966f59cd6cb9a69f" - integrity sha512-rxN6sHUXEZYCKV05MEh4z4WpPSqIw+aP7n9ZN6WYAAvZoEAghEK1WeVZMZcHRBwyaKflU43PCUAJNjFxCzPDjg== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.3.tgz#5f6a4e9bbf90e8f775083154c3d7205cfb804762" + integrity sha512-Df7KW3Re7G6cIpIhQtqHin8yUxUHYAqiE41ffopbmU5+FifYUNV7RVyTg8rQdkEagg83m14QtS8InvNb95Zqug== "@types/d3-drag@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.2.tgz#5562da3e7b33d782c2c1f9e65c5e91bb01ee82cf" - integrity sha512-qmODKEDvyKWVHcWWCOVcuVcOwikLVsyc4q4EBJMREsoQnR2Qoc2cZQUyFUPgO9q4S3qdSqJKBsuefv+h0Qy+tw== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.3.tgz#829a58420d8818be65a005795068964ff80a387b" + integrity sha512-82AuQMpBQjuXeIX4tjCYfWjpm3g7aGCfx6dFlxX2JlRaiME/QWcHzBsINl7gbHCODA2anPYlL31/Trj/UnjK9A== dependencies: "@types/d3-selection" "*" "@types/d3-dsv@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.1.tgz#c51a3505cee42653454b74a00f8713dc3548c362" - integrity sha512-76pBHCMTvPLt44wFOieouXcGXWOF0AJCceUvaFkxSZEu4VDUdv93JfpMa6VGNFs01FHfuP4a5Ou68eRG1KBfTw== + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.2.tgz#0504c17388714e28a601dcaaf400147271ea14c2" + integrity sha512-DooW5AOkj4AGmseVvbwHvwM/Ltu0Ks0WrhG6r5FG9riHT5oUUTHz6xHsHqJSVU8ZmPkOqlUEY2obS5C9oCIi2g== "@types/d3-ease@*": version "3.0.0" @@ -356,16 +356,16 @@ integrity sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA== "@types/d3-fetch@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.2.tgz#fe1f335243e07c9bd520c9a71756fed8330c54b1" - integrity sha512-gllwYWozWfbep16N9fByNBDTkJW/SyhH6SGRlXloR7WdtAaBui4plTP+gbUgiEot7vGw/ZZop1yDZlgXXSuzjA== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.3.tgz#ae55cc49bd71b448182deff0cc4b448eff1f9b33" + integrity sha512-/EsDKRiQkby3Z/8/AiZq8bsuLDo/tYHnNIZkUpSeEHWV7fHUl6QFBjvMPbhkKGk9jZutzfOkGygCV7eR/MkcXA== dependencies: "@types/d3-dsv" "*" "@types/d3-force@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.4.tgz#2d50bd2b695f709797e1745644f6bc123e6e5f5a" - integrity sha512-q7xbVLrWcXvSBBEoadowIUJ7sRpS1yvgMWnzHJggFy5cUZBq2HZL5k/pBSm0GdYWS1vs5/EDwMjSKF55PDY4Aw== + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.5.tgz#835bbe01e084195175ddf71b7900bd44a9a23e8e" + integrity sha512-EGG+IWx93ESSXBwfh/5uPuR9Hp8M6o6qEGU7bBQslxCvrdUBQZha/EFpu/VMdLU4B0y4Oe4h175nSm7p9uqFug== "@types/d3-format@*": version "3.0.1" @@ -373,16 +373,16 @@ integrity sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg== "@types/d3-geo@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.0.3.tgz#535e5f24be13722964c52354301be09b752f5d6e" - integrity sha512-bK9uZJS3vuDCNeeXQ4z3u0E7OeJZXjUgzFdSOtNtMCJCLvDtWDwfpRVWlyt3y8EvRzI0ccOu9xlMVirawolSCw== + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.0.4.tgz#fa38f02256c3023ea3b88cb011cff1426eb7ff52" + integrity sha512-kmUK8rVVIBPKJ1/v36bk2aSgwRj2N/ZkjDT+FkMT5pgedZoPlyhaG62J+9EgNIgUXE6IIL0b7bkLxCzhE6U4VQ== dependencies: "@types/geojson" "*" "@types/d3-hierarchy@*": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b3a446b5437faededb30ac32b7cc0486559ab1e2" - integrity sha512-9hjRTVoZjRFR6xo8igAJyNXQyPX6Aq++Nhb5ebrUF414dv4jr2MitM2fWiOY475wa3Za7TOS2Gh9fmqEhLTt0A== + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.3.tgz#9e935540e2494f3402938bf53811ed74ca3c36ba" + integrity sha512-GpSK308Xj+HeLvogfEc7QsCOcIxkDwLhFYnOoohosEzOqv7/agxwvJER1v/kTC+CY1nfazR0F7gnHo7GE41/fw== "@types/d3-interpolate@*": version "3.0.1" @@ -417,21 +417,21 @@ integrity sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw== "@types/d3-scale@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.3.tgz#7a5780e934e52b6f63ad9c24b105e33dd58102b5" - integrity sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ== + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.4.tgz#3c5e2263eea5a3670cd91043b9f4d150a94c43f1" + integrity sha512-eq1ZeTj0yr72L8MQk6N6heP603ubnywSDRfNpi5enouR112HzGLS6RIvExCzZTraFF4HdzNpJMwA/zGiMoHUUw== dependencies: "@types/d3-time" "*" "@types/d3-selection@*": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.5.tgz#27cd53b7672d405025e2414d98532d7934c16ebd" - integrity sha512-xCB0z3Hi8eFIqyja3vW8iV01+OHGYR2di/+e+AiOcXIOrY82lcvWW8Ke1DYE/EUVMsBl4Db9RppSBS3X1U6J0w== + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.6.tgz#c35b5320188e921d10f77f50198705a14b8aecf6" + integrity sha512-2ACr96USZVjXR9KMD9IWi1Epo4rSDKnUtYn6q2SPhYxykvXTw9vR77lkFNruXVg4i1tzQtBxeDMx0oNvJWbF1w== "@types/d3-shape@*": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.1.tgz#15cc497751dac31192d7aef4e67a8d2c62354b95" - integrity sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A== + version "3.1.2" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.2.tgz#a3d421d8b0bc0c6c67cb3f4b4471ddc133cb0117" + integrity sha512-NN4CXr3qeOUNyK5WasVUV8NCSAx/CRVcwcb0BuuS1PiTqwIm6ABi1SyasLZ/vsVCFDArF+W4QiGzSry1eKYQ7w== dependencies: "@types/d3-path" "*" @@ -451,16 +451,16 @@ integrity sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g== "@types/d3-transition@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.3.tgz#d4ac37d08703fb039c87f92851a598ba77400402" - integrity sha512-/S90Od8Id1wgQNvIA8iFv9jRhCiZcGhPd2qX0bKF/PS+y0W5CrXKgIiELd2CvG1mlQrWK/qlYh3VxicqG1ZvgA== + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.4.tgz#1515cd38bdc6d84103d7b6ccb25acdb72b5dd095" + integrity sha512-512a4uCOjUzsebydItSXsHrPeQblCVk8IKjqCUmrlvBWkkVh3donTTxmURDo1YPwIVDh5YVwCAO6gR4sgimCPQ== dependencies: "@types/d3-selection" "*" "@types/d3-zoom@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.3.tgz#5c29006a61ff7ca512fe21398c66ad95dd846674" - integrity sha512-OWk1yYIIWcZ07+igN6BeoG6rqhnJ/pYe+R1qWFM2DtW49zsoSjgb9G5xB0ZXA8hh2jAzey1XuRmMSoXdKw8MDA== + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.4.tgz#1c754cf9f3ac96c59e6d9372c4d49f09e3e6fce3" + integrity sha512-cqkuY1ah9ZQre2POqjSLcM8g40UVya/qwEUrNYP2/rCVljbmqKCVcv+ebvwhlI5azIbSEL7m+os6n+WlYA43aA== dependencies: "@types/d3-interpolate" "*" "@types/d3-selection" "*" @@ -644,6 +644,11 @@ dependencies: "@use-gesture/core" "10.2.27" +accessor-fn@1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/accessor-fn/-/accessor-fn-1.5.0.tgz#9353e10194da404366657f47177cd9bcb4463ee7" + integrity sha512-dml7D96DY/K5lt4Ra2jMnpL9Bhw5HEGws4p1OAIxFFj9Utd/RxNfEO3T3f0QIWFNwQU7gNxH9snUfqF/zNkP/w== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -902,7 +907,7 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== -"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: +"d3-array@1 - 3", "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: version "3.2.4" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== @@ -914,6 +919,11 @@ d3-axis@3: resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== +d3-binarytree@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/d3-binarytree/-/d3-binarytree-1.0.2.tgz#ed43ebc13c70fbabfdd62df17480bc5a425753cc" + integrity sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw== + d3-brush@3: version "3.0.0" resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" @@ -985,6 +995,17 @@ d3-fetch@3: dependencies: d3-dsv "1 - 3" +"d3-force-3d@2 - 3", d3-force-3d@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/d3-force-3d/-/d3-force-3d-3.0.5.tgz#9c8931b49acc3554f9110e128bc580cd3ab830f2" + integrity sha512-tdwhAhoTYZY/a6eo9nR7HP3xSW/C6XvJTbeRpR92nlPzH6OiE+4MliN9feuSFd0tPtEUo+191qOhCTWx3NYifg== + dependencies: + d3-binarytree "1" + d3-dispatch "1 - 3" + d3-octree "1" + d3-quadtree "1 - 3" + d3-timer "1 - 3" + d3-force@3: version "3.0.0" resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" @@ -1018,6 +1039,11 @@ d3-hierarchy@3: dependencies: d3-color "1 - 3" +d3-octree@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/d3-octree/-/d3-octree-1.0.2.tgz#b39026b82701e45c7163e34ee056dc492035a017" + integrity sha512-Qxg4oirJrNXauiuC94uKMbgxwnhdda9xRLl9ihq45srlJ4Ga3CSgqGcAL8iW7N5CIv4Oz8x3E734ulxyvHPvwA== + "d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" @@ -1038,7 +1064,7 @@ d3-random@3: resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== -d3-scale-chromatic@3: +"d3-scale-chromatic@1 - 3", d3-scale-chromatic@3: version "3.0.0" resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a" integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g== @@ -1046,7 +1072,7 @@ d3-scale-chromatic@3: d3-color "1 - 3" d3-interpolate "1 - 3" -d3-scale@4: +"d3-scale@1 - 4", d3-scale@4: version "4.0.2" resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== @@ -1151,6 +1177,13 @@ damerau-levenshtein@^1.0.8: resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== +data-joint@1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/data-joint/-/data-joint-1.3.1.tgz#d134950322c90f531e81bbbe8454277549031466" + integrity sha512-tMK0m4OVGqiA3zkn8JmO6YAqD8UwJqIAx4AAwFl1SKTtKAqcXePuT+n2aayiX9uITtlN3DFtKKTOxJRUc2+HvQ== + dependencies: + index-array-by "^1.4.0" + debounce@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" @@ -1890,6 +1923,11 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== +index-array-by@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/index-array-by/-/index-array-by-1.4.1.tgz#425f26cf0c744a47ebadf47366692e52043cf17b" + integrity sha512-Zu6THdrxQdyTuT2uA5FjUoBEsFHPzHcPIj18FszN6yXKHxSfGcR4TPLabfuT//E25q1Igyx9xta2WMvD/x9P/g== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -2126,6 +2164,13 @@ json5@^1.0.2: object.assign "^4.1.4" object.values "^1.1.6" +kapsule@1: + version "1.14.4" + resolved "https://registry.yarnpkg.com/kapsule/-/kapsule-1.14.4.tgz#55f19fe7a04dfe111e9efc7d85a895e6440ef828" + integrity sha512-Ro1US5B5mtyZMM+NqW/0fqcBf9oEO7fG0gYY9FY+BVGo4KaonVsplFfuYx3pZ/GLCQfYE5cONduILLktsYjUpQ== + dependencies: + lodash-es "4" + ktx-parse@^0.4.5: version "0.4.5" resolved "https://registry.yarnpkg.com/ktx-parse/-/ktx-parse-0.4.5.tgz#79905e22281a9d3e602b2ff522df1ee7d1813aa6" @@ -2163,6 +2208,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash-es@4: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash.clamp@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/lodash.clamp/-/lodash.clamp-4.0.3.tgz#5c24bedeeeef0753560dc2b4cb4671f90a6ddfaa" @@ -2311,6 +2361,37 @@ next@^13.4.12: "@next/swc-win32-ia32-msvc" "13.4.12" "@next/swc-win32-x64-msvc" "13.4.12" +ngraph.events@^1.0.0, ngraph.events@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ngraph.events/-/ngraph.events-1.2.2.tgz#3ceb92d676a04a4e7ce60a09fa8e17a4f0346d7f" + integrity sha512-JsUbEOzANskax+WSYiAPETemLWYXmixuPAlmZmhIbIj6FH/WDgEGCGnRwUQBK0GjOnVm8Ui+e5IJ+5VZ4e32eQ== + +ngraph.forcelayout@3: + version "3.3.1" + resolved "https://registry.yarnpkg.com/ngraph.forcelayout/-/ngraph.forcelayout-3.3.1.tgz#981e1baee5e0593c490bc27219169f9cedfa4f8b" + integrity sha512-MKBuEh1wujyQHFTW57y5vd/uuEOK0XfXYxm3lC7kktjJLRdt/KEKEknyOlc6tjXflqBKEuYBBcu7Ax5VY+S6aw== + dependencies: + ngraph.events "^1.0.0" + ngraph.merge "^1.0.0" + ngraph.random "^1.0.0" + +ngraph.graph@20: + version "20.0.1" + resolved "https://registry.yarnpkg.com/ngraph.graph/-/ngraph.graph-20.0.1.tgz#579470d1d805583239704dc913e2095540aaf371" + integrity sha512-VFsQ+EMkT+7lcJO1QP8Ik3w64WbHJl27Q53EO9hiFU9CRyxJ8HfcXtfWz/U8okuoYKDctbciL6pX3vG5dt1rYA== + dependencies: + ngraph.events "^1.2.1" + +ngraph.merge@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ngraph.merge/-/ngraph.merge-1.0.0.tgz#d763cdfa48b1bbd4270ea246f06c9c8ff5d3477c" + integrity sha512-5J8YjGITUJeapsomtTALYsw7rFveYkM+lBj3QiYZ79EymQcuri65Nw3knQtFxQBU1r5iOaVRXrSwMENUPK62Vg== + +ngraph.random@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ngraph.random/-/ngraph.random-1.1.0.tgz#5345c4bb63865c85d98ee6f13eab1395d8545a90" + integrity sha512-h25UdUN/g8U7y29TzQtRm/GvGr70lK37yQPvPKXXuVfs7gCm82WipYFZcksQfeKumtOemAzBIcT7lzzyK/edLw== + npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -2876,6 +2957,22 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +three-forcegraph@^1.41.10: + version "1.41.10" + resolved "https://registry.yarnpkg.com/three-forcegraph/-/three-forcegraph-1.41.10.tgz#a162263b5c5db957c88cb496baff057f12bb26ef" + integrity sha512-XrrH0HzlWQiY5A30RMtlu+SpAM/LOoHMyaQouCFgavTSLrSsP4UIUP3eWrfJ0be1xDPuMGt3iofSYjgu4UT2NA== + dependencies: + accessor-fn "1" + d3-array "1 - 3" + d3-force-3d "2 - 3" + d3-scale "1 - 4" + d3-scale-chromatic "1 - 3" + data-joint "1" + kapsule "1" + ngraph.forcelayout "3" + ngraph.graph "20" + tinycolor2 "1" + three-mesh-bvh@^0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/three-mesh-bvh/-/three-mesh-bvh-0.6.1.tgz#63cebe46ff90221286f97e1fea59b433bb95304f" @@ -2908,6 +3005,11 @@ tiny-inflate@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== +tinycolor2@1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" + integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== + titleize@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" From 4fd1673fc3db91008ea7987d6e960835ddfece1f Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Mon, 25 Sep 2023 15:21:51 +0900 Subject: [PATCH 07/27] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20objTreeSync=20f?= =?UTF-8?q?unction=20to=20wb=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- states/whiteboard.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/states/whiteboard.ts b/states/whiteboard.ts index 2c2d3f5..e9a18b2 100644 --- a/states/whiteboard.ts +++ b/states/whiteboard.ts @@ -14,6 +14,7 @@ interface WhiteBoardActions { addBundle: (offset: Coord) => void; addObj: (obj: Obj, global?: boolean) => void; updateObj: (obj: Obj) => void; + syncObjTree: () => void; setCurrentTool: (tool: Tool) => void; setCurrentObj: (obj: string | null) => void; setDrag: (drag: DragData | null) => void; @@ -57,6 +58,9 @@ export const useWhiteBoard = create()((set, } }, updateObj: (obj: Obj) => set((state) => ({ objMap: state.objMap.set(obj.objId, obj) })), + syncObjTree: () => { + set({ objTree: constructRootObjTree(get().objMap) }); + }, setCurrentTool: (tool: Tool) => set(() => ({ currentTool: tool })), setCurrentObj: (obj: string | null) => set(() => ({ currentObj: obj })), setDrag: (drag: DragData | null) => set(() => ({ drag: drag })), From bdc47f510041b30da03d74af0042f9a72cee6082 Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Mon, 25 Sep 2023 15:23:11 +0900 Subject: [PATCH 08/27] =?UTF-8?q?=F0=9F=90=9B=20fix:=20increase=20camera?= =?UTF-8?q?=20render=20range?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/WhiteBoard/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/WhiteBoard/index.tsx b/components/WhiteBoard/index.tsx index 065db4d..60a02aa 100644 --- a/components/WhiteBoard/index.tsx +++ b/components/WhiteBoard/index.tsx @@ -16,7 +16,7 @@ export default function WhiteBoard({ style }: WhiteBoardProps) { flat frameloop="demand" style={style} - camera={{ position: [0, 0, 100], zoom: 1 }} + camera={{ position: [0, 0, 100], zoom: 1, far: 1000000, near: 0 }} orthographic > From 7d72279ea9caa31b294fcb5f1b80595dfe151f1a Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Mon, 25 Sep 2023 15:25:14 +0900 Subject: [PATCH 09/27] =?UTF-8?q?=E2=9C=A8=20feat:=20remove=20selection=20?= =?UTF-8?q?fade=20animation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit remove upTime state --- components/WhiteBoard/MouseHandler.tsx | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/components/WhiteBoard/MouseHandler.tsx b/components/WhiteBoard/MouseHandler.tsx index e6caf92..e3d7ea1 100644 --- a/components/WhiteBoard/MouseHandler.tsx +++ b/components/WhiteBoard/MouseHandler.tsx @@ -66,7 +66,7 @@ export default function MouseHandler({ forceTool }: MouseHandlerProps) { const [bundlePos, setBundlePos] = useState({ x: 0, y: 0 }); - const { upPos, setUpPos, upTime, setUpTime } = usePointerUp( + const { upPos, setUpPos } = usePointerUp( mouse, camera, currentTool, @@ -89,7 +89,6 @@ export default function MouseHandler({ forceTool }: MouseHandlerProps) { setDraw, setOpacity, setUpPos, - setUpTime, setCurrentObj, ); @@ -111,15 +110,6 @@ export default function MouseHandler({ forceTool }: MouseHandlerProps) { useDraw(currentTool, upPos, downPos, draw, setDraw, addObj); useFrame((s) => { - // update opacity (if selection is active) - if (drag !== null) { - setSelection(false); - } else if (upTime > 0 && selection) { - const newO = MAX_OPACITY - (s.clock.elapsedTime - upTime); - if (newO < 0) setSelection(false); - else setOpacity(newO); - } - // update camera position (if pan is active) if (cameraPan) { const newPos = getPos(s.mouse, s.camera); @@ -231,7 +221,6 @@ const usePointerUp = ( setDrag: (drag: DragData | null) => void, ) => { const [upPos, setUpPos] = useState({ x: 0, y: 0 }); // mouse down position - const [upTime, setUpTime] = useState(0); // pointer up useEffect(() => { @@ -258,7 +247,6 @@ const usePointerUp = ( case 'TEXT': if (e.button == 0) { setUpPos(newPos); - setUpTime(0); setSelection(false); setDraw(true); } @@ -266,8 +254,8 @@ const usePointerUp = ( case 'SELECT': if (e.button == 0) { setUpPos(newPos); - setUpTime(clock.elapsedTime); } + setSelection(false); break; case 'BUNDLE': if (e.button == 0) { @@ -293,7 +281,7 @@ const usePointerUp = ( tool, ]); - return { upPos, setUpPos, upTime, setUpTime }; + return { upPos, setUpPos }; }; const usePointerDown = ( @@ -306,7 +294,6 @@ const usePointerDown = ( setDraw: Dispatch>, setOpacity: Dispatch>, setUpPos: Dispatch>, - setUpTime: Dispatch>, setCurrentObj: (obj: string | null) => void, ) => { const [downPos, setDownPos] = useState({ x: 0, y: 0 }); // mouse down position @@ -344,7 +331,6 @@ const usePointerDown = ( setOpacity(MAX_OPACITY); setUpPos(newPos); setDownPos(newPos); - setUpTime(0); setCurrentObj(null); } break; @@ -365,7 +351,6 @@ const usePointerDown = ( setOpacity, setSelection, setUpPos, - setUpTime, tool, ]); From 7771f55eb6d3cbf29269a7147550d30585e86d3b Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Mon, 25 Sep 2023 15:39:22 +0900 Subject: [PATCH 10/27] =?UTF-8?q?=E2=9C=A8=20feat:=20nested=20obj=20render?= =?UTF-8?q?ing=20(relative=20coords)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wrap ObjRenderers: - [TYPE]Renderer: gets objId as input, loads object value to objRenderer - [TYPE]ObjRenderer: gets obj value as input -> can render object directly without adding to objMap when rendering a node, wrap itself and child nodes with three.js group pass groupRef to current obj's Renderer groupRef's position is set according to current obj (mesh position should be 0,0,0) + set transparent flag to materials for accurate z-index calculation --- components/WhiteBoard/CircleObjRenderer.tsx | 24 ++++-- components/WhiteBoard/LineObjRenderer.tsx | 49 +++++++----- components/WhiteBoard/NodeRenderer.tsx | 80 ++++++++++++++----- .../WhiteBoard/RectangleObjRenderer.tsx | 29 +++++-- components/WhiteBoard/SelectionRenderer.tsx | 7 +- components/WhiteBoard/TextObjRenderer.tsx | 42 ++++++---- utils/whiteboardHelper.ts | 6 ++ 7 files changed, 171 insertions(+), 66 deletions(-) diff --git a/components/WhiteBoard/CircleObjRenderer.tsx b/components/WhiteBoard/CircleObjRenderer.tsx index 6344779..f490a1d 100644 --- a/components/WhiteBoard/CircleObjRenderer.tsx +++ b/components/WhiteBoard/CircleObjRenderer.tsx @@ -1,20 +1,34 @@ 'use client'; import { Circle } from '@react-three/drei'; -import { ObjRendererProps } from './NodeRenderer'; +import { ObjRendererProps, RendererProps } from './NodeRenderer'; +import { useWhiteBoard } from '@/states/whiteboard'; +import { useFrame } from '@react-three/fiber'; + +export function CircleRenderer({ objId, dimensionsRef, groupRef }: RendererProps) { + const obj = useWhiteBoard((state) => state.objMap.get(objId) as CircleObj); + useFrame(() => { + if (groupRef === undefined || groupRef.current === null || obj === undefined) return; + groupRef.current.position.setX(obj.x + obj.r); + groupRef.current.position.setY(obj.y + obj.r); + groupRef.current.position.setZ(obj.depth); + }); + if (obj === undefined) return <>; + return ; +} export default function CircleObjRenderer({ rawObj, dimensionsRef }: ObjRendererProps) { const obj = rawObj as CircleObj; if (dimensionsRef !== undefined) { - dimensionsRef.current.x = obj.x; - dimensionsRef.current.y = obj.y; + dimensionsRef.current.x = -obj.r; + dimensionsRef.current.y = -obj.r; dimensionsRef.current.w = obj.r * 2; dimensionsRef.current.h = obj.r * 2; } return ( - - + + ); } diff --git a/components/WhiteBoard/LineObjRenderer.tsx b/components/WhiteBoard/LineObjRenderer.tsx index 0e08461..7936c6c 100644 --- a/components/WhiteBoard/LineObjRenderer.tsx +++ b/components/WhiteBoard/LineObjRenderer.tsx @@ -1,26 +1,45 @@ 'use client'; -import { ThreeEvent } from '@react-three/fiber'; +import { ThreeEvent, useFrame } from '@react-three/fiber'; import { forwardRef, useImperativeHandle, useMemo, useRef } from 'react'; import { Euler, Mesh } from 'three'; -import { ObjRendererProps } from './NodeRenderer'; +import { ObjRendererProps, RendererProps } from './NodeRenderer'; +import { useWhiteBoard } from '@/states/whiteboard'; +import { calculateLineGeometry } from '@/utils/whiteboardHelper'; + +export function LineRenderer({ objId, dimensionsRef, groupRef }: RendererProps) { + const obj = useWhiteBoard((state) => state.objMap.get(objId) as LineObj); + useFrame(() => { + if (groupRef === undefined || groupRef.current === null || obj === undefined) return; + groupRef.current.position.setX(Math.min(obj.x, obj.x2)); + groupRef.current.position.setY(Math.min(obj.y, obj.y2)); + groupRef.current.position.setZ(obj.depth); + }); + + if (obj === undefined) return <>; + return ; +} export default function LineObjRenderer({ rawObj, dimensionsRef }: ObjRendererProps) { const obj = rawObj as LineObj; + const baseX = Math.min(obj.x, obj.x2); + const baseY = Math.min(obj.y, obj.y2); + if (dimensionsRef !== undefined) { - dimensionsRef.current.x = Math.min(obj.x, obj.x2); - dimensionsRef.current.y = Math.min(obj.y, obj.y2); + dimensionsRef.current.x = 0; + dimensionsRef.current.y = 0; dimensionsRef.current.w = Math.abs(obj.x - obj.x2); dimensionsRef.current.h = Math.abs(obj.y - obj.y2); } + return ( ); } @@ -42,11 +61,11 @@ export interface FlatLineRef { export const FlatLine = forwardRef((props, ref) => { const { x, y, x2, y2, strokeWidth, color, depth, onPointerDown } = props; - const { w, d } = useMemo(() => calculate(x, y, x2, y2), [x, x2, y, y2]); + const { w, d } = useMemo(() => calculateLineGeometry(x, y, x2, y2), [x, x2, y, y2]); const meshRef = useRef(null!); useImperativeHandle(ref, () => ({ setPoints: (x: number, y: number, x2: number, y2: number, strokeWidth: number) => { - const { w, d } = calculate(x, y, x2, y2); + const { w, d } = calculateLineGeometry(x, y, x2, y2); meshRef.current.position.set((x + x2) / 2, (y + y2) / 2, depth); meshRef.current.setRotationFromEuler(new Euler(0, 0, d)); meshRef.current.scale.set(w, strokeWidth, 1); @@ -62,14 +81,8 @@ export const FlatLine = forwardRef((props, ref) => { onPointerDown={onPointerDown} > - + ); }); FlatLine.displayName = 'FlatLine'; - -function calculate(x: number, y: number, x2: number, y2: number) { - const w = Math.sqrt(Math.pow(x - x2, 2) + Math.pow(y - y2, 2)); - const d = Math.atan2(y2 - y, x2 - x); - return { w, d }; -} diff --git a/components/WhiteBoard/NodeRenderer.tsx b/components/WhiteBoard/NodeRenderer.tsx index 8d0979f..6e69da7 100644 --- a/components/WhiteBoard/NodeRenderer.tsx +++ b/components/WhiteBoard/NodeRenderer.tsx @@ -1,18 +1,25 @@ 'use client'; import { useWhiteBoard } from '@/states/whiteboard'; -import RectangleObjRenderer from './RectangleObjRenderer'; -import TextObjRenderer from './TextObjRenderer'; import { useThree } from '@react-three/fiber'; import { getPos } from '@/utils/whiteboardHelper'; import SelectionRenderer from './SelectionRenderer'; -import { MutableRefObject, memo, useRef } from 'react'; -import LineObjRenderer from './LineObjRenderer'; -import CircleObjRenderer from './CircleObjRenderer'; -import GraphObjRenderer from './GraphObjRenderer'; +import { MutableRefObject, RefObject, memo, useRef } from 'react'; +import { RectangleRenderer } from './RectangleObjRenderer'; +import { TextRenderer } from './TextObjRenderer'; +import { LineRenderer } from './LineObjRenderer'; +import { CircleRenderer } from './CircleObjRenderer'; +import { Group } from 'three'; export interface ObjRendererProps { rawObj: Obj; dimensionsRef?: MutableRefObject; + dimensions?: ObjDimensions; +} + +export interface RendererProps { + objId: string; + dimensionsRef?: MutableRefObject; + groupRef?: RefObject; } interface NodeRendererProps { @@ -20,48 +27,81 @@ interface NodeRendererProps { } export default function NodeRenderer({ node }: NodeRendererProps) { - const obj = useWhiteBoard((state) => state.objMap.get(node.objId)); + const objExists = useWhiteBoard((state) => state.objMap.get(node.objId) !== undefined); + const groupRef = useRef(null); return ( - <> - {obj === undefined ? <> : } + + {objExists ? : <>} {node.childNodes.map((n) => { return ; })} - + ); } interface RenderWrapperProps { obj: Obj; dimensionsRef: MutableRefObject; + groupRef?: RefObject; } -function RenderWrapper({ obj, dimensionsRef }: RenderWrapperProps) { +function RenderWrapper({ obj, dimensionsRef, groupRef }: RenderWrapperProps) { switch (obj.type) { case 'RECT': - return ; + return ( + + ); case 'TEXT': - return ; + return ( + + ); case 'LINE': - return ; + return ( + + ); case 'CIRCLE': - return ; - case 'GRAPH': - return ; + return ( + + ); } } const MemoizedRenderWrapper = memo( RenderWrapper, - (prev, next) => prev.obj.objId === next.obj.objId && prev.dimensionsRef === next.dimensionsRef, + (prev, next) => + prev.obj.objId === next.obj.objId && + prev.dimensionsRef === next.dimensionsRef && + prev.obj.type === next.obj.type && + prev.groupRef === next.groupRef, ); interface ObjectWrapperProps { + groupRef?: RefObject; objId: string; } -function ObjectWrapper({ objId }: ObjectWrapperProps) { +function ObjectWrapper({ objId, groupRef }: ObjectWrapperProps) { const obj = useWhiteBoard((state) => state.objMap.get(objId))!; const dimensionsRef = useRef({ x: 0, y: 0, w: 0, h: 0 }); const { currentObj, setCurrentObj, setDrag, currentTool } = useWhiteBoard((state) => ({ @@ -92,7 +132,7 @@ function ObjectWrapper({ objId }: ObjectWrapperProps) { } }} > - + {selection ? : <>} ); diff --git a/components/WhiteBoard/RectangleObjRenderer.tsx b/components/WhiteBoard/RectangleObjRenderer.tsx index 6fd9b38..089d1f0 100644 --- a/components/WhiteBoard/RectangleObjRenderer.tsx +++ b/components/WhiteBoard/RectangleObjRenderer.tsx @@ -1,21 +1,40 @@ 'use client'; -import { ObjRendererProps } from './NodeRenderer'; +import { useWhiteBoard } from '@/states/whiteboard'; +import { ObjRendererProps, RendererProps } from './NodeRenderer'; +import { useFrame } from '@react-three/fiber'; + +export function RectangleRenderer({ objId, dimensionsRef, groupRef }: RendererProps) { + const obj = useWhiteBoard((state) => state.objMap.get(objId)); + useFrame(() => { + if (groupRef === undefined || groupRef.current === null || obj === undefined) return; + groupRef.current.position.setX(obj.x); + groupRef.current.position.setY(obj.y); + groupRef.current.position.setZ(obj.depth); // gap between nodes + }); + if (obj === undefined) return <>; + return ; +} // renders given rectangle object, sets selection onClick export default function RectangleObjRenderer({ rawObj, dimensionsRef }: ObjRendererProps) { const obj = rawObj as RectObj; if (dimensionsRef !== undefined) { - dimensionsRef.current.x = obj.x; - dimensionsRef.current.y = obj.y; + dimensionsRef.current.x = 0; + dimensionsRef.current.y = 0; dimensionsRef.current.w = obj.w; dimensionsRef.current.h = obj.h; } return ( - + - + ); } diff --git a/components/WhiteBoard/SelectionRenderer.tsx b/components/WhiteBoard/SelectionRenderer.tsx index 5f5fbe8..45f91e3 100644 --- a/components/WhiteBoard/SelectionRenderer.tsx +++ b/components/WhiteBoard/SelectionRenderer.tsx @@ -55,7 +55,6 @@ export default function SelectionRenderer({ dimensionsRef, objId }: SelectionRen if (dimensionsRef.current.h <= 0) dimensionsRef.current.h = 0; if (dimensionsRef.current.w <= 0) dimensionsRef.current.w = 0; - if (dimensionsRef.current.h === 0 && dimensionsRef.current.w === 0) return; return ( <> @@ -252,12 +251,14 @@ interface LinePointsProps { } function LinePoints({ obj, onPointerDown }: LinePointsProps) { + const baseX = Math.min(obj.x, obj.x2); + const baseY = Math.min(obj.y, obj.y2); return ( <> {/* NE */} - onPointerDown(e, 'ne')} x={obj.x} y={obj.y} /> + onPointerDown(e, 'ne')} x={obj.x - baseX} y={obj.y - baseY} /> {/* SW */} - onPointerDown(e, 'sw')} x={obj.x2} y={obj.y2} /> + onPointerDown(e, 'sw')} x={obj.x2 - baseX} y={obj.y2 - baseY} /> ); } diff --git a/components/WhiteBoard/TextObjRenderer.tsx b/components/WhiteBoard/TextObjRenderer.tsx index 2358a4e..6ac1d53 100644 --- a/components/WhiteBoard/TextObjRenderer.tsx +++ b/components/WhiteBoard/TextObjRenderer.tsx @@ -1,20 +1,33 @@ 'use client'; import { Text } from '@react-three/drei'; -import { useThree } from '@react-three/fiber'; -import { ObjRendererProps } from './NodeRenderer'; +import { invalidate, useFrame } from '@react-three/fiber'; +import { ObjRendererProps, RendererProps } from './NodeRenderer'; +import { useWhiteBoard } from '@/states/whiteboard'; +import { DEFAULT_FONT } from '@/utils/whiteboardHelper'; -export default function TextObjRenderer({ rawObj, dimensionsRef }: ObjRendererProps) { - const obj = rawObj as TextObj; - const { invalidate } = useThree(); +export function TextRenderer({ objId, dimensionsRef, groupRef }: RendererProps) { + const obj = useWhiteBoard((state) => state.objMap.get(objId) as TextObj); + useFrame(() => { + const position = calculatePosition(obj); + if (groupRef !== undefined && groupRef.current !== null) { + groupRef.current.position.setX(position.x); + groupRef.current.position.setY(position.y); + groupRef.current.position.setZ(obj.depth); + } + if (dimensionsRef !== undefined) { + dimensionsRef.current.w = obj.w; + dimensionsRef.current.x = 0; + dimensionsRef.current.y = 0; + } + }); - if (dimensionsRef !== undefined) { - dimensionsRef.current.w = obj.w; - dimensionsRef.current.x = obj.x; - dimensionsRef.current.y = obj.y; - } + if (obj === undefined) return <>; - const position = calculatePosition(obj); + return ; +} +export default function TextObjRenderer({ rawObj, dimensionsRef }: ObjRendererProps) { + const obj = rawObj as TextObj; return ( { - if (geometry.boundingBox === null) return; + if (geometry.boundingBox === null || dimensionsRef === undefined) return; const newHeight = geometry.boundingBox.max.y - geometry.boundingBox.min.y; - if (dimensionsRef === undefined) return; - if (dimensionsRef.current.h !== newHeight) { + if (dimensionsRef.current.h != newHeight) { dimensionsRef.current.h = newHeight; invalidate(); } diff --git a/utils/whiteboardHelper.ts b/utils/whiteboardHelper.ts index 5d21f3f..13d138d 100644 --- a/utils/whiteboardHelper.ts +++ b/utils/whiteboardHelper.ts @@ -538,6 +538,12 @@ export function createForceBundleFromMindmap(response: MindmapResponse): ObjBund }; } +export function calculateLineGeometry(x: number, y: number, x2: number, y2: number) { + const w = Math.sqrt(Math.pow(x - x2, 2) + Math.pow(y - y2, 2)); + const d = Math.atan2(y2 - y, x2 - x); + return { w, d }; +} + // DAGRE.js tree version // // export function createTreeBundleFromMindmap(response: MindmapResponse): ObjBundle { From c2887770496ca4e837d190492f249b13e93836ce Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Mon, 25 Sep 2023 15:46:30 +0900 Subject: [PATCH 11/27] =?UTF-8?q?=E2=9C=A8=20feat:=20graph=20obj=20rendere?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit uses d3 for layout generation LiveGraph: - whole graph inside single object - layout is regenerated on graph data update - cannot be modified by user - converted to a group of nodeObjs onClick (ObjectifyGraph) GraphRoot & GraphNode: - graph divided into nodes - each node is handled as a subtree --- components/WhiteBoard/GraphObjRenderer.tsx | 7 - components/WhiteBoard/GraphRenderer.tsx | 272 +++++++++++++++++++++ components/WhiteBoard/MouseHandler.tsx | 19 ++ components/WhiteBoard/NodeRenderer.tsx | 28 +++ global.d.ts | 43 +++- 5 files changed, 359 insertions(+), 10 deletions(-) delete mode 100644 components/WhiteBoard/GraphObjRenderer.tsx create mode 100644 components/WhiteBoard/GraphRenderer.tsx diff --git a/components/WhiteBoard/GraphObjRenderer.tsx b/components/WhiteBoard/GraphObjRenderer.tsx deleted file mode 100644 index 209aa4d..0000000 --- a/components/WhiteBoard/GraphObjRenderer.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import LineObjRenderer from './LineObjRenderer'; -import { ObjRendererProps } from './NodeRenderer'; - -export default function GraphObjRenderer({ rawObj, dimensionsRef }: ObjRendererProps) { - const obj = rawObj as GraphObj; - return <>; -} diff --git a/components/WhiteBoard/GraphRenderer.tsx b/components/WhiteBoard/GraphRenderer.tsx new file mode 100644 index 0000000..f71beb5 --- /dev/null +++ b/components/WhiteBoard/GraphRenderer.tsx @@ -0,0 +1,272 @@ +'use client'; +import * as d3 from 'd3'; +import { RendererProps } from './NodeRenderer'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { invalidate, useFrame } from '@react-three/fiber'; +import * as THREE from 'three'; +import { Group } from 'three'; +import { useWhiteBoard } from '@/states/whiteboard'; +import { DEFAULT_FONT, calculateLineGeometry, genId } from '@/utils/whiteboardHelper'; +// @ts-ignore +import { Text, preloadFont } from 'troika-three-text'; +import { Text as DreiText } from '@react-three/drei'; +import { FlatLine, FlatLineRef } from './LineObjRenderer'; + +// Live graph : graph constantly updated by text2graph model, user cannot modify +export function LiveGraphRenderer({ objId, dimensionsRef }: RendererProps) { + const planeMaterial = useMemo(() => { + const material = new THREE.MeshBasicMaterial({ color: 'grey', transparent: false }); + material.depthTest = true; + material.depthWrite = true; + return material; + }, []); + const planeGeometry = useMemo(() => new THREE.PlaneGeometry(1, 1), []); + + const obj = useWhiteBoard((state) => state.objMap.get(objId) as LiveGraphObj); + + const graphRef = useRef>(); + const dataRef = useRef>(); + const groupRef = useRef(null); + const [fontLoaded, setFontLoaded] = useState(false); + + // render after font preload + preloadFont({ font: DEFAULT_FONT }, () => { + setFontLoaded(true); + invalidate(); + }); + + useFrame(() => { + if ( + dataRef.current === undefined || + graphRef.current === undefined || + dimensionsRef === undefined || + groupRef.current === null + ) + return; + dimensionsRef.current.x = obj.x; + dimensionsRef.current.y = obj.y; + groupRef.current.position.set(obj.x, obj.y, obj.depth); + }); + + // update tree layout + useEffect(() => { + if (groupRef.current === null) return; + // remove items from mesh group + for (const child of groupRef.current.children) { + child.removeFromParent(); + if (child.hasOwnProperty('dispose')) (child as any).dispose(); // prevent memory leak + } + groupRef.current.clear(); + + if (!fontLoaded) return; + + const children = new Map(Object.entries(obj.data.graph)); + const root = d3.hierarchy(obj.data.root, (d) => { + return children.get(d.toString()); + }); + + graphRef.current = d3.tree().nodeSize([30, 300]); + dataRef.current = graphRef.current(root); + + // link meshes + for (const v of dataRef.current.links()) { + const link = new THREE.Mesh(planeGeometry, planeMaterial); + const { w, d } = calculateLineGeometry(v.source.y, v.source.x, v.target.y, v.target.x); + link.scale.setX(w); + link.rotation.set(0, 0, d); + link.position.set((v.source.y + v.target.y) / 2, (v.source.x + v.target.x) / 2, obj.depth); + groupRef.current.add(link); + } + + // node meshes + for (const v of dataRef.current.descendants()) { + const text = new Text(); + text.name = 'label'; + text.font = DEFAULT_FONT; + text.fontSize = 15; + text.text = obj.data.keywords[v.data]; + text.color = 'black'; + text.position.setX(v.y); + text.position.setY(v.x); + text.position.setZ(obj.depth); + text.anchorX = 'center'; + text.anchorY = 'middle'; + text.transparent = false; + text.outlineWidth = 5; + text.outlineColor = 'white'; + groupRef.current.add(text); + } + }, [ + fontLoaded, + obj.data.graph, + obj.data.keywords, + obj.data.root, + obj.depth, + planeGeometry, + planeMaterial, + ]); + + return ( + { + // TODO: create a proper ui for this function + // fixes and objectify current LiveGraph + objectifyGraph(objId); + }} + ref={groupRef} + > + ); +} + +export function GraphRootRenderer({ objId, dimensionsRef, groupRef }: RendererProps) { + const obj = useWhiteBoard((state) => state.objMap.get(objId) as GraphRootObj); + + useFrame(() => { + if (groupRef === undefined || groupRef.current === null || obj === undefined) return; + groupRef.current.position.setX(obj.x); + groupRef.current.position.setY(obj.y); + groupRef.current.position.setZ(obj.depth); + }); + + return ( + { + if (geometry.boundingBox === null || dimensionsRef === undefined) return; + dimensionsRef.current.h = 10 + geometry.boundingBox.max.y - geometry.boundingBox.min.y; + dimensionsRef.current.w = 10 + geometry.boundingBox.max.x - geometry.boundingBox.min.x; + dimensionsRef.current.x = geometry.boundingBox.min.x - 5; + dimensionsRef.current.y = geometry.boundingBox.min.y - 5; + }} + > + {obj.label} + + ); +} + +export function GraphNodeRenderer({ objId, dimensionsRef, groupRef }: RendererProps) { + const obj = useWhiteBoard((state) => state.objMap.get(objId) as GraphNodeObj); + const lineRef = useRef(null); + + useFrame(() => { + if ( + groupRef === undefined || + groupRef.current === null || + obj === undefined || + lineRef.current === null + ) + return; + groupRef.current.position.setX(obj.x); + groupRef.current.position.setY(obj.y); + groupRef.current.position.setZ(-0.00000000001); // gap between nodes + lineRef.current.setPoints(0, 0, -obj.x, -obj.y, 1); + }); + + return ( + <> + { + if (geometry.boundingBox === null || dimensionsRef === undefined) return; + dimensionsRef.current.h = 10 + geometry.boundingBox.max.y - geometry.boundingBox.min.y; + dimensionsRef.current.w = 10 + geometry.boundingBox.max.x - geometry.boundingBox.min.x; + dimensionsRef.current.x = geometry.boundingBox.min.x - 5; + dimensionsRef.current.y = geometry.boundingBox.min.y - 5; + }} + > + {obj.label} + + + + ); +} + +// Live graph -> graph: convert every node to objs +function objectifyGraph(objId: string) { + const obj = useWhiteBoard.getState().objMap.get(objId) as LiveGraphObj; + const objs: Obj[] = []; + + // create graph data + const children = new Map(Object.entries(obj.data.graph)); + const root = d3.hierarchy(obj.data.root, (d) => { + return children.get((d as number).toString()); + }); + + // create tree layout + const tree = d3.tree().nodeSize([30, 300])(root); + + // nodes + const nodes = new Map(); + for (const v of tree.descendants()) { + const node: GraphNodeObj | GraphRootObj = + v.parent === null + ? { + type: 'GRAPHROOT', + label: obj.data.keywords[v.data], + objId: obj.objId, + x: v.y, + y: v.x, + depth: obj.depth, + parentId: obj.parentId, + } + : { + type: 'GRAPHNODE', + label: obj.data.keywords[v.data], + objId: obj.data.keywords[v.data] + genId(), + x: v.y - (v.parent?.y ?? 0), + y: v.x - (v.parent?.x ?? 0), + depth: obj.depth, + parentId: obj.objId, + }; + nodes.set(v.data, node); + } + + // edges + for (const v of tree.links()) { + const parent = nodes.get(v.source.data); + const child = nodes.get(v.target.data); + if (parent === undefined || child === undefined) continue; + child.parentId = parent.objId; + child.x; + child.y; + } + + for (const [_i, node] of nodes) { + objs.push(node); + } + + for (const newObj of objs) { + // change current Obj (liveGraph -> graph) + if (newObj.objId === obj.objId) useWhiteBoard.getState().updateObj(newObj); + // add new objs (nodes, edges) + else useWhiteBoard.getState().addObj(newObj, false); + } + + // update render tree + useWhiteBoard.getState().syncObjTree(); +} diff --git a/components/WhiteBoard/MouseHandler.tsx b/components/WhiteBoard/MouseHandler.tsx index e3d7ea1..827ddeb 100644 --- a/components/WhiteBoard/MouseHandler.tsx +++ b/components/WhiteBoard/MouseHandler.tsx @@ -501,6 +501,25 @@ function handleDrag(obj: Obj, newPos: Coord, drag: DragData, updateObj: (obj: Ob return handleLineDrag(obj as LineObj, newPos, drag, updateObj); case 'CIRCLE': return handleCircleDrag(obj as CircleObj, newPos, drag, updateObj); + case 'GRAPHNODE': + case 'GRAPHROOT': + return handleGraphNodeDrag(obj as GraphNodeObj, newPos, drag, updateObj); + } +} + +function handleGraphNodeDrag( + obj: GraphNodeObj, + newPos: Coord, + drag: DragData, + updateObj: (obj: Obj) => void, +) { + switch (drag.mode) { + case 'move': + return updateObj({ + ...obj, + x: validateValue(newPos.x + drag.prevObj.x - drag.mousePos.x), + y: validateValue(newPos.y + drag.prevObj.y - drag.mousePos.y), + }); } } diff --git a/components/WhiteBoard/NodeRenderer.tsx b/components/WhiteBoard/NodeRenderer.tsx index 6e69da7..db48fb3 100644 --- a/components/WhiteBoard/NodeRenderer.tsx +++ b/components/WhiteBoard/NodeRenderer.tsx @@ -8,6 +8,7 @@ import { RectangleRenderer } from './RectangleObjRenderer'; import { TextRenderer } from './TextObjRenderer'; import { LineRenderer } from './LineObjRenderer'; import { CircleRenderer } from './CircleObjRenderer'; +import { GraphNodeRenderer, GraphRootRenderer, LiveGraphRenderer } from './GraphRenderer'; import { Group } from 'three'; export interface ObjRendererProps { @@ -84,6 +85,33 @@ function RenderWrapper({ obj, dimensionsRef, groupRef }: RenderWrapperProps) { groupRef={groupRef} /> ); + case 'LIVEGRAPH': + return ( + + ); + case 'GRAPHNODE': + return ( + + ); + case 'GRAPHROOT': + return ( + + ); } } diff --git a/global.d.ts b/global.d.ts index 4b0f0dd..03c11ff 100644 --- a/global.d.ts +++ b/global.d.ts @@ -4,7 +4,15 @@ interface ObjNode { depth: number; } -type ObjType = 'RECT' | 'TEXT' | 'ROOT' | 'LINE' | 'CIRCLE' | 'GRAPH'; +type ObjType = + | 'RECT' + | 'TEXT' + | 'ROOT' + | 'LINE' + | 'CIRCLE' + | 'GRAPHROOT' + | 'LIVEGRAPH' + | 'GRAPHNODE'; interface Obj { objId: string; @@ -22,11 +30,20 @@ interface ObjDimensions { h: number; } -interface GraphObj extends Obj { - type: 'GRAPH'; +interface LiveGraphObj extends Obj { + type: 'LIVEGRAPH'; data: MindmapResponse; } +interface GraphNodeObj extends Obj { + type: 'GRAPHNODE'; + label: string; +} + +interface GraphRootObj extends GraphNodeObj { + type: 'GRAPHROOT'; +} + interface CircleObj extends Obj { type: 'CIRCLE'; r: number; @@ -37,6 +54,26 @@ interface CircleOptions { color?: string; } +interface GraphData { + nodes: NodeData[]; + links: LinkData[]; +} + +interface NodeData { + id: string; + name: string; + val: number; + color: string; + r: number; + __threeObj?: Mesh; +} + +interface LinkData { + source: string; + target: string; + color: string; +} + interface RectObj extends Obj { type: 'RECT'; w: number; From 2d9b3680a8c51b5c2ec473ecfb8154176ef9c657 Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Wed, 11 Oct 2023 13:55:23 +0900 Subject: [PATCH 12/27] =?UTF-8?q?=E2=9E=95=20build:=20add=20dependencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + yarn.lock | 371 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 362 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 2048132..632a503 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@types/node": "20.4.2", "@types/react": "18.2.14", "@types/react-dom": "18.2.7", + "@types/react-pdf": "^7.0.0", "@types/three": "^0.154.0", "d3": "^7.8.5", "d3-force-3d": "^3.0.5", @@ -24,6 +25,7 @@ "next": "^13.4.12", "react": "18.2.0", "react-dom": "18.2.0", + "react-pdf": "^7.5.0", "three": "^0.154.0", "three-forcegraph": "^1.41.10", "typescript": "5.1.6", diff --git a/yarn.lock b/yarn.lock index edff4bd..ee51177 100644 --- a/yarn.lock +++ b/yarn.lock @@ -92,6 +92,21 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@mapbox/node-pre-gyp@^1.0.0": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" + integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + "@mediapipe/tasks-vision@0.10.2-rc2": version "0.10.2-rc2" resolved "https://registry.yarnpkg.com/@mediapipe/tasks-vision/-/tasks-vision-0.10.2-rc2.tgz#e3fa5d84d58b9031a0e975d1e5ef8eb8e4a6fc11" @@ -538,6 +553,13 @@ dependencies: "@types/react" "*" +"@types/react-pdf@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@types/react-pdf/-/react-pdf-7.0.0.tgz#75ddcfd4c7a7f5c3f9d96ec506d5cddeb1020fbf" + integrity sha512-G0a+5UiKk3AvEauBP/Js7r9kGZNW3iBbS6kXkH0foGSaKWR6K3ElTe7Y4tlolc2VKbM9udmMxpkbxh/dtR2wXA== + dependencies: + react-pdf "*" + "@types/react-reconciler@^0.26.7": version "0.26.7" resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.26.7.tgz#0c4643f30821ae057e401b0d9037e03e8e9b2a36" @@ -644,6 +666,11 @@ dependencies: "@use-gesture/core" "10.2.27" +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + accessor-fn@1: version "1.5.0" resolved "https://registry.yarnpkg.com/accessor-fn/-/accessor-fn-1.5.0.tgz#9353e10194da404366657f47177cd9bcb4463ee7" @@ -659,6 +686,13 @@ acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -681,6 +715,19 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -846,6 +893,15 @@ caniuse-lite@^1.0.30001406: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz#90fabae294215c3495807eb24fc809e11dc2f0a8" integrity sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA== +canvas@^2.11.2: + version "2.11.2" + resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.2.tgz#553d87b1e0228c7ac0fc72887c3adbac4abbd860" + integrity sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.0" + nan "^2.17.0" + simple-get "^3.0.3" + chalk@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -866,11 +922,21 @@ chevrotain@^10.1.2: lodash "4.17.21" regexp-to-ast "0.5.0" +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + client-only@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== +clsx@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" + integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -883,6 +949,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + commander@7: version "7.2.0" resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" @@ -893,6 +964,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -1189,6 +1265,13 @@ debounce@^1.2.1: resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== +debug@4, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -1196,12 +1279,12 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== dependencies: - ms "2.1.2" + mimic-response "^2.0.0" deep-is@^0.1.3: version "0.1.4" @@ -1246,6 +1329,11 @@ delaunator@5: dependencies: robust-predicates "^3.0.0" +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + dequal@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" @@ -1258,6 +1346,11 @@ detect-gpu@^5.0.28: dependencies: webgl-constants "^1.1.1" +detect-libc@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" + integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -1284,6 +1377,11 @@ draco3d@^1.4.1: resolved "https://registry.yarnpkg.com/draco3d/-/draco3d-1.5.6.tgz#0d570a9792e3a3a9fafbfea065b692940441c626" integrity sha512-+3NaRjWktb5r61ZFoDejlykPEFKT5N/LkbXsaddlw6xNSXBanUYpFc2AXXpbJDilPHazcSreU/DpQIaxfX0NfQ== +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + emoji-regex@^9.2.2: version "9.2.2" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" @@ -1690,6 +1788,13 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1715,6 +1820,21 @@ functions-have-names@^1.2.2, functions-have-names@^1.2.3: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" @@ -1881,6 +2001,11 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -1888,6 +2013,14 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -1936,7 +2069,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2013,6 +2146,11 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -2257,11 +2395,35 @@ maath@^0.6.0: resolved "https://registry.yarnpkg.com/maath/-/maath-0.6.0.tgz#7841d0fb95bbb37d19b08b7c5458ef70190950d2" integrity sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw== +make-cancellable-promise@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/make-cancellable-promise/-/make-cancellable-promise-1.3.1.tgz#3bd89704c75afe6251cdd6a82baca1fcfbd2c792" + integrity sha512-DWOzWdO3xhY5ESjVR+wVFy03rpt0ZccS4bunccNwngoX6rllKlMZm6S9ZnJ5nMuDDweqDMjtaO0g6tZeh+cCUA== + +make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-event-props@^1.6.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/make-event-props/-/make-event-props-1.6.1.tgz#1d587017c3f1f3b42719b775af93d5253656ccdd" + integrity sha512-JhvWq/iz1BvlmnPvLJjXv+xnMPJZuychrDC68V+yCGQJn5chcA8rLGKo5EP1XwIKVrigSXKLmbeXAGkf36wdCQ== + material-symbols@^0.10.1: version "0.10.1" resolved "https://registry.yarnpkg.com/material-symbols/-/material-symbols-0.10.1.tgz#a49cfce1a37079a70fd26b92f0e790b986072266" integrity sha512-hGylDJScyjeR0LI524AVfCLI/thKuUX2rG78BYYHEg0R/IS4NzSh28aCP0k088968dxItxV/4hNNR4MAiXrb8Q== +merge-refs@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/merge-refs/-/merge-refs-1.2.1.tgz#abddc800375395a4a4eb5c45ebf2a52557fdbe34" + integrity sha512-pRPz39HQz2xzHdXAGvtJ9S8aEpNgpUjzb5yPC3ytozodmsHg+9nqgRs7/YOmn9fM/TLzntAC8AdGTidKxOq9TQ== + dependencies: + "@types/react" "*" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -2300,6 +2462,11 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +mimic-response@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -2312,6 +2479,31 @@ minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mmd-parser@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mmd-parser/-/mmd-parser-1.0.4.tgz#87cc05782cb5974ca854f0303fc5147bc9d690e7" @@ -2327,6 +2519,11 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +nan@^2.17.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" + integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== + nanoid@^3.3.4: version "3.3.6" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" @@ -2392,6 +2589,20 @@ ngraph.random@^1.0.0: resolved "https://registry.yarnpkg.com/ngraph.random/-/ngraph.random-1.1.0.tgz#5345c4bb63865c85d98ee6f13eab1395d8545a90" integrity sha512-h25UdUN/g8U7y29TzQtRm/GvGr70lK37yQPvPKXXuVfs7gCm82WipYFZcksQfeKumtOemAzBIcT7lzzyK/edLw== +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -2406,6 +2617,16 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -2466,7 +2687,7 @@ object.values@^1.1.6: define-properties "^1.1.4" es-abstract "^1.20.4" -once@^1.3.0: +once@^1.3.0, once@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -2568,6 +2789,19 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +path2d-polyfill@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz#24c554a738f42700d6961992bf5f1049672f2391" + integrity sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA== + +pdfjs-dist@3.11.174: + version "3.11.174" + resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz#5ff47b80f2d58c8dd0d74f615e7c6a7e7e704c4b" + integrity sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA== + optionalDependencies: + canvas "^2.11.2" + path2d-polyfill "^2.0.1" + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -2597,7 +2831,7 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prop-types@^15.6.0, prop-types@^15.8.1: +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -2641,6 +2875,20 @@ react-merge-refs@^1.1.0: resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06" integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ== +react-pdf@*, react-pdf@^7.5.0: + version "7.5.0" + resolved "https://registry.yarnpkg.com/react-pdf/-/react-pdf-7.5.0.tgz#a49d51faf2a68e0bc30d5a8ff5335061bb72a4b1" + integrity sha512-hX7SfQGd9T6pdd3H5HcR1VzrRCehkhnBh/tsyz9GO9cXrYHgoxupboVL2VCQpBBSak+/UQSMCj+3JTOdheuwwQ== + dependencies: + clsx "^2.0.0" + make-cancellable-promise "^1.3.1" + make-event-props "^1.6.0" + merge-refs "^1.2.1" + pdfjs-dist "3.11.174" + prop-types "^15.6.2" + tiny-invariant "^1.0.0" + tiny-warning "^1.0.0" + react-reconciler@^0.27.0: version "0.27.0" resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.27.0.tgz#360124fdf2d76447c7491ee5f0e04503ed9acf5b" @@ -2663,6 +2911,15 @@ react@18.2.0: dependencies: loose-envify "^1.1.0" +readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + regenerator-runtime@^0.13.11: version "0.13.11" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" @@ -2751,6 +3008,11 @@ rw@1: resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-regex-test@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" @@ -2779,18 +3041,23 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" -semver@^6.3.0: +semver@^6.0.0, semver@^6.3.0: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.7: +semver@^7.3.5, semver@^7.3.7: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -2812,11 +3079,25 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55" + integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -2842,6 +3123,15 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string.prototype.codepointat@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc" @@ -2888,6 +3178,13 @@ string.prototype.trimstart@^1.0.6: define-properties "^1.1.4" es-abstract "^1.20.4" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -2952,6 +3249,18 @@ tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tar@^6.1.11: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" + integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -3005,6 +3314,16 @@ tiny-inflate@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== +tiny-invariant@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" + integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== + +tiny-warning@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + tinycolor2@1: version "1.6.0" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" @@ -3022,6 +3341,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + troika-three-text@^0.47.2: version "0.47.2" resolved "https://registry.yarnpkg.com/troika-three-text/-/troika-three-text-0.47.2.tgz#fdf89059c010563bb829262b20c41f69ca79b712" @@ -3138,6 +3462,11 @@ use-sync-external-store@1.2.0: resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + utility-types@^3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" @@ -3161,6 +3490,19 @@ webgl-sdf-generator@1.1.1: resolved "https://registry.yarnpkg.com/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz#3e1b422b3d87cd3cc77f2602c9db63bc0f6accbd" integrity sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -3191,6 +3533,13 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" From 619afeded86930b21e1cdee76ac976d00097f9ea Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Wed, 11 Oct 2023 13:56:37 +0900 Subject: [PATCH 13/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20update=20next.js?= =?UTF-8?q?=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next.config.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/next.config.js b/next.config.js index 658404a..949f771 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,9 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + webpack: (config) => { + config.resolve.alias.canvas = false; + return config; + }, +}; module.exports = nextConfig; From 206d0bfd058400377b3bf06c0fc64f637f9bd05b Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Fri, 13 Oct 2023 05:47:15 +0900 Subject: [PATCH 14/27] =?UTF-8?q?=F0=9F=90=9B=20fix:=20tool=20button=20shr?= =?UTF-8?q?inks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/WhiteBoard/ToolSelector/toolSelector.module.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/WhiteBoard/ToolSelector/toolSelector.module.css b/components/WhiteBoard/ToolSelector/toolSelector.module.css index 77ea04c..fac1a1b 100644 --- a/components/WhiteBoard/ToolSelector/toolSelector.module.css +++ b/components/WhiteBoard/ToolSelector/toolSelector.module.css @@ -16,8 +16,9 @@ align-items: center; border-radius: 50%; cursor: default; - width: 33px; - height: 33px; + min-width: 33px; + min-height: 33px; + flex: 0; color: var(--background); background-color: transparent; } From 876fef3e700ddd7a31240b72e62e420753d99c5d Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Fri, 13 Oct 2023 05:48:18 +0900 Subject: [PATCH 15/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20break=20?= =?UTF-8?q?down=20tool=20button=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/WhiteBoard/ToolSelector/index.tsx | 75 ++++++++++++++++---- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/components/WhiteBoard/ToolSelector/index.tsx b/components/WhiteBoard/ToolSelector/index.tsx index 9aa41df..dd9f603 100644 --- a/components/WhiteBoard/ToolSelector/index.tsx +++ b/components/WhiteBoard/ToolSelector/index.tsx @@ -4,35 +4,80 @@ import styles from './toolSelector.module.css'; import 'material-symbols'; export default function ToolSelector() { + const { currentTool, setCurrentTool } = useWhiteBoard((state) => ({ + currentTool: state.currentTool, + setCurrentTool: state.setCurrentTool, + })); return (
- - - - - - + + + + + +
); } interface ToolButtonProps { + currentTool: Tool; + setCurrentTool: (tool: Tool) => void; toolName: Tool; icon: string; } -function ToolButton({ toolName, icon }: ToolButtonProps) { - const { currentTool, setCurrentTool } = useWhiteBoard((state) => ({ - currentTool: state.currentTool, - setCurrentTool: state.setCurrentTool, - })); +export function ToolButton({ currentTool, setCurrentTool, toolName, icon }: ToolButtonProps) { + return ( + setCurrentTool(toolName)} + /> + ); +} +interface SmallIconButtonProps { + icon: string; + selected: boolean; + onClick?: () => void; +} + +export function SmallIconButton({ icon, selected, onClick }: SmallIconButtonProps) { return (
setCurrentTool(toolName)} + className={`${styles.buttonContainer} ${selected ? styles.buttonSelection : ''}`} + onClick={onClick} > {icon}
From b382f67133c9dc211ec7584a9170ca1e855a818b Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Fri, 13 Oct 2023 06:27:09 +0900 Subject: [PATCH 16/27] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20feat:=20add=20hig?= =?UTF-8?q?hlight=20tool=20for=20pdf=20viewer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- global.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.d.ts b/global.d.ts index 03c11ff..35a8ce3 100644 --- a/global.d.ts +++ b/global.d.ts @@ -119,7 +119,7 @@ interface Coord { y: number; } -type Tool = 'HAND' | 'SELECT' | 'RECT' | 'TEXT' | 'LINE' | 'BUNDLE' | 'CIRCLE'; +type Tool = 'HAND' | 'SELECT' | 'RECT' | 'TEXT' | 'LINE' | 'BUNDLE' | 'CIRCLE' | 'HIGHLIGHT'; interface WBDocumentMetadata { documentId: number; From 3e0d88eb07cc2aeceddf92f13d9d32f167285a48 Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Fri, 13 Oct 2023 06:27:53 +0900 Subject: [PATCH 17/27] =?UTF-8?q?=E2=9C=A8=20feat:=20bottom=20control=20pa?= =?UTF-8?q?nel=20for=20pdf=20viewer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BottomPanel/BottomPanel.module.css | 72 ++++++++ components/PdfViewer/BottomPanel/index.tsx | 155 ++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 components/PdfViewer/BottomPanel/BottomPanel.module.css create mode 100644 components/PdfViewer/BottomPanel/index.tsx diff --git a/components/PdfViewer/BottomPanel/BottomPanel.module.css b/components/PdfViewer/BottomPanel/BottomPanel.module.css new file mode 100644 index 0000000..f3d6f6f --- /dev/null +++ b/components/PdfViewer/BottomPanel/BottomPanel.module.css @@ -0,0 +1,72 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + border-radius: 10px 10px 0 0; + width: 100%; + background-color: var(--primary-dark); + overflow: clip; + user-select: none; +} + +.handle { + cursor: row-resize; + height: 20px; + flex: 0; + display: flex; + align-items: center; + justify-content: center; + width: 100%; +} + +.utils { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + justify-content: space-around; + gap: 10px; + --background: var(--foreground); + color: var(--background); + text-align: center; +} + +.documentControls { + gap: 5px; + height: 46px; + display: flex; + flex-direction: row; + align-items: center; +} + +.tools { + gap: 10px; + height: 46px; + display: flex; + align-items: center; +} + +.keywords { + display: flex; + justify-content: center; + width: 100%; + flex-wrap: wrap; + padding: 10px; + align-items: center; + overflow: auto; + gap: 5px; +} + +.keyword { + padding: 2px 5px; + border-radius: 5px; + background-color: var(--secondary-light); + color: var(--primary-dark); +} + +.icon { + cursor: pointer; + text-align: center; + color: var(--background); +} diff --git a/components/PdfViewer/BottomPanel/index.tsx b/components/PdfViewer/BottomPanel/index.tsx new file mode 100644 index 0000000..ecd0e34 --- /dev/null +++ b/components/PdfViewer/BottomPanel/index.tsx @@ -0,0 +1,155 @@ +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import styles from './BottomPanel.module.css'; +import { SmallIconButton, ToolButton } from '@/components/WhiteBoard/ToolSelector'; +import 'material-symbols'; + +const DEFAULT_HEIGHT = 66; + +interface BottomPanelProps { + currentTool: Tool; + setCurrentTool: (tool: Tool) => void; + keywords: string[]; + setKeywords: Dispatch>; + scale: number; + setScale: Dispatch>; + page: number; + setPage: Dispatch>; + numPages: number; +} + +export default function BottomPanel({ + currentTool, + setCurrentTool, + keywords, + setKeywords, + scale, + setScale, + page, + setPage, + numPages, +}: BottomPanelProps) { + const [height, setHeight] = useState(DEFAULT_HEIGHT); + const [drag, setDrag] = useState(false); + + useEffect(() => { + if (!drag) return; + const pointerUp = () => setDrag(false); + const pointerMove = (e: MouseEvent) => + setHeight(Math.max(window.innerHeight - e.clientY + 10, DEFAULT_HEIGHT)); + document.addEventListener('pointermove', pointerMove); + document.addEventListener('pointerup', pointerUp); + return () => { + document.removeEventListener('pointermove', pointerMove); + document.removeEventListener('pointerup', pointerUp); + }; + }, [drag]); + + return ( +
+
{ + setDrag(true); + }} + > + e.stopPropagation()} + onClick={() => { + setHeight((height) => (height === DEFAULT_HEIGHT ? -1 : DEFAULT_HEIGHT)); + }} + > + {height === DEFAULT_HEIGHT ? 'expand_less' : 'expand_more'} + +
+
+
+ { + setScale((scale) => scale - 0.1); + }} + /> +

{`${Math.round(scale * 100)}%`}

+ { + setScale((scale) => scale + 0.1); + }} + /> +

|

+ { + setPage((page) => { + const newPage = page - 1; + if (newPage < 0) return 0; + return newPage; + }); + }} + /> +

{page + 1}

+ { + setPage((page) => { + const newPage = page + 1; + if (newPage >= numPages) return numPages - 1; + return newPage; + }); + }} + /> +
+
+ + +
+
+ +
+ {keywords.map((v) => ( + { + setKeywords((keywords) => keywords.filter((k) => k !== v)); + }} + /> + ))} +
+
+ ); +} + +interface KeywordProps { + label: string; + onClick?: () => void; +} + +function Keyword({ label, onClick }: KeywordProps) { + return ( +
+ {label} +
+ ); +} From ab75b0b5dd531551c37c50dcdb82993b0aa1cda6 Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Fri, 13 Oct 2023 06:36:51 +0900 Subject: [PATCH 18/27] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20feat:=20add=20sou?= =?UTF-8?q?rceData=20to=20WBDocument=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- global.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/global.d.ts b/global.d.ts index 35a8ce3..b949e46 100644 --- a/global.d.ts +++ b/global.d.ts @@ -124,6 +124,7 @@ type Tool = 'HAND' | 'SELECT' | 'RECT' | 'TEXT' | 'LINE' | 'BUNDLE' | 'CIRCLE' | interface WBDocumentMetadata { documentId: number; documentName: string; + data: WBSourceData; createdAt: number; modifiedAt: number; } @@ -166,6 +167,11 @@ interface MindmapResponse { graph: Map; } +interface WBSourceData { + type: 'pdf' | 'audio'; + url: string; +} + interface ObjBundle { x: number; y: number; From 6af621596c8f7698b850c6ca4b234c5ecd6d10af Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Fri, 13 Oct 2023 06:40:55 +0900 Subject: [PATCH 19/27] =?UTF-8?q?=E2=9C=A8=20feat:=20implement=20PdfViewer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit react-pdf 활용 bottomPanel를 통해 문서와 상호작용 를 활용한 간단한 키워드 하이라이팅 기능 (예외처리 필요) --- app/globals.css | 8 ++ components/PdfViewer/PdfViewer.module.css | 20 +++ components/PdfViewer/index.tsx | 145 ++++++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 components/PdfViewer/PdfViewer.module.css create mode 100644 components/PdfViewer/index.tsx diff --git a/app/globals.css b/app/globals.css index f041dcb..223c3c5 100644 --- a/app/globals.css +++ b/app/globals.css @@ -104,3 +104,11 @@ div.loading-bar { scale: 100%; } } + +mark { + text-decoration: underline; + text-decoration-color: red; + text-decoration-thickness: 2px; + color: transparent; + background: transparent; +} diff --git a/components/PdfViewer/PdfViewer.module.css b/components/PdfViewer/PdfViewer.module.css new file mode 100644 index 0000000..af7772e --- /dev/null +++ b/components/PdfViewer/PdfViewer.module.css @@ -0,0 +1,20 @@ +.container { + display: flex; + height: 100%; + width: 100%; + flex: 0; + flex-direction: column; +} + +.viewerContainer { + display: flex; + align-items: center; + flex-direction: column; + width: 100%; + flex: 1; + overflow: auto; +} + +.viewer { + margin: 0 auto; +} diff --git a/components/PdfViewer/index.tsx b/components/PdfViewer/index.tsx new file mode 100644 index 0000000..9698898 --- /dev/null +++ b/components/PdfViewer/index.tsx @@ -0,0 +1,145 @@ +'use client'; +import { Document, Page, pdfjs } from 'react-pdf'; +import styles from './PdfViewer.module.css'; +import 'react-pdf/dist/Page/TextLayer.css'; +import 'react-pdf/dist/Page/AnnotationLayer.css'; +import { useCallback, useState } from 'react'; +import BottomPanel from './BottomPanel'; + +pdfjs.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.js', + import.meta.url, +).toString(); + +const SCALE_MAX = 10; +const SCALE_MIN = 0.8; + +interface PdfViewerProps { + url: string; +} + +export default function PdfViewer({ url }: PdfViewerProps) { + const [keywords, setKeywords] = useState([]); + const [tool, setTool] = useState('SELECT'); + const [scale, setScale] = useState(1); + const [page, setPage] = useState(0); + const [numPages, setNumPages] = useState(0); + + const onPointerUp = () => { + if (tool !== 'HIGHLIGHT') return; + // add selection to keywords + const word = window.getSelection()?.toString() ?? ''; + if (word.length === 0) return; + setKeywords((keywords) => [...keywords, word]); + }; + + const textRenderer = useCallback( + (textItem: any) => { + // highlights keywords + const pos: Pos[] = []; + for (const s of keywords) { + // find all occurences + const matches = (textItem.str as string).matchAll(new RegExp(s, 'gi')); + for (const m of matches) { + if (m.index === undefined) continue; + pos.push({ start: m.index, end: m.index + s.length }); + } + } + // add marks + const str = mergeAndApplyMarks(textItem.str as string, pos); + return str; + }, + [keywords], + ); + + return ( +
+
+
+ { + setNumPages(d.numPages); + }} + > + + +
+
+ +
+ ); +} + +interface Pos { + start: number; + end: number; +} + +function posSorter(a: Pos, b: Pos) { + // latter item comes first + if (a.end > b.end) { + return -1; + } else if (a.end < b.end) { + return 1; + } else { + return a.start > b.start ? -1 : 1; + } +} + +export function mergeAndApplyMarks(sourceStr: string, pos: Pos[]): string { + let str = sourceStr; + // sort occurences + pos.sort(posSorter); + + // apply occurences + const bounds: Pos = { start: -1, end: -1 }; + for (const p of pos) { + // first + if (bounds.start === -1 || bounds.end === -1) { + // update bounds + bounds.end = p.end; + bounds.start = p.start; + } else if (p.end < bounds.start) { + // mark + str = + str.slice(0, bounds.start) + + '' + + str.slice(bounds.start, bounds.end) + + '' + + str.slice(bounds.end); + // update bounds + bounds.end = p.end; + bounds.start = p.start; + } else { + bounds.end = Math.max(bounds.end, p.end); + bounds.start = Math.min(bounds.start, p.start); + } + } + // apply last mark + if (bounds.start !== -1 || bounds.end !== -1) { + str = + str.slice(0, bounds.start) + + '' + + str.slice(bounds.start, bounds.end) + + '' + + str.slice(bounds.end); + } + + return str; +} From 928ac0a7cc9c06dd8ab824a72399ba1fb5681746 Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Fri, 13 Oct 2023 06:41:07 +0900 Subject: [PATCH 20/27] =?UTF-8?q?=E2=9C=A8=20feat:=20restyle=20sidebar=20a?= =?UTF-8?q?nd=20add=20pdfViewer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사이드바 크기 조절 pdf 뷰어 탭으로 추가 (탭 삭제하고 뷰어만 남길 예정) --- app/document/[documentId]/page.module.css | 9 ++++-- app/document/[documentId]/page.tsx | 35 +++++++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/app/document/[documentId]/page.module.css b/app/document/[documentId]/page.module.css index 9a28b41..0a782ac 100644 --- a/app/document/[documentId]/page.module.css +++ b/app/document/[documentId]/page.module.css @@ -4,6 +4,13 @@ background-color: var(--secondary-dark); } +.divider { + background-color: var(--primary-light); + width: 10px; + cursor: col-resize; + user-select: none; +} + .container { animation: var(--intro-animation); background-color: var(--secondary-dark); @@ -18,8 +25,6 @@ .sideBar { display: flex; flex-direction: column; - width: 350px; - flex: 0 0 350px; } .sideBarUp { diff --git a/app/document/[documentId]/page.tsx b/app/document/[documentId]/page.tsx index 66a1a4d..8fd223c 100644 --- a/app/document/[documentId]/page.tsx +++ b/app/document/[documentId]/page.tsx @@ -12,6 +12,7 @@ import TreeViewer from '@/components/WhiteBoard/TreeViewer'; import ToolSelector from '@/components/WhiteBoard/ToolSelector'; import LoadingScreen from '../../../components/LoadingScreen'; import OverlayWrapper from '@/components/OverlayWrapper'; +import PdfViewer from '@/components/PdfViewer'; const WhiteBoard = dynamic(() => import('@/components/WhiteBoard'), { ssr: false }); interface DocumentPageProps { @@ -25,7 +26,24 @@ export default function DoucumentsPage({ params }: DocumentPageProps) { resetWhiteBoard: state.resetWhiteBoard, })); - const document = useDocument(parseInt(params.documentId)); + const [sideBarWidth, setSideBarWidth] = useState(500); + const [dividerActive, setDividerActive] = useState(false); + + useEffect(() => { + if (!dividerActive) return; + const onPointerUp = () => setDividerActive(false); + const onPointerMove = (e: MouseEvent) => { + setSideBarWidth(e.clientX - 5); + }; + document.addEventListener('pointerup', onPointerUp); + document.addEventListener('pointermove', onPointerMove); + return () => { + document.removeEventListener('pointerup', onPointerUp); + document.removeEventListener('pointermove', onPointerMove); + }; + }, [dividerActive]); + + const documentData = useDocument(parseInt(params.documentId)); const [overlay, setOverlay] = useState(null); @@ -34,21 +52,26 @@ export default function DoucumentsPage({ params }: DocumentPageProps) { resetWhiteBoard(); }, [resetWhiteBoard]); - if (document === null) return ; + if (documentData === null) return ; return (
-
+
- - + +
- + +
+
setDividerActive(true)} />
From 98cb4e16db0b593ed211a3c853cd6975eb78f482 Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Sat, 14 Oct 2023 14:03:56 +0900 Subject: [PATCH 21/27] =?UTF-8?q?=F0=9F=92=84=20feat:=20remove=20unnecessa?= =?UTF-8?q?ry=20elements=20from=20sidebar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/document/[documentId]/page.module.css | 5 +++++ app/document/[documentId]/page.tsx | 11 +++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/document/[documentId]/page.module.css b/app/document/[documentId]/page.module.css index 0a782ac..bc8a349 100644 --- a/app/document/[documentId]/page.module.css +++ b/app/document/[documentId]/page.module.css @@ -27,6 +27,11 @@ flex-direction: column; } +.viewerContainer { + background-color: var(--background); + height: 100%; +} + .sideBarUp { display: flex; flex-direction: column; diff --git a/app/document/[documentId]/page.tsx b/app/document/[documentId]/page.tsx index 8fd223c..61635dd 100644 --- a/app/document/[documentId]/page.tsx +++ b/app/document/[documentId]/page.tsx @@ -61,15 +61,10 @@ export default function DoucumentsPage({ params }: DocumentPageProps) { className={styles.sideBar} style={{ width: `${sideBarWidth}px`, flex: `0 0 ${sideBarWidth}px` }} > -
- - -
- - - + +
- +
setDividerActive(true)} />
From aecbb97b47521726296d3b1f3bcbdb9e574ab316 Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:24:45 +0900 Subject: [PATCH 22/27] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20feat:=20wb=20sour?= =?UTF-8?q?ce=20data=20type=20definition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- global.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/global.d.ts b/global.d.ts index b949e46..2395610 100644 --- a/global.d.ts +++ b/global.d.ts @@ -167,8 +167,10 @@ interface MindmapResponse { graph: Map; } +type WBSourceDataType = 'pdf' | 'audio'; + interface WBSourceData { - type: 'pdf' | 'audio'; + type: WBSourceDataType; url: string; } From 47541b1724838bbdf4d4453859909d60ce7f9cd3 Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:25:21 +0900 Subject: [PATCH 23/27] =?UTF-8?q?=F0=9F=90=9B=20fix:=20sidebar=20layout=20?= =?UTF-8?q?flicker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/document/[documentId]/page.module.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/document/[documentId]/page.module.css b/app/document/[documentId]/page.module.css index bc8a349..480349f 100644 --- a/app/document/[documentId]/page.module.css +++ b/app/document/[documentId]/page.module.css @@ -29,7 +29,8 @@ .viewerContainer { background-color: var(--background); - height: 100%; + flex-grow: 1; + overflow: auto; } .sideBarUp { From ef5c7cc7f3d15caddb227ba3bd188db3ceb9aba6 Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:25:44 +0900 Subject: [PATCH 24/27] =?UTF-8?q?=E2=9C=A8=20feat:=20show=20data=20type=20?= =?UTF-8?q?in=20documentTitle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DocumentTitle/documentTitle.module.css | 12 ++++++------ .../components/DocumentTitle/index.tsx | 19 ++++++++++++++----- app/document/[documentId]/page.tsx | 5 ++++- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app/document/[documentId]/components/DocumentTitle/documentTitle.module.css b/app/document/[documentId]/components/DocumentTitle/documentTitle.module.css index f0a8927..82b4f44 100644 --- a/app/document/[documentId]/components/DocumentTitle/documentTitle.module.css +++ b/app/document/[documentId]/components/DocumentTitle/documentTitle.module.css @@ -1,13 +1,13 @@ .container { cursor: default; box-sizing: border-box; - height: 50px; + height: 46px; display: flex; flex-direction: row; align-items: center; justify-content: flex-start; - padding: 0 0 0 30px; - gap: 3px; + padding: 0 0 0 10px; + gap: 10px; } .label { @@ -17,7 +17,7 @@ .icon { color: var(--background); - width: 30px; - font-size: 30px; - font-variation-settings: 'FILL' 0, 'wght' 200, 'GRAD' 0, 'opsz' 24; + width: 24px; + font-size: 24px; + font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; } diff --git a/app/document/[documentId]/components/DocumentTitle/index.tsx b/app/document/[documentId]/components/DocumentTitle/index.tsx index bf418fd..fb9cb64 100644 --- a/app/document/[documentId]/components/DocumentTitle/index.tsx +++ b/app/document/[documentId]/components/DocumentTitle/index.tsx @@ -4,16 +4,25 @@ import 'material-symbols'; interface DocumentTitleProps { documentName: string; + sourceDataType: WBSourceDataType; } -export default function DocumentTitle({ documentName: documentName }: DocumentTitleProps) { +export default function DocumentTitle({ documentName, sourceDataType }: DocumentTitleProps) { return (
- -

문서

- - Chevron_Right + + {typeToIcon(sourceDataType)} +

{documentName}

); } + +function typeToIcon(type: WBSourceDataType): string { + switch (type) { + case 'pdf': + return 'picture_as_pdf'; + case 'audio': + return 'text_to_speech'; + } +} diff --git a/app/document/[documentId]/page.tsx b/app/document/[documentId]/page.tsx index 61635dd..64d8531 100644 --- a/app/document/[documentId]/page.tsx +++ b/app/document/[documentId]/page.tsx @@ -61,7 +61,10 @@ export default function DoucumentsPage({ params }: DocumentPageProps) { className={styles.sideBar} style={{ width: `${sideBarWidth}px`, flex: `0 0 ${sideBarWidth}px` }} > - +
From e782af2595cace0af4813168d2a3548b10c68b53 Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:26:50 +0900 Subject: [PATCH 25/27] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20refactor:=20remove?= =?UTF-8?q?=20overlay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/document/[documentId]/page.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/document/[documentId]/page.tsx b/app/document/[documentId]/page.tsx index 64d8531..1a3ee8b 100644 --- a/app/document/[documentId]/page.tsx +++ b/app/document/[documentId]/page.tsx @@ -1,17 +1,12 @@ 'use client'; import dynamic from 'next/dynamic'; -import { ReactNode, Suspense, useEffect, useState } from 'react'; +import { Suspense, useEffect, useState } from 'react'; import styles from './page.module.css'; import { useWhiteBoard } from '@/states/whiteboard'; import { getDocument } from '@/utils/api'; import DocumentTitle from './components/DocumentTitle'; -import ActionButtonGroup from './components/ActionButtonGroup'; -import Tab from './components/Tab'; -import ObjectPropertyEditor from '@/components/WhiteBoard/ObjectPropertyEditor'; -import TreeViewer from '@/components/WhiteBoard/TreeViewer'; import ToolSelector from '@/components/WhiteBoard/ToolSelector'; import LoadingScreen from '../../../components/LoadingScreen'; -import OverlayWrapper from '@/components/OverlayWrapper'; import PdfViewer from '@/components/PdfViewer'; const WhiteBoard = dynamic(() => import('@/components/WhiteBoard'), { ssr: false }); @@ -45,8 +40,6 @@ export default function DoucumentsPage({ params }: DocumentPageProps) { const documentData = useDocument(parseInt(params.documentId)); - const [overlay, setOverlay] = useState(null); - // reset whiteboard useEffect(() => { resetWhiteBoard(); @@ -81,7 +74,6 @@ export default function DoucumentsPage({ params }: DocumentPageProps) {
- {overlay}
); } From 808ee06cbbc9d67a6e2d4a7013f3008030a03c8d Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:30:06 +0900 Subject: [PATCH 26/27] =?UTF-8?q?=F0=9F=92=84=20fix:=20document=20title=20?= =?UTF-8?q?height=20shrinking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/DocumentTitle/documentTitle.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/document/[documentId]/components/DocumentTitle/documentTitle.module.css b/app/document/[documentId]/components/DocumentTitle/documentTitle.module.css index 82b4f44..843f268 100644 --- a/app/document/[documentId]/components/DocumentTitle/documentTitle.module.css +++ b/app/document/[documentId]/components/DocumentTitle/documentTitle.module.css @@ -1,7 +1,7 @@ .container { cursor: default; box-sizing: border-box; - height: 46px; + min-height: 46px; display: flex; flex-direction: row; align-items: center; From a1676f7ac2e6c3169cbb6882070defa722d8fe51 Mon Sep 17 00:00:00 2001 From: junhea <97426534+junhea@users.noreply.github.com> Date: Sat, 14 Oct 2023 16:05:55 +0900 Subject: [PATCH 27/27] =?UTF-8?q?=E2=9C=A8=20feat:=20set=20scale=20value?= =?UTF-8?q?=20bounds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/PdfViewer/BottomPanel/index.tsx | 6 ++++-- components/PdfViewer/index.tsx | 3 --- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/components/PdfViewer/BottomPanel/index.tsx b/components/PdfViewer/BottomPanel/index.tsx index ecd0e34..2ad2759 100644 --- a/components/PdfViewer/BottomPanel/index.tsx +++ b/components/PdfViewer/BottomPanel/index.tsx @@ -4,6 +4,8 @@ import { SmallIconButton, ToolButton } from '@/components/WhiteBoard/ToolSelecto import 'material-symbols'; const DEFAULT_HEIGHT = 66; +const SCALE_MAX = 10; +const SCALE_MIN = 1; interface BottomPanelProps { currentTool: Tool; @@ -74,7 +76,7 @@ export default function BottomPanel({ icon="zoom_out" selected={false} onClick={() => { - setScale((scale) => scale - 0.1); + setScale((scale) => Math.max(scale - 0.1, SCALE_MIN)); }} />

{`${Math.round(scale * 100)}%`}

@@ -82,7 +84,7 @@ export default function BottomPanel({ icon="zoom_in" selected={false} onClick={() => { - setScale((scale) => scale + 0.1); + setScale((scale) => Math.min(scale + 0.1, SCALE_MAX)); }} />

|

diff --git a/components/PdfViewer/index.tsx b/components/PdfViewer/index.tsx index 9698898..0a4646b 100644 --- a/components/PdfViewer/index.tsx +++ b/components/PdfViewer/index.tsx @@ -11,9 +11,6 @@ pdfjs.GlobalWorkerOptions.workerSrc = new URL( import.meta.url, ).toString(); -const SCALE_MAX = 10; -const SCALE_MIN = 0.8; - interface PdfViewerProps { url: string; }