From 12f5a435ee6476e2e07ded0c9cdd597c70ca8af0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 13 Jan 2025 22:26:35 -0500 Subject: changing ai for images and collections to work mostly the same way. fixes for document view/collection/images to keep things working when the ai editor view reduces the rendered document size. fixed so that freeform views overlaid on images/vieos/etc have the ui menu items of collections. --- src/ClientUtils.ts | 23 +- src/client/util/SelectionManager.ts | 3 +- src/client/views/GlobalKeyHandler.ts | 8 +- src/client/views/PropertiesView.tsx | 48 +++-- src/client/views/TagsView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.scss | 15 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 236 ++++++++++----------- src/client/views/nodes/DocumentView.scss | 1 - src/client/views/nodes/DocumentView.tsx | 48 +++-- src/client/views/nodes/ImageBox.scss | 19 +- src/client/views/nodes/ImageBox.tsx | 113 ++++++---- src/client/views/smartdraw/DrawingFillHandler.tsx | 17 +- src/client/views/smartdraw/SmartDrawHandler.tsx | 8 +- src/server/ApiManagers/UploadManager.ts | 22 +- 14 files changed, 316 insertions(+), 247 deletions(-) (limited to 'src') diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index 8f62b9060..e5dbb81db 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -144,15 +144,20 @@ export namespace ClientUtils { export async function convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false, replaceRootFilename: string | undefined = undefined) { try { const posting = ClientUtils.prepend('/uploadURI'); - const returnedUri = await rp.post(posting, { - body: { - uri: imageUri, - name: returnedFilename, - nosuffix, - replaceRootFilename, - }, - json: true, - }); + const returnedUri = await rp + .post(posting, { + body: { + uri: imageUri, + name: returnedFilename, + nosuffix, + replaceRootFilename, + }, + json: true, + }) + .catch(e => { + alert('Data URI Error: ' + e.toString()); + return undefined; + }); return returnedUri; } catch (e) { console.log('ConvertDataURI :' + e); diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 1ab84421c..a1f2849cd 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -88,7 +88,8 @@ ScriptingGlobals.add(function SelectedDocType(type: string, expertMode: boolean, return DocumentView.Selected().lastElement()?._props.renderDepth === 0; } const selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(DocumentView.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement()); - return selected?.type === type || selected?.type_collection === type || !type; + const matchOverlayFreeform = type === CollectionViewType.Freeform && DocumentView.Selected().lastElement()?.ComponentView?.annotationKey; + return matchOverlayFreeform || selected?.type === type || selected?.type_collection === type || !type; }); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function deselectAll() { diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index f16338361..b200aff65 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -169,10 +169,10 @@ export class KeyManager { return { stopPropagation: true, preventDefault: true }; } break; - case 'arrowleft': return this.nudge(-1,0, 'nudge left') - case 'arrowright': return this.nudge(1,0, 'nudge right'); - case 'arrowup': return this.nudge(0, -1, 'nudge up'); - case 'arrowdown': return this.nudge(0, 1, 'nudge down'); + case 'arrowleft': return (e.target as any).type !== 'text' && this.nudge(-1, 0, 'nudge left') // if target is an input box, then we don't want to nudge any Docs since we're justing moving within the text itself. + case 'arrowright': return (e.target as any).type !== 'text' && this.nudge( 1, 0, 'nudge right'); + case 'arrowup': return (e.target as any).type !== 'text' && this.nudge(0, -1, 'nudge up'); + case 'arrowdown': return (e.target as any).type !== 'text' && this.nudge(0, 1, 'nudge down'); default: } // prettier-ignore diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index e52189f56..42aa6782f 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -111,6 +111,7 @@ export class PropertiesView extends ObservableReactComponent { - !isNaN(val) && (this.refStrength = val); - }); return (
- {!targetDoc.layout_isSvg && this.containsInkDoc && ( -
-
- } - iconPlacement="left" - align="flex-start" - fillWidth - toggleType={ToggleType.BUTTON} - onClick={undoable(() => DrawingFillHandler.drawingToImage(targetDoc, this.refStrength, StrCast(targetDoc.title) !== 'grouping' ? StrCast(targetDoc.title) : ''), 'createImage')} - /> -
-
{strength}
+
+ {!targetDoc.layout_isSvg && this.containsInkDoc && (
-
- )} + )} +
{ + !isNaN(val) && (this.refStrength = val); + }); + const targetDoc = this.selectedLayoutDoc; return ( <> { this.openAppearance = bool; }} onDoubleClick={this.CloseAll}> {this.selectedStrokes.length ? this.inkEditor : null} + { this.openFirefly = bool; }} onDoubleClick={this.CloseAll}> + <> +
+ } + iconPlacement="left" + align="flex-start" + fillWidth + toggleType={ToggleType.BUTTON} + onClick={undoable(() => DrawingFillHandler.drawingToImage(targetDoc, this.refStrength, StrCast(targetDoc.title) !== 'grouping' ? StrCast(targetDoc.title) : ''), 'createImage')} + /> +
+
{strength}
+ +
{ this.openTransform = bool; }} onDoubleClick={this.CloseAll}> {this.transformEditor} diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 21b6a76c7..0edd89204 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -366,7 +366,7 @@ export class TagsView extends ObservableReactComponent { backgroundColor: this.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT, borderColor: this.isEditing ? Colors.BLACK : Colors.TRANSPARENT, position: 'relative', - top: this._props.Views.length > 1 ? 25 : `calc(-${this.InsetDist} * ${1 / this.currentScale}px)`, + top: this._props.Views.lastElement()._showAIEditor ? undefined : this._props.Views.length > 1 ? 25 : `calc(-${this.InsetDist} * ${1 / this.currentScale}px)`, }}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index dff2cb282..82887a7a5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -312,10 +312,14 @@ .collectionfreeformview-aiView-prompt { height: 25px; + width: 100%; } .collectionFreeFormView-aiView-strength { text-align: center; + align-items: center; + display: flex; + width: 150px; } .collectionFreeformView-aiView-options-container, @@ -323,13 +327,20 @@ text-align: start; font-weight: normal; padding: 5px; + width: 100%; + display: flex; + .collectionFreeformView-aiView-subtitle { + margin: auto; + width: 40px; + } } .collectionFreeformView-aiView-options, .collectionFreeFormView-aiView-regenerate { display: flex; flex-direction: row; - gap: 10px; - justify-content: center; align-items: center; + align-items: center; + width: 100%; + gap: 10px; } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 112bfd178..5b4f81379 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1877,8 +1877,8 @@ export class CollectionFreeFormView extends CollectionSubView { - const showRegenerate = this.Document[DocData].ai; return (
e.stopPropagation()}> - Edit Collection with AI - {showRegenerate && ( -
- Regenerate AI Image -
- this._canInteract && (this._regenInput = e.target.value))} - placeholder="Prompt (Optional)" - /> -
-
- )}
- Create Image with Firefly + Firefly:
- this._canInteract && (this._drawingFillInput = e.target.value))} /> + this._canInteract && (this._drawingFillInput = e.target.value))} + />
- Reference Strength + Similarity : } + icon={this._drawingFillLoading ? : } iconPlacement="right" onClick={undoable( action(() => { this._drawingFillLoading = true; - DrawingFillHandler.drawingToImage( - this.props.Document, - this._fireflyRefStrength, - this._drawingFillInput !== '' ? this._drawingFillInput : StrCast(this.props.Document.title) !== 'grouping' ? StrCast(this.props.Document.title) : '' + DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._drawingFillInput || StrCast(this.Document.title))?.then( + action(() => { + this._drawingFillLoading = false; + }) ); - this._drawingFillInput = ''; - this._drawingFillLoading = false; }), 'create image' )} />
+
+ Regenerate +
+ this._canInteract && (this._regenInput = e.target.value))} + placeholder="..under development.." + /> +
+
); }; @@ -2311,83 +2311,81 @@ export class CollectionFreeFormView extends CollectionSubView -
{ - this.createDashEventsTarget(r); - this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); - this._oldWheel = r; - // prevent wheel events from passivly propagating up through containers - r?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); - r?.addEventListener('mouseleave', this.onMouseLeave); - r?.addEventListener('mouseenter', this.onMouseEnter); - }} - onWheel={this.onPointerWheel} - onClick={this.onClick} - onPointerDown={this.onPointerDown} - onPointerMove={this.onCursorMove} - onDrop={this.onExternalDrop} - onDragOver={e => e.preventDefault()} - onContextMenu={this.onContextMenu} - style={{ - pointerEvents: this._props.isContentActive() && SnappingManager.IsDragging ? 'all' : this._props.pointerEvents?.(), - textAlign: this.isAnnotationOverlay ? 'initial' : undefined, - transform: `scale(${this.nativeDimScaling})`, - width: `${100 / this.nativeDimScaling}%`, - height: this._props.getScrollHeight?.() ?? `${100 / this.nativeDimScaling}%`, - }}> - {Doc.ActiveTool === InkTool.Eraser && Doc.ActiveEraser === InkEraserTool.Radius && this._showEraserCircle && ( -
{ + this.createDashEventsTarget(r); + this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); + this._oldWheel = r; + // prevent wheel events from passivly propagating up through containers + r?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + r?.addEventListener('mouseleave', this.onMouseLeave); + r?.addEventListener('mouseenter', this.onMouseEnter); + }} + onWheel={this.onPointerWheel} + onClick={this.onClick} + onPointerDown={this.onPointerDown} + onPointerMove={this.onCursorMove} + onDrop={this.onExternalDrop} + onDragOver={e => e.preventDefault()} + onContextMenu={this.onContextMenu} + style={{ + pointerEvents: this._props.isContentActive() && SnappingManager.IsDragging ? 'all' : this._props.pointerEvents?.(), + textAlign: this.isAnnotationOverlay ? 'initial' : undefined, + transform: `scale(${this.nativeDimScaling})`, + width: `${100 / this.nativeDimScaling}%`, + height: this._props.getScrollHeight?.() ?? `${100 / this.nativeDimScaling}%`, + }}> + {Doc.ActiveTool === InkTool.Eraser && Doc.ActiveEraser === InkEraserTool.Radius && this._showEraserCircle && ( +
+ )} + {this.paintFunc ? ( + // need this so that any live dashfieldviews will update the underlying text that the code eval reads + ) : this._lightboxDoc ? ( +
+ - )} - {this.paintFunc ? ( - // need this so that any live dashfieldviews will update the underlying text that the code eval reads - ) : this._lightboxDoc ? ( -
- -
- ) : ( - <> - {this._firstRender ? this.placeholder : this.marqueeView} - {this._props.noOverlay ? null : } - {!this.GroupChildDrag ? null :
} - - )} -
+
+ ) : ( + <> + {this._firstRender ? this.placeholder : this.marqueeView} + {this._props.noOverlay ? null : } + {!this.GroupChildDrag ? null :
} + + )}
); } diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index a3d47290a..8e7307d23 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -280,7 +280,6 @@ .documentView-editorView { width: 100%; - overflow-y: scroll; justify-items: center; background-color: rgb(223, 223, 223); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 640d27aa1..e37658ca5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -85,7 +85,7 @@ export interface DocumentViewProps extends FieldViewSharedProps { reactParent?: React.Component; // parent React component view (see CollectionFreeFormDocumentView) } @observer -export class DocumentViewInternal extends DocComponent() { +export class DocumentViewInternal extends DocComponent() { // this makes mobx trace() statements more descriptive public get displayName() { return 'DocumentViewInternal(' + this.Document.title + ')'; } // prettier-ignore public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered. @@ -109,7 +109,7 @@ export class DocumentViewInternal extends DocComponent(); private _titleRef = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; - constructor(props: FieldViewProps & DocumentViewProps) { + constructor(props: FieldViewProps & DocumentViewProps & { showAIEditor: boolean }) { super(props); makeObservable(this); } @@ -122,12 +122,6 @@ export class DocumentViewInternal extends DocComponent> = undefined; // needs to be accessed from DocumentView wrapper class @observable _animateScaleTime: Opt = undefined; // milliseconds for animating between views. defaults to 300 if not uset @observable _animateScalingTo = 0; - @observable public _showAIEditor: boolean = false; - - @action - showAIEditor() { - this._showAIEditor = !this._showAIEditor; - } get _contentDiv() { return this._mainCont.current; } // prettier-ignore get _docView() { return this._props.DocumentView?.(); } // prettier-ignore @@ -689,7 +683,11 @@ export class DocumentViewInternal extends DocComponent this._rootSelected; panelHeight = () => this._props.PanelHeight() - this.headerMargin; - screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin); + screenToLocalContent = () => + this._props + .ScreenToLocalTransform() + .translate(0, -this.headerMargin) + .scale(this._props.showAIEditor ? (this._props.PanelHeight() || 1) / this.rph() : 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)); } // prettier-ignore setContentView = action((view: ViewBoxInterface) => { this._componentView = view; }); // prettier-ignore @@ -715,9 +713,9 @@ export class DocumentViewInternal extends DocComponent this._props.PanelWidth() - this._aiWinHeight; - rph = () => this.panelHeight() - this._aiWinHeight; + @observable _aiWinHeight = 95; + rpw = () => (this.rph() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1); + rph = () => Math.max(10, this._props.PanelHeight() - this._aiWinHeight); @computed get viewBoxContents() { TraceMobx(); const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; @@ -728,8 +726,8 @@ export class DocumentViewInternal extends DocComponent
- {!this._showAIEditor ? null : ( + {!this._props.showAIEditor ? null : ( <>
() { @computed private get nativeScaling() { if (this.shouldNotScale) return 1; const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0; - if (this.layout_fitWidth || this._props.PanelHeight() / (this.effectiveNativeHeight || 1) > this._props.PanelWidth() / (this.effectiveNativeWidth || 1)) { - return Math.max(minTextScale, this._props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or layout_fitWidth + const ai = this._showAIEditor && this.nativeWidth === this.layoutDoc.width ? 95 : 0; + const effNW = Math.max(this.effectiveNativeWidth - ai, 1); + const effNH = Math.max(this.effectiveNativeHeight - ai, 1); + if (this.layout_fitWidth || (this._props.PanelHeight() - ai) / effNH > (this._props.PanelWidth() - ai) / effNW) { + return Math.max(minTextScale, (this._props.PanelWidth() - ai) / effNW); // width-limited or layout_fitWidth } - return Math.max(minTextScale, this._props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled + return Math.max(minTextScale, (this._props.PanelHeight() - ai) / effNH); // height-limited or unscaled } @computed private get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this._props.PanelWidth(); @@ -1231,7 +1232,7 @@ export class DocumentView extends DocComponent() { } @computed get layout_fitWidth() { - return this._props.fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth; + return this._showAIEditor ? false : (this._props.fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth); } @computed get anchorViewDoc() { return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document.link_anchor_2) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document.link_anchor_1) : undefined; @@ -1327,9 +1328,11 @@ export class DocumentView extends DocComponent() { } }; + @observable public _showAIEditor: boolean = false; + @action public toggleAIEditor = () => { - this._docViewInternal && this._docViewInternal.showAIEditor(); + this._showAIEditor = !this._showAIEditor; }; public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => { @@ -1516,6 +1519,7 @@ export class DocumentView extends DocComponent() { }}> () { @observable private _regenInput = ''; @observable private _canInteract = true; @observable private _regenerateLoading = false; - @observable private _prevImgs: FireflyImageData[] = []; + @observable private _prevImgs: FireflyImageData[] = StrCast(this.Document.ai_firefly_history) ? JSON.parse(StrCast(this.Document.ai_firefly_history)) : []; constructor(props: FieldViewProps) { super(props); @@ -386,8 +387,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { : UpdateIcon( this.layoutDoc[Id] + '_icon_' + new Date().getTime(), contentDiv, - usePanelDimensions ? this._props.PanelWidth() : NumCast(this.layoutDoc._width), - usePanelDimensions ? this._props.PanelHeight() : NumCast(this.layoutDoc._height), + usePanelDimensions || true ? this._props.PanelWidth() : NumCast(this.layoutDoc._width), + usePanelDimensions || true ? this._props.PanelHeight() : NumCast(this.layoutDoc._height), this._props.PanelWidth(), this._props.PanelHeight(), 0, @@ -543,65 +544,91 @@ export class ImageBox extends ViewBoxAnnotatableComponent() {
{ this.dataDoc[this.fieldKey] = new ImageField(img.pathname); this.dataDoc.ai_firefly_prompt = img.prompt; this.dataDoc.ai_firefly_seed = img.seed; }} /> - {img.prompt} + {img.prompt}
))}
); }; + @observable private _fireflyRefStrength = 0; componentAIView = () => { const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey])); - const showRegenerate = this.Document[DocData].ai; return (
- {showRegenerate && ( -
-
- this._canInteract && (this._regenInput = e.target.value))} - placeholder="Prompt (Optional)" - /> -
+
- )} +
- {showRegenerate && More Options: } + More Options: