diff options
author | bobzel <zzzman@gmail.com> | 2025-05-05 12:29:18 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2025-05-05 12:29:18 -0400 |
commit | 8d15a14a1f230b12b8ed90dad5650f3c8857feb6 (patch) | |
tree | 47c2f8cf1277433d9845782ae8e234edbb614278 | |
parent | 54b8c7e9ec258fd43ec523aaf3d967a646022cee (diff) | |
parent | d4659e2bd3ddb947683948083232c26fb1227f39 (diff) |
Merge branch 'master' into task_nodes_aarav
14 files changed, 174 insertions, 116 deletions
diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx index bb7a07f0e..2290516dc 100644 --- a/src/client/views/ObservableReactComponent.tsx +++ b/src/client/views/ObservableReactComponent.tsx @@ -15,6 +15,33 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, obj this._props = props; makeObservable(this); } + __passiveWheel: HTMLElement | null = null; + _isContentActive: () => boolean | undefined = () => false; + + /** + * default method to stop wheel events from bubbling up to parent components. + * @param e + */ + onPassiveWheel = (e: WheelEvent) => { + if (this._isContentActive?.()) e.stopPropagation(); + }; + + /** + * This fixes the problem where a component uses wheel events to scroll, but is nested inside another component that + * can also scroll. In that case, the wheel event will bubble up to the parent component and cause it to scroll in addition. + * This is based on the native HTML5 behavior where wheel events are passive by default, meaning that they do not prevent the default action of scrolling. + * This method should be called from a ref={} property on or above the component that uses wheel events to scroll. + * @param ele HTMLELement containing the component that will scroll + * @param isContentActive function determining if the component is active and should handle the wheel event. + * @param onPassiveWheel an optional function to call to handle the wheel event (and block its propagation. If omitted, the event won't propagate. + */ + fixWheelEvents = (ele: HTMLElement | null, isContentActive: () => boolean | undefined, onPassiveWheel?: (e: WheelEvent) => void) => { + this._isContentActive = isContentActive; + this.__passiveWheel?.removeEventListener('wheel', onPassiveWheel ?? this.onPassiveWheel); + this.__passiveWheel = ele; + ele?.addEventListener('wheel', onPassiveWheel ?? this.onPassiveWheel, { passive: false }); + }; + componentDidUpdate(prevProps: Readonly<T>): void { Object.keys(prevProps) .filter(pkey => (prevProps as {[key:string]: unknown})[pkey] !== (this.props as {[key:string]: unknown})[pkey]) diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 50de7c601..d5edc3e0b 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -40,7 +40,6 @@ import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; export class CollectionCardView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [key: string]: IReactionDisposer } = {}; - private _oldWheel: HTMLElement | null = null; private _dropped = false; // set when a card doc has just moved and the drop method has been called - prevents the pointerUp method from hiding doc decorations (which needs to be done when clicking on a card to animate it to front/center) private _setCurDocScript = () => ScriptField.MakeScript('scriptContext.layoutDoc._card_curDoc=this', { scriptContext: 'any' })!; private _draggerRef = React.createRef<HTMLDivElement>(); @@ -56,13 +55,7 @@ export class CollectionCardView extends CollectionSubView() { } protected createDashEventsTarget = (ele: HTMLDivElement | null) => { this._dropDisposer?.(); - if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); - } - this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); - this._oldWheel = ele; - // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling - ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + this.fixWheelEvents(ele, this._props.isContentActive); }; @computed get cardWidth() { return NumCast(this.layoutDoc._cardWidth, 50); @@ -487,7 +480,6 @@ export class CollectionCardView extends CollectionSubView() { ); }); } - onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); contentScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1); docViewProps = (): DocumentViewProps => ({ diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 9c8ef5519..755e31925 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -25,7 +25,6 @@ const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = requi @observer export class CollectionCarousel3DView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; - private _oldWheel: HTMLElement | null = null; constructor(props: SubCollectionViewProps) { super(props); @@ -44,10 +43,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); } - this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); - this._oldWheel = ele; - // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling - ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + this.fixWheelEvents(ele, this._props.isContentActive); }; @computed get scrollSpeed() { @@ -101,7 +97,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { focus = (anchor: Doc, options: FocusViewOptions): Opt<number> => { const docs = DocListCast(this.Document[this.fieldKey]); if (anchor.type === DocumentType.CONFIG || docs.includes(anchor)) { - const newIndex = anchor.config_carousel_index ?? docs.getIndex(DocCast(anchor.annotationOn, anchor)); + const newIndex = anchor.config_carousel_index ?? docs.getIndex(DocCast(anchor.annotationOn, anchor)!); options.didMove = newIndex !== this.layoutDoc._carousel_index; options.didMove && (this.layoutDoc._carousel_index = newIndex); } @@ -221,7 +217,6 @@ export class CollectionCarousel3DView extends CollectionSubView() { return this.panelWidth() * (1 - index); } - onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); curDoc = () => this.carouselItems[NumCast(this.layoutDoc._carousel_index)]?.layout; answered = (correct: boolean) => (!correct || !this.curDoc() || NumCast(this.layoutDoc._carousel_index) === this.carouselItems.length - 1) && this.changeSlide(1); docViewProps = () => ({ diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 975dc52fe..ac1981012 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -21,7 +21,6 @@ import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; export class CollectionCarouselView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; - _oldWheel: HTMLElement | null = null; _fadeTimer: NodeJS.Timeout | undefined; @observable _last_index = this.carouselIndex; @observable _last_opacity = 1; @@ -43,10 +42,7 @@ export class CollectionCarouselView extends CollectionSubView() { if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); } - this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); - this._oldWheel = ele; - // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling - ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + this.fixWheelEvents(ele, this._props.isContentActive); }; @computed get captionMarginX(){ return NumCast(this.layoutDoc.caption_xMargin, 50); } // prettier-ignore @@ -115,7 +111,6 @@ export class CollectionCarouselView extends CollectionSubView() { : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined; // prettier-ignore - onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => { return ( diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index f11e646cc..be570564b 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -55,7 +55,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection _docXfs: { height: () => number; width: () => number; stackedDocTransform: () => Transform }[] = []; // Doesn't look like this field is being used anywhere. Obsolete? _columnStart: number = 0; - _oldWheel: HTMLElement | null = null; @observable _refList: HTMLElement[] = []; // map of node headers to their heights. Used in Masonry @@ -721,7 +720,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return this._props.isContentActive() === false ? 'none' : undefined; } - onPassiveWheel = (e: WheelEvent) => this._props.isContentActive() && e.stopPropagation(); render() { TraceMobx(); const editableViewProps = { @@ -744,10 +742,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection ref={ele => { this._masonryGridRef = ele; this.createDashEventsTarget(ele); // so the whole grid is the drop target? - this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); - this._oldWheel = ele; - // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling - ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + this.fixWheelEvents(ele, this._props.isContentActive); }} style={{ overflowY: this.isContentActive() ? 'auto' : 'hidden', diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index c4971c204..2364fd74a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -2268,10 +2268,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection id={this._paintedId} ref={r => { this.createDashEventsTarget(r); - this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); - this._oldWheel = r; - // prevent wheel events from passivly propagating up through containers - r?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + this.fixWheelEvents(r, this._props.isContentActive, this.onPassiveWheel); r?.addEventListener('mouseleave', this.onMouseLeave); r?.addEventListener('mouseenter', this.onMouseEnter); }} diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx index 624c85beb..142085e14 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx @@ -150,11 +150,6 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() { FaceRecognitionHandler.UniqueFaceRemoveFaceImage(imgDoc, this.Document); }, 'remove doc from face'); - /** - * This stops scroll wheel events when they are used to scroll the face collection. - */ - onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); - render() { return ( <div className="face-document-item" ref={ele => this.createDropTarget(ele!)}> @@ -181,12 +176,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() { style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none', }} - ref={action((ele: HTMLDivElement | null) => { - this._listRef?.removeEventListener('wheel', this.onPassiveWheel); - this._listRef = ele; - // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling - ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); - })}> + ref={r => this.fixWheelEvents(r, this._props.isContentActive)}> {FaceRecognitionHandler.UniqueFaceImages(this.Document).map((doc, i) => { const [name, type] = ImageCastToNameType(doc?.[Doc.LayoutDataKey(doc)]) ?? ['-missing-', '.png']; return ( diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 82ca96839..c06391f35 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1293,7 +1293,6 @@ export class CollectionSchemaView extends CollectionSubView() { isContentActive = () => this._props.isSelected() || this._props.isContentActive(); screenToLocal = () => this.ScreenToLocalBoxXf().translate(-this.tableWidth, 0); previewWidthFunc = () => this.previewWidth; - onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); displayedDocsFunc = () => this.docsWithDrag.docs; render() { return ( @@ -1307,15 +1306,7 @@ export class CollectionSchemaView extends CollectionSubView() { this.setColDrag(false); }}> <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }} /> - <div - className="schema-table" - style={{ width: `calc(100% - ${this.previewWidth}px)` }} - onWheel={e => this._props.isContentActive() && e.stopPropagation()} - ref={ele => { - // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling - this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); - (this._oldWheel = ele)?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); - }}> + <div className="schema-table" style={{ width: `calc(100% - ${this.previewWidth}px)` }} onWheel={e => this._props.isContentActive() && e.stopPropagation()} ref={ele => this.fixWheelEvents(ele, this._props.isContentActive)}> <div className="schema-header-row" style={{ height: this.rowHeightFunc() }}> <div className="row-menu" style={{ width: CollectionSchemaView._rowMenuWidth }}> <IconButton diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx index 3b666bad5..7cfccf0dc 100644 --- a/src/client/views/nodes/DiagramBox.tsx +++ b/src/client/views/nodes/DiagramBox.tsx @@ -185,11 +185,6 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return '( )'; }; - /** - * This stops scroll wheel events when they are used to scroll the face collection. - */ - onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); - render() { return ( <div @@ -197,12 +192,7 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none', }} - ref={action((ele: HTMLDivElement | null) => { - this._boxRef?.removeEventListener('wheel', this.onPassiveWheel); - this._boxRef = ele; - // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling - ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); - })}> + ref={r => this.fixWheelEvents(r, this._props.isContentActive)}> <div className="DIYNodeBox-searchbar"> <input type="text" value={this._inputValue} onKeyDown={action(e => e.key === 'Enter' && this.generateMermaidCode())} onChange={action(e => (this._inputValue = e.target.value))} /> <button type="button" onClick={this.generateMermaidCode}> diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index ac1a6ece9..9f7a5d03f 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: fit-content; + height: 100%; } .imageBox-fadeBlocker, @@ -242,12 +242,9 @@ } } .imageBox-regenerate-dialog { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + position: absolute; background: white; - padding: 20px; + padding: 10px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); z-index: 10000; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 31a135fa7..d16baada6 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,4 +1,4 @@ -import { Button, Colors, EditableText, Size, Type } from '@dash/components'; +import { Button, Colors, EditableText, IconButton, Size, Toggle, ToggleType, Type } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Slider, Tooltip } from '@mui/material'; import axios from 'axios'; @@ -353,6 +353,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @action openOutpaintPrompt = () => { + this._outpaintVAlign = ''; + this._outpaintAlign = ''; this._showOutpaintPrompt = true; }; @@ -362,6 +364,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; @action + cancelOutpaintPrompt = () => { + const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']); + const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']); + this.Document._width = origWidth; + this.Document._height = origHeight; + this._outpaintingInProgress = false; + this.closeOutpaintPrompt(); + }; + + @action handlePromptChange = (val: string | number) => { this._outpaintPromptInput = '' + val; }; @@ -377,17 +389,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (!field) 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; // Revert dimensions if prompt is blank (acts like Cancel) if (!customPrompt) { - this.Document._width = origWidth; - this.Document._height = origHeight; - this._outpaintingInProgress = false; + this.cancelOutpaintPrompt(); return; } @@ -410,11 +417,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>'; this._mainCont?.appendChild(loadingOverlay); + const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']); + const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']); const response = await Networking.PostToServer('/outpaintImage', { imageUrl: currentPath, prompt: customPrompt, originalDimensions: { width: Math.min(newWidth, origWidth), height: Math.min(newHeight, origHeight) }, newDimensions: { width: newWidth, height: newHeight }, + halignment: this._outpaintAlign, + valignment: this._outpaintVAlign, }); const error = ('error' in response && (response.error as string)) || ''; @@ -447,8 +458,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this.Document[this.fieldKey + '_outpaintOriginalWidth'] = undefined; this.Document[this.fieldKey + '_outpaintOriginalHeight'] = undefined; } else { - this.Document._width = origWidth; - this.Document._height = origHeight; + this.cancelOutpaintPrompt(); alert('Failed to receive a valid image URL from server.'); } batch.end(); @@ -456,30 +466,106 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { 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.'); + this.cancelOutpaintPrompt(); + alert('An error occurred while outpainting.' + error); } finally { runInAction(() => (this._outpaintingInProgress = false)); } }; - componentUI = () => + @observable _outpaintAlign = ''; + @observable _outpaintVAlign = ''; + @computed get outpaintVertical() { + return this._props.PanelWidth() / this._props.PanelHeight() < this.nativeSize.nativeWidth / this.nativeSize.nativeHeight; + } + + componentUI = (/* boundsLeft: number, boundsTop: number*/) => !this._showOutpaintPrompt ? null : ( - <div key="imageBox-componentui" className="imageBox-regenerate-dialog" style={{ backgroundColor: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}> - <h3>Outpaint Image</h3> - <EditableText - placeholder="Enter a prompt for extending the image:" - setVal={val => this.handlePromptChange(val)} - val={this._outpaintPromptInput} - type={Type.TERT} - color={SettingsManager.userColor} - background={SettingsManager.userBackgroundColor} - /> - <div className="buttons"> - <Button text="Cancel" type={Type.TERT} onClick={this.closeOutpaintPrompt} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} /> - <Button text="Generate" type={Type.TERT} onClick={this.submitOutpaintPrompt} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} /> + <div + key="imageBox-componentui" + className="imageBox-regenerate-dialog" + style={{ + top: -70 + (this._props.DocumentView?.().getBounds?.top ?? 0), + left: this._props.DocumentView?.().getBounds?.left ?? 0, + backgroundColor: SettingsManager.userBackgroundColor, + color: SettingsManager.userColor, + }}> + <div style={{ position: 'absolute', top: 5, right: 5 }}> + <IconButton type={Type.TERT} onClick={this.cancelOutpaintPrompt} icon={<FontAwesomeIcon icon="times" color={'red'} />} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} /> + </div> + <div>Outpaint Image</div> + <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}> + <EditableText + placeholder="Enter a prompt for extending the image:" + setVal={val => this.handlePromptChange(val)} + val={this._outpaintPromptInput} + type={Type.TERT} + color={SettingsManager.userColor} + background={SettingsManager.userBackgroundColor} + /> + <div className="buttons" style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}> + <IconButton type={Type.TERT} onClick={this.submitOutpaintPrompt} icon={<AiOutlineSend />} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} /> + <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}> + {this.outpaintVertical ? null : ( + <Toggle + type={Type.TERT} + toggleType={ToggleType.BUTTON} + toggleStatus={this._outpaintAlign === 'left' && this._outpaintVAlign === ''} + onClick={action(() => (this._outpaintAlign = 'left'))} + icon={<FontAwesomeIcon icon="chevron-left" color={SnappingManager.userColor} />} + color={SnappingManager.userColor} + background={SnappingManager.userVariantColor} + /> + )} + <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}> + {!this.outpaintVertical ? null : ( + <Toggle + type={Type.TERT} + toggleType={ToggleType.BUTTON} + toggleStatus={this._outpaintAlign === '' && this._outpaintVAlign === 'top'} + onClick={action(() => (this._outpaintVAlign = 'top'))} + icon={<FontAwesomeIcon icon="chevron-up" color={SnappingManager.userColor} />} + color={SnappingManager.userColor} + background={SnappingManager.userColor} + /> + )} + <Toggle + type={Type.TERT} + toggleType={ToggleType.BUTTON} + toggleStatus={this._outpaintAlign === '' && this._outpaintVAlign === ''} + onClick={action(() => { + this._outpaintAlign = ''; + this._outpaintVAlign = ''; + })} + icon={<FontAwesomeIcon icon="bullseye" color={SnappingManager.userColor} />} + color={SnappingManager.userColor} + background={SnappingManager.userColor} + /> + {!this.outpaintVertical ? null : ( + <Toggle + type={Type.TERT} + toggleType={ToggleType.BUTTON} + toggleStatus={this._outpaintAlign === '' && this._outpaintVAlign === 'bottom'} + onClick={action(() => (this._outpaintVAlign = 'bottom'))} + icon={<FontAwesomeIcon icon="chevron-down" color={SnappingManager.userColor} />} + color={SnappingManager.userColor} + background={SnappingManager.userColor} + /> + )} + </div> + {this.outpaintVertical ? null : ( + <Toggle + type={Type.TERT} + toggleType={ToggleType.BUTTON} + toggleStatus={this._outpaintAlign === 'right' && this._outpaintVAlign === ''} + onClick={action(() => (this._outpaintAlign = 'right'))} + icon={<FontAwesomeIcon icon="chevron-right" color={SnappingManager.userColor} />} + color={SnappingManager.userColor} + background={SnappingManager.userColor} + /> + )} + </div> + </div> </div> </div> ); @@ -718,13 +804,25 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { })} key={this.layoutDoc[Id]} onPointerDown={this.marqueeDown}> - <div className="imageBox-fader" style={{ opacity: backAlpha }}> + <div + className="imageBox-fader" + style={{ + opacity: backAlpha, + flexDirection: this._outpaintVAlign ? 'row' : 'column', + alignItems: this._outpaintAlign === 'left' || this._outpaintVAlign === 'top' ? 'flex-start' : this._outpaintAlign === 'right' || this._outpaintVAlign === 'bottom' ? 'flex-end' : undefined, + }}> <img alt="" ref={action((r: HTMLImageElement | null) => (this.imageRef = r))} key="paths" src={srcpath} - style={{ transform, transformOrigin, height: this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined ? '100%' : undefined }} + style={{ + position: 'relative', + transform, + transformOrigin, + width: this._outpaintAlign ? 'max-content' : this._outpaintAlign ? '100%' : undefined, + height: this._outpaintVAlign ? 'max-content' : 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/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index bebd86ac2..df505eb16 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -26,7 +26,6 @@ type CalendarView = 'multiMonth' | 'dayGridMonth' | 'timeGridWeek' | 'timeGridDa export class CalendarBox extends CollectionSubView() { _calendarRef: HTMLDivElement | null = null; _calendar: Calendar | undefined; - _oldWheel: HTMLElement | null = null; _observer: ResizeObserver | undefined; _eventsDisposer: IReactionDisposer | undefined; _selectDisposer: IReactionDisposer | undefined; @@ -251,7 +250,6 @@ export class CalendarBox extends CollectionSubView() { setTimeout(() => cal?.view.calendar.select(this.dateSelect.start, this.dateSelect.end)); }; - onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); render() { return ( <div @@ -275,10 +273,7 @@ export class CalendarBox extends CollectionSubView() { }} ref={r => { this.createDashEventsTarget(r); - this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); - this._oldWheel = r; - // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling - r?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + this.fixWheelEvents(r, this._props.isContentActive); if (r) { this._observer?.disconnect(); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index d6fa3172d..c51f6c38b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -2120,11 +2120,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB ) : styleFromLayout?.height === '0px' ? null : ( <div className="formattedTextBox" - ref={r => { - this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); - this._oldWheel = r; - r?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); - }} + ref={r => this.fixWheelEvents(r, this._props.isContentActive, this.onPassiveWheel)} style={{ ...(this._props.dontScale ? {} diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index 1b8a85a5c..e934e635b 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -343,14 +343,14 @@ export default class FireflyManager extends ApiManager { numVariations: 1, placement: { inset: { - 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), + left: 0, // Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2), + top: 0, // Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2), + right: 0, // Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2), + bottom: 0, // Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2), }, alignment: { - horizontal: 'center', - vertical: 'center', + horizontal: req.body.halignment || 'center', + vertical: req.body.valignment || 'center', }, }, }), |