aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/ImageBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/ImageBox.tsx')
-rw-r--r--src/client/views/nodes/ImageBox.tsx185
1 files changed, 183 insertions, 2 deletions
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index de8a9ad21..e0ef8f423 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,13 +1,13 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import axios from 'axios';
-import { Colors } from '@dash/components';
+import { Colors, Button, Type } from '@dash/components';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { extname } from 'path';
import * as React from 'react';
import ReactLoading from 'react-loading';
-import { ClientUtils, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
+import { ClientUtils, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -32,6 +32,7 @@ import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { OverlayView } from '../OverlayView';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { PinDocView, PinProps } from '../PinFuncs';
+import { StickerPalette } from '../smartdraw/StickerPalette';
import { StyleProp } from '../StyleProp';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
@@ -39,6 +40,10 @@ import { FocusViewOptions } from './FocusViewOptions';
import './ImageBox.scss';
import { OpenWhere } from './OpenWhere';
import { Upload } from '../../../server/SharedMediaTypes';
+import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler';
+import { SettingsManager } from '../../util/SettingsManager';
+import { AiOutlineSend } from 'react-icons/ai';
+import { FireflyImageData } from '../smartdraw/FireflyConstants';
export class ImageEditorData {
// eslint-disable-next-line no-use-before-define
@@ -350,10 +355,47 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}),
icon: 'pencil-alt',
});
+ this.layoutDoc.ai &&
+ funcs.push({
+ description: 'Regenerate AI Image',
+ event: action(e => {
+ !SmartDrawHandler.Instance.ShowRegenerate ? SmartDrawHandler.Instance.displayRegenerate(e?.x || 0, e?.y || 0) : SmartDrawHandler.Instance.hideRegenerate();
+ }),
+ icon: 'pen-to-square',
+ });
+ funcs.push({
+ description: this.Document.savedAsSticker ? 'Sticker Saved!' : 'Save to Stickers',
+ event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')),
+ icon: this.Document.savedAsSticker ? 'clipboard-check' : 'file-arrow-down',
+ });
ContextMenu.Instance?.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
}
};
+ // updateIcon = () => new Promise<void>(res => res());
+ updateIcon = (usePanelDimensions?: boolean) => {
+ const contentDiv = this._mainCont.current;
+ return !contentDiv
+ ? new Promise<void>(res => res())
+ : 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),
+ this._props.PanelWidth(),
+ this._props.PanelHeight(),
+ 0,
+ 1,
+ false,
+ '',
+ (iconFile, nativeWidth, nativeHeight) => {
+ this.dataDoc.icon = new ImageField(iconFile);
+ this.dataDoc.icon_nativeWidth = nativeWidth;
+ this.dataDoc.icon_nativeHeight = nativeHeight;
+ }
+ );
+ };
+
choosePath = (url: URL) => {
if (!url?.href) return '';
const lower = url.href.toLowerCase();
@@ -487,6 +529,145 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
);
}
+ @observable private _regenInput = '';
+ @observable private _canInteract = true;
+ @observable private _regenerateLoading = false;
+ @observable private _prevImgs: FireflyImageData[] = [];
+
+ componentAIViewHistory = () => {
+ return (
+ <div className="imageBox-aiView-history">
+ {this._prevImgs.map(img => (
+ <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;
+ }}
+ />
+ ))}
+ </div>
+ );
+ };
+
+ 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">
+ Edit Image with AI
+ {showRegenerate && (
+ <div className="imageBox-aiView-regenerate-container">
+ <text className="imageBox-aiView-subtitle">Regenerate AI Image</text>
+ <div className="imageBox-aiView-regenerate">
+ <input
+ className="imageBox-aiView-input"
+ aria-label="Edit instructions input"
+ // className="smartdraw-input"
+ type="text"
+ value={this._regenInput}
+ onChange={action(e => this._canInteract && (this._regenInput = e.target.value))}
+ // onKeyDown={this.handleKeyPress}
+ placeholder="Prompt (Optional)"
+ />
+ <Button
+ text="Regenerate"
+ type={Type.SEC}
+ // style={{ alignSelf: 'flex-end' }}
+ icon={this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
+ iconPlacement="right"
+ onClick={undoable(
+ action(async () => {
+ this._regenerateLoading = true;
+ await 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: NumCast(this.dataDoc.ai_firefly_seed), 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._regenerateLoading = false;
+ this._regenInput = '';
+ }
+ });
+ }),
+ 'regenerate image'
+ )}
+ />
+ <Button
+ // style={{ alignSelf: 'flex-end' }}
+ text="Get Variations"
+ type={Type.SEC}
+ // icon={this._isLoading && this._regenInput !== '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
+ iconPlacement="right"
+ // onClick={this.handleSendClick}
+ />
+ </div>
+ </div>
+ )}
+ <div className="imageBox-aiView-options-container">
+ {showRegenerate && <text className="imageBox-aiView-subtitle"> More Image Options </text>}
+ <div className="imageBox-aiView-options">
+ <Button
+ type={Type.TERT}
+ text="Get Text"
+ icon={<FontAwesomeIcon icon="font" />}
+ color={SettingsManager.userBackgroundColor}
+ iconPlacement="right"
+ onClick={() => {
+ Networking.PostToServer('/queryFireflyImageText', {
+ file: (file => {
+ const ext = extname(file);
+ return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
+ })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href),
+ }).then(text => alert(text));
+ }}
+ />
+ <Button
+ type={Type.TERT}
+ text="Generative Fill"
+ icon={<FontAwesomeIcon icon="fill" />}
+ color={SettingsManager.userBackgroundColor}
+ // icon={this._isLoading && this._regenInput !== '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
+ iconPlacement="right"
+ onClick={action(() => {
+ ImageEditorData.Open = true;
+ ImageEditorData.Source = (field && this.choosePath(field.url)) || '';
+ ImageEditorData.AddDoc = this._props.addDocument;
+ ImageEditorData.RootDoc = this.Document;
+ })}
+ />
+ <Button
+ type={Type.TERT}
+ text="Expand"
+ icon={<FontAwesomeIcon icon="expand" />}
+ color={SettingsManager.userBackgroundColor}
+ // icon={this._isLoading && this._regenInput !== '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
+ iconPlacement="right"
+ onClick={() => {
+ Networking.PostToServer('/expandImage', {
+ prompt: 'sunny skies',
+ file: (file => {
+ const ext = extname(file);
+ return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
+ })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href),
+ }).then((info: Upload.ImageInformation) => {
+ const img = Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { title: 'expand:' + this.Document.title });
+ DocUtils.assignImageInfo(info, img);
+ this._props.addDocTab(img, OpenWhere.addRight);
+ });
+ }}
+ />
+ </div>
+ </div>
+ </div>
+ );
+ };
+
@computed get annotationLayer() {
TraceMobx();
return <div className="imageBox-annotationLayer" style={{ height: this._props.PanelHeight() }} ref={this._annotationLayer} />;