diff options
Diffstat (limited to 'src')
9 files changed, 58 insertions, 68 deletions
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<DocumentDecora embedding.y = -NumCast(embedding._height) / 2; CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([embedding], { title: 'Tab for ' + embedding.title }), OpenWhereMod.right); } else if (e.altKey) { - // open same document in new tab - CollectionDockingView.ToggleSplit(selView.Document, OpenWhereMod.right); + // open same document in new tab or in custom editor + selView.ComponentView?.docEditorView?.() ?? CollectionDockingView.ToggleSplit(selView.Document, OpenWhereMod.right); } else { let openDoc = selView.Document; if (openDoc.layout_fieldKey === 'layout_icon') { @@ -821,7 +822,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora {hideDeleteButton ? null : topBtn('close', 'times', undefined, () => this.onCloseClick(true), 'Close')} {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, () => this.onCloseClick(undefined), 'Minimize')} {titleArea} - {hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')} + {hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection, opption: in editor view)')} </div> {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<P> extends ObservableReactComponent<React } promoteCollection?: () => void; // moves contents of collection to parent hasChildDocs?: () => Doc[]; + docEditorView?: () => void; updateIcon?: (usePanelDimensions?: boolean) => Promise<void>; // 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<FieldViewProps>() { 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<FieldViewProps>() { 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<ImageEditTool>(genFillTool); + const [currToolType, setCurrToolType] = useState<ImageToolType>(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' }} /> - <ApplyFuncButtons onClick={() => currTool.applyFunc(cutType, cursorData.width, edits, isFirstDoc)} loading={loading} onReset={handleReset} btnText={currTool.btnText} /> + <ApplyFuncButtons onClick={() => currTool().applyFunc(cutType, cursorData.width, edits, isFirstDoc)} loading={loading} onReset={handleReset} btnText={currTool().btnText} /> <IconButton color={activeColor} tooltip="close" icon={<CgClose size="16px" />} onClick={handleViewClose} /> </div> </div> @@ -607,8 +596,8 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc return ( <div className="sideControlsContainer" style={{ backgroundColor: bgColor }}> <div className="sideControls"> - <div className="imageToolsContainer">{imageEditTools.map(tool => ImageToolButton(tool, tool.type === currTool.type, changeTool))}</div> - {currTool.type == ImageToolType.Cut && ( + <div className="imageToolsContainer">{imageEditTools.map(tool => ImageToolButton(tool, tool.type === currTool().type, changeTool))}</div> + {currTool().type == ImageToolType.Cut && ( <div className="cutToolsContainer"> <Button style={{ width: '100%' }} text="Keep in" type={Type.TERT} color={cutType == CutMode.IN ? SettingsManager.userColor : bgColor} onClick={() => setCutType(CutMode.IN)} /> <Button style={{ width: '100%' }} text="Keep out" type={Type.TERT} color={cutType == CutMode.OUT ? SettingsManager.userColor : bgColor} onClick={() => setCutType(CutMode.OUT)} /> @@ -617,7 +606,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc </div> )} <div className="sliderContainer" onPointerDown={e => e.stopPropagation()}> - {currTool.type === ImageToolType.GenerativeFill && ( + {currTool().type === ImageToolType.GenerativeFill && ( <Slider sx={{ '& input[type="range"]': { @@ -633,7 +622,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc onChange={(e, val) => setCursorData(prev => ({ ...prev, width: val as number }))} /> )} - {currTool.type === ImageToolType.Cut && ( + {currTool().type === ImageToolType.Cut && ( <Slider sx={{ '& input[type="range"]': { @@ -780,7 +769,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc {renderSideIcons()} {renderEditThumbnails()} </div> - {currTool.type === ImageToolType.GenerativeFill && renderPromptBox()} + {currTool().type === ImageToolType.GenerativeFill && renderPromptBox()} </div> ); }; 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 ( - <div key={tool.name} className="imageEditorButtonContainer"> + <div key={tool.type} className="imageEditorButtonContainer"> <Button style={{ width: '100%' }} - text={tool.name} + text={tool.type} type={Type.TERT} color={isActive ? SettingsManager.userVariantColor : bgColor} icon={<FontAwesomeIcon icon={tool.icon} />} diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts index ece0f4d7f..1c6a38a24 100644 --- a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts +++ b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts @@ -87,7 +87,6 @@ export class ImageUtility { body: fd, }); const data = await res.json(); - console.log(data.data); return { status: 'success', urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`), diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts index a14b55439..02dbc0312 100644 --- a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts +++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts @@ -13,8 +13,8 @@ export interface Point { } export enum ImageToolType { - GenerativeFill = 'genFill', - Cut = 'cut', + GenerativeFill = 'Generative Fill', + Cut = 'Cut', } export enum CutMode { @@ -26,7 +26,6 @@ export enum CutMode { export interface ImageEditTool { type: ImageToolType; - name: string; btnText: string; icon: IconProp; // this is the function that the image tool applies, so it can be defined depending on the tool |