aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/smartdraw/StickerPalette.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2025-01-08 10:29:50 -0500
committerbobzel <zzzman@gmail.com>2025-01-08 10:29:50 -0500
commit8da4acee81701b2b779713b1ff4cc4d60e80f437 (patch)
tree9c66c25463274ac6928041c28be8b4c6266d5fe9 /src/client/views/smartdraw/StickerPalette.tsx
parent7ec6b821746eeaf139799044013b45578619a7ed (diff)
code cleanup
Diffstat (limited to 'src/client/views/smartdraw/StickerPalette.tsx')
-rw-r--r--src/client/views/smartdraw/StickerPalette.tsx434
1 files changed, 211 insertions, 223 deletions
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>
);
}