aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/DocumentView.tsx49
-rw-r--r--src/client/views/nodes/ImageBox.scss21
-rw-r--r--src/client/views/nodes/ImageBox.tsx154
3 files changed, 129 insertions, 95 deletions
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 44b214644..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,10 +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;
@@ -729,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}
@@ -738,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}
@@ -749,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"
@@ -1169,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();
@@ -1232,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;
@@ -1328,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) => {
@@ -1517,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 94a497cf0..29b24b774 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;
@@ -163,7 +164,6 @@
.imageBox-aiView {
text-align: center;
font-weight: bold;
- width: 100%;
.imageBox-aiView-subtitle {
position: relative;
@@ -173,14 +173,29 @@
.imageBox-aiView-regenerate-container,
.imageBox-aiView-options-container {
font-weight: normal;
- margin: 5px;
display: flex;
}
.imageBox-aiView-regenerate,
.imageBox-aiView-options {
display: flex;
- margin: auto;
+ 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: 100%;
}
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 3a9086777..ec6ce8c2a 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
@@ -96,8 +97,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// variables for AI Image Editor
@observable private _regenInput = '';
- @observable private _regenLoading = false;
- @observable private _prevImgs: FireflyImageData[] = [];
+ @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)) : [];
constructor(props: FieldViewProps) {
super(props);
@@ -385,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,
@@ -547,82 +549,96 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const imgs: FireflyImageData[] = this.dataDoc.ai_firefly_history ? JSON.parse(StrCast(this.dataDoc.ai_firefly_history)) : [];
return (
<div className="imageBox-aiView-history">
- {imgs.length >= 2 && (
- <Button
- text="Clear History"
- type={Type.SEC}
- style={{ marginTop: '5px' }}
- size={Size.XSMALL}
- onClick={action(() => {
- this._prevImgs = [];
- this.dataDoc.ai_firefly_history = undefined;
- })}
- />
- )}
- {imgs.length >= 2 &&
- imgs.map(img => (
- <div>
- <img
- key={img.pathname}
- className="imageBox-aiView-img"
- src={img.href}
- onClick={() => {
- this.dataDoc[this.fieldKey] = new ImageField(img.pathname);
- this.dataDoc.ai_firefly_prompt = img.prompt;
- this.dataDoc.ai_firefly_seed = img.seed;
- }}
- />
- <text className="imageBox-aiView-caption">{img.prompt.replace(/ ~~~/g, ',')}</text>
- </div>
- ))}
+ <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>
);
};
+ @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">
- <text>Edit Image with AI </text>
- {showRegenerate && (
- <div className="imageBox-aiView-regenerate-container">
- <div className="imageBox-aiView-regenerate">
- <input
- style={{ transform: `scale(${this.uiInputScaling})` }}
- className="imageBox-aiView-input"
- aria-label="Edit instructions input"
- type="text"
- value={this._regenInput}
- onChange={action(e => (this._regenInput = e.target.value))}
- placeholder="Prompt (Optional)"
- />
- <Button
- style={{ transform: `scale(${this.uiBtnScaling})` }}
- text="Regenerate Image"
- type={Type.SEC}
- onClick={action(async () => {
- this._regenLoading = 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.dataDoc[this.fieldKey] = imgField;
- this._prevImgs.unshift({ prompt: newImgs[0].prompt, seed: newImgs[0].seed, href: this.paths.lastElement(), pathname: url });
- this.dataDoc.ai_firefly_history = JSON.stringify(this._prevImgs);
- this._regenLoading = 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 style={{ transform: `scale(${this.uiBtnScaling})` }} 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">
+ <span className="imageBox-aiView-subtitle"> More Options: </span>
<div className="imageBox-aiView-options">
{showRegenerate && (
<text className="imageBox-aiView-subtitle" style={{ transform: `scale(${this.uiBtnScaling})` }}>