aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx18
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx15
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFill.scss1
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFill.tsx103
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts116
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts2
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts4
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx4
8 files changed, 118 insertions, 145 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ba31916a7..34f707b30 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -237,7 +237,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
elementFunc = () => this._layoutElements;
- shrinkWrap = () => {
+ fitContentOnce = () => {
if (this.props.DocumentView?.().nativeWidth) return;
const vals = this.fitToContentVals;
this.layoutDoc._freeform_panX = vals.bounds.cx;
@@ -1592,6 +1592,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
elements => (this._layoutElements = elements || []),
{ fireImmediately: true, name: 'doLayout' }
);
+
+ this._disposers.fitContent = reaction(
+ () => this.rootDoc.fitContentOnce,
+ fitContentOnce => {
+ if (fitContentOnce) this.fitContentOnce();
+ },
+ { fireImmediately: true, name: 'fitContent' }
+ );
})
);
}
@@ -1782,6 +1790,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!Doc.noviceMode &&
optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' });
this.props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' });
+ this.props.renderDepth &&
+ optionItems.push({
+ description: 'Fit Content Once',
+ event: () => {
+ this.fitContentOnce();
+ },
+ icon: 'object-group',
+ });
if (!Doc.noviceMode) {
optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' });
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9f4483e8d..072977150 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -937,6 +937,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
generateImage = async () => {
console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text);
+ GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
GPTPopup.Instance?.setImgTargetDoc(this.rootDoc);
GPTPopup.Instance.setImgUrls([]);
GPTPopup.Instance.setMode(GPTPopupMode.IMAGE);
@@ -1270,13 +1271,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
setTimeout(this.autoLink, 20);
}
- // Accessing editor and text doc for gpt assisted text edits
- if (this._editorView && selected) {
- console.log('Setting');
- GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
- // AnchorMenu.Instance?.setEditorView(this._editorView);
- // AnchorMenu.Instance?.setTextDoc(this.dataDoc);
- }
+ // // Accessing editor and text doc for gpt assisted text edits
+ // if (this._editorView && selected) {
+ // console.log('Setting');
+ // GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
+ // // AnchorMenu.Instance?.setEditorView(this._editorView);
+ // // AnchorMenu.Instance?.setTextDoc(this.dataDoc);
+ // }
}),
{ fireImmediately: true }
);
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.scss b/src/client/views/nodes/generativeFill/GenerativeFill.scss
index b1e570cf1..c2669a950 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.scss
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.scss
@@ -16,6 +16,7 @@ $scale: 0.5;
.generativeFillControls {
flex-shrink: 0;
height: $navHeight;
+ color: #000000;
background-color: #ffffff;
z-index: 999;
width: 100%;
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 2b3cfb920..f8f9fe077 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -3,21 +3,19 @@ import React = require('react');
import { useEffect, useRef, useState } from 'react';
import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
import { BrushHandler } from './generativeFillUtils/BrushHandler';
-import { IconButton, TextField } from '@mui/material';
+import { Box, IconButton, Slider, TextField } from '@mui/material';
import { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces';
import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
import { PointerHandler } from './generativeFillUtils/PointerHandler';
-import { BsBrush, BsEraser, BsX } from 'react-icons/bs';
-import { AiOutlineUpload } from 'react-icons/ai';
+import { BsEraser, BsX } from 'react-icons/bs';
import { CiUndo, CiRedo } from 'react-icons/ci';
import Buttons from './GenerativeFillButtons';
-import { EditableText } from 'browndash-components';
import { MainView } from '../../MainView';
import { Doc } from '../../../../fields/Doc';
import { Networking } from '../../../Network';
import { Utils } from '../../../../Utils';
import { DocUtils, Docs } from '../../../documents/Documents';
-import { DocCast, NumCast } from '../../../../fields/Types';
+import { Cast, DocCast, NumCast } from '../../../../fields/Types';
import { CollectionDockingView } from '../../collections/CollectionDockingView';
import { OpenWhere, OpenWhereMod } from '../DocumentView';
import { Oval } from 'react-loader-spinner';
@@ -25,12 +23,11 @@ import { Oval } from 'react-loader-spinner';
/**
* For images not 1024x1024 fill in the rest in solid black, or a
* reflected version of the image.
+ *
+ * add a branch from image directly checkbox
*/
/**
- * TODO: Look into img onload, sometimes the canvas doesn't update properly
- *
- * Ref:
*
*
* CollectionDockingView.AddSplit(Doc.MakeCopy(DocCast(Doc.UserDoc().emptyPane)), OpenWhereMod.right);
@@ -88,12 +85,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
const freeformPosition = useRef<number[]>([0, 0]);
// which urls were already saved to canvas
- const savedSrcs = useRef<string[]>([]);
+ const savedSrcs = useRef<Set<string>>(new Set());
// references to keep track of tree structure
const newCollectionRef = useRef<Doc | null>(null);
const parentDoc = useRef<Doc | null>(null);
const childrenDocs = useRef<Doc[]>([]);
+ const addToExistingCollection = useRef<boolean>(false);
// Undo and Redo
const handleUndo = () => {
@@ -250,18 +248,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
}));
};
- // File upload
- const uploadImg = (e: React.ChangeEvent<HTMLInputElement>) => {
- if (e.target.files) {
- const file = e.target.files[0];
- const image = new Image();
- const imgUrl = URL.createObjectURL(file);
- image.src = imgUrl;
- ImageUtility.drawImgToCanvas(image, canvasRef);
- currImg.current = image;
- }
- };
-
// Get AI Edit
const getEdit = async () => {
const img = currImg.current;
@@ -280,6 +266,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
// create first image
if (!newCollectionRef.current) {
+ if (addToExistingCollection.current) {
+ }
if (!(originalImg.current && imageRootDoc)) return;
console.log('creating first image');
// create new collection and add it to the view
@@ -292,7 +280,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
});
DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false });
// add the doc to the main freeform
- addDoc?.(newCollectionRef.current);
+ // addDoc?.(newCollectionRef.current);
await createNewImgDoc(originalImg.current, true);
} else {
parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1];
@@ -321,7 +309,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
if (!parentDoc.current) return;
const startY = NumCast(parentDoc.current.y);
const len = childrenDocs.current.length;
- console.log(len);
let initialYPositions: number[] = [];
for (let i = 0; i < len; i++) {
initialYPositions.push(startY + i * offsetDistanceY);
@@ -398,9 +385,10 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
};
const handleViewClose = () => {
- // if (newCollectionRef.current) {
- // CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
- // }
+ if (newCollectionRef.current) {
+ newCollectionRef.current.fitContentOnce = true;
+ CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
+ }
MainView.Instance.setImageEditorOpen(false);
MainView.Instance.setImageEditorSource('');
setEdits([]);
@@ -409,12 +397,17 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
return (
<div className="generativeFillContainer" style={{ display: imageEditorOpen ? 'flex' : 'none' }}>
<div className="generativeFillControls">
- <h1>Generative Fill</h1>
+ <h1>AI Image Editor</h1>
<div style={{ display: 'flex', alignItems: 'center', gap: '1.5rem' }}>
<Buttons canvasRef={canvasRef} currImg={currImg} getEdit={getEdit} loading={loading} onSave={onSave} onReset={handleReset} />
<IconButton onClick={handleViewClose}>
<BsX color={activeColor} />
</IconButton>
+ {saveLoading && (
+ <span style={{ height: '100%', display: 'flex', alignItems: 'center', gap: '8px' }}>
+ Saving image... <Oval height={20} width={20} color="#000000" visible={true} ariaLabel="oval-loading" secondaryColor="#ffffff89" strokeWidth={3} strokeWidthSecondary={3} />
+ </span>
+ )}
</div>
</div>
{/* Main canvas for editing */}
@@ -439,20 +432,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
</div>
{/* Icons */}
<div className="iconContainer">
- <input ref={fileRef} type="file" accept="image/*" onChange={uploadImg} style={{ display: 'none' }} />
- <IconButton
- onClick={() => {
- if (fileRef.current) {
- fileRef.current.click();
- }
- }}>
- <AiOutlineUpload />
- </IconButton>
<IconButton
onClick={() => {
setBrushStyle(BrushStyle.ADD);
}}>
- <BsBrush color={brushStyle === BrushStyle.ADD ? activeColor : 'inherit'} />
+ <BsEraser color={brushStyle === BrushStyle.ADD ? activeColor : 'inherit'} />
</IconButton>
{/* Undo and Redo */}
<IconButton
@@ -475,6 +459,28 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
}}>
<CiRedo />
</IconButton>
+ <Box
+ sx={{
+ height: 225,
+ width: '100%',
+ display: 'flex',
+ justifyContent: 'center',
+ }}>
+ <Slider
+ sx={{
+ '& input[type="range"]': {
+ WebkitAppearance: 'slider-vertical',
+ },
+ }}
+ orientation="vertical"
+ min={10}
+ max={500}
+ defaultValue={150}
+ onChange={(e, val) => {
+ setCursorData(prev => ({ ...prev, width: val as number }));
+ }}
+ />
+ </Box>
</div>
{/* Edits thumbnails*/}
<div className="editsBox">
@@ -484,12 +490,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
width={100}
height={100}
src={edit}
- onClick={() => {
+ onClick={async () => {
+ // if (savedSrcs.current.has(edit)) return;
const img = new Image();
img.src = edit;
ImageUtility.drawImgToCanvas(img, canvasRef);
currImg.current = img;
- onSave();
+ savedSrcs.current.add(edit);
+ await onSave();
}}
/>
))}
@@ -521,14 +529,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
currImg.current = img;
}}
/>
- <div
- style={{
- position: 'absolute',
- top: 10,
- right: 10,
- }}>
- {saveLoading && <Oval height={20} width={20} color="#ffffff" visible={true} ariaLabel="oval-loading" secondaryColor="#ffffff89" strokeWidth={3} strokeWidthSecondary={3} />}
- </div>
</div>
)}
</div>
@@ -541,15 +541,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
type="text"
label="Prompt"
placeholder="Prompt..."
- InputLabelProps={{ style: { fontSize: '1.5rem' } }}
- inputProps={{ style: { fontSize: '1.5rem' } }}
+ InputLabelProps={{ style: { fontSize: '16px' } }}
+ inputProps={{ style: { fontSize: '16px' } }}
sx={{
backgroundColor: '#ffffff',
position: 'absolute',
- bottom: '1rem',
+ bottom: '16px',
transform: 'translateX(calc(50vw - 50%))',
- width: 'calc(100vw - 4rem)',
- scale: 1.2,
+ width: 'calc(100vw - 64px)',
}}
/>
</div>
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
index c2716e083..f84f04190 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
@@ -1,87 +1,45 @@
-import { GenerativeFillMathHelpers } from "./GenerativeFillMathHelpers";
-import { eraserColor } from "./generativeFillConstants";
-import { Point } from "./generativeFillInterfaces";
+import { GenerativeFillMathHelpers } from './GenerativeFillMathHelpers';
+import { eraserColor } from './generativeFillConstants';
+import { Point } from './generativeFillInterfaces';
export class BrushHandler {
- static brushCircle = (
- x: number,
- y: number,
- brushRadius: number,
- ctx: CanvasRenderingContext2D
- ) => {
- ctx.globalCompositeOperation = "destination-out";
- ctx.shadowColor = "#ffffffeb";
- ctx.shadowBlur = 5;
- ctx.beginPath();
- ctx.arc(x, y, brushRadius, 0, 2 * Math.PI);
- ctx.fill();
- ctx.closePath();
- };
+ static brushCircle = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D) => {
+ ctx.globalCompositeOperation = 'destination-out';
+ ctx.shadowColor = '#ffffffeb';
+ ctx.shadowBlur = 5;
+ ctx.beginPath();
+ ctx.arc(x, y, brushRadius, 0, 2 * Math.PI);
+ ctx.fill();
+ ctx.closePath();
+ };
- static brushCircleOverlay = (
- x: number,
- y: number,
- brushRadius: number,
- ctx: CanvasRenderingContext2D,
- fillColor: string,
- erase: boolean
- ) => {
- ctx.globalCompositeOperation = "destination-out";
- // ctx.globalCompositeOperation = erase ? "destination-out" : "source-over";
- ctx.fillStyle = fillColor;
- ctx.shadowColor = eraserColor;
- ctx.shadowBlur = 5;
- ctx.beginPath();
- ctx.arc(x, y, brushRadius, 0, 2 * Math.PI);
- ctx.fill();
- ctx.closePath();
- };
+ static brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => {
+ ctx.globalCompositeOperation = 'destination-out';
+ // ctx.globalCompositeOperation = erase ? "destination-out" : "source-over";
+ ctx.fillStyle = fillColor;
+ ctx.shadowColor = eraserColor;
+ ctx.shadowBlur = 5;
+ ctx.beginPath();
+ ctx.arc(x, y, brushRadius, 0, 2 * Math.PI);
+ ctx.fill();
+ ctx.closePath();
+ };
- static createBrushPath = (
- startPoint: Point,
- endPoint: Point,
- brushRadius: number,
- ctx: CanvasRenderingContext2D
- ) => {
- const dist = GenerativeFillMathHelpers.distanceBetween(
- startPoint,
- endPoint
- );
+ static createBrushPath = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D) => {
+ const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint);
- for (let i = 0; i < dist; i += 5) {
- const s = i / dist;
- BrushHandler.brushCircle(
- startPoint.x * (1 - s) + endPoint.x * s,
- startPoint.y * (1 - s) + endPoint.y * s,
- brushRadius,
- ctx
- );
- }
- };
+ for (let i = 0; i < dist; i += 5) {
+ const s = i / dist;
+ BrushHandler.brushCircle(startPoint.x * (1 - s) + endPoint.x * s, startPoint.y * (1 - s) + endPoint.y * s, brushRadius, ctx);
+ }
+ };
- static createBrushPathOverlay = (
- startPoint: Point,
- endPoint: Point,
- brushRadius: number,
- ctx: CanvasRenderingContext2D,
- fillColor: string,
- erase: boolean
- ) => {
- const dist = GenerativeFillMathHelpers.distanceBetween(
- startPoint,
- endPoint
- );
+ static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => {
+ const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint);
- for (let i = 0; i < dist; i += 5) {
- const s = i / dist;
- BrushHandler.brushCircleOverlay(
- startPoint.x * (1 - s) + endPoint.x * s,
- startPoint.y * (1 - s) + endPoint.y * s,
- brushRadius,
- ctx,
- fillColor,
- erase
- );
- }
- };
+ for (let i = 0; i < dist; i += 5) {
+ const s = i / dist;
+ BrushHandler.brushCircleOverlay(startPoint.x * (1 - s) + endPoint.x * s, startPoint.y * (1 - s) + endPoint.y * s, brushRadius, ctx, fillColor, erase);
+ }
+ };
}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 48055903c..45cf7196b 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -28,7 +28,7 @@ export class ImageUtility {
fd.append('image', imgBlob, 'image.png');
fd.append('mask', maskBlob, 'mask.png');
fd.append('prompt', prompt);
- fd.append('size', '1024x1024');
+ fd.append('size', '512x512');
fd.append('n', n ? JSON.stringify(n) : '1');
fd.append('response_format', 'b64_json');
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
index 5a8d33742..412a4d238 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
@@ -1,9 +1,9 @@
// constants
-export const canvasSize = 1024;
+export const canvasSize = 512;
export const freeformRenderSize = 300;
export const offsetDistanceY = freeformRenderSize + 200;
export const offsetX = 200;
-export const newCollectionSize = 1000;
+export const newCollectionSize = 500;
export const activeColor = '#1976d2';
export const eraserColor = '#e1e9ec';
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index aeee90d16..fc6fc1af8 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -124,9 +124,7 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
* Transfers the image urls to actual image docs
*/
private transferToImage = (source: string) => {
- console.log('Text Anchor', this.textAnchor);
- console.log('Whole doc anchor', this.imgTargetDoc);
- const textAnchor = this.textAnchor ?? this.imgTargetDoc;
+ const textAnchor = this.imgTargetDoc;
if (!textAnchor) return;
const newDoc = Docs.Create.ImageDocument(source, {
x: NumCast(textAnchor.x) + NumCast(textAnchor._width) + 10,