1
0
mirror of https://github.com/koloml/furbooru-tagging-assistant.git synced 2025-12-23 23:02:58 +00:00

More comfortable simple components system

This commit is contained in:
2024-03-31 21:28:29 +04:00
parent d51beccd3b
commit c22023775a
5 changed files with 202 additions and 24 deletions

View File

@@ -1,5 +1,10 @@
import {createMaintenancePopup} from "$lib/components/MaintenancePopup.js";
import {createMaintenanceTools} from "$lib/components/MaintenanceTools.js";
document.querySelectorAll('.media-box').forEach(mediaBoxElement => {
mediaBoxElement.appendChild(createMaintenancePopup());
mediaBoxElement.appendChild(
createMaintenanceTools(
createMaintenancePopup()
)
);
});

View File

@@ -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;
}

View 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;
}

View 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);
}
}

View 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;
}