diff --git a/.gitignore b/.gitignore index 89ab2665af..1817c96c5d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ licenses.json /darwin-amd*/ /darwin-arm*/ /windows/ +.aider* +.env diff --git a/src/scss/_mixins.scss b/src/scss/_mixins.scss index eaae5439ef..21deea1434 100644 --- a/src/scss/_mixins.scss +++ b/src/scss/_mixins.scss @@ -1,6 +1,5 @@ $easeInQuint: cubic-bezier(0.22, 1, 0.36, 1); $transitionCommon: 0.1s $easeInQuint; -$transitionSmoothCommon: 0.35s $easeInQuint; $transitionAllCommon: all $transitionCommon; $transitionSidebarTime: 0.18s; diff --git a/src/scss/component/common.scss b/src/scss/component/common.scss index 0f179ea555..ab60c1f748 100644 --- a/src/scss/component/common.scss +++ b/src/scss/component/common.scss @@ -6,6 +6,7 @@ @import "./editor"; @import "./emptySearch"; @import "./error"; +@import "./floater"; @import "./footer"; @import "./frame"; @import "./header"; diff --git a/src/scss/component/floater.scss b/src/scss/component/floater.scss new file mode 100644 index 0000000000..c9d42eea76 --- /dev/null +++ b/src/scss/component/floater.scss @@ -0,0 +1 @@ +.floater { position: absolute; pointer-events: none; z-index: 200; } \ No newline at end of file diff --git a/src/scss/component/media/audio.scss b/src/scss/component/media/audio.scss index 6064ec21a8..5c11be63a5 100644 --- a/src/scss/component/media/audio.scss +++ b/src/scss/component/media/audio.scss @@ -52,5 +52,5 @@ } } -.volume.input-drag-vertical { opacity: 0; transition: opacity $transitionSmoothCommon; pointer-events: none; } +.volume.input-drag-vertical { opacity: 0; transition: opacity 0.35s ease-in-out; pointer-events: none; } .volume.input-drag-vertical.visible { opacity: 1; pointer-events: auto; } diff --git a/src/scss/form/common.scss b/src/scss/form/common.scss index debdbb0afd..1d717c1b20 100644 --- a/src/scss/form/common.scss +++ b/src/scss/form/common.scss @@ -1,6 +1,6 @@ @import "./button"; -@import "./dragHorizontal"; -@import "./dragVertical"; +@import "./drag/horizontal"; +@import "./drag/vertical"; @import "./filter"; @import "./input"; @import "./inputWithFile"; diff --git a/src/scss/form/dragHorizontal.scss b/src/scss/form/drag/horizontal.scss similarity index 100% rename from src/scss/form/dragHorizontal.scss rename to src/scss/form/drag/horizontal.scss diff --git a/src/scss/form/dragVertical.scss b/src/scss/form/drag/vertical.scss similarity index 100% rename from src/scss/form/dragVertical.scss rename to src/scss/form/drag/vertical.scss diff --git a/src/ts/component/form/dragHorizontal.tsx b/src/ts/component/form/drag/horizontal.tsx similarity index 100% rename from src/ts/component/form/dragHorizontal.tsx rename to src/ts/component/form/drag/horizontal.tsx diff --git a/src/ts/component/form/dragVertical.tsx b/src/ts/component/form/drag/vertical.tsx similarity index 97% rename from src/ts/component/form/dragVertical.tsx rename to src/ts/component/form/drag/vertical.tsx index 7d3ca98673..a8fe9bb58e 100644 --- a/src/ts/component/form/dragVertical.tsx +++ b/src/ts/component/form/drag/vertical.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useEffect, useImperativeHandle, useRef } from 'react'; -import Input from './input'; +import { Input } from 'Component'; interface Props { id?: string; diff --git a/src/ts/component/index.tsx b/src/ts/component/index.tsx index 168493a711..5935ea0fcc 100644 --- a/src/ts/component/index.tsx +++ b/src/ts/component/index.tsx @@ -51,8 +51,8 @@ import Checkbox from './form/checkbox'; import Textarea from './form/textarea'; import Button from './form/button'; import Select from './form/select'; -import DragHorizontal from './form/dragHorizontal'; -import DragVertical from './form/dragVertical'; +import DragHorizontal from './form/drag/horizontal'; +import DragVertical from './form/drag/vertical'; import Pin from './form/pin'; import Filter from './form/filter'; import Phrase from './form/phrase'; diff --git a/src/ts/component/util/floater.tsx b/src/ts/component/util/floater.tsx index 0282479178..302d765613 100644 --- a/src/ts/component/util/floater.tsx +++ b/src/ts/component/util/floater.tsx @@ -1,22 +1,28 @@ +import { H } from 'Lib'; import React, { useState, useEffect, ReactNode, useRef } from 'react'; import ReactDOM from 'react-dom'; -import { useElementMovement } from './useMovementObserver'; + interface Props { children: ReactNode; anchorEl: HTMLElement | null; - anchorTo?: 'top' | 'bottom'; + anchorTo?: AnchorTo; offset?: { - x?: number; - y?: number; + left?: number; + top?: number; }; } +export enum AnchorTo { + Top = 'top', + Bottom = 'bottom', +} + export const Floater: React.FC = ({ children, anchorEl, - anchorTo = 'bottom', - offset = { x: 0, y: 0 }, + anchorTo = AnchorTo.Bottom, + offset = { top: 0, left: 0 }, }) => { const ref = useRef(null); const [ position, setPosition ] = useState({ top: 0, left: 0 }); @@ -24,44 +30,41 @@ export const Floater: React.FC = ({ const onMove = () => { if (anchorEl && ref.current) { const anchorElRect = anchorEl.getBoundingClientRect(); - const floaterElRect = ref.current.getBoundingClientRect(); + const elRect = ref.current.getBoundingClientRect(); - const { top, left, bottom, width } = anchorElRect; - const fh = floaterElRect.height; - const fw = floaterElRect.width; + const { top: at, left: al, bottom: ab, width: aw } = anchorElRect; + const eh = elRect.height; + const ew = elRect.width; - const x = Number(offset.x) || 0; - const y = Number(offset.y) || 0; + const ot = Number(offset.top) || 0; + const ol = Number(offset.left) || 0; - let pt = 0; - let pl = 0; + let nt = 0; + let nl = 0; - if (anchorTo == 'top') { - pt = top - fh + y; - pl = left + width / 2 - fw / 2 + x; - } else { - pt = bottom + y; - pl = left + width / 2 - fw / 2 + x; + switch (anchorTo) { + case AnchorTo.Top: + nt = at - eh + ot; + nl = al + aw / 2 - ew / 2 + ol; + break; + case AnchorTo.Bottom: + nt = ab + ot; + nl = al + aw / 2 - ew / 2 + ol; + break; }; - setPosition({ top: pt, left: pl }); + setPosition({ top: nt, left: nl }); }; }; - useElementMovement(anchorEl, onMove); + H.useElementMovement(anchorEl, onMove); useEffect(() => onMove(), [ anchorEl, ref.current ]); return ReactDOM.createPortal( -
+ style={{ transform: `translate3d(${position.top}px, ${-position.left}px, 0px)`}} + > {children}
, document.body diff --git a/src/ts/component/util/media/audio.tsx b/src/ts/component/util/media/audio.tsx index d8e66d47d5..1f40f97f20 100644 --- a/src/ts/component/util/media/audio.tsx +++ b/src/ts/component/util/media/audio.tsx @@ -1,9 +1,8 @@ import * as React from 'react'; import $ from 'jquery'; -import { Icon, DragHorizontal } from 'Component'; +import { Icon, DragHorizontal, DragVertical } from 'Component'; import { U } from 'Lib'; -import DragVertical from 'Component/form/dragVertical'; -import { Floater } from '../floater'; +import { AnchorTo, Floater } from '../floater'; import _ from 'lodash'; interface PlaylistItem { @@ -108,8 +107,8 @@ class MediaAudio extends React.Component { void; constructor(element: HTMLElement, callback: (position: Position) => void) { @@ -19,17 +22,31 @@ class ElementMovementObserver { this.onMove = callback; this.lastPosition = this.getPosition(); - // Create observer instance - this.observer = new MutationObserver(() => { + this.movementObserver = new MutationObserver(() => { + this.checkForMovement(); + }); + + this.resizeObserver = new ResizeObserver(() => { this.checkForMovement(); }); - // Start observing this.startObserving(); + + } - private getPosition(): Position { + private checkForMovement = () => { + const currentPosition = this.getPosition(); + + if (this.hasPositionChanged(currentPosition)) { + this.lastPosition = currentPosition; + this.onMove(currentPosition); + } + }; + + private getPosition (): Position { const rect = this.element.getBoundingClientRect(); + return { x: rect.left + window.scrollX, y: rect.top + window.scrollY, @@ -38,16 +55,8 @@ class ElementMovementObserver { }; } - private checkForMovement(): void { - const currentPosition = this.getPosition(); - if (this.hasPositionChanged(currentPosition)) { - this.lastPosition = currentPosition; - this.onMove(currentPosition); - } - } - - private hasPositionChanged(currentPosition: Position): boolean { + private hasPositionChanged (currentPosition: Position): boolean { return ( currentPosition.x !== this.lastPosition.x || currentPosition.y !== this.lastPosition.y || @@ -56,8 +65,7 @@ class ElementMovementObserver { ); } - private startObserving(): void { - // Configure observer options + private startObserving (): void { const config: MutationObserverInit = { attributes: true, childList: true, @@ -65,31 +73,27 @@ class ElementMovementObserver { characterData: true }; - // Start observing the element and its descendants - this.observer.observe(this.element, config); + // Observe the element and its descendants for movement + this.movementObserver.observe(this.element, config); // Observe the document body for layout changes - this.observer.observe(document.body, config); + this.movementObserver.observe(document.body, config); - const resizeObserver = new ResizeObserver(() => { - this.checkForMovement(); - }); - resizeObserver.observe(this.element); + // Observe the element for size changes + this.resizeObserver.observe(this.element); - window.addEventListener('scroll', () => { - this.checkForMovement(); - }); + // And handle scroll + window.addEventListener('scroll', this.checkForMovement); } - public disconnect(): void { - this.observer.disconnect(); + public disconnect (): void { + this.movementObserver.disconnect(); + this.resizeObserver.disconnect(); + window.removeEventListener('scroll', this.checkForMovement); } } -export function useElementMovement( - element: HTMLElement | null, - callback: (position: Position) => void -) { +export default function useElementMovement ( element: HTMLElement | null, callback: (position: Position) => void ) { useEffect(() => { if (!element) return; diff --git a/src/ts/lib/index.ts b/src/ts/lib/index.ts index 110492f404..a8eaac6fcb 100644 --- a/src/ts/lib/index.ts +++ b/src/ts/lib/index.ts @@ -4,6 +4,7 @@ import * as M from 'Model'; // Models import * as S from 'Store'; // Stores import * as U from './util'; // Utils import * as C from './api/command'; // Commands +import * as H from './hook'; // React Hooks import Renderer from './renderer'; import { dispatcher } from './api/dispatcher'; @@ -34,6 +35,7 @@ export { S, U, J, + H, keyboard, sidebar, focus,