All checks were successful
Queue Release Build / prepare (push) Successful in 1m6s
Deploy Web Apps / deploy (push) Successful in 7m35s
Queue Release Build / build-windows (push) Successful in 29m57s
Queue Release Build / build-linux (push) Successful in 46m28s
Queue Release Build / finalize (push) Successful in 49s
168 lines
4.7 KiB
TypeScript
168 lines
4.7 KiB
TypeScript
import {
|
|
ElementRef,
|
|
Injectable,
|
|
inject
|
|
} from '@angular/core';
|
|
import {
|
|
ConnectedPosition,
|
|
Overlay,
|
|
OverlayRef
|
|
} from '@angular/cdk/overlay';
|
|
import { ComponentPortal } from '@angular/cdk/portal';
|
|
import {
|
|
Subscription,
|
|
filter,
|
|
fromEvent
|
|
} from 'rxjs';
|
|
import { PluginActionMenuComponent } from './plugin-action-menu.component';
|
|
import { ViewportService } from '../../../../core/platform';
|
|
|
|
const GAP = 10;
|
|
const VIEWPORT_MARGIN = 8;
|
|
const POSITIONS: ConnectedPosition[] = [
|
|
{ originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top', offsetX: GAP },
|
|
{ originX: 'end', originY: 'bottom', overlayX: 'start', overlayY: 'bottom', offsetX: GAP },
|
|
{ originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top', offsetX: -GAP },
|
|
{ originX: 'start', originY: 'bottom', overlayX: 'end', overlayY: 'bottom', offsetX: -GAP }
|
|
];
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
export class PluginActionMenuService {
|
|
private readonly overlay = inject(Overlay);
|
|
private readonly viewport = inject(ViewportService);
|
|
private currentOrigin: HTMLElement | null = null;
|
|
private overlayRef: OverlayRef | null = null;
|
|
private overlaySubscriptions: Subscription | null = null;
|
|
private scrollBlocker: (() => void) | null = null;
|
|
|
|
open(origin: ElementRef | HTMLElement): void {
|
|
const rawEl = origin instanceof ElementRef ? origin.nativeElement : origin;
|
|
|
|
if (this.overlayRef) {
|
|
const sameOrigin = rawEl === this.currentOrigin;
|
|
|
|
this.close();
|
|
|
|
if (sameOrigin) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const elementRef = origin instanceof ElementRef ? origin : new ElementRef(origin);
|
|
const isMobile = this.viewport.isMobile();
|
|
|
|
this.currentOrigin = rawEl;
|
|
|
|
if (isMobile) {
|
|
const positionStrategy = this.overlay
|
|
.position()
|
|
.global()
|
|
.left('0')
|
|
.right('0')
|
|
.bottom('0');
|
|
|
|
this.overlayRef = this.overlay.create({
|
|
positionStrategy,
|
|
scrollStrategy: this.overlay.scrollStrategies.block(),
|
|
hasBackdrop: true,
|
|
backdropClass: 'cdk-overlay-dark-backdrop',
|
|
panelClass: 'metoyou-bottom-sheet-panel'
|
|
});
|
|
} else {
|
|
const positionStrategy = this.overlay
|
|
.position()
|
|
.flexibleConnectedTo(elementRef)
|
|
.withPositions(POSITIONS)
|
|
.withViewportMargin(VIEWPORT_MARGIN)
|
|
.withPush(true);
|
|
|
|
this.overlayRef = this.overlay.create({
|
|
positionStrategy,
|
|
scrollStrategy: this.overlay.scrollStrategies.noop()
|
|
});
|
|
}
|
|
|
|
this.syncThemeVars();
|
|
|
|
const componentRef = this.overlayRef.attach(new ComponentPortal(PluginActionMenuComponent));
|
|
const subscriptions = new Subscription();
|
|
|
|
subscriptions.add(componentRef.instance.closed.subscribe(() => this.close()));
|
|
|
|
if (isMobile) {
|
|
subscriptions.add(this.overlayRef.backdropClick().subscribe(() => this.close()));
|
|
this.overlaySubscriptions = subscriptions;
|
|
|
|
return;
|
|
}
|
|
|
|
subscriptions.add(fromEvent<PointerEvent>(document, 'pointerdown')
|
|
.pipe(
|
|
filter((event) => {
|
|
const target = event.target as Node;
|
|
|
|
if (this.overlayRef?.overlayElement.contains(target)) {
|
|
return false;
|
|
}
|
|
|
|
if (this.currentOrigin?.contains(target)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
})
|
|
)
|
|
.subscribe(() => this.close()));
|
|
|
|
this.overlaySubscriptions = subscriptions;
|
|
this.blockScroll();
|
|
}
|
|
|
|
close(): void {
|
|
this.scrollBlocker?.();
|
|
this.scrollBlocker = null;
|
|
this.overlaySubscriptions?.unsubscribe();
|
|
this.overlaySubscriptions = null;
|
|
|
|
if (this.overlayRef) {
|
|
this.overlayRef.dispose();
|
|
this.overlayRef = null;
|
|
this.currentOrigin = null;
|
|
}
|
|
}
|
|
|
|
private blockScroll(): void {
|
|
const handler = (event: Event): void => {
|
|
if (this.overlayRef?.overlayElement.contains(event.target as Node)) {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
};
|
|
const opts: AddEventListenerOptions = { passive: false, capture: true };
|
|
|
|
document.addEventListener('wheel', handler, opts);
|
|
document.addEventListener('touchmove', handler, opts);
|
|
|
|
this.scrollBlocker = () => {
|
|
document.removeEventListener('wheel', handler, opts);
|
|
document.removeEventListener('touchmove', handler, opts);
|
|
};
|
|
}
|
|
|
|
private syncThemeVars(): void {
|
|
const appRoot = document.querySelector<HTMLElement>('[data-theme-key="appRoot"]');
|
|
const container = document.querySelector<HTMLElement>('.cdk-overlay-container');
|
|
|
|
if (!appRoot || !container) {
|
|
return;
|
|
}
|
|
|
|
for (const prop of Array.from(appRoot.style)) {
|
|
if (prop.startsWith('--')) {
|
|
container.style.setProperty(prop, appRoot.style.getPropertyValue(prop));
|
|
}
|
|
}
|
|
}
|
|
}
|