diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 2 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 1 | ||||
-rw-r--r-- | src/client/views/global/globalScripts.ts | 8 | ||||
-rw-r--r-- | src/client/views/nodes/FontIconBox/FontIconBox.tsx | 28 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 6 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 76 | ||||
-rw-r--r-- | src/client/views/nodes/generativeFill/GenerativeFill.tsx | 115 | ||||
-rw-r--r-- | src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx | 6 | ||||
-rw-r--r-- | src/client/views/pdf/AnchorMenu.tsx | 1 | ||||
-rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.scss | 2 | ||||
-rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.tsx | 99 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 4 |
13 files changed, 210 insertions, 140 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c9a5175bb..6592ecffc 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -633,6 +633,8 @@ export class CurrentUserUtils { { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "View All", icon: "object-group", toolTip: "Fit all Docs to View", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + // want the same style as toggle button, but don't want it to act as an actual toggle, so set disableToggle to true, + { title: "Fit All", icon: "arrows-left-right", toolTip: "Fit all Docs to View (persistent)", btnType: ButtonType.IconClickButton, ignoreClick: false, expertMode: false, toolType:"viewAllPersist", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Arrange",icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 60eb64caa..8fba9b886 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -80,7 +80,6 @@ export class MainView extends React.Component { @observable public imageEditorSource: string = ''; @action public setImageEditorSource = (source: string) => (this.imageEditorSource = source); @observable public imageRootDoc: Doc | undefined; - @action public setImageRootDoc = (doc: Doc) => (this.imageRootDoc = doc); @observable public addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined; @observable public LastButton: Opt<Doc>; @@ -350,6 +349,7 @@ export class MainView extends React.Component { fa.faMousePointer, fa.faMusic, fa.faObjectGroup, + fa.faArrowsLeftRight, fa.faPause, fa.faPen, fa.faPenNib, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 29122cb91..f353dfab7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1599,6 +1599,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection () => this.rootDoc.fitContentOnce, fitContentOnce => { if (fitContentOnce) this.fitContentOnce(); + this.rootDoc.fitContentOnce = false; }, { fireImmediately: true, name: 'fitContent' } ); diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 256377758..38bf1042d 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -85,10 +85,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); }); -ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) { +ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'viewAllPersist', checkResult?: boolean) { const selected = SelectionManager.Docs().lastElement(); // prettier-ignore - const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([ + const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'viewAllPersist', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([ ['grid', { checkResult: (doc:Doc) => BoolCast(doc._freeform_backgroundGrid, false), setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, @@ -101,6 +101,10 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snapli checkResult: (doc:Doc) => BoolCast(doc._freeform_fitContentsToBox, false), setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox, }], + ['viewAllPersist', { + checkResult: (doc:Doc) => false, + setDoc: (doc:Doc) => doc.fitContentOnce = true + }], ['clusters', { waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire checkResult: (doc:Doc) => BoolCast(doc._freeform_useClusters, false), diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 91eac675f..90ec212fc 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -29,6 +29,7 @@ export enum ButtonType { DropdownList = 'dropdownList', DropdownButton = 'dropdownBtn', ClickButton = 'clickBtn', + IconClickButton = 'iconClickBtn', ToggleButton = 'toggleBtn', ColorButton = 'colorBtn', ToolButton = 'toolBtn', @@ -346,6 +347,32 @@ export class FontIconBox extends DocComponent<ButtonProps>() { ); } + @computed get iconClickButton() { + // Determine the type of toggle button + const buttonText: string = StrCast(this.rootDoc.buttonText); + const tooltip: string = StrCast(this.rootDoc.toolTip); + + const script = ScriptCast(this.rootDoc.onClick); + const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false; + // Colors + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + + return ( + <Toggle + tooltip={`${tooltip}`} + toggleType={ToggleType.BUTTON} + type={Type.PRIM} + toggleStatus={toggleStatus} + text={buttonText} + color={color} + icon={this.Icon(color)!} + label={this.label} + onPointerDown={() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: !toggleStatus, _readOnly_: false })} + /> + ); + } + /** * Default */ @@ -393,6 +420,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { case ButtonType.DropdownButton: return this.dropdownButton; case ButtonType.MultiToggleButton: return this.multiToggleButton; case ButtonType.ToggleButton: return this.toggleButton; + case ButtonType.IconClickButton: return this.iconClickButton; case ButtonType.ClickButton: case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} />; case ButtonType.TextButton: return <Button {...btnProps} text={StrCast(this.rootDoc.buttonText)}/>; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 4ce359f3f..352e0fbdc 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -252,8 +252,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp funcs.push({ description: 'Open Image Editor', event: action(() => { - MainView.Instance.setImageEditorOpen(true); - MainView.Instance.setImageEditorSource(this.choosePath(field.url)); + MainView.Instance.imageEditorOpen = true; + MainView.Instance.imageEditorSource = this.choosePath(field.url); MainView.Instance.addDoc = this.props.addDocument; MainView.Instance.imageRootDoc = this.rootDoc; }), @@ -306,7 +306,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp choosePath(url: URL) { const lower = url.href.toLowerCase(); if (url.protocol === 'data') return url.href; - if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf("dashblobstore") === -1) return Utils.CorsProxy(url.href); + if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href); if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return `/assets/unknown-file-icon-hi.png`; const ext = extname(url.href); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 332f0f467..5acc45d02 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -946,44 +946,46 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text); GPTPopup.Instance?.setTextAnchor(this.getAnchor(false)); GPTPopup.Instance?.setImgTargetDoc(this.rootDoc); - GPTPopup.Instance.setImgUrls([]); - GPTPopup.Instance.setMode(GPTPopupMode.IMAGE); - GPTPopup.Instance.setVisible(true); + // GPTPopup.Instance.setImgUrls([]); + // GPTPopup.Instance.setMode(GPTPopupMode.IMAGE); + // GPTPopup.Instance.setVisible(true); GPTPopup.Instance.addToCollection = this.props.addDocument; - GPTPopup.Instance.setLoading(true); - - try { - // make this support multiple images - let image_urls = await gptImageCall((this.dataDoc.text as RichTextField)?.Text); - console.log(image_urls); - if (image_urls) { - const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] }); - const source = Utils.prepend(result.accessPaths.agnostic.client); - GPTPopup.Instance.setImgUrls([source]); - - // const newDoc = Docs.Create.ImageDocument(source, { - // x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10, - // y: NumCast(this.rootDoc.y), - // _height: 200, - // _width: 200, - // data_nativeWidth: result.nativeWidth, - // data_nativeHeight: result.nativeHeight, - // }); - // if (Doc.IsInMyOverlay(this.rootDoc)) { - // newDoc.overlayX = this.rootDoc.x; - // newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height); - // Doc.AddToMyOverlay(newDoc); - // } else { - // this.props.addDocument?.(newDoc); - // } - // // Create link between prompt and image - // DocUtils.MakeLink(this.rootDoc, newDoc, { link_relationship: 'Image Prompt' }); - } - } catch (err) { - console.log(err); - return ''; - } - GPTPopup.Instance.setLoading(false); + // GPTPopup.Instance.setLoading(true); + GPTPopup.Instance.setImgDesc((this.dataDoc.text as RichTextField)?.Text); + GPTPopup.Instance.generateImage(); + + // try { + // // make this support multiple images + // let image_urls = await gptImageCall((this.dataDoc.text as RichTextField)?.Text); + // console.log(image_urls); + // if (image_urls) { + // const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] }); + // const source = Utils.prepend(result.accessPaths.agnostic.client); + // GPTPopup.Instance.setImgUrls([source]); + + // // const newDoc = Docs.Create.ImageDocument(source, { + // // x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10, + // // y: NumCast(this.rootDoc.y), + // // _height: 200, + // // _width: 200, + // // data_nativeWidth: result.nativeWidth, + // // data_nativeHeight: result.nativeHeight, + // // }); + // // if (Doc.IsInMyOverlay(this.rootDoc)) { + // // newDoc.overlayX = this.rootDoc.x; + // // newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height); + // // Doc.AddToMyOverlay(newDoc); + // // } else { + // // this.props.addDocument?.(newDoc); + // // } + // // // Create link between prompt and image + // // DocUtils.MakeLink(this.rootDoc, newDoc, { link_relationship: 'Image Prompt' }); + // } + // } catch (err) { + // console.log(err); + // return ''; + // } + // GPTPopup.Instance.setLoading(false); }; breakupDictation = () => { diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index f8f9fe077..99068d647 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -3,7 +3,7 @@ import React = require('react'); import { useEffect, useRef, useState } from 'react'; import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler'; import { BrushHandler } from './generativeFillUtils/BrushHandler'; -import { Box, IconButton, Slider, TextField } from '@mui/material'; +import { Box, Checkbox, FormControlLabel, IconButton, Slider, TextField } from '@mui/material'; import { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces'; import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants'; import { PointerHandler } from './generativeFillUtils/PointerHandler'; @@ -19,6 +19,7 @@ import { Cast, DocCast, NumCast } from '../../../../fields/Types'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; import { OpenWhere, OpenWhereMod } from '../DocumentView'; import { Oval } from 'react-loader-spinner'; +import { CheckBox } from '../../search/CheckBox'; /** * For images not 1024x1024 fill in the rest in solid black, or a @@ -72,6 +73,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const [saveLoading, setSaveLoading] = useState(false); + const [isNewCollection, setIsNewCollection] = useState(false); // the current image in the main canvas const currImg = useRef<HTMLImageElement | null>(null); // the unedited version of each generation (parent) @@ -81,9 +83,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // stores redo stack const redoStack = useRef<string[]>([]); - // early stage properly, likely will get rid of - const freeformPosition = useRef<number[]>([0, 0]); - // which urls were already saved to canvas const savedSrcs = useRef<Set<string>>(new Set()); @@ -91,7 +90,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const newCollectionRef = useRef<Doc | null>(null); const parentDoc = useRef<Doc | null>(null); const childrenDocs = useRef<Doc[]>([]); - const addToExistingCollection = useRef<boolean>(false); // Undo and Redo const handleUndo = () => { @@ -111,10 +109,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }; const handleRedo = () => { - // TODO: handle undo as well + const ctx = ImageUtility.getCanvasContext(canvasRef); + if (!ctx || !currImg.current || !canvasRef.current) return; + const target = redoStack.current[redoStack.current.length - 1]; if (!target) { } else { + undoStack.current = [...undoStack.current, canvasRef.current?.toDataURL()]; const img = new Image(); img.src = target; ImageUtility.drawImgToCanvas(img, canvasRef); @@ -188,7 +189,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD ImageUtility.drawImgToCanvas(img, canvasRef); currImg.current = img; originalImg.current = img; - freeformPosition.current = [0, 0]; return () => { console.log('cleanup'); @@ -197,7 +197,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD childrenDocs.current = []; currImg.current = null; originalImg.current = null; - freeformPosition.current = [0, 0]; undoStack.current = []; redoStack.current = []; }; @@ -266,24 +265,25 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // create first image if (!newCollectionRef.current) { - if (addToExistingCollection.current) { + if (!isNewCollection) { + // newcollection should stay null + } else { + if (!(originalImg.current && imageRootDoc)) return; + // create new collection and add it to the view + newCollectionRef.current = Docs.Create.FreeformDocument([], { + x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX, + y: NumCast(imageRootDoc.y), + _width: newCollectionSize, + _height: newCollectionSize, + title: 'Image edit collection', + }); + DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false }); + // add the doc to the main freeform + addDoc?.(newCollectionRef.current); + await createNewImgDoc(originalImg.current, true); } - if (!(originalImg.current && imageRootDoc)) return; - console.log('creating first image'); - // create new collection and add it to the view - newCollectionRef.current = Docs.Create.FreeformDocument([], { - x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX, - y: NumCast(imageRootDoc.y), - _width: newCollectionSize, - _height: newCollectionSize, - title: 'Image edit collection', - }); - DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false }); - // add the doc to the main freeform - // addDoc?.(newCollectionRef.current); - await createNewImgDoc(originalImg.current, true); } else { - parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1]; + // parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1]; childrenDocs.current = []; } @@ -292,12 +292,24 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const { urls } = res as APISuccess; const image = new Image(); image.src = urls[0]; + + // need to crop images agh + // save to dash + + // const imageRes = await Promise.all( + // urls.map(async url => { + // const newImg = new Image(); + // newImg.src = url; + // await onSave(newImg); + // }) + // ); + + // map each url to [url, imgDoc] setEdits(urls); + ImageUtility.drawImgToCanvas(image, canvasRef); currImg.current = image; - onSave(); - freeformPosition.current[0] += 1; - freeformPosition.current[1] = 0; + // onSave(currImg.current); } catch (err) { console.log(err); } @@ -324,7 +336,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // creates a new image document and returns its reference const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise<Doc | undefined> => { - if (!newCollectionRef.current || !imageRootDoc) return; + if (!imageRootDoc) return; const src = img.src; const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] }); const source = Utils.prepend(result.accessPaths.agnostic.client); @@ -332,7 +344,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD if (firstDoc) { const x = 0; const initialY = 0; - console.log('first doc'); const newImg = Docs.Create.ImageDocument(source, { x: x, @@ -343,16 +354,19 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD data_nativeHeight: result.nativeHeight, }); - Doc.AddDocToList(newCollectionRef.current, undefined, newImg); + if (isNewCollection && newCollectionRef.current) { + Doc.AddDocToList(newCollectionRef.current, undefined, newImg); + } else { + addDoc?.(newImg); + } + parentDoc.current = newImg; return newImg; } else { if (!parentDoc.current) return; const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX; // dummy position - console.log('creating child elements'); const initialY = 0; - const newImg = Docs.Create.ImageDocument(source, { x: x, y: initialY, @@ -363,21 +377,26 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }); childrenDocs.current.push(newImg); + + if (isNewCollection && newCollectionRef.current) { + Doc.AddDocToList(newCollectionRef.current, undefined, newImg); + } else { + addDoc?.(newImg); + } + DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: 'Image Edit', link_displayLine: true }); adjustImgPositions(); - - Doc.AddDocToList(newCollectionRef.current, undefined, newImg); return newImg; } }; // need to maybe call on every img click, not just when the save btn is clicked - const onSave = async () => { + const onSave = async (img: HTMLImageElement) => { setSaveLoading(true); - if (!currImg.current || !originalImg.current || !imageRootDoc) return; + // if (!currImg.current || !originalImg.current || !imageRootDoc) return; try { console.log('creating another image'); - await createNewImgDoc(currImg.current, false); + await createNewImgDoc(img, false); } catch (err) { console.log(err); } @@ -387,7 +406,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const handleViewClose = () => { if (newCollectionRef.current) { newCollectionRef.current.fitContentOnce = true; - CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); + // CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); } MainView.Instance.setImageEditorOpen(false); MainView.Instance.setImageEditorSource(''); @@ -399,7 +418,20 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD <div className="generativeFillControls"> <h1>AI Image Editor</h1> <div style={{ display: 'flex', alignItems: 'center', gap: '1.5rem' }}> - <Buttons canvasRef={canvasRef} currImg={currImg} getEdit={getEdit} loading={loading} onSave={onSave} onReset={handleReset} /> + <FormControlLabel + control={ + <Checkbox + checked={isNewCollection} + onChange={e => { + setIsNewCollection(prev => !prev); + }} + /> + } + label={'Create New Collection'} + labelPlacement="end" + sx={{ whiteSpace: 'nowrap' }} + /> + <Buttons canvasRef={canvasRef} currImg={currImg} getEdit={getEdit} loading={loading} onReset={handleReset} /> <IconButton onClick={handleViewClose}> <BsX color={activeColor} /> </IconButton> @@ -491,13 +523,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD height={100} src={edit} onClick={async () => { - // if (savedSrcs.current.has(edit)) return; const img = new Image(); img.src = edit; ImageUtility.drawImgToCanvas(img, canvasRef); currImg.current = img; savedSrcs.current.add(edit); - await onSave(); + undoStack.current = []; + redoStack.current = []; + await onSave(img); }} /> ))} diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx index 53c6cec84..b4d56b408 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx @@ -11,14 +11,10 @@ interface ButtonContainerProps { currImg: React.MutableRefObject<HTMLImageElement | null>; getEdit: () => Promise<void>; loading: boolean; - onSave: () => Promise<void>; onReset: () => void; } -const Buttons = ({ canvasRef, currImg, loading, getEdit, onSave, onReset }: ButtonContainerProps) => { - const handleSave = () => { - onSave(); - }; +const Buttons = ({ canvasRef, currImg, loading, getEdit, onReset }: ButtonContainerProps) => { return ( <div className="generativeFillBtnContainer"> diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 7404650d6..1be8fe6ab 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -130,6 +130,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { * @param e pointer down event */ gptSummarize = async (e: React.PointerEvent) => { + // move this logic to gptpopup, need to implement generate again GPTPopup.Instance.setVisible(true); this.setHighlightRange(undefined); GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY); diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index 478b7d4ba..5d966395c 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -123,9 +123,11 @@ $highlightedText: #82e0ff; cursor: pointer; .img-container { + pointer-events: none; position: relative; img { + pointer-events: all; position: relative; } } diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index fc6fc1af8..943c38d42 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -10,6 +10,11 @@ import { DocUtils, Docs } from '../../../documents/Documents'; import { Button, IconButton, Type } from 'browndash-components'; import { NumCast, StrCast } from '../../../../fields/Types'; import { CgClose } from 'react-icons/cg'; +import { AnchorMenu } from '../AnchorMenu'; +import { gptAPICall, gptImageCall } from '../../../apis/gpt/GPT'; +import { RichTextField } from '../../../../fields/RichTextField'; +import { Networking } from '../../../Network'; +import { Utils } from '../../../../Utils'; export enum GPTPopupMode { SUMMARY, @@ -43,10 +48,17 @@ export class GPTPopup extends React.Component<GPTPopupProps> { }; @observable - public imageUrls: string[] = []; + public imgDesc: string = ''; @action - public setImgUrls = (imgs: string[]) => { - this.imageUrls = imgs; + public setImgDesc = (text: string) => { + this.imgDesc = text; + }; + + @observable + public imgUrls: string[][] = []; + @action + public setImgUrls = (imgs: string[][]) => { + this.imgUrls = imgs; }; @observable @@ -102,6 +114,30 @@ export class GPTPopup extends React.Component<GPTPopupProps> { public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false; public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined; + generateImage = async () => { + if (this.imgDesc === '') return; + this.setImgUrls([]); + this.setMode(GPTPopupMode.IMAGE); + this.setVisible(true); + this.setLoading(true); + + try { + // make this support multiple images + let image_urls = await gptImageCall(this.imgDesc); + console.log(image_urls); + if (image_urls && image_urls[0]) { + const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] }); + const source = Utils.prepend(result.accessPaths.agnostic.client); + console.log('Source', source); + this.setImgUrls([[image_urls[0], source]]); + } + } catch (err) { + console.log(err); + return ''; + } + GPTPopup.Instance.setLoading(false); + }; + /** * Transfers the summarization text to a sidebar annotation text document. */ @@ -113,8 +149,9 @@ export class GPTPopup extends React.Component<GPTPopupProps> { _layout_autoHeight: true, }); this.addDoc(newDoc, this.sidebarId); - if (this.targetAnchor) { - DocUtils.MakeLink(newDoc, this.targetAnchor, { + const anchor = AnchorMenu.Instance?.GetAnchor(undefined, false); + if (anchor) { + DocUtils.MakeLink(newDoc, anchor, { link_relationship: 'GPT Summary', }); } @@ -163,17 +200,22 @@ export class GPTPopup extends React.Component<GPTPopupProps> { <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> {this.heading('GENERATED IMAGE')} <div className="image-content-wrapper"> - {this.imageUrls.map(rawSrc => ( + {this.imgUrls.map(rawSrc => ( <div className="img-wrapper"> <div className="img-container"> - <img key={rawSrc} src={this.getPreviewUrl(rawSrc)} width={150} height={150} alt="dalle generation" /> + <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" /> </div> <div className="btn-container"> - <Button text="Save Image" onClick={() => this.transferToImage(rawSrc)} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} /> + <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} /> </div> </div> ))} </div> + {!this.loading && ( + <> + <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} /> + </> + )} </div> ); }; @@ -227,43 +269,6 @@ export class GPTPopup extends React.Component<GPTPopupProps> { </> ); - editBox = () => { - const hr = this.highlightRange; - return ( - <> - <div> - {this.heading('TEXT EDIT SUGGESTIONS')} - <div className="content-wrapper"> - {hr && ( - <div> - {this.text.slice(0, hr[0])} <span className="highlighted-text">{this.text.slice(hr[0], hr[1])}</span> {this.text.slice(hr[1])} - </div> - )} - </div> - </div> - {hr && !this.loading && ( - <> - <div className="btns-wrapper"> - <> - <button className="icon-btn" onPointerDown={this.callEditApi}> - <FontAwesomeIcon icon="redo-alt" size="lg" /> - </button> - <button - className="text-btn" - onClick={e => { - this.replaceText(this.text); - }}> - Replace Text - </button> - </> - </div> - {this.aiWarning()} - </> - )} - </> - ); - }; - aiWarning = () => this.done ? ( <div className="ai-warning"> @@ -277,14 +282,14 @@ export class GPTPopup extends React.Component<GPTPopupProps> { heading = (headingText: string) => ( <div className="summary-heading"> <label className="summary-text">{headingText}</label> - {this.loading ? <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> : <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />} + {this.loading ? <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> : <IconButton color={StrCast(Doc.UserDoc().userVariantColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />} </div> ); render() { return ( <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}> - {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : this.editBox()} + {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : <></>} </div> ); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index c9fee4813..1319a236d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -424,10 +424,6 @@ export class PDFViewer extends React.Component<IViewerProps> { // Changing which document to add the annotation to (the currently selected PDF) GPTPopup.Instance.setSidebarId('data_sidebar'); - const anchor = this._getAnchor(undefined, false); - if (anchor) { - GPTPopup.Instance.setTargetAnchor(anchor); - } GPTPopup.Instance.addDoc = this.props.sidebarAddDoc; }; |