diff options
| author | Zachary Zhang <zacharyzhang7@gmail.com> | 2024-08-31 00:46:29 -0400 |
|---|---|---|
| committer | Zachary Zhang <zacharyzhang7@gmail.com> | 2024-08-31 00:46:29 -0400 |
| commit | 196294f331496262bef256da8b8e9dbc80288bea (patch) | |
| tree | 85ff27b7a8070585f9a5ef71dff63566e03232ba /src/client/views/nodes/DiagramBox.tsx | |
| parent | 0cf61501ec9be34294935f01973c1bd9cad6d267 (diff) | |
| parent | c36607691e0b7f5c04f3209a64958f5e51ddd785 (diff) | |
Merge branch 'master' into zach-starter
Diffstat (limited to 'src/client/views/nodes/DiagramBox.tsx')
| -rw-r--r-- | src/client/views/nodes/DiagramBox.tsx | 643 |
1 files changed, 140 insertions, 503 deletions
diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx index 5a712b8b0..79cf39152 100644 --- a/src/client/views/nodes/DiagramBox.tsx +++ b/src/client/views/nodes/DiagramBox.tsx @@ -1,45 +1,42 @@ /* eslint-disable prettier/prettier */ /* eslint-disable jsx-a11y/control-has-associated-label */ import mermaid from 'mermaid'; -import { action, makeObservable, observable, reaction, computed } from 'mobx'; +import { action, computed, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../../fields/Doc'; -import { List } from '../../../fields/List'; +import { DocData } from '../../../fields/DocSymbols'; import { RichTextField } from '../../../fields/RichTextField'; -import { DocCast, BoolCast } from '../../../fields/Types'; +import { Cast, DocCast, NumCast } from '../../../fields/Types'; +import { Gestures } from '../../../pen-gestures/GestureTypes'; import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { LinkManager } from '../../util/LinkManager'; +import { undoable } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { InkingStroke } from '../InkingStroke'; import './DiagramBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; -import { PointData } from '../../../pen-gestures/GestureTypes'; -import { InkField } from '../../../fields/InkField'; - -enum menuState { - option, - mermaidCode, - drawing, - gpt, - justCreated, -} +import { FormattedTextBox } from './formattedText/FormattedTextBox'; @observer export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DiagramBox, fieldKey); } - private _ref: React.RefObject<HTMLDivElement> = React.createRef(); - private _dragRef = React.createRef<HTMLDivElement>(); + static isPointInBox = (box: Doc, pt: number[]): boolean => { + if (typeof pt[0] === 'number' && typeof box.x === 'number' && typeof box.y === 'number' && typeof pt[1] === 'number') { + return pt[0] < box.x + NumCast(box.width) && pt[0] > box.x && pt[1] > box.y && pt[1] < box.y + NumCast(box.height); + } + return false; + }; + constructor(props: FieldViewProps) { super(props); makeObservable(this); } - @observable menuState = menuState.justCreated; @observable renderDiv: React.ReactNode; @observable inputValue = ''; @observable createInputValue = ''; @@ -47,12 +44,21 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @observable errorMessage = ''; @observable mermaidCode = ''; @observable isExampleMenuOpen = false; + @observable _showCode = false; + @observable _inputValue = ''; + @observable _generating = false; + @observable _errorMessage = ''; @action handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { this.inputValue = e.target.value; console.log(e.target.value); }; - async componentDidMount() { + + @computed get mermaidcode() { + return Cast(this.Document[DocData].text, RichTextField, null)?.Text ?? ''; + } + + componentDidMount() { this._props.setContentViewBox?.(this); mermaid.initialize({ securityLevel: 'loose', @@ -61,510 +67,147 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { flowchart: { useMaxWidth: false, htmlLabels: true, curve: 'cardinal' }, gantt: { useMaxWidth: true, useWidth: 2000 }, }); - if (!this.Document.testValue) { - this.Document.height = 500; - this.Document.width = 500; - } - this.Document.testValue = 'a'; - this.mermaidCode = 'a'; - if (typeof this.Document.drawingMermaidCode === 'string' && this.Document.menuState === 'drawing') { - this.renderMermaidAsync(this.Document.drawingMermaidCode); - } - // this is so that ever time a new doc, text node or ink node, is created, this.createMermaidCode will run which will create a save - // reaction( - // () => DocListCast(this.Document.data), - // () => this.lockInkStroke(), - // { fireImmediately: true } - // ); - // reaction( - // () => - // DocListCast(this.Document.data) - // .filter(doc => doc.type === 'rich text') - // .map(doc => (doc.text as RichTextField).Text), - // () => this.convertDrawingToMermaidCode(), - // { fireImmediately: true } - // ); - // const rectangleXValues = computed(() => - // DocListCast(this.Document.data) - // .filter(doc => doc.title === 'rectangle') - // .map(doc => doc.x) - // ); - // reaction( - // () => rectangleXValues.get(), - // () => this.lockInkStroke(), - // { fireImmediately: true } - // ); - this.lockInkStroke(); - } - - componentDidUpdate = () => { - if (typeof this.Document.drawingMermaidCode === 'string' && this.Document.menuState === 'drawing') { - this.renderMermaidAsync(this.Document.drawingMermaidCode); - } - if (typeof this.Document.gptMermaidCode === 'string' && this.Document.menuState === 'gpt') { - this.renderMermaidAsync(this.Document.gptMermaidCode); - } - }; - switchRenderDiv() { - switch (this.Document.menuState) { - case 'option': - this.renderDiv = this.renderOption(); - break; - case 'drawing': - this.renderDiv = this.renderDrawing(); - break; - case 'gpt': - this.renderDiv = this.renderGpt(); - break; - case 'mermaidCode': - this.renderDiv = this.renderMermaidCode(); - break; - default: - this.menuState = menuState.option; - this.renderDiv = this.renderOption(); - } + // when a new doc/text/ink/shape is created in the freeform view, this generates the corresponding mermaid diagram code + reaction( + () => DocListCast(this.Document.data), + docArray => docArray.length && this.convertDrawingToMermaidCode(docArray), + { fireImmediately: true } + ); } - renderMermaid = async (str: string) => { + renderMermaid = (str: string) => { try { - const { svg, bindFunctions } = await this.mermaidDiagram(str); - return { svg, bindFunctions }; + return mermaid.render('graph' + Date.now(), str); } catch (error) { - // console.error('Error rendering mermaid diagram:', error); return { svg: '', bindFunctions: undefined }; } }; - mermaidDiagram = async (str: string) => mermaid.render('graph' + Date.now(), str); - async renderMermaidAsync(mermaidCode: string) { + renderMermaidAsync = async (mermaidCode: string, dashDiv: HTMLDivElement) => { try { const { svg, bindFunctions } = await this.renderMermaid(mermaidCode); - const dashDiv = document.getElementById('dashDiv' + this.Document.title); - if (dashDiv) { - dashDiv.innerHTML = svg; - // this.changeHeightWidth(svg); - if (bindFunctions) { - bindFunctions(dashDiv); - } - } + dashDiv.innerHTML = svg; + bindFunctions?.(dashDiv); } catch (error) { console.error('Error rendering Mermaid:', error); } - } - changeHeightWidth(svgString: string) { - const pattern = /width="([\d.]+)"\s*height="([\d.]+)"/; + }; - const match = svgString.match(pattern); + setMermaidCode = undoable((res: string) => { + this.Document[DocData].text = new RichTextField( + JSON.stringify({ + doc: { + type: 'doc', + content: [ + { + type: 'code_block', + content: [ + { type: 'text', text: `^@mermaids\n` }, + { type: 'text', text: this.removeWords(res) }, + ], + }, + ], + }, + selection: { type: 'text', anchor: 1, head: 1 }, + }), + res + ); + }, 'set mermaid code'); - if (match) { - const width = parseFloat(match[1]); - const height = parseFloat(match[2]); - console.log(width); - console.log(height); - this.Document.width = width; - this.Document.height = height; - } - } - @action handleRenderClick = () => { - this.mermaidCode = ''; - if (this.inputValue) { - this.generateMermaidCode(); - } - }; - @action async generateMermaidCode() { - console.log('Generating Mermaid Code'); - const dashDiv = document.getElementById('dashDiv' + this.Document.title); - if (dashDiv) { - dashDiv.innerHTML = ''; - } - this.loading = true; - let prompt = ''; - prompt = 'Write this in mermaid code and only give me the mermaid code: ' + this.inputValue; - // } - const res = await gptAPICall(prompt, GPTCallType.MERMAID); - this.loading = true; - if (res === 'Error connecting with API.') { - // If GPT call failed - console.error('GPT call failed'); - this.errorMessage = 'GPT call failed; please try again.'; - } else if (res !== null) { - // If GPT call succeeded, set htmlCode;;; TODO: check if valid html - this.mermaidCode = res; - console.log('GPT call succeeded:' + res); - this.errorMessage = ''; - } - this.renderMermaidAsync.call(this, this.removeWords(this.mermaidCode)); - this.Document.gptMermaidCode = this.removeWords(this.mermaidCode); - } - removeWords(inputStrIn: string) { - const inputStr = inputStrIn.replace('```mermaid', ''); - return inputStr.replace('```', ''); - } - // method to convert the drawings on collection node side the mermaid code - async convertDrawingToMermaidCode() { - let mermaidCode = ''; - let diagramExists = false; - if (this.Document.data instanceof List) { - const docArray: Doc[] = DocListCast(this.Document.data); - const rectangleArray = docArray.filter(doc => doc.title === 'rectangle' || doc.title === 'circle'); - const lineArray = docArray.filter(doc => doc.title === 'line' || doc.title === 'stroke'); - const textArray = docArray.filter(doc => doc.type === 'rich text'); - const timeoutPromise = () => - new Promise(resolve => { - setTimeout(resolve, 0); - }); - await timeoutPromise(); - const inkStrokeArray = lineArray.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())).filter(inkView => inkView?.ComponentView instanceof InkingStroke); - if (inkStrokeArray[0] && inkStrokeArray.length === lineArray.length) { - // if (this.isLeftRightDiagram(docArray)) { - // mermaidCode = 'graph LR;'; - // } else { - // mermaidCode = 'graph TD;'; - // } - const inkingStrokeArray = inkStrokeArray.map(stroke => stroke?.ComponentView); - for (let i = 0; i < rectangleArray.length; i++) { - const rectangle = rectangleArray[i]; - for (let j = 0; j < lineArray.length; j++) { - const inkScaleX = (inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkScaleX; - const inkScaleY = (inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkScaleY; - const inkStrokeXArray = (inkingStrokeArray[j] as InkingStroke) - ?.inkScaledData() - .inkData.map(coord => coord.X) - .map(doc => doc * inkScaleX); - const inkStrokeYArray = (inkingStrokeArray[j] as InkingStroke) - ?.inkScaledData() - .inkData.map(coord => coord.Y) - .map(doc => doc * inkScaleY); - // need to minX and minY to since the inkStroke.x and.y is not relative to the doc. so I have to do some calcluations - const minX: number = Math.min(...inkStrokeXArray); - const minY: number = Math.min(...inkStrokeYArray); - const startX = inkStrokeXArray[0] - minX + (lineArray[j]?.x as number); - const startY = inkStrokeYArray[0] - minY + (lineArray[j]?.y as number); - const endX = inkStrokeXArray[inkStrokeXArray.length - 1] - minX + (lineArray[j].x as number); - const endY = inkStrokeYArray[inkStrokeYArray.length - 1] - minY + (lineArray[j].y as number); - if (this.isPointInBox(rectangle, [startX, startY])) { - for (let k = 0; k < rectangleArray.length; k++) { - const rectangle2 = rectangleArray[k]; - if (this.isPointInBox(rectangle2, [endX, endY]) && typeof rectangle.x === 'number' && typeof rectangle2.x === 'number') { - diagramExists = true; - const linkedDocs: Doc[] = LinkManager.Instance.getAllRelatedLinks(lineArray[j]).map(d => DocCast(LinkManager.getOppositeAnchor(d, lineArray[j]))); - if (linkedDocs.length !== 0) { - const linkedText = (linkedDocs[0].text as RichTextField).Text; - mermaidCode += Math.abs(rectangle.x) + this.getTextInBox(rectangle, textArray) + '---|' + linkedText + '|' + Math.abs(rectangle2.x) + this.getTextInBox(rectangle2, textArray) + ';'; - } else { - mermaidCode += Math.abs(rectangle.x) + this.getTextInBox(rectangle, textArray) + '---' + Math.abs(rectangle2.x) + this.getTextInBox(rectangle2, textArray) + ';'; - } - } - } - } - } + generateMermaidCode = action(() => { + this._generating = true; + const prompt = 'Write this in mermaid code and only give me the mermaid code: ' + this._inputValue; + gptAPICall(prompt, GPTCallType.MERMAID).then( + action(res => { + this._generating = false; + if (res === 'Error connecting with API.') { + this._errorMessage = 'GPT call failed; please try again.'; } - // this will save the text - if (diagramExists) { - this.Document.drawingMermaidCode = mermaidCode; + // If GPT call succeeded, set mermaid code on Doc which will trigger a rendering if _showCode is false + else if (res && this.isValidCode(res)) { + this.setMermaidCode(res); + this._errorMessage = ''; } else { - this.Document.drawingMermaidCode = ''; + this._errorMessage = 'GPT call succeeded but invalid html; please try again.'; } - } - } - } - async lockInkStroke() { - console.log('hello'); - console.log( - DocListCast(this.Document.data) - .filter(doc => doc.title === 'rectangle') - .map(doc => doc.x) + }) ); - if (this.Document.data instanceof List) { - const docArray: Doc[] = DocListCast(this.Document.data); - const rectangleArray = docArray.filter(doc => doc.title === 'rectangle' || doc.title === 'circle'); - if (rectangleArray[0]) { - console.log(rectangleArray[0].x); - } - const lineArray = docArray.filter(doc => doc.title === 'line' || doc.title === 'stroke'); - const timeoutPromise = () => - new Promise(resolve => { - setTimeout(resolve, 0); - }); - await timeoutPromise(); - const inkStrokeArray = lineArray.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())).filter(inkView => inkView?.ComponentView instanceof InkingStroke); - const inkingStrokeArray = inkStrokeArray.map(stroke => stroke?.ComponentView); - for (let j = 0; j < lineArray.length; j++) { - const inkScaleX = (inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkScaleX; - const inkScaleY = (inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkScaleY; - const inkStrokeXArray = (inkingStrokeArray[j] as InkingStroke) - ?.inkScaledData() - .inkData.map(coord => coord.X) - .map(doc => doc * inkScaleX); - const inkStrokeYArray = (inkingStrokeArray[j] as InkingStroke) - ?.inkScaledData() - .inkData.map(coord => coord.Y) - .map(doc => doc * inkScaleY); - // need to minX and minY to since the inkStroke.x and.y is not relative to the doc. so I have to do some calcluations - const minX: number = Math.min(...inkStrokeXArray); - const minY: number = Math.min(...inkStrokeYArray); - const startX = inkStrokeXArray[0] - minX + (lineArray[j]?.x as number); - const startY = inkStrokeYArray[0] - minY + (lineArray[j]?.y as number); - const endX = inkStrokeXArray[inkStrokeXArray.length - 1] - minX + (lineArray[j].x as number); - const endY = inkStrokeYArray[inkStrokeYArray.length - 1] - minY + (lineArray[j].y as number); - let closestStartRect: Doc = lineArray[0]; - let closestStartDistance = 9999999; - let closestEndRect: Doc = lineArray[0]; - let closestEndDistance = 9999999; - rectangleArray.forEach(rectangle => { - const midPoint = this.getMidPoint(rectangle); - if (this.euclideanDistance(midPoint.X, midPoint.Y, startX, startY) < closestStartDistance && this.euclideanDistance(midPoint.X, midPoint.Y, endX, endY) < closestEndDistance) { - if (this.euclideanDistance(midPoint.X, midPoint.Y, startX, startY) < this.euclideanDistance(midPoint.X, midPoint.Y, endX, endY)) { - closestStartDistance = this.euclideanDistance(midPoint.X, midPoint.Y, startX, startY); - closestStartRect = rectangle; - } else { - closestEndDistance = this.euclideanDistance(midPoint.X, midPoint.Y, startX, startY); - closestEndRect = rectangle; - } - } else if (this.euclideanDistance(midPoint.X, midPoint.Y, startX, startY) < closestStartDistance) { - closestStartDistance = this.euclideanDistance(midPoint.X, midPoint.Y, startX, startY); - closestStartRect = rectangle; - } else if (this.euclideanDistance(midPoint.X, midPoint.Y, endX, endY) < closestEndDistance) { - closestEndDistance = this.euclideanDistance(midPoint.X, midPoint.Y, startX, startY); - closestEndRect = rectangle; - } - }); - const inkToDelete: Doc = lineArray[j]; - if ( - typeof closestStartRect.x === 'number' && - typeof closestStartRect.y === 'number' && - typeof closestEndRect.x === 'number' && - typeof closestEndRect.y === 'number' && - typeof closestStartRect.width === 'number' && - typeof closestStartRect.height === 'number' && - typeof closestEndRect.height === 'number' && - typeof closestEndRect.width === 'number' - ) { - const points: PointData[] = [ - { X: closestStartRect.x, Y: closestStartRect.y }, - { X: closestStartRect.x, Y: closestStartRect.y }, - { X: closestEndRect.x, Y: closestEndRect.y }, - { X: closestEndRect.x, Y: closestEndRect.y }, - ]; - let inkX = 0; - let inkY = 0; - if (this.getMidPoint(closestEndRect).X < this.getMidPoint(closestStartRect).X) { - inkX = this.getMidPoint(closestEndRect).X; - } else { - inkX = this.getMidPoint(closestStartRect).X; - } - if (this.getMidPoint(closestEndRect).Y < this.getMidPoint(closestStartRect).Y) { - inkY = this.getMidPoint(closestEndRect).Y; - } else { - inkY = this.getMidPoint(closestStartRect).Y; - } - const newInkDoc = Docs.Create.AudioDocument(''); // get rid of this!! - // const newInkDoc:Doc=Docs.Create.InkDocument( - // points, - // { title: 'stroke', - // x: inkX, - // y: inkY, - // strokeWidth: Math.abs(closestEndRect.x+closestEndRect.width/2-closestStartRect.x-closestStartRect.width/2), - // _height: Math.abs(closestEndRect.y+closestEndRect.height/2-closestStartRect.y-closestStartRect.height/2), - // stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore - // 1) + }); + isValidCode = (html: string) => (html ? true : false); + removeWords = (inputStrIn: string) => inputStrIn.replace('```mermaid', '').replace(`^@mermaids`, '').replace('```', ''); - DocumentManager.Instance.AddViewRenderedCb(this.Document, docViewForYourCollection => { - if (docViewForYourCollection && docViewForYourCollection.ComponentView) { - if (docViewForYourCollection.ComponentView.addDocument && docViewForYourCollection.ComponentView.removeDocument) { - docViewForYourCollection.ComponentView?.removeDocument(inkToDelete); - docViewForYourCollection.ComponentView?.addDocument(newInkDoc); + // method to convert the drawings on collection node side the mermaid code + convertDrawingToMermaidCode = async (docArray: Doc[]) => { + const rectangleArray = docArray.filter(doc => doc.title === Gestures.Rectangle || doc.title === Gestures.Circle); + const lineArray = docArray.filter(doc => doc.title === Gestures.Line || doc.title === Gestures.Stroke); + const textArray = docArray.filter(doc => doc.type === DocumentType.RTF); + await new Promise(resolve => setTimeout(resolve)); + const inkStrokeArray = lineArray.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())).filter(inkView => inkView?.ComponentView instanceof InkingStroke); + if (inkStrokeArray[0] && inkStrokeArray.length === lineArray.length) { + let mermaidCode = `graph TD \n`; + const inkingStrokeArray = inkStrokeArray.map(stroke => stroke?.ComponentView as InkingStroke).filter(stroke => stroke); + for (const rectangle of rectangleArray) { + for (const inkStroke of inkingStrokeArray) { + const inkData = inkStroke.inkScaledData(); + const { inkScaleX, inkScaleY } = inkData; + const inkStrokeXArray = inkData.inkData.map(coord => coord.X * inkScaleX); + const inkStrokeYArray = inkData.inkData.map(coord => coord.Y * inkScaleY); + // need to minX and minY to since the inkStroke.x and.y is not relative to the doc. so I have to do some calcluations + const offX = Math.min(...inkStrokeXArray) - NumCast(inkStroke.Document.x); + const offY = Math.min(...inkStrokeYArray) - NumCast(inkStroke.Document.y); - // const bruh2= DocListCast(this.Document.data).filter(doc => doc.title === 'line' || doc.title === 'stroke').map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())).filter(inkView => inkView?.ComponentView instanceof InkingStroke).map(stroke => stroke?.ComponentView); - // console.log(bruh2) - // console.log((bruh2[0] as InkingStroke)?.inkScaledData()) + const startX = inkStrokeXArray[0] - offX; + const startY = inkStrokeYArray[0] - offY; + const endX = inkStrokeXArray.lastElement() - offX; + const endY = inkStrokeYArray.lastElement() - offY; + if (DiagramBox.isPointInBox(rectangle, [startX, startY])) { + for (const rectangle2 of rectangleArray) { + if (DiagramBox.isPointInBox(rectangle2, [endX, endY])) { + const linkedDocs = LinkManager.Instance.getAllRelatedLinks(inkStroke.Document).map(d => DocCast(LinkManager.getOppositeAnchor(d, inkStroke.Document))); + const linkedDocText = Cast(linkedDocs[0]?.text, RichTextField, null)?.Text; + const linkText = linkedDocText ? `|${linkedDocText}|` : ''; + mermaidCode += ' ' + Math.abs(NumCast(rectangle.x)) + this.getTextInBox(rectangle, textArray) + '-->' + linkText + Math.abs(NumCast(rectangle2.x)) + this.getTextInBox(rectangle2, textArray) + `\n`; } } - }); - } - } - } - } - getMidPoint(rectangle: Doc) { - let midPoint = { X: 0, Y: 0 }; - if (typeof rectangle.x === 'number' && typeof rectangle.width === 'number' && typeof rectangle.y === 'number' && typeof rectangle.height === 'number') { - midPoint = { X: rectangle.x + rectangle.width / 2, Y: rectangle.y + rectangle.height / 2 }; - } - return midPoint; - } - euclideanDistance(x1: number, y1: number, x2: number, y2: number): number { - const deltaX = x2 - x1; - const deltaY = y2 - y1; - return Math.sqrt(deltaX * deltaX + deltaY * deltaY); - } - // isLeftRightDiagram = (docArray: Doc[]) => { - // const filteredDocs = docArray.filter(doc => doc.title === 'rectangle' || doc.title === 'circle'); - // const xDoc = filteredDocs.map(doc => doc.x) as number[]; - // const minX = Math.min(...xDoc); - // const xWidthDoc = filteredDocs.map(doc => { - // if (typeof doc.x === 'number' && typeof doc.width === 'number') { - // return doc.x + doc.width; - // } - // }) as number[]; - // const maxX = Math.max(...xWidthDoc); - // const yDoc = filteredDocs.map(doc => doc.y) as number[]; - // const minY = Math.min(...yDoc); - // const yHeightDoc = filteredDocs.map(doc => { - // if (typeof doc.x === 'number' && typeof doc.width === 'number') { - // return doc.x + doc.width; - // } - // }) as number[]; - // const maxY = Math.max(...yHeightDoc); - // if (maxX - minX > maxY - minY) { - // return true; - // } - // return false; - // }; - getTextInBox = (box: Doc, richTextArray: Doc[]): string => { - for (let i = 0; i < richTextArray.length; i++) { - const textDoc = richTextArray[i]; - if (typeof textDoc.x === 'number' && typeof textDoc.y === 'number' && typeof box.x === 'number' && typeof box.height === 'number' && typeof box.width === 'number' && typeof box.y === 'number') { - if (textDoc.x > box.x && textDoc.x < box.x + box.width && textDoc.y > box.y && textDoc.y < box.y + box.height) { - if (box.title === 'rectangle') { - return '(' + ((textDoc.text as RichTextField)?.Text ?? '') + ')'; - } - if (box.title === 'circle') { - return '((' + ((textDoc.text as RichTextField)?.Text ?? '') + '))'; } } + this.setMermaidCode(mermaidCode); } } - return '( )'; - }; - isPointInBox = (box: Doc, line: number[]): boolean => { - if (typeof line[0] === 'number' && typeof box.x === 'number' && typeof box.width === 'number' && typeof box.height === 'number' && typeof box.y === 'number' && typeof line[1] === 'number') { - return line[0] < box.x + box.width && line[0] > box.x && line[1] > box.y && line[1] < box.y + box.height; - } - return false; - }; - drawingButton = () => { - this.Document.menuState = 'drawing'; }; - gptButton = () => { - this.Document.menuState = 'gpt'; - }; - mermaidButton = () => { - this.Document.menuState = 'mermaidCode'; - }; - optionButton = () => { - this.Document.menuState = 'option'; - }; - renderOption(): React.ReactNode { - return ( - <div className="buttonCollections"> - <button type="button" onClick={this.drawingButton}> - Drawing - Create diagram from ink drawing - </button> - <button type="button" onClick={this.gptButton}> - GPT - Generate diagram with AI prompt - </button> - <button type="button" onClick={this.mermaidButton}> - Mermaid Editor - Create diagram with mermaid code - </button> - </div> - ); - } - renderDrawing(): React.ReactNode { - return ( - <div ref={this._dragRef} className="DiagramBox-wrapper"> - <div className="content"> - <div className="topBar"> - <button className="backButtonDrawing" type="button" onClick={this.optionButton}> - Back - </button> - {!this.Document.mermaidCode && <p>Click the red pen icon to flip onto the collection side and draw a diagram with ink</p>} - </div> - <div id={'dashDiv' + this.Document.title} className="diagramBox" /> - </div> - </div> - ); - } - renderGpt(): React.ReactNode { - return ( - <div ref={this._dragRef} className="DiagramBox-wrapper"> - <div className="content"> - <div className="search-bar"> - <button className="backButton" type="button" onClick={this.optionButton}> - Back - </button> - <textarea value={this.inputValue} placeholder="Enter GPT prompt" onChange={this.handleInputChange} onInput={e => this.autoResize(e.target as HTMLTextAreaElement)} /> - <div className="rightButtons"> - <button className="generateButton" type="button" onClick={this.handleRenderClick}> - Generate - </button> - <button className="convertButton" type="button" onClick={this.handleConvertButton}> - Edit - </button> - </div> - </div> - {this.mermaidCode ? ( - <div id={'dashDiv' + this.Document.title} className="diagramBox" /> - ) : ( - <div>{this.loading ? <div className="loading-circle" /> : <div>{this.errorMessage ? this.errorMessage : 'Insert prompt to generate diagram'}</div>}</div> - )} - </div> - </div> - ); - } - handleConvertButton = () => { - this.Document.menuState = 'mermaidCode'; - if (typeof this.Document.gptMermaidCode === 'string') { - this.createInputValue = this.removeFirstEmptyLine(this.Document.gptMermaidCode); - console.log(this.Document.gptMermaidCode); - this.renderMermaidAsync(this.Document.gptMermaidCode); + getTextInBox = (box: Doc, richTextArray: Doc[]) => { + for (const textDoc of richTextArray) { + if (DiagramBox.isPointInBox(box, [NumCast(textDoc.x), NumCast(textDoc.y)])) { + switch (box.title) { + case Gestures.Rectangle: return '(' + ((textDoc.text as RichTextField)?.Text ?? '') + ')'; + case Gestures.Circle: return '((' + ((textDoc.text as RichTextField)?.Text ?? '') + '))'; + default: + } // prettier-ignore + } } + return '( )'; }; - removeFirstEmptyLine(input: string): string { - const lines = input.split('\n'); - let emptyLineRemoved = false; - const resultLines = lines.filter(line => { - if (!emptyLineRemoved && line.trim() === '') { - emptyLineRemoved = true; - return false; - } - return true; - }); - return resultLines.join('\n'); - } - renderMermaidCode(): React.ReactNode { + render() { return ( - <div ref={this._dragRef} className="DiagramBox-wrapper"> - <div className="contentCode"> - <div className="search-bar"> - <button className="backButton" type="button" onClick={this.optionButton}> - Back - </button> - <button className="exampleButton" type="button" onClick={this.exampleButton}> - Examples - </button> - </div> - {this.isExampleMenuOpen && ( - <div className="exampleButtonContainer"> - <button type="button" onClick={this.flowButton}> - Flow - </button> - <button type="button" onClick={this.pieButton}> - Pie - </button> - <button type="button" onClick={this.timelineButton}> - Timeline - </button> - <button type="button" onClick={this.classButton}> - Class - </button> - <button type="button" onClick={this.mindmapButton}> - Mindmap - </button> + <div className="DIYNodeBox"> + <div className="DIYNodeBox-searchbar"> + <input type="text" value={this._inputValue} onKeyDown={action(e => e.key === 'Enter' && this.generateMermaidCode())} onChange={action(e => (this._inputValue = e.target.value))} /> + <button type="button" onClick={this.generateMermaidCode}> + Gen + </button> + <input type="checkbox" onClick={action(() => (this._showCode = !this._showCode))} /> + </div> + <div className="DIYNodeBox-content"> + {this._showCode ? ( + <FormattedTextBox {...this._props} fieldKey="text" /> + ) : this._generating ? ( + <div className="loading-circle" /> + ) : ( + <div className="diagramBox" ref={r => r && this.renderMermaidAsync.call(this, this.removeWords(this.mermaidcode), r)}> + {this._errorMessage || 'Type a prompt to generate a diagram'} </div> )} - <textarea value={this.createInputValue} placeholder="Enter Mermaid Code" onChange={this.handleInputChangeEditor} /> - <div id={'dashDiv' + this.Document.title} className="diagramBox" /> </div> </div> ); @@ -583,14 +226,12 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { C -->|One| D[Laptop] C -->|Two| E[iPhone] C -->|Three| F[fa:fa-car Car]`; - this.renderMermaidAsync(this.createInputValue); }; pieButton = () => { this.createInputValue = `pie title Pets adopted by volunteers "Dogs" : 386 "Cats" : 85 "Rats" : 15`; - this.renderMermaidAsync(this.createInputValue); }; timelineButton = () => { this.createInputValue = `gantt @@ -602,7 +243,6 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { section Another Task in sec :2014-01-12 , 12d another task : 24d`; - this.renderMermaidAsync(this.createInputValue); }; classButton = () => { this.createInputValue = `classDiagram @@ -626,7 +266,6 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { +bool is_wild +run() }`; - this.renderMermaidAsync(this.createInputValue); }; mindmapButton = () => { this.createInputValue = `mindmap @@ -646,12 +285,10 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { Tools Pen and paper Mermaid`; - this.renderMermaidAsync(this.createInputValue); }; handleInputChangeEditor = (e: React.ChangeEvent<HTMLTextAreaElement>) => { if (typeof e.target.value === 'string') { this.createInputValue = e.target.value; - this.renderMermaidAsync(e.target.value); } }; removeWhitespace(str: string): string { @@ -686,17 +323,17 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { Midterm Exams : des15, 2025-03-25, 2025-03-30 Final Exams : des16, 2025-05-10, 2025-05-15 Graduation : des17, 2025-05-20, 2025-05-21`; - render() { - this.switchRenderDiv(); - return ( - <div ref={this._ref} className="DiagramBox"> - {this.renderDiv} - </div> - ); - } } Docs.Prototypes.TemplateMap.set(DocumentType.DIAGRAM, { - layout: { view: DiagramBox, dataField: 'dadta' }, - options: { _height: 700, _width: 700, _layout_fitWidth: false, _layout_nativeDimEditable: true, _layout_reflowVertical: true, waitForDoubleClickToClick: 'always', _layout_reflowHorizontal: true, systemIcon: 'BsGlobe' }, + layout: { view: DiagramBox, dataField: 'data' }, + options: { + _height: 300, // + _layout_fitWidth: true, + _layout_nativeDimEditable: true, + _layout_reflowVertical: true, + _layout_reflowHorizontal: true, + waitForDoubleClickToClick: 'always', + systemIcon: 'BsGlobe', + }, }); |
