RSS Git Download  Clone
Raw Blame History 4kB 151 lines
import {
    Injectable,
    NgZone,
    OnDestroy,
    signal,
    Signal,
} from '@angular/core';

import { log as defaultLog } from '../util/log';

const log = defaultLog.factory('media query');

export enum MediaQuerySettingType {
    Width,
}

let mediaQuerySettingId = 0;

export interface MediaQuerySetting {
    name: string;
    min: number;
    max: number;
    type: MediaQuerySettingType;
    _id: number;
}

@Injectable()
export class MediaQueryService implements OnDestroy {

    settings: MediaQuerySetting[] = [];

    lastResult: MediaQuerySetting[] = [];

    width: number;
    height: number;

    private readonly _state = signal<MediaQuerySetting[]>([]);
    /** Signal-native state. Read inside an `effect()` to react. */
    public readonly state: Signal<MediaQuerySetting[]> = this._state.asReadonly();

    onResize: EventListener;

    debounce: any;

    constructor(
        private ngZone: NgZone,
    ) {
        this.register([
            <MediaQuerySetting>{
                name: 'small',
                min: 0,
                max: 599,
                type: MediaQuerySettingType.Width,
            },
            <MediaQuerySetting>{
                name: 'large',
                min: 600,
                max: Infinity,
                type: MediaQuerySettingType.Width,
            },
        ]);

        const debounceTime = 500;
        this.onResize = () => {
            clearTimeout(this.debounce);
            this.debounce = setTimeout(() => {
                this.ngZone.run(() => {
                    if (typeof window !== 'undefined') {
                        this.width = window.innerWidth;
                        this.height = window.innerHeight;
                    }
                    this.findMediaQuery();
                });
            }, debounceTime);
        };

        if (typeof window !== 'undefined') {
            this.debounce = setTimeout(() => {
                this.onResize(null);
            }, debounceTime);

            window.addEventListener('resize', this.onResize);
        }
    }

    public register(settings: MediaQuerySetting[]) {
        const unregisterIds: Array<number> = [];
        for (let setting of settings) {
            let found = false;
            for (let useSetting of this.settings) {
                if (useSetting.name === setting.name && useSetting.type === setting.type) {
                    found = true;
                    console.warn(`corifeus-web media-query service has duplicate settings`);
                    break;
                }
            }
            if (found === false) {
                mediaQuerySettingId++;
                unregisterIds.push(mediaQuerySettingId);
                setting._id = mediaQuerySettingId;
                this.settings.push(setting);
            }
        }
        this.findMediaQuery();

        const self = this;
        return ((unregisterIds: Array<number>) => {
            return function () {
                const newSettings: MediaQuerySetting[] = [];
                for (let setting of self.settings) {
                    let keep = true;
                    for (let unregisterId of unregisterIds) {
                        if (setting._id === unregisterId) {
                            keep = false;
                            break;
                        }
                    }
                    if (keep) newSettings.push(setting);
                }
                self.settings = newSettings;
            };
        })(unregisterIds);
    }

    private findMediaQuery() {
        const results: MediaQuerySetting[] = [];
        this.settings.forEach((setting) => {
            switch (setting.type) {
                case MediaQuerySettingType.Width: {
                    const minFound = this.width >= setting.min;
                    const maxFound = this.width <= setting.max;
                    if (minFound && maxFound) results.push(setting);
                    break;
                }
            }
        });

        if (JSON.stringify(results) !== JSON.stringify(this.lastResult)) {
            this.lastResult = results;
            this._state.set(results);
        }
    }

    ngOnDestroy() {
        if (typeof window !== 'undefined') {
            window.removeEventListener('resize', this.onResize);
        }
        clearTimeout(this.debounce);
    }
}