From d4659e2bd3ddb947683948083232c26fb1227f39 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 2 May 2025 13:09:24 -0400
Subject: fixed up image outpaint dialog and added options for placementt.

---
 src/client/views/nodes/ImageBox.tsx | 156 +++++++++++++++++++++++++++++-------
 1 file changed, 127 insertions(+), 29 deletions(-)

(limited to 'src/client/views/nodes/ImageBox.tsx')

diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 31a135fa7..d16baada6 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,4 +1,4 @@
-import { Button, Colors, EditableText, Size, Type } from '@dash/components';
+import { Button, Colors, EditableText, IconButton, Size, Toggle, ToggleType, Type } from '@dash/components';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Slider, Tooltip } from '@mui/material';
 import axios from 'axios';
@@ -353,6 +353,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @action
     openOutpaintPrompt = () => {
+        this._outpaintVAlign = '';
+        this._outpaintAlign = '';
         this._showOutpaintPrompt = true;
     };
 
@@ -361,6 +363,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._showOutpaintPrompt = false;
     };
 
+    @action
+    cancelOutpaintPrompt = () => {
+        const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']);
+        const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']);
+        this.Document._width = origWidth;
+        this.Document._height = origHeight;
+        this._outpaintingInProgress = false;
+        this.closeOutpaintPrompt();
+    };
+
     @action
     handlePromptChange = (val: string | number) => {
         this._outpaintPromptInput = '' + val;
@@ -377,17 +389,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         const field = Cast(this.dataDoc[this.fieldKey], ImageField);
         if (!field) return;
 
-        const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']);
-        const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']);
-
         // Set flag that outpainting is in progress
         this._outpaintingInProgress = true;
 
         // Revert dimensions if prompt is blank (acts like Cancel)
         if (!customPrompt) {
-            this.Document._width = origWidth;
-            this.Document._height = origHeight;
-            this._outpaintingInProgress = false;
+            this.cancelOutpaintPrompt();
             return;
         }
 
@@ -410,11 +417,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>';
             this._mainCont?.appendChild(loadingOverlay);
 
+            const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']);
+            const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']);
             const response = await Networking.PostToServer('/outpaintImage', {
                 imageUrl: currentPath,
                 prompt: customPrompt,
                 originalDimensions: { width: Math.min(newWidth, origWidth), height: Math.min(newHeight, origHeight) },
                 newDimensions: { width: newWidth, height: newHeight },
+                halignment: this._outpaintAlign,
+                valignment: this._outpaintVAlign,
             });
 
             const error = ('error' in response && (response.error as string)) || '';
@@ -447,8 +458,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     this.Document[this.fieldKey + '_outpaintOriginalWidth'] = undefined;
                     this.Document[this.fieldKey + '_outpaintOriginalHeight'] = undefined;
                 } else {
-                    this.Document._width = origWidth;
-                    this.Document._height = origHeight;
+                    this.cancelOutpaintPrompt();
                     alert('Failed to receive a valid image URL from server.');
                 }
                 batch.end();
@@ -456,30 +466,106 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
             this._mainCont?.removeChild(loadingOverlay);
         } catch (error) {
-            console.error('Error during outpainting:', error);
-            this.Document._width = origWidth;
-            this.Document._height = origHeight;
-            alert('An error occurred while outpainting. Please try again.');
+            this.cancelOutpaintPrompt();
+            alert('An error occurred while outpainting.' + error);
         } finally {
             runInAction(() => (this._outpaintingInProgress = false));
         }
     };
 
