From 3f54517e96ccff233b1560627995024e137dbdfd Mon Sep 17 00:00:00 2001 From: sharkiecodes Date: Tue, 11 Mar 2025 16:27:30 -0400 Subject: Doing outpainting implementation --- src/client/views/nodes/ImageBox.tsx | 409 ++++++++++++++++++++++++++++-------- 1 file changed, 326 insertions(+), 83 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 5b06e9fc5..114d5226b 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,6 +1,7 @@ import { Button, Colors, Size, Type } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Slider, Tooltip } from '@mui/material'; +import { DimensionField } from '../../../fields/DimensionField'; import axios from 'axios'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx'; import { observer } from 'mobx-react'; @@ -46,6 +47,7 @@ import { FocusViewOptions } from './FocusViewOptions'; import './ImageBox.scss'; import { OpenWhere } from './OpenWhere'; import { RichTextField } from '../../../fields/RichTextField'; +import { List } from '../../../fields/List'; export class ImageEditorData { // eslint-disable-next-line no-use-before-define @@ -103,6 +105,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { @observable private _regenerateLoading = false; @observable private _prevImgs: FireflyImageData[] = StrCast(this.Document.ai_firefly_history) ? JSON.parse(StrCast(this.Document.ai_firefly_history)) : []; + // Add these observable properties to the ImageBox class + @observable private _outpaintingInProgress = false; + @observable private _outpaintingPrompt = ''; + constructor(props: FieldViewProps) { super(props); makeObservable(this); @@ -135,6 +141,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { }; componentDidMount() { + super.componentDidMount?.(); this._disposers.sizer = reaction( () => ({ forceFull: this._props.renderDepth < 1 || this.layoutDoc._showFullRes, @@ -148,9 +155,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { ); const { layoutDoc } = this; this._disposers.path = reaction( - () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width), height: this.layoutDoc._height }), - ({ nativeSize, width, height }) => { - if ((layoutDoc === this.layoutDoc && !this.layoutDoc._layout_nativeDimEditable) || !height) { + () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }), + ({ nativeSize, width }) => { + if ((layoutDoc === this.layoutDoc && !this.layoutDoc._layout_nativeDimEditable) || !this.layoutDoc._height) { this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; } }, @@ -166,6 +173,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { }, { fireImmediately: true } ); + this._disposers.outpainting = reaction( + () => this.Document?._needsOutpainting, + needsOutpainting => { + if (needsOutpainting && this.Document?._outpaintingResize) { + this.processOutpainting(); + } + } + ); } componentWillUnmount() { @@ -214,7 +229,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { }, true); } else if (hitDropTarget(e.target as HTMLElement, this._regenerateIconRef.current)) { this._regenerateLoading = true; - const drag = de.complete.docDragData.draggedDocuments.lastElement(); + const drag = de.complete.docDragData?.draggedDocuments.lastElement(); const dragField = drag[Doc.LayoutFieldKey(drag)]; const oldPrompt = StrCast(this.Document.ai_firefly_prompt, StrCast(this.Document.title)); const newPrompt = (text: string) => (oldPrompt ? `${oldPrompt} ~~~ ${text}` : text); @@ -339,6 +354,202 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { } }); + // Add this method to process outpainting when resize is complete + @action + processOutpainting = async () => { + const field = Cast(this.dataDoc[this.fieldKey], ImageField); + if (!field) return; + + const origWidth = NumCast(this.Document._outpaintingOriginalWidth); + const origHeight = NumCast(this.Document._outpaintingOriginalHeight); + + if (!origWidth || !origHeight) { + console.error("Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first."); + return; + } + + //alert(`Original dimensions: ${origWidth} x ${origHeight}`); + + + // Set flag that outpainting is in progress + this._outpaintingInProgress = true; + + try { + // Get the current path to the image + const currentPath = this.choosePath(field.url); + + // Get original and new dimensions for calculating mask + const newWidth = NumCast(this.Document._width); + const newHeight = NumCast(this.Document._height); + + // Optional: Ask user for a prompt to guide the outpainting + let prompt = "Extend this image naturally with matching content"; + const customPrompt = await new Promise((resolve) => { + const dialog = document.createElement('div'); + Object.assign(dialog.style, { + position: 'fixed', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + background: 'white', + padding: '20px', + borderRadius: '8px', + boxShadow: '0 4px 12px rgba(0,0,0,0.2)', + zIndex: '10000' + }); + + const title = document.createElement('h3'); + title.style.marginTop = '0'; + title.textContent = 'Outpaint Image'; + + const description = document.createElement('p'); + description.textContent = 'Enter a prompt for extending the image:'; + + const input = document.createElement('input'); + input.id = 'outpaint-prompt'; + input.type = 'text'; + input.value = 'Extend this image naturally with matching content'; + Object.assign(input.style, { + width: '300px', + padding: '8px', + marginBottom: '10px' + }); + + const buttonContainer = document.createElement('div'); + Object.assign(buttonContainer.style, { + display: 'flex', + justifyContent: 'flex-end', + gap: '10px' + }); + + const cancelButton = document.createElement('button'); + cancelButton.textContent = 'Cancel'; + + const confirmButton = document.createElement('button'); + confirmButton.textContent = 'Generate'; + Object.assign(confirmButton.style, { + background: '#0078d4', + color: 'white', + border: 'none', + padding: '8px 16px' + }); + + buttonContainer.appendChild(cancelButton); + buttonContainer.appendChild(confirmButton); + + dialog.appendChild(title); + dialog.appendChild(description); + dialog.appendChild(input); + dialog.appendChild(buttonContainer); + + document.body.appendChild(dialog); + + cancelButton.onclick = () => { + document.body.removeChild(dialog); + resolve(""); + }; + + confirmButton.onclick = () => { + const promptValue = input.value; + document.body.removeChild(dialog); + resolve(promptValue); + }; + }); + + // If user cancelled, reset dimensions to original + if (!customPrompt) { + this.Document._width = origWidth; + this.Document._height = origHeight; + this._outpaintingInProgress = false; + return; + } + + // Show loading indicator + const loadingOverlay = document.createElement('div'); + loadingOverlay.style.position = 'absolute'; + loadingOverlay.style.top = '0'; + loadingOverlay.style.left = '0'; + loadingOverlay.style.width = '100%'; + loadingOverlay.style.height = '100%'; + loadingOverlay.style.background = 'rgba(0,0,0,0.5)'; + loadingOverlay.style.display = 'flex'; + loadingOverlay.style.justifyContent = 'center'; + loadingOverlay.style.alignItems = 'center'; + loadingOverlay.innerHTML = '
Generating outpainted image...
'; + this._mainCont?.appendChild(loadingOverlay); + + // Call the outpaint API + const response = await Networking.PostToServer('/outpaintImageFour', { + imageUrl: currentPath, + prompt: customPrompt, + originalDimensions: { + width: origWidth, + height: origHeight + }, + newDimensions: { + width: newWidth, + height: newHeight + } + }); + if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') { + console.log("Response is valid and contains URL:", response.url); + + } else { + console.error("Unexpected API response:", response); + alert("Failed to receive a valid image URL from server."); + } + + + if (response && 'url' in response && typeof response.url === 'string') { + // Save the original image as an alternate + if (!this.dataDoc[this.fieldKey + '_alternates']) { + this.dataDoc[this.fieldKey + '_alternates'] = new List(); + } + + // Create a copy of the current image as an alternate + const originalDoc = Docs.Create.ImageDocument(field.url.href, { + title: `Original: ${this.Document.title}`, + _nativeWidth: Doc.NativeWidth(this.dataDoc), + _nativeHeight: Doc.NativeHeight(this.dataDoc) + }); + + // Add to alternates + Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc); + + // Update the image with the outpainted version + this.dataDoc[this.fieldKey] = new ImageField(response.url); + + // Update native dimensions + Doc.SetNativeWidth(this.dataDoc, newWidth); + Doc.SetNativeHeight(this.dataDoc, newHeight); + + // Add AI metadata + this.Document.ai = true; + this.Document.ai_outpainted = true; + this.Document.ai_outpaint_prompt = customPrompt; + } else { + // If failed, revert to original dimensions + this.Document._width = origWidth; + this.Document._height = origHeight; + alert("Failed to outpaint image. Please try again."); + } + + // Remove loading overlay + this._mainCont?.removeChild(loadingOverlay); + + } catch (error) { + console.error("Error during outpainting:", error); + // Revert to original dimensions on error + this.Document._width = origWidth + this.Document._height = origHeight + alert("An error occurred while outpainting. Please try again."); + } finally { + // Clear the outpainting flags + this._outpaintingInProgress = false; + delete this.Document._originalDims; + } + }; + specificContextMenu = (): void => { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (field) { @@ -396,6 +607,27 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')), icon: this.Document.savedAsSticker ? 'clipboard-check' : 'file-arrow-down', }); + // Add new outpainting option + funcs.push({ + description: 'Outpaint Image', + event: () => { + this.processOutpainting(); + }, + icon: 'brush' + }); + // Add outpainting history option if the image was outpainted + this.Document.ai_outpainted && + funcs.push({ + description: 'View Original Image', + event: action(() => { + const alternates = DocListCast(this.dataDoc[this.fieldKey + '_alternates']); + if (alternates && alternates.length) { + // Toggle to show the original image + this.layoutDoc[this.fieldKey + '_usePath'] = 'alternate'; + } + }), + icon: 'image', + }); ContextMenu.Instance?.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' }); } }; @@ -434,7 +666,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { const ext = extname(url.href); return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext); }; - getScrollHeight = () => (this._props.fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc._freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined); + getScrollHeight = () => (this._props.fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc.freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined); @computed get usingAlternate() { const usePath = StrCast(this.Document[this.fieldKey + '_usePath']); @@ -587,7 +819,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { /> {fadepath === srcpath ? null : (
- +
)} @@ -746,82 +978,89 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']); const doc = this.usingAlternate ? (alts.lastElement() ?? this.Document) : this.Document; return ( -
{ - if (!this._forcedScroll) { - if (this.layoutDoc._layout_scrollTop || this._mainCont?.scrollTop) { - this._ignoreScroll = true; - this.layoutDoc._layout_scrollTop = this._mainCont?.scrollTop; - this._ignoreScroll = false; + <> +
{ + if (!this._forcedScroll) { + if (this.layoutDoc._layout_scrollTop || this._mainCont?.scrollTop) { + this._ignoreScroll = true; + this.layoutDoc._layout_scrollTop = this._mainCont?.scrollTop; + this._ignoreScroll = false; + } } - } - })} - style={{ - width: this._props.PanelWidth() ? undefined : `100%`, - height: this._props.PanelHeight() ? undefined : `100%`, - pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined, - borderRadius, - overflow: this.layoutDoc.layout_fitWidth || this._props.fitWidth?.(this.Document) ? 'auto' : 'hidden', - }}> - - {this.content} - - {this.Loading ? ( -
- + })} + style={{ + width: this._props.PanelWidth() ? undefined : `100%`, + height: this._props.PanelHeight() ? undefined : `100%`, + pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined, + borderRadius, + overflow: this.layoutDoc.layout_fitWidth || this._props.fitWidth?.(this.Document) ? 'auto' : 'hidden', + }}> + + {this.content} + + {this.Loading ? ( +
+ +
+ ) : null} + {this.regenerateImageIcon} + {this.overlayImageIcon} + {this.annotationLayer} + {!this._mainCont || !this.DocumentView || !this._annotationLayer.current ? null : ( + this.getImageDesc()} + /> + )} +
+ {this._outpaintingInProgress && ( +
+
- ) : null} - {this.regenerateImageIcon} - {this.overlayImageIcon} - {this.annotationLayer} - {!this._mainCont || !this.DocumentView || !this._annotationLayer.current ? null : ( - this.getImageDesc()} - /> )} -
+ ); } @@ -834,10 +1073,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { const file = input.files?.[0]; if (file) { const disposer = OverlayView.ShowSpinner(); - DocUtils.uploadFileToDoc(file, {}, this.Document).then(doc => { - disposer(); - doc && (doc.height = undefined); - }); + const [{ result }] = await Networking.UploadFilesToServer({ file }); + if (result instanceof Error) { + alert('Error uploading files - possibly due to unsupported file types'); + } else { + this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client); + !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc); + } + disposer(); } else { console.log('No file selected'); } -- cgit v1.2.3-70-g09d2 From 79f44d95eb789d7b3cca33eedc38072f2bb43d2a Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 11 Mar 2025 18:15:34 -0400 Subject: fixed calling firefly api for expand image --- src/client/views/nodes/ImageBox.tsx | 111 ++++--- src/client/views/smartdraw/DrawingFillHandler.tsx | 1 + src/server/ApiManagers/FireflyManager.ts | 363 ++++------------------ 3 files changed, 118 insertions(+), 357 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 114d5226b..a7dd5de15 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,7 +1,6 @@ import { Button, Colors, Size, Type } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Slider, Tooltip } from '@mui/material'; -import { DimensionField } from '../../../fields/DimensionField'; import axios from 'axios'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx'; import { observer } from 'mobx-react'; @@ -359,32 +358,31 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { processOutpainting = async () => { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (!field) return; - + const origWidth = NumCast(this.Document._outpaintingOriginalWidth); const origHeight = NumCast(this.Document._outpaintingOriginalHeight); - + if (!origWidth || !origHeight) { - console.error("Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first."); + console.error('Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first.'); return; } - + //alert(`Original dimensions: ${origWidth} x ${origHeight}`); - - + // Set flag that outpainting is in progress this._outpaintingInProgress = true; - + try { // Get the current path to the image const currentPath = this.choosePath(field.url); - + // Get original and new dimensions for calculating mask const newWidth = NumCast(this.Document._width); const newHeight = NumCast(this.Document._height); - + // Optional: Ask user for a prompt to guide the outpainting - let prompt = "Extend this image naturally with matching content"; - const customPrompt = await new Promise((resolve) => { + let prompt = 'Extend this image naturally with matching content'; + const customPrompt = await new Promise(resolve => { const dialog = document.createElement('div'); Object.assign(dialog.style, { position: 'fixed', @@ -395,16 +393,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { padding: '20px', borderRadius: '8px', boxShadow: '0 4px 12px rgba(0,0,0,0.2)', - zIndex: '10000' + zIndex: '10000', }); - + const title = document.createElement('h3'); title.style.marginTop = '0'; title.textContent = 'Outpaint Image'; - + const description = document.createElement('p'); description.textContent = 'Enter a prompt for extending the image:'; - + const input = document.createElement('input'); input.id = 'outpaint-prompt'; input.type = 'text'; @@ -412,50 +410,50 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { Object.assign(input.style, { width: '300px', padding: '8px', - marginBottom: '10px' + marginBottom: '10px', }); - + const buttonContainer = document.createElement('div'); Object.assign(buttonContainer.style, { display: 'flex', justifyContent: 'flex-end', - gap: '10px' + gap: '10px', }); - + const cancelButton = document.createElement('button'); cancelButton.textContent = 'Cancel'; - + const confirmButton = document.createElement('button'); confirmButton.textContent = 'Generate'; Object.assign(confirmButton.style, { background: '#0078d4', color: 'white', border: 'none', - padding: '8px 16px' + padding: '8px 16px', }); - + buttonContainer.appendChild(cancelButton); buttonContainer.appendChild(confirmButton); - + dialog.appendChild(title); dialog.appendChild(description); dialog.appendChild(input); dialog.appendChild(buttonContainer); - + document.body.appendChild(dialog); - + cancelButton.onclick = () => { document.body.removeChild(dialog); - resolve(""); + resolve(''); }; - + confirmButton.onclick = () => { const promptValue = input.value; document.body.removeChild(dialog); resolve(promptValue); }; }); - + // If user cancelled, reset dimensions to original if (!customPrompt) { this.Document._width = origWidth; @@ -463,7 +461,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this._outpaintingInProgress = false; return; } - + // Show loading indicator const loadingOverlay = document.createElement('div'); loadingOverlay.style.position = 'absolute'; @@ -477,52 +475,50 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { loadingOverlay.style.alignItems = 'center'; loadingOverlay.innerHTML = '
Generating outpainted image...
'; this._mainCont?.appendChild(loadingOverlay); - + // Call the outpaint API - const response = await Networking.PostToServer('/outpaintImageFour', { + const response = await Networking.PostToServer('/outpaintImage', { imageUrl: currentPath, prompt: customPrompt, originalDimensions: { width: origWidth, - height: origHeight + height: origHeight, }, newDimensions: { width: newWidth, - height: newHeight - } + height: newHeight, + }, }); if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') { - console.log("Response is valid and contains URL:", response.url); - + console.log('Response is valid and contains URL:', response.url); } else { - console.error("Unexpected API response:", response); - alert("Failed to receive a valid image URL from server."); + console.error('Unexpected API response:', response); + alert('Failed to receive a valid image URL from server.'); } - - + if (response && 'url' in response && typeof response.url === 'string') { // Save the original image as an alternate if (!this.dataDoc[this.fieldKey + '_alternates']) { this.dataDoc[this.fieldKey + '_alternates'] = new List(); } - + // Create a copy of the current image as an alternate const originalDoc = Docs.Create.ImageDocument(field.url.href, { title: `Original: ${this.Document.title}`, _nativeWidth: Doc.NativeWidth(this.dataDoc), - _nativeHeight: Doc.NativeHeight(this.dataDoc) + _nativeHeight: Doc.NativeHeight(this.dataDoc), }); - + // Add to alternates Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc); - + // Update the image with the outpainted version this.dataDoc[this.fieldKey] = new ImageField(response.url); - + // Update native dimensions Doc.SetNativeWidth(this.dataDoc, newWidth); Doc.SetNativeHeight(this.dataDoc, newHeight); - + // Add AI metadata this.Document.ai = true; this.Document.ai_outpainted = true; @@ -531,18 +527,17 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { // If failed, revert to original dimensions this.Document._width = origWidth; this.Document._height = origHeight; - alert("Failed to outpaint image. Please try again."); + alert('Failed to outpaint image. Please try again.'); } - + // Remove loading overlay this._mainCont?.removeChild(loadingOverlay); - } catch (error) { - console.error("Error during outpainting:", error); + console.error('Error during outpainting:', error); // Revert to original dimensions on error - this.Document._width = origWidth - this.Document._height = origHeight - alert("An error occurred while outpainting. Please try again."); + this.Document._width = origWidth; + this.Document._height = origHeight; + alert('An error occurred while outpainting. Please try again.'); } finally { // Clear the outpainting flags this._outpaintingInProgress = false; @@ -608,12 +603,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { icon: this.Document.savedAsSticker ? 'clipboard-check' : 'file-arrow-down', }); // Add new outpainting option - funcs.push({ - description: 'Outpaint Image', + funcs.push({ + description: 'Outpaint Image', event: () => { this.processOutpainting(); - }, - icon: 'brush' + }, + icon: 'brush', }); // Add outpainting history option if the image was outpainted this.Document.ai_outpainted && diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index c672bc718..37799a98d 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -48,6 +48,7 @@ export class DrawingFillHandler { .then(res => { const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { _width: 400, _height: 400 }); drawing[DocData].ai_firefly_generatedDocs = genratedDocs; + if ('error' in res) throw new Error(res.error as string); (res as Upload.ImageInformation[]).map(info => Doc.AddDocToList( genratedDocs, diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index 1e0fdb595..8b0fa6ec7 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -8,7 +8,7 @@ import { DashUploadUtils } from '../DashUploadUtils'; import { _error, _invalid, _success, Method } from '../RouteManager'; import { Directory, filesDirectory } from '../SocketData'; import ApiManager, { Registration } from './ApiManager'; -import { Upload } from '../SharedMediaTypes'; +import { Upload } from '../SharedMediaTypes'; export default class FireflyManager extends ApiManager { getBearerToken = () => @@ -332,315 +332,80 @@ export default class FireflyManager extends ApiManager { register({ method: Method.POST, - subscription: '/outpaintImageFour', + subscription: '/outpaintImage', secureHandler: ({ req, res }) => - - this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel) - .then(uploadUrl => { - if (uploadUrl instanceof Error) { - _invalid(res, uploadUrl.message); - return; - } - return this.getBearerToken() - .then(tokenResponse => tokenResponse?.json()) - .then((tokenData: { access_token: string }) => - fetch('https://firefly-api.adobe.io/v3/images/expand', { - method: 'POST', - headers: { - //'Content-Type': 'application/json', - 'Accept': 'application/json', - 'x-api-key': process.env._CLIENT_FIREFLY_CLIENT_ID ?? '', - 'Authorization': `Bearer ${tokenData.access_token}`, + this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel).then(uploadUrl => { + if (uploadUrl instanceof Error) { + _invalid(res, uploadUrl.message); + return; + } + return this.getBearerToken() + .then(tokenResponse => tokenResponse?.json()) + .then((tokenData: { access_token: string }) => + fetch('https://firefly-api.adobe.io/v3/images/expand', { + method: 'POST', + headers: [ + ['Content-Type', 'application/json'], + ['Accept', 'application/json'], + ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''], + ['Authorization', `Bearer ${tokenData.access_token}`], + ], + body: JSON.stringify({ + image: { + source: { url: uploadUrl }, }, - body: JSON.stringify({ - image: { - source: { url: uploadUrl }, - }, - size: { - width: req.body.newDimensions.width, - height: req.body.newDimensions.height, + // mask: { + // source: { url: uploadUrl }, + // }, + size: { + width: Math.round(req.body.newDimensions.width), + height: Math.round(req.body.newDimensions.height), + }, + prompt: req.body.prompt ?? '', + numVariations: 1, + placement: { + inset: { + left: 0, + top: 0, + right: Math.round(req.body.newDimensions.width - req.body.originalDimensions.width), + bottom: Math.round(req.body.newDimensions.height - req.body.originalDimensions.height), }, - prompt: req.body.prompt ?? '', - numVariations: 1, - placement: { - inset: { - left: 0, - top: 0, - right: req.body.newDimensions.width - req.body.originalDimensions.width, - bottom: req.body.newDimensions.height - req.body.originalDimensions.height, - }, - alignment: { - horizontal: 'center', - vertical: 'center', - }, + alignment: { + horizontal: 'center', + vertical: 'center', }, - }), - }) - ) - .then(expandResp => expandResp?.json()) - .then(expandData => { - if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { - console.error('Firefly validation error:', expandData); - _error(res, expandData.message ?? 'Failed to generate image'); - } else { - DashUploadUtils.UploadImage(expandData.outputs[0].image.url) - .then((info: Upload.ImageInformation | Error) => { - if (info instanceof Error) { - _invalid(res, info.message); - } else { - _success(res, { url: info.accessPaths.agnostic.client }); - } - }) - .catch(uploadErr => { - console.error('DashUpload Error:', uploadErr); - _error(res, 'Failed to upload generated image.'); - }); - } + }, + }), }) - .catch(err => { - console.error('Firefly request error:', err); - _error(res, 'Failed to expand image'); - }); - }), - }); - register({ - method: Method.POST, - subscription: '/outpaintImageThree', - secureHandler: ({ req, res }) => - new Promise(resolver => { - this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel) - .then(dropboxImgUrl => { - if (dropboxImgUrl instanceof Error) { - _invalid(res, dropboxImgUrl.message); - throw new Error('Error uploading image to dropbox'); + ) + .then(expandResp => expandResp?.json()) + .then(expandData => { + if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { + console.error('Firefly validation error:', expandData); + _error(res, expandData.message ?? 'Failed to generate image'); + } else { + return DashUploadUtils.UploadImage(expandData.outputs[0].image.url) + .then((info: Upload.ImageInformation | Error) => { + if (info instanceof Error) { + _invalid(res, info.message); + } else { + _success(res, { url: info.accessPaths.agnostic.client }); + } + }) + .catch(uploadErr => { + console.error('DashUpload Error:', uploadErr); + _error(res, 'Failed to upload generated image.'); + }); } - return dropboxImgUrl; }) - .then(dropboxImgUrl => - this.getBearerToken().then(tokenResponse => - tokenResponse?.json().then((tokenData: { access_token: string }) => - fetch('https://firefly-api.adobe.io/v3/images/expand', { - method: 'POST', - headers: [ - ['Content-Type', 'application/json'], - ['Accept', 'application/json'], - ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''], - ['Authorization', `Bearer ${tokenData.access_token}`], - ], - body: JSON.stringify({ - image: { - source: { - url: dropboxImgUrl, - }, - }, - numVariations: 1, - prompt: req.body.prompt, - size: { - width: req.body.newDimensions.width, - height: req.body.newDimensions.height, - }, - placement: { - inset: { - left: 0, - top: 0, - right: req.body.newDimensions.width - req.body.originalDimensions.width, - bottom: req.body.newDimensions.height - req.body.originalDimensions.height, - }, - alignment: { - horizontal: 'center', - vertical: 'center', - }, - }, - }), - }) - .then(resp => resp.json()) - .then(expandData => { - if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { - _invalid(res, expandData.message ?? 'Failed to expand image'); - resolver(); - } else { - DashUploadUtils.UploadImage(expandData.outputs[0].image.url) - .then(info => { - if (info instanceof Error) { - _invalid(res, info.message); - } else { - _success(res, { url: info.accessPaths.agnostic.client }); - } - }) - .then(resolver) - .catch(uploadErr => { - console.error('DashUpload Error:', uploadErr); - _invalid(res, 'Failed to upload generated image.'); - resolver(); - }); - } - }) - .catch(err => { - console.error('Firefly API Error:', err); - _error(res, 'Failed to expand image'); - resolver(); - }) - ) - ) - ) .catch(err => { - console.error('Dropbox Error:', err); - _error(res, 'Failed to upload image to Dropbox'); - resolver(); + console.error('Firefly request error:', err); + _error(res, 'Failed to expand image'); }); }), }); - - - register({ - method: Method.POST, - subscription: '/outpaintImageTwo', - secureHandler: async ({ req, res }) => { - try { - const uploadUrl = await this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel); - - if (uploadUrl instanceof Error) { - _invalid(res, uploadUrl.message); - return; - } - - const tokenResponse = await this.getBearerToken(); - const tokenData = await tokenResponse?.json(); - - const body = JSON.stringify({ - image: { source: { url: uploadUrl } }, - prompt: req.body.prompt, - numVariations: 1, - size: { - width: req.body.newDimensions.width, - height: req.body.newDimensions.height, - }, - placement: { - inset: { - left: 0, - top: 0, - right: req.body.newDimensions.width - req.body.originalDimensions.width, - bottom: req.body.newDimensions.height - req.body.originalDimensions.height, - }, - alignment: { - horizontal: 'center', - vertical: 'center', - }, - }, - }); - - console.log('Sending outpainting request:', body); - - const expandResp = await fetch('https://firefly-api.adobe.io/v3/images/expand', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - 'x-api-key': process.env._CLIENT_FIREFLY_CLIENT_ID ?? '', - Authorization: `Bearer ${tokenData.access_token}`, - }, - body, - }); - - const expandData = await expandResp.json(); - console.log('Received expandData:', expandData); - - if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { - console.error('Expand API Error:', expandData); - _error(res, expandData.message ?? 'Failed to generate image'); - return; - } - - const uploadedInfo = await DashUploadUtils.UploadImage(expandData.outputs[0].image.url); - - if (uploadedInfo instanceof Error) { - console.error('Upload Error:', uploadedInfo.message); - _invalid(res, uploadedInfo.message); - return; - } - - console.log('Successfully uploaded image URL:', uploadedInfo.accessPaths.agnostic.client); - _success(res, { url: uploadedInfo.accessPaths.agnostic.client }); - } catch (err) { - console.error('Unexpected error during outpainting:', err); - _error(res, 'Unexpected error during outpainting'); - } - }, - }); - - register({ - - - method: Method.POST, - subscription: '/outpaintImage', - secureHandler: ({ req, res }) => - this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel).then(uploadUrl => - uploadUrl instanceof Error - ? _invalid(res, uploadUrl.message) - : this.getBearerToken() - .then(tokenResponse => tokenResponse?.json()) - .then((tokenData: { access_token: string }) => - fetch('https://firefly-api.adobe.io/v3/images/expand', { - method: 'POST', - headers: [ - ['Content-Type', 'application/json'], - ['Accept', 'application/json'], - ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''], - ['Authorization', `Bearer ${tokenData.access_token}`], - ], - body: JSON.stringify({ - image: { source: { url: uploadUrl } }, - numVariations: 1, - size: { - width: req.body.newDimensions.width, - height: req.body.newDimensions.height, - }, - prompt: req.body.prompt, - placement: { - inset: { - left: 0, - top: 0, - right: 0, //req.body.newDimensions.width - req.body.originalDimensions.width, - bottom: 0 //req.body.newDimensions.height - req.body.originalDimensions.height, - }, - alignment: { - horizontal: 'center', - vertical: 'center', - }, - }, - }), - }) - ) - .then(expandResp => expandResp.json()) - .then(expandData => { - if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { - _error(res, expandData.message ?? "Failed to generate image"); - } else { - DashUploadUtils.UploadImage(expandData.outputs[0].image.url) - .then((info: Upload.ImageInformation | Error) => { - if (info instanceof Error) { - _invalid(res, info.message); - } else { - // THIS IS CRUCIAL: Respond exactly how your front end expects - console.log("Successfully uploaded image. URL:", info.accessPaths.agnostic.client); - _success(res, { url: info.accessPaths.agnostic.client }); - } - }) - .catch(uploadErr => { - - console.error('DashUpload Error:', uploadErr); - _error(res, 'Failed to upload generated image.'); - }); - } - }) - .catch(err => { - console.error(err); - _error(res, 'Failed to expand image'); - }) - ), - }); - /* register({ + /* register({ method: Method.POST subscription: '/queryFireflyOutpaint', secureHandler: ({req, res}) => @@ -683,7 +448,7 @@ export default class FireflyManager extends ApiManager { : this.expandImage(uploadUrl, req.body.prompt).then(text => { if (text.error_code) _error(res, text.message); else - DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => { + return DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => { if (info instanceof Error) _invalid(res, info.message); else _success(res, info); }); -- cgit v1.2.3-70-g09d2 From d0b117d1f31f911d46dffe69e2f5c40b2bc5de3e Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 11 Mar 2025 23:08:16 -0400 Subject: getting rid of old expand image code --- src/client/views/nodes/ImageBox.tsx | 24 ++---------------------- src/server/ApiManagers/FireflyManager.ts | 17 ----------------- 2 files changed, 2 insertions(+), 39 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index a7dd5de15..14adfaf1e 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -552,9 +552,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' }); funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' }); funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' }); - funcs.push({ - description: 'GetImageText', - event: () => { + funcs.push({ description: 'GetImageText', event: () => { Networking.PostToServer('/queryFireflyImageText', { file: (file => { const ext = extname(file); @@ -563,25 +561,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { }).then(text => alert(text)); }, icon: 'expand-arrows-alt', - }); - funcs.push({ - description: 'Expand Image', - event: () => { - Networking.PostToServer('/expandImage', { - prompt: 'sunny skies', - file: (file => { - const ext = extname(file); - return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext); - })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href), - }).then(res => { - const info = res as Upload.ImageInformation; - const img = Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { title: 'expand:' + this.Document.title }); - DocUtils.assignImageInfo(info, img); - this._props.addDocTab(img, OpenWhere.addRight); - }); - }, - icon: 'expand-arrows-alt', - }); + }); // prettier-ignore funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' }); funcs.push({ description: 'Open Image Editor', event: this.docEditorView, icon: 'pencil-alt' }); this.layoutDoc.ai && diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index 77655bb23..887c4aa72 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -429,23 +429,6 @@ export default class FireflyManager extends ApiManager { ) ), }); - register({ - method: Method.POST, - subscription: '/expandImage', - secureHandler: ({ req, res }) => - this.uploadImageToDropbox(req.body.file, req.user as DashUserModel).then(uploadUrl => - uploadUrl instanceof Error - ? _invalid(res, uploadUrl.message) - : this.expandImage(uploadUrl, req.body.prompt).then(text => { - if (text.error_code) _error(res, text.message); - else - return DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => { - if (info instanceof Error) _invalid(res, info.message); - else _success(res, info); - }); - }) - ), - }); // construct this url and send user to it. It will allow them to authorize their dropbox account and will send the resulting token to our endpoint /refreshDropbox // https://www.dropbox.com/oauth2/authorize?client_id=DROPBOX_CLIENT_ID&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox -- cgit v1.2.3-70-g09d2 From 22a40443193320487c27ce02bd3f134d13cb7d65 Mon Sep 17 00:00:00 2001 From: sharkiecodes Date: Wed, 9 Apr 2025 23:58:46 -0400 Subject: cleaned up code --- src/client/views/nodes/ImageBox.scss | 32 +++ src/client/views/nodes/ImageBox.tsx | 494 ++++++++++++++++++++++------------- 2 files changed, 351 insertions(+), 175 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 3d6942e6f..d7a14a9df 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -245,3 +245,35 @@ color: black; } } +.imageBox-regenerate-dialog { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0,0,0,0.2); + z-index: 10000; + + h3 { margin-top: 0; } + + input { + width: 300px; + padding: 8px; + margin-bottom: 10px; + } + + .buttons { + display: flex; + justify-content: flex-end; + gap: 10px; + + .generate-btn { + background: #0078d4; + color: white; + border: none; + padding: 8px 16px; + } + } + } \ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 14adfaf1e..033b0b5a2 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -353,197 +353,340 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { } }); - // Add this method to process outpainting when resize is complete - @action - processOutpainting = async () => { - const field = Cast(this.dataDoc[this.fieldKey], ImageField); - if (!field) return; + @observable _showOutpaintPrompt: boolean = false; +@observable _outpaintPromptInput: string = 'Extend this image naturally with matching content'; + +@action +openOutpaintPrompt = () => { + this._showOutpaintPrompt = true; +}; + +@action +closeOutpaintPrompt = () => { + this._showOutpaintPrompt = false; +}; + +@action +handlePromptChange = (e: React.ChangeEvent) => { + this._outpaintPromptInput = e.target.value; +}; + +@action +submitOutpaintPrompt = () => { + this.closeOutpaintPrompt(); + this.processOutpaintingWithPrompt(this._outpaintPromptInput); +}; + +@action +processOutpaintingWithPrompt = async (customPrompt: string) => { + const field = Cast(this.dataDoc[this.fieldKey], ImageField); + if (!field) return; + + const origWidth = NumCast(this.Document._outpaintingOriginalWidth); + const origHeight = NumCast(this.Document._outpaintingOriginalHeight); + + if (!origWidth || !origHeight) { + console.error('Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first.'); + return; + } - const origWidth = NumCast(this.Document._outpaintingOriginalWidth); - const origHeight = NumCast(this.Document._outpaintingOriginalHeight); + // Set flag that outpainting is in progress + this._outpaintingInProgress = true; - if (!origWidth || !origHeight) { - console.error('Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first.'); + try { + const currentPath = this.choosePath(field.url); + const newWidth = NumCast(this.Document._width); + const newHeight = NumCast(this.Document._height); + + // Revert dimensions if prompt is blank (acts like Cancel) + if (!customPrompt) { + this.Document._width = origWidth; + this.Document._height = origHeight; + this._outpaintingInProgress = false; return; } - //alert(`Original dimensions: ${origWidth} x ${origHeight}`); - - // Set flag that outpainting is in progress - this._outpaintingInProgress = true; - - try { - // Get the current path to the image - const currentPath = this.choosePath(field.url); - - // Get original and new dimensions for calculating mask - const newWidth = NumCast(this.Document._width); - const newHeight = NumCast(this.Document._height); - - // Optional: Ask user for a prompt to guide the outpainting - let prompt = 'Extend this image naturally with matching content'; - const customPrompt = await new Promise(resolve => { - const dialog = document.createElement('div'); - Object.assign(dialog.style, { - position: 'fixed', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - background: 'white', - padding: '20px', - borderRadius: '8px', - boxShadow: '0 4px 12px rgba(0,0,0,0.2)', - zIndex: '10000', - }); - - const title = document.createElement('h3'); - title.style.marginTop = '0'; - title.textContent = 'Outpaint Image'; - - const description = document.createElement('p'); - description.textContent = 'Enter a prompt for extending the image:'; - - const input = document.createElement('input'); - input.id = 'outpaint-prompt'; - input.type = 'text'; - input.value = 'Extend this image naturally with matching content'; - Object.assign(input.style, { - width: '300px', - padding: '8px', - marginBottom: '10px', - }); - - const buttonContainer = document.createElement('div'); - Object.assign(buttonContainer.style, { - display: 'flex', - justifyContent: 'flex-end', - gap: '10px', - }); - - const cancelButton = document.createElement('button'); - cancelButton.textContent = 'Cancel'; - - const confirmButton = document.createElement('button'); - confirmButton.textContent = 'Generate'; - Object.assign(confirmButton.style, { - background: '#0078d4', - color: 'white', - border: 'none', - padding: '8px 16px', - }); - - buttonContainer.appendChild(cancelButton); - buttonContainer.appendChild(confirmButton); - - dialog.appendChild(title); - dialog.appendChild(description); - dialog.appendChild(input); - dialog.appendChild(buttonContainer); - - document.body.appendChild(dialog); - - cancelButton.onclick = () => { - document.body.removeChild(dialog); - resolve(''); - }; + // Optional: add loading indicator + const loadingOverlay = document.createElement('div'); + loadingOverlay.style.position = 'absolute'; + loadingOverlay.style.top = '0'; + loadingOverlay.style.left = '0'; + loadingOverlay.style.width = '100%'; + loadingOverlay.style.height = '100%'; + loadingOverlay.style.background = 'rgba(0,0,0,0.5)'; + loadingOverlay.style.display = 'flex'; + loadingOverlay.style.justifyContent = 'center'; + loadingOverlay.style.alignItems = 'center'; + loadingOverlay.innerHTML = '
Generating outpainted image...
'; + this._mainCont?.appendChild(loadingOverlay); + + const response = await Networking.PostToServer('/outpaintImage', { + imageUrl: currentPath, + prompt: customPrompt, + originalDimensions: { width: origWidth, height: origHeight }, + newDimensions: { width: newWidth, height: newHeight }, + }); - confirmButton.onclick = () => { - const promptValue = input.value; - document.body.removeChild(dialog); - resolve(promptValue); - }; - }); + if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') { + console.log('Received outpainted image:', response.url); - // If user cancelled, reset dimensions to original - if (!customPrompt) { - this.Document._width = origWidth; - this.Document._height = origHeight; - this._outpaintingInProgress = false; - return; + if (!this.dataDoc[this.fieldKey + '_alternates']) { + this.dataDoc[this.fieldKey + '_alternates'] = new List(); } - // Show loading indicator - const loadingOverlay = document.createElement('div'); - loadingOverlay.style.position = 'absolute'; - loadingOverlay.style.top = '0'; - loadingOverlay.style.left = '0'; - loadingOverlay.style.width = '100%'; - loadingOverlay.style.height = '100%'; - loadingOverlay.style.background = 'rgba(0,0,0,0.5)'; - loadingOverlay.style.display = 'flex'; - loadingOverlay.style.justifyContent = 'center'; - loadingOverlay.style.alignItems = 'center'; - loadingOverlay.innerHTML = '
Generating outpainted image...
'; - this._mainCont?.appendChild(loadingOverlay); - - // Call the outpaint API - const response = await Networking.PostToServer('/outpaintImage', { - imageUrl: currentPath, - prompt: customPrompt, - originalDimensions: { - width: origWidth, - height: origHeight, - }, - newDimensions: { - width: newWidth, - height: newHeight, - }, + const originalDoc = Docs.Create.ImageDocument(field.url.href, { + title: `Original: ${this.Document.title}`, + _nativeWidth: Doc.NativeWidth(this.dataDoc), + _nativeHeight: Doc.NativeHeight(this.dataDoc), }); - if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') { - console.log('Response is valid and contains URL:', response.url); - } else { - console.error('Unexpected API response:', response); - alert('Failed to receive a valid image URL from server.'); - } - - if (response && 'url' in response && typeof response.url === 'string') { - // Save the original image as an alternate - if (!this.dataDoc[this.fieldKey + '_alternates']) { - this.dataDoc[this.fieldKey + '_alternates'] = new List(); - } - - // Create a copy of the current image as an alternate - const originalDoc = Docs.Create.ImageDocument(field.url.href, { - title: `Original: ${this.Document.title}`, - _nativeWidth: Doc.NativeWidth(this.dataDoc), - _nativeHeight: Doc.NativeHeight(this.dataDoc), - }); - - // Add to alternates - Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc); - // Update the image with the outpainted version - this.dataDoc[this.fieldKey] = new ImageField(response.url); + Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc); - // Update native dimensions - Doc.SetNativeWidth(this.dataDoc, newWidth); - Doc.SetNativeHeight(this.dataDoc, newHeight); + // Replace with new outpainted image + this.dataDoc[this.fieldKey] = new ImageField(response.url); - // Add AI metadata - this.Document.ai = true; - this.Document.ai_outpainted = true; - this.Document.ai_outpaint_prompt = customPrompt; - } else { - // If failed, revert to original dimensions - this.Document._width = origWidth; - this.Document._height = origHeight; - alert('Failed to outpaint image. Please try again.'); - } + Doc.SetNativeWidth(this.dataDoc, newWidth); + Doc.SetNativeHeight(this.dataDoc, newHeight); - // Remove loading overlay - this._mainCont?.removeChild(loadingOverlay); - } catch (error) { - console.error('Error during outpainting:', error); - // Revert to original dimensions on error + this.Document.ai = true; + this.Document.ai_outpainted = true; + this.Document.ai_outpaint_prompt = customPrompt; + } else { + console.error('Unexpected API response:', response); this.Document._width = origWidth; this.Document._height = origHeight; - alert('An error occurred while outpainting. Please try again.'); - } finally { - // Clear the outpainting flags - this._outpaintingInProgress = false; - delete this.Document._originalDims; + alert('Failed to receive a valid image URL from server.'); } - }; + + this._mainCont?.removeChild(loadingOverlay); + } catch (error) { + console.error('Error during outpainting:', error); + this.Document._width = origWidth; + this.Document._height = origHeight; + alert('An error occurred while outpainting. Please try again.'); + } finally { + this._outpaintingInProgress = false; + delete this.Document._originalDims; + } +}; + +processOutpainting = () => { + this.openOutpaintPrompt(); +}; + +componentUI = () => ( + <> + {this._showOutpaintPrompt && ( +
+

Outpaint Image

+

Enter a prompt for extending the image:

+ +
+ + +
+
+ )} + +); + + // // Add this method to process outpainting when resize is complete + // @action + // processOutpainting = async () => { + // const field = Cast(this.dataDoc[this.fieldKey], ImageField); + // if (!field) return; + + // const origWidth = NumCast(this.Document._outpaintingOriginalWidth); + // const origHeight = NumCast(this.Document._outpaintingOriginalHeight); + + // if (!origWidth || !origHeight) { + // console.error('Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first.'); + // return; + // } + + // //alert(`Original dimensions: ${origWidth} x ${origHeight}`); + + // // Set flag that outpainting is in progress + // this._outpaintingInProgress = true; + + // try { + // // Get the current path to the image + // const currentPath = this.choosePath(field.url); + + // // Get original and new dimensions for calculating mask + // const newWidth = NumCast(this.Document._width); + // const newHeight = NumCast(this.Document._height); + + // // Optional: Ask user for a prompt to guide the outpainting + // let prompt = 'Extend this image naturally with matching content'; + // const customPrompt = await new Promise(resolve => { + // const dialog = document.createElement('div'); + // Object.assign(dialog.style, { + // position: 'fixed', + // top: '50%', + // left: '50%', + // transform: 'translate(-50%, -50%)', + // background: 'white', + // padding: '20px', + // borderRadius: '8px', + // boxShadow: '0 4px 12px rgba(0,0,0,0.2)', + // zIndex: '10000', + // }); + + // const title = document.createElement('h3'); + // title.style.marginTop = '0'; + // title.textContent = 'Outpaint Image'; + + // const description = document.createElement('p'); + // description.textContent = 'Enter a prompt for extending the image:'; + + // const input = document.createElement('input'); + // input.id = 'outpaint-prompt'; + // input.type = 'text'; + // input.value = 'Extend this image naturally with matching content'; + // Object.assign(input.style, { + // width: '300px', + // padding: '8px', + // marginBottom: '10px', + // }); + + // const buttonContainer = document.createElement('div'); + // Object.assign(buttonContainer.style, { + // display: 'flex', + // justifyContent: 'flex-end', + // gap: '10px', + // }); + + // const cancelButton = document.createElement('button'); + // cancelButton.textContent = 'Cancel'; + + // const confirmButton = document.createElement('button'); + // confirmButton.textContent = 'Generate'; + // Object.assign(confirmButton.style, { + // background: '#0078d4', + // color: 'white', + // border: 'none', + // padding: '8px 16px', + // }); + + // buttonContainer.appendChild(cancelButton); + // buttonContainer.appendChild(confirmButton); + + // dialog.appendChild(title); + // dialog.appendChild(description); + // dialog.appendChild(input); + // dialog.appendChild(buttonContainer); + + // document.body.appendChild(dialog); + + // cancelButton.onclick = () => { + // document.body.removeChild(dialog); + // resolve(''); + // }; + + // confirmButton.onclick = () => { + // const promptValue = input.value; + // document.body.removeChild(dialog); + // resolve(promptValue); + // }; + // }); + + // // If user cancelled, reset dimensions to original + // if (!customPrompt) { + // this.Document._width = origWidth; + // this.Document._height = origHeight; + // this._outpaintingInProgress = false; + // return; + // } + + // // Show loading indicator + // const loadingOverlay = document.createElement('div'); + // loadingOverlay.style.position = 'absolute'; + // loadingOverlay.style.top = '0'; + // loadingOverlay.style.left = '0'; + // loadingOverlay.style.width = '100%'; + // loadingOverlay.style.height = '100%'; + // loadingOverlay.style.background = 'rgba(0,0,0,0.5)'; + // loadingOverlay.style.display = 'flex'; + // loadingOverlay.style.justifyContent = 'center'; + // loadingOverlay.style.alignItems = 'center'; + // loadingOverlay.innerHTML = '
Generating outpainted image...
'; + // this._mainCont?.appendChild(loadingOverlay); + + // // Call the outpaint API + // const response = await Networking.PostToServer('/outpaintImage', { + // imageUrl: currentPath, + // prompt: customPrompt, + // originalDimensions: { + // width: origWidth, + // height: origHeight, + // }, + // newDimensions: { + // width: newWidth, + // height: newHeight, + // }, + // }); + // if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') { + // console.log('Response is valid and contains URL:', response.url); + // } else { + // console.error('Unexpected API response:', response); + // alert('Failed to receive a valid image URL from server.'); + // } + + // if (response && 'url' in response && typeof response.url === 'string') { + // // Save the original image as an alternate + // if (!this.dataDoc[this.fieldKey + '_alternates']) { + // this.dataDoc[this.fieldKey + '_alternates'] = new List(); + // } + + // // Create a copy of the current image as an alternate + // const originalDoc = Docs.Create.ImageDocument(field.url.href, { + // title: `Original: ${this.Document.title}`, + // _nativeWidth: Doc.NativeWidth(this.dataDoc), + // _nativeHeight: Doc.NativeHeight(this.dataDoc), + // }); + + // // Add to alternates + // Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc); + + // // Update the image with the outpainted version + // this.dataDoc[this.fieldKey] = new ImageField(response.url); + + // // Update native dimensions + // Doc.SetNativeWidth(this.dataDoc, newWidth); + // Doc.SetNativeHeight(this.dataDoc, newHeight); + + // // Add AI metadata + // this.Document.ai = true; + // this.Document.ai_outpainted = true; + // this.Document.ai_outpaint_prompt = customPrompt; + // } else { + // // If failed, revert to original dimensions + // this.Document._width = origWidth; + // this.Document._height = origHeight; + // alert('Failed to outpaint image. Please try again.'); + // } + + // // Remove loading overlay + // this._mainCont?.removeChild(loadingOverlay); + // } catch (error) { + // console.error('Error during outpainting:', error); + // // Revert to original dimensions on error + // this.Document._width = origWidth; + // this.Document._height = origHeight; + // alert('An error occurred while outpainting. Please try again.'); + // } finally { + // // Clear the outpainting flags + // this._outpaintingInProgress = false; + // delete this.Document._originalDims; + // } + // }; specificContextMenu = (): void => { const field = Cast(this.dataDoc[this.fieldKey], ImageField); @@ -586,10 +729,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { funcs.push({ description: 'Outpaint Image', event: () => { - this.processOutpainting(); + this.openOutpaintPrompt(); }, icon: 'brush', }); + // Add outpainting history option if the image was outpainted this.Document.ai_outpainted && funcs.push({ -- cgit v1.2.3-70-g09d2 From 06bc78476ef050b8ce12a015a7d5492e01aa8cb8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 21 Apr 2025 14:08:53 -0400 Subject: added undo for outpaint --- src/client/views/nodes/ImageBox.tsx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index c3df82611..7ad3ee2b6 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -27,7 +27,7 @@ import { Networking } from '../../Network'; import { DragManager } from '../../util/DragManager'; import { SettingsManager } from '../../util/SettingsManager'; import { SnappingManager } from '../../util/SnappingManager'; -import { undoable, undoBatch } from '../../util/UndoManager'; +import { undoable, undoBatch, UndoManager } from '../../util/UndoManager'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; @@ -367,7 +367,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { @action handlePromptChange = (val: string | number) => { - this._outpaintPromptInput = val; + this._outpaintPromptInput = '' + val; }; @action @@ -392,19 +392,19 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { // Set flag that outpainting is in progress this._outpaintingInProgress = true; + // Revert dimensions if prompt is blank (acts like Cancel) + if (!customPrompt) { + this.Document._width = origWidth; + this.Document._height = origHeight; + this._outpaintingInProgress = false; + return; + } + try { const currentPath = this.choosePath(field.url); const newWidth = NumCast(this.Document._width); const newHeight = NumCast(this.Document._height); - // Revert dimensions if prompt is blank (acts like Cancel) - if (!customPrompt) { - this.Document._width = origWidth; - this.Document._height = origHeight; - this._outpaintingInProgress = false; - return; - } - // Optional: add loading indicator const loadingOverlay = document.createElement('div'); loadingOverlay.style.position = 'absolute'; @@ -426,6 +426,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { newDimensions: { width: newWidth, height: newHeight }, }); + const batch = UndoManager.StartBatch('outpaint image'); if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') { console.log('Received outpainted image:', response.url); @@ -456,6 +457,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this.Document._height = origHeight; alert('Failed to receive a valid image URL from server.'); } + batch.end(); this._mainCont?.removeChild(loadingOverlay); } catch (error) { @@ -464,7 +466,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this.Document._height = origHeight; alert('An error occurred while outpainting. Please try again.'); } finally { - this._outpaintingInProgress = false; + runInAction(() => (this._outpaintingInProgress = false)); } }; -- cgit v1.2.3-70-g09d2 From e8f1d494d36a5e1f1ee33d59faa4be2559cd752e Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 22 Apr 2025 10:54:38 -0400 Subject: adding drop handling code for scrapbookBox --- src/client/documents/DocUtils.ts | 60 +++++++-------- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/InkingStroke.tsx | 2 +- src/client/views/Main.tsx | 4 +- .../views/collections/CollectionStackingView.tsx | 5 +- src/client/views/collections/CollectionSubView.tsx | 3 +- src/client/views/collections/CollectionView.tsx | 20 ++--- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 + .../collectionGrid/CollectionGridView.tsx | 1 + .../collectionLinear/CollectionLinearView.tsx | 1 + .../CollectionMulticolumnView.tsx | 1 + .../CollectionMultirowView.tsx | 1 + src/client/views/nodes/FieldView.tsx | 2 +- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 4 +- src/client/views/nodes/ImageBox.tsx | 5 +- src/client/views/nodes/scrapbook/ScrapbookBox.tsx | 87 ++++++++++++---------- 16 files changed, 104 insertions(+), 96 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index db0770b12..df14dce5a 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -308,13 +308,7 @@ export namespace DocUtils { */ export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise> { let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise) | undefined; - - if (type.indexOf('scrapbook') !== -1) { - ctor = Docs.Create.ScrapbookDocument; - if (!options._width) options._width = 400; - if (!options._title) options._title = 'New Scrapbook'; - } - + if (type.indexOf('image') !== -1) { ctor = Docs.Create.ImageDocument; if (!options._width) options._width = 300; @@ -718,33 +712,31 @@ export namespace DocUtils { nativeWidth: 40, nativeHeight: 40, }) - : (defaultTextTemplate?.type === DocumentType.JOURNAL ? Docs.Create.DailyJournalDocument:Docs.Create.TextDocument)( - '', - { - annotationOn, - backgroundColor, - x, - y, - title, - ...(defaultTextTemplate - ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance - : { - _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200, - _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35, - _layout_autoHeight: true, - backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor), - borderColor: Doc.UserDoc().borderColor as string, - borderWidth: Doc.UserDoc().borderWidth as number, - text_centered: BoolCast(Doc.UserDoc().textCentered), - text_fitBox: BoolCast(Doc.UserDoc().fitBox), - text_align: StrCast(Doc.UserDoc().textAlign), - text_fontColor: StrCast(Doc.UserDoc().fontColor), - text_fontFamily: StrCast(Doc.UserDoc().fontFamily), - text_fontWeight: StrCast(Doc.UserDoc().fontWeight), - text_fontStyle: StrCast(Doc.UserDoc().fontStyle), - text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration), - }), - }); + : (defaultTextTemplate?.type === DocumentType.JOURNAL ? Docs.Create.DailyJournalDocument : Docs.Create.TextDocument)('', { + annotationOn, + backgroundColor, + x, + y, + title, + ...(defaultTextTemplate + ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance + : { + _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200, + _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35, + _layout_autoHeight: true, + backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor), + borderColor: Doc.UserDoc().borderColor as string, + borderWidth: Doc.UserDoc().borderWidth as number, + text_centered: BoolCast(Doc.UserDoc().textCentered), + text_fitBox: BoolCast(Doc.UserDoc().fitBox), + text_align: StrCast(Doc.UserDoc().textAlign), + text_fontColor: StrCast(Doc.UserDoc().fontColor), + text_fontFamily: StrCast(Doc.UserDoc().fontFamily), + text_fontWeight: StrCast(Doc.UserDoc().fontWeight), + text_fontStyle: StrCast(Doc.UserDoc().fontStyle), + text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration), + }), + }); if (defaultTextTemplate) { tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 7424aaf2c..3f11a4713 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -240,7 +240,7 @@ export class DocumentDecorations extends ObservableReactComponent() * factor for converting between ink and screen space. */ inkScaledData = () => { - const inkData = Cast(this.dataDoc[this.fieldKey], InkField, Cast(this.layoutDoc[this.fieldKey], InkField, null))?.inkData ?? []; + const inkData = Cast(this.dataDoc[this.fieldKey], InkField, Cast(this.layoutDoc[this.fieldKey], InkField, null) ?? null)?.inkData ?? []; const inkStrokeWidth = NumCast(this.layoutDoc.stroke_width, 1); const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2; const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 7992ed412..b884eb8c8 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -53,7 +53,6 @@ import { WebBox } from './nodes/WebBox'; import { CalendarBox } from './nodes/calendarBox/CalendarBox'; import { ChatBox } from './nodes/chatbot/chatboxcomponents/ChatBox'; import { DailyJournal } from './nodes/formattedText/DailyJournal'; -import { ScrapbookVersionTwo } from './nodes/scrapbook/ScrapbookVersionTwo'; import { DashDocCommentView } from './nodes/formattedText/DashDocCommentView'; import { DashDocView } from './nodes/formattedText/DashDocView'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; @@ -66,6 +65,7 @@ import { PresBox, PresSlideBox } from './nodes/trails'; import { FaceRecognitionHandler } from './search/FaceRecognitionHandler'; import { SearchBox } from './search/SearchBox'; import { StickerPalette } from './smartdraw/StickerPalette'; +import { ScrapbookBox } from './nodes/scrapbook/ScrapbookBox'; dotenv.config(); @@ -121,7 +121,6 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; FormattedTextBox, DailyJournal, // AARAV ImageBox, - ScrapbookVersionTwo, FontIconBox, LabelBox, EquationBox, @@ -136,6 +135,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; VideoBox, AudioBox, RecordingBox, + ScrapbookBox, PresBox, PresSlideBox, SearchBox, diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 9155227dd..f11e646cc 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -343,7 +343,7 @@ export class CollectionStackingView extends CollectionSubView Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null) ?? null); // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list - getDisplayDoc(doc: Doc, trans: () => string, count: number) { + getDisplayDoc = (doc: Doc, trans: () => string, count: number) => { const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined; this._docXfs.push({ stackedDocTransform: this.getDocTransform(doc), width: this.getDocWidth(doc), height: this.getDocHeight(doc) }); return count > this._renderCount ? null : ( @@ -384,6 +384,7 @@ export class CollectionStackingView extends CollectionSubView ); - } + }; getDocTransform = computedFn((doc: Doc) => () => { // these must be referenced for document decorations to update when the text box container is scrolled diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index bc7d6f897..9a0fda3f8 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -58,6 +58,7 @@ export interface CollectionViewProps extends React.PropsWithChildren number; childContextMenuItems?: () => { script: ScriptField; label: string }[]; childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection + childRejectDrop?: (draggedDoc: Doc[] | undefined, subView?: DocumentView) => boolean; // whether a child document can be dropped on this document childHideDecorationTitle?: boolean; childHideResizeHandles?: boolean; childHideDecorations?: boolean; @@ -323,7 +324,7 @@ export function CollectionSubView() { protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean { const { docDragData } = de.complete; - if (docDragData && !docDragData.draggedDocuments.includes(this.Document)) { + if (docDragData && !docDragData.draggedDocuments.includes(this.Document) && !this._props.rejectDrop?.(docDragData.draggedDocuments, this.DocumentView?.())) { let added; const dropAction = docDragData.dropAction || docDragData.userDropAction; const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index c9af92a1b..eb9caf29d 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -183,15 +183,17 @@ export class CollectionView extends ViewBoxAnnotatableComponent - onClicks.push({ - description: `Set child ${childClick.title}`, - icon: 'edit', - event: () => { - this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)); - }, - }) - ); + DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null)?.data) + .filter(childClick => ScriptCast(childClick.data)) + .forEach(childClick => + onClicks.push({ + description: `Set child ${childClick.title}`, + icon: 'edit', + event: () => { + this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)!); + }, + }) + ); !Doc.IsSystem(this.Document) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 842293358..08126d4fe 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -496,6 +496,7 @@ export class CollectionFreeFormView extends CollectionSubView { + if (this._props.rejectDrop?.(de.complete.docDragData?.draggedDocuments, this._props.DocumentView?.())) return false; if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de, de.complete.annoDragData); if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData); if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData); @@ -1562,6 +1563,7 @@ export class CollectionFreeFormView extends CollectionSubView string | undefined; // is this document part of a group that is active // eslint-disable-next-line no-use-before-define setContentViewBox?: (view: ViewBoxInterface) => void; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox - + rejectDrop?: (draggedDoc: Doc[] | undefined, subView?: DocumentView) => boolean; // whether a document drop is rejected PanelWidth: () => number; PanelHeight: () => number; isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 3190757e2..5941e1669 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -117,10 +117,10 @@ export class FontIconBox extends ViewBoxBaseComponent() { default: type = 'slider'; break; } // prettier-ignore - const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, value, _readOnly_: value === undefined }); + const numScript = (value?: number) => ScriptCast(this.Document.script)?.script.run({ this: this.Document, value, _readOnly_: value === undefined }); const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string; // Script for checking the outcome of the toggle - const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3))); + const checkResult = Number(Number(numScript()?.result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3))); return ( () { drop = undoable( action((e: Event, de: DragManager.DropEvent) => { - if (de.complete.docDragData) { + if (de.complete.docDragData && this._props.rejectDrop?.(de.complete.docDragData?.draggedDocuments, this.DocumentView?.())) { let added: boolean | undefined; const hitDropTarget = (ele: HTMLElement, dropTarget: HTMLDivElement | null): boolean => { if (!ele) return false; @@ -938,6 +938,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { ScreenToLocalTransform={this.screenToLocalTransform} select={emptyFunction} focus={this.focus} + rejectDrop={this._props.rejectDrop} getScrollHeight={this.getScrollHeight} NativeDimScaling={returnOne} isAnyChildContentActive={returnFalse} diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx index 56cfcda70..b02976067 100644 --- a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx +++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx @@ -1,24 +1,25 @@ -import { makeObservable } from 'mobx'; +import { action, makeObservable, observable } from 'mobx'; import * as React from 'react'; -import { ViewBoxAnnotatableComponent } from '../../DocComponent'; -import { FieldView, FieldViewProps } from '../FieldView'; +import { Doc } from '../../../../fields/Doc'; +import { List } from '../../../../fields/List'; +import { emptyFunction } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { DocumentType } from '../../../documents/DocumentTypes'; -import { action, observable } from 'mobx'; -import { DocListCast } from '../../../../fields/Doc'; -import { Doc } from '../../../../fields/Doc'; +import { CollectionView } from '../../collections/CollectionView'; +import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { DocumentView } from '../DocumentView'; -import { FormattedTextBox } from '../formattedText/FormattedTextBox'; -import { List } from '../../../../fields/List'; +import { FieldView, FieldViewProps } from '../FieldView'; +import { DragManager } from '../../../util/DragManager'; // Scrapbook view: a container that lays out its child items in a grid/template export class ScrapbookBox extends ViewBoxAnnotatableComponent() { @observable createdDate: string; + private _dropDisposer?: DragManager.DragDropDisposer; constructor(props: FieldViewProps) { super(props); makeObservable(this); this.createdDate = this.getFormattedDate(); - + // ensure we always have a List in dataDoc['items'] if (!this.dataDoc[this.fieldKey]) { this.dataDoc[this.fieldKey] = new List(); @@ -31,12 +32,6 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent() return FieldView.LayoutString(ScrapbookBox, fieldStr); } - - - - - - getFormattedDate(): string { return new Date().toLocaleDateString(undefined, { year: 'numeric', @@ -55,40 +50,50 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent() componentDidMount() { this.setTitle(); - if (!this.dataDoc[this.fieldKey]) { - this.dataDoc[this.fieldKey] = new List(); - } } + childRejectDrop = (draggedDoc: Doc[] | undefined, subView?: DocumentView) => { + if (draggedDoc?.length === 1 && subView) { + if (subView.Document.type === DocumentType.IMG && draggedDoc[0].$type !== DocumentType.IMG) { + return true; + } + } + return false; + }; + rejectDrop = (draggedDoc: Doc[] | undefined, subView?: DocumentView) => { + if (draggedDoc?.length === 1 && draggedDoc[0].$type !== DocumentType.IMG) { + return true; + } + return false; + }; + onInternalDrop = (e: Event, de: DragManager.DropEvent) => { + if (de.complete.docDragData?.draggedDocuments[0]?.$type === DocumentType.IMG) { + return true; + } + return false; + }; + + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { + this._dropDisposer?.(); + if (ele) { + this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); + } + }; + render() { - // cast into an array even if empty - const items: Doc[] = DocListCast(this.dataDoc[this.fieldKey]); - return ( -
- +
r && this.createDashEventsTarget(r)}> +
- - //
- // {items.length === 0 - // ?
Drop docs here
- // : items.map((childDoc, idx) => ( - // - // )) - // } - //
); - } + } } - // Register scrapbook Docs.Prototypes.TemplateMap.set(DocumentType.SCRAPBOOK, { layout: { view: ScrapbookBox, dataField: 'items' }, -- cgit v1.2.3-70-g09d2 From 6be2ce925e3a0ba1e88e568ddbc57e3a272e6ff0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 23 Apr 2025 15:02:50 -0400 Subject: fixing bad merge --- src/client/views/nodes/ImageBox.tsx | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index c59c38fd8..1fcabaf21 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -8,7 +8,7 @@ import { extname } from 'path'; import * as React from 'react'; import { AiOutlineSend } from 'react-icons/ai'; import ReactLoading from 'react-loading'; -import { ClientUtils, DashColor, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils'; +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'; @@ -948,6 +948,35 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { addDocument={this.addDocument}> {this.content} + {this.Loading ? ( +
+ +
+ ) : null} + {this.regenerateImageIcon} + {this.overlayImageIcon} + {this.annotationLayer} + {!this._mainCont || !this.DocumentView || !this._annotationLayer.current ? null : ( + this.getImageDesc()} + /> + )} {this._outpaintingInProgress && (
-- cgit v1.2.3-70-g09d2 From 0d758430301d934a465ea090abf41f4a596a146c Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 23 Apr 2025 15:29:26 -0400 Subject: fixed outpainting to get authorization for dropbox --- src/client/documents/Documents.ts | 63 ++++++----------------- src/client/views/nodes/ImageBox.tsx | 55 ++++++++++---------- src/client/views/smartdraw/DrawingFillHandler.tsx | 6 ++- 3 files changed, 49 insertions(+), 75 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2a2f9d342..e694419a4 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -526,18 +526,17 @@ export class DocumentOptions { ai_firefly_seed?: number; ai_firefly_prompt?: string; - /** - * JSON‐stringified slot configuration for ScrapbookBox - */ - scrapbookConfig?: string; - - /** - * The list of embedded Doc instances in each Scrapbook slot - */ - scrapbookContents?: List; - - _outpaintingMetadata?: STRt = new StrInfo('serialized JSON metadata needed for image outpainting', false); + /** + * JSON‐stringified slot configuration for ScrapbookBox + */ + scrapbookConfig?: string; + /** + * The list of embedded Doc instances in each Scrapbook slot + */ + scrapbookContents?: List; + + _outpaintingMetadata?: STRt = new StrInfo('serialized JSON metadata needed for image outpainting', false); } export const DocOptions = new DocumentOptions(); @@ -587,31 +586,6 @@ export namespace Docs { options: { acl: '' }, }, ], - - // AARAV ADD // - [ - DocumentType.JOURNAL, - { - layout: { view: EmptyBox, dataField: 'text' }, - options: { - title: 'Daily Journal', - acl_Guest: SharingPermissions.View, - }, - }, - ], - // AARAV ADD // - - [ - DocumentType.SCRAPBOOK, - { - layout: { view: EmptyBox, dataField: 'text' }, - options: { - title: 'Scrapbook', - acl_Guest: SharingPermissions.View, - }, - - }, - ], ]); const suffix = 'Proto'; @@ -944,18 +918,17 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey); } - export function ScrapbookDocument( - items: Doc[] = [], - options: DocumentOptions = {}, - fieldKey: string = 'items' - ) { + export function ScrapbookDocument(items: Doc[] = [], options: DocumentOptions = {}, fieldKey: string = 'items') { return InstanceFromProto( Prototypes.get(DocumentType.SCRAPBOOK), new List(items), { - title: options.title - ?? new Date().toLocaleDateString(undefined, { - year: 'numeric', month: 'short', day: 'numeric' + title: + options.title ?? + new Date().toLocaleDateString(undefined, { + year: 'numeric', + month: 'short', + day: 'numeric', }), ...options, }, @@ -1006,8 +979,6 @@ export namespace Docs { // AARAV ADD // - - export function LinkDocument(source: Doc, target: Doc, options: DocumentOptions = {}, id?: string) { const linkDoc = InstanceFromProto( Prototypes.get(DocumentType.LINK), diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 1fcabaf21..9c6b8e99c 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -140,7 +140,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { }; componentDidMount() { - super.componentDidMount?.(); this._disposers.sizer = reaction( () => ({ forceFull: this._props.renderDepth < 1 || this.layoutDoc._showFullRes, @@ -426,38 +425,40 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { newDimensions: { width: newWidth, height: newHeight }, }); - const batch = UndoManager.StartBatch('outpaint image'); - if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') { - console.log('Received outpainted image:', response.url); - - if (!this.dataDoc[this.fieldKey + '_alternates']) { - this.dataDoc[this.fieldKey + '_alternates'] = new List(); - } + const error = ('error' in response && (response.error as string)) || ''; + if (error.includes('Dropbox') && confirm('Outpaint image failed. Try authorizing DropBox?\r\n' + error.replace(/^[^"]*/, ''))) { + DrawingFillHandler.authorizeDropbox(); + } else { + const batch = UndoManager.StartBatch('outpaint image'); + if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') { + if (!this.dataDoc[this.fieldKey + '_alternates']) { + this.dataDoc[this.fieldKey + '_alternates'] = new List(); + } - const originalDoc = Docs.Create.ImageDocument(field.url.href, { - title: `Original: ${this.Document.title}`, - _nativeWidth: Doc.NativeWidth(this.dataDoc), - _nativeHeight: Doc.NativeHeight(this.dataDoc), - }); + const originalDoc = Docs.Create.ImageDocument(field.url.href, { + title: `Original: ${this.Document.title}`, + _nativeWidth: Doc.NativeWidth(this.dataDoc), + _nativeHeight: Doc.NativeHeight(this.dataDoc), + }); - Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc); + Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc); - // Replace with new outpainted image - this.dataDoc[this.fieldKey] = new ImageField(response.url); + // Replace with new outpainted image + this.dataDoc[this.fieldKey] = new ImageField(response.url); - Doc.SetNativeWidth(this.dataDoc, newWidth); - Doc.SetNativeHeight(this.dataDoc, newHeight); + Doc.SetNativeWidth(this.dataDoc, newWidth); + Doc.SetNativeHeight(this.dataDoc, newHeight); - this.Document.$ai = true; - this.Document.$ai_outpainted = true; - this.Document.$ai_outpaint_prompt = customPrompt; - } else { - console.error('Unexpected API response:', response); - this.Document._width = origWidth; - this.Document._height = origHeight; - alert('Failed to receive a valid image URL from server.'); + this.Document.$ai = true; + this.Document.$ai_outpainted = true; + this.Document.$ai_outpaint_prompt = customPrompt; + } else { + this.Document._width = origWidth; + this.Document._height = origHeight; + alert('Failed to receive a valid image URL from server.'); + } + batch.end(); } - batch.end(); this._mainCont?.removeChild(loadingOverlay); } catch (error) { diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index b0945fd83..2c69284db 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -12,6 +12,9 @@ import { AspectRatioLimits, FireflyDimensionsMap, FireflyImageDimensions, Firefl const DashDropboxId = '2m86iveqdr9vzsa'; export class DrawingFillHandler { + static authorizeDropbox = () => { + window.open(`https://www.dropbox.com/oauth2/authorize?client_id=${DashDropboxId}&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox`, '_blank')?.focus(); + }; static drawingToImage = async (drawing: Doc, strength: number, user_prompt: string, styleDoc?: Doc) => { const tags = StrListCast(drawing.$tags).map(tag => tag.slice(1)); const styles = tags.filter(tag => FireflyStylePresets.has(tag)); @@ -46,8 +49,7 @@ export class DrawingFillHandler { .then(res => { const error = ('error' in res && (res.error as string)) || ''; if (error.includes('Dropbox') && confirm('Create image failed. Try authorizing DropBox?\r\n' + error.replace(/^[^"]*/, ''))) { - window.open(`https://www.dropbox.com/oauth2/authorize?client_id=${DashDropboxId}&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox`, '_blank')?.focus(); - return; + return DrawingFillHandler.authorizeDropbox(); } const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { title: StrCast(drawing.title) + ' AI Images', _width: 400, _height: 400 }); drawing.$ai_firefly_generatedDocs = genratedDocs; -- cgit v1.2.3-70-g09d2 From 78ac87b8acf63079071e5e8805692ed8c30042ce Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 23 Apr 2025 22:02:36 -0400 Subject: lots of typechecking fixes. --- .../apis/google_docs/GooglePhotosClientUtils.ts | 2 +- src/client/util/CalendarManager.tsx | 2 +- src/client/util/DocumentManager.ts | 8 +++--- src/client/util/LinkManager.ts | 3 +- src/client/util/ReplayMovements.ts | 6 ++-- src/client/util/ScriptManager.ts | 9 ++---- src/client/util/SearchUtil.ts | 5 ++-- .../util/reportManager/ReportManagerComponents.tsx | 4 +-- src/client/views/DashboardView.tsx | 28 +++++++++---------- src/client/views/InkStrokeProperties.ts | 8 +++--- src/client/views/InkingStroke.tsx | 12 ++++---- src/client/views/PropertiesButtons.tsx | 23 ++++++++-------- .../views/PropertiesDocBacklinksSelector.tsx | 8 ++---- src/client/views/PropertiesDocContextSelector.tsx | 4 +-- src/client/views/UndoStack.tsx | 5 +--- src/client/views/animationtimeline/Region.tsx | 32 ++++++++++------------ src/client/views/animationtimeline/Track.tsx | 24 ++++++++-------- src/client/views/collections/CollectionMenu.tsx | 4 +-- .../collections/CollectionNoteTakingViewColumn.tsx | 2 +- .../views/collections/CollectionPileView.tsx | 1 - .../collections/CollectionStackedTimeline.tsx | 2 +- .../views/collections/CollectionTreeView.tsx | 8 +++--- .../views/collections/FlashcardPracticeUI.tsx | 2 +- src/client/views/collections/TreeView.tsx | 4 +-- .../CollectionFreeFormLayoutEngines.tsx | 2 +- .../collectionFreeForm/FaceCollectionBox.tsx | 13 +++++---- .../collectionSchema/SchemaColumnHeader.tsx | 2 +- .../collectionSchema/SchemaTableCell.tsx | 18 ++++++------ src/client/views/linking/LinkMenuGroup.tsx | 5 ++-- src/client/views/linking/LinkMenuItem.tsx | 4 +-- .../views/newlightbox/ExploreView/ExploreView.tsx | 4 +-- src/client/views/newlightbox/NewLightboxView.tsx | 7 ++--- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 16 +++++------ .../views/nodes/DataVizBox/SchemaCSVPopUp.tsx | 6 ++-- .../nodes/DataVizBox/components/Histogram.tsx | 17 ++++++------ .../nodes/DataVizBox/components/LineChart.tsx | 25 +++++++++++------ .../views/nodes/DataVizBox/components/TableBox.tsx | 2 +- src/client/views/nodes/EquationBox.tsx | 1 - src/client/views/nodes/FieldView.tsx | 7 +++-- src/client/views/nodes/FunctionPlotBox.tsx | 11 ++++---- src/client/views/nodes/ImageBox.tsx | 2 +- .../views/nodes/MapBox/DirectionsAnchorMenu.tsx | 2 +- src/client/views/nodes/calendarBox/CalendarBox.tsx | 3 +- .../nodes/chatbot/chatboxcomponents/ChatBox.tsx | 20 ++++++++------ .../views/nodes/chatbot/tools/GetDocsTool.ts | 5 +++- .../views/nodes/chatbot/vectorstore/Vectorstore.ts | 15 +++++----- .../views/nodes/formattedText/DailyJournal.tsx | 2 +- src/client/views/nodes/scrapbook/ScrapbookBox.tsx | 2 +- src/client/views/pdf/Annotation.tsx | 7 +++-- src/client/views/search/FaceRecognitionHandler.tsx | 6 ++-- src/fields/Doc.ts | 2 +- src/fields/Types.ts | 2 ++ 52 files changed, 207 insertions(+), 207 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index 4b86a8341..15fd6313a 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -311,7 +311,7 @@ export namespace GooglePhotos { sources .filter(source => ImageCast(Doc.GetProto(source).data)) .forEach(async source => { - const data = ImageCast(Doc.GetProto(source).data); + const data = ImageCast(Doc.GetProto(source).data)!; const url = data.url.href; const target = Doc.MakeEmbedding(source); const description = parseDescription(target, descriptionKey); diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx index 4e321a893..b50e39c02 100644 --- a/src/client/util/CalendarManager.tsx +++ b/src/client/util/CalendarManager.tsx @@ -162,7 +162,7 @@ export class CalendarManager extends ObservableReactComponent { console.log('my calendars: ', Doc.MyCalendars); if (this.creationType === 'new-calendar') { - Doc.AddDocToList(Doc.MyCalendars, 'data', calendar); // add to new calendar to dashboard calendars + Doc.MyCalendars && Doc.AddDocToList(Doc.MyCalendars, 'data', calendar); // add to new calendar to dashboard calendars } } }; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ad57c2a62..3bae2881e 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -173,10 +173,10 @@ export class DocumentManager { while ( containerDocContext.length && DocCast(containerDocContext[0]?.embedContainer) && - DocCast(containerDocContext[0].embedContainer)?._type_collection !== CollectionViewType.Docking && + DocCast(containerDocContext[0].embedContainer)!._type_collection !== CollectionViewType.Docking && (includeExistingViews || !DocumentManager.Instance.getDocumentView(containerDocContext[0])) ) { - containerDocContext = [Cast(containerDocContext[0].embedContainer, Doc, null), ...containerDocContext]; + containerDocContext = [DocCast(containerDocContext[0].embedContainer)!, ...containerDocContext]; } return containerDocContext; } @@ -248,7 +248,7 @@ export class DocumentManager { finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done. ) => { const options = optionsIn; - Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc); + Doc.MyRecentlyClosed && Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc); const docContextPath = DocumentManager.GetContextPath(targetDoc, true); if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false; let activatedTab = false; @@ -272,7 +272,7 @@ export class DocumentManager { })); if (options.openLocation?.includes(OpenWhere.lightbox)) { // even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox) - const target = DocCast(targetDoc.annotationOn, targetDoc); + const target = DocCast(targetDoc.annotationOn, targetDoc)!; const compView = this.getDocumentView(DocCast(target.embedContainer))?.ComponentView; if ((compView?.addDocTab ?? compView?._props.addDocTab)?.(target, options.openLocation)) { await new Promise(waitres => { diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 344e2e4c0..d8e0c4cbe 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -217,7 +217,8 @@ export class LinkManager { } // finds the opposite anchor of a given anchor in a link - public static getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc | undefined { + public static getOppositeAnchor(linkDoc: Doc | undefined, anchor: Doc | undefined): Doc | undefined { + if (!linkDoc || !anchor) return undefined; const id = LinkManager.anchorIndex(linkDoc, anchor); const a1 = DocCast(linkDoc.link_anchor_1); const a2 = DocCast(linkDoc.link_anchor_2); diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts index 62a09a8bc..4f0423342 100644 --- a/src/client/util/ReplayMovements.ts +++ b/src/client/util/ReplayMovements.ts @@ -108,9 +108,11 @@ export class ReplayMovements { movements.forEach((movement, i) => { if (typeof movement.doc === 'string') { - movements[i].doc = IdToDoc(movement.doc); - if (!movements[i].doc) { + const doc = IdToDoc(movement.doc); + if (!doc) { console.log('ERROR: tracked doc not found'); + } else { + movements[i].doc = doc; } } }); diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts index 9158f6c0b..8c7f88bf6 100644 --- a/src/client/util/ScriptManager.ts +++ b/src/client/util/ScriptManager.ts @@ -1,7 +1,6 @@ -import { Doc, DocListCast } from '../../fields/Doc'; +import { Doc, DocListCast, StrListCast } from '../../fields/Doc'; import { List } from '../../fields/List'; -import { listSpec } from '../../fields/Schema'; -import { Cast, StrCast } from '../../fields/Types'; +import { StrCast } from '../../fields/Types'; import { Docs } from '../documents/Documents'; import { ScriptingGlobals } from './ScriptingGlobals'; @@ -10,7 +9,6 @@ export class ScriptManager { // eslint-disable-next-line no-use-before-define private static _instance: ScriptManager; public static get Instance(): ScriptManager { - // eslint-disable-next-line no-return-assign return this._instance || (this._instance = new this()); } private constructor() { @@ -58,7 +56,7 @@ export class ScriptManager { public static addScriptToGlobals(scriptDoc: Doc): void { ScriptingGlobals.removeGlobal(StrCast(scriptDoc.name)); - const params = Cast(scriptDoc['data-params'], listSpec('string'), []); + const params = StrListCast(scriptDoc['data-params']); const paramNames = params.reduce((o: string, p: string) => { let out = o; if (params.indexOf(p) === params.length - 1) { @@ -69,7 +67,6 @@ export class ScriptManager { return out; }, '' as string); - // eslint-disable-next-line no-new-func const f = new Function(paramNames, StrCast(scriptDoc.script)); Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false }); diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index e4adcaa7e..fc3bb99ab 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -13,7 +13,7 @@ export namespace SearchUtil { const blockedKeys = matchKeyNames ? [] : Object.entries(DocOptions) - .filter(([, info]: [string, FInfo]) => !info?.searchable()) + .filter(([, info]: [string, FieldType | FInfo | undefined]) => (info instanceof FInfo ? !info.searchable() : true)) .map(([key]) => key); const exact = queryIn.startsWith('='); @@ -22,8 +22,7 @@ export namespace SearchUtil { const results = new ObservableMap(); if (collectionDoc) { const docs = DocListCast(collectionDoc[Doc.LayoutDataKey(collectionDoc)]); - // eslint-disable-next-line @typescript-eslint/ban-types - const docIDs: String[] = []; + const docIDs: string[] = []; SearchUtil.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => { const dtype = StrCast(doc.type) as DocumentType; if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) { diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx index 92f877859..80653779e 100644 --- a/src/client/util/reportManager/ReportManagerComponents.tsx +++ b/src/client/util/reportManager/ReportManagerComponents.tsx @@ -1,5 +1,3 @@ -/* eslint-disable react/require-default-props */ -/* eslint-disable prefer-destructuring */ /* eslint-disable no-use-before-define */ import * as React from 'react'; import ReactMarkdown from 'react-markdown'; @@ -299,7 +297,7 @@ export function IssueView({ issue }: IssueViewProps) { )} - + {issueBody} diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index f61f6db18..3ceb23ffd 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -113,12 +113,12 @@ export class DashboardView extends ObservableReactComponent { getDashboards = (whichGroup: DashboardGroup) => { if (whichGroup === DashboardGroup.MyDashboards) { - return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard.$author === ClientUtils.CurrentUserEmail()); + return DocListCast(Doc.MyDashboards?.data).filter(dashboard => dashboard.$author === ClientUtils.CurrentUserEmail()); } - return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig); + return DocListCast(Doc.MySharedDocs?.data_dashboards).filter(doc => doc.dockingConfig); }; - isUnviewedSharedDashboard = (dashboard: Doc) => !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard); + isUnviewedSharedDashboard = (dashboard: Doc) => !DocListCast(Doc.MySharedDocs?.viewed).includes(dashboard); @undoBatch createNewDashboard = (name: string, background?: string) => { @@ -155,7 +155,7 @@ export class DashboardView extends ObservableReactComponent { @action openNewDashboardModal = () => { this.openModal = true; - this.setNewDashboardName(`Dashboard ${DocListCast(Doc.MyDashboards.data).length + 1}`); + this.setNewDashboardName(`Dashboard ${DocListCast(Doc.MyDashboards?.data).length + 1}`); }; _downX: number = 0; @@ -191,7 +191,7 @@ export class DashboardView extends ObservableReactComponent {