diff options
author | sharkiecodes <lanyi_stroud@brown.edu> | 2025-05-11 22:58:07 -0400 |
---|---|---|
committer | sharkiecodes <lanyi_stroud@brown.edu> | 2025-05-11 22:58:07 -0400 |
commit | 64a9a1a982ec3f11adfed68cc0f18eb61059aff8 (patch) | |
tree | 6fa012ae5c2d4af7f5dcb57aafec7d42eab363ab /src | |
parent | 67450b443b70099ce51a8db2872b2c04e09b4558 (diff) |
integrated outpainting with scrapbooks
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 7 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 1 | ||||
-rw-r--r-- | src/client/views/nodes/scrapbook/ScrapbookBox.tsx | 136 |
3 files changed, 118 insertions, 26 deletions
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index ab665e984..94e5e662c 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -36,6 +36,7 @@ import { ImageBox } from './nodes/ImageBox'; import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { TagsView } from './TagsView'; +import { ScrapbookBox } from './nodes/scrapbook/ScrapbookBox'; interface DocumentDecorationsProps { PanelWidth: number; @@ -446,7 +447,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora onPointerDown = (e: React.PointerEvent): void => { SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them DocumentView.Selected() - .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox) + .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox || dv.ComponentView instanceof ScrapbookBox) .forEach(dv => { dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalWidth'] = NumCast(dv.Document._width); dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalHeight'] = NumCast(dv.Document._height); @@ -502,7 +503,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora this._interactionLock = true; this._snapPt = thisPt; - const outpainted = e.shiftKey ? DocumentView.Selected().filter(dv => dv.ComponentView instanceof ImageBox) : []; + const outpainted = e.shiftKey ? DocumentView.Selected().filter(dv => dv.ComponentView instanceof ImageBox || dv.ComponentView instanceof ScrapbookBox) : []; const notOutpainted = e.shiftKey ? DocumentView.Selected().filter(dv => !outpainted.includes(dv)) : DocumentView.Selected(); // Special handling for shift-drag resize (outpainting of Images by resizing without scaling content - fill in with firefly GAI) @@ -765,7 +766,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora const rotation = DocumentView.Selected().length === 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0; // Radius constants - const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView; + const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof ScrapbookBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView; const borderRadius = numberValue(Cast(seldocview.Document.layout_borderRounding, 'string', null)); const docMax = Math.min(NumCast(seldocview.Document._width) / 2, NumCast(seldocview.Document._height) / 2); const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 9d459d7eb..0eb74740f 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -7,7 +7,6 @@ import { observer } from 'mobx-react'; import { extname } from 'path'; import * as React from 'react'; import { AiOutlineSend } from 'react-icons/ai'; -import { ImageLabelBoxData } from '../collections/collectionFreeForm/ImageLabelBox'; import ReactLoading from 'react-loading'; import { ClientUtils, imageUrlToBase64, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx index ced2df6c5..ad3bfa7ad 100644 --- a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx +++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx @@ -1,4 +1,4 @@ -import { action, makeObservable, observable } from 'mobx'; +import { action, makeObservable, observable, reaction } from 'mobx'; import * as React from 'react'; import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; @@ -18,7 +18,11 @@ import { ImageBox } from '../ImageBox'; import { FireflyImageDimensions } from '../../smartdraw/FireflyConstants'; import { SmartDrawHandler } from '../../smartdraw/SmartDrawHandler'; import { ImageCast } from '../../../../fields/Types'; -import { lengthToDegrees } from '@turf/turf'; +import { SnappingManager } from '../../../util/SnappingManager'; +import { IReactionDisposer } from 'mobx'; +import { observer } from 'mobx-react'; +import { ImageField } from '../../../../fields/URLField'; +import { runInAction } from 'mobx'; enum ScrapbookPresetType { Classic = 'Classic', @@ -28,16 +32,17 @@ enum ScrapbookPresetType { } // Scrapbook view: a container that lays out its child items in a grid/template +@observer export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { - state = { - loading: false, - src: '', - }; - - @observable createdDate: string; + @observable loading = false; + @observable src = ''; + @observable imgDoc: Doc | undefined; + private _disposers: { [name: string]: IReactionDisposer } = {}; + private imageBoxRef = React.createRef<ImageBox>(); + // @observable configs : ScrapbookItemConfig[] constructor(props: FieldViewProps) { @@ -337,7 +342,7 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() } } - + async generateAiImage() { this.setState({ loading: true }); @@ -365,10 +370,98 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() componentDidMount() { //this.initScrapbook(ScrapbookPresetType.Default); this.setTitle(); - this.generateAiImage(); + this.generateAiImageCorrect(); + + this._disposers.propagateResize = reaction( + () => ({ w: this.layoutDoc._width, h: this.layoutDoc._height }), + (dims, prev) => { + // prev is undefined on the first run, so bail early + if (!prev || !SnappingManager.ShiftKey || !this.imgDoc) return; + + // either guard the ref… + const imageBox = this.imageBoxRef.current; + if (!imageBox) return; + + // …or just hard-code the fieldKey if you know it’s always `"data"` + const key = imageBox.props.fieldKey; + + runInAction(() => { + if(!this.imgDoc){ + return + } + // use prev.w/h (the *old* size) as your orig dims + this.imgDoc[key + '_outpaintOriginalWidth'] = prev.w; + this.imgDoc[key + '_outpaintOriginalHeight'] = prev.h; + ;(this.imageBoxRef.current as any).layoutDoc._width = dims.w + ;(this.imageBoxRef.current as any).layoutDoc._height = dims.h + + // tell the imageDoc to resize itself to the *new* scrapbook size + //this.imgDoc._width = dims.w; + //this.imgDoc._height = dims.h; + //Doc.SetNativeWidth(this.imgDoc, dims.w); + //Doc.SetNativeHeight(this.imgDoc, dims.h); + }); + } + ); + /* + this._disposers.propagateResize = reaction( + () => ({ w: this.layoutDoc._width, h: this.layoutDoc._height }), + ({ w, h }, prev) => { + // only when shift is held (i.e. outpaint mode) + if (SnappingManager.ShiftKey && this.imgDoc) { + const key = this.imageBoxRef.current!.props.fieldKey; // “data” + // record original size on the *image* doc: + this.imgDoc[key + '_outpaintOriginalWidth'] = this.imgDoc._width; + this.imgDoc[key + '_outpaintOriginalHeight'] = this.imgDoc._height; + } + } + );*/ + + + // this._disposers.outpaint = reaction( + // () => this.imgDoc?.[this.imgDoc.fieldKey + '_outpaintOriginalWidth'], + // originalWidth => { + // if (originalWidth !== undefined && !SnappingManager.ShiftKey) { + // this.imageBoxRef.current?.openOutpaintPrompt(); // ✅ CORRECT! + // } + // } + // ); } + async generateAiImageCorrect() { + this.loading = true; + + const prompt = 'A serene mountain landscape at sunrise, ultra-wide, pastel sky, abstract, scrapbook background'; + const dimensions = FireflyImageDimensions.Square; + + SmartDrawHandler.CreateWithFirefly(prompt, dimensions) + .then(action(doc => { + if (doc instanceof Doc) { + const imgField = ImageCast(doc.data); + if (imgField?.url.href) { + this.src = imgField.url.href; + const url = new ImageField(this.src); + this.imgDoc = Docs.Create.ImageDocument(url, { title: 'Generated Background', _width: 1792, _height: 2304, + _nativeWidth: 1792, _nativeHeight: 1792 + }, ); + } else { + alert('Image URL missing.'); + this.src = ''; + } + + } else { + alert('Failed to generate document.'); + } + })) + .catch(e => { + alert(`Generation error: ${e}`); + }) + .finally(action(() => { + this.loading = false; + })); + } + childRejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => { return true; // disable dropping documents onto any child of the scrapbook. }; @@ -446,18 +539,18 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() }; render() { - const { loading, src } = this.state; + return ( <div style={{ background: 'beige', width: '100%', height: '100%' }}> - {loading && ( + {this.loading && ( <div style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', background: 'rgba(255,255,255,0.8)' }}> <ReactLoading type="spin" width={50} height={50} /> </div> )} - {loading && ( + {this.loading && ( <div style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', background: 'rgba(255,255,255,0.8)', zIndex: 10 }}> @@ -465,15 +558,14 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() </div> )} {/* Render AI-generated background */} - {src && ( - <ImageBox - {...this._props} - Document={Docs.Create.ImageDocument(src, { title: 'Generated Background' })} - fieldKey="data" - /> - )} - - <CollectionView + {this.src && this.imgDoc && ( + <ImageBox + ref={this.imageBoxRef} + {...this._props} + Document={this.imgDoc} + fieldKey="data" + /> + )} <CollectionView {...this._props} // setContentViewBox={emptyFunction} rejectDrop={this.rejectDrop} |