-    componentUI = () =>
+    @observable _outpaintAlign = '';
+    @observable _outpaintVAlign = '';
+    @computed get outpaintVertical() {
+        return this._props.PanelWidth() / this._props.PanelHeight() < this.nativeSize.nativeWidth / this.nativeSize.nativeHeight;
+    }
+
+    componentUI = (/* boundsLeft: number, boundsTop: number*/) =>
         !this._showOutpaintPrompt ? null : (
-            <div key="imageBox-componentui" className="imageBox-regenerate-dialog" style={{ backgroundColor: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
-                <h3>Outpaint Image</h3>
-                <EditableText
-                    placeholder="Enter a prompt for extending the image:"
-                    setVal={val => this.handlePromptChange(val)}
-                    val={this._outpaintPromptInput}
-                    type={Type.TERT}
-                    color={SettingsManager.userColor}
-                    background={SettingsManager.userBackgroundColor}
-                />
-                <div className="buttons">
-                    <Button text="Cancel" type={Type.TERT} onClick={this.closeOutpaintPrompt} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} />
-                    <Button text="Generate" type={Type.TERT} onClick={this.submitOutpaintPrompt} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} />
+            <div
+                key="imageBox-componentui"
+                className="imageBox-regenerate-dialog"
+                style={{
+                    top: -70 + (this._props.DocumentView?.().getBounds?.top ?? 0),
+                    left: this._props.DocumentView?.().getBounds?.left ?? 0,
+                    backgroundColor: SettingsManager.userBackgroundColor,
+                    color: SettingsManager.userColor,
+                }}>
+                <div style={{ position: 'absolute', top: 5, right: 5 }}>
+                    <IconButton type={Type.TERT} onClick={this.cancelOutpaintPrompt} icon={<FontAwesomeIcon icon="times" color={'red'} />} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} />
+                </div>
+                <div>Outpaint Image</div>
+                <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
+                    <EditableText
+                        placeholder="Enter a prompt for extending the image:"
+                        setVal={val => this.handlePromptChange(val)}
+                        val={this._outpaintPromptInput}
+                        type={Type.TERT}
+                        color={SettingsManager.userColor}
+                        background={SettingsManager.userBackgroundColor}
+                    />
+                    <div className="buttons" style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
+                        <IconButton type={Type.TERT} onClick={this.submitOutpaintPrompt} icon={<AiOutlineSend />} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} />
+                        <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
+                            {this.outpaintVertical ? null : (
+                                <Toggle
+                                    type={Type.TERT}
+                                    toggleType={ToggleType.BUTTON}
+                                    toggleStatus={this._outpaintAlign === 'left' && this._outpaintVAlign === ''}
+                                    onClick={action(() => (this._outpaintAlign = 'left'))}
+                                    icon={<FontAwesomeIcon icon="chevron-left" color={SnappingManager.userColor} />}
+                                    color={SnappingManager.userColor}
+                                    background={SnappingManager.userVariantColor}
+                                />
+                            )}
+                            <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
+                                {!this.outpaintVertical ? null : (
+                                    <Toggle
+                                        type={Type.TERT}
+                                        toggleType={ToggleType.BUTTON}
+                                        toggleStatus={this._outpaintAlign === '' && this._outpaintVAlign === 'top'}
+                                        onClick={action(() => (this._outpaintVAlign = 'top'))}
+                                        icon={<FontAwesomeIcon icon="chevron-up" color={SnappingManager.userColor} />}
+                                        color={SnappingManager.userColor}
+                                        background={SnappingManager.userColor}
+                                    />
+                                )}
+                                <Toggle
+                                    type={Type.TERT}
+                                    toggleType={ToggleType.BUTTON}
+                                    toggleStatus={this._outpaintAlign === '' && this._outpaintVAlign === ''}
+                                    onClick={action(() => {
+                                        this._outpaintAlign = '';
+                                        this._outpaintVAlign = '';
+                                    })}
+                                    icon={<FontAwesomeIcon icon="bullseye" color={SnappingManager.userColor} />}
+                                    color={SnappingManager.userColor}
+                                    background={SnappingManager.userColor}
+                                />
+                                {!this.outpaintVertical ? null : (
+                                    <Toggle
+                                        type={Type.TERT}
+                                        toggleType={ToggleType.BUTTON}
+                                        toggleStatus={this._outpaintAlign === '' && this._outpaintVAlign === 'bottom'}
+                                        onClick={action(() => (this._outpaintVAlign = 'bottom'))}
+                                        icon={<FontAwesomeIcon icon="chevron-down" color={SnappingManager.userColor} />}
+                                        color={SnappingManager.userColor}
+                                        background={SnappingManager.userColor}
+                                    />
+                                )}
+                            </div>
+                            {this.outpaintVertical ? null : (
+                                <Toggle
+                                    type={Type.TERT}
+                                    toggleType={ToggleType.BUTTON}
+                                    toggleStatus={this._outpaintAlign === 'right' && this._outpaintVAlign === ''}
+                                    onClick={action(() => (this._outpaintAlign = 'right'))}
+                                    icon={<FontAwesomeIcon icon="chevron-right" color={SnappingManager.userColor} />}
+                                    color={SnappingManager.userColor}
+                                    background={SnappingManager.userColor}
+                                />
+                            )}
+                        </div>
+                    </div>
                 </div>
             </div>
         );
@@ -718,13 +804,25 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 })}
                 key={this.layoutDoc[Id]}
                 onPointerDown={this.marqueeDown}>
-                <div className="imageBox-fader" style={{ opacity: backAlpha }}>
+                <div
+                    className="imageBox-fader"
+                    style={{
+                        opacity: backAlpha,
+                        flexDirection: this._outpaintVAlign ? 'row' : 'column',
+                        alignItems: this._outpaintAlign === 'left' || this._outpaintVAlign === 'top' ? 'flex-start' : this._outpaintAlign === 'right' || this._outpaintVAlign === 'bottom' ? 'flex-end' : undefined,
+                    }}>
                     <img
                         alt=""
                         ref={action((r: HTMLImageElement | null) => (this.imageRef = r))}
                         key="paths"
                         src={srcpath}
-                        style={{ transform, transformOrigin, height: this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined ? '100%' : undefined }}
+                        style={{
+                            position: 'relative',
+                            transform,
+                            transformOrigin,
+                            width: this._outpaintAlign ? 'max-content' : this._outpaintAlign ? '100%' : undefined,
+                            height: this._outpaintVAlign ? 'max-content' : this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined ? '100%' : undefined,
+                        }}
                         onError={action(e => (this._error = e.toString()))}
                         draggable={false}
                         width={nativeWidth}
-- 
cgit v1.2.3-70-g09d2

