aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ClientUtils.ts23
-rw-r--r--src/client/util/SelectionManager.ts3
-rw-r--r--src/client/views/GlobalKeyHandler.ts8
-rw-r--r--src/client/views/PropertiesView.tsx48
-rw-r--r--src/client/views/TagsView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss15
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx236
-rw-r--r--src/client/views/nodes/DocumentView.scss1
-rw-r--r--src/client/views/nodes/DocumentView.tsx48
-rw-r--r--src/client/views/nodes/ImageBox.scss19
-rw-r--r--src/client/views/nodes/ImageBox.tsx113
-rw-r--r--src/client/views/smartdraw/DrawingFillHandler.tsx17
-rw-r--r--src/client/views/smartdraw/SmartDrawHandler.tsx8
-rw-r--r--src/server/ApiManagers/UploadManager.ts22
14 files changed, 316 insertions, 247 deletions
diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index 8f62b9060..e5dbb81db 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -144,15 +144,20 @@ export namespace ClientUtils {
export async function convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false, replaceRootFilename: string | undefined = undefined) {
try {
const posting = ClientUtils.prepend('/uploadURI');
- const returnedUri = await rp.post(posting, {
- body: {
- uri: imageUri,
- name: returnedFilename,
- nosuffix,
- replaceRootFilename,
- },
- json: true,
- });
+ const returnedUri = await rp
+ .post(posting, {
+ body: {
+ uri: imageUri,
+ name: returnedFilename,
+ nosuffix,
+ replaceRootFilename,
+ },
+ json: true,
+ })
+ .catch(e => {
+ alert('Data URI Error: ' + e.toString());
+ return undefined;
+ });
return returnedUri;
} catch (e) {
console.log('ConvertDataURI :' + e);
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 1ab84421c..a1f2849cd 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -88,7 +88,8 @@ ScriptingGlobals.add(function SelectedDocType(type: string, expertMode: boolean,
return DocumentView.Selected().lastElement()?._props.renderDepth === 0;
}
const selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(DocumentView.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
- return selected?.type === type || selected?.type_collection === type || !type;
+ const matchOverlayFreeform = type === CollectionViewType.Freeform && DocumentView.Selected().lastElement()?.ComponentView?.annotationKey;
+ return matchOverlayFreeform || selected?.type === type || selected?.type_collection === type || !type;
});
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function deselectAll() {
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index f16338361..b200aff65 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -169,10 +169,10 @@ export class KeyManager {
return { stopPropagation: true, preventDefault: true };
}
break;
- case 'arrowleft': return this.nudge(-1,0, 'nudge left')
- case 'arrowright': return this.nudge(1,0, 'nudge right');
- case 'arrowup': return this.nudge(0, -1, 'nudge up');
- case 'arrowdown': return this.nudge(0, 1, 'nudge down');
+ case 'arrowleft': return (e.target as any).type !== 'text' && this.nudge(-1, 0, 'nudge left') // if target is an input box, then we don't want to nudge any Docs since we're justing moving within the text itself.
+ case 'arrowright': return (e.target as any).type !== 'text' && this.nudge( 1, 0, 'nudge right');
+ case 'arrowup': return (e.target as any).type !== 'text' && this.nudge(0, -1, 'nudge up');
+ case 'arrowdown': return (e.target as any).type !== 'text' && this.nudge(0, 1, 'nudge down');
default:
} // prettier-ignore
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index e52189f56..42aa6782f 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -111,6 +111,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@observable openContexts: boolean = true;
@observable openLinks: boolean = true;
@observable openAppearance: boolean = true;
+ @observable openFirefly: boolean = true;
@observable openTransform: boolean = true;
@observable openFilters: boolean = false;
@observable openStyling: boolean = true;
@@ -982,26 +983,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
10,
1
);
- const strength = this.getNumber('Reference Strength', '', 1, 100, this.refStrength, (val: number) => {
- !isNaN(val) && (this.refStrength = val);
- });
return (
<div>
- {!targetDoc.layout_isSvg && this.containsInkDoc && (
- <div>
- <div className="drawing-to-image">
- <Toggle
- text={'Create Image'}
- color={SettingsManager.userColor}
- icon={<FontAwesomeIcon icon="fill-drip" />}
- iconPlacement="left"
- align="flex-start"
- fillWidth
- toggleType={ToggleType.BUTTON}
- onClick={undoable(() => DrawingFillHandler.drawingToImage(targetDoc, this.refStrength, StrCast(targetDoc.title) !== 'grouping' ? StrCast(targetDoc.title) : ''), 'createImage')}
- />
- </div>
- <div className="strength-slider">{strength}</div>
+ <div>
+ {!targetDoc.layout_isSvg && this.containsInkDoc && (
<div className="color">
<Toggle
text={'Color with GPT'}
@@ -1016,8 +1001,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}, 'colorWithGPT')}
/>
</div>
- </div>
- )}
+ )}
+ </div>
<div className="smooth">
<Toggle
text={'Smooth Ink Strokes'}
@@ -1113,6 +1098,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
this.openTransform = false;
this.openFields = false;
this.openSharing = false;
+ this.openAppearance = false;
+ this.openFirefly = false;
this.openLayout = false;
this.openFilters = false;
};
@@ -1323,11 +1310,32 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@computed get inkSubMenu() {
+ const strength = this.getNumber('Reference Strength', '', 1, 100, this.refStrength, (val: number) => {
+ !isNaN(val) && (this.refStrength = val);
+ });
+ const targetDoc = this.selectedLayoutDoc;
return (
<>
<PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => { this.openAppearance = bool; }} onDoubleClick={this.CloseAll}>
{this.selectedStrokes.length ? this.inkEditor : null}
</PropertiesSection>
+ <PropertiesSection title="Firefly" isOpen={this.openFirefly} setIsOpen={bool => { this.openFirefly = bool; }} onDoubleClick={this.CloseAll}>
+ <>
+ <div className="drawing-to-image">
+ <Toggle
+ text="Create Image"
+ color={SettingsManager.userColor}
+ icon={<FontAwesomeIcon icon="fill-drip" />}
+ iconPlacement="left"
+ align="flex-start"
+ fillWidth
+ toggleType={ToggleType.BUTTON}
+ onClick={undoable(() => DrawingFillHandler.drawingToImage(targetDoc, this.refStrength, StrCast(targetDoc.title) !== 'grouping' ? StrCast(targetDoc.title) : ''), 'createImage')}
+ />
+ </div>
+ <div className="strength-slider">{strength}</div>
+ </>
+ </PropertiesSection>
<PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => { this.openTransform = bool; }} onDoubleClick={this.CloseAll}>
{this.transformEditor}
</PropertiesSection>
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index 21b6a76c7..0edd89204 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -366,7 +366,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
backgroundColor: this.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT,
borderColor: this.isEditing ? Colors.BLACK : Colors.TRANSPARENT,
position: 'relative',
- top: this._props.Views.length > 1 ? 25 : `calc(-${this.InsetDist} * ${1 / this.currentScale}px)`,
+ top: this._props.Views.lastElement()._showAIEditor ? undefined : this._props.Views.length > 1 ? 25 : `calc(-${this.InsetDist} * ${1 / this.currentScale}px)`,
}}>
<div className="tagsView-content" style={{ width: '100%' }}>
<div className="tagsView-list">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index dff2cb282..82887a7a5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -312,10 +312,14 @@
.collectionfreeformview-aiView-prompt {
height: 25px;
+ width: 100%;
}
.collectionFreeFormView-aiView-strength {
text-align: center;
+ align-items: center;
+ display: flex;
+ width: 150px;
}
.collectionFreeformView-aiView-options-container,
@@ -323,13 +327,20 @@
text-align: start;
font-weight: normal;
padding: 5px;
+ width: 100%;
+ display: flex;
+ .collectionFreeformView-aiView-subtitle {
+ margin: auto;
+ width: 40px;
+ }
}
.collectionFreeformView-aiView-options,
.collectionFreeFormView-aiView-regenerate {
display: flex;
flex-direction: row;
- gap: 10px;
- justify-content: center;
align-items: center;
+ align-items: center;
+ width: 100%;
+ gap: 10px;
}
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 112bfd178..5b4f81379 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1877,8 +1877,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
: UpdateIcon(
this.layoutDoc[Id] + '_icon_' + new Date().getTime(),
contentDiv,
- usePanelDimensions ? this._props.PanelWidth() : NumCast(this.layoutDoc._width),
- usePanelDimensions ? this._props.PanelHeight() : NumCast(this.layoutDoc._height),
+ usePanelDimensions || true ? this._props.PanelWidth() : NumCast(this.layoutDoc._width),
+ usePanelDimensions || true ? this._props.PanelHeight() : NumCast(this.layoutDoc._height),
this._props.PanelWidth(),
this._props.PanelHeight(),
0,
@@ -2228,45 +2228,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
componentAIView = () => {
- const showRegenerate = this.Document[DocData].ai;
return (
<div className="collectionfreeformview-aiView" onPointerDown={e => e.stopPropagation()}>
- Edit Collection with AI
- {showRegenerate && (
- <div className="collectionfreeformview-aiView-regenerate-container">
- <text className="collectionfreeformview-aiView-subtitle">Regenerate AI Image</text>
- <div className="collectionfreeformview-aiView-regenerate">
- <input
- className="collectionfreeformview-aiView-input"
- aria-label="Edit instructions input"
- type="text"
- value={this._regenInput}
- onChange={action(e => this._canInteract && (this._regenInput = e.target.value))}
- placeholder="Prompt (Optional)"
- />
- <Button
- text="Regenerate"
- type={Type.SEC}
- icon={this._regenLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
- iconPlacement="right"
- onClick={action(async () => {
- this._regenLoading = true;
- SmartDrawHandler.Instance.CreateDrawingDoc = this.createDrawingDoc;
- SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
- SmartDrawHandler.Instance.RemoveDrawing = this.removeDrawing;
- await SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput, true);
- this._regenLoading = false;
- })}
- />
- </div>
- </div>
- )}
<div className="collectionfreeformview-aiView-options-container">
- <text className="collectionfreeformview-aiView-subtitle"> Create Image with Firefly </text>
+ <span className="collectionfreeformview-aiView-subtitle">Firefly:</span>
<div className="collectionfreeformview-aiView-options">
- <input className="collectionfreeformview-aiView-prompt" placeholder="Prompt (Optional)" type="text" value={this._drawingFillInput} onChange={action(e => this._canInteract && (this._drawingFillInput = e.target.value))} />
+ <input
+ className="collectionfreeformview-aiView-prompt"
+ placeholder={this._drawingFillInput || StrCast(this.Document.title) || 'Describe image'}
+ type="text"
+ value={this._drawingFillInput}
+ onChange={action(e => this._canInteract && (this._drawingFillInput = e.target.value))}
+ />
<div className="collectionfreeformview-aiView-strength">
- Reference Strength
+ Similarity
<Slider
className="collectionfreeformview-aiView-slider"
sx={{
@@ -2286,24 +2261,49 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
<Button
text="Send"
type={Type.SEC}
- icon={this._drawingFillLoading && this._drawingFillInput !== '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
+ icon={this._drawingFillLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
iconPlacement="right"
onClick={undoable(
action(() => {
this._drawingFillLoading = true;
- DrawingFillHandler.drawingToImage(
- this.props.Document,
- this._fireflyRefStrength,
- this._drawingFillInput !== '' ? this._drawingFillInput : StrCast(this.props.Document.title) !== 'grouping' ? StrCast(this.props.Document.title) : ''
+ DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._drawingFillInput || StrCast(this.Document.title))?.then(
+ action(() => {
+ this._drawingFillLoading = false;
+ })
);
- this._drawingFillInput = '';
- this._drawingFillLoading = false;
}),
'create image'
)}
/>
</div>
</div>
+ <div className="collectionfreeformview-aiView-regenerate-container">
+ <span className="collectionfreeformview-aiView-subtitle">Regenerate</span>
+ <div className="collectionfreeformview-aiView-regenerate">
+ <input
+ className="collectionfreeformview-aiView-input"
+ aria-label="Edit instructions input"
+ type="text"
+ value={this._regenInput}
+ onChange={action(e => this._canInteract && (this._regenInput = e.target.value))}
+ placeholder="..under development.."
+ />
+ <Button
+ text="Regenerate"
+ type={Type.SEC}
+ icon={this._regenLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
+ iconPlacement="right"
+ // onClick={action(async () => {
+ // this._regenLoading = true;
+ // SmartDrawHandler.Instance.CreateDrawingDoc = this.createDrawingDoc;
+ // SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
+ // SmartDrawHandler.Instance.RemoveDrawing = this.removeDrawing;
+ // await SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput, true);
+ // this._regenLoading = false;
+ // })}
+ />
+ </div>
+ </div>
</div>
);
};
@@ -2311,83 +2311,81 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
render() {
TraceMobx();
return (
- <div>
- <div
- className="collectionfreeformview-container"
- id={this._paintedId}
- ref={r => {
- this.createDashEventsTarget(r);
- this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
- this._oldWheel = r;
- // prevent wheel events from passivly propagating up through containers
- r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
- r?.addEventListener('mouseleave', this.onMouseLeave);
- r?.addEventListener('mouseenter', this.onMouseEnter);
- }}
- onWheel={this.onPointerWheel}
- onClick={this.onClick}
- onPointerDown={this.onPointerDown}
- onPointerMove={this.onCursorMove}
- onDrop={this.onExternalDrop}
- onDragOver={e => e.preventDefault()}
- onContextMenu={this.onContextMenu}
- style={{
- pointerEvents: this._props.isContentActive() && SnappingManager.IsDragging ? 'all' : this._props.pointerEvents?.(),
- textAlign: this.isAnnotationOverlay ? 'initial' : undefined,
- transform: `scale(${this.nativeDimScaling})`,
- width: `${100 / this.nativeDimScaling}%`,
- height: this._props.getScrollHeight?.() ?? `${100 / this.nativeDimScaling}%`,
- }}>
- {Doc.ActiveTool === InkTool.Eraser && Doc.ActiveEraser === InkEraserTool.Radius && this._showEraserCircle && (
- <div
- onPointerMove={this.onCursorMove}
- style={{
- position: 'fixed',
- left: this._eraserX,
- top: this._eraserY,
- width: (ActiveEraserWidth() + 5) * 2,
- height: (ActiveEraserWidth() + 5) * 2,
- borderRadius: '50%',
- border: '1px solid gray',
- transform: 'translate(-50%, -50%)',
- }}
+ <div
+ className="collectionfreeformview-container"
+ id={this._paintedId}
+ ref={r => {
+ this.createDashEventsTarget(r);
+ this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+ this._oldWheel = r;
+ // prevent wheel events from passivly propagating up through containers
+ r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+ r?.addEventListener('mouseleave', this.onMouseLeave);
+ r?.addEventListener('mouseenter', this.onMouseEnter);
+ }}
+ onWheel={this.onPointerWheel}
+ onClick={this.onClick}
+ onPointerDown={this.onPointerDown}
+ onPointerMove={this.onCursorMove}
+ onDrop={this.onExternalDrop}
+ onDragOver={e => e.preventDefault()}
+ onContextMenu={this.onContextMenu}
+ style={{
+ pointerEvents: this._props.isContentActive() && SnappingManager.IsDragging ? 'all' : this._props.pointerEvents?.(),
+ textAlign: this.isAnnotationOverlay ? 'initial' : undefined,
+ transform: `scale(${this.nativeDimScaling})`,
+ width: `${100 / this.nativeDimScaling}%`,
+ height: this._props.getScrollHeight?.() ?? `${100 / this.nativeDimScaling}%`,
+ }}>
+ {Doc.ActiveTool === InkTool.Eraser && Doc.ActiveEraser === InkEraserTool.Radius && this._showEraserCircle && (
+ <div
+ onPointerMove={this.onCursorMove}
+ style={{
+ position: 'fixed',
+ left: this._eraserX,
+ top: this._eraserY,
+ width: (ActiveEraserWidth() + 5) * 2,
+ height: (ActiveEraserWidth() + 5) * 2,
+ borderRadius: '50%',
+ border: '1px solid gray',
+ transform: 'translate(-50%, -50%)',
+ }}
+ />
+ )}
+ {this.paintFunc ? (
+ <FormattedTextBox {...this.props} /> // need this so that any live dashfieldviews will update the underlying text that the code eval reads
+ ) : this._lightboxDoc ? (
+ <div style={{ padding: 15, width: '100%', height: '100%' }}>
+ <DocumentView
+ {...this._props}
+ Document={this._lightboxDoc}
+ containerViewPath={this.DocumentView?.().docViewPath}
+ TemplateDataDocument={undefined}
+ PanelWidth={this.lightboxPanelWidth}
+ PanelHeight={this.lightboxPanelHeight}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ onClickScript={this.onChildClickHandler}
+ onKey={this.onKeyDown}
+ onDoubleClickScript={this.onChildDoubleClickHandler}
+ childFilters={this.childDocFilters}
+ childFiltersByRanges={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this.isContentActive}
+ isContentActive={this._props.childContentsActive ?? emptyFunction}
+ addDocTab={this.addDocTab}
+ ScreenToLocalTransform={this.lightboxScreenToLocal}
+ fitContentsToBox={undefined}
+ focus={this.focus}
/>
- )}
- {this.paintFunc ? (
- <FormattedTextBox {...this.props} /> // need this so that any live dashfieldviews will update the underlying text that the code eval reads
- ) : this._lightboxDoc ? (
- <div style={{ padding: 15, width: '100%', height: '100%' }}>
- <DocumentView
- {...this._props}
- Document={this._lightboxDoc}
- containerViewPath={this.DocumentView?.().docViewPath}
- TemplateDataDocument={undefined}
- PanelWidth={this.lightboxPanelWidth}
- PanelHeight={this.lightboxPanelHeight}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
- onClickScript={this.onChildClickHandler}
- onKey={this.onKeyDown}
- onDoubleClickScript={this.onChildDoubleClickHandler}
- childFilters={this.childDocFilters}
- childFiltersByRanges={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this.isContentActive}
- isContentActive={this._props.childContentsActive ?? emptyFunction}
- addDocTab={this.addDocTab}
- ScreenToLocalTransform={this.lightboxScreenToLocal}
- fitContentsToBox={undefined}
- focus={this.focus}
- />
- </div>
- ) : (
- <>
- {this._firstRender ? this.placeholder : this.marqueeView}
- {this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
- {!this.GroupChildDrag ? null : <div className="collectionFreeForm-groupDropper" />}
- </>
- )}
- </div>
+ </div>
+ ) : (
+ <>
+ {this._firstRender ? this.placeholder : this.marqueeView}
+ {this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
+ {!this.GroupChildDrag ? null : <div className="collectionFreeForm-groupDropper" />}
+ </>
+ )}
</div>
);
}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index a3d47290a..8e7307d23 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -280,7 +280,6 @@
.documentView-editorView {
width: 100%;
- overflow-y: scroll;
justify-items: center;
background-color: rgb(223, 223, 223);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 640d27aa1..e37658ca5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -85,7 +85,7 @@ export interface DocumentViewProps extends FieldViewSharedProps {
reactParent?: React.Component; // parent React component view (see CollectionFreeFormDocumentView)
}
@observer
-export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps>() {
+export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps & { showAIEditor: boolean }>() {
// this makes mobx trace() statements more descriptive
public get displayName() { return 'DocumentViewInternal(' + this.Document.title + ')'; } // prettier-ignore
public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered.
@@ -109,7 +109,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
private _mainCont = React.createRef<HTMLDivElement>();
private _titleRef = React.createRef<EditableView>();
private _dropDisposer?: DragManager.DragDropDisposer;
- constructor(props: FieldViewProps & DocumentViewProps) {
+ constructor(props: FieldViewProps & DocumentViewProps & { showAIEditor: boolean }) {
super(props);
makeObservable(this);
}
@@ -122,12 +122,6 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
@observable _componentView: Opt<ViewBoxInterface<FieldViewProps>> = undefined; // needs to be accessed from DocumentView wrapper class
@observable _animateScaleTime: Opt<number> = undefined; // milliseconds for animating between views. defaults to 300 if not uset
@observable _animateScalingTo = 0;
- @observable public _showAIEditor: boolean = false;
-
- @action
- showAIEditor() {
- this._showAIEditor = !this._showAIEditor;
- }
get _contentDiv() { return this._mainCont.current; } // prettier-ignore
get _docView() { return this._props.DocumentView?.(); } // prettier-ignore
@@ -689,7 +683,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
rootSelected = () => this._rootSelected;
panelHeight = () => this._props.PanelHeight() - this.headerMargin;
- screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
+ screenToLocalContent = () =>
+ this._props
+ .ScreenToLocalTransform()
+ .translate(0, -this.headerMargin)
+ .scale(this._props.showAIEditor ? (this._props.PanelHeight() || 1) / this.rph() : 1);
onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHdlr;
setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height)); } // prettier-ignore
setContentView = action((view: ViewBoxInterface<FieldViewProps>) => { this._componentView = view; }); // prettier-ignore
@@ -715,9 +713,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
return this._props.styleProvider?.(doc, props, property);
};
- @observable _aiWinHeight = 100;
- rpw = () => this._props.PanelWidth() - this._aiWinHeight;
- rph = () => this.panelHeight() - this._aiWinHeight;
+ @observable _aiWinHeight = 95;
+ rpw = () => (this.rph() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1);
+ rph = () => Math.max(10, this._props.PanelHeight() - this._aiWinHeight);
@computed get viewBoxContents() {
TraceMobx();
const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
@@ -728,8 +726,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
className="documentView-contentsView"
style={{
pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'),
- width: this._showAIEditor ? this.rpw() : undefined,
- height: this._showAIEditor ? this.rph() : this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
+ width: this._props.showAIEditor ? this.rpw() : undefined,
+ height: this._props.showAIEditor ? this.rph() : this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
<DocumentContentsView
{...this._props}
@@ -737,8 +735,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
pointerEvents={this.contentPointerEvents}
setContentViewBox={this.setContentView}
childFilters={this.childFilters}
- PanelHeight={this._showAIEditor ? this.rpw : this.panelHeight}
- PanelWidth={this._showAIEditor ? this.rph : this._props.PanelWidth}
+ PanelWidth={this._props.showAIEditor ? this.rpw : this._props.PanelWidth}
+ PanelHeight={this._props.showAIEditor ? this.rph : this.panelHeight}
setHeight={this.setHeight}
isContentActive={this.isContentActive}
ScreenToLocalTransform={this.screenToLocalContent}
@@ -748,7 +746,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
hideClickBehaviors={BoolCast(this.Document.hideClickBehaviors)}
/>
</div>
- {!this._showAIEditor ? null : (
+ {!this._props.showAIEditor ? null : (
<>
<div
className="documentView-editorView-history"
@@ -1168,10 +1166,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
@computed private get nativeScaling() {
if (this.shouldNotScale) return 1;
const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
- if (this.layout_fitWidth || this._props.PanelHeight() / (this.effectiveNativeHeight || 1) > this._props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
- return Math.max(minTextScale, this._props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or layout_fitWidth
+ const ai = this._showAIEditor && this.nativeWidth === this.layoutDoc.width ? 95 : 0;
+ const effNW = Math.max(this.effectiveNativeWidth - ai, 1);
+ const effNH = Math.max(this.effectiveNativeHeight - ai, 1);
+ if (this.layout_fitWidth || (this._props.PanelHeight() - ai) / effNH > (this._props.PanelWidth() - ai) / effNW) {
+ return Math.max(minTextScale, (this._props.PanelWidth() - ai) / effNW); // width-limited or layout_fitWidth
}
- return Math.max(minTextScale, this._props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
+ return Math.max(minTextScale, (this._props.PanelHeight() - ai) / effNH); // height-limited or unscaled
}
@computed private get panelWidth() {
return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this._props.PanelWidth();
@@ -1231,7 +1232,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
@computed get layout_fitWidth() {
- return this._props.fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
+ return this._showAIEditor ? false : (this._props.fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth);
}
@computed get anchorViewDoc() {
return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document.link_anchor_2) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document.link_anchor_1) : undefined;
@@ -1327,9 +1328,11 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
};
+ @observable public _showAIEditor: boolean = false;
+
@action
public toggleAIEditor = () => {
- this._docViewInternal && this._docViewInternal.showAIEditor();
+ this._showAIEditor = !this._showAIEditor;
};
public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => {
@@ -1516,6 +1519,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}}>
<DocumentViewInternal
{...this._props}
+ showAIEditor={this._showAIEditor}
reactParent={undefined}
isHovering={this.isHovering}
fieldKey={this.LayoutFieldKey}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 1b6b3c85a..a50320f5e 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -40,6 +40,7 @@
max-height: 100%;
pointer-events: inherit;
background: transparent;
+ z-index: -10000;
img {
height: auto;
@@ -159,6 +160,7 @@
.imageBox-aiView {
text-align: center;
font-weight: bold;
+ width: 100%;
.imageBox-aiView-subtitle {
position: relative;
@@ -168,20 +170,29 @@
.imageBox-aiView-regenerate-container,
.imageBox-aiView-options-container {
font-weight: normal;
- margin: 5px;
display: flex;
}
.imageBox-aiView-regenerate,
.imageBox-aiView-options {
display: flex;
- flex-direction: row;
- justify-content: center;
+ align-items: center;
flex-direction: row;
gap: 5px;
+ width: 100%;
}
+ .imageBox-aiView-strength {
+ text-align: center;
+ align-items: center;
+ display: flex;
+ width: 125px;
+ }
+ .imageBox-aiView-slider {
+ width: 50px;
+ margin-left: 5px;
+ }
.imageBox-aiView-input {
- width: 50%;
+ width: 100%;
}
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 9838af4d0..9223fc180 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,5 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@mui/material';
+import { Slider, Tooltip } from '@mui/material';
import axios from 'axios';
import { Colors, Button, Type, Size } from '@dash/components';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
@@ -44,6 +44,7 @@ import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler';
import { SettingsManager } from '../../util/SettingsManager';
import { AiOutlineSend } from 'react-icons/ai';
import { FireflyImageData } from '../smartdraw/FireflyConstants';
+import { DrawingFillHandler } from '../smartdraw/DrawingFillHandler';
export class ImageEditorData {
// eslint-disable-next-line no-use-before-define
@@ -98,7 +99,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@observable private _regenInput = '';
@observable private _canInteract = true;
@observable private _regenerateLoading = false;
- @observable private _prevImgs: FireflyImageData[] = [];
+ @observable private _prevImgs: FireflyImageData[] = StrCast(this.Document.ai_firefly_history) ? JSON.parse(StrCast(this.Document.ai_firefly_history)) : [];
constructor(props: FieldViewProps) {
super(props);
@@ -386,8 +387,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
: UpdateIcon(
this.layoutDoc[Id] + '_icon_' + new Date().getTime(),
contentDiv,
- usePanelDimensions ? this._props.PanelWidth() : NumCast(this.layoutDoc._width),
- usePanelDimensions ? this._props.PanelHeight() : NumCast(this.layoutDoc._height),
+ usePanelDimensions || true ? this._props.PanelWidth() : NumCast(this.layoutDoc._width),
+ usePanelDimensions || true ? this._props.PanelHeight() : NumCast(this.layoutDoc._height),
this._props.PanelWidth(),
this._props.PanelHeight(),
0,
@@ -543,65 +544,91 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
<div key={img.pathname}>
<img
className="imageBox-aiView-img"
- src={img.href}
+ 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;
}}
/>
- <text>{img.prompt}</text>
+ <span>{img.prompt}</span>
</div>
))}
</div>
);
};
+ @observable private _fireflyRefStrength = 0;
componentAIView = () => {
const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey]));
- const showRegenerate = this.Document[DocData].ai;
return (
<div className="imageBox-aiView">
- {showRegenerate && (
- <div className="imageBox-aiView-regenerate-container">
- <div className="imageBox-aiView-regenerate">
- <input
- className="imageBox-aiView-input"
- aria-label="Edit instructions input"
- type="text"
- value={this._regenInput}
- onChange={action(e => this._canInteract && (this._regenInput = e.target.value))}
- placeholder="Prompt (Optional)"
- />
- <Button
- text="Regenerate Image"
- type={Type.SEC}
- // style={{ alignSelf: 'flex-end' }}
- icon={this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
- iconPlacement="right"
- onClick={action(async () => {
- this._regenerateLoading = true;
- SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput, true).then(newImgs => {
- if (newImgs[0]) {
- const url = newImgs[0].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: newImgs[0].prompt, seed: newImgs[0].seed, href: this.paths.lastElement(), pathname: url });
- this.dataDoc.ai_firefly_history = `${this._prevImgs}`;
- this.dataDoc[this.fieldKey] = imgField;
- this._regenerateLoading = false;
- this._regenInput = '';
- }
- });
- })}
+ <div className="imageBox-aiView-regenerate-container">
+ <div className="imageBox-aiView-regenerate">
+ Firefly:
+ <input
+ className="imageBox-aiView-input"
+ aria-label="Edit instructions input"
+ type="text"
+ value={this._regenInput}
+ onChange={action(e => this._canInteract && (this._regenInput = e.target.value))}
+ placeholder={this._regenInput || StrCast(this.Document.title)}
+ />
+ <div className="imageBox-aiView-strength">
+ <span style={{ width: 60 }}>Similarity</span>
+ <Slider
+ className="imageBox-aiView-slider"
+ sx={{
+ '& .MuiSlider-track': { color: SettingsManager.userVariantColor },
+ '& .MuiSlider-rail': { color: SettingsManager.userBackgroundColor },
+ '& .MuiSlider-thumb': { color: SettingsManager.userVariantColor, '&.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"
/>
- <Button text="Get Variations" type={Type.SEC} iconPlacement="right" />
</div>
+ <Button
+ text="Create"
+ type={Type.SEC}
+ // style={{ alignSelf: 'flex-end' }}
+ icon={this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
+ iconPlacement="right"
+ onClick={action(async () => {
+ this._regenerateLoading = true;
+ if (this._fireflyRefStrength) {
+ DrawingFillHandler.drawingToImage(this.props.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 => {
+ if (newImgs[0]) {
+ const url = newImgs[0].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: newImgs[0].prompt, seed: newImgs[0].seed, pathname: url });
+ this.dataDoc.ai_firefly_history = JSON.stringify(this._prevImgs);
+ this.dataDoc.ai_firefly_prompt = newImgs[0].prompt;
+ this.dataDoc[this.fieldKey] = imgField;
+ this._regenerateLoading = false;
+ this._regenInput = '';
+ }
+ })
+ );
+ })}
+ />
</div>
- )}
+ </div>
<div className="imageBox-aiView-options-container">
- {showRegenerate && <text className="imageBox-aiView-subtitle"> More Options: </text>}
+ <span className="imageBox-aiView-subtitle"> More Options: </span>
<div className="imageBox-aiView-options">
<Button
type={Type.TERT}
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index 423e3b2ac..d9a01c436 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -15,7 +15,7 @@ export class DrawingFillHandler {
const docData = drawing[DocData];
const tags: string[] = ((docData?.tags as unknown as string[]) ?? []).map(tag => tag.slice(1)) ?? [];
const styles = tags.filter(tag => FireflyStylePresets.has(tag));
- DocumentView.GetDocImage(drawing)?.then(imageField => {
+ return DocumentView.GetDocImage(drawing)?.then(imageField => {
if (imageField) {
const aspectRatio = (drawing.width as number) / (drawing.height as number);
let dims: { width: number; height: number };
@@ -31,18 +31,17 @@ export class DrawingFillHandler {
const { href } = ImageCast(imageField).url;
const hrefParts = href.split('.');
const structureUrl = `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`;
- imageUrlToBase64(structureUrl)
- .then((hrefBase64: string) => gptDescribeImage(hrefBase64))
- .then(prompt => {
+ return imageUrlToBase64(structureUrl)
+ .then(gptDescribeImage)
+ .then(prompt =>
Networking.PostToServer('/queryFireflyImageFromStructure',
- { prompt: `${user_prompt}, ${prompt}`, width: dims.width, height: dims.height, structureUrl, strength, styles })
+ { prompt: `${user_prompt || prompt}`, width: dims.width, height: dims.height, structureUrl, strength, styles })
.then((info: Upload.ImageInformation) =>
DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(info.accessPaths.agnostic.client,
- { ai: 'firefly', ai_firefly_prompt: user_prompt || prompt, _width: 500, data_nativeWidth: info.nativeWidth, data_nativeHeight: info.nativeHeight }), OpenWhere.addRight)
- ).catch(e => alert("create image failed: " + e.toString())); // prettier-ignore
- });
+ { ai: 'firefly', title: user_prompt || prompt, ai_firefly_prompt: user_prompt || prompt, _width: 500, data_nativeWidth: info.nativeWidth, data_nativeHeight: info.nativeHeight }), OpenWhere.addRight)
+ ).catch(e => alert("create image failed: " + e.toString()))
+ ); // prettier-ignore
}
- return false;
});
};
}
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index f635b5642..7db9ef133 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -271,16 +271,16 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
createImageWithFirefly = (input: string, seed?: number, changeInPlace?: boolean): Promise<FireflyImageData> => {
this._lastInput.text = input;
const dims = FireflyDimensionsMap[this._imgDims];
- return Networking.PostToServer('/queryFireflyImage', { prompt: input, width: dims.width, height: dims.height, seed: seed })
+ return Networking.PostToServer('/queryFireflyImage', { prompt: input, width: dims.width, height: dims.height, seed })
.then(img => {
- const seed = img.accessPaths.agnostic.client.match(/\/(\d+)upload/)[1];
+ const newseed = img.accessPaths.agnostic.client.match(/\/(\d+)upload/)[1];
if (!changeInPlace) {
const imgDoc: Doc = Docs.Create.ImageDocument(img.accessPaths.agnostic.client, {
title: input.match(/^(.*?)~~~.*$/)?.[1] || input,
nativeWidth: dims.width,
nativeHeight: dims.height,
ai: 'firefly',
- ai_firefly_seed: seed,
+ ai_firefly_seed: newseed,
ai_firefly_prompt: input,
});
DocumentViewInternal.addDocTabFunc(imgDoc, OpenWhere.addRight);
@@ -306,7 +306,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
case DocumentType.IMG:
if (this._regenInput) {
// if (this._selectedDoc) {
- const newPrompt = `${doc.ai_firefly_prompt} ~~~ ${this._regenInput}`;
+ const newPrompt = doc.ai_firefly_prompt ? `${doc.ai_firefly_prompt} ~~~ ${this._regenInput}` : this._regenInput;
return this.createImageWithFirefly(newPrompt, NumCast(doc?.ai_firefly_seed), changeInPlace);
// }
}
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 5a880901b..c9d5df547 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -274,14 +274,20 @@ export default class UploadManager extends ApiManager {
.filter(f => regex.test(f))
.map(f => fs.unlinkSync(serverPath + f));
}
- imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
- const ext = path.extname(savedName).toLowerCase();
- const outputPath = serverPathToFile(Directory.images, filename + ext);
- if (AcceptableMedia.imageFormats.includes(ext)) {
- workerResample(savedName, outputPath, origSuffix, false);
- }
- res.send(clientPathToFile(Directory.images, filename + ext));
- });
+ imageDataUri
+ .outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix)))
+ .then((savedName: string) => {
+ const ext = path.extname(savedName).toLowerCase();
+ const outputPath = serverPathToFile(Directory.images, filename + ext);
+ if (AcceptableMedia.imageFormats.includes(ext)) {
+ workerResample(savedName, outputPath, origSuffix, false);
+ }
+ res.send(clientPathToFile(Directory.images, filename + ext));
+ })
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ .catch((e: any) => {
+ res.status(404).json({ error: e.toString() });
+ });
},
});
}