aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/nodes/ImageBox.tsx10
-rw-r--r--src/client/views/smartdraw/SmartDrawHandler.tsx366
-rw-r--r--src/client/views/smartdraw/StickerPalette.tsx434
3 files changed, 404 insertions, 406 deletions
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index a1fa9a283..ba0959d73 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -7,8 +7,8 @@ import { observer } from 'mobx-react';
import { extname } from 'path';
import * as React from 'react';
import ReactLoading from 'react-loading';
-import { ClientUtils, DashColor, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils';
-import { Doc, DocListCast, Opt, returnEmptyDoclist } from '../../../fields/Doc';
+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';
import { InkTool } from '../../../fields/InkField';
@@ -34,7 +34,7 @@ import { AnchorMenu } from '../pdf/AnchorMenu';
import { PinDocView, PinProps } from '../PinFuncs';
import { StickerPalette } from '../smartdraw/StickerPalette';
import { StyleProp } from '../StyleProp';
-import { DocumentView, DocumentViewInternal } from './DocumentView';
+import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { FocusViewOptions } from './FocusViewOptions';
import './ImageBox.scss';
@@ -44,7 +44,6 @@ import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler';
import { Button } from 'browndash-components';
import { SettingsManager } from '../../util/SettingsManager';
import { AiOutlineSend } from 'react-icons/ai';
-import { returnEmptyDocViewList } from '../StyleProvider';
import { FireflyImageData } from '../smartdraw/FireflyConstants';
export class ImageEditorData {
@@ -541,6 +540,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
<div className="imageBox-aiView-history">
{this._prevImgs.map(img => (
<img
+ key={img.pathname}
className="imageBox-aiView-img"
src={img.href}
onClick={() => {
@@ -583,7 +583,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
onClick={undoable(
action(async () => {
this._regenerateLoading = true;
- SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput, true).then(newImgs => {
+ 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);
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index 5ebe2e358..58db091bc 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -26,6 +26,7 @@ import './SmartDrawHandler.scss';
import { Networking } from '../../Network';
import { OpenWhere } from '../nodes/OpenWhere';
import { FireflyDimensionsMap, FireflyImageDimensions, FireflyImageData } from './FireflyConstants';
+import { DocumentType } from '../../documents/DocumentTypes';
export interface DrawingOptions {
text: string;
@@ -272,19 +273,19 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
const dims = FireflyDimensionsMap[this._imgDims];
return Networking.PostToServer('/queryFireflyImage', { prompt: input, width: dims.width, height: dims.height, seed: seed }).then(img => {
if (!changeInPlace) {
- const seed = img.accessPaths.agnostic.client.match(/\/(\d+)upload/)[1];
+ const aiseed = img.accessPaths.agnostic.client.match(/\/(\d+)upload/)[1];
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: aiseed,
ai_firefly_prompt: input,
});
DocumentViewInternal.addDocTabFunc(imgDoc, OpenWhere.addRight);
this._selectedDocs.push(imgDoc);
}
- return { prompt: input, seed: seed, pathname: img.accessPaths.agnostic.client };
+ return { prompt: input, seed, pathname: img.accessPaths.agnostic.client };
});
};
@@ -293,44 +294,43 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
* @param doc the drawing Docs to regenerate
*/
@action
- regenerate = async (drawingDocs: Doc[], lastInput?: DrawingOptions, lastResponse?: string, regenInput?: string, changeInPlace?: boolean) => {
+ regenerate = (drawingDocs: Doc[], lastInput?: DrawingOptions, lastResponse?: string, regenInput?: string, changeInPlace?: boolean) => {
if (lastInput) this._lastInput = lastInput;
if (lastResponse) this._lastResponse = lastResponse;
if (regenInput) this._regenInput = regenInput;
- return await Promise.all(
+ return Promise.all(
drawingDocs.map(async doc => {
- const docData = doc[DocData];
- if (docData.type == 'image') {
- const seed: number = docData?.ai_firefly_seed as number;
- if (this._regenInput !== '') {
- // if (this._selectedDoc) {
- const newPrompt = `${docData.ai_firefly_prompt} ~~~ ${this._regenInput}`;
- return this.createImageWithFirefly(newPrompt, seed, changeInPlace);
- // }
- } else {
- return this.createImageWithFirefly(this._lastInput.text || StrCast(docData.ai_firefly_prompt), undefined, changeInPlace);
- }
- }
- if (docData.type == 'collection') {
- try {
- let res;
- if (this._regenInput !== '') {
- const prompt: string = `This is your previously generated svg code: ${this._lastResponse} for the user input "${this._lastInput.text}". Please regenerate it with the provided specifications.`;
- res = await gptAPICall(`"${this._regenInput}"`, GPTCallType.DRAW, prompt, true);
- this._lastInput.text = `${this._lastInput.text} ~~~ ${this._regenInput}`;
- } else {
- res = await gptAPICall(`"${this._lastInput.text}", "${this._lastInput.complexity}", "${this._lastInput.size}"`, GPTCallType.DRAW, undefined, true);
+ switch (doc.type) {
+ case DocumentType.IMG:
+ if (this._regenInput) {
+ // if (this._selectedDoc) {
+ const newPrompt = `${doc.ai_firefly_prompt} ~~~ ${this._regenInput}`;
+ return this.createImageWithFirefly(newPrompt, NumCast(doc?.ai_firefly_seed), changeInPlace);
+ // }
}
- if (!res) {
- console.error('GPT call failed');
- return;
+ return this.createImageWithFirefly(this._lastInput.text || StrCast(doc.ai_firefly_prompt), undefined, changeInPlace);
+ case DocumentType.COL: {
+ try {
+ let res;
+ if (this._regenInput) {
+ const prompt = `This is your previously generated svg code: ${this._lastResponse} for the user input "${this._lastInput.text}". Please regenerate it with the provided specifications.`;
+ res = await gptAPICall(`"${this._regenInput}"`, GPTCallType.DRAW, prompt, true);
+ this._lastInput.text = `${this._lastInput.text} ~~~ ${this._regenInput}`;
+ } else {
+ res = await gptAPICall(`"${this._lastInput.text}", "${this._lastInput.complexity}", "${this._lastInput.size}"`, GPTCallType.DRAW, undefined, true);
+ }
+ if (res) {
+ const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor);
+ this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, doc);
+ const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes);
+ drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res);
+ } else {
+ console.error('GPT call failed');
+ }
+ } catch (err) {
+ console.error('Error regenerating drawing', err);
}
- const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor);
- this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, doc);
- const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes);
- drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res);
- } catch (err) {
- console.error('Error regenerating drawing', err);
+ break;
}
}
})
@@ -340,7 +340,6 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
/**
* Parses the svg code that GPT returns into Bezier curves, with coordinates and colors.
*/
- @action
parseSvg = async (res: string, startPoint: { X: number; Y: number }, regenerate: boolean, autoColor: boolean) => {
const svg = res.match(/<svg[^>]*>([\s\S]*?)<\/svg>/g);
if (svg) {
@@ -401,7 +400,108 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
});
}, 'color strokes');
- renderDisplay() {
+ renderGenerateOutputOptions = () => (
+ <div className="smartdraw-output-options">
+ <div className="drawing-checkbox">
+ Generate Ink
+ <Checkbox
+ sx={{
+ color: 'white',
+ '&.Mui-checked': {
+ color: SettingsManager.userVariantColor,
+ },
+ }}
+ checked={this._generateDrawing}
+ onChange={() => this._canInteract && (this._generateDrawing = !this._generateDrawing)}
+ />
+ </div>
+ <div className="image-checkbox">
+ Generate Image
+ <Checkbox
+ sx={{
+ color: 'white',
+ '&.Mui-checked': {
+ color: SettingsManager.userVariantColor,
+ },
+ }}
+ checked={this._generateImage}
+ onChange={() => this._canInteract && (this._generateImage = !this._generateImage)}
+ />
+ </div>
+ </div>
+ );
+
+ renderGenerateDrawing = () => (
+ <div className="smartdraw-options-container">
+ Drawing Options
+ <div className="smartdraw-options">
+ <div className="smartdraw-auto-color">
+ Auto color
+ <Switch
+ sx={{
+ '& .MuiSwitch-switchBase.Mui-checked': { color: SettingsManager.userColor },
+ '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { backgroundColor: SettingsManager.userVariantColor },
+ }}
+ defaultChecked={true}
+ value={this._autoColor}
+ size="small"
+ onChange={action(() => this._canInteract && (this._autoColor = !this._autoColor))}
+ />
+ </div>
+ <div className="smartdraw-complexity">
+ Complexity
+ <Slider
+ className="smartdraw-slider"
+ sx={{
+ '& .MuiSlider-track': { color: SettingsManager.userVariantColor },
+ '& .MuiSlider-rail': { color: SettingsManager.userColor },
+ '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } },
+ }}
+ min={1}
+ max={10}
+ step={1}
+ size="small"
+ value={this._complexity}
+ onChange={action((e, val) => this._canInteract && (this._complexity = val as number))}
+ valueLabelDisplay="auto"
+ />
+ </div>
+ <div className="smartdraw-size">
+ Size (in pixels)
+ <Slider
+ className="smartdraw-slider"
+ sx={{
+ '& .MuiSlider-track': { color: SettingsManager.userVariantColor },
+ '& .MuiSlider-rail': { color: SettingsManager.userColor },
+ '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}20` } },
+ }}
+ min={50}
+ max={700}
+ step={10}
+ size="small"
+ value={this._size}
+ onChange={action((e, val) => this._canInteract && (this._size = val as number))}
+ valueLabelDisplay="auto"
+ />
+ </div>
+ </div>
+ </div>
+ );
+
+ renderGenerateImage = () => (
+ <div className="smartdraw-options-container">
+ Image Options
+ <div className="smartdraw-dimensions">
+ <RadioGroup row defaultValue="square" sx={{ alignItems: 'center' }}>
+ {Object.values(FireflyImageDimensions).map(dim => (
+ <FormControlLabel sx={{ width: '40%' }} key={dim} value={dim} control={<Radio />} onChange={() => this._canInteract && (this._imgDims = dim)} label={dim} />
+ ))}
+ </RadioGroup>
+ </div>
+ </div>
+ );
+
+ renderDisplay = () => {
return (
<div
className="smart-draw-handler"
@@ -444,154 +544,64 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
</div>
{this._showOptions && (
<div>
- <div className="smartdraw-output-options">
- <div className="drawing-checkbox">
- Generate Ink
- <Checkbox
- sx={{
- color: 'white',
- '&.Mui-checked': {
- color: SettingsManager.userVariantColor,
- },
- }}
- checked={this._generateDrawing}
- onChange={() => this._canInteract && (this._generateDrawing = !this._generateDrawing)}
- />
- </div>
- <div className="image-checkbox">
- Generate Image
- <Checkbox
- sx={{
- color: 'white',
- '&.Mui-checked': {
- color: SettingsManager.userVariantColor,
- },
- }}
- checked={this._generateImage}
- onChange={() => this._canInteract && (this._generateImage = !this._generateImage)}
- />
- </div>
- </div>
- {this._generateDrawing && (
- <div className="smartdraw-options-container">
- Drawing Options
- <div className="smartdraw-options">
- <div className="smartdraw-auto-color">
- Auto color
- <Switch
- sx={{
- '& .MuiSwitch-switchBase.Mui-checked': { color: SettingsManager.userColor },
- '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { backgroundColor: SettingsManager.userVariantColor },
- }}
- defaultChecked={true}
- value={this._autoColor}
- size="small"
- onChange={action(() => this._canInteract && (this._autoColor = !this._autoColor))}
- />
- </div>
- <div className="smartdraw-complexity">
- Complexity
- <Slider
- className="smartdraw-slider"
- sx={{
- '& .MuiSlider-track': { color: SettingsManager.userVariantColor },
- '& .MuiSlider-rail': { color: SettingsManager.userColor },
- '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } },
- }}
- min={1}
- max={10}
- step={1}
- size="small"
- value={this._complexity}
- onChange={action((e, val) => this._canInteract && (this._complexity = val as number))}
- valueLabelDisplay="auto"
- />
- </div>
- <div className="smartdraw-size">
- Size (in pixels)
- <Slider
- className="smartdraw-slider"
- sx={{
- '& .MuiSlider-track': { color: SettingsManager.userVariantColor },
- '& .MuiSlider-rail': { color: SettingsManager.userColor },
- '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}20` } },
- }}
- min={50}
- max={700}
- step={10}
- size="small"
- value={this._size}
- onChange={action((e, val) => this._canInteract && (this._size = val as number))}
- valueLabelDisplay="auto"
- />
- </div>
- </div>
- </div>
- )}
- {this._generateImage && (
- <div className="smartdraw-options-container">
- Image Options
- <div className="smartdraw-dimensions">
- <RadioGroup row defaultValue="square" sx={{ alignItems: 'center' }}>
- <FormControlLabel sx={{ width: '40%' }} value="square" control={<Radio />} onChange={() => this._canInteract && (this._imgDims = FireflyImageDimensions.Square)} label="Square" />
- <FormControlLabel sx={{ width: '40%' }} value="landscape" control={<Radio />} onChange={() => this._canInteract && (this._imgDims = FireflyImageDimensions.Landscape)} label="Landscape" />
- <FormControlLabel sx={{ width: '40%' }} value="portrait" control={<Radio />} onChange={() => this._canInteract && (this._imgDims = FireflyImageDimensions.Portrait)} label="Portrait" />
- <FormControlLabel sx={{ width: '40%' }} value="widescreen" control={<Radio />} onChange={() => this._canInteract && (this._imgDims = FireflyImageDimensions.Widescreen)} label="Widescreen" />
- </RadioGroup>
- </div>
- </div>
- )}
+ {this.renderGenerateOutputOptions()}
+ {this._generateDrawing ? this.renderGenerateDrawing() : null}
+ {this._generateImage ? this.renderGenerateImage() : null}
</div>
)}
</div>
);
- }
+ };
- renderRegenerate() {
- return (
- <div
- className="smart-draw-handler"
- style={{
- left: this._pageX,
- ...(this._yRelativeToTop ? { top: Math.max(0, this._pageY) } : { bottom: this._pageY }),
- background: SettingsManager.userBackgroundColor,
- color: SettingsManager.userColor,
- }}>
- <div className="regenerate-box">
- <IconButton
- tooltip="Regenerate"
- icon={this._isLoading && this._regenInput === '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <FontAwesomeIcon icon={'rotate'} />}
- color={SettingsManager.userColor}
- onClick={this.handleSendClick}
- />
- <IconButton tooltip="Edit with GPT" icon={<FontAwesomeIcon icon="pen-to-square" />} color={SettingsManager.userColor} onClick={action(() => (this._showEditBox = !this._showEditBox))} />
- {this._showEditBox && (
- <div className="edit-box">
- <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="Edit instructions"
- />
- <Button
- style={{ alignSelf: 'flex-end' }}
- text="Send"
- icon={this._isLoading && this._regenInput !== '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
- iconPlacement="right"
- color={SettingsManager.userColor}
- onClick={this.handleSendClick}
- />
- </div>
- )}
- </div>
+ renderRegenerateEditBox = () => (
+ <div className="edit-box">
+ <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="Edit instructions"
+ />
+ <Button
+ style={{ alignSelf: 'flex-end' }}
+ text="Send"
+ icon={this._isLoading && this._regenInput !== '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
+ iconPlacement="right"
+ color={SettingsManager.userColor}
+ onClick={this.handleSendClick}
+ />
+ </div>
+ );
+
+ renderRegenerate = () => (
+ <div
+ className="smart-draw-handler"
+ style={{
+ left: this._pageX,
+ ...(this._yRelativeToTop ? { top: Math.max(0, this._pageY) } : { bottom: this._pageY }),
+ background: SettingsManager.userBackgroundColor,
+ color: SettingsManager.userColor,
+ }}>
+ <div className="regenerate-box">
+ <IconButton
+ tooltip="Regenerate"
+ icon={this._isLoading && this._regenInput === '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <FontAwesomeIcon icon={'rotate'} />}
+ color={SettingsManager.userColor}
+ onClick={this.handleSendClick}
+ />
+ <IconButton tooltip="Edit with GPT" icon={<FontAwesomeIcon icon="pen-to-square" />} color={SettingsManager.userColor} onClick={action(() => (this._showEditBox = !this._showEditBox))} />
+ {this._showEditBox ? this.renderRegenerateEditBox() : null}
</div>
- );
- }
+ </div>
+ );
render() {
- return this._display ? this.renderDisplay() : this.ShowRegenerate ? this.renderRegenerate() : null;
+ return this._display
+ ? this.renderDisplay() //
+ : this.ShowRegenerate
+ ? this.renderRegenerate()
+ : null;
}
}
diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx
index d23763eb9..e3345f547 100644
--- a/src/client/views/smartdraw/StickerPalette.tsx
+++ b/src/client/views/smartdraw/StickerPalette.tsx
@@ -7,7 +7,7 @@ import * as React from 'react';
import { AiOutlineSend } from 'react-icons/ai';
import ReactLoading from 'react-loading';
import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
-import { emptyFunction } from '../../../Utils';
+import { emptyFunction, numberRange } from '../../../Utils';
import { Doc, DocListCast, returnEmptyDoclist } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { ImageCast, NumCast } from '../../../fields/Types';
@@ -29,6 +29,11 @@ interface StickerPaletteProps {
Document: Doc;
}
+enum StickerPaletteMode {
+ create,
+ view,
+}
+
/**
* The StickerPalette can be toggled in the lightbox view of a document. The goal of the palette
* is to offer an easy way for users to create stickers and drag and drop them onto a document.
@@ -41,7 +46,34 @@ interface StickerPaletteProps {
*/
@observer
export class StickerPalette extends ObservableReactComponent<StickerPaletteProps> {
- @observable private _paletteMode: 'create' | 'view' = 'view';
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(StickerPalette, fieldKey);
+ }
+ /**
+ * Adds a doc to the sticker palette. Gets a snapshot of the document to use as a preview in the palette. When this
+ * preview is dragged onto a parent document, a copy of that document is added as a sticker.
+ */
+ public static addToPalette = async (doc: Doc) => {
+ if (!doc.savedAsSticker) {
+ const docView = DocumentView.getDocumentView(doc);
+ await docView?.ComponentView?.updateIcon?.(true);
+ const { clone } = await Doc.MakeClone(doc);
+ clone.title = doc.title;
+ const image = ImageCast(doc.icon, ImageCast(clone[Doc.LayoutFieldKey(clone)]))?.url?.href;
+ Doc.AddDocToList(Doc.MyStickers, 'data', makeUserTemplateButtonOrImage(clone, image));
+ doc.savedAsSticker = true;
+ }
+ };
+
+ public static getIcon(group: Doc) {
+ const docView = DocumentView.getDocumentView(group);
+ docView?.ComponentView?.updateIcon?.(true);
+ return !docView ? undefined : new Promise<ImageField | undefined>(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 1000));
+ }
+
+ private _gptRes: string[] = [];
+
+ @observable private _paletteMode = StickerPaletteMode.view;
@observable private _userInput: string = '';
@observable private _isLoading: boolean = false;
@observable private _canInteract: boolean = true;
@@ -49,7 +81,6 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
@observable private _docView: DocumentView | null = null;
@observable private _docCarouselView: DocumentView | null = null;
@observable private _opts: DrawingOptions = { text: '', complexity: 5, size: 200, autoColor: true, x: 0, y: 0 };
- private _gptRes: string[] = [];
constructor(props: StickerPaletteProps) {
super(props);
@@ -60,51 +91,40 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
this.resetPalette(true);
}
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(StickerPalette, fieldKey);
- }
-
- Contains = (view: DocumentView) => {
- return (this._docView && (view.containerViewPath?.() ?? []).concat(view).includes(this._docView)) || (this._docCarouselView && (view.containerViewPath?.() ?? []).concat(view).includes(this._docCarouselView));
- };
+ Contains = (view: DocumentView) =>
+ (this._docView && (view.containerViewPath?.() ?? []).concat(view).includes(this._docView)) || //
+ (this._docCarouselView && (view.containerViewPath?.() ?? []).concat(view).includes(this._docCarouselView));
return170 = () => 170;
- @action
- handleKeyPress = async (event: React.KeyboardEvent) => {
+ handleKeyPress = (event: React.KeyboardEvent) => {
if (event.key === 'Enter') {
- await this.generateDrawings();
+ this.generateDrawings();
}
};
- @action
- setPaletteMode = (mode: 'create' | 'view') => {
+ setPaletteMode = action((mode: StickerPaletteMode) => {
this._paletteMode = mode;
- };
+ });
- @action
- setUserInput = (input: string) => {
+ setUserInput = action((input: string) => {
if (!this._isLoading) this._userInput = input;
- };
+ });
- @action
- setDetail = (detail: number) => {
+ setDetail = action((detail: number) => {
if (this._canInteract) this._opts.complexity = detail;
- };
+ });
- @action
- setColor = (autoColor: boolean) => {
+ setColor = action((autoColor: boolean) => {
if (this._canInteract) this._opts.autoColor = autoColor;
- };
+ });
- @action
- setSize = (size: number) => {
+ setSize = action((size: number) => {
if (this._canInteract) this._opts.size = size;
- };
+ });
- @action
- resetPalette = (changePaletteMode: boolean) => {
- if (changePaletteMode) this.setPaletteMode('view');
+ resetPalette = action((changePaletteMode: boolean) => {
+ if (changePaletteMode) this.setPaletteMode(StickerPaletteMode.view);
this.setUserInput('');
this.setDetail(5);
this.setColor(true);
@@ -114,55 +134,31 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
this._opts = { text: '', complexity: 5, size: 200, autoColor: true, x: 0, y: 0 };
this._gptRes = [];
this._props.Document[DocData].data = undefined;
- };
-
- /**
- * Adds a doc to the sticker palette. Gets a snapshot of the document to use as a preview in the palette. When this
- * preview is dragged onto a parent document, a copy of that document is added as a sticker.
- */
- public static addToPalette = async (doc: Doc) => {
- if (!doc.savedAsSticker) {
- const docView = DocumentView.getDocumentView(doc);
- await docView?.ComponentView?.updateIcon?.(true);
- const { clone } = await Doc.MakeClone(doc);
- clone.title = doc.title;
- const image = ImageCast(doc.icon, ImageCast(clone[Doc.LayoutFieldKey(clone)]))?.url?.href;
- Doc.AddDocToList(Doc.MyStickers, 'data', makeUserTemplateButtonOrImage(clone, image));
- doc.savedAsSticker = true;
- }
- };
-
- public static getIcon(group: Doc) {
- const docView = DocumentView.getDocumentView(group);
- if (docView) {
- docView.ComponentView?.updateIcon?.(true);
- return new Promise<ImageField | undefined>(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 1000));
- }
- return undefined;
- }
+ });
/**
* Calls the draw with AI functions in SmartDrawHandler to allow users to generate drawings straight from
* the sticker palette.
*/
@undoBatch
- generateDrawings = action(async () => {
+ generateDrawings = action(() => {
this._isLoading = true;
const prevDrawings = DocListCast(this._props.Document[DocData].data);
this._props.Document[DocData].data = undefined;
SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
this._canInteract = false;
- await Promise.all(
- Array.from({ length: 3 }).map((_, i) => {
+ Promise.all(
+ numberRange(3).map(i => {
return this._showRegenerate
? SmartDrawHandler.Instance.regenerate(prevDrawings, this._opts, this._gptRes[i], this._userInput)
: SmartDrawHandler.Instance.drawWithGPT({ X: 0, Y: 0 }, this._userInput, this._opts.complexity, this._opts.size, this._opts.autoColor);
})
- );
- this._opts.text !== '' ? (this._opts.text = `${this._opts.text} ~~~ ${this._userInput}`) : (this._opts.text = this._userInput);
- this._userInput = '';
- this._isLoading = false;
- this._showRegenerate = true;
+ ).then(() => {
+ this._opts.text !== '' ? (this._opts.text = `${this._opts.text} ~~~ ${this._userInput}`) : (this._opts.text = this._userInput);
+ this._userInput = '';
+ this._isLoading = false;
+ this._showRegenerate = true;
+ });
});
@action
@@ -177,7 +173,7 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
* AddToPalette() is generically used to add any document to the palette, while this defines the behavior for when a user
* presses the "save drawing" button.
*/
- saveDrawing = async () => {
+ saveDrawing = () => {
const cIndex = NumCast(this._props.Document.carousel_index);
const focusedDrawing = DocListCast(this._props.Document.data)[cIndex];
const docData = focusedDrawing[DocData];
@@ -191,168 +187,160 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
focusedDrawing.width = this._opts.size;
docData.x = this._opts.x;
docData.y = this._opts.y;
- await StickerPalette.addToPalette(focusedDrawing);
- this.resetPalette(true);
+ StickerPalette.addToPalette(focusedDrawing).then(() => this.resetPalette(true));
+ };
+
+ renderCreateInput = () => (
+ <div className="palette-create">
+ <input
+ className="palette-create-input"
+ aria-label="label-input"
+ id="new-label"
+ type="text"
+ value={this._userInput}
+ onChange={e => this.setUserInput(e.target.value)}
+ placeholder={this._showRegenerate ? '(Optional) Enter edits' : 'Enter item to draw'}
+ onKeyDown={this.handleKeyPress}
+ />
+ <Button
+ style={{ alignSelf: 'flex-end' }}
+ tooltip={this._showRegenerate ? 'Regenerate' : 'Send'}
+ icon={this._isLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : this._showRegenerate ? <FontAwesomeIcon icon={'rotate'} /> : <AiOutlineSend />}
+ iconPlacement="right"
+ color={SettingsManager.userColor}
+ onClick={this.generateDrawings}
+ />
+ </div>
+ );
+ renderCreateOptions = () => (
+ <div className="palette-create-options">
+ <div className="palette-color">
+ Color
+ <Switch
+ sx={{
+ '& .MuiSwitch-switchBase.Mui-checked': {
+ color: SettingsManager.userColor,
+ },
+ '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': {
+ backgroundColor: SettingsManager.userVariantColor,
+ },
+ }}
+ defaultChecked={true}
+ value={this._opts.autoColor}
+ size="small"
+ onChange={() => this.setColor(!this._opts.autoColor)}
+ />
+ </div>
+ <div className="palette-detail">
+ Detail
+ <Slider
+ sx={{
+ '& .MuiSlider-thumb': {
+ color: SettingsManager.userColor,
+ '&.Mui-focusVisible, &:hover, &.Mui-active': {
+ boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10`,
+ },
+ },
+ '& .MuiSlider-track': {
+ color: SettingsManager.userVariantColor,
+ },
+ '& .MuiSlider-rail': {
+ color: SettingsManager.userColor,
+ },
+ }}
+ style={{ width: '80%' }}
+ min={1}
+ max={10}
+ step={1}
+ size="small"
+ value={this._opts.complexity}
+ onChange={(e, val) => typeof val === 'number' && this.setDetail(val)}
+ valueLabelDisplay="auto"
+ />
+ </div>
+ <div className="palette-size">
+ Size
+ <Slider
+ sx={{
+ '& .MuiSlider-thumb': {
+ color: SettingsManager.userColor,
+ '&.Mui-focusVisible, &:hover, &.Mui-active': {
+ boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}20`,
+ },
+ },
+ '& .MuiSlider-track': {
+ color: SettingsManager.userVariantColor,
+ },
+ '& .MuiSlider-rail': {
+ color: SettingsManager.userColor,
+ },
+ }}
+ style={{ width: '80%' }}
+ min={50}
+ max={500}
+ step={10}
+ size="small"
+ value={this._opts.size}
+ onChange={(e, val) => typeof val === 'number' && this.setSize(val)}
+ valueLabelDisplay="auto"
+ />
+ </div>
+ </div>
+ );
+ renderDoc = (doc: Doc, refFunc: (r: DocumentView) => void) => {
+ return (
+ <DocumentView
+ ref={refFunc}
+ Document={doc}
+ addDocument={undefined}
+ addDocTab={DocumentViewInternal.addDocTabFunc}
+ pinToPres={DocumentView.PinDoc}
+ containerViewPath={returnEmptyDocViewList}
+ styleProvider={DefaultStyleProvider}
+ removeDocument={returnFalse}
+ ScreenToLocalTransform={Transform.Identity}
+ PanelWidth={this.return170}
+ PanelHeight={this.return170}
+ renderDepth={1}
+ isContentActive={returnTrue}
+ focus={emptyFunction}
+ whenChildContentsActiveChanged={emptyFunction}
+ childFilters={returnEmptyFilter}
+ childFiltersByRanges={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ />
+ );
};
+ renderPaletteCreate = () => (
+ <>
+ {this.renderCreateInput()}
+ {this.renderCreateOptions()}
+ {this.renderDoc(this._props.Document, (r: DocumentView) => {
+ this._docCarouselView = r;
+ })}
+ <div className="palette-buttons">
+ <Button text="Back" tooltip="Back to All Stickers" icon={<FontAwesomeIcon icon="reply" />} color={SettingsManager.userColor} onClick={() => this.resetPalette(true)} />
+ <div className="palette-save-reset">
+ <Button tooltip="Save" icon={<FontAwesomeIcon icon="file-arrow-down" />} color={SettingsManager.userColor} onClick={this.saveDrawing} />
+ <Button tooltip="Reset" icon={<FontAwesomeIcon icon="rotate-left" />} color={SettingsManager.userColor} onClick={() => this.resetPalette(false)} />
+ </div>
+ </div>
+ </>
+ );
+ renderPaletteView = () => (
+ <>
+ {this.renderDoc(Doc.MyStickers, (r: DocumentView) => {
+ this._docView = r;
+ })}
+ <Button text="Add" icon={<FontAwesomeIcon icon="square-plus" />} color={SettingsManager.userColor} onClick={() => this.setPaletteMode(StickerPaletteMode.create)} />
+ </>
+ );
render() {
return (
<div className="sticker-palette" style={{ zIndex: 1000 }} onClick={e => e.stopPropagation()}>
- {this._paletteMode === 'view' && (
- <>
- <DocumentView
- ref={r => (this._docView = r)}
- Document={Doc.MyStickers}
- addDocument={undefined}
- addDocTab={DocumentViewInternal.addDocTabFunc}
- pinToPres={DocumentView.PinDoc}
- containerViewPath={returnEmptyDocViewList}
- styleProvider={DefaultStyleProvider}
- removeDocument={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- PanelWidth={this.return170}
- PanelHeight={this.return170}
- renderDepth={0}
- isContentActive={returnTrue}
- focus={emptyFunction}
- whenChildContentsActiveChanged={emptyFunction}
- childFilters={returnEmptyFilter}
- childFiltersByRanges={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- />
- <Button text="Add" icon={<FontAwesomeIcon icon="square-plus" />} color={SettingsManager.userColor} onClick={() => this.setPaletteMode('create')} />
- </>
- )}
- {this._paletteMode === 'create' && (
- <>
- <div className="palette-create">
- <input
- className="palette-create-input"
- aria-label="label-input"
- id="new-label"
- type="text"
- value={this._userInput}
- onChange={e => {
- this.setUserInput(e.target.value);
- }}
- placeholder={this._showRegenerate ? '(Optional) Enter edits' : 'Enter item to draw'}
- onKeyDown={this.handleKeyPress}
- />
- <Button
- style={{ alignSelf: 'flex-end' }}
- tooltip={this._showRegenerate ? 'Regenerate' : 'Send'}
- icon={this._isLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : this._showRegenerate ? <FontAwesomeIcon icon={'rotate'} /> : <AiOutlineSend />}
- iconPlacement="right"
- color={SettingsManager.userColor}
- onClick={this.generateDrawings}
- />
- </div>
- <div className="palette-create-options">
- <div className="palette-color">
- Color
- <Switch
- sx={{
- '& .MuiSwitch-switchBase.Mui-checked': {
- color: SettingsManager.userColor,
- },
- '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': {
- backgroundColor: SettingsManager.userVariantColor,
- },
- }}
- defaultChecked={true}
- value={this._opts.autoColor}
- size="small"
- onChange={() => this.setColor(!this._opts.autoColor)}
- />
- </div>
- <div className="palette-detail">
- Detail
- <Slider
- sx={{
- '& .MuiSlider-thumb': {
- color: SettingsManager.userColor,
- '&.Mui-focusVisible, &:hover, &.Mui-active': {
- boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10`,
- },
- },
- '& .MuiSlider-track': {
- color: SettingsManager.userVariantColor,
- },
- '& .MuiSlider-rail': {
- color: SettingsManager.userColor,
- },
- }}
- style={{ width: '80%' }}
- min={1}
- max={10}
- step={1}
- size="small"
- value={this._opts.complexity}
- onChange={(e, val) => {
- this.setDetail(val as number);
- }}
- valueLabelDisplay="auto"
- />
- </div>
- <div className="palette-size">
- Size
- <Slider
- sx={{
- '& .MuiSlider-thumb': {
- color: SettingsManager.userColor,
- '&.Mui-focusVisible, &:hover, &.Mui-active': {
- boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}20`,
- },
- },
- '& .MuiSlider-track': {
- color: SettingsManager.userVariantColor,
- },
- '& .MuiSlider-rail': {
- color: SettingsManager.userColor,
- },
- }}
- style={{ width: '80%' }}
- min={50}
- max={500}
- step={10}
- size="small"
- value={this._opts.size}
- onChange={(e, val) => {
- this.setSize(val as number);
- }}
- valueLabelDisplay="auto"
- />
- </div>
- </div>
- <DocumentView
- ref={r => (this._docCarouselView = r)}
- Document={this._props.Document}
- addDocument={undefined}
- addDocTab={DocumentViewInternal.addDocTabFunc}
- pinToPres={DocumentView.PinDoc}
- containerViewPath={returnEmptyDocViewList}
- styleProvider={DefaultStyleProvider}
- removeDocument={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- PanelWidth={this.return170}
- PanelHeight={this.return170}
- renderDepth={1}
- isContentActive={returnTrue}
- focus={emptyFunction}
- whenChildContentsActiveChanged={emptyFunction}
- childFilters={returnEmptyFilter}
- childFiltersByRanges={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- />
- <div className="palette-buttons">
- <Button text="Back" tooltip="Back to All Stickers" icon={<FontAwesomeIcon icon="reply" />} color={SettingsManager.userColor} onClick={() => this.resetPalette(true)} />
- <div className="palette-save-reset">
- <Button tooltip="Save" icon={<FontAwesomeIcon icon="file-arrow-down" />} color={SettingsManager.userColor} onClick={this.saveDrawing} />
- <Button tooltip="Reset" icon={<FontAwesomeIcon icon="rotate-left" />} color={SettingsManager.userColor} onClick={() => this.resetPalette(false)} />
- </div>
- </div>
- </>
- )}
+ {this._paletteMode === StickerPaletteMode.view ? this.renderPaletteView() : null}
+ {this._paletteMode === StickerPaletteMode.create ? this.renderPaletteCreate() : null}
</div>
);
}