aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsharkiecodes <lanyi_stroud@brown.edu>2025-05-11 22:58:07 -0400
committersharkiecodes <lanyi_stroud@brown.edu>2025-05-11 22:58:07 -0400
commit64a9a1a982ec3f11adfed68cc0f18eb61059aff8 (patch)
tree6fa012ae5c2d4af7f5dcb57aafec7d42eab363ab
parent67450b443b70099ce51a8db2872b2c04e09b4558 (diff)
integrated outpainting with scrapbooks
-rw-r--r--src/client/views/DocumentDecorations.tsx7
-rw-r--r--src/client/views/nodes/ImageBox.tsx1
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookBox.tsx136
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}