diff options
Diffstat (limited to 'src/client/views/nodes/ImageBox.tsx')
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 190 |
1 files changed, 83 insertions, 107 deletions
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d16baada6..30fc44f62 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; @@ -114,6 +112,28 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this._props.setContentViewBox?.(this); } + @computed get oupaintOriginalSize(): { width: number; height: number } { + return { + width: NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']), + height: NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']), + }; + } + set outpaintOriginalSize(prop: { width: number; height: number } | undefined) { + this.Document[this.fieldKey + '_outpaintOriginalWidth'] = prop?.width; + this.Document[this.fieldKey + '_outpaintOriginalHeight'] = prop?.height; + } + + @computed get imgNativeSize() { + return { + nativeWidth: NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500)), + nativeHeight: NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500)), + }; + } + set imgNativeSize(prop: { nativeWidth: number; nativeHeight: number }) { + this.dataDoc[this.fieldKey + '_nativeWidth'] = prop.nativeWidth; + this.dataDoc[this.fieldKey + '_nativeHeight'] = prop.nativeHeight; + } + protected createDropTarget = (ele: HTMLDivElement) => { this._mainCont = ele; this._dropDisposer?.(); @@ -154,7 +174,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; // reset dimensions of templates rendered with content or if image changes. afterwards, remove this flag. this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; } }, @@ -171,7 +192,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { { fireImmediately: true } ); this._disposers.outpaint = reaction( - () => this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined && !SnappingManager.ShiftKey, + () => this.outpaintOriginalSize?.width && !SnappingManager.ShiftKey, complete => complete && this.openOutpaintPrompt(), { fireImmediately: true } ); @@ -227,19 +248,20 @@ 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; } else if (de.altKey || !this.dataDoc[this.fieldKey]) { - const layoutDoc = de.complete.docDragData?.draggedDocuments[0]; - const targetField = Doc.LayoutDataKey(layoutDoc); - const targetDoc = layoutDoc[DocData]; - if (targetDoc[targetField] instanceof ImageField) { + const dropDoc = de.complete.docDragData?.draggedDocuments[0]; + const dropDocFieldKey = Doc.LayoutDataKey(dropDoc); + const dropDataDoc = dropDoc[DocData]; + if (dropDataDoc[dropDocFieldKey] instanceof ImageField) { added = true; - this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField); - Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(targetDoc), this.fieldKey); - Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(targetDoc), this.fieldKey); + this.dataDoc.layout_resetNativeDim = true; + this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(dropDataDoc[dropDocFieldKey] as ImageField); + Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(dropDataDoc), this.fieldKey); + Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(dropDataDoc), this.fieldKey); } } added === false && e.preventDefault(); @@ -258,18 +280,17 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @undoBatch setNativeSize = action(() => { - const oldnativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']); + const oldnativeWidth = this.imgNativeSize.nativeWidth; const nscale = NumCast(this._props.PanelWidth()) * NumCast(this.layoutDoc._freeform_scale, 1); const nw = nscale / oldnativeWidth; - this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw; - this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw; + this.imgNativeSize = { nativeWidth: this.imgNativeSize.nativeWidth * nw, nativeHeight: this.imgNativeSize.nativeHeight * nw }; this.dataDoc.freeform_panX = nw * NumCast(this.dataDoc.freeform_panX); this.dataDoc.freeform_panY = nw * NumCast(this.dataDoc.freeform_panY); this.dataDoc.freeform_panX_max = this.dataDoc.freeform_panX_max ? nw * NumCast(this.dataDoc.freeform_panX_max) : undefined; this.dataDoc.freeform_panX_min = this.dataDoc.freeform_panX_min ? nw * NumCast(this.dataDoc.freeform_panX_min) : undefined; this.dataDoc.freeform_panY_max = this.dataDoc.freeform_panY_max ? nw * NumCast(this.dataDoc.freeform_panY_max) : undefined; this.dataDoc.freeform_panY_min = this.dataDoc.freeform_panY_min ? nw * NumCast(this.dataDoc.freeform_panY_min) : undefined; - const newnativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']); + const newnativeWidth = this.imgNativeSize.nativeWidth; DocListCast(this.dataDoc[this.annotationKey]).forEach(doc => { doc.x = (NumCast(doc.x) / oldnativeWidth) * newnativeWidth; doc.y = (NumCast(doc.y) / oldnativeWidth) * newnativeWidth; @@ -281,13 +302,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }); @undoBatch rotate = action(() => { - const nw = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']); - const nh = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']); + const nativeSize = this.imgNativeSize; const w = this.layoutDoc._width; const h = this.layoutDoc._height; this.dataDoc[this.fieldKey + '_rotation'] = (NumCast(this.dataDoc[this.fieldKey + '_rotation']) + 90) % 360; - this.dataDoc[this.fieldKey + '_nativeWidth'] = nh; - this.dataDoc[this.fieldKey + '_nativeHeight'] = nw; + this.imgNativeSize = { nativeWidth: nativeSize.nativeHeight, nativeHeight: nativeSize.nativeWidth }; // swap width and height this.layoutDoc._width = h; this.layoutDoc._height = w; }); @@ -303,7 +322,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const anchy = NumCast(cropping.y); const anchw = NumCast(cropping._width); const anchh = NumCast(cropping._height); - const viewScale = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) / anchw; + const viewScale = this.nativeSize.nativeWidth / anchw; cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width); cropping.y = NumCast(this.Document.y); cropping.onClick = undefined; @@ -365,11 +384,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @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.Document._width, this.Document._height] = [this.oupaintOriginalSize.width, this.oupaintOriginalSize.height]; this._outpaintingInProgress = false; + this.outpaintOriginalSize = undefined; this.closeOutpaintPrompt(); }; @@ -417,8 +434,7 @@ 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 { width: origWidth, height: origHeight } = this.oupaintOriginalSize; const response = await Networking.PostToServer('/outpaintImage', { imageUrl: currentPath, prompt: customPrompt, @@ -455,8 +471,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this.Document.$ai = true; this.Document.$ai_outpainted = true; this.Document.$ai_outpaint_prompt = customPrompt; - this.Document[this.fieldKey + '_outpaintOriginalWidth'] = undefined; - this.Document[this.fieldKey + '_outpaintOriginalHeight'] = undefined; + this.outpaintOriginalSize = undefined; } else { this.cancelOutpaintPrompt(); alert('Failed to receive a valid image URL from server.'); @@ -669,8 +684,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @computed get nativeSize() { TraceMobx(); if (this.paths.length && this.paths[0].includes(DefaultPath)) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0 }; - const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500)); - const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500)); + const { nativeWidth, nativeHeight } = this.imgNativeSize; const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '_nativeOrientation'], 1); return { nativeWidth, nativeHeight, nativeOrientation }; } @@ -690,7 +704,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @computed get overlayImageIcon() { const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`]; - return ( + return this._regenerateLoading ? null : ( <Tooltip title={ <div className="dash-tooltip"> @@ -732,14 +746,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ); } @computed get regenerateImageIcon() { - return ( + return this._regenerateLoading ? null : ( <Tooltip title={'click to show AI generations. Drop an image on to create a new generation'}> <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, @@ -821,7 +835,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { transform, transformOrigin, width: this._outpaintAlign ? 'max-content' : this._outpaintAlign ? '100%' : undefined, - height: this._outpaintVAlign ? 'max-content' : this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined ? '100%' : undefined, + height: this._outpaintVAlign ? 'max-content' : this.outpaintOriginalSize?.width ? '100%' : undefined, }} onError={action(e => (this._error = e.toString()))} draggable={false} @@ -845,34 +859,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 +876,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> ); @@ -986,6 +958,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return { width, height }; }; savedAnnotations = () => this._savedAnnotations; + rejectDrop = (de: DragManager.DropEvent, subView?: DocumentView | undefined) => (this.dataDoc[this.fieldKey] === undefined ? true : (this._props.rejectDrop?.(de, subView) ?? false)); render() { TraceMobx(); const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string; @@ -1029,7 +1002,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ScreenToLocalTransform={this.screenToLocalTransform} select={emptyFunction} focus={this.focus} - rejectDrop={this._props.rejectDrop} + rejectDrop={this.rejectDrop} getScrollHeight={this.getScrollHeight} NativeDimScaling={returnOne} isAnyChildContentActive={returnFalse} @@ -1091,8 +1064,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { if (result instanceof Error) { 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); + runInAction(() => { + this.dataDoc.layout_resetNativeDim = true; + !(result instanceof Error) && DocUtils.assignUploadInfo(result, this.dataDoc, this.fieldKey); + this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client); + }); } disposer(); } else { |