diff options
Diffstat (limited to 'src/client/views')
41 files changed, 374 insertions, 411 deletions
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 0136f6abe..253db08de 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -497,8 +497,8 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() {...this._props} setHeight={undefined} setContentViewBox={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text) - yPadding={10} - xPadding={10} + yMargin={10} + xMargin={10} fieldKey="text" // dontRegisterView={true} noSidebar diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index ad6bb09c7..c49b7e6de 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -173,7 +173,7 @@ export class MainView extends ObservableReactComponent<object> { views => views.length > 1 && document.activeElement instanceof HTMLElement && document.activeElement?.blur() ); reaction( - () => Doc.MyDockedBtns?.linearView_IsOpen, + () => Doc.MyDockedBtns?.linearView_isOpen, open => SnappingManager.SetPrintToConsole(!!open) ); const scriptTag = document.createElement('script'); diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index e4811a902..b2e42652d 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -198,6 +198,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP const targetCreator = (annotationOn: Doc | undefined) => { const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, annotationOn, 'yellow'); + target.layout_fitWidth = true; DocumentView.SetSelectOnLoad(target); return target; }; diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx index 2290516dc..cf4bf991e 100644 --- a/src/client/views/ObservableReactComponent.tsx +++ b/src/client/views/ObservableReactComponent.tsx @@ -16,15 +16,13 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, obj makeObservable(this); } __passiveWheel: HTMLElement | null = null; - _isContentActive: () => boolean | undefined = () => false; + __isContentActive: () => boolean | undefined = () => false; /** * default method to stop wheel events from bubbling up to parent components. * @param e */ - onPassiveWheel = (e: WheelEvent) => { - if (this._isContentActive?.()) e.stopPropagation(); - }; + onPassiveWheel = (e: WheelEvent) => this.__isContentActive?.() && e.stopPropagation(); /** * This fixes the problem where a component uses wheel events to scroll, but is nested inside another component that @@ -36,7 +34,7 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, obj * @param onPassiveWheel an optional function to call to handle the wheel event (and block its propagation. If omitted, the event won't propagate. */ fixWheelEvents = (ele: HTMLElement | null, isContentActive: () => boolean | undefined, onPassiveWheel?: (e: WheelEvent) => void) => { - this._isContentActive = isContentActive; + this.__isContentActive = isContentActive; this.__passiveWheel?.removeEventListener('wheel', onPassiveWheel ?? this.onPassiveWheel); this.__passiveWheel = ele; ele?.addEventListener('wheel', onPassiveWheel ?? this.onPassiveWheel, { passive: false }); diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 28566ba1d..ded342df0 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -165,7 +165,7 @@ export class PropertiesButtons extends React.Component { // // containerDoc._forceActive = // //containerDoc._freeform_fitContentsToBox = // containerDoc._isLightbox = !containerDoc._isLightbox; - // //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined; + // //containerDoc._xMargin = containerDoc._yMargin = containerDoc._isLightbox ? 10 : undefined; // const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]); // //dv.Document.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' }); // containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.layout_linkDisplay = false))); diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts index 0ddac8914..d8dab8e89 100644 --- a/src/client/views/ViewBoxInterface.ts +++ b/src/client/views/ViewBoxInterface.ts @@ -64,5 +64,4 @@ export abstract class ViewBoxInterface<P> extends ObservableReactComponent<React 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?: () => JSX.Element; - componentAIViewHistory?: () => JSX.Element; } diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index d5edc3e0b..3dc7bc515 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -133,7 +133,7 @@ export class CollectionCardView extends CollectionSubView() { } @computed get yMargin() { - return this._props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth())); + return this._props.yMargin || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth())); } @computed get cardDeckWidth() { diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index ac1981012..3c5bc10de 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -206,7 +206,7 @@ export class CollectionCarouselView extends CollectionSubView() { marginLeft: this.captionMarginX, width: `calc(100% - ${this.captionMarginX * 2}px)`, }}> - <FormattedTextBox xPadding={10} yPadding={10} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={this.curDoc()} TemplateDataDocument={undefined} /> + <FormattedTextBox xMargin={10} yMargin={10} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={this.curDoc()} TemplateDataDocument={undefined} /> </div> )} </> diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index be570564b..25a222cbb 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -89,7 +89,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth())); } @computed get yMargin() { - return this._props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth())); + return this._props.yMargin || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth())); } @computed get gridGap() { @@ -381,8 +381,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection hideDecorations={this._props.childHideDecorations} childFiltersByRanges={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} - xPadding={NumCast(this.layoutDoc._childXPadding, this._props.childXPadding)} - yPadding={NumCast(this.layoutDoc._childYPadding, this._props.childYPadding)} + xMargin={NumCast(this.layoutDoc._childXPadding, this._props.childXPadding)} + yMargin={NumCast(this.layoutDoc._childYPadding, this._props.childYPadding)} rejectDrop={this._props.childRejectDrop} addDocument={this._props.addDocument} moveDocument={this._props.moveDocument} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 994669734..345f60e75 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -71,6 +71,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< } _ele: HTMLElement | null = null; + _eleMasonrySingle = React.createRef<HTMLDivElement>(); protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) => { const dragData = de.complete.docDragData; @@ -92,13 +93,13 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< createColumnDropRef = (ele: HTMLDivElement | null) => { this.dropDisposer?.(); if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Doc, this.onInternalPreDrop.bind(this)); - else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1); + else if (this._eleMasonrySingle.current) this.props.refList.splice(this.props.refList.indexOf(this._eleMasonrySingle.current), 1); this._ele = ele; }; @action componentDidMount() { - this._ele && this.props.refList.push(this._ele); + this._eleMasonrySingle.current && this.props.refList.push(this._eleMasonrySingle.current); this._disposers.collapser = reaction( () => this._props.headingObject?.collapsed, collapsed => { this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false; }, // prettier-ignore @@ -363,6 +364,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< }}> <div key={`${heading}-stack`} + ref={this._eleMasonrySingle} className="collectionStackingView-masonrySingle" style={{ padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`, diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index e79d0a76d..01a8da313 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -9,7 +9,7 @@ import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, DateCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; +import { BoolCast, Cast, DateCast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; @@ -356,12 +356,16 @@ export function CollectionSubView<X>() { return !!added; } if (de.complete.annoDragData) { - const dropCreator = de.complete.annoDragData.dropDocCreator; - de.complete.annoDragData.dropDocCreator = () => { - const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined); - this.addDocument(dropped); - return dropped; - }; + if (![de.complete.annoDragData.dragDocument.embedContainer, de.complete.annoDragData.dragDocument].includes(this.Document)) { + de.complete.annoDragData.dropDocCreator = () => this.getAnchor?.(true) || this.Document; + } else { + const dropCreator = de.complete.annoDragData.dropDocCreator; + de.complete.annoDragData.dropDocCreator = () => { + const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined); + this.addDocument(dropped); + return dropped; + }; + } return true; } return false; @@ -415,7 +419,7 @@ export function CollectionSubView<X>() { const tags = html.split('<'); if (tags[0] === '') tags.splice(0, 1); let img = tags[0].startsWith('img') ? tags[0] : tags.length > 1 && tags[1].startsWith('img') ? tags[1] : ''; - const cors = img.includes('corsProxy') ? img.match(/http.*corsProxy\//)![0] : ''; + const cors = img.includes('corsproxy') ? img.match(/http.*corsproxy\//)![0] : ''; img = cors ? img.replace(cors, '') : img; if (img) { const imgSrc = img.split('src="')[1].split('"')[0]; @@ -561,7 +565,17 @@ export function CollectionSubView<X>() { } const loading = Docs.Create.LoadingDocument(file, options); Doc.addCurrentlyLoading(loading); - DocUtils.uploadFileToDoc(file, {}, loading); + DocUtils.uploadFileToDoc(file, {}, loading).then(d => { + if (d && d?.type === DocumentType.IMG) { + const imgTemplate = DocCast(Doc.UserDoc().defaultImageLayout); + if (imgTemplate) { + const templateFieldKey = StrCast(imgTemplate.title); + d.layout_fieldKey = templateFieldKey; + d[templateFieldKey] = imgTemplate; + } + } + }); + return loading; }) )) diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 4348bc7dc..c4373aaa7 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -163,8 +163,8 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter} searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} fitContentsToBox={returnTrue} - xPadding={this.xPadding} - yPadding={this.yPadding} + xMargin={this.xPadding} + yMargin={this.yPadding} /> <div className="miniOverlay" onPointerDown={this.miniDown}> <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} /> diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index fb2d0955f..5b2f1ff81 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1042,8 +1042,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { disableBrushing={this.treeView._props.disableBrushing} hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)} dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)} - xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} - yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} + xMargin={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} + yMargin={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} @@ -1148,8 +1148,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { moveDocument={this.move} removeDocument={this._props.removeDoc} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} - xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} - yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} + xMargin={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} + yMargin={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} addDocTab={this._props.addDocTab} pinToPres={this.treeView._props.pinToPres} disableBrushing={this.treeView._props.disableBrushing} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 93314e383..5bbe93a90 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -258,8 +258,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection override contentBounds = () => { const { x, y, r, b } = aggregateBounds( this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!), - NumCast(this.layoutDoc._xPadding, NumCast(this.layoutDoc._xMargin, this._props.xPadding ?? 0)), - NumCast(this.layoutDoc._yPadding, NumCast(this.layoutDoc._yMargin, this._props.yPadding ?? 0)) + NumCast(this.layoutDoc._xMargin, this._props.xMargin ?? 0), + NumCast(this.layoutDoc._yMargin, this._props.yMargin ?? 0) ); const [width, height] = [r - x, b - y]; return { @@ -1261,15 +1261,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection /** * Adds the created drawing to the freeform canvas and sets the metadata. */ - addDrawing = (doc: Doc, opts: DrawingOptions, gptRes: string, x?: number, y?: number) => { - doc.$title = opts.text; - doc.$width = opts.size; - doc.$ai_drawing_input = opts.text; - doc.$ai_drawing_complexity = opts.complexity; - doc.$ai_drawing_colored = opts.autoColor; - doc.$ai_drawing_size = opts.size; - doc.$ai_drawing_data = gptRes; - doc.$ai = 'gpt'; + addDrawing = (doc: Doc, opts: DrawingOptions, x?: number, y?: number) => { + doc.$ai_prompt = opts.text; this._drawingContainer = doc; if (x !== undefined && y !== undefined) { [doc.x, doc.y] = this.screenToFreeformContentsXf.transformPoint(x, y); @@ -1815,7 +1808,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection () => { if (this.Document.isGroup && this.childDocs.length === this.childDocList?.length) { const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: NumCast(cd._width), height: NumCast(cd._height) })); - return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding)); + return aggregateBounds(clist, NumCast(this.layoutDoc._xMargin), NumCast(this.layoutDoc._yMargin)); } return undefined; }, @@ -2017,7 +2010,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection event: action(() => { SmartDrawHandler.Instance.AddDrawing = this.addDrawing; SmartDrawHandler.Instance.RemoveDrawing = this.removeDrawing; - !SmartDrawHandler.Instance.ShowRegenerate ? SmartDrawHandler.Instance.displayRegenerate(this._downX, this._downY - 10) : SmartDrawHandler.Instance.hideRegenerate(); + !SmartDrawHandler.Instance.ShowRegenerate ? SmartDrawHandler.Instance.displayRegenerate(this._downX, this._downY - 10, NumCast(this.layoutDoc[this.scaleFieldKey])) : SmartDrawHandler.Instance.hideRegenerate(); }), icon: 'pen-to-square', }); @@ -2222,9 +2215,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection placeholder={this._drawingFillInput || StrCast(this.Document.title) || 'Describe image'} type="text" value={this._drawingFillInput} - onChange={action(e => { - this._drawingFillInput = e.target.value; - })} + onChange={action(e => (this._drawingFillInput = e.target.value))} /> <div className="collectionFreeFormView-aiView-strength"> <span className="collectionFreeFormView-aiView-similarity">Similarity</span> @@ -2253,11 +2244,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onClick={undoable( action(() => { this._drawingFillLoading = true; - DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._drawingFillInput || StrCast(this.Document.title))?.then( - action(() => { - this._drawingFillLoading = false; - }) - ); + DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._drawingFillInput || StrCast(this.Document.title))?.then(action(() => (this._drawingFillLoading = false))); }), 'create image' )} @@ -2265,37 +2252,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection </div> </div> </div> - <div className="collectionfreeformview-aiView-regenerate-container"> - <span className="collectionfreeformview-aiView-subtitle">Regenerate</span> - <div className="collectionfreeformview-aiView-regenerate"> - <input - className="collectionfreeformview-aiView-input" - aria-label="Edit instructions input" - type="text" - value={this._regenInput} - onChange={action(e => { - this._regenInput = e.target.value; - })} - placeholder="..under development.." - /> - <div className="collectionFreeFormView-aiView-regenBtn"> - <Button - text="Regenerate" - type={Type.SEC} - icon={this._regenLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />} - iconPlacement="right" - // onClick={action(async () => { - // this._regenLoading = true; - // SmartDrawHandler.Instance.CreateDrawingDoc = this.createDrawingDoc; - // SmartDrawHandler.Instance.AddDrawing = this.addDrawing; - // SmartDrawHandler.Instance.RemoveDrawing = this.removeDrawing; - // await SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput, true); - // this._regenLoading = false; - // })} - /> - </div> - </div> - </div> </div> ); }; diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 1d5e70be7..b837b3a86 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -58,7 +58,7 @@ export class CollectionGridView extends CollectionSubView() { return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth())); } @computed get yMargin() { - return this._props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth())); + return this._props.yMargin || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth())); } @computed get gridGap() { return NumCast(this.Document._gridGap, 10); @@ -206,7 +206,7 @@ export class CollectionGridView extends CollectionSubView() { setContentViewBox={emptyFunction} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} onClickScript={this.onChildClickHandler} - dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'} + dontCenter={StrCast(this.layoutDoc.layout_dontCenter, StrCast(childLayout.layout_dontCenter)) as 'x' | 'y' | 'xy'} showTags={BoolCast(this.layoutDoc.showChildTags) || BoolCast(this.Document._layout_showTags)} /> ); diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 3c2a99b1e..d0a1e6f0d 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -27,7 +27,7 @@ import './CollectionLinearView.scss'; /** * CollectionLinearView is the class for rendering the horizontal collection * of documents, it useful for horizontal menus. It can either be expandable - * or not using the linearView_Expandable field. + * or not using the linearView_expandable field. * It is used in the following locations: * - It is used in the popup menu on the bottom left (see docButtons() in MainView.tsx) * - It is used for the context sensitive toolbar at the top (see contMenuButtons() in CollectionMenu.tsx) @@ -52,7 +52,7 @@ export class CollectionLinearView extends CollectionSubView() { componentDidMount() { this._widthDisposer = reaction( - () => 5 + NumCast(this.dataDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0), + () => 5 + NumCast(this.dataDoc.linearView_btnWidth, this.dimension()) + (this.layoutDoc.linearView_isOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0), width => { this.childDocs.length && (this.layoutDoc._width = width); }, @@ -208,7 +208,7 @@ export class CollectionLinearView extends CollectionSubView() { render() { const flexDir = StrCast(this.Document.flexDirection); // Specify direction of linear view content const flexGap = NumCast(this.Document.flexGap); // Specify the gap between linear view content - const isExpanded = BoolCast(this.layoutDoc.linearView_IsOpen); + const isExpanded = BoolCast(this.layoutDoc.linearView_isOpen); const menuOpener = ( <Toggle @@ -219,9 +219,9 @@ export class CollectionLinearView extends CollectionSubView() { type={Type.TERT} onPointerDown={e => e.stopPropagation()} toggleType={ToggleType.BUTTON} - toggleStatus={BoolCast(this.layoutDoc.linearView_IsOpen)} + toggleStatus={BoolCast(this.layoutDoc.linearView_isOpen)} onClick={() => { - this.layoutDoc.linearView_IsOpen = !isExpanded; + this.layoutDoc.linearView_isOpen = !isExpanded; ScriptCast(this.Document.onClick)?.script.run({ this: this.Document }, console.log); }} tooltip={isExpanded ? 'Close' : 'Open'} @@ -231,10 +231,10 @@ export class CollectionLinearView extends CollectionSubView() { ); return ( - <div className={`collectionLinearView-outer ${this.layoutDoc.linearView_SubMenu}`} style={{ backgroundColor: this.layoutDoc.linearView_IsOpen ? undefined : 'transparent' }}> + <div className="collectionLinearView-outer" style={{ backgroundColor: this.layoutDoc.linearView_isOpen ? undefined : 'transparent' }}> <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}> - {!this.layoutDoc.linearView_Expandable ? null : menuOpener} - {!this.layoutDoc.linearView_IsOpen ? null : ( + {!this.layoutDoc.linearView_expandable ? null : menuOpener} + {!this.layoutDoc.linearView_isOpen && this.layoutDoc.linearView_expandable ? null : ( <div className="collectionLinearView-content" style={{ diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index c06391f35..6442385c0 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -359,7 +359,7 @@ export class CollectionSchemaView extends CollectionSubView() { @action addNewKey = (key: string, defaultVal: FieldType | undefined) => { this.childDocs.forEach(doc => { - doc[DocData][key] = defaultVal; + if (doc[DocData][key] === undefined) doc[DocData][key] = defaultVal; }); }; diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index 16d33eb93..134f2ed31 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -115,12 +115,11 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea }; const readOnly = this.getFinfo(fieldKey)?.readOnly ?? false; const cursor = !readOnly ? 'text' : 'default'; - const pointerEvents: 'all' | 'none' = 'all'; - return { color, fieldProps, cursor, pointerEvents }; + return { color, fieldProps, cursor }; }; @computed get editableView() { - const { color, fieldProps, pointerEvents } = this.renderProps(this._props); + const { color, fieldProps } = this.renderProps(this._props); return ( <div @@ -132,7 +131,6 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea style={{ color, width: '100%', - pointerEvents, }}> <EditableView ref={r => { @@ -232,6 +230,7 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea className="schema-column-header" style={{ width: this._props.columnWidths[this._props.columnIndex], + pointerEvents: this.props.isContentActive() ? undefined : 'none', }} onPointerEnter={() => { this.handlePointerEnter(); diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index f4beb1004..cb3adae10 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -156,6 +156,11 @@ ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) { }); // toggle: Set overlay status of selected document // eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(function setDefaultImageTemplate(checkResult?: boolean) { + return DocumentView.setDefaultImageTemplate(checkResult); +}); +// toggle: Set overlay status of selected document +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) { if (checkResult) { return DocumentView.Selected().length ? StrCast(DocumentView.SelectedDocs().lastElement().layout_headingColor) : Doc.SharingDoc()?.headingColor; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 940c4cb99..3805b0dca 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -148,7 +148,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static getValues(doc: Doc, time: number, fillIn: boolean = true) { return CollectionFreeFormDocumentView.animFields.reduce( (p, val) => { - p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce( + p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : [])!.reduce( (prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev), undefined as unknown as number ); @@ -161,7 +161,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static getStringValues(doc: Doc, time: number) { return CollectionFreeFormDocumentView.animStringFields.reduce( (p, val) => { - p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev), undefined as unknown as string); + p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])])!.reduce((prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev), undefined as unknown as string); return p; }, {} as { [val: string]: Opt<string> } @@ -171,7 +171,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static setStringValues(time: number, d: Doc, vals: { [val: string]: Opt<string> }) { const timecode = Math.round(time); Object.keys(vals).forEach(val => { - const findexed = Cast(d[`${val}_indexed`], listSpec('string'), []).slice(); + const findexed = Cast(d[`${val}_indexed`], listSpec('string'), [])!.slice(); findexed[timecode] = vals[val] || ''; d[`${val}_indexed`] = new List<string>(findexed); }); @@ -180,7 +180,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) { const timecode = Math.round(time); Object.keys(vals).forEach(val => { - const findexed = Cast(d[`${val}_indexed`], listSpec('number'), []).slice(); + const findexed = Cast(d[`${val}_indexed`], listSpec('number'), [])!.slice(); findexed[timecode] = vals[val] as unknown as number; d[`${val}_indexed`] = new List<number>(findexed); }); @@ -204,15 +204,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF docs.forEach(doc => { this.animFields.forEach(val => { const findexed = Cast(doc[`${val.key}_indexed`], listSpec('number'), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as unknown as number); + (findexed?.length ?? 0) <= timecode + 1 && findexed?.push(undefined as unknown as number); }); this.animStringFields.forEach(val => { const findexed = Cast(doc[`${val}_indexed`], listSpec('string'), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as unknown as string); + (findexed?.length ?? 0) <= timecode + 1 && findexed?.push(undefined as unknown as string); }); this.animDataFields(doc).forEach(val => { const findexed = Cast(doc[`${val}_indexed`], listSpec(InkField), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as unknown as InkField); + (findexed?.length ?? 0) <= timecode + 1 && findexed?.push(undefined as unknown as InkField); }); }); return newTimer; diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 5ac66f2cd..c4351a200 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -119,6 +119,7 @@ display: flex; justify-content: center; align-items: center; + margin: auto; position: relative; // allows contents to be positioned relative/below title > .formattedTextBox { position: absolute; // position a child text box @@ -302,6 +303,6 @@ background: transparent; .documentView-editorView-resizer { - height: 5px; + height: 2px; } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 284014e54..05706fe6b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -648,11 +648,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field rootSelected = () => this._rootSelected; panelHeight = () => this._props.PanelHeight() - this.headerMargin - 2 * NumCast(this.Document.borderWidth); - screenToLocalContent = () => - this._props - .ScreenToLocalTransform() - .translate(-NumCast(this.Document.borderWidth), -this.headerMargin - NumCast(this.Document.borderWidth)) - .scale(this.viewingAiEditor() ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1); + aiShift = () => (!this.viewingAiEditor() ? 0 : (this._props.PanelWidth() - this.aiContentsWidth()) / 2); + aiScale = () => (this.viewingAiEditor() ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1); onClickFunc = () => (this.disableClickScriptFunc ? undefined : this.onClickHdlr); setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height + 2 * NumCast(this.Document.borderWidth))); } // prettier-ignore setContentView = action((view: ViewBoxInterface<FieldViewProps>) => (this._componentView = view)); @@ -678,7 +675,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field return this._props.styleProvider?.(doc, props, property); }; - @observable _aiWinHeight = 88; + @observable _aiWinHeight = 32; TagsBtnHeight = 22; @computed get currentScale() { @@ -703,33 +700,21 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field @computed get uiBtnScaling() { return Math.max(this.maxWidgetSize / this.TagsBtnHeight, 1) * Math.min(1, this.viewScaling); } // prettier-ignore aiContentsWidth = () => (this.aiContentsHeight() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1); - aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - this._aiWinHeight * this.uiBtnScaling); + aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - (this._aiWinHeight + (this.tagsOverlayFunc() ? 22 : 0)) * this.uiBtnScaling); @computed get aiEditor() { return ( - <> - <div - className="documentView-editorView-history" - ref={r => this.historyRef(this._oldAiWheel, (this._oldAiWheel = r))} - style={{ - transform: `scale(${this.uiBtnScaling})`, - height: this.aiContentsHeight() / this.uiBtnScaling, - width: ((this._props.PanelWidth() - this.aiContentsWidth()) * 0.95) / this.uiBtnScaling, - }}> - {this._componentView?.componentAIViewHistory?.() ?? null} - </div> - <div - className="documentView-editorView" - style={{ - background: SnappingManager.userVariantColor, - width: `${100 / this.uiBtnScaling}%`, // - transform: `scale(${this.uiBtnScaling})`, - }} - ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}> - <div className="documentView-editorView-resizer" /> - {this._componentView?.componentAIView?.() ?? null} - {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null} - </div> - </> + <div + className="documentView-editorView" + style={{ + background: SnappingManager.userVariantColor, + width: `${100 / this.uiBtnScaling}%`, // + transform: `scale(${this.uiBtnScaling})`, + }} + ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}> + <div className="documentView-editorView-resizer" /> + {this._componentView?.componentAIView?.() ?? null} + {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null} + </div> ); } @computed get tagsOverlay() { @@ -755,7 +740,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field } widgetOverlayFunc = () => (this.widgetDecorations ? this.widgetOverlay : null); viewingAiEditor = () => (this._props.showAIEditor && this._componentView?.componentAIView?.() !== undefined ? this.aiEditor : null); - _contentsRef = React.createRef<DocumentContentsView>(); + @observable _contentsRef: DocumentContentsView | undefined = undefined; @computed get viewBoxContents() { TraceMobx(); const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; @@ -771,7 +756,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field }}> <DocumentContentsView {...this._props} - ref={this._contentsRef} + ref={action((r: DocumentContentsView) => (this._contentsRef = r))} layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')} pointerEvents={this.contentPointerEvents} setContentViewBox={this.setContentView} @@ -780,7 +765,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field PanelHeight={this.viewingAiEditor() ? this.aiContentsHeight : this.panelHeight} setHeight={this.setHeight} isContentActive={this.isContentActive} - ScreenToLocalTransform={this.screenToLocalContent} rootSelected={this.rootSelected} onClickScript={this.onClickFunc} setTitleFocus={this.setTitleFocus} @@ -913,8 +897,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field }}> <FormattedTextBox {...this._props} - yPadding={10} - xPadding={10} + yMargin={10} + xMargin={10} fieldKey={this.showCaption} styleProvider={this.captionStyleProvider} dontRegisterView @@ -1184,7 +1168,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { * @returns boolean whether sub-component Doc is in synch with the layoutDoc that this view thinks its rendering */ IsInvalid = (renderDoc?: Doc): boolean => { - const docContents = this._docViewInternal?._contentsRef.current; + const docContents = this._docViewInternal?._contentsRef; return !( (!renderDoc || (docContents?.layoutDoc === renderDoc && // @@ -1291,7 +1275,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { if (this.ComponentView?.screenBounds?.()) { return this.ComponentView.screenBounds(); } - const xf = this.screenToContentsTransform().scale(this.nativeScaling).inverse(); + const xf = this.screenToContentBoundsTransform().inverse(); const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)]; // transition is returned so that the bounds will 'update' at the end of an animated transition. This is needed by xAnchor in LinkBox @@ -1405,28 +1389,36 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined); }, 'set custom view'); - public static setDefaultTemplate(checkResult?: boolean) { - if (checkResult) { - return Doc.UserDoc().defaultTextLayout; + private static getTemplate(view: DocumentView | undefined) { + if (view) { + if (!view.layoutDoc.isTemplateDoc) { + MakeTemplate(view.Document); + Doc.AddDocToList(Doc.UserDoc(), 'template_user', view.Document); + Doc.AddDocToList(DocListCast(Doc.MyTools?.data)[1], 'data', makeUserTemplateButtonOrImage(view.Document)); + DocCast(Doc.UserDoc().template_user) && view.Document && Doc.AddDocToList(DocCast(Doc.UserDoc().template_user)!, 'data', view.Document); + return view.Document; + } + return DocCast(Doc.LayoutField(view.Document)) ?? view.Document; } + } + public static setDefaultTemplate(checkResult?: boolean) { + if (checkResult) return Doc.UserDoc().defaultTextLayout; const view = DocumentView.Selected()[0]?._props.renderDepth > 0 ? DocumentView.Selected()[0] : undefined; undoable(() => { - let tempDoc: Opt<Doc>; - if (view) { - if (!view.layoutDoc.isTemplateDoc) { - tempDoc = view.Document; - MakeTemplate(tempDoc); - Doc.AddDocToList(Doc.UserDoc(), 'template_user', tempDoc); - Doc.AddDocToList(DocListCast(Doc.MyTools?.data)[1], 'data', makeUserTemplateButtonOrImage(tempDoc)); - DocCast(Doc.UserDoc().template_user) && tempDoc && Doc.AddDocToList(DocCast(Doc.UserDoc().template_user)!, 'data', tempDoc); - } else { - tempDoc = DocCast(Doc.LayoutField(view.Document)); - } - } + const tempDoc = DocumentView.getTemplate(view); Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined; }, 'set default template')(); return undefined; } + public static setDefaultImageTemplate(checkResult?: boolean) { + if (checkResult) return Doc.UserDoc().defaultImageLayout; + const view = DocumentView.Selected()[0]?._props.renderDepth > 0 ? DocumentView.Selected()[0] : undefined; + undoable(() => { + const tempDoc = DocumentView.getTemplate(view); + Doc.UserDoc().defaultImageLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined; + }, 'set default image template')(); + return undefined; + } /** * This switches between the current view of a Doc and a specified alternate layout view. @@ -1498,17 +1490,22 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { isHovering = () => this._isHovering; selfView = () => this; /** - * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView) + * @returns Transform to the document view's available panel space (in the coordinate system of whatever contains the DocumentView) */ screenToViewTransform = () => this._props.ScreenToLocalTransform(); /** + * @returns Transform to the document view after centering in available panel space(in the coordinate system of whatever contains the DocumentView) + */ + private screenToContentBoundsTransform = () => this.screenToViewTransform().translate(-this.centeringX, -this.centeringY); + /** * @returns Transform to the coordinate system of the contents of the document view (includes native dimension scaling and centering) */ screenToContentsTransform = () => this._props .ScreenToLocalTransform() .translate(-this.centeringX, -this.centeringY) - .scale(1 / this.nativeScaling); + .translate(-(this._docViewInternal?.aiShift() ?? 0), 0) + .scale((this._docViewInternal?.aiScale() ?? 1) / this.nativeScaling); htmlOverlay = () => { const effect = StrCast(this._htmlOverlayEffect?.presentation_effect, StrCast(this._htmlOverlayEffect?.followLinkAnimEffect)); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index a6872f8dc..f6b405a43 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -52,8 +52,8 @@ export interface FieldViewSharedProps { renderDepth: number; scriptContext?: unknown; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document screenXPadding?: (view: DocumentView | undefined) => number; // padding in screen space coordinates (used by text box to reflow around UI buttons in carouselView) - xPadding?: number; - yPadding?: number; + xMargin?: number; + yMargin?: number; dontRegisterView?: boolean; dropAction?: dropActionType; dragAction?: dropActionType; diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx index 0bbd6a0d3..d04ec3a10 100644 --- a/src/client/views/nodes/IconTagBox.tsx +++ b/src/client/views/nodes/IconTagBox.tsx @@ -58,16 +58,20 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> { const tag = StrCast(key.toolType); const color = dv._props.styleProvider?.(dv.layoutDoc, dv.ComponentView?._props, StyleProp.FontColor) as string; return ( - <Toggle - tooltip={`Click to add/remove the tag ${tag}`} - toggleStatus={TagItem.docHasTag(dv.Document, tag)} - toggleType={ToggleType.BUTTON} - icon={<FontAwesomeIcon className={`fontIconBox-icon-${ToggleType.BUTTON}`} icon={icon} color={color} />} - size={Size.XSMALL} - type={Type.PRIM} - onClick={() => this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag))} - color={color} - /> + <div> + {' '} + {/* tooltips require the wrapped item to be an element ref */} + <Toggle + tooltip={`Click to add/remove the tag ${tag}`} + toggleStatus={TagItem.docHasTag(dv.Document, tag)} + toggleType={ToggleType.BUTTON} + icon={<FontAwesomeIcon className={`fontIconBox-icon-${ToggleType.BUTTON}`} icon={icon} color={color} />} + size={Size.XSMALL} + type={Type.PRIM} + onClick={() => this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag))} + color={color} + /> + </div> ); }; diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 9f7a5d03f..5a6292fab 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -236,7 +236,7 @@ .imageBox-aiView-input { overflow: hidden; text-overflow: ellipsis; - max-width: 65%; + max-width: 80%; width: 100%; color: black; } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d16baada6..f7ad5c7e2 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,6 +1,6 @@ -import { Button, Colors, EditableText, IconButton, Size, Toggle, ToggleType, Type } from '@dash/components'; +import { Button, Colors, EditableText, IconButton, NumberDropdown, Size, Toggle, ToggleType, Type } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Slider, Tooltip } from '@mui/material'; +import { Tooltip } from '@mui/material'; import axios from 'axios'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -37,7 +37,6 @@ import { OverlayView } from '../OverlayView'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { PinDocView, PinProps } from '../PinFuncs'; import { DrawingFillHandler } from '../smartdraw/DrawingFillHandler'; -import { FireflyImageData, isFireflyImageData } from '../smartdraw/FireflyConstants'; import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler'; import { StickerPalette } from '../smartdraw/StickerPalette'; import { StyleProp } from '../StyleProp'; @@ -102,7 +101,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @observable private _regenInput = ''; @observable private _canInteract = true; @observable private _regenerateLoading = false; - @observable private _prevImgs: FireflyImageData[] = StrCast(this.Document.ai_firefly_history) ? JSON.parse(StrCast(this.Document.ai_firefly_history)) : []; // Add these observable properties to the ImageBox class @observable private _outpaintingInProgress = false; @@ -154,7 +152,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this._disposers.path = reaction( () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width), height: this.layoutDoc._height }), ({ nativeSize, width, height }) => { - if (!this.layoutDoc._layout_nativeDimEditable || !height) { + if (!this.layoutDoc._layout_nativeDimEditable || !height || this.layoutDoc.layout_resetNativeDim) { + this.layoutDoc.layout_resetNativeDim = undefined; // template images need to reset their dimensions when they are rendered with content. afterwards, remove this flag. this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; } }, @@ -227,7 +226,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const drag = de.complete.docDragData.draggedDocuments.lastElement(); const dragField = drag[Doc.LayoutDataKey(drag)]; const descText = RTFCast(dragField)?.Text || StrCast(dragField) || RTFCast(drag.text)?.Text || StrCast(drag.text) || StrCast(this.Document.title); - const oldPrompt = StrCast(this.Document.ai_firefly_prompt, StrCast(this.Document.title)); + const oldPrompt = StrCast(this.Document.ai_prompt, StrCast(this.Document.title)); const newPrompt = (text: string) => (oldPrompt ? `${oldPrompt} ~~~ ${text}` : text); DrawingFillHandler.drawingToImage(this.Document, 90, newPrompt(descText), drag)?.then(action(() => (this._regenerateLoading = false))); added = false; @@ -737,9 +736,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { <div className="imageBox-regenerateDropTarget" ref={this._regenerateIconRef} - onClick={() => DocCast(this.Document.ai_firefly_generatedDocs) && DocumentView.showDocument(DocCast(this.Document.ai_firefly_generatedDocs)!, { openLocation: OpenWhere.addRight })} + onClick={() => DocCast(this.Document.ai_generatedDocs) && DocumentView.showDocument(DocCast(this.Document.ai_generatedDocs)!, { openLocation: OpenWhere.addRight })} style={{ - display: (this._props.isContentActive() && (SnappingManager.CanEmbed || this.Document.ai_firefly_generatedDocs)) || this._regenerateLoading ? 'block' : 'none', + display: (this._props.isContentActive() && (SnappingManager.CanEmbed || this.Document.ai_generatedDocs)) || this._regenerateLoading ? 'block' : 'none', transform: `scale(${this.uiBtnScaling})`, width: this._sideBtnWidth, height: this._sideBtnWidth, @@ -845,34 +844,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @observable _filterFunc: ((doc: Doc) => boolean) | undefined = undefined; @observable private _fireflyRefStrength = 0; - componentAIViewHistory = () => ( - <div className="imageBox-aiView-history"> - <Button text="Clear History" type={Type.SEC} size={Size.XSMALL} /> - {this._prevImgs.map(img => ( - <div key={img.pathname}> - <img - className="imageBox-aiView-img" - src={ClientUtils.prepend(img.pathname.replace(extname(img.pathname), '_s' + extname(img.pathname)))} - onClick={() => { - this.dataDoc[this.fieldKey] = new ImageField(img.pathname); - this.dataDoc.ai_firefly_prompt = img.prompt; - this.dataDoc.ai_firefly_seed = img.seed; - }} - /> - <span>{img.prompt}</span> - </div> - ))} - </div> - ); - componentAIView = () => { - const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey])); return ( <div className="imageBox-aiView"> <div className="imageBox-aiView-regenerate"> - <span className="imageBox-aiView-firefly" style={{ color: SnappingManager.userColor }}> - Firefly: - </span> <input style={{ color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }} className="imageBox-aiView-input" @@ -886,57 +861,39 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { <Button text="Create" type={Type.TERT} + size={Size.XSMALL} color={SnappingManager.userColor} background={SnappingManager.userBackgroundColor} // style={{ alignSelf: 'flex-end' }} icon={this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />} iconPlacement="right" - onClick={action(async () => { + onClick={action(() => { this._regenerateLoading = true; - if (this._fireflyRefStrength) { - DrawingFillHandler.drawingToImage(this.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title))?.then(action(() => (this._regenerateLoading = false))); - } else { - SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput || StrCast(this.Document.title), true).then( - action(newImgs => { - const firstImg = newImgs[0]; - if (isFireflyImageData(firstImg)) { - const url = firstImg.pathname; - const imgField = new ImageField(url); - this._prevImgs.length === 0 && - this._prevImgs.push({ prompt: StrCast(this.dataDoc.ai_firefly_prompt), seed: this.dataDoc.ai_firefly_seed as number, href: this.paths.lastElement(), pathname: field?.url.pathname ?? '' }); - this._prevImgs.unshift({ prompt: firstImg.prompt, seed: firstImg.seed, pathname: url }); - this.dataDoc.ai_firefly_history = JSON.stringify(this._prevImgs); - this.dataDoc.ai_firefly_prompt = firstImg.prompt; - this.dataDoc[this.fieldKey] = imgField; - this._regenerateLoading = false; - this._regenInput = ''; - } - }) - ); - } + DrawingFillHandler.drawingToImage(this.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title))?.then(action(() => (this._regenerateLoading = false))); })} /> </div> - </div> - <div className="imageBox-aiView-strength"> - <span className="imageBox-aiView-similarity" style={{ color: SnappingManager.userColor }}> - Similarity - </span> - <Slider - className="imageBox-aiView-slider" - sx={{ - '& .MuiSlider-track': { color: SettingsManager.userColor }, - '& .MuiSlider-rail': { color: SettingsManager.userBackgroundColor }, - '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } }, - }} - min={0} - max={100} - step={1} - size="small" - value={this._fireflyRefStrength} - onChange={action((e, val) => this._canInteract && (this._fireflyRefStrength = val as number))} - valueLabelDisplay="auto" - /> + <div> + <NumberDropdown + color={SnappingManager.userColor} + background={SnappingManager.userBackgroundColor} + numberDropdownType="slider" + showPlusMinus={false} + formLabel="similarity" + tooltip="structure similarity of created images to current image" + type={Type.PRIM} + width={75} + min={0} + max={100} + number={this._fireflyRefStrength} + size={Size.XXSMALL} + setNumber={undoable( + action(val => this._canInteract && (this._fireflyRefStrength = val as number)), + `${this.Document.title} button set from list` + )} + fillWidth + /> + </div> </div> </div> ); @@ -1092,7 +1049,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { alert('Error uploading files - possibly due to unsupported file types'); } else { this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client); - !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc); + !(result instanceof Error) && DocUtils.assignUploadInfo(result, this.dataDoc); } disposer(); } else { diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index b08ed84b7..4cbe01b82 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -27,7 +27,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { private dropDisposer?: DragManager.DragDropDisposer; private _timeout: NodeJS.Timeout | undefined; private _divRef: HTMLDivElement | null = null; - private _reaction: IReactionDisposer | undefined; + private _disposers: { [key: string]: IReactionDisposer } = {}; constructor(props: FieldViewProps) { super(props); @@ -43,7 +43,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { componentDidMount() { this._props.setContentViewBox?.(this); - this._reaction = reaction( + this._disposers.active = reaction( () => this.Title, () => document.activeElement !== this._divRef && this._forceRerender++ ); @@ -51,7 +51,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { componentWillUnMount() { this._timeout && clearTimeout(this._timeout); this.setText(this._divRef?.innerText ?? ''); - this._reaction?.(); + Object.values(this._disposers).forEach(disposer => disposer()); } @observable _forceRerender = 0; @@ -171,20 +171,21 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { render() { TraceMobx(); const boxParams = this.fitTextToBox(undefined); // this causes mobx to trigger re-render when data changes + const [xmargin, ymargin] = [NumCast(this.layoutDoc._xMargin), NumCast(this.layoutDoc._uMargin)]; return ( <div className="labelBox-outerDiv" ref={this.createDropTarget} style={{ boxShadow: this.boxShadow }}> <div className="labelBox-mainButton" style={{ backgroundColor: this.backgroundColor, - color: StrCast(this.layoutDoc._text_fontColor, StrCast(this.layoutDoc._color)), - fontFamily: StrCast(this.layoutDoc._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)) || 'inherit', + color: StrCast(this.layoutDoc[`${this.fieldKey}_fontColor`], StrCast(this.layoutDoc._color)), + fontFamily: StrCast(this.layoutDoc[`${this.fieldKey}_fontFamily`], StrCast(Doc.UserDoc().fontFamily)) || 'inherit', letterSpacing: StrCast(this.layoutDoc.letterSpacing), - textTransform: StrCast(this.layoutDoc[this.fieldKey + '_transform']) as Property.TextTransform, - paddingLeft: NumCast(this.layoutDoc._xPadding), - paddingRight: NumCast(this.layoutDoc._xPadding), - paddingTop: NumCast(this.layoutDoc._yPadding), - paddingBottom: NumCast(this.layoutDoc._yPadding), + textTransform: StrCast(this.layoutDoc[`${this.fieldKey}_transform`]) as Property.TextTransform, + paddingLeft: xmargin, + paddingRight: xmargin, + paddingTop: ymargin, + paddingBottom: ymargin, width: this._props.PanelWidth(), height: this._props.PanelHeight(), whiteSpace: boxParams.multiLine ? 'pre-wrap' : 'pre', @@ -192,8 +193,8 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { <div key={this._forceRerender} style={{ - width: this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xPadding), - height: this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yPadding), + width: this._props.PanelWidth() - 2 * xmargin, + height: this._props.PanelHeight() - 2 * ymargin, outline: 'unset !important', }} onKeyDown={e => { @@ -214,12 +215,14 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { this._divRef?.removeEventListener('focusout', this.keepFocus); this._divRef?.addEventListener('focusout', this.keepFocus); }} - onBlur={() => { + onBlur={e => { this._divRef?.removeEventListener('focusout', this.keepFocus); this.setText(this._divRef?.innerText ?? ''); - RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined); - FormattedTextBox.LiveTextUndo?.end(); - FormattedTextBox.LiveTextUndo = undefined; + if (!FormattedTextBox.tryKeepingFocus(e.relatedTarget, () => this._divRef?.focus())) { + RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined); + FormattedTextBox.LiveTextUndo?.end(); + FormattedTextBox.LiveTextUndo = undefined; + } }} dangerouslySetInnerHTML={{ __html: `<span class="textFitted textFitAlignVert" style="display: inline-block; text-align: center; font-size: 100px; height: 0px;">${this.Title?.startsWith('#') ? '' : (this.Title ?? '')}</span>`, diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 83c44c80f..55e6d5596 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -557,8 +557,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { NativeHeight={this.sidebarNativeHeightFunc} PanelHeight={this._props.PanelHeight} PanelWidth={this.sidebarWidth} - xPadding={0} - yPadding={0} + xMargin={0} + yMargin={0} viewField={this.SidebarKey} isAnnotationOverlay={false} originTopLeft diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 4f02d68d6..603dcad5c 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -335,8 +335,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() select={emptyFunction} isContentActive={emptyFunction} NativeDimScaling={returnOne} - xPadding={25} - yPadding={10} + xMargin={25} + yMargin={10} whenChildContentsActiveChanged={emptyFunction} removeDocument={returnFalse} moveDocument={returnFalse} diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 5603786f0..838dbea9d 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -454,7 +454,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { iframeDown = (e: PointerEvent) => { this._textAnnotationCreator = undefined; const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection(); - if (sel?.empty) + if (sel?.empty && !(e.target as any).textContent) sel.empty(); // Chrome else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox @@ -509,10 +509,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { try { href = iframe?.contentWindow?.location.href; } catch { - runInAction(() => this._warning++); + // runInAction(() => this._warning++); href = undefined; } - let requrlraw = decodeURIComponent(href?.replace(ClientUtils.prepend('') + '/corsProxy/', '') ?? this._url.toString()); + let requrlraw = decodeURIComponent(href?.replace(ClientUtils.prepend('') + '/corsproxy/', '') ?? this._url.toString()); if (requrlraw !== this._url.toString()) { if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) { const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g); @@ -565,9 +565,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { 'click', undoable( action((e: MouseEvent) => { - let eleHref = ''; + let eleHref = (e.target as any)?.outerHTML?.split('"="')[1]?.split('"')[0]; for (let ele = e.target as HTMLElement | Element | null; ele; ele = ele.parentElement) { - if (ele instanceof HTMLAnchorElement) { + if ('href' in ele) { eleHref = (typeof ele.href === 'string' ? ele.href : eleHref) || (ele.parentElement && 'href' in ele.parentElement ? (ele.parentElement.href as string) : eleHref); } } @@ -576,7 +576,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const batch = UndoManager.StartBatch('webclick'); e.stopPropagation(); setTimeout(() => { - this.setData(eleHref.replace(ClientUtils.prepend(''), origin)); + const url = eleHref.replace(ClientUtils.prepend(''), origin); + this.setData(url); batch.end(); }); if (this._outerRef.current) { @@ -858,7 +859,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ); } if (field instanceof WebField) { - const url = this.layoutDoc[this.fieldKey + '_useCors'] ? ClientUtils.CorsProxy(this._webUrl) : this._webUrl; + const url = this.layoutDoc[this.fieldKey + '_useCors'] ? '/corsproxy/' + this._webUrl : this._webUrl; const scripts = this.dataDoc[this.fieldKey + '_allowScripts'] || this._webUrl.includes('wikipedia.org') || this._webUrl.includes('google.com') || this._webUrl.startsWith('https://bing'); // if (!scripts) console.log('No scripts for: ' + url); return ( @@ -1074,15 +1075,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { childPointerEvents = () => (this._props.isContentActive() ? 'all' : undefined); @computed get webpage() { TraceMobx(); - const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; + // const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as Property.PointerEvents | undefined); - const scale = previewScale * (this._props.NativeDimScaling?.() || 1); + // const scale = previewScale * (this._props.NativeDimScaling?.() || 1); return ( <div className="webBox-outerContent" ref={this._outerRef} style={{ - height: `${100 / scale}%`, + height: '100%', //`${100 / scale}%`, pointerEvents, }} // when active, block wheel events from propagating since they're handled by the iframe @@ -1175,6 +1176,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { className="webBox-container" style={{ width: `calc(${100 / scale}% - ${!this.SidebarShown ? 0 : ((this.sidebarWidth() - WebBox.sidebarResizerWidth) / scale) * (this._previewWidth ? scale : 1)}px)`, + height: `${100 / scale}%`, transform: `scale(${scale})`, pointerEvents, }} diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js index b727107a9..ef465c453 100644 --- a/src/client/views/nodes/WebBoxRenderer.js +++ b/src/client/views/nodes/WebBoxRenderer.js @@ -21,7 +21,7 @@ const ForeignHtmlRenderer = function (styleSheets) { return window.location.origin + extension; } function CorsProxy(url) { - return prepend('/corsProxy/') + encodeURIComponent(url); + return prepend('/corsproxy/') + encodeURIComponent(url); } /** * diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index c51f6c38b..57720baae 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1164,7 +1164,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB }; @computed get tagsHeight() { - return this.DocumentView?.().showTags ? Math.max(0, 20 - Math.max(this._props.yPadding ?? 0, NumCast(this.layoutDoc._yMargin))) * this.ScreenToLocalBoxXf().Scale : 0; + return this.DocumentView?.().showTags ? Math.max(0, 20 - Math.max(this._props.yMargin ?? 0, NumCast(this.layoutDoc._yMargin))) * this.ScreenToLocalBoxXf().Scale : 0; } @computed get contentScaling() { @@ -1224,13 +1224,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB incomingValue => { if (this.EditorView && this.ApplyingChange !== this.fieldKey) { if (incomingValue?.data) { - const updatedState = JSON.parse(incomingValue.data.Data); + const updatedState = JSON.parse(incomingValue.data.Data.replace(/\n/g, '')); if (JSON.stringify(this.EditorView.state.toJSON()) !== JSON.stringify(updatedState)) { this.EditorView.updateState(EditorState.fromJSON(this.config, updatedState)); this.tryUpdateScrollHeight(); } } else if (this.EditorView.state.doc.textContent !== (incomingValue?.str ?? '')) { selectAll(this.EditorView.state, tx => this.EditorView?.dispatch(tx.insertText(incomingValue?.str ?? ''))); + this.tryUpdateScrollHeight(); } } }, @@ -1750,19 +1751,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB * When a text box loses focus, it might be because a text button was clicked (eg, bold, italics) or color picker. * In these cases, force focus back onto the text box. * @param target + * @returns true if focus was kept on the text box, false otherwise */ - tryKeepingFocus = (target: Element | null) => { + public static tryKeepingFocus(target: Element | null, refocusFunc?: () => void) { for (let newFocusEle = target instanceof HTMLElement ? target : null; newFocusEle; newFocusEle = newFocusEle?.parentElement) { // bcz: HACK!! test if parent of new focused element is a UI button (should be more specific than testing className) if (['fonticonbox', 'antimodeMenu-cont', 'popup-container'].includes(newFocusEle?.className ?? '')) { - return this.EditorView?.focus(); // keep focus on text box + refocusFunc?.(); // keep focus on text box + return true; } } - }; + return false; + } @action onBlur = (e: React.FocusEvent) => { - this.tryKeepingFocus(e.relatedTarget); + FormattedTextBox.tryKeepingFocus(e.relatedTarget, () => this.EditorView?.focus()); if (this.ProseRef?.children[0] !== e.nativeEvent.target) return; if (!(this.EditorView?.state.selection instanceof NodeSelection) || this.EditorView.state.selection.node.type !== this.EditorView.state.schema.nodes.footnote) { const stordMarks = this.EditorView?.state.storedMarks?.slice(); @@ -1880,7 +1884,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB } }; tryUpdateScrollHeight = () => { - const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0); + const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yMargin || 0); const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined; if (this.EditorView && children && !SnappingManager.IsDragging) { const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0; @@ -2112,8 +2116,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB const rounded = StrCast(this.layoutDoc._layout_borderRounding) === '100%' ? '-rounded' : ''; setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide); - const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), 0, this._props.xPadding ?? 0, this._props.screenXPadding?.(this._props.DocumentView?.()) ?? 0); - const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, this._props.yPadding ?? 0); // prettier-ignore + const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), 0, this._props.xMargin ?? 0, this._props.screenXPadding?.(this._props.DocumentView?.()) ?? 0); + const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, this._props.yMargin ?? 0); // prettier-ignore const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' > return this.isLabel ? ( <LabelBox {...this._props} /> diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx index 85bd95d15..198b8e713 100644 --- a/src/client/views/nodes/imageEditor/ImageEditor.tsx +++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx @@ -281,11 +281,14 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc try { const canvasOriginalImg = ImageUtility.getCanvasImg(img); if (!canvasOriginalImg) return; - const canvasMask = ImageUtility.getCanvasMask(canvas, canvasOriginalImg); + const canvasMask = ImageUtility.getCanvasMask(canvas, canvas); if (!canvasMask) return; const maskBlob = await ImageUtility.canvasToBlob(canvasMask); const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg); const res = await ImageUtility.getEdit(imgBlob, maskBlob, input || 'Fill in the image in the same style', 2); + if ((res as any).status == 'error') { + alert((res as any).message); + } // create first image if (!newCollectionRef.current) { diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts index 1c6a38a24..d6093c6eb 100644 --- a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts +++ b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts @@ -75,7 +75,7 @@ export class ImageUtility { fd.append('mask', maskBlob, 'mask.png'); fd.append('prompt', prompt); fd.append('size', '1024x1024'); - fd.append('n', n ? JSON.stringify(n) : '1'); + fd.append('n', n ? n + '' : '1'); fd.append('response_format', 'b64_json'); try { @@ -268,14 +268,14 @@ export class ImageUtility { ctx.drawImage(img, xOffset, 0, width, height); // draw reflected image padding - this.drawHorizontalReflection(ctx, canvas, xOffset); + // this.drawHorizontalReflection(ctx, canvas, xOffset); } else { // vertical padding, y offset const yOffset = Math.floor((canvasSize - height) / 2); ctx.drawImage(img, 0, yOffset, width, height); // draw reflected image padding - this.drawVerticalReflection(ctx, canvas, yOffset); + // this.drawVerticalReflection(ctx, canvas, yOffset); } return canvas; }; diff --git a/src/client/views/nodes/trails/PresSlideBox.tsx b/src/client/views/nodes/trails/PresSlideBox.tsx index 3dbb3da88..55a655c7a 100644 --- a/src/client/views/nodes/trails/PresSlideBox.tsx +++ b/src/client/views/nodes/trails/PresSlideBox.tsx @@ -559,10 +559,10 @@ export class PresSlideBox extends ViewBoxBaseComponent<FieldViewProps>() { style={{ backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent', opacity: this._dragging ? 0.3 : 1, - paddingLeft: NumCast(this.layoutDoc._xPadding, this._props.xPadding), - paddingRight: NumCast(this.layoutDoc._xPadding, this._props.xPadding), - paddingTop: NumCast(this.layoutDoc._yPadding, this._props.yPadding), - paddingBottom: NumCast(this.layoutDoc._yPadding, this._props.yPadding), + paddingLeft: NumCast(this.layoutDoc._xMargin, this._props.xMargin), + paddingRight: NumCast(this.layoutDoc._xMargin, this._props.xMargin), + paddingTop: NumCast(this.layoutDoc._yPadding, this._props.yMargin), + paddingBottom: NumCast(this.layoutDoc._yPadding, this._props.yMargin), }} onDoubleClick={action(() => { this.toggleProperties(); diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index bb43291ee..f6fa45221 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -7,6 +7,11 @@ $highlightedText: #82e0ff; $inputHeight: 60px; $headingHeight: 32px; +.gptPopup-sortBox { + display: block; + max-height: calc(100% - 45px); // leave room for input +} + .gptPopup-summary-box { position: fixed; padding-left: 10px; @@ -87,6 +92,7 @@ $headingHeight: 32px; } .btns-wrapper-gpt { height: 100%; + width: 100%; display: flex; justify-content: center; align-items: center; @@ -97,7 +103,6 @@ $headingHeight: 32px; flex-direction: column; width: 100%; height: 100%; - overflow-y: auto; padding-right: 5px; } diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index c45d8e052..568e48edf 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -93,7 +93,7 @@ export class GPTPopup extends ObservableReactComponent<object> { this.onGptResponse = (sortResult: string, questionType: GPTDocCommand, args?: string) => this.processGptResponse(selDoc, this._textToDocMap, sortResult, questionType, args); this.onQuizRandom = () => this.randomlyChooseDoc(selDoc.Document, hasChildDocs()); this._documentDescriptions = Promise.all(hasChildDocs().map(doc => - Doc.getDescription(doc).then(text => this._textToDocMap.set(text.trim(), doc) && `${DescriptionSeperator}${text}${DescriptionSeperator}`) + Doc.getDescription(doc).then(text => this._textToDocMap.set(text.replace(/\n/g, ' ').trim(), doc) && `${DescriptionSeperator}${text}${DescriptionSeperator}`) )).then(docDescriptions => docDescriptions.join()); // prettier-ignore } }, @@ -211,7 +211,7 @@ export class GPTPopup extends ObservableReactComponent<object> { const selView = DocumentView.Selected().lastElement(); const selDoc = selView?.Document; if (selDoc && (selView._props.renderDepth > 1 || selDoc[Doc.LayoutDataKey(selDoc)] instanceof ImageField)) { - const oldPrompt = StrCast(selDoc.ai_firefly_prompt, StrCast(selDoc.title)); + const oldPrompt = StrCast(selDoc.ai_prompt, StrCast(selDoc.title)); const newPrompt = oldPrompt ? `${oldPrompt} ~~~ ${imgDesc}` : imgDesc; return DrawingFillHandler.drawingToImage(selDoc, 100, newPrompt, selDoc) .then(action(() => (this._userPrompt = ''))) @@ -406,73 +406,80 @@ export class GPTPopup extends ObservableReactComponent<object> { scrollToBottom = () => setTimeout(() => this._messagesEndRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' }), 50); gptMenu = () => ( - <div className="btns-wrapper-gpt"> - <Button - tooltip="Ask Firefly to create images" - text="Ask Firefly" - onClick={() => this.setMode(GPTPopupMode.FIREFLY)} - color={SettingsManager.userColor} - background={SettingsManager.userVariantColor} - type={Type.TERT} - style={{ - width: '100%', - height: '40%', - textAlign: 'center', - color: '#ffffff', - fontSize: '16px', - marginBottom: '10px', - }} - /> - <Button - tooltip="Ask GPT to sort, tag, define, or filter your Docs!" - text="Ask GPT" - onClick={() => this.setMode(GPTPopupMode.USER_PROMPT)} - color={SettingsManager.userColor} - background={SettingsManager.userVariantColor} - type={Type.TERT} - style={{ - width: '100%', - height: '40%', - textAlign: 'center', - color: '#ffffff', - fontSize: '16px', - marginBottom: '10px', - }} - /> - <Button - tooltip="Test your knowledge by verifying answers with ChatGPT" - text="Take Quiz" - onClick={() => { - this._conversationArray = ['Define the selected card!']; - this.setMode(GPTPopupMode.QUIZ_RESPONSE); - this.onQuizRandom?.(); - }} - color={SettingsManager.userColor} - background={SettingsManager.userVariantColor} - type={Type.TERT} - style={{ - width: '100%', - height: '40%', - textAlign: 'center', - color: '#ffffff', - fontSize: '16px', - }} - /> + <div style={{ display: 'flex', maxHeight: 'calc(100% - 32px)', overflow: 'auto' }}> + <div className="btns-wrapper-gpt"> + <Button + tooltip="Ask Firefly to create images" + text="Ask Firefly" + onClick={() => this.setMode(GPTPopupMode.FIREFLY)} + color={SettingsManager.userColor} + background={SettingsManager.userVariantColor} + type={Type.TERT} + style={{ + width: '100%', + height: '40%', + textAlign: 'center', + color: '#ffffff', + fontSize: '16px', + marginBottom: '10px', + }} + /> + <Button + tooltip="Ask GPT to sort, tag, define, or filter your Docs!" + text="Ask GPT" + onClick={() => this.setMode(GPTPopupMode.USER_PROMPT)} + color={SettingsManager.userColor} + background={SettingsManager.userVariantColor} + type={Type.TERT} + style={{ + width: '100%', + height: '40%', + textAlign: 'center', + color: '#ffffff', + fontSize: '16px', + marginBottom: '10px', + }} + /> + <Button + tooltip="Test your knowledge by verifying answers with ChatGPT" + text="Take Quiz" + onClick={() => { + this._conversationArray = ['Define the selected card!']; + this.setMode(GPTPopupMode.QUIZ_RESPONSE); + this.onQuizRandom?.(); + }} + color={SettingsManager.userColor} + background={SettingsManager.userVariantColor} + type={Type.TERT} + style={{ + width: '100%', + height: '40%', + textAlign: 'center', + color: '#ffffff', + fontSize: '16px', + }} + /> + </div> </div> ); callGpt = action((mode: GPTPopupMode) => { this.setGptProcessing(true); + const reset = action(() => { + this.setGptProcessing(false); + this._userPrompt = ''; + this._quizAnswer = ''; + }); switch (mode) { case GPTPopupMode.FIREFLY: this._fireflyArray.push(this._userPrompt); - return this.generateFireflyImage(this._userPrompt).then(action(() => (this._userPrompt = ''))); + return this.generateFireflyImage(this._userPrompt).then(reset); case GPTPopupMode.USER_PROMPT: this._conversationArray.push(this._userPrompt); - return this.generateUserPromptResponse(this._userPrompt).then(action(() => (this._userPrompt = ''))); + return this.generateUserPromptResponse(this._userPrompt).then(reset); case GPTPopupMode.QUIZ_RESPONSE: this._conversationArray.push(this._quizAnswer); - return this.generateQuizAnswerAnalysis(DocumentView.SelectedDocs().lastElement(), this._quizAnswer).then(action(() => (this._quizAnswer = ''))); + return this.generateQuizAnswerAnalysis(DocumentView.SelectedDocs().lastElement(), this._quizAnswer).then(reset); } }); @@ -490,18 +497,20 @@ export class GPTPopup extends ObservableReactComponent<object> { }; gptUserInput = () => ( - <div className="btns-wrapper-gpt"> - <div className="chat-wrapper"> - <div className="chat-bubbles"> - {(this._mode === GPTPopupMode.FIREFLY ? this._fireflyArray : this._conversationArray).map((message, index) => ( - <div key={index} className={`chat-bubble ${index % 2 === 1 ? 'user-message' : 'chat-message'}`}> - {message} - </div> - ))} - {this._gptProcessing && <div className="chat-bubble chat-message">...</div>} - </div> + <div style={{ display: 'flex', maxHeight: 'calc(100% - 32px)', overflow: 'auto' }}> + <div className="btns-wrapper-gpt"> + <div className="chat-wrapper"> + <div className="chat-bubbles"> + {(this._mode === GPTPopupMode.FIREFLY ? this._fireflyArray : this._conversationArray).map((message, index) => ( + <div key={index} className={`chat-bubble ${index % 2 === 1 ? 'user-message' : 'chat-message'}`}> + {message} + </div> + ))} + {this._gptProcessing && <div className="chat-bubble chat-message">...</div>} + </div> - <div ref={this._messagesEndRef} style={{ height: '100px' }} /> + <div ref={this._messagesEndRef} style={{ height: '40px' }} /> + </div> </div> </div> ); @@ -520,7 +529,7 @@ export class GPTPopup extends ObservableReactComponent<object> { onChange={e => onChange(e.target.value)} onKeyDown={e => this.handleKeyPress(e, this._mode)} type="text" - style={{ color: SnappingManager.userColor }} + style={{ color: 'black' }} placeholder={placeholder} /> <Button // @@ -744,7 +753,7 @@ export class GPTPopup extends ObservableReactComponent<object> { onClick={() => this._collectionContext && Doc.setDocFilter(this._collectionContext, 'tags', GPTPopup.ChatTag, 'remove')} /> {[GPTPopupMode.USER_PROMPT, GPTPopupMode.QUIZ_RESPONSE, GPTPopupMode.FIREFLY].includes(this._mode) && ( - <IconButton color={SettingsManager.userVariantColor} background={SettingsManager.userColor} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} /> + <IconButton color={SettingsManager.userVariantColor} background={SettingsManager.userColor} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={action(() => (this._mode = GPTPopupMode.GPT_MENU))} /> )} </> )} @@ -753,12 +762,12 @@ export class GPTPopup extends ObservableReactComponent<object> { render() { return ( - <div className="gptPopup-summary-box" style={{ background: SnappingManager.userColor, color: SnappingManager.userBackgroundColor, display: SnappingManager.ChatVisible ? 'flex' : 'none', overflow: 'auto' }}> + <div className="gptPopup-summary-box" style={{ background: SnappingManager.userColor, color: SnappingManager.userBackgroundColor, display: SnappingManager.ChatVisible ? 'flex' : 'none' }}> {(() => { //prettier-ignore switch (this._mode) { case GPTPopupMode.USER_PROMPT: return this.promptBox("ASK", this._userPrompt, this.setUserPrompt, 'Ask GPT to sort, tag, define, or filter your documents for you!'); - case GPTPopupMode.FIREFLY: return this.promptBox("CREATE", this._userPrompt, this.setUserPrompt, StrCast(DocumentView.Selected().lastElement()?.Document.ai_firefly_prompt, 'Ask Firefly to generate images')); + case GPTPopupMode.FIREFLY: return this.promptBox("CREATE", this._userPrompt, this.setUserPrompt, StrCast(DocumentView.Selected().lastElement()?.Document.ai_prompt, 'Ask Firefly to generate images')); case GPTPopupMode.QUIZ_RESPONSE: return this.promptBox("QUIZ", this._quizAnswer, this.setQuizAnswer, 'Describe/answer the selected document!'); case GPTPopupMode.GPT_MENU: return this.menuBox(); case GPTPopupMode.SUMMARY: return this.summaryBox(); diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index 2c69284db..f773957e7 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -51,19 +51,19 @@ export class DrawingFillHandler { if (error.includes('Dropbox') && confirm('Create image failed. Try authorizing DropBox?\r\n' + error.replace(/^[^"]*/, ''))) { return DrawingFillHandler.authorizeDropbox(); } - const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { title: StrCast(drawing.title) + ' AI Images', _width: 400, _height: 400 }); - drawing.$ai_firefly_generatedDocs = genratedDocs; + const genratedDocs = DocCast(drawing.ai_generatedDocs) ?? Docs.Create.MasonryDocument([], { title: StrCast(drawing.title) + ' AI Images', _width: 400, _height: 400 }); + drawing.$ai_generatedDocs = genratedDocs; (res as Upload.ImageInformation[]).map(info => Doc.AddDocToList( genratedDocs, undefined, Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { ai: 'firefly', + ai_prompt: newPrompt, tags: new List<string>(['@ai']), title: newPrompt, _data_usePath: 'alternate:hover', data_alternates: new List<Doc>([drawing]), - ai_firefly_prompt: newPrompt, _width: 500, data_nativeWidth: info.nativeWidth, data_nativeHeight: info.nativeHeight, diff --git a/src/client/views/smartdraw/SmartDrawHandler.scss b/src/client/views/smartdraw/SmartDrawHandler.scss index cca7d77c7..e80f1122b 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.scss +++ b/src/client/views/smartdraw/SmartDrawHandler.scss @@ -73,5 +73,6 @@ .edit-box { display: flex; flex-direction: row; + color: black; } } diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index 4f0cd3978..3976ec39e 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -61,7 +61,6 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { static Instance: SmartDrawHandler; private _lastInput: DrawingOptions = { text: '', complexity: 5, size: 350, autoColor: true, x: 0, y: 0 }; - private _lastResponse: string = ''; private _selectedDocs: Doc[] = []; @observable private _display: boolean = false; @@ -97,7 +96,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { CollectionFreeForm, FormattedTextBox, StickerPalette) to define how a drawing document should be added or removed in their respective locations (to the freeform canvas, to the sticker palette's preview, etc.) */ - public AddDrawing: (doc: Doc, opts: DrawingOptions, gptRes: string, x?: number, y?: number) => void = unimplementedFunction; + public AddDrawing: (doc: Doc, opts: DrawingOptions, x?: number, y?: number) => void = unimplementedFunction; public RemoveDrawing: (useLastContainer: boolean, doc?: Doc) => void = unimplementedFunction; /** * This creates the ink document that represents a drawing, so it goes through the strokes that make up the drawing, @@ -105,7 +104,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { * classes to customize the way the drawing docs get created. For example, the freeform canvas has a different way of * defining document bounds, so CreateDrawingDoc is redefined when that class calls gpt draw functions. */ - public static CreateDrawingDoc: (strokeList: [InkData, string, string][], opts: DrawingOptions, gptRes: string, containerDoc?: Doc) => Doc | undefined = (strokeList: [InkData, string, string][], opts: DrawingOptions) => { + public static CreateDrawingDoc: (strokeList: [InkData, string, string][], opts: DrawingOptions, gptRes: string, containerDoc?: Doc) => Doc | undefined = (strokeList: [InkData, string, string][], opts: DrawingOptions, gptRes: string) => { const drawing: Doc[] = []; strokeList.forEach((stroke: [InkData, string, string]) => { const bounds = InkField.getBounds(stroke[0]); @@ -130,7 +129,14 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { drawing.push(inkDoc); }); - return MarqueeView.getCollection(drawing, undefined, true, { left: 1, top: 1, width: 1, height: 1 }); + const drawn = MarqueeView.getCollection(drawing, undefined, true, { left: 1, top: 1, width: 1, height: 1 }); + + drawn.$ai_drawing = true; + drawn.$ai_drawing_complexity = opts.complexity; + drawn.$ai_drawing_colored = opts.autoColor; + drawn.$ai_drawing_size = opts.size; + drawn.$ai_drawing_data = gptRes; + return drawn; }; @action @@ -146,15 +152,16 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { * the regenerate popup show by user command. */ @action - displayRegenerate = (x: number, y: number) => { + displayRegenerate = (x: number, y: number, scale: number) => { this._selectedDocs = [DocumentView.SelectedDocs()?.lastElement()]; [this._pageX, this._pageY] = [x, y]; + this._scale = scale; this._display = false; this.ShowRegenerate = true; this._showEditBox = false; const docData = this._selectedDocs[0]; - this._lastResponse = StrCast(docData.$drawingData); - 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 }; + this._regenInput = StrCast(docData.$ai_prompt, StrCast(docData.title)); + this._lastInput = { text: StrCast(docData.$ai_prompt), complexity: NumCast(docData.$ai_drawing_complexity), size: NumCast(docData.$ai_drawing_size), autoColor: BoolCast(docData.$ai_drawing_colored), x: this._pageX, y: this._pageY }; }; /** @@ -168,9 +175,6 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { this._isLoading = false; this._showOptions = false; this._userInput = ''; - this._complexity = 5; - this._size = 350; - this._autoColor = true; Doc.ActiveTool = InkTool.None; } }; @@ -184,7 +188,6 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { this.ShowRegenerate = false; this._isLoading = false; this._regenInput = ''; - this._lastInput = { text: '', complexity: 5, size: 350, autoColor: true, x: 0, y: 0 }; } }; @@ -208,7 +211,9 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { this._isLoading = true; this._canInteract = false; if (this.ShowRegenerate) { - await this.regenerate(this._selectedDocs, undefined, undefined, this._regenInput).then(action(() => (this._showEditBox = false))); + this._lastInput.x = X; + this._lastInput.y = Y; + await this.regenerate(this._selectedDocs).then(action(() => (this._showEditBox = false))); } else { this._showOptions = false; try { @@ -234,13 +239,15 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { */ drawWithGPT = async (screenPt: { 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: screenPt.X, y: screenPt.Y }; + this._lastInput = { text: input, complexity, size, autoColor, x: screenPt.X, y: screenPt.Y }; const res = await gptAPICall(`"${input}", "${complexity}", "${size}"`, GPTCallType.DRAW, undefined, true); if (res) { const strokeData = await this.parseSvg(res, { X: 0, Y: 0 }, false, autoColor); const drawingDoc = strokeData && SmartDrawHandler.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); - drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res, screenPt.X, screenPt.Y); - drawingDoc && this._selectedDocs.push(drawingDoc); + if (drawingDoc) { + this.AddDrawing(drawingDoc, this._lastInput, screenPt.X, screenPt.Y); + this._selectedDocs.push(drawingDoc); + } return strokeData; } else { console.error('GPT call failed'); @@ -255,7 +262,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { createImageWithFirefly = (input: string, seed?: number): Promise<FireflyImageData | Doc | undefined> => { this._lastInput.text = input; return SmartDrawHandler.CreateWithFirefly(input, this._imgDims, seed).then(doc => { - doc instanceof Doc && this.AddDrawing(doc, this._lastInput, input, this._pageX, this._pageY); + doc instanceof Doc && this.AddDrawing(doc, this._lastInput, this._pageX, this._pageY); return doc; }); }; /** @@ -301,8 +308,8 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { _width: Math.min(400, dims.width), _height: (Math.min(400, dims.width) * dims.height) / dims.width, ai: 'firefly', - ai_firefly_seed: +(newseed ?? 0), - ai_firefly_prompt: input, + ai_prompt_seed: +(newseed ?? 0), + ai_prompt: input, }); }) .catch(e => { @@ -316,33 +323,30 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { * @param doc the drawing Docs to regenerate */ @action - regenerate = (drawingDocs: Doc[], lastInput?: DrawingOptions, lastResponse?: string, regenInput?: string, changeInPlace?: boolean) => { - if (lastInput) this._lastInput = lastInput; - if (lastResponse) this._lastResponse = lastResponse; + regenerate = (drawingDocs: Doc[], regenInput?: string, changeInPlace?: boolean) => { if (regenInput) this._regenInput = regenInput; return Promise.all( drawingDocs.map(async doc => { switch (doc.type) { case DocumentType.IMG: { const func = changeInPlace ? this.recreateImageWithFirefly : this.createImageWithFirefly; - const newPrompt = doc.ai_firefly_prompt ? `${doc.ai_firefly_prompt} ~~~ ${this._regenInput}` : this._regenInput; - return this._regenInput ? func(newPrompt, NumCast(doc?.ai_firefly_seed)) : func(this._lastInput.text || StrCast(doc.ai_firefly_prompt)); + const newPrompt = doc.ai_prompt && doc.ai_prompt !== this._regenInput ? `${doc.ai_prompt} ~~~ ${this._regenInput}` : this._regenInput; + return this._regenInput ? func(newPrompt, NumCast(doc?.ai_prompt_seed)) : func(this._lastInput.text || StrCast(doc.ai_prompt)); } case DocumentType.COL: { try { const res = await (async () => { if (this._regenInput) { - const prompt = `This is your previously generated svg code: ${this._lastResponse} for the user input "${this._lastInput.text}". Please regenerate it with the provided specifications.`; + const prompt = `This is your previously generated svg code: ${doc.$ai_drawing_data} for the user input "${doc.ai_prompt}". Please regenerate it with the provided specifications.`; this._lastInput.text = `${this._lastInput.text} ~~~ ${this._regenInput}`; return gptAPICall(`"${this._regenInput}"`, GPTCallType.DRAW, prompt, true); } - return gptAPICall(`"${this._lastInput.text}", "${this._lastInput.complexity}", "${this._lastInput.size}"`, GPTCallType.DRAW, undefined, true); + return gptAPICall(`"${doc.$ai_prompt}", "${doc.$ai_drawing_complexity}", "${doc.$ai_drawing_size}"`, GPTCallType.DRAW, undefined, true); })(); if (res) { - const strokeData = await this.parseSvg(res, { X: this._lastInput.x ?? 0, Y: this._lastInput.y ?? 0 }, true, lastInput?.autoColor || this._autoColor); - this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, doc); + const strokeData = await this.parseSvg(res, { X: 0, Y: 0 }, true, this._autoColor); const drawingDoc = strokeData && SmartDrawHandler.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); - drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); + drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, this._lastInput.x, this._lastInput.y); } else { console.error('GPT call failed'); } @@ -363,7 +367,6 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { const svg = res.match(/<svg[^>]*>([\s\S]*?)<\/svg>/g); if (svg) { - this._lastResponse = svg[0]; const svgObject = await parse(svg[0]); console.log(res, svgObject); const svgStrokes: INode[] = svgObject.children; @@ -398,7 +401,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { */ colorWithGPT = async (drawing: Doc) => { const img = await DocumentView.GetDocImage(drawing); - const { href } = ImageCast(img).url; + const { href } = ImageCast(img)?.url ?? { href: '' }; const hrefParts = href.split('.'); const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`; try { @@ -664,7 +667,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { <div className="regenerate-box"> <IconButton tooltip="Regenerate" - icon={this._isLoading && this._regenInput === '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <FontAwesomeIcon icon={'rotate'} />} + icon={this._isLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <FontAwesomeIcon icon={'rotate'} />} color={SettingsManager.userColor} onClick={() => this.handleSendClick(this._pageX, this._pageY)} /> diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx index 0e234e966..6ef3d26ad 100644 --- a/src/client/views/smartdraw/StickerPalette.tsx +++ b/src/client/views/smartdraw/StickerPalette.tsx @@ -9,7 +9,7 @@ import ReactLoading from 'react-loading'; import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils'; import { emptyFunction, numberRange } from '../../../Utils'; import { Doc, DocListCast, returnEmptyDoclist } from '../../../fields/Doc'; -import { ImageCast, NumCast } from '../../../fields/Types'; +import { ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; @@ -147,9 +147,9 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps SmartDrawHandler.Instance.AddDrawing = this.addDrawing; this._canInteract = false; Promise.all( - numberRange(3).map(i => { + numberRange(3).map(() => { return this._showRegenerate - ? SmartDrawHandler.Instance.regenerate(prevDrawings, this._opts, this._gptRes[i], this._userInput) + ? SmartDrawHandler.Instance.regenerate(prevDrawings, this._userInput) : SmartDrawHandler.Instance.drawWithGPT({ X: 0, Y: 0 }, this._userInput, this._opts.complexity || 0, this._opts.size || 0, !!this._opts.autoColor); }) ).then(() => { @@ -161,8 +161,8 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps }); @action - addDrawing = (drawing: Doc, opts: DrawingOptions, gptRes: string) => { - this._gptRes.push(gptRes); + addDrawing = (drawing: Doc) => { + this._gptRes.push(StrCast(drawing.$ai_drawing_data)); drawing.$freeform_fitContentsToBox = true; Doc.AddDocToList(this._props.Doc, 'data', drawing); }; @@ -176,7 +176,7 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps const cIndex = NumCast(this._props.Doc.carousel_index); const focusedDrawing = DocListCast(this._props.Doc.data)[cIndex]; focusedDrawing.$title = this._opts.text?.match(/^(.*?)~~~.*$/)?.[1] || this._opts.text; - focusedDrawing.$ai_drawing_input = this._opts.text; + focusedDrawing.$ai_prompt = this._opts.text; focusedDrawing.$ai_drawing_complexity = this._opts.complexity; focusedDrawing.$ai_drawing_colored = this._opts.autoColor; focusedDrawing.$ai_drawing_size = this._opts.size; |
