mirror of
https://github.com/koloml/furbooru-tagging-assistant.git
synced 2025-12-24 15:12:58 +00:00
More comfortable simple components system
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings.js";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile.js";
|
||||
import {maintenanceToolsEvents} from "$lib/components/MaintenanceTools.js";
|
||||
import {BaseComponent} from "$lib/components/base/BaseComponent.js";
|
||||
|
||||
export class MaintenancePopup {
|
||||
/** @type {HTMLElement} */
|
||||
#container;
|
||||
|
||||
export class MaintenancePopup extends BaseComponent {
|
||||
/** @type {HTMLElement} */
|
||||
#tagsListElement;
|
||||
|
||||
@@ -14,33 +13,46 @@ export class MaintenancePopup {
|
||||
/** @type {MaintenanceProfile|null} */
|
||||
#activeProfile = null;
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} container
|
||||
*/
|
||||
constructor(container) {
|
||||
this.#container = container;
|
||||
}
|
||||
/** @type {import('MaintenanceTools.js').MaintenanceTools|null} */
|
||||
#parentTools = null;
|
||||
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
build() {
|
||||
this.#container.innerHTML = '';
|
||||
this.#container.classList.add('maintenance-popup');
|
||||
this.container.innerHTML = '';
|
||||
this.container.classList.add('maintenance-popup');
|
||||
|
||||
this.#tagsListElement = document.createElement('div');
|
||||
this.#tagsListElement.classList.add('tags-list');
|
||||
|
||||
this.#container.append(
|
||||
this.container.append(
|
||||
this.#tagsListElement,
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
init() {
|
||||
MaintenancePopup.#watchActiveProfile(activeProfile => {
|
||||
this.#activeProfile = activeProfile;
|
||||
this.#container.classList.toggle('is-active', activeProfile !== null);
|
||||
this.#refreshTagsList();
|
||||
});
|
||||
this.once(maintenanceToolsEvents.init, this.#onToolsContainerInitialized.bind(this));
|
||||
MaintenancePopup.#watchActiveProfile(this.#onActiveProfileChanged.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('MaintenanceTools.js').MaintenanceTools} toolsInstance
|
||||
*/
|
||||
#onToolsContainerInitialized(toolsInstance) {
|
||||
this.#parentTools = toolsInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MaintenanceProfile|null} activeProfile
|
||||
*/
|
||||
#onActiveProfileChanged(activeProfile) {
|
||||
this.#activeProfile = activeProfile;
|
||||
this.container.classList.toggle('is-active', activeProfile !== null);
|
||||
this.#refreshTagsList();
|
||||
}
|
||||
|
||||
#refreshTagsList() {
|
||||
@@ -62,6 +74,13 @@ export class MaintenancePopup {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isActive() {
|
||||
return this.container.classList.contains('is-active');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} tagName
|
||||
* @return {HTMLElement}
|
||||
@@ -70,6 +89,7 @@ export class MaintenancePopup {
|
||||
const tagElement = document.createElement('span');
|
||||
tagElement.classList.add('tag');
|
||||
tagElement.innerText = tagName;
|
||||
tagElement.dataset.name = tagName;
|
||||
|
||||
return tagElement;
|
||||
}
|
||||
@@ -124,9 +144,7 @@ export class MaintenancePopup {
|
||||
export function createMaintenancePopup() {
|
||||
const container = document.createElement('div');
|
||||
|
||||
new MaintenancePopup(container)
|
||||
.build()
|
||||
.init();
|
||||
new MaintenancePopup(container);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
54
src/lib/components/MaintenanceTools.js
Normal file
54
src/lib/components/MaintenanceTools.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import {BaseComponent} from "$lib/components/base/BaseComponent.js";
|
||||
import {getComponent} from "$lib/components/base/ComponentUtils.js";
|
||||
import {MaintenancePopup} from "$lib/components/MaintenancePopup.js";
|
||||
|
||||
export const maintenanceToolsEvents = {
|
||||
init: 'maintenance-tools-init'
|
||||
}
|
||||
|
||||
export class MaintenanceTools extends BaseComponent {
|
||||
/** @type {MaintenancePopup|null} */
|
||||
#maintenancePopup = null;
|
||||
|
||||
init() {
|
||||
for (let childElement of this.container.children) {
|
||||
const component = getComponent(childElement);
|
||||
|
||||
if (!component) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.#maintenancePopup && component instanceof MaintenancePopup) {
|
||||
this.#maintenancePopup = component;
|
||||
}
|
||||
|
||||
component.emit(maintenanceToolsEvents.init, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {MaintenancePopup|null}
|
||||
*/
|
||||
get maintenancePopup() {
|
||||
return this.#maintenancePopup;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a maintenance popup element.
|
||||
* @param {HTMLElement} childrenElements List of children elements to append to the component.
|
||||
* @return {HTMLElement} The maintenance popup element.
|
||||
*/
|
||||
export function createMaintenanceTools(...childrenElements) {
|
||||
const mediaBoxToolsContainer = document.createElement('div');
|
||||
mediaBoxToolsContainer.classList.add('media-box-tools');
|
||||
|
||||
if (childrenElements.length) {
|
||||
mediaBoxToolsContainer.append(...childrenElements);
|
||||
}
|
||||
|
||||
new MaintenanceTools(mediaBoxToolsContainer)
|
||||
.init();
|
||||
|
||||
return mediaBoxToolsContainer;
|
||||
}
|
||||
79
src/lib/components/base/BaseComponent.js
Normal file
79
src/lib/components/base/BaseComponent.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import {bindComponent} from "$lib/components/base/ComponentUtils.js";
|
||||
|
||||
/**
|
||||
* @abstract
|
||||
*/
|
||||
export class BaseComponent {
|
||||
/** @type {HTMLElement} */
|
||||
#container;
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} container
|
||||
*/
|
||||
constructor(container) {
|
||||
this.#container = container;
|
||||
|
||||
bindComponent(container, this);
|
||||
|
||||
this.build();
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
build() {
|
||||
// This method can be implemented by the component classes to modify or create the inner elements.
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
init() {
|
||||
// This method can be implemented by the component classes to initialize the component.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {HTMLElement}
|
||||
* @protected
|
||||
*/
|
||||
get container() {
|
||||
return this.#container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit the custom event on the container element.
|
||||
* @param {keyof HTMLElementEventMap|string} event The event name.
|
||||
* @param {any} [detail] The event detail. Can be omitted.
|
||||
*/
|
||||
emit(event, detail = undefined) {
|
||||
this.#container.dispatchEvent(new CustomEvent(event, {detail}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to the DOM event on the container element.
|
||||
* @param {keyof HTMLElementEventMap|string} event The event name.
|
||||
* @param {function(Event): void} listener The event listener.
|
||||
* @param {AddEventListenerOptions|undefined} [options] The event listener options. Can be omitted.
|
||||
* @return {function(): void} The unsubscribe function.
|
||||
*/
|
||||
on(event, listener, options = undefined) {
|
||||
this.#container.addEventListener(event, listener, options);
|
||||
|
||||
return () => void this.#container.removeEventListener(event, listener, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to the DOM event on the container element. The event listener will be called only once.
|
||||
* @param {keyof HTMLElementEventMap|string} event The event name.
|
||||
* @param {function(Event): void} listener The event listener.
|
||||
* @param {AddEventListenerOptions|undefined} [options] The event listener options. Can be omitted.
|
||||
* @return {function(): void} The unsubscribe function.
|
||||
*/
|
||||
once(event, listener, options = undefined) {
|
||||
options = options || {};
|
||||
options.once = true;
|
||||
|
||||
return this.on(event, listener, options);
|
||||
}
|
||||
}
|
||||
22
src/lib/components/base/ComponentUtils.js
Normal file
22
src/lib/components/base/ComponentUtils.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const instanceSymbol = Symbol('instance');
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} element
|
||||
* @return {import('./BaseComponent.js').BaseComponent|null}
|
||||
*/
|
||||
export function getComponent(element) {
|
||||
return element[instanceSymbol] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the component to the selected element.
|
||||
* @param {HTMLElement} element The element to bind the component to.
|
||||
* @param {import('./BaseComponent.js').BaseComponent} instance The component instance.
|
||||
*/
|
||||
export function bindComponent(element, instance) {
|
||||
if (element[instanceSymbol]) {
|
||||
throw new Error('The element is already bound to a component.');
|
||||
}
|
||||
|
||||
element[instanceSymbol] = instance;
|
||||
}
|
||||
Reference in New Issue
Block a user