From 2561cd19c9d99c86f4384cfeeccc42bd8af59a94 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Thu, 6 Feb 2025 15:04:32 +0400 Subject: [PATCH 1/2] Making container getter public --- src/lib/components/base/BaseComponent.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/components/base/BaseComponent.js b/src/lib/components/base/BaseComponent.js index 90838ff..233eaa4 100644 --- a/src/lib/components/base/BaseComponent.js +++ b/src/lib/components/base/BaseComponent.js @@ -45,7 +45,6 @@ export class BaseComponent { /** * @return {HTMLElement} - * @protected */ get container() { return this.#container; From 4ea0e11ec13ad3cefabbd12a023082977eebfaa4 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Thu, 6 Feb 2025 15:43:52 +0400 Subject: [PATCH 2/2] Implementing the unified functions for custom events with types --- src/lib/components/MaintenancePopup.js | 22 +++-- src/lib/components/MaintenanceStatusIcon.js | 4 +- src/lib/components/MediaBoxTools.js | 8 +- src/lib/components/MediaBoxWrapper.js | 6 +- src/lib/components/events/comms.ts | 92 +++++++++++++++++++ .../events/maintenance-popup-events.ts | 13 +++ 6 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 src/lib/components/events/comms.ts create mode 100644 src/lib/components/events/maintenance-popup-events.ts diff --git a/src/lib/components/MaintenancePopup.js b/src/lib/components/MaintenancePopup.js index ea3ab0f..1435d55 100644 --- a/src/lib/components/MaintenancePopup.js +++ b/src/lib/components/MaintenancePopup.js @@ -4,6 +4,12 @@ import {BaseComponent} from "$lib/components/base/BaseComponent.js"; import {getComponent} from "$lib/components/base/ComponentUtils.js"; import ScrapedAPI from "$lib/booru/scraped/ScrapedAPI.js"; import {tagsBlacklist} from "$config/tags.ts"; +import {emitterAt} from "$lib/components/events/comms"; +import { + eventActiveProfileChanged, + eventMaintenanceStateChanged, + eventTagsUpdated +} from "$lib/components/events/maintenance-popup-events"; class BlackListedTagsEncounteredError extends Error { /** @@ -45,6 +51,8 @@ export class MaintenancePopup extends BaseComponent { /** @type {number|null} */ #tagsSubmissionTimer = null; + #emitter = emitterAt(this); + /** * @protected */ @@ -95,7 +103,8 @@ export class MaintenancePopup extends BaseComponent { this.#activeProfile = activeProfile; this.container.classList.toggle('is-active', activeProfile !== null); this.#refreshTagsList(); - this.emit('active-profile-changed', activeProfile); + + this.#emitter.emit(eventActiveProfileChanged, activeProfile); } #refreshTagsList() { @@ -181,7 +190,7 @@ export class MaintenancePopup extends BaseComponent { } this.#isPlanningToSubmit = true; - this.emit('maintenance-state-change', 'waiting'); + this.#emitter.emit(eventMaintenanceStateChanged, 'waiting'); } } @@ -208,7 +217,7 @@ export class MaintenancePopup extends BaseComponent { this.#isPlanningToSubmit = false; this.#isSubmitting = true; - this.emit('maintenance-state-change', 'processing'); + this.#emitter.emit(eventMaintenanceStateChanged, 'processing'); let maybeTagsAndAliasesAfterUpdate; @@ -249,17 +258,18 @@ export class MaintenancePopup extends BaseComponent { } MaintenancePopup.#notifyAboutPendingSubmission(false); - this.emit('maintenance-state-change', 'failed'); + + this.#emitter.emit(eventMaintenanceStateChanged, 'failed'); this.#isSubmitting = false; return; } if (maybeTagsAndAliasesAfterUpdate) { - this.emit('tags-updated', maybeTagsAndAliasesAfterUpdate); + this.#emitter.emit(eventTagsUpdated, maybeTagsAndAliasesAfterUpdate); } - this.emit('maintenance-state-change', 'complete'); + this.#emitter.emit(eventMaintenanceStateChanged, 'complete'); this.#tagsToAdd.clear(); this.#tagsToRemove.clear(); diff --git a/src/lib/components/MaintenanceStatusIcon.js b/src/lib/components/MaintenanceStatusIcon.js index 9f7737e..680a937 100644 --- a/src/lib/components/MaintenanceStatusIcon.js +++ b/src/lib/components/MaintenanceStatusIcon.js @@ -1,5 +1,7 @@ import {BaseComponent} from "$lib/components/base/BaseComponent.js"; import {getComponent} from "$lib/components/base/ComponentUtils.js"; +import {on} from "$lib/components/events/comms"; +import {eventMaintenanceStateChanged} from "$lib/components/events/maintenance-popup-events"; export class MaintenanceStatusIcon extends BaseComponent { /** @type {import('MediaBoxTools.js').MediaBoxTools} */ @@ -16,7 +18,7 @@ export class MaintenanceStatusIcon extends BaseComponent { throw new Error('Status icon element initialized outside of the media box!'); } - this.#mediaBoxTools.on('maintenance-state-change', this.#onMaintenanceStateChanged.bind(this)); + on(this.#mediaBoxTools, eventMaintenanceStateChanged, this.#onMaintenanceStateChanged.bind(this)); } /** diff --git a/src/lib/components/MediaBoxTools.js b/src/lib/components/MediaBoxTools.js index 53430b0..a4c4477 100644 --- a/src/lib/components/MediaBoxTools.js +++ b/src/lib/components/MediaBoxTools.js @@ -1,6 +1,8 @@ import {BaseComponent} from "$lib/components/base/BaseComponent.js"; import {getComponent} from "$lib/components/base/ComponentUtils.js"; import {MaintenancePopup} from "$lib/components/MaintenancePopup.js"; +import {on} from "$lib/components/events/comms"; +import {eventActiveProfileChanged} from "$lib/components/events/maintenance-popup-events"; export class MediaBoxTools extends BaseComponent { /** @type {import('MediaBoxWrapper.js').MediaBoxWrapper|null} */ @@ -34,11 +36,11 @@ export class MediaBoxTools extends BaseComponent { } } - this.on('active-profile-changed', this.#onActiveProfileChanged.bind(this)); + on(this, eventActiveProfileChanged, this.#onActiveProfileChanged.bind(this)); } /** - * @param {CustomEvent} profileChangedEvent + * @param {CustomEvent} profileChangedEvent */ #onActiveProfileChanged(profileChangedEvent) { this.container.classList.toggle('has-active-profile', profileChangedEvent.detail !== null); @@ -61,7 +63,7 @@ export class MediaBoxTools extends BaseComponent { /** * Create a maintenance popup element. - * @param {HTMLElement} childrenElements List of children elements to append to the component. + * @param {HTMLElement[]} childrenElements List of children elements to append to the component. * @return {HTMLElement} The maintenance popup element. */ export function createMediaBoxTools(...childrenElements) { diff --git a/src/lib/components/MediaBoxWrapper.js b/src/lib/components/MediaBoxWrapper.js index 5fb1a3f..4cc41e6 100644 --- a/src/lib/components/MediaBoxWrapper.js +++ b/src/lib/components/MediaBoxWrapper.js @@ -1,6 +1,8 @@ import {BaseComponent} from "$lib/components/base/BaseComponent.js"; import {getComponent} from "$lib/components/base/ComponentUtils.js"; import {buildTagsAndAliasesMap} from "$lib/booru/TagsUtils.js"; +import {on} from "$lib/components/events/comms"; +import {eventTagsUpdated} from "$lib/components/events/maintenance-popup-events"; export class MediaBoxWrapper extends BaseComponent { #thumbnailContainer = null; @@ -13,11 +15,11 @@ export class MediaBoxWrapper extends BaseComponent { this.#thumbnailContainer = this.container.querySelector('.image-container'); this.#imageLinkElement = this.#thumbnailContainer.querySelector('a'); - this.on('tags-updated', this.#onTagsUpdatedRefreshTagsAndAliases.bind(this)); + on(this, eventTagsUpdated, this.#onTagsUpdatedRefreshTagsAndAliases.bind(this)); } /** - * @param {CustomEvent>} tagsUpdatedEvent + * @param {CustomEvent|null>} tagsUpdatedEvent */ #onTagsUpdatedRefreshTagsAndAliases(tagsUpdatedEvent) { const updatedMap = tagsUpdatedEvent.detail; diff --git a/src/lib/components/events/comms.ts b/src/lib/components/events/comms.ts new file mode 100644 index 0000000..a45f40a --- /dev/null +++ b/src/lib/components/events/comms.ts @@ -0,0 +1,92 @@ +import type {MaintenancePopupEventsMap} from "$lib/components/events/maintenance-popup-events.ts"; +import {BaseComponent} from "$lib/components/base/BaseComponent"; + +interface EventsMapping extends MaintenancePopupEventsMap { +} + +type EventCallback = (event: CustomEvent) => void; +type UnsubscribeFunction = () => void; +type ResolvableTarget = EventTarget | BaseComponent; + +function resolveTarget(componentOrElement: ResolvableTarget): EventTarget { + if (componentOrElement instanceof BaseComponent) { + return componentOrElement.container; + } + + return componentOrElement; +} + +export function emit( + targetOrComponent: ResolvableTarget, + event: Event, + details: EventsMapping[Event] +) { + const target = resolveTarget(targetOrComponent); + + target.dispatchEvent( + new CustomEvent(event, { + detail: details, + bubbles: true + }) + ); +} + +export function on( + targetOrComponent: ResolvableTarget, + eventName: Event, + callback: EventCallback, + options: AddEventListenerOptions | null = null +): UnsubscribeFunction { + const target = resolveTarget(targetOrComponent); + const controller = new AbortController(); + + target.addEventListener( + eventName, + callback as EventListener, + { + signal: controller.signal, + once: options?.once + } + ); + + return () => controller.abort(); +} + +const onceOptions = {once: true}; + +export function once( + targetOrComponent: ResolvableTarget, + eventName: Event, + callback: EventCallback +): UnsubscribeFunction { + return on( + targetOrComponent, + eventName, + callback, + onceOptions + ); +} + +class TargetedEmitter { + readonly #element: ResolvableTarget; + + constructor(targetOrComponent: ResolvableTarget) { + this.#element = targetOrComponent; + } + + emit(eventName: Event, details: EventsMapping[Event]): void { + emit(this.#element, eventName, details); + } + + on(eventName: Event, callback: EventCallback, options: AddEventListenerOptions | null = null): UnsubscribeFunction { + return on(this.#element, eventName, callback, options); + } + + once(eventName: Event, callback: EventCallback): UnsubscribeFunction { + return once(this.#element, eventName, callback); + } +} + +export function emitterAt(targetOrComponent: ResolvableTarget): TargetedEmitter { + return new TargetedEmitter(targetOrComponent); +} diff --git a/src/lib/components/events/maintenance-popup-events.ts b/src/lib/components/events/maintenance-popup-events.ts new file mode 100644 index 0000000..8ecea44 --- /dev/null +++ b/src/lib/components/events/maintenance-popup-events.ts @@ -0,0 +1,13 @@ +import type MaintenanceProfile from "$entities/MaintenanceProfile.ts"; + +export const eventActiveProfileChanged = 'active-profile-changed'; +export const eventMaintenanceStateChanged = 'maintenance-state-change'; +export const eventTagsUpdated = 'tags-updated'; + +type MaintenanceState = 'processing' | 'failed' | 'complete' | 'waiting'; + +export interface MaintenancePopupEventsMap { + [eventActiveProfileChanged]: MaintenanceProfile | null; + [eventMaintenanceStateChanged]: MaintenanceState; + [eventTagsUpdated]: Map | null; +}