From b60b1863be5ecf987f3d3f2483e28e74f634853f Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 25 Apr 2025 16:02:18 -0400 Subject: fixed face collection to show up. fixed pasting text dirextly into freeformview to create docs properly. also fixed paste to match source text styling better. fixed copying out of pdf. fixed formatting of gpt (()) calls from text box by using paste handling code. --- .../views/nodes/formattedText/FormattedTextBox.tsx | 62 ++++++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9897a0062..164c64107 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1344,8 +1344,63 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor'); + handlePaste = (view: EditorView, event: ClipboardEvent /* , slice: Slice */): boolean => { + return this.doPaste(view, event.clipboardData); + }; + doPaste = (view: EditorView, data: DataTransfer | null) => { + const html = data?.getData('text/html'); + const text = data?.getData('text/plain'); + const pdfAnchorId = data?.getData('dash/pdfAnchor'); + if (html && !pdfAnchorId) { + const replaceDivsWithParagraphs = (expr: string) => { + // Create a temporary DOM container + const container = document.createElement('div'); + container.innerHTML = expr; + + // Recursive function to process all divs + function processDivs(node: HTMLElement) { + // Get all div elements in the current node (live collection) + const divs = node.getElementsByTagName('div'); + + // We need to convert to array because we'll be modifying the DOM + const divsArray = Array.from(divs); + + for (const div of divsArray) { + // Create replacement paragraph + const p = document.createElement('p'); + + // Copy all attributes + for (const attr of div.attributes) { + p.setAttribute(attr.name, attr.value); + } + + // Move all child nodes + while (div.firstChild) { + p.appendChild(div.firstChild); + } + + // Replace the div with the paragraph + div.parentNode?.replaceChild(p, div); + + // Process any nested divs that were moved into the new paragraph + processDivs(p); + } + } + + // Start processing from the container + processDivs(container); + + return container.innerHTML; + }; + const fixedHTML = replaceDivsWithParagraphs(html); + // .replace(/]*)>(.*?)<\/div>/g, '$2

'); // prettier-ignore + this._inDrop = true; + view.pasteHTML(html.split(' this.EditorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it. } -- cgit v1.2.3-70-g09d2 From a25dc02334de3f5b58aff1911bdae30d49a1d26b Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 28 Apr 2025 12:52:29 -0400 Subject: cleaned up outpainting and Doc resize code. fixes problems with doc resizing of text boxes with ctrl-key. --- src/client/documents/Documents.ts | 2 - src/client/views/DocumentDecorations.tsx | 106 +++++++-------------- src/client/views/nodes/ImageBox.tsx | 10 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/server/ApiManagers/FireflyManager.ts | 8 +- 5 files changed, 39 insertions(+), 89 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e694419a4..a4a668085 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -535,8 +535,6 @@ export class DocumentOptions { * 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(); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3f11a4713..2d39b827d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -445,6 +445,12 @@ export class DocumentDecorations extends ObservableReactComponent { SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them + DocumentView.Selected() + .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox) + .forEach(dv => { + dv.Document._outpaintingOriginalWidth = NumCast(dv.Document._width); + dv.Document._outpaintingOriginalHeight = NumCast(dv.Document._height); + }); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); e.stopPropagation(); const id = (this._resizeHdlId = e.currentTarget.className); @@ -484,50 +490,31 @@ export class DocumentDecorations extends ObservableReactComponent { + runInAction(() => { // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate) this._interactionLock = true; this._snapPt = thisPt; - // Special handling for shift+click (outpainting mode) - if (e.shiftKey && DocumentView.Selected().some(dv => dv.ComponentView instanceof ImageBox)) { - DocumentView.Selected().forEach(docView => { - if (docView.ComponentView instanceof ImageBox) { - // Set flag for outpainting mode - docView.Document._outpaintingResize = true; + const notOutpainted = DocumentView.Selected().filter(dv => !e.shiftKey || !(dv.ComponentView instanceof ImageBox)); + // Special handling for shift-drag resize (outpainting of Images) + DocumentView.Selected() + .filter(dv => !notOutpainted.includes(dv)) + .forEach(dv => this.resizeViewForOutpainting(dv, refPt, scale, { dragHdl, shiftKey: e.shiftKey })); // Adjust only the document dimensions without scaling internal content - // Adjust only the document dimensions without scaling internal content - this.resizeViewForOutpainting(docView, refPt, scale, { dragHdl, shiftKey: e.shiftKey }); - } else { - // Handle regular resize for non-image components - e.ctrlKey && !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions(); - const hasFixedAspect = this.hasFixedAspect(docView.Document); - const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y }; - this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey: e.ctrlKey }); - } - }); - } else { - // Regular resize behavior (existing code) - e.ctrlKey && DocumentView.Selected().forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()); - const hasFixedAspect = DocumentView.Selected() - .map(dv => dv.Document) - .some(this.hasFixedAspect); - const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y }; - DocumentView.Selected().forEach(docView => this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey: e.ctrlKey })); - } + // Regular resize behavior for docs not being outpainted + e.ctrlKey && notOutpainted.forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()); + const hasFixedAspect = notOutpainted.map(dv => dv.Document).some(this.hasFixedAspect); + const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y }; + notOutpainted.forEach(docView => this.resizeView(docView, refPt, scaleAspect, { dragHdl, freezeNativeDims: e.ctrlKey })); - await new Promise(res => { - setTimeout(() => { - res((this._interactionLock = undefined)); - }); - }); + new Promise(res => setTimeout(() => res((this._interactionLock = undefined)))); }); return false; @@ -545,11 +532,6 @@ export class DocumentDecorations extends ObservableReactComponent { + onPointerUp = (e: PointerEvent): void => { SnappingManager.SetIsResizing(undefined); SnappingManager.clearSnapLines(); // Check if any outpainting needs to be processed - DocumentView.Selected().forEach(view => { - if (view.Document._needsOutpainting && view.ComponentView instanceof ImageBox) { - // Trigger outpainting process in the ImageBox component - (view.ComponentView as ImageBox).processOutpainting(); - - // Clear flags - view.Document._needsOutpainting = false; - view.Document._outpaintingResize = false; - } - }); + DocumentView.Selected() + .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox) + .forEach(view => (view.ComponentView as ImageBox).processOutpainting()); this._resizeHdlId = ''; this._resizeUndo?.end(); @@ -647,7 +608,7 @@ export class DocumentDecorations extends ObservableReactComponent { + resizeView = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; freezeNativeDims: boolean }) => { const doc = docView.Document; if (doc.isGroup) { DocListCast(doc.data) @@ -660,25 +621,24 @@ export class DocumentDecorations extends ObservableReactComponent() { }, { fireImmediately: true } ); - this._disposers.outpainting = reaction( - () => this.Document?._needsOutpainting, - needsOutpainting => { - if (needsOutpainting && this.Document?._outpaintingResize) { - this.processOutpainting(); - } - } - ); } componentWillUnmount() { @@ -421,7 +413,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { const response = await Networking.PostToServer('/outpaintImage', { imageUrl: currentPath, prompt: customPrompt, - originalDimensions: { width: origWidth, height: origHeight }, + originalDimensions: { width: Math.min(newWidth, origWidth), height: Math.min(newHeight, origHeight) }, newDimensions: { width: newWidth, height: newHeight }, }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 164c64107..98e461a52 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1149,7 +1149,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { this.layoutDoc['_' + this.fieldKey + '_height'] = scrollHeight; - if (!this.layoutDoc.isTemplateForField) this.layoutDoc._nativeHeight = scrollHeight; + if (!this.layoutDoc.isTemplateForField && NumCast(this.layoutDoc._nativeHeight)) this.layoutDoc._nativeHeight = scrollHeight; }); addPlugin = (plugin: Plugin) => { diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index 5311ca643..63581d3b3 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -343,10 +343,10 @@ export default class FireflyManager extends ApiManager { 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), + left: Math.round(req.body.newDimensions.width - req.body.originalDimensions.width) / 2, + top: Math.round(req.body.newDimensions.height - req.body.originalDimensions.height) / 2, + right: Math.round(req.body.newDimensions.width - req.body.originalDimensions.width) / 2, + bottom: Math.round(req.body.newDimensions.height - req.body.originalDimensions.height) / 2, }, alignment: { horizontal: 'center', -- cgit v1.2.3-70-g09d2 From 0694277e33c4a5c4f66b1cebef38f9d86c38cf34 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 29 Apr 2025 12:29:02 -0400 Subject: clean up outpaint code. fix image sizing when outpainting. --- src/client/views/DocumentDecorations.tsx | 25 ++++++------- src/client/views/nodes/ImageBox.scss | 42 +++++++++++----------- src/client/views/nodes/ImageBox.tsx | 24 ++++++------- .../views/nodes/formattedText/FormattedTextBox.tsx | 1 - .../imageMeshTool/imageMeshToolButton.tsx | 15 ++------ 5 files changed, 46 insertions(+), 61 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 2d39b827d..ab665e984 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -448,8 +448,8 @@ export class DocumentDecorations extends ObservableReactComponent e.shiftKey && dv.ComponentView instanceof ImageBox) .forEach(dv => { - dv.Document._outpaintingOriginalWidth = NumCast(dv.Document._width); - dv.Document._outpaintingOriginalHeight = NumCast(dv.Document._height); + dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalWidth'] = NumCast(dv.Document._width); + dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalHeight'] = NumCast(dv.Document._height); }); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); e.stopPropagation(); @@ -502,14 +502,15 @@ export class DocumentDecorations extends ObservableReactComponent !e.shiftKey || !(dv.ComponentView instanceof ImageBox)); - // Special handling for shift-drag resize (outpainting of Images) - DocumentView.Selected() - .filter(dv => !notOutpainted.includes(dv)) - .forEach(dv => this.resizeViewForOutpainting(dv, refPt, scale, { dragHdl, shiftKey: e.shiftKey })); // Adjust only the document dimensions without scaling internal content + const outpainted = e.shiftKey ? DocumentView.Selected().filter(dv => dv.ComponentView instanceof ImageBox) : []; + const notOutpainted = e.shiftKey ? DocumentView.Selected().filter(dv => !outpainted.includes(dv)) : DocumentView.Selected(); - // Regular resize behavior for docs not being outpainted + // Special handling for shift-drag resize (outpainting of Images by resizing without scaling content - fill in with firefly GAI) + e.shiftKey && outpainted.forEach(dv => this.resizeViewForOutpainting(dv, refPt, scale, { dragHdl, shiftKey: e.shiftKey })); + + // Special handling for not outpainted Docs when ctrl-resizing (setup native dimesions for modification) e.ctrlKey && notOutpainted.forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()); + const hasFixedAspect = notOutpainted.map(dv => dv.Document).some(this.hasFixedAspect); const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y }; notOutpainted.forEach(docView => this.resizeView(docView, refPt, scaleAspect, { dragHdl, freezeNativeDims: e.ctrlKey })); @@ -546,20 +547,14 @@ export class DocumentDecorations extends ObservableReactComponent { + onPointerUp = () => { SnappingManager.SetIsResizing(undefined); SnappingManager.clearSnapLines(); - // Check if any outpainting needs to be processed - DocumentView.Selected() - .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox) - .forEach(view => (view.ComponentView as ImageBox).processOutpainting()); - this._resizeHdlId = ''; this._resizeUndo?.end(); diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 9fc20ffd4..ac1a6ece9 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -106,7 +106,7 @@ height: 100%; img { object-fit: contain; - height: 100%; + height: fit-content; } .imageBox-fadeBlocker, @@ -249,27 +249,29 @@ background: white; padding: 20px; border-radius: 8px; - box-shadow: 0 4px 12px rgba(0,0,0,0.2); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); z-index: 10000; - - h3 { margin-top: 0; } - + + h3 { + margin-top: 0; + } + input { - width: 300px; - padding: 8px; - margin-bottom: 10px; + 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; - } + 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 010028af7..31a135fa7 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -170,6 +170,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { }, { fireImmediately: true } ); + this._disposers.outpaint = reaction( + () => this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined && !SnappingManager.ShiftKey, + complete => complete && this.openOutpaintPrompt(), + { fireImmediately: true } + ); } componentWillUnmount() { @@ -372,13 +377,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { 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[this.fieldKey + '_outpaintOriginalWidth']); + const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']); // Set flag that outpainting is in progress this._outpaintingInProgress = true; @@ -444,8 +444,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this.Document.$ai = true; this.Document.$ai_outpainted = true; this.Document.$ai_outpaint_prompt = customPrompt; - this.Document._outpaintingOriginalWidth = undefined; - this.Document._outpaintingOriginalHeight = undefined; + this.Document[this.fieldKey + '_outpaintOriginalWidth'] = undefined; + this.Document[this.fieldKey + '_outpaintOriginalHeight'] = undefined; } else { this.Document._width = origWidth; this.Document._height = origHeight; @@ -465,11 +465,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { } }; - processOutpainting = () => this.openOutpaintPrompt(); - componentUI = () => !this._showOutpaintPrompt ? null : ( -
+

Outpaint Image

() { ref={action((r: HTMLImageElement | null) => (this.imageRef = r))} key="paths" src={srcpath} - style={{ transform, transformOrigin, height: 'fit-content' }} + style={{ transform, transformOrigin, height: this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined ? '100%' : undefined }} onError={action(e => (this._error = e.toString()))} draggable={false} width={nativeWidth} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 98e461a52..d6fa3172d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1349,7 +1349,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const html = data?.getData('text/html'); - const text = data?.getData('text/plain'); const pdfAnchorId = data?.getData('dash/pdfAnchor'); if (html && !pdfAnchorId) { const replaceDivsWithParagraphs = (expr: string) => { diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx index c02a1eb94..e580c7070 100644 --- a/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx +++ b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx @@ -13,20 +13,11 @@ interface ButtonContainerProps { btnText: string; imageWidth: number; imageHeight: number; - gridXSize: number; // X subdivisions - gridYSize: number; // Y subdivisions + gridXSize: number; // X subdivisions + gridYSize: number; // Y subdivisions } -export function MeshTransformButton({ - loading, - onClick, - onReset, - btnText, - imageWidth, - imageHeight, - gridXSize, - gridYSize -}: ButtonContainerProps) { +export function MeshTransformButton({ loading, onClick, onReset, btnText, imageWidth, imageHeight, gridXSize, gridYSize }: ButtonContainerProps) { const [showGrid, setShowGrid] = React.useState(false); const [isGridInteractive, setIsGridInteractive] = React.useState(false); // Controls the dragging of control points const imageRef = React.useRef(null); // Reference to the image element -- cgit v1.2.3-70-g09d2