From b5944e87f9d4f3149161de4de0d76db486461c76 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Wed, 30 Oct 2024 19:38:18 -0400 Subject: integrated image stickers with palette --- src/client/documents/Documents.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 5f2a592ae..b79e8930b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1044,7 +1044,7 @@ export namespace Docs { } export function AnnoPaletteDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.ANNOPALETTE), new List([Doc.MyAnnos]), { ...(options || {}) }); + return InstanceFromProto(Prototypes.get(DocumentType.ANNOPALETTE), new List([Doc.MyStickers]), { ...(options || {}) }); } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { -- cgit v1.2.3-70-g09d2 From 8680d1c31d4f835663c070f5b8cef57254e75e28 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 12 Nov 2024 16:42:35 -0500 Subject: made equation background same as text. fixed dflt stroke/link width to both be 1. made function plot axis ranges get saved to Doc. marked equation->function links as being svgs. fixed initial size of equation boxes.. --- src/client/documents/DocUtils.ts | 3 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/PropertiesView.tsx | 2 +- src/client/views/StyleProvider.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 7 +--- src/client/views/global/globalScripts.ts | 2 +- src/client/views/linking/LinkMenuItem.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 3 +- src/client/views/nodes/EquationBox.scss | 2 - src/client/views/nodes/EquationBox.tsx | 2 +- src/client/views/nodes/FunctionPlotBox.tsx | 43 +++++++++++++++++++--- src/client/views/nodes/LinkBox.tsx | 2 +- 12 files changed, 50 insertions(+), 22 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 067b9c5e0..741d3a10e 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -168,7 +168,7 @@ export namespace DocUtils { return rangeFilteredDocs; } - export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string }, id?: string, showPopup?: number[]) { + export function MakeLink(source: Doc, target: Doc, linkSettings: { layout_isSvg?: boolean; link_relationship?: string; link_description?: string }, id?: string, showPopup?: number[]) { if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; if (target.doc === Doc.UserDoc()) return undefined; @@ -220,6 +220,7 @@ export namespace DocUtils { link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined, link_relationship: linkSettings.link_relationship, link_description: linkSettings.link_description, + layout_isSvg: linkSettings.layout_isSvg, x: ComputedField.MakeFunction(`((this.${a}?.x||0)+(this.${b}?.x||0))/2`) as unknown as number, // x can accept functions even though type says it can't y: ComputedField.MakeFunction(`((this.${a}?.y||0)+(this.${b}?.y||0))/2`) as unknown as number, // y can accept functions even though type says it can't link_autoMoveAnchors: true, diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index fe23fe062..7b2a7ae0f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1142,7 +1142,7 @@ pie title Minerals in my tap water } // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function activeInkTool() { return Doc.ActiveTool=== InkTool.Ink || DocumentView.Selected().some(dv => dv.layoutDoc.layout_isSvg); }, "is a pen tool or an ink stroke active"); +ScriptingGlobals.add(function activeInkTool() { return Doc.ActiveTool=== InkTool.Ink || DocumentView.Selected().some(dv => dv.layoutDoc.layout_isSvg); }, "is a pen tool or an ink stroke active"); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function NotRadiusEraser() { return Doc.ActiveTool !== InkTool.Eraser || Doc.ActiveEraser !== InkEraserTool.Radius; }, "is the active tool anything but the radius eraser"); // eslint-disable-next-line prefer-arrow-callback diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 10c2a9898..2bd8bc98a 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -830,7 +830,7 @@ export class PropertiesView extends ObservableReactComponent { doc[DocData].stroke_width = Math.round(value * 100) / 100; }); } // prettier-ignore @computed get hgtInput() { diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 3a5f57908..0a9da1237 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -257,7 +257,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt SetactiveHideTextLabels(value? false : true), }], [ InkProperty.StrokeWidth, { - checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()), + checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width, 1) : ActiveInkWidth()), setInk: (doc: Doc) => { doc[DocData].stroke_width = NumCast(value); }, setMode: () => SetActiveInkWidth(value.toString()), }], diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index b24fca8e2..1c92c3b0d 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -90,7 +90,7 @@ export class LinkMenuItem extends ObservableReactComponent { moveEv => { const dragData = new DragManager.DocumentDragData([this._props.linkDoc], dropActionType.embed); dragData.dropPropertiesToRemove = ['hidden']; - DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y, undefined, e => (this._props.linkDoc._layout_isSvg = true)); + DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y, undefined, () => (this._props.linkDoc._layout_isSvg = true)); return true; }, emptyFunction, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 30f9e6363..92e98db87 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -459,10 +459,9 @@ export class DocumentViewInternal extends DocComponent span { width: fit-content; } diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index 472fa56a0..34b46381d 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -81,7 +81,7 @@ export class EquationBox extends ViewBoxBaseComponent() { _height: 300, backgroundColor: 'white', }); - const link = DocUtils.MakeLink(this.Document, graph, { link_relationship: 'function', link_description: 'input' }); + const link = DocUtils.MakeLink(this.Document, graph, { layout_isSvg: true, link_relationship: 'function', link_description: 'input' }); this._props.addDocument?.(graph); link && this._props.addDocument?.(link); e.stopPropagation(); diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index 6b439cd64..91c351895 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -1,5 +1,5 @@ import functionPlot, { Chart } from 'function-plot'; -import { computed, makeObservable, reaction } from 'mobx'; +import { action, computed, makeObservable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../../fields/Doc'; @@ -15,6 +15,8 @@ import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { PinDocView, PinProps } from '../PinFuncs'; import { FieldView, FieldViewProps } from './FieldView'; +import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; @observer export class FunctionPlotBox extends ViewBoxAnnotatableComponent() { @@ -65,18 +67,24 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent ); return funcs; } + computeYScale = (width: number, height: number, xScale: number[]) => { + const xDiff = xScale[1] - xScale[0]; + const yDiff = (height * xDiff) / width; + return [-yDiff / 2, yDiff / 2]; + }; createGraph = (ele?: HTMLDivElement) => { this._plotEle = ele || this._plotEle; const width = this._props.PanelWidth(); const height = this._props.PanelHeight(); + const xrange = Cast(this.layoutDoc.xRange, listSpec('number'), [-10, 10]); try { this._plotEle?.children.length && this._plotEle.removeChild(this._plotEle.children[0]); this._plot = functionPlot({ target: '#' + this._plotEle?.id, width, height, - xAxis: { domain: Cast(this.layoutDoc.xRange, listSpec('number'), [-10, 10]) }, - yAxis: { domain: Cast(this.layoutDoc.yRange, listSpec('number'), [-1, 9]) }, + xAxis: { domain: xrange }, + yAxis: { domain: this.computeYScale(width, height, xrange) }, // Cast(this.layoutDoc.yRange, listSpec('number'), [-1, 9]) }, grid: true, data: this.graphFuncs.map(fn => ({ fn, @@ -94,7 +102,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => { // const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc); if (res) { - const link = DocUtils.MakeLink(doc, this.Document, { link_relationship: 'function', link_description: 'input' }); + const link = DocUtils.MakeLink(doc, this.Document, { layout_isSvg: true, link_relationship: 'function', link_description: 'input' }); link && this._props.addDocument?.(link); } return res; @@ -115,7 +123,32 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent // if (this.layout_autoHeight) this.tryUpdateScrollHeight(); }; @computed get theGraph() { - return
r && this.createGraph(r)} style={{ position: 'absolute', width: '100%', height: '100%' }} onPointerDown={e => e.stopPropagation()} />; + return ( +
r && this.createGraph(r)} + style={{ position: 'absolute', width: '100%', height: '100%' }} + onPointerDown={e => { + e.stopPropagation(); + setupMoveUpEvents( + this, + e, + returnFalse, + action(() => { + if (this._plot?.options.xAxis?.domain) { + this.Document.xRange = new List(this._plot.options.xAxis.domain); + } + if (this._plot?.options.yAxis?.domain) { + this.Document.yRange = new List(this._plot.options.yAxis.domain); + } + }), + emptyFunction, + false, + false + ); + }} + /> + ); } render() { TraceMobx(); diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 4d9d2460e..c62a8afb1 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -160,7 +160,7 @@ export class LinkBox extends ViewBoxBaseComponent() { // eslint-disable-next-line camelcase const { stroke_markerScale: strokeMarkerScale, stroke_width: strokeRawWidth, stroke_startMarker: strokeStartMarker, stroke_endMarker: strokeEndMarker, stroke_dash: strokeDash } = this.Document; - const strokeWidth = NumCast(strokeRawWidth, 4); + const strokeWidth = NumCast(strokeRawWidth, 1); const linkDesc = StrCast(this.dataDoc.link_description) || ' '; const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : ''); return ( -- cgit v1.2.3-70-g09d2 From 6a6215a447c3104f5fea8a813270b8c1fc39ad75 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 12 Nov 2024 18:03:12 -0500 Subject: fixed resizing height to 0 to autoResize. fixed setting background color default for equations (and everything else) to textBackgroundColor from user doc. Added a math pseudo-font to trigger entering equations instead of rich text. --- src/client/documents/DocUtils.ts | 70 +++++++++++++++++++++----------- src/client/util/CurrentUserUtils.ts | 4 +- src/client/views/DocumentDecorations.tsx | 6 +-- src/client/views/nodes/EquationBox.tsx | 14 +++++-- 4 files changed, 63 insertions(+), 31 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 741d3a10e..c466344b3 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -358,6 +358,16 @@ export namespace DocUtils { return ctor ? ctor(path, overwriteDoc ? { ...options, title: StrCast(overwriteDoc.title, path) } : options, overwriteDoc) : undefined; } + /** + * Adds items to the doc creator (':') context menu for creating each document type + * @param docTextAdder + * @param docAdder + * @param x + * @param y + * @param simpleMenu + * @param pivotField + * @param pivotValue + */ export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string | number | boolean): void { const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data) .filter(btnDoc => !btnDoc.hidden) @@ -371,6 +381,7 @@ export namespace DocUtils { newDoc.author = ClientUtils.CurrentUserEmail(); newDoc.x = x; newDoc.y = y; + newDoc[DocData].backgroundColor = Doc.UserDoc().textBackgroundColor; Doc.SetSelectOnLoad(newDoc); if (pivotField) { newDoc[pivotField] = pivotValue; @@ -675,30 +686,43 @@ export namespace DocUtils { export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) { const defaultTextTemplate = DocCast(Doc.UserDoc().defaultTextLayout); - const tbox = Docs.Create.TextDocument('', { - annotationOn, - backgroundColor, - x, - y, - title, - ...(defaultTextTemplate - ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance - : { - _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200, - _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35, - _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), - _layout_fitWidth: true, - _layout_autoHeight: true, - backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor), - text_fitBox: BoolCast(Doc.UserDoc().fitBox), - text_align: StrCast(Doc.UserDoc().textAlign), + const tbox = + StrCast(Doc.UserDoc().fontFamily) === 'Math' + ? Docs.Create.EquationDocument('', { + // + annotationOn, + backgroundColor: backgroundColor ?? StrCast(Doc.UserDoc().textBackgroundColor), + x, + y, + title, text_fontColor: StrCast(Doc.UserDoc().fontColor), - text_fontFamily: StrCast(Doc.UserDoc().fontFamily), - text_fontWeight: StrCast(Doc.UserDoc().fontWeight), - text_fontStyle: StrCast(Doc.UserDoc().fontStyle), - text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration), - }), - }); + _width: 35, + _height: 35, + }) + : Docs.Create.TextDocument('', { + annotationOn, + backgroundColor, + x, + y, + title, + ...(defaultTextTemplate + ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance + : { + _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200, + _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35, + _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), + _layout_fitWidth: true, + _layout_autoHeight: true, + backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor), + text_fitBox: BoolCast(Doc.UserDoc().fitBox), + text_align: StrCast(Doc.UserDoc().textAlign), + text_fontColor: StrCast(Doc.UserDoc().fontColor), + text_fontFamily: StrCast(Doc.UserDoc().fontFamily), + text_fontWeight: StrCast(Doc.UserDoc().fontWeight), + text_fontStyle: StrCast(Doc.UserDoc().fontStyle), + text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration), + }), + }); if (defaultTextTemplate) { tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 7b2a7ae0f..c01f57a59 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -372,7 +372,7 @@ pie title Minerals in my tap water {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, {key: "Flashcard", creator: opts => Docs.Create.FlashcardDocument("", undefined, undefined, opts),opts: { _width: 300, _height: 300}}, {key: "Image", creator: opts => Docs.Create.ImageDocument("", opts), opts: { _width: 400, _height:400 }}, - {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }}, + {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 35, _height: 35, }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }}, @@ -731,7 +731,7 @@ pie title Minerals in my tap water static textTools():Button[] { return [ { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, - btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, + btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text", "Math"]) }, { title: " Size", toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 9 }, { title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'} }, { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'} }, diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 492c2bda1..07e362672 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -506,7 +506,7 @@ export class DocumentDecorations extends ObservableReactComponent { - const [w, h] = [this.Bounds.r - this.Bounds.x, this.Bounds.b - this.Bounds.y]; + const [w, h] = [Math.max(1, this.Bounds.r - this.Bounds.x), Math.max(1, this.Bounds.b - this.Bounds.y)]; const [moveX, moveY] = [thisPt.x - this._snapPt.x, thisPt.y - this._snapPt.y]; switch (dragHdl) { case 'topLeft': return { scale: { x: 1 - moveX / w, y: 1 -moveY / h }, refPt: [this.Bounds.r, this.Bounds.b] }; @@ -566,8 +566,8 @@ export class DocumentDecorations extends ObservableReactComponent() { @@ -56,6 +57,8 @@ export class EquationBox extends ViewBoxBaseComponent() { { fireImmediately: true } ); } + @computed get fontSize() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) as string; } // prettier-ignore + @computed get fontColor() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor) as string; } // prettier-ignore @action keyPressed = (e: KeyboardEvent) => { @@ -68,6 +71,9 @@ export class EquationBox extends ViewBoxBaseComponent() { _height: 25, x: NumCast(this.layoutDoc.x), y: NumCast(this.layoutDoc.y) + _height + 10, + backgroundColor: StrCast(this.Document.backgroundColor), + color: StrCast(this.Document.color), + fontSize: this.fontSize, }); Doc.SetSelectOnLoad(nextEq); this._props.addDocument?.(nextEq); @@ -118,10 +124,12 @@ export class EquationBox extends ViewBoxBaseComponent() { className="equationBox-cont" onPointerDown={e => !e.ctrlKey && e.stopPropagation()} style={{ + minWidth: `${100 / scale}%`, transform: `scale(${scale})`, height: `${100 / scale}%`, pointerEvents: !this._props.isSelected() ? 'none' : undefined, - fontSize: StrCast(this.Document._text_fontSize), + fontSize: this.fontSize, + color: this.fontColor, }} onKeyDown={e => e.stopPropagation()}> @@ -132,5 +140,5 @@ export class EquationBox extends ViewBoxBaseComponent() { Docs.Prototypes.TemplateMap.set(DocumentType.EQUATION, { layout: { view: EquationBox, dataField: 'text' }, - options: { acl: '', fontSize: '14px', _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, // systemIcon: 'BsSuperscript' + BsSubscript + options: { acl: '', fontSize: '14px', _nativeWidth: 35, _nativeHeight: 35, _layout_reflowHorizontal: false, _layout_reflowVertical: false, _layout_nativeDimEditable: false, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, // systemIcon: 'BsSuperscript' + BsSubscript }); -- cgit v1.2.3-70-g09d2 From d57fdc07fb88282903157b414c4a0886ddaf8bc6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 12 Nov 2024 21:06:42 -0500 Subject: updated equationBox to support margins/padding like text. fixed initial undo of newly typed equation. --- src/client/documents/DocUtils.ts | 8 +++++-- src/client/documents/Documents.ts | 6 +++-- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/PropertiesView.tsx | 2 +- src/client/views/nodes/EquationBox.tsx | 42 ++++++++++++++++++++++++++-------- 5 files changed, 44 insertions(+), 16 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index c466344b3..d11a3e235 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -696,8 +696,12 @@ export namespace DocUtils { y, title, text_fontColor: StrCast(Doc.UserDoc().fontColor), - _width: 35, - _height: 35, + _width: 50, + _height: 50, + _yMargin: 10, + _xMargin: 10, + nativeWidth: 40, + nativeHeight: 40, }) : Docs.Create.TextDocument('', { annotationOn, diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d898fe0c5..6828a1929 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -197,8 +197,10 @@ export class DocumentOptions { data_nativeWidth?: NUMt = new NumInfo('native width of data field contents (e.g., the pixel width of an image)', false); data_nativeHeight?: NUMt = new NumInfo('native height of data field contents (e.g., the pixel height of an image)', false); linearBtnWidth?: NUMt = new NumInfo('unexpanded width of a linear menu button (button "width" changes when it expands)', false); - _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)', false); - _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)', false); + _nativeWidth?: NUMt = new NumInfo('Deprecated: use nativeWidth. native width of document contents (e.g., the pixel width of an image)', false); + _nativeHeight?: NUMt = new NumInfo('Deprecated: use nativeHeight. native height of document contents (e.g., the pixel height of an image)', false); + nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)', false); + nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)', false); acl?: STRt = new StrInfo('unused except as a display category in KeyValueBox'); acl_Guest?: STRt = new StrInfo("permissions granted to users logged in as 'guest' (either view, or private)"); // public permissions diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c01f57a59..49a4a981a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -372,7 +372,7 @@ pie title Minerals in my tap water {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, {key: "Flashcard", creator: opts => Docs.Create.FlashcardDocument("", undefined, undefined, opts),opts: { _width: 300, _height: 300}}, {key: "Image", creator: opts => Docs.Create.ImageDocument("", opts), opts: { _width: 400, _height:400 }}, - {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 35, _height: 35, }}, + {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 50, _height: 50, nativeWidth: 40, nativeHeight: 40, _xMargin: 10, _yMargin: 10}}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }}, diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 2bd8bc98a..1158d93e9 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -141,7 +141,7 @@ export class PropertiesView extends ObservableReactComponent() { @@ -68,7 +69,7 @@ export class EquationBox extends ViewBoxBaseComponent() { const nextEq = Docs.Create.EquationDocument(e.shiftKey ? StrCast(this.dataDoc.text) : 'x', { title: '# math', _width, - _height: 25, + _height: 20, x: NumCast(this.layoutDoc.x), y: NumCast(this.layoutDoc.y) + _height + 10, backgroundColor: StrCast(this.Document.backgroundColor), @@ -102,27 +103,32 @@ export class EquationBox extends ViewBoxBaseComponent() { updateSize = () => { const style = this._ref.current?.element.current && getComputedStyle(this._ref.current.element.current); if (style?.width.endsWith('px') && style?.height.endsWith('px')) { + const mathWidth = Math.max(35, NumCast(this.layoutDoc.xMargin) * 2 + Number(style.width.replace('px', ''))); + const mathHeight = Math.max(20, NumCast(this.layoutDoc.yMargin) * 2 + Number(style.height.replace('px', ''))); if (this.layoutDoc._nativeWidth) { // if equation has been scaled then editing the expression must also edit the native dimensions to keep the aspect ratio const prevNwidth = NumCast(this.layoutDoc._nativeWidth); - const newNwidth = (this.layoutDoc._nativeWidth = Math.max(35, Number(style.width.replace('px', '')))); - const newNheight = (this.layoutDoc._nativeHeight = Math.max(25, Number(style.height.replace('px', '')))); - this.layoutDoc._width = (NumCast(this.layoutDoc._width) * NumCast(this.layoutDoc._nativeWidth)) / prevNwidth; - this.layoutDoc._height = (NumCast(this.layoutDoc._width) * newNheight) / newNwidth; + this.layoutDoc._nativeWidth = mathWidth; + this.layoutDoc._nativeHeight = mathHeight; + this.layoutDoc._width = mathWidth * (NumCast(this.layoutDoc._width) / prevNwidth); + this.layoutDoc._height = mathHeight * (NumCast(this.layoutDoc._width) / mathWidth); } else { - this.layoutDoc._width = Math.max(35, Number(style.width.replace('px', ''))); - this.layoutDoc._height = Math.max(25, Number(style.height.replace('px', ''))); + this.layoutDoc._width = mathWidth; + this.layoutDoc._height = mathHeight; } } }; render() { TraceMobx(); - const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1); + const scale = this._props.NativeDimScaling?.() || 1; return (
this.updateSize()} className="equationBox-cont" onPointerDown={e => !e.ctrlKey && e.stopPropagation()} + onBlur={() => { + FormattedTextBox.LiveTextUndo?.end(); + }} style={{ minWidth: `${100 / scale}%`, transform: `scale(${scale})`, @@ -130,6 +136,10 @@ export class EquationBox extends ViewBoxBaseComponent() { pointerEvents: !this._props.isSelected() ? 'none' : undefined, fontSize: this.fontSize, color: this.fontColor, + paddingLeft: NumCast(this.layoutDoc.xMargin), + paddingRight: NumCast(this.layoutDoc.xMargin), + paddingTop: NumCast(this.layoutDoc.yMargin), + paddingBottom: NumCast(this.layoutDoc.yMargin), }} onKeyDown={e => e.stopPropagation()}> @@ -140,5 +150,17 @@ export class EquationBox extends ViewBoxBaseComponent() { Docs.Prototypes.TemplateMap.set(DocumentType.EQUATION, { layout: { view: EquationBox, dataField: 'text' }, - options: { acl: '', fontSize: '14px', _nativeWidth: 35, _nativeHeight: 35, _layout_reflowHorizontal: false, _layout_reflowVertical: false, _layout_nativeDimEditable: false, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, // systemIcon: 'BsSuperscript' + BsSubscript + options: { + acl: '', + _xMargin: 10, + _yMargin: 10, + fontSize: '14px', + _nativeWidth: 40, + _nativeHeight: 40, + _layout_reflowHorizontal: false, + _layout_reflowVertical: false, + _layout_nativeDimEditable: false, + layout_hideDecorationTitle: true, + systemIcon: 'BsCalculatorFill', + }, // systemIcon: 'BsSuperscript' + BsSubscript }); -- cgit v1.2.3-70-g09d2 From 0eff48b757ca81860a883d25e147b8a869e5fe00 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Mon, 30 Dec 2024 23:35:24 -0500 Subject: created image regeneration with dialogue --- src/client/apis/gpt/GPT.ts | 39 +++- src/client/documents/Documents.ts | 4 + src/client/views/PropertiesView.scss | 1 + src/client/views/PropertiesView.tsx | 12 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 22 +-- src/client/views/nodes/ImageBox.tsx | 10 ++ src/client/views/nodes/imageEditor/ImageEditor.tsx | 2 +- src/client/views/pdf/AnchorMenu.tsx | 1 + src/client/views/smartdraw/DrawingFillHandler.tsx | 11 +- src/client/views/smartdraw/SmartDrawHandler.scss | 8 +- src/client/views/smartdraw/SmartDrawHandler.tsx | 197 ++++++++++++++------- src/client/views/smartdraw/StickerPalette.tsx | 1 + src/server/ApiManagers/FireflyManager.ts | 20 ++- src/server/DashUploadUtils.ts | 1 - 14 files changed, 238 insertions(+), 91 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 03380e4d6..9241eb120 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -249,6 +249,41 @@ const gptHandwriting = async (src: string): Promise => { } }; +const gptDescribeImage = async (image: string): Promise => { + try { + const response = await openai.chat.completions.create({ + model: 'gpt-4o', + temperature: 0, + messages: [ + { + role: 'user', + content: [ + { + type: 'text', + text: `Identify what this drawing is, naming as many elements and their location in the drawing as possible`, + }, + { + type: 'image_url', + image_url: { + url: `${image}`, + detail: 'low', + }, + }, + ], + }, + ], + }); + if (response.choices[0].message.content) { + console.log('GPT DESCRIPTION', response.choices[0].message.content); + return response.choices[0].message.content; + } + return 'Unknown drawing'; + } catch (err) { + console.log(err); + return 'Error connecting with API'; + } +}; + const gptDrawingColor = async (image: string, coords: string[]): Promise => { try { const response = await openai.chat.completions.create({ @@ -276,11 +311,11 @@ const gptDrawingColor = async (image: string, coords: string[]): Promise if (response.choices[0].message.content) { return response.choices[0].message.content; } - return 'Missing labels'; + return 'Unknown drawing'; } catch (err) { console.log(err); return 'Error connecting with API'; } }; -export { gptAPICall, gptImageCall, GPTCallType, gptImageLabel, gptGetEmbedding, gptHandwriting, gptDrawingColor }; +export { gptAPICall, gptImageCall, GPTCallType, gptImageLabel, gptGetEmbedding, gptHandwriting, gptDescribeImage, gptDrawingColor }; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c51c1645d..785af3409 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -516,6 +516,10 @@ export class DocumentOptions { card_sort?: STRt = new StrInfo('way cards are sorted in deck view'); card_sort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending'); + + ai_generated?: boolean; // to mark items as ai generated + firefly_seed?: number; + firefly_prompt?: string; } export const DocOptions = new DocumentOptions(); diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss index 693c75ebf..7866e67e7 100644 --- a/src/client/views/PropertiesView.scss +++ b/src/client/views/PropertiesView.scss @@ -642,6 +642,7 @@ .smooth, .color, +.strength-slider, .smooth-slider { margin-top: 7px; } diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index aefdeee17..5b24eb7ea 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -982,6 +982,9 @@ export class PropertiesView extends ObservableReactComponent { + !isNaN(val) && (this.refStrength = val); + }); return (
{!targetDoc.layout_isSvg && this.containsInkDoc && ( @@ -995,9 +998,10 @@ export class PropertiesView extends ObservableReactComponent DrawingFillHandler.drawingToImage(targetDoc, 'fill in the details of this image'), 'createImage')} + onClick={undoable(() => DrawingFillHandler.drawingToImage(targetDoc, this.refStrength, 'fill in the details of this image'), 'createImage')} />
+
{strength}
{ + doc[DocData].drawing_refStrength = Number(value); + }); + } @computed get smoothAmt() { return Number(this.getField('stroke_smoothAmount') || '5'); } // prettier-ignore set smoothAmt(value) { this.selectedStrokes.forEach(doc => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index acf72e5cb..4bccdd286 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1295,6 +1295,7 @@ export class CollectionFreeFormView extends CollectionSubView { - SmartDrawHandler.Instance.CreateDrawingDoc = this.createDrawingDoc; - SmartDrawHandler.Instance.AddDrawing = this.addDrawing; - SmartDrawHandler.Instance.RemoveDrawing = this.removeDrawing; - !SmartDrawHandler.Instance.ShowRegenerate ? SmartDrawHandler.Instance.displayRegenerate(this._downX, this._downY - 10) : SmartDrawHandler.Instance.hideRegenerate(); - }), - icon: 'pen-to-square', - }); + this.layoutDoc.drawingData != undefined && + optionItems.push({ + description: 'Show Drawing Editor', + event: action(() => { + SmartDrawHandler.Instance.CreateDrawingDoc = this.createDrawingDoc; + SmartDrawHandler.Instance.AddDrawing = this.addDrawing; + SmartDrawHandler.Instance.RemoveDrawing = this.removeDrawing; + !SmartDrawHandler.Instance.ShowRegenerate ? SmartDrawHandler.Instance.displayRegenerate(this._downX, this._downY - 10) : SmartDrawHandler.Instance.hideRegenerate(); + }), + icon: 'pen-to-square', + }); optionItems.push({ description: this.Document.savedAsSticker ? 'Sticker Saved!' : 'Save to Stickers', event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')), diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 25e7b566f..8f6a90e61 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -40,6 +40,7 @@ import { FocusViewOptions } from './FocusViewOptions'; import './ImageBox.scss'; import { OpenWhere } from './OpenWhere'; import { Upload } from '../../../server/SharedMediaTypes'; +import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler'; export class ImageEditorData { // eslint-disable-next-line no-use-before-define @@ -351,6 +352,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { }), icon: 'pencil-alt', }); + this.layoutDoc.ai_generated && + funcs.push({ + description: 'Regenerate AI Image', + event: action(() => { + console.log('COOOORDS', this.dataDoc.width as number, this.dataDoc.y as number); + !SmartDrawHandler.Instance.ShowRegenerate ? SmartDrawHandler.Instance.displayRegenerate(this.dataDoc.x as number, (this.dataDoc.y as number) - 10) : SmartDrawHandler.Instance.hideRegenerate(); + }), + icon: 'pen-to-square', + }); funcs.push({ description: this.Document.savedAsSticker ? 'Sticker Saved!' : 'Save to Stickers', event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')), diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx index a39878924..2a8bc034d 100644 --- a/src/client/views/nodes/imageEditor/ImageEditor.tsx +++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx @@ -411,7 +411,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc let finalImgURL: string = url; // crop the image for these brush modes to remove excess blank space around the image contents if (currCutType == CutMode.IN || currCutType == CutMode.DRAW_IN) { - const croppedData = cropImage(image, minX, maxX, minY, maxY); + const croppedData = cropImage(image, Math.max(minX, 0), Math.min(maxX, image.width), Math.max(minY, 0), Math.min(maxY, image.height)); finalImg = croppedData; finalImgURL = croppedData.src; } diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index fe03f32a5..bb8082061 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -158,6 +158,7 @@ export class AnchorMenu extends AntimodeMenu { docData.drawingColored = opts.autoColor; docData.drawingSize = opts.size; docData.drawingData = gptRes; + docData.ai_generated = true; }); pointerDown = (e: React.PointerEvent) => { diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index 48e71bc9f..1a470f995 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -1,21 +1,26 @@ +import { imageUrlToBase64 } from '../../../ClientUtils'; import { Doc } from '../../../fields/Doc'; import { ImageCast } from '../../../fields/Types'; import { Upload } from '../../../server/SharedMediaTypes'; +import { gptDescribeImage } from '../../apis/gpt/GPT'; import { Docs } from '../../documents/Documents'; import { Networking } from '../../Network'; import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import { OpenWhere } from '../nodes/OpenWhere'; export class DrawingFillHandler { - static drawingToImage = (drawing: Doc, prompt: string) => + static drawingToImage = (drawing: Doc, strength: number, prompt: string) => DocumentView.GetDocImage(drawing)?.then(imageField => { if (imageField) { const { href } = ImageCast(imageField).url; const hrefParts = href.split('.'); const structureUrl = `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`; - const strength: number = 100; - Networking.PostToServer('/queryFireflyImageFromStructure', { prompt, structureUrl, strength }).then((info: Upload.ImageInformation) => + imageUrlToBase64(structureUrl) + .then((hrefBase64: string) => gptDescribeImage(hrefBase64)) + .then((prompt: string) => { + Networking.PostToServer('/queryFireflyImageFromStructure', { prompt, structureUrl, strength }).then((info: Upload.ImageInformation) => DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(info.accessPaths.agnostic.client, {}), OpenWhere.addRight)) // prettier-ignore + }); } return false; }); diff --git a/src/client/views/smartdraw/SmartDrawHandler.scss b/src/client/views/smartdraw/SmartDrawHandler.scss index c25273876..513779512 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.scss +++ b/src/client/views/smartdraw/SmartDrawHandler.scss @@ -12,7 +12,13 @@ } } - .smartdraw-options { + .smartdraw-output-options { + display: flex; + flex-direction: row; + justify-content: center; + } + + .smartdraw-svg-options { margin-top: 5px; display: flex; flex-direction: row; diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index 036ac5983..fb1a5771e 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Slider, Switch } from '@mui/material'; +import { Checkbox, Slider, Switch } from '@mui/material'; import { Button, IconButton } from 'browndash-components'; import { action, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -74,6 +74,8 @@ export class SmartDrawHandler extends ObservableReactComponent { @observable private _autoColor: boolean = true; @observable private _regenInput: string = ''; @observable private _canInteract: boolean = true; + @observable private _generateDrawing: boolean = true; + @observable private _generateImage: boolean = true; @observable public ShowRegenerate: boolean = false; @@ -195,6 +197,7 @@ export class SmartDrawHandler extends ObservableReactComponent { */ @action handleSendClick = async () => { + if (!this._generateImage && !this._generateDrawing) return; this._isLoading = true; this._canInteract = false; if (this.ShowRegenerate) { @@ -212,7 +215,12 @@ export class SmartDrawHandler extends ObservableReactComponent { this._showOptions = false; }); try { - await this.drawWithGPT({ X: this._pageX, Y: this._pageY }, this._userInput, this._complexity, this._size, this._autoColor); + if (this._generateImage) { + await this.createImageWithFirefly(this._userInput); + } + if (this._generateDrawing) { + await this.drawWithGPT({ X: this._pageX, Y: this._pageY }, this._userInput, this._complexity, this._size, this._autoColor); + } this.hideSmartDrawHandler(); runInAction(() => { @@ -240,15 +248,12 @@ export class SmartDrawHandler extends ObservableReactComponent { drawWithGPT = async (startPt: { X: number; Y: number }, input: string, complexity: number, size: number, autoColor: boolean) => { if (input) { this._lastInput = { text: input, complexity: complexity, size: size, autoColor: autoColor, x: startPt.X, y: startPt.Y }; - - Networking.PostToServer('/queryFireflyImage', { prompt: input }).then(img => DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(img.accessPaths.agnostic.client, { title: input }), OpenWhere.addRight)); - const res = await gptAPICall(`"${input}", "${complexity}", "${size}"`, GPTCallType.DRAW, undefined, true); if (res) { const strokeData = await this.parseSvg(res, startPt, false, autoColor); const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); - + this._selectedDoc = drawingDoc; this._errorOccurredOnce = false; return strokeData; } else { @@ -258,6 +263,23 @@ export class SmartDrawHandler extends ObservableReactComponent { return undefined; }; + /** + * Calls Firefly API to create an image based on user input + */ + createImageWithFirefly = (input: string, seed?: number) => { + this._lastInput.text = input; + return Networking.PostToServer('/queryFireflyImage', { prompt: input, seed: seed }).then(img => { + const imgDoc: Doc = Docs.Create.ImageDocument(img.accessPaths.agnostic.client, { + title: input.match(/^(.*?)~~~.*$/)?.[1] || input, + ai_generated: true, + firefly_seed: img.accessPaths.agnostic.client.match(/\/(\d+)upload/)[1], + firefly_prompt: input, + }); + DocumentViewInternal.addDocTabFunc(imgDoc, OpenWhere.addRight); + this._selectedDoc = imgDoc; + }); + }; + /** * Regenerates drawings with the option to add a specific regenerate prompt/request. */ @@ -266,27 +288,39 @@ export class SmartDrawHandler extends ObservableReactComponent { if (lastInput) this._lastInput = lastInput; if (lastResponse) this._lastResponse = lastResponse; if (regenInput) this._regenInput = regenInput; - - try { - let res; + if (this._generateDrawing) { + try { + let res; + if (this._regenInput !== '') { + const prompt: string = `This is your previously generated svg code: ${this._lastResponse} for the user input "${this._lastInput.text}". Please regenerate it with the provided specifications.`; + res = await gptAPICall(`"${this._regenInput}"`, GPTCallType.DRAW, prompt, true); + this._lastInput.text = `${this._lastInput.text} ~~~ ${this._regenInput}`; + } else { + res = await gptAPICall(`"${this._lastInput.text}", "${this._lastInput.complexity}", "${this._lastInput.size}"`, GPTCallType.DRAW, undefined, true); + } + if (!res) { + console.error('GPT call failed'); + return; + } + const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor); + this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, this._selectedDoc); + const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); + drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); + } catch (err) { + console.error('Error regenerating drawing', err); + } + } + if (this._generateImage) { if (this._regenInput !== '') { - const prompt: string = `This is your previously generated svg code: ${this._lastResponse} for the user input "${this._lastInput.text}". Please regenerate it with the provided specifications.`; - res = await gptAPICall(`"${this._regenInput}"`, GPTCallType.DRAW, prompt, true); - this._lastInput.text = `${this._lastInput.text} ~~~ ${this._regenInput}`; + if (this._selectedDoc) { + const docData = this._selectedDoc[DocData]; + const newPrompt = `${docData.firefly_prompt}, ${this._regenInput}`; + const seed: number = docData?.firefly_seed as number; + await this.createImageWithFirefly(newPrompt, seed); + } } else { - res = await gptAPICall(`"${this._lastInput.text}", "${this._lastInput.complexity}", "${this._lastInput.size}"`, GPTCallType.DRAW, undefined, true); + await this.createImageWithFirefly(this._lastInput.text); } - if (!res) { - console.error('GPT call failed'); - return; - } - const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor); - this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, this._selectedDoc); - const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); - drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); - return strokeData; - } catch (err) { - console.error('Error regenerating drawing', err); } }; @@ -397,58 +431,87 @@ export class SmartDrawHandler extends ObservableReactComponent { {this._showOptions && (
-
-
- Auto color - this._canInteract && (this._autoColor = !this._autoColor))} - /> -
-
- Complexity - +
+ Generate Ink + this._canInteract && (this._complexity = val as number))} - valueLabelDisplay="auto" + checked={this._generateDrawing} + onChange={() => this._canInteract && (this._generateDrawing = !this._generateDrawing)} />
-
- Size (in pixels) - + Generate Image + this._canInteract && (this._size = val as number))} - valueLabelDisplay="auto" + checked={this._generateImage} + onChange={() => this._canInteract && (this._generateImage = !this._generateImage)} />
-
+ {this._generateDrawing && ( +
+
+ Auto color + this._canInteract && (this._autoColor = !this._autoColor))} + /> +
+
+ Complexity + this._canInteract && (this._complexity = val as number))} + valueLabelDisplay="auto" + /> +
+
+ Size (in pixels) + this._canInteract && (this._size = val as number))} + valueLabelDisplay="auto" + /> +
+
+ )}
)}
diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx index d56878f10..352a02e32 100644 --- a/src/client/views/smartdraw/StickerPalette.tsx +++ b/src/client/views/smartdraw/StickerPalette.tsx @@ -186,6 +186,7 @@ export class StickerPalette extends ObservableReactComponent { + generateImage = (prompt: string = 'a realistic illustration of a cat coding', seed?: number) => { + let body = `{ "prompt": "${prompt}" }`; + if (seed) { + body = `{ "prompt": "${prompt}", "seeds": [${seed}]}`; + } const fetched = this.getBearerToken().then(response => response?.json().then((data: { access_token: string }) => fetch('https://firefly-api.adobe.io/v3/images/generate', { @@ -76,9 +80,15 @@ export default class FireflyManager extends ApiManager { ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''], ['Authorization', `Bearer ${data.access_token}`], ], - body: `{ "prompt": "${prompt}" }`, + body: body, }) - .then(response2 => response2.json().then(json => (json.outputs?.[0] as { image: { url: string } })?.image.url)) + .then(response2 => + response2.json().then(json => { + const seed = json.outputs?.[0]?.seed; + const url = json.outputs?.[0]?.image?.url; + return { seed, url }; + }) + ) .catch(error => { console.error('Error:', error); return undefined; @@ -226,8 +236,8 @@ export default class FireflyManager extends ApiManager { method: Method.POST, subscription: '/queryFireflyImage', secureHandler: ({ req, res }) => - this.generateImage(req.body.prompt).then(url => - DashUploadUtils.UploadImage(url ?? '').then(info => { + this.generateImage(req.body.prompt, req.body.seed).then(img => + DashUploadUtils.UploadImage(img?.url ?? '', undefined, img?.seed).then(info => { if (info instanceof Error) _invalid(res, info.message); else _success(res, info); }) diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 623172894..2177c5d97 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -458,7 +458,6 @@ export namespace DashUploadUtils { return { name: result.name, message: result.message }; } const outputFile = filename || result.filename || ''; - return UploadInspectedImage(result, outputFile, prefix, isLocal().exec(source) || source.startsWith('data:') ? true : false); }; -- cgit v1.2.3-70-g09d2 From 1d62d867621b293c41ff8488ca5a3bd6010723d5 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Sun, 5 Jan 2025 23:47:18 -0500 Subject: added AI image editor --- src/client/documents/Documents.ts | 6 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/DocumentButtonBar.tsx | 7 +- src/client/views/ViewBoxInterface.ts | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 12 +-- src/client/views/nodes/DocumentView.tsx | 22 +++- src/client/views/nodes/ImageBox.scss | 28 +++++ src/client/views/nodes/ImageBox.tsx | 113 ++++++++++++++++++++- src/client/views/pdf/AnchorMenu.tsx | 12 +-- src/client/views/smartdraw/DrawingFillHandler.tsx | 2 +- src/client/views/smartdraw/SmartDrawHandler.tsx | 92 +++++++++-------- src/client/views/smartdraw/StickerPalette.tsx | 17 ++-- src/server/ApiManagers/FireflyManager.ts | 1 - 13 files changed, 235 insertions(+), 80 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 785af3409..7f1387ff8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -517,9 +517,9 @@ export class DocumentOptions { card_sort?: STRt = new StrInfo('way cards are sorted in deck view'); card_sort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending'); - ai_generated?: boolean; // to mark items as ai generated - firefly_seed?: number; - firefly_prompt?: string; + ai?: string; // to mark items as ai generated + ai_firefly_seed?: number; + ai_firefly_prompt?: string; } export const DocOptions = new DocumentOptions(); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 9af79a02e..b41fd09dc 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -779,7 +779,7 @@ pie title Minerals in my tap water { title: " Size", toolTip: "Size of area pencil eraser", btnType: ButtonType.NumberSliderButton, toolType: InkProperty.EraserWidth,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"NotRadiusEraser()"}, numBtnMin: 1, linearBtnWidth:40}, { title: "Mask", toolTip: "Make Stroke a Stencil Mask", btnType: ButtonType.ToggleButton, icon: "user-circle", toolType: InkProperty.Mask, scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } }, { title: "Labels", toolTip: "Show Labels Inside Shapes", btnType: ButtonType.ToggleButton, icon: "text-width", toolType: InkProperty.Labels, scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}}, - { title: "Smart Draw", toolTip: "Draw with GPT", btnType: ButtonType.ToggleButton, icon: "user-pen", toolType: InkTool.SmartDraw, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'}, funcs: {hidden: "IsNoviceMode()"}}, + { title: "Smart Draw", toolTip: "Draw with AI", btnType: ButtonType.ToggleButton, icon: "user-pen", toolType: InkTool.SmartDraw, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'}, funcs: {hidden: "IsNoviceMode()"}}, ]; } diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index b7033af3f..d722b28b5 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -319,12 +319,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( const targetDoc = this.view0?.Document; return !targetDoc ? null : ( Edit with AI}> -
{ - CalendarManager.Instance.open(this.view0, targetDoc); - }}> +
this.view0?.toggleAIEditor(), 'toggle AI editor')}>
diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts index b7980d74e..df08f2564 100644 --- a/src/client/views/ViewBoxInterface.ts +++ b/src/client/views/ViewBoxInterface.ts @@ -60,4 +60,5 @@ export abstract class ViewBoxInterface

extends ObservableReactComponent boolean; dontRegisterView?: () => boolean; // KeyValueBox's don't want to register their views isUnstyledView?: () => boolean; // SchemaView and KeyValue are unstyled -- not titles, no opacity, no animations + componentAIView?: (top: number) => JSX.Element; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ef0b80720..9af698ec7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1290,12 +1290,12 @@ export class CollectionFreeFormView extends CollectionSubView> = undefined; // needs to be accessed from DocumentView wrapper class @observable _animateScaleTime: Opt = undefined; // milliseconds for animating between views. defaults to 300 if not uset @observable _animateScalingTo = 0; + @observable public _showAIEditor: boolean = false; + + @action + showAIEditor() { + this._showAIEditor = !this._showAIEditor; + } get _contentDiv() { return this._mainCont.current; } // prettier-ignore get _docView() { return this._props.DocumentView?.(); } // prettier-ignore @@ -552,7 +558,6 @@ export class DocumentViewInternal extends DocComponent DocumentView.SetLightboxDoc(this.Document), icon: 'external-link-alt' }); } appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'map-pin' }); - appearanceItems.push({ description: 'Make Image', event: () => DrawingFillHandler.drawingToImage(this.Document, StrCast(this.Document.title)), icon: 'map-pin' }); !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' }); @@ -711,6 +716,8 @@ export class DocumentViewInternal extends DocComponent this._props.PanelWidth() * 0.6; + rph = () => this.panelHeight() * 0.6; @computed get viewBoxContents() { TraceMobx(); const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; @@ -720,7 +727,9 @@ export class DocumentViewInternal extends DocComponent + {this._showAIEditor && (this._componentView?.componentAIView?.(this.rph()) ?? null)}

); } @@ -1286,6 +1297,11 @@ export class DocumentView extends DocComponent() { } }; + @action + public toggleAIEditor = () => { + this._docViewInternal && this._docViewInternal.showAIEditor(); + }; + public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => { this._htmlOverlayText = text; this._htmlOverlayEffect = effect; diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 3ffda5a35..03314e90f 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -139,3 +139,31 @@ .imageBox-fadeBlocker-hover { opacity: 0; } + +.imageBox-aiView { + padding: 5px; + position: absolute; + overflow: scroll; + text-align: center; + font-weight: bold; + margin-top: 5px; + + .imageBox-aiView-subtitle { + align-self: start; + } + + .imageBox-aiView-regenerate-container, + .imageBox-aiView-options-container { + font-weight: normal; + text-align: start; + } + + .imageBox-aiView-regenerate, + .imageBox-aiView-options { + display: flex; + flex-direction: row; + justify-content: center; + flex-direction: row; + gap: 5px; + } +} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 7ce429f0f..f00580d77 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,7 +1,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import axios from 'axios'; -import { Colors } from 'browndash-components'; +import { Colors, Type } from 'browndash-components'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { extname } from 'path'; @@ -41,6 +41,9 @@ import './ImageBox.scss'; import { OpenWhere } from './OpenWhere'; import { Upload } from '../../../server/SharedMediaTypes'; import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler'; +import { Button } from 'browndash-components'; +import { SettingsManager } from '../../util/SettingsManager'; +import { AiOutlineSend } from 'react-icons/ai'; export class ImageEditorData { // eslint-disable-next-line no-use-before-define @@ -352,7 +355,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { }), icon: 'pencil-alt', }); - this.layoutDoc.ai_generated && + this.layoutDoc.ai && funcs.push({ description: 'Regenerate AI Image', event: action(e => { @@ -526,6 +529,112 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { ); } + @observable private _regenInput = ''; + @observable private _canInteract = true; + @observable private _regenerateLoading = false; + + componentAIView = (top: number) => { + const field = Cast(this.dataDoc[this.fieldKey], ImageField); + const showRegenerate = this.Document[DocData].ai; + return ( +
+ Edit Image with AI + {showRegenerate && ( +
+ Regenerate AI Image +
+ this._canInteract && (this._regenInput = e.target.value))} + // onKeyDown={this.handleKeyPress} + placeholder="Prompt (Optional)" + /> +
+
+ )} +
+ {showRegenerate && More Image Options } +
+
+
+
+ ); + }; + @computed get annotationLayer() { TraceMobx(); return
; diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index bb8082061..2e704aa8d 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -153,12 +153,12 @@ export class AnchorMenu extends AntimodeMenu { this.AddDrawingAnnotation(drawing); const docData = drawing[DocData]; docData.title = opts.text.match(/^(.*?)~~~.*$/)?.[1] || opts.text; - docData.drawingInput = opts.text; - docData.drawingComplexity = opts.complexity; - docData.drawingColored = opts.autoColor; - docData.drawingSize = opts.size; - docData.drawingData = gptRes; - docData.ai_generated = true; + docData.ai_drawing_input = opts.text; + docData.ai_drawing_complexity = opts.complexity; + docData.ai_drawing_colored = opts.autoColor; + docData.ai_drawing_size = opts.size; + docData.ai_drawing_data = gptRes; + docData.ai = 'gpt'; }); pointerDown = (e: React.PointerEvent) => { diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index 52652d377..8e41ee105 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -35,7 +35,7 @@ export class DrawingFillHandler { .then((hrefBase64: string) => gptDescribeImage(hrefBase64)) .then((prompt: string) => { Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: prompt, width: dims.width, height: dims.height, structureUrl: structureUrl, strength: strength, styles: styles }).then((info: Upload.ImageInformation) => - DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { ai_generated: true, nativeWidth: dims.width, nativeHeight: dims.height }), OpenWhere.addRight) + DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { ai: 'firefly', ai_firefly_prompt: prompt, nativeWidth: dims.width, nativeHeight: dims.height }), OpenWhere.addRight) ); // prettier-ignore }); } diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index 6c9470480..0c67c7a13 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -58,7 +58,7 @@ export class SmartDrawHandler extends ObservableReactComponent { private _lastInput: DrawingOptions = { text: '', complexity: 5, size: 350, autoColor: true, x: 0, y: 0 }; private _lastResponse: string = ''; - private _selectedDoc: Doc | undefined = undefined; + private _selectedDocs: Doc[] = []; private _errorOccurredOnce = false; @observable private _display: boolean = false; @@ -144,14 +144,14 @@ export class SmartDrawHandler extends ObservableReactComponent { */ @action displayRegenerate = (x: number, y: number) => { - this._selectedDoc = DocumentView.SelectedDocs()?.lastElement(); + this._selectedDocs = [DocumentView.SelectedDocs()?.lastElement()]; [this._pageX, this._pageY] = [x, y]; this._display = false; this.ShowRegenerate = true; this._showEditBox = false; - const docData = this._selectedDoc[DocData]; + const docData = this._selectedDocs[0][DocData]; this._lastResponse = StrCast(docData.drawingData); - this._lastInput = { text: StrCast(docData.drawingInput), complexity: NumCast(docData.drawingComplexity), size: NumCast(docData.drawingSize), autoColor: BoolCast(docData.drawingColored), x: this._pageX, y: this._pageY }; + this._lastInput = { text: StrCast(docData.ai_drawing_input), complexity: NumCast(docData.ai_drawing_complexity), size: NumCast(docData.ai_drawing_size), autoColor: BoolCast(docData.ai_drawing_colored), x: this._pageX, y: this._pageY }; }; /** @@ -205,8 +205,9 @@ export class SmartDrawHandler extends ObservableReactComponent { this._isLoading = true; this._canInteract = false; if (this.ShowRegenerate) { - await this.regenerate(); + await this.regenerate(this._selectedDocs); runInAction(() => { + this._selectedDocs = []; this._regenInput = ''; this._showEditBox = false; }); @@ -253,7 +254,7 @@ export class SmartDrawHandler extends ObservableReactComponent { const strokeData = await this.parseSvg(res, startPt, false, autoColor); const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); - this._selectedDoc = drawingDoc; + drawingDoc && this._selectedDocs.push(drawingDoc); this._errorOccurredOnce = false; return strokeData; } else { @@ -274,57 +275,62 @@ export class SmartDrawHandler extends ObservableReactComponent { title: input.match(/^(.*?)~~~.*$/)?.[1] || input, nativeWidth: dims.width, nativeHeight: dims.height, - ai_generated: true, - firefly_seed: img.accessPaths.agnostic.client.match(/\/(\d+)upload/)[1], - firefly_prompt: input, + ai: 'firefly', + ai_firefly_seed: img.accessPaths.agnostic.client.match(/\/(\d+)upload/)[1], + ai_firefly_prompt: input, }); DocumentViewInternal.addDocTabFunc(imgDoc, OpenWhere.addRight); - this._selectedDoc = imgDoc; + this._selectedDocs.push(imgDoc); }); }; /** * Regenerates drawings with the option to add a specific regenerate prompt/request. + * @param doc the drawing Docs to regenerate */ @action - regenerate = async (lastInput?: DrawingOptions, lastResponse?: string, regenInput?: string) => { + regenerate = async (drawingDocs: Doc[], lastInput?: DrawingOptions, lastResponse?: string, regenInput?: string) => { if (lastInput) this._lastInput = lastInput; if (lastResponse) this._lastResponse = lastResponse; if (regenInput) this._regenInput = regenInput; - if (this._generateImage) { - if (this._regenInput !== '') { - if (this._selectedDoc) { - const docData = this._selectedDoc[DocData]; - const newPrompt = `${docData.firefly_prompt}, ${this._regenInput}`; - const seed: number = docData?.firefly_seed as number; - await this.createImageWithFirefly(newPrompt, seed); + await Promise.all( + drawingDocs.map(async doc => { + const docData = doc[DocData]; + if (docData.type == 'image') { + const seed: number = docData?.ai_firefly_seed as number; + if (this._regenInput !== '') { + // if (this._selectedDoc) { + const newPrompt = `${docData.ai_firefly_prompt}, ${this._regenInput}`; + await this.createImageWithFirefly(newPrompt, seed); + // } + } else { + await this.createImageWithFirefly(this._lastInput.text || StrCast(docData.ai_firefly_prompt)); + } } - } else { - await this.createImageWithFirefly(this._lastInput.text); - } - } - if (this._generateDrawing) { - try { - let res; - if (this._regenInput !== '') { - const prompt: string = `This is your previously generated svg code: ${this._lastResponse} for the user input "${this._lastInput.text}". Please regenerate it with the provided specifications.`; - res = await gptAPICall(`"${this._regenInput}"`, GPTCallType.DRAW, prompt, true); - this._lastInput.text = `${this._lastInput.text} ~~~ ${this._regenInput}`; - } else { - res = await gptAPICall(`"${this._lastInput.text}", "${this._lastInput.complexity}", "${this._lastInput.size}"`, GPTCallType.DRAW, undefined, true); - } - if (!res) { - console.error('GPT call failed'); - return; + if (docData.type == 'collection') { + try { + let res; + if (this._regenInput !== '') { + const prompt: string = `This is your previously generated svg code: ${this._lastResponse} for the user input "${this._lastInput.text}". Please regenerate it with the provided specifications.`; + res = await gptAPICall(`"${this._regenInput}"`, GPTCallType.DRAW, prompt, true); + this._lastInput.text = `${this._lastInput.text} ~~~ ${this._regenInput}`; + } else { + res = await gptAPICall(`"${this._lastInput.text}", "${this._lastInput.complexity}", "${this._lastInput.size}"`, GPTCallType.DRAW, undefined, true); + } + if (!res) { + console.error('GPT call failed'); + return; + } + const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor); + this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, doc); + const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); + drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); + } catch (err) { + console.error('Error regenerating drawing', err); + } } - const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor); - this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, this._selectedDoc); - const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); - drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); - } catch (err) { - console.error('Error regenerating drawing', err); - } - } + }) + ); }; /** diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx index 352a02e32..d23763eb9 100644 --- a/src/client/views/smartdraw/StickerPalette.tsx +++ b/src/client/views/smartdraw/StickerPalette.tsx @@ -142,19 +142,20 @@ export class StickerPalette extends ObservableReactComponent { this._isLoading = true; + const prevDrawings = DocListCast(this._props.Document[DocData].data); this._props.Document[DocData].data = undefined; SmartDrawHandler.Instance.AddDrawing = this.addDrawing; this._canInteract = false; await Promise.all( Array.from({ length: 3 }).map((_, i) => { return this._showRegenerate - ? SmartDrawHandler.Instance.regenerate(this._opts, this._gptRes[i], this._userInput) + ? SmartDrawHandler.Instance.regenerate(prevDrawings, this._opts, this._gptRes[i], this._userInput) : SmartDrawHandler.Instance.drawWithGPT({ X: 0, Y: 0 }, this._userInput, this._opts.complexity, this._opts.size, this._opts.autoColor); }) ); @@ -181,12 +182,12 @@ export class StickerPalette extends ObservableReactComponent { - console.log('DIMENSIONS', width, height); let body = `{ "prompt": "${prompt}", "size": { "width": ${width}, "height": ${height}} }`; if (seed) { console.log('RECEIVED SEED', seed); -- cgit v1.2.3-70-g09d2 From 51a9f85b4ddc38ac825efcefc0c6db23f3b9914e Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Thu, 9 Jan 2025 11:19:31 -0500 Subject: added ai editor to collections --- src/client/apis/gpt/GPT.ts | 2 +- src/client/documents/Documents.ts | 1 + .../collectionFreeForm/CollectionFreeFormView.scss | 29 +++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 98 +++++++++++++++------- src/client/views/nodes/DocumentView.scss | 2 + src/client/views/nodes/DocumentView.tsx | 4 +- src/client/views/nodes/ImageBox.scss | 2 - src/client/views/nodes/ImageBox.tsx | 73 +++++++++------- src/client/views/smartdraw/DrawingFillHandler.tsx | 2 +- src/client/views/smartdraw/SmartDrawHandler.tsx | 2 +- 10 files changed, 151 insertions(+), 64 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 4b0d58cc1..cf3a28a8e 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -260,7 +260,7 @@ const gptDescribeImage = async (image: string): Promise => { content: [ { type: 'text', - text: `Briefly identify what this drawing is, naming all the drawing elements and their location within the image. Do not include anything about the drawing style.`, + text: `Very briefly identify what this drawing is and list all the drawing elements and their location within the image. Do not include anything about the drawing style.`, }, { type: 'image_url', diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 7f1387ff8..0bff74ac1 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -19,6 +19,7 @@ import { DocServer } from '../DocServer'; import { dropActionType } from '../util/DropActionTypes'; import { CollectionViewType, DocumentType } from './DocumentTypes'; import { Id } from '../../fields/FieldSymbols'; +import { FireflyImageData } from '../views/smartdraw/FireflyConstants'; class EmptyBox { public static LayoutString() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 2c94446fb..dff2cb282 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -304,3 +304,32 @@ display: none; } } + +.collectionFreeformView-aiView { + text-align: center; + font-weight: bold; + width: 100%; + + .collectionfreeformview-aiView-prompt { + height: 25px; + } + + .collectionFreeFormView-aiView-strength { + text-align: center; + } + + .collectionFreeformView-aiView-options-container, + .collectionFreeFormView-aiView-regenerate-container { + text-align: start; + font-weight: normal; + padding: 5px; + } + .collectionFreeformView-aiView-options, + .collectionFreeFormView-aiView-regenerate { + display: flex; + flex-direction: row; + gap: 10px; + justify-content: center; + align-items: center; + } +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7834eb352..112bfd178 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,9 +1,5 @@ import { Bezier } from 'bezier-js'; -<<<<<<< HEAD -import { Button, Colors, Type } from 'browndash-components'; -======= -import { Colors } from '@dash/components'; ->>>>>>> 2f7d1f0073943e1eb9e0f34c4459bc0176377697 +import { Button, Colors, Type } from '@dash/components'; import { Property } from 'csstype'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -73,6 +69,9 @@ import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; import ReactLoading from 'react-loading'; import { SettingsManager } from '../../../util/SettingsManager'; +import { Slider } from '@mui/material'; +import { AiOutlineSend } from 'react-icons/ai'; +import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler'; @observer class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> { @@ -2194,8 +2193,21 @@ export class CollectionFreeFormView extends CollectionSubView e.stopPropagation(); + // protected createDashEventsTarget = (ele: HTMLDivElement | null) => { + // // this._dropDisposer?.(); + // this._oldDrag?.removeEventListener('pointerdown', this.onPassiveDrag); + // this._oldDrag = ele; + // // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling + // ele?.addEventListener('pointerdown', this.onPassiveDrag, { passive: false }); + // }; componentAIViewHistory = () => { return ( @@ -2218,49 +2230,79 @@ export class CollectionFreeFormView extends CollectionSubView { const showRegenerate = this.Document[DocData].ai; return ( -
- Edit Image with AI +
e.stopPropagation()}> + Edit Collection with AI {showRegenerate && (
- {/* Regenerate AI Image + Regenerate AI Image
this._canInteract && (this._regenInput = e.target.value))} - // onKeyDown={this.handleKeyPress} placeholder="Prompt (Optional)" />
*/} +
)}
- {showRegenerate && Turn Drawing to Image } -
+ Create Image with Firefly +
+ this._canInteract && (this._drawingFillInput = e.target.value))} /> +
+ Reference Strength + this._canInteract && (this._fireflyRefStrength = val as number))} + valueLabelDisplay="auto" + /> +
+
); diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 82195b9c1..a3d47290a 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -281,6 +281,8 @@ .documentView-editorView { width: 100%; overflow-y: scroll; + justify-items: center; + background-color: rgb(223, 223, 223); .documentView-editorView-resizer { height: 5px; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 622eccc4f..d656bcf50 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -716,8 +716,8 @@ export class DocumentViewInternal extends DocComponent this._props.PanelWidth() * 0.6; - rph = () => this.panelHeight() * 0.6; + rpw = () => this._props.PanelWidth() / 2; + rph = () => this.panelHeight() / 2; @computed get viewBoxContents() { TraceMobx(); const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index e083f52b4..9532f4ad3 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -159,8 +159,6 @@ .imageBox-aiView { text-align: center; font-weight: bold; - align-content: center; - height: 100%; .imageBox-aiView-subtitle { position: relative; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index fe069eacc..db8bb2c6e 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,7 +1,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import axios from 'axios'; -import { Colors, Button, Type } from '@dash/components'; +import { Colors, Button, Type, Size } from '@dash/components'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { extname } from 'path'; @@ -76,6 +76,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { return FieldView.LayoutString(ImageBox, fieldKey); } _ffref = React.createRef(); + _oldWheel: HTMLElement | null = null; private _ignoreScroll = false; private _forcedScroll = false; private _dropDisposer?: DragManager.DragDropDisposer; @@ -94,6 +95,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { @observable private _error = ''; @observable private _isHovering = false; // flag to switch between primary and alternate images on hover + // variables for AI Image Editor + @observable private _regenInput = ''; + @observable private _canInteract = true; + @observable private _regenerateLoading = false; + @observable private _prevImgs: FireflyImageData[] = []; + constructor(props: FieldViewProps) { super(props); makeObservable(this); @@ -529,25 +536,34 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { ); } - @observable private _regenInput = ''; - @observable private _canInteract = true; - @observable private _regenerateLoading = false; - @observable private _prevImgs: FireflyImageData[] = []; + onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); + + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { + // this._dropDisposer?.(); + this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); + this._oldWheel = ele; + // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling + ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + }; componentAIViewHistory = () => { return ( -
+
+
); @@ -557,7 +573,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey])); const showRegenerate = this.Document[DocData].ai; return ( -
+
{ + this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); + this._oldWheel = ele; + // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling + ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + }}> Edit Image with AI {showRegenerate && (
@@ -566,11 +589,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this._canInteract && (this._regenInput = e.target.value))} - // onKeyDown={this.handleKeyPress} placeholder="Prompt (Optional)" />
)} @@ -704,8 +719,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { const { nativeWidth: width, nativeHeight: height } = await Networking.PostToServer('/inspectImage', { source: this.paths[0] }); return { width, height }; }; - savedAnnotations = () => this._savedAnnotations; + render() { TraceMobx(); const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string; diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index fea4acb67..43d16df89 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -35,7 +35,7 @@ export class DrawingFillHandler { .then((hrefBase64: string) => gptDescribeImage(hrefBase64)) .then(prompt => { Networking.PostToServer('/queryFireflyImageFromStructure', - { prompt: user_prompt || prompt, width: dims.width, height: dims.height, structureUrl, strength, styles }) + { prompt: `${user_prompt}, ${prompt}`, width: dims.width, height: dims.height, structureUrl, strength, styles }) .then((info: Upload.ImageInformation) => DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { ai: 'firefly', ai_firefly_prompt: user_prompt || prompt, nativeWidth: dims.width, nativeHeight: dims.height }), OpenWhere.addRight) diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index 9248cbee3..4052ea852 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -279,7 +279,7 @@ export class SmartDrawHandler extends ObservableReactComponent { nativeWidth: dims.width, nativeHeight: dims.height, ai: 'firefly', - ai_firefly_seed: aiseed, + ai_firefly_seed: seed, ai_firefly_prompt: input, }); DocumentViewInternal.addDocTabFunc(imgDoc, OpenWhere.addRight); -- cgit v1.2.3-70-g09d2