From c079b7aa916d7eb35ce186806ca312c84e910cb2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 14 Feb 2025 14:07:18 -0500 Subject: major restructure of GPTPopup so that it works with all collections. --- src/client/views/ViewBoxInterface.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/client/views/ViewBoxInterface.ts') diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts index a66a20cf6..30da8c616 100644 --- a/src/client/views/ViewBoxInterface.ts +++ b/src/client/views/ViewBoxInterface.ts @@ -22,6 +22,7 @@ export abstract class ViewBoxInterface

extends ObservableReactComponent void; // moves contents of collection to parent + hasChildDocs?: () => Doc[]; updateIcon?: (usePanelDimensions?: boolean) => Promise; // updates the icon representation of the document getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) restoreView?: (viewSpec: Doc) => boolean; // DEPRECATED: do not use, it will go away. see PresBox.restoreTargetDocView -- cgit v1.2.3-70-g09d2 From 30ec70722678eba318fb7521a8b2bd642328bf54 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 23 Feb 2025 16:34:40 -0500 Subject: added option+maximize to open in doc editor view (currently only for images). fixed imageEditor to generate edits the first time through. --- src/client/views/DocumentDecorations.tsx | 7 +- src/client/views/StyleProviderQuiz.tsx | 2 +- src/client/views/ViewBoxInterface.ts | 1 + src/client/views/nodes/ImageBox.tsx | 21 +++--- src/client/views/nodes/calendarBox/CalendarBox.tsx | 2 +- src/client/views/nodes/imageEditor/ImageEditor.tsx | 83 ++++++++++------------ .../views/nodes/imageEditor/ImageEditorButtons.tsx | 4 +- .../imageEditor/imageEditorUtils/ImageHandler.ts | 1 - .../imageEditorUtils/imageEditorInterfaces.ts | 5 +- 9 files changed, 58 insertions(+), 68 deletions(-) (limited to 'src/client/views/ViewBoxInterface.ts') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 54ff3904d..d9b6bdf1a 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -36,6 +36,7 @@ import { ImageBox } from './nodes/ImageBox'; import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { TagsView } from './TagsView'; +import { ImageField } from '../../fields/URLField'; interface DocumentDecorationsProps { PanelWidth: number; @@ -284,8 +285,8 @@ export class DocumentDecorations extends ObservableReactComponent this.onCloseClick(true), 'Close')} {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, () => this.onCloseClick(undefined), 'Minimize')} {titleArea} - {hideOpenButton ?

: topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')} + {hideOpenButton ?
: topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection, opption: in editor view)')}
{hideResizers ? null : ( <> diff --git a/src/client/views/StyleProviderQuiz.tsx b/src/client/views/StyleProviderQuiz.tsx index d8eeb3490..db9ab831a 100644 --- a/src/client/views/StyleProviderQuiz.tsx +++ b/src/client/views/StyleProviderQuiz.tsx @@ -125,7 +125,7 @@ export namespace styleProviderQuiz { try { const hrefBase64 = await createCanvas(img); const response = await gptImageLabel(hrefBase64, 'Make flashcards out of this image with each question and answer labeled as "question" and "answer". Do not label each flashcard and do not include asterisks: '); - AnchorMenu.Instance.transferToFlashcard(response, NumCast(img.layoutDoc['x']), NumCast(img.layoutDoc['y'])); + AnchorMenu.Instance.transferToFlashcard(response, NumCast(img.layoutDoc.x), NumCast(img.layoutDoc.y)); } catch (error) { console.log('Error', error); } diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts index 30da8c616..b943259ff 100644 --- a/src/client/views/ViewBoxInterface.ts +++ b/src/client/views/ViewBoxInterface.ts @@ -23,6 +23,7 @@ export abstract class ViewBoxInterface

extends ObservableReactComponent void; // moves contents of collection to parent hasChildDocs?: () => Doc[]; + docEditorView?: () => void; updateIcon?: (usePanelDimensions?: boolean) => Promise; // updates the icon representation of the document getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) restoreView?: (viewSpec: Doc) => boolean; // DEPRECATED: do not use, it will go away. see PresBox.restoreTargetDocView diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index ff6baf199..dd28832f9 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -316,6 +316,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { return cropping; }; + docEditorView = action(() => { + const field = Cast(this.dataDoc[this.fieldKey], ImageField); + if (field) { + ImageEditorData.Open = true; + ImageEditorData.Source = this.choosePath(field.url); + ImageEditorData.AddDoc = this._props.addDocument; + ImageEditorData.RootDoc = this.Document; + } + }); + specificContextMenu = (): void => { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (field) { @@ -353,16 +363,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { icon: 'expand-arrows-alt', }); funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' }); - funcs.push({ - description: 'Open Image Editor', - event: action(() => { - ImageEditorData.Open = true; - ImageEditorData.Source = this.choosePath(field.url); - ImageEditorData.AddDoc = this._props.addDocument; - ImageEditorData.RootDoc = this.Document; - }), - icon: 'pencil-alt', - }); + funcs.push({ description: 'Open Image Editor', event: this.docEditorView, icon: 'pencil-alt' }); this.layoutDoc.ai && funcs.push({ description: 'Regenerate AI Image', diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index c1f6f5859..009eb82cd 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -6,7 +6,7 @@ import interactionPlugin from '@fullcalendar/interaction'; import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { dateRangeStrToDates, simulateMouseClick } from '../../../../ClientUtils'; +import { dateRangeStrToDates } from '../../../../ClientUtils'; import { Doc } from '../../../../fields/Doc'; import { BoolCast, NumCast, StrCast } from '../../../../fields/Types'; import { CollectionSubView, SubCollectionViewProps } from '../../collections/CollectionSubView'; diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx index 6b1d05031..3c0ab3da5 100644 --- a/src/client/views/nodes/imageEditor/ImageEditor.tsx +++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx @@ -90,18 +90,8 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc * @param type The new tool type we are changing to */ const changeTool = (type: ImageToolType) => { - switch (type) { - case ImageToolType.GenerativeFill: - setCurrTool(genFillTool); - setCursorData(prev => ({ ...prev, width: genFillTool.sliderDefault as number })); - break; - case ImageToolType.Cut: - setCurrTool(cutTool); - setCursorData(prev => ({ ...prev, width: cutTool.sliderDefault as number })); - break; - default: - break; - } + setCurrToolType(type); + setCursorData(prev => ({ ...prev, width: currTool().sliderDefault as number })); }; // Undo and Redo const handleUndo = () => { @@ -171,9 +161,8 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc // handles brushing on pointer movement useEffect(() => { - if (!isBrushing) return undefined; const canvas = canvasRef.current; - if (!canvas) return undefined; + if (!isBrushing || !canvas) return undefined; const ctx = ImageUtility.getCanvasContext(canvasRef); if (!ctx) return undefined; @@ -188,33 +177,29 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }; drawingAreaRef.current?.addEventListener('pointermove', handlePointerMove); - return () => { - drawingAreaRef.current?.removeEventListener('pointermove', handlePointerMove); - }; + return () => drawingAreaRef.current?.removeEventListener('pointermove', handlePointerMove); }, [isBrushing]); // first load useEffect(() => { - const loadInitial = async () => { - if (!imageEditorSource || imageEditorSource === '') return; - const img = new Image(); - const res = await ImageUtility.urlToBase64(imageEditorSource); - if (!res) return; - img.src = `data:image/png;base64,${res}`; - - img.onload = () => { - currImg.current = img; - originalImg.current = img; - const imgWidth = img.naturalWidth; - const imgHeight = img.naturalHeight; - const scale = Math.min(canvasSize / imgWidth, canvasSize / imgHeight); - const width = imgWidth * scale; - const height = imgHeight * scale; - setCanvasDims({ width, height }); - }; - }; - - loadInitial(); + if (imageEditorSource && imageEditorSource) { + ImageUtility.urlToBase64(imageEditorSource).then(res => { + if (res) { + const img = new Image(); + img.src = `data:image/png;base64,${res}`; + img.onload = () => { + currImg.current = img; + originalImg.current = img; + const imgWidth = img.naturalWidth; + const imgHeight = img.naturalHeight; + const scale = Math.min(canvasSize / imgWidth, canvasSize / imgHeight); + const width = imgWidth * scale; + const height = imgHeight * scale; + setCanvasDims({ width, height }); + }; + } + }); + } // cleanup return () => { @@ -300,7 +285,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc if (!canvasMask) return; const maskBlob = await ImageUtility.canvasToBlob(canvasMask); const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg); - const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2); + const res = await ImageUtility.getEdit(imgBlob, maskBlob, input || 'Fill in the image in the same style', 2); // create first image if (!newCollectionRef.current) { @@ -569,11 +554,15 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc setIsFirstDoc(true); }; + function currTool() { + return imageEditTools.find(tool => tool.type === currToolType) ?? genFillTool; + } + // defines the tools and sets current tool - const genFillTool: ImageEditTool = { type: ImageToolType.GenerativeFill, name: 'Generative Fill', btnText: 'GET EDITS', icon: 'fill', applyFunc: getEdit, sliderMin: 25, sliderMax: 500, sliderDefault: 150 }; - const cutTool: ImageEditTool = { type: ImageToolType.Cut, name: 'Cut', btnText: 'CUT IMAGE', icon: 'scissors', applyFunc: cutImage, sliderMin: 1, sliderMax: 50, sliderDefault: 5 }; + const genFillTool: ImageEditTool = { type: ImageToolType.GenerativeFill, btnText: 'GET EDITS', icon: 'fill', applyFunc: getEdit, sliderMin: 25, sliderMax: 500, sliderDefault: 150 }; + const cutTool: ImageEditTool = { type: ImageToolType.Cut, btnText: 'CUT IMAGE', icon: 'scissors', applyFunc: cutImage, sliderMin: 1, sliderMax: 50, sliderDefault: 5 }; const imageEditTools: ImageEditTool[] = [genFillTool, cutTool]; - const [currTool, setCurrTool] = useState(genFillTool); + const [currToolType, setCurrToolType] = useState(ImageToolType.GenerativeFill); // the top controls for making a new collection, resetting, and applying edits, function renderControls() { @@ -595,7 +584,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc labelPlacement="end" sx={{ whiteSpace: 'nowrap' }} /> - currTool.applyFunc(cutType, cursorData.width, edits, isFirstDoc)} loading={loading} onReset={handleReset} btnText={currTool.btnText} /> + currTool().applyFunc(cutType, cursorData.width, edits, isFirstDoc)} loading={loading} onReset={handleReset} btnText={currTool().btnText} /> } onClick={handleViewClose} />

@@ -607,8 +596,8 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc return (
-
{imageEditTools.map(tool => ImageToolButton(tool, tool.type === currTool.type, changeTool))}
- {currTool.type == ImageToolType.Cut && ( +
{imageEditTools.map(tool => ImageToolButton(tool, tool.type === currTool().type, changeTool))}
+ {currTool().type == ImageToolType.Cut && (
)}
e.stopPropagation()}> - {currTool.type === ImageToolType.GenerativeFill && ( + {currTool().type === ImageToolType.GenerativeFill && ( setCursorData(prev => ({ ...prev, width: val as number }))} /> )} - {currTool.type === ImageToolType.Cut && ( + {currTool().type === ImageToolType.Cut && ( - {currTool.type === ImageToolType.GenerativeFill && renderPromptBox()} + {currTool().type === ImageToolType.GenerativeFill && renderPromptBox()}
); }; diff --git a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx index 985dc914f..3eaa251f2 100644 --- a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx +++ b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx @@ -53,10 +53,10 @@ export function ApplyFuncButtons({ loading, onClick: getEdit, onReset, btnText } export function ImageToolButton(tool: ImageEditTool, isActive: boolean, selectTool: (type: ImageToolType) => void) { return ( -
+