diff --git a/.eslintrc.yml b/.eslintrc.yml index 2b8a75cf..d57eb233 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -86,9 +86,6 @@ rules: arrow-parens: - error - always - no-underscore-dangle: - - error - - allowAfterThis: true no-multiple-empty-lines: - error - max: 1 diff --git a/source/scripts/libs/components/loader/loader.js b/source/scripts/libs/components/loader/loader.js index 79e4deb6..b203af35 100644 --- a/source/scripts/libs/components/loader/loader.js +++ b/source/scripts/libs/components/loader/loader.js @@ -1,28 +1,33 @@ import { getLoaderElement } from './libs/helpers/helpers.js' class Loader { + /** @type {HTMLElement} */ + #containerNode + + /** @type {HTMLElement | undefined} */ + #loaderNode + /** * @param {{ * containerNode: HTMLElement * }} constructor */ constructor({ containerNode }) { - this._containerNode = containerNode + this.#containerNode = containerNode - /** @type {HTMLElement | undefined} */ - this._loaderNode = undefined + this.#loaderNode = undefined } /** @returns {void} */ init() { - this._loaderNode = getLoaderElement() + this.#loaderNode = getLoaderElement() - this._containerNode.prepend(this._loaderNode) + this.#containerNode.prepend(this.#loaderNode) } /** @returns {void} */ remove() { - let loaderNode = /** @type {HTMLElement} */ (this._loaderNode) + let loaderNode = /** @type {HTMLElement} */ (this.#loaderNode) loaderNode.remove() } diff --git a/source/scripts/libs/components/toast/toast.js b/source/scripts/libs/components/toast/toast.js index 44d05759..b43dcc14 100644 --- a/source/scripts/libs/components/toast/toast.js +++ b/source/scripts/libs/components/toast/toast.js @@ -9,21 +9,27 @@ import { /** @typedef {import('~/libs/types/types').ToastMessagePayload} ToastMessagePayload */ class Toast { + /** @type {boolean} */ + #isShowingMessage + + /** @type {ToastMessagePayload[]} */ + #messages + + /** @type {HTMLElement | undefined} */ + #toastNode + constructor() { - /** @type {HTMLElement | undefined} */ - this._toastNode = undefined - /** @type {ToastMessagePayload[]} */ - this._messages = [] - /** @type {boolean} */ - this._isShowingMessage = false + this.#toastNode = undefined + this.#messages = [] + this.#isShowingMessage = false } /** * @param {ToastMessagePayload} messagePayload * @returns {Promise} */ - async _displayToastMessage(messagePayload) { - let toastNode = /** @type {HTMLElement} */ (this._toastNode) + async #displayToastMessage(messagePayload) { + let toastNode = /** @type {HTMLElement} */ (this.#toastNode) let { cb, duration = TOAST_DEFAULT_DURATION, message } = messagePayload toastNode.classList.add(TOAST_SHOW_CLASS_NAME) @@ -41,29 +47,29 @@ class Toast { } /** @returns {Promise} */ - async _initShowingMessages() { + async #initShowingMessages() { let messagePayload = /** @type {ToastMessagePayload} */ ( - this._messages.shift() + this.#messages.shift() ) - this._isShowingMessage = true + this.#isShowingMessage = true - await this._displayToastMessage(messagePayload) + await this.#displayToastMessage(messagePayload) - let hasMessages = this._messages.length > 0 + let hasMessages = this.#messages.length > 0 if (hasMessages) { - this._initShowingMessages() + this.#initShowingMessages() return } - this._isShowingMessage = false + this.#isShowingMessage = false } /** @returns {void} */ init() { - this._toastNode = /** @type {HTMLElement} */ ( + this.#toastNode = /** @type {HTMLElement} */ ( document.querySelector(`.toast`) ) } @@ -73,10 +79,10 @@ class Toast { * @returns {void} */ pushMessage(message) { - this._messages.push(message) + this.#messages.push(message) - if (!this._isShowingMessage) { - this._initShowingMessages() + if (!this.#isShowingMessage) { + this.#initShowingMessages() } } } diff --git a/source/scripts/libs/packages/http/http.package.js b/source/scripts/libs/packages/http/http.package.js index efc28721..b11834d0 100644 --- a/source/scripts/libs/packages/http/http.package.js +++ b/source/scripts/libs/packages/http/http.package.js @@ -49,7 +49,7 @@ class Http { * }} options * @returns {Headers} */ - _getHeaders({ contentType }) { + #getHeaders({ contentType }) { let headers = new Headers() if (contentType) { @@ -71,7 +71,7 @@ class Http { */ load(url, options = {}) { let { contentType, method = HttpMethod.GET, payload } = options - let headers = this._getHeaders({ + let headers = this.#getHeaders({ contentType, }) let isJSON = checkIsOneOf(contentType, ContentType.JSON) diff --git a/source/scripts/libs/packages/storage/storage.package.js b/source/scripts/libs/packages/storage/storage.package.js index 40173c2f..9e85b3d0 100644 --- a/source/scripts/libs/packages/storage/storage.package.js +++ b/source/scripts/libs/packages/storage/storage.package.js @@ -1,20 +1,19 @@ class Storage { + /** @type {globalThis.Storage} */ + #storage + /** * @param {{ * storage: globalThis.Storage * }} constructor */ constructor({ storage }) { - /** - * @private - * @type {globalThis.Storage} - */ - this._storage = storage + this.#storage = storage } /** @returns {void} */ clear() { - return this._storage.clear() + return this.#storage.clear() } /** @@ -22,7 +21,7 @@ class Storage { * @returns {string | null} */ getItem(key) { - return this._storage.getItem(key) + return this.#storage.getItem(key) } /** @@ -30,7 +29,7 @@ class Storage { * @returns {void} */ removeItem(key) { - return this._storage.removeItem(key) + return this.#storage.removeItem(key) } /** @@ -39,7 +38,7 @@ class Storage { * @returns {void} */ setItem(key, value) { - return this._storage.setItem(key, value) + return this.#storage.setItem(key, value) } } diff --git a/source/scripts/packages/timeline/timeline-api.js b/source/scripts/packages/timeline/timeline-api.js index 46089783..117dc7c8 100644 --- a/source/scripts/packages/timeline/timeline-api.js +++ b/source/scripts/packages/timeline/timeline-api.js @@ -8,6 +8,18 @@ import { TimelineApiPath } from './libs/enums/enums.js' /** @typedef {import('./libs/types/types')} TimelineCreatePayload */ class TimelineApi { + /** @type {(typeof ApiPath)[keyof typeof ApiPath]} */ + #apiPath + + /** @type {string} */ + #baseUrl + + /** @type {string} */ + #filesApiPath + + /** @type {Http} */ + #http + /** * @param {{ * baseUrl: string @@ -16,31 +28,31 @@ class TimelineApi { * }} config */ constructor({ baseUrl, filesApiPath, http }) { - this._http = http - this._baseUrl = baseUrl - this._filesApiPath = filesApiPath - this._apiPath = ApiPath.TIMELINE + this.#http = http + this.#baseUrl = baseUrl + this.#filesApiPath = filesApiPath + this.#apiPath = ApiPath.TIMELINE } /** * @param {string} path * @returns {string} */ - _getApiUrl(path) { - return `${this._baseUrl}${this._apiPath}${path}` + #getApiUrl(path) { + return `${this.#baseUrl}${this.#apiPath}${path}` } /** * @param {string} path * @returns {string} */ - _getFileUrl(path) { - return `${this._filesApiPath}${path}.json` + #getFileUrl(path) { + return `${this.#filesApiPath}${path}.json` } /** @returns {Promise} */ getTimelines() { - return this._http.load(this._getFileUrl(this._apiPath), { + return this.#http.load(this.#getFileUrl(this.#apiPath), { method: HttpMethod.GET, }) } @@ -50,7 +62,7 @@ class TimelineApi { * @returns {Promise} */ saveTimeline(payload) { - return this._http.load(this._getApiUrl(TimelineApiPath.ROOT), { + return this.#http.load(this.#getApiUrl(TimelineApiPath.ROOT), { contentType: ContentType.JSON, method: HttpMethod.POST, payload, diff --git a/source/scripts/pages/form/form.js b/source/scripts/pages/form/form.js index 88d78f80..1211e519 100644 --- a/source/scripts/pages/form/form.js +++ b/source/scripts/pages/form/form.js @@ -13,47 +13,38 @@ let timelineSkillTypes = Object.values(TimelineSkillType) let timelineTypes = Object.values(TimelineType) class Form { + /** @type {HTMLFormElement | undefined} */ + #formNode + + /** @type {(event_: SubmitEvent) => Promise} */ + #handleSubmit + + /** @type {TimelineApi} */ + #timelineApi + /** * @param {{ * timelineApi: TimelineApi * }} constructor */ constructor({ timelineApi }) { - this._timelineApi = timelineApi - - /** @type {HTMLFormElement | undefined} */ - this._formNode = undefined - - this._handleSubmit = this._handleSubmit.bind(this) - } - - /** - * @param {SubmitEvent} event_ - * @returns {Promise} - */ - async _handleSubmit(event_) { - event_.preventDefault() - - let formNode = /** @type {HTMLFormElement} */ (this._formNode) - let formValues = /** @type {TimelineCreatePayload} */ ( - getFormValues(formNode) - ) + this.#timelineApi = timelineApi - await this._timelineApi.saveTimeline(getTransformedTimeline(formValues)) + this.#formNode = undefined - formNode.reset() + this.#handleSubmit = this.#submitHandler.bind(this) } - /** @returns {void} */ - _initListeners() { - let formNode = /** @type {HTMLFormElement} */ (this._formNode) + /** @type {() => void} */ + #initListeners() { + let formNode = /** @type {HTMLFormElement} */ (this.#formNode) - formNode.addEventListener(`submit`, this._handleSubmit) + formNode.addEventListener(`submit`, this.#handleSubmit) } /** @returns {void} */ - _initSelects() { - let formNode = /** @type {HTMLFormElement} */ (this._formNode) + #initSelects() { + let formNode = /** @type {HTMLFormElement} */ (this.#formNode) fillSelectOptions( /** @type {HTMLSelectElement} */ ( @@ -69,15 +60,32 @@ class Form { ) } + /** + * @param {SubmitEvent} event_ + * @returns {Promise} + */ + async #submitHandler(event_) { + event_.preventDefault() + + let formNode = /** @type {HTMLFormElement} */ (this.#formNode) + let formValues = /** @type {TimelineCreatePayload} */ ( + getFormValues(formNode) + ) + + await this.#timelineApi.saveTimeline(getTransformedTimeline(formValues)) + + formNode.reset() + } + /** @returns {void} */ init() { - this._formNode = /** @type {HTMLFormElement} */ ( + this.#formNode = /** @type {HTMLFormElement} */ ( document.querySelector(`form[name="timeline"]`) ) - this._initSelects() + this.#initSelects() - this._initListeners() + this.#initListeners() } } diff --git a/source/scripts/pages/home/home.js b/source/scripts/pages/home/home.js index c4ead051..513e0843 100644 --- a/source/scripts/pages/home/home.js +++ b/source/scripts/pages/home/home.js @@ -13,6 +13,27 @@ import { /** @typedef {import('~/packages/timeline/timeline').TimelineApi} TimelineApi */ class Home { + /** @type {EasterEgg} */ + #easterEggComponent + + /** @type {(message: ToastMessagePayload) => void} */ + #handleNotificationAdd + + /** @type {(settings: SettingButtonPayload) => HTMLButtonElement} */ + #handleSettingButtonAppend + + /** @type {Navigation} */ + #navigationComponent + + /** @type {Settings} */ + #settingsComponent + + /** @type {Timeline} */ + #timelineComponent + + /** @type {Toast} */ + #toastComponent + /** * @param {{ * storage: Storage @@ -20,46 +41,47 @@ class Home { * }} constructor */ constructor({ storage, timelineApi }) { - this._handleSettingBtnAppend = this._handleSettingBtnAppend.bind(this) - this._handleNotificationAdd = this._handleNotificationAdd.bind(this) + this.#handleSettingButtonAppend = + this.#appendSettingButtonHandler.bind(this) + this.#handleNotificationAdd = this.#addSettingButtonHandler.bind(this) - this._settingsComponent = new Settings({ + this.#settingsComponent = new Settings({ storage, }) - this._navigationComponent = new Navigation() - this._timelineComponent = new Timeline({ + this.#navigationComponent = new Navigation() + this.#timelineComponent = new Timeline({ timelineApi, }) - this._easterEggComponent = new EasterEgg({ - onNotificationAdd: this._handleNotificationAdd, - onSettingBtnAppend: this._handleSettingBtnAppend, + this.#easterEggComponent = new EasterEgg({ + onNotificationAdd: this.#handleNotificationAdd, + onSettingButtonAppend: this.#handleSettingButtonAppend, }) - this._toastComponent = new Toast() + this.#toastComponent = new Toast() } /** * @param {ToastMessagePayload} message * @returns {void} */ - _handleNotificationAdd(message) { - this._toastComponent.pushMessage(message) + #addSettingButtonHandler(message) { + this.#toastComponent.pushMessage(message) } /** * @param {SettingButtonPayload} settings * @returns {HTMLButtonElement} */ - _handleSettingBtnAppend(settings) { - return this._settingsComponent.appendNewBtn(settings) + #appendSettingButtonHandler(settings) { + return this.#settingsComponent.appendNewButton(settings) } /** @returns {void} */ init() { - this._toastComponent.init() - this._settingsComponent.init() - this._navigationComponent.init() - this._timelineComponent.init() - this._easterEggComponent.init() + this.#toastComponent.init() + this.#settingsComponent.init() + this.#navigationComponent.init() + this.#timelineComponent.init() + this.#easterEggComponent.init() } } diff --git a/source/scripts/pages/home/libs/components/easter-egg/easter-egg.js b/source/scripts/pages/home/libs/components/easter-egg/easter-egg.js index 3dcae7d0..0655ab0a 100644 --- a/source/scripts/pages/home/libs/components/easter-egg/easter-egg.js +++ b/source/scripts/pages/home/libs/components/easter-egg/easter-egg.js @@ -19,47 +19,74 @@ import { /** @typedef {import('~/pages/home/libs/types/types').SettingButtonPayload} SettingButtonPayload */ class EasterEgg { + /** @type {HTMLAudioElement | undefined} */ + #audioNode + + /** @type {HTMLButtonElement | undefined} */ + #easterEggButtonNode + + /** @type {HTMLElement | undefined} */ + #easterEggContainerNode + + /** @type {() => void} */ + #handleEasterEggClick + + /** + * @type {( + * name: (typeof SettingName)[keyof typeof SettingName], + * isChecked: boolean, + * ) => void} + */ + #handleSettingButtonClick + + /** @type {() => void} */ + #handleWindowResize + + /** @type {(payload: ToastMessagePayload) => void} */ + #onNotificationAdd + + /** @type {(payload: SettingButtonPayload) => HTMLButtonElement} */ + #onSettingButtonAppend + /** * @param {{ * onNotificationAdd: (payload: ToastMessagePayload) => void - * onSettingBtnAppend: ( + * onSettingButtonAppend: ( * payload: SettingButtonPayload, * ) => HTMLButtonElement * }} constructor */ - constructor({ onNotificationAdd, onSettingBtnAppend }) { - this._onNotificationAdd = onNotificationAdd - this._onSettingBtnAppend = onSettingBtnAppend - - /** @type {HTMLElement | undefined} */ - this._easterEggContainerNode = undefined - /** @type {HTMLButtonElement | undefined} */ - this._easterEggButtonNode = undefined - /** @type {HTMLAudioElement | undefined} */ - this._audioNode = undefined - - this._handleEasterEggClick = this._handleEasterEggClick.bind(this) - this._handleSettingBtnClick = this._handleSettingBtnClick.bind(this) - this._handleWindowResize = initDebounce( - this._handleWindowResize.bind(this), + constructor({ onNotificationAdd, onSettingButtonAppend }) { + this.#onNotificationAdd = onNotificationAdd + this.#onSettingButtonAppend = onSettingButtonAppend + + this.#easterEggContainerNode = undefined + this.#easterEggButtonNode = undefined + this.#audioNode = undefined + + this.#handleEasterEggClick = this.#clickEasterEggClickHandler.bind(this) + this.#handleSettingButtonClick = + this.#clickSettingButtonHandler.bind(this) + this.#handleWindowResize = initDebounce( + this.#resizeWindowHandler, RESIZE_DELAY, ) } /** @returns {void} */ - _handleEasterEggClick() { + #clickEasterEggClickHandler() { let easterEggContainerNode = /** @type {HTMLElement} */ ( - this._easterEggContainerNode + this.#easterEggContainerNode ) - this._onNotificationAdd({ + this.#onNotificationAdd({ /** @returns {void} */ cb: () => { - let buttonNode = this._onSettingBtnAppend({ + let buttonNode = this.#onSettingButtonAppend({ isDefaultChecked: true, label: SettingButtonLabel.SWITCH_LOVE, name: SettingName.WHATISLOVE, - onClick: this._handleSettingBtnClick, + onClick: this.#handleSettingButtonClick, }) buttonNode.focus() @@ -68,9 +95,9 @@ class EasterEgg { message: NotificationMessage.LOVE, }) - this._renderPlayer() + this.#renderPlayer() - this._removeListeners() + this.#removeListeners() easterEggContainerNode.remove() } @@ -80,54 +107,54 @@ class EasterEgg { * @param {boolean} isChecked * @returns {void} */ - _handleSettingBtnClick(_name, isChecked) { - let audioNode = /** @type {HTMLAudioElement} */ (this._audioNode) + #clickSettingButtonHandler(_name, isChecked) { + let audioNode = /** @type {HTMLAudioElement} */ (this.#audioNode) isChecked ? audioNode.play() : audioNode.pause() } /** @returns {void} */ - _handleWindowResize() { - this._setRandomPosition() - } - - /** @returns {void} */ - _initListeners() { + #initListeners() { let easterEggButtonNode = /** @type {HTMLElement} */ ( - this._easterEggButtonNode + this.#easterEggButtonNode ) easterEggButtonNode.addEventListener( `click`, - this._handleEasterEggClick, + this.#handleEasterEggClick, ) - globalThis.addEventListener(`resize`, this._handleWindowResize) + globalThis.addEventListener(`resize`, this.#handleWindowResize) } /** @returns {void} */ - _removeListeners() { + #removeListeners() { let easterEggButtonNode = /** @type {HTMLElement} */ ( - this._easterEggButtonNode + this.#easterEggButtonNode ) easterEggButtonNode.removeEventListener( `click`, - this._handleEasterEggClick, + this.#handleEasterEggClick, ) - globalThis.removeEventListener(`resize`, this._handleWindowResize) + globalThis.removeEventListener(`resize`, this.#handleWindowResize) } /** @returns {void} */ - _renderPlayer() { - this._audioNode = getPlayerElement(SOUND_SRC) + #renderPlayer() { + this.#audioNode = getPlayerElement(SOUND_SRC) + + document.body.append(this.#audioNode) + } - document.body.append(this._audioNode) + /** @returns {void} */ + #resizeWindowHandler() { + this.#setRandomPosition() } /** @returns {void} */ - _setRandomPosition() { + #setRandomPosition() { let easterEggContainerNode = /** @type {HTMLElement} */ ( - this._easterEggContainerNode + this.#easterEggContainerNode ) let { x, y } = getNodeRandomCoords(easterEggContainerNode) @@ -137,16 +164,16 @@ class EasterEgg { /** @returns {void} */ init() { - this._easterEggContainerNode = /** @type {HTMLElement} */ ( + this.#easterEggContainerNode = /** @type {HTMLElement} */ ( document.querySelector(`.not-easter-egg`) ) - this._easterEggButtonNode = /** @type {HTMLButtonElement} */ ( + this.#easterEggButtonNode = /** @type {HTMLButtonElement} */ ( document.querySelector(`.not-easter-egg__button`) ) - this._setRandomPosition() + this.#setRandomPosition() - this._initListeners() + this.#initListeners() } } diff --git a/source/scripts/pages/home/libs/components/navigation/navigation.js b/source/scripts/pages/home/libs/components/navigation/navigation.js index 110f36e2..f70417c4 100644 --- a/source/scripts/pages/home/libs/components/navigation/navigation.js +++ b/source/scripts/pages/home/libs/components/navigation/navigation.js @@ -4,87 +4,104 @@ import { checkIsOneOf, subscribeFocusTrap } from '~/libs/helpers/helpers.js' import { HEADER_ACTIVE_CLASS } from './libs/constants/constants.js' class Navigation { - constructor() { - /** @type {HTMLElement | undefined} */ - this._headerNode = undefined - /** @type {HTMLElement | undefined} */ - this._headerOverlayNode = undefined - /** @type {HTMLButtonElement | undefined} */ - this._headerButtonNode = undefined - /** @type {(() => void) | undefined} */ - this._cleanFocusTrap = undefined - - this._handleEscapePress = this._handleEscapePress.bind(this) - this._handleOverlayClick = this._handleOverlayClick.bind(this) - this._handleNavBtnClick = this._handleNavBtnClick.bind(this) - } + /** @type {(() => void) | undefined} */ + #cleanFocusTrap - /** - * @param {KeyboardEvent} event_ - * @returns {void} - */ - _handleEscapePress({ key }) { - if (checkIsOneOf(key, KeyboardKey.ESCAPE)) { - this._handleOverlayClick() - } + /** @type {(event_: KeyboardEvent) => void} */ + #handleEscapePress + + /** @type {(event_: Event) => void} */ + #handleNavButtonClick + + /** @type {() => void} */ + #handleOverlayClick + + /** @type {HTMLButtonElement | undefined} */ + #headerButtonNode + + /** @type {HTMLElement | undefined} */ + #headerNode + + /** @type {HTMLElement | undefined} */ + #headerOverlayNode + + constructor() { + this.#headerNode = undefined + this.#headerOverlayNode = undefined + this.#headerButtonNode = undefined + this.#cleanFocusTrap = undefined + + this.#handleNavButtonClick = this.#clickNavButtonHandler.bind(this) + this.#handleOverlayClick = this.#clickOverlayHandler.bind(this) + this.#handleEscapePress = this.#pressEscapeHandler.bind(this) } /** * @param {Event} event_ * @returns {void} */ - _handleNavBtnClick(event_) { + #clickNavButtonHandler(event_) { event_.stopPropagation() let hasClass = /** @type {HTMLElement} */ ( - this._headerNode + this.#headerNode ).classList.contains(HEADER_ACTIVE_CLASS) - this._toggleOverlay(!hasClass) + this.#toggleOverlay(!hasClass) } /** @returns {void} */ - _handleOverlayClick() { - this._toggleOverlay(false) + #clickOverlayHandler() { + this.#toggleOverlay(false) } /** @returns {void} */ - _initListeners() { + #initListeners() { let headerButtonNode = /** @type {HTMLButtonElement} */ ( - this._headerButtonNode + this.#headerButtonNode ) - headerButtonNode.addEventListener(`click`, this._handleNavBtnClick) + headerButtonNode.addEventListener(`click`, this.#handleNavButtonClick) } /** @returns {void} */ - _initOverlayListeners() { + #initOverlayListeners() { let headerOverlayNode = /** @type {HTMLElement} */ ( - this._headerOverlayNode + this.#headerOverlayNode ) - headerOverlayNode.addEventListener(`click`, this._handleOverlayClick) - globalThis.addEventListener(`keydown`, this._handleEscapePress) + headerOverlayNode.addEventListener(`click`, this.#handleOverlayClick) + globalThis.addEventListener(`keydown`, this.#handleEscapePress) + } + + /** + * @param {KeyboardEvent} event_ + * @returns {void} + */ + #pressEscapeHandler({ key }) { + if (checkIsOneOf(key, KeyboardKey.ESCAPE)) { + this.#handleOverlayClick() + } } /** @returns {void} */ - _removeOverlayListeners() { + #removeOverlayListeners() { let headerOverlayNode = /** @type {HTMLElement} */ ( - this._headerOverlayNode + this.#headerOverlayNode ) - headerOverlayNode.removeEventListener(`click`, this._handleOverlayClick) - globalThis.removeEventListener(`keydown`, this._handleEscapePress) + headerOverlayNode.removeEventListener(`click`, this.#handleOverlayClick) + globalThis.removeEventListener(`keydown`, this.#handleEscapePress) } /** * @param {boolean} isActive * @returns {void} */ - _toggleOverlay(isActive) { - let headerNode = /** @type {HTMLElement} */ (this._headerNode) + #toggleOverlay(isActive) { + let headerNode = /** @type {HTMLElement} */ (this.#headerNode) let headerButtonNode = /** @type {HTMLButtonElement} */ ( - this._headerButtonNode + this.#headerButtonNode ) document.body.style.overflowY = isActive ? `hidden` : `` @@ -93,36 +110,36 @@ class Navigation { headerButtonNode.ariaExpanded = isActive.toString() - isActive ? this._initOverlayListeners() : this._removeOverlayListeners() + isActive ? this.#initOverlayListeners() : this.#removeOverlayListeners() let focusTrapElements = /** @type {HTMLElement[]} */ ([ headerButtonNode, ...this.headerLinkNodes, ]) - this._cleanFocusTrap = isActive + this.#cleanFocusTrap = isActive ? subscribeFocusTrap(...focusTrapElements) - : /** @type {() => undefined} */ (this._cleanFocusTrap)() + : /** @type {() => undefined} */ (this.#cleanFocusTrap)() } /** @returns {void} */ init() { - this._headerNode = /** @type {HTMLElement} */ ( + this.#headerNode = /** @type {HTMLElement} */ ( document.querySelector(`.header`) ) - this._headerOverlayNode = /** @type {HTMLElement} */ ( - this._headerNode.querySelector(`.header__navigation-wrapper`) + this.#headerOverlayNode = /** @type {HTMLElement} */ ( + this.#headerNode.querySelector(`.header__navigation-wrapper`) ) - this._headerButtonNode = /** @type {HTMLButtonElement} */ ( - this._headerNode.querySelector(`.header__toggle-button`) + this.#headerButtonNode = /** @type {HTMLButtonElement} */ ( + this.#headerNode.querySelector(`.header__toggle-button`) ) - this._initListeners() + this.#initListeners() } /** @returns {NodeListOf} */ get headerLinkNodes() { - let headerNode = /** @type {HTMLElement} */ (this._headerNode) + let headerNode = /** @type {HTMLElement} */ (this.#headerNode) return headerNode.querySelectorAll(`.navigation__item a[href]`) } diff --git a/source/scripts/pages/home/libs/components/settings/libs/components/control/control.js b/source/scripts/pages/home/libs/components/settings/libs/components/control/control.js index a0a96186..a22114af 100644 --- a/source/scripts/pages/home/libs/components/settings/libs/components/control/control.js +++ b/source/scripts/pages/home/libs/components/settings/libs/components/control/control.js @@ -1,6 +1,21 @@ /** @typedef {typeof import('~/pages/home/libs/enums/enums').SettingName} SettingName */ class Control { + /** @type {HTMLFieldSetElement | undefined} */ + #controlNode + + /** @type {string | null} */ + #defaultValue + + /** @type {(event_: Event) => void} */ + #handleSwitchChange + + /** @type {SettingName[keyof SettingName]} */ + #name + + /** @type {(name: SettingName[keyof SettingName], value: string) => void} */ + #onChange + /** * @param {{ * defaultValue: string | null @@ -12,44 +27,32 @@ class Control { * }} constructor */ constructor({ defaultValue, name, onChange }) { - this._name = name - this._onChange = onChange - this._defaultValue = defaultValue + this.#name = name + this.#onChange = onChange + this.#defaultValue = defaultValue - /** @type {HTMLFieldSetElement | undefined} */ - this._controlNode = undefined + this.#controlNode = undefined - this._handleSwitchChange = this._handleSwitchChange.bind(this) - } - - /** - * @param {Event} event_ - * @returns {void} - */ - _handleSwitchChange({ target }) { - this._onChange( - this._name, - /** @type {HTMLInputElement} */ (target).value, - ) + this.#handleSwitchChange = this.#switchChangeHandler.bind(this) } /** @returns {void} */ - _initListeners() { - let controlNode = /** @type {HTMLFieldSetElement} */ (this._controlNode) + #initListeners() { + let controlNode = /** @type {HTMLFieldSetElement} */ (this.#controlNode) - controlNode.addEventListener(`change`, this._handleSwitchChange) + controlNode.addEventListener(`change`, this.#handleSwitchChange) } /** @returns {void} */ - _setInitialValue() { - let controlNode = /** @type {HTMLFieldSetElement} */ (this._controlNode) + #setInitialValue() { + let controlNode = /** @type {HTMLFieldSetElement} */ (this.#controlNode) let inputNodes = /** @type {NodeListOf} */ ( - controlNode.querySelectorAll(`input[name="${this._name}"]`) + controlNode.querySelectorAll(`input[name="${this.#name}"]`) ) for (let it of inputNodes) { - let isChecked = it.value === this._defaultValue + let isChecked = it.value === this.#defaultValue if (isChecked) { it.checked = isChecked @@ -57,18 +60,29 @@ class Control { } } + /** + * @param {Event} event_ + * @returns {void} + */ + #switchChangeHandler({ target }) { + this.#onChange( + this.#name, + /** @type {HTMLInputElement} */ (target).value, + ) + } + /** * @param {string} selector * @returns {void} */ init(selector) { - this._controlNode = /** @type {HTMLFieldSetElement} */ ( + this.#controlNode = /** @type {HTMLFieldSetElement} */ ( document.querySelector(selector) ) - this._setInitialValue() + this.#setInitialValue() - this._initListeners() + this.#initListeners() } } diff --git a/source/scripts/pages/home/libs/components/settings/libs/components/switch/switch.js b/source/scripts/pages/home/libs/components/settings/libs/components/switch/switch.js index 34b5c90f..fe68c3ff 100644 --- a/source/scripts/pages/home/libs/components/settings/libs/components/switch/switch.js +++ b/source/scripts/pages/home/libs/components/settings/libs/components/switch/switch.js @@ -1,6 +1,26 @@ /** @typedef {typeof import('~/pages/home/libs/enums/enums').SettingName} SettingName */ class Switch { + /** @type {(event_: Event) => void} */ + #handleSwitchClick + + /** @type {boolean} */ + #isDefaultChecked + + /** @type {SettingName[keyof SettingName]} */ + #name + + /** + * @type {( + * name: SettingName[keyof SettingName], + * isChecked: boolean, + * ) => void} + */ + #onClick + + /** @type {HTMLButtonElement | undefined} */ + #switchNode + /** * @param {{ * isDefaultChecked: boolean @@ -12,41 +32,41 @@ class Switch { * }} constructor */ constructor({ isDefaultChecked, name, onClick }) { - this._name = name - this._isDefaultChecked = isDefaultChecked - this._onClick = onClick + this.#name = name + this.#isDefaultChecked = isDefaultChecked + this.#onClick = onClick - this._switchNode = undefined + this.#switchNode = undefined - this._handleSwitchClick = this._handleSwitchClick.bind(this) + this.#handleSwitchClick = this.#clickSwitchHandler.bind(this) } /** * @param {Event} event_ * @returns {void} */ - _handleSwitchClick({ target }) { + #clickSwitchHandler({ target }) { let isChecked = /** @type {HTMLElement} */ (target).ariaChecked === `true` - this._isChecked = !isChecked + this.#isChecked = !isChecked - this._onClick(this._name, !isChecked) + this.#onClick(this.#name, !isChecked) } /** @returns {void} */ - _initListeners() { - let switchNode = /** @type {HTMLElement} */ (this._switchNode) + #initListeners() { + let switchNode = /** @type {HTMLElement} */ (this.#switchNode) - switchNode.addEventListener(`click`, this._handleSwitchClick) + switchNode.addEventListener(`click`, this.#handleSwitchClick) } /** * @param {boolean} isChecked * @returns {void} */ - set _isChecked(isChecked) { - let switchNode = /** @type {HTMLElement} */ (this._switchNode) + set #isChecked(isChecked) { + let switchNode = /** @type {HTMLElement} */ (this.#switchNode) switchNode.ariaChecked = isChecked.toString() } @@ -56,17 +76,17 @@ class Switch { * @returns {HTMLButtonElement} */ init(selector) { - this._switchNode = /** @type {HTMLButtonElement} */ ( + this.#switchNode = /** @type {HTMLButtonElement} */ ( document.querySelector(selector) ) - this._isChecked = this._isDefaultChecked + this.#isChecked = this.#isDefaultChecked - this._initListeners() + this.#initListeners() - this._onClick(this._name, this._isDefaultChecked) + this.#onClick(this.#name, this.#isDefaultChecked) - return this._switchNode + return this.#switchNode } } diff --git a/source/scripts/pages/home/libs/components/settings/settings.js b/source/scripts/pages/home/libs/components/settings/settings.js index 43cbf5d8..6e972b14 100644 --- a/source/scripts/pages/home/libs/components/settings/settings.js +++ b/source/scripts/pages/home/libs/components/settings/settings.js @@ -10,18 +10,31 @@ let RESULT_VALUE = /** @type {const} */ (`auto`) /** @typedef {import('~/libs/packages/storage/storage').Storage} Storage */ class Settings { + /** + * @type {( + * name: (typeof SettingName)[keyof typeof SettingName], + * value: string, + * ) => void} + */ + #handleControlChange + + /** @type {HTMLElement | undefined} */ + #settingListNode + + /** @type {Storage} */ + #storage + /** * @param {{ * storage: Storage * }} constructor */ constructor({ storage }) { - this._storage = storage + this.#storage = storage - /** @type {HTMLElement | undefined} */ - this._settingListNode = undefined + this.#settingListNode = undefined - this._handleControlChange = this._handleControlChange.bind(this) + this.#handleControlChange = this.#changeControlHandler.bind(this) } /** @@ -29,29 +42,29 @@ class Settings { * @param {string} value * @returns {void} */ - _handleControlChange(name, value) { + #changeControlHandler(name, value) { if (value === RESULT_VALUE) { - this._removeSettingAttr(name) + this.#removeSettingAttr(name) - return this._storage.removeItem(name) + return this.#storage.removeItem(name) } - this._setSettingAttr(name, value) + this.#setSettingAttr(name, value) - this._storage.setItem(name, value) + this.#storage.setItem(name, value) } /** * @param {(typeof SettingName)[keyof typeof SettingName]} name * @returns {void} */ - _initControl(name) { - let defaultValue = this._setInitialSettingAttr(name) + #initControl(name) { + let defaultValue = this.#setInitialSettingAttr(name) new Control({ defaultValue, name, - onChange: this._handleControlChange, + onChange: this.#handleControlChange, }).init(`.settings__item-fieldset--${name}`) } @@ -59,7 +72,7 @@ class Settings { * @param {(typeof SettingName)[keyof typeof SettingName]} name * @returns {void} */ - _removeSettingAttr(name) { + #removeSettingAttr(name) { document.documentElement.removeAttribute(getCustomAttributeName(name)) } @@ -67,12 +80,12 @@ class Settings { * @param {(typeof SettingName)[keyof typeof SettingName]} name * @returns {string | null} */ - _setInitialSettingAttr(name) { - let value = this._storage.getItem(name) + #setInitialSettingAttr(name) { + let value = this.#storage.getItem(name) let hasValue = Boolean(value) if (hasValue) { - this._setSettingAttr(name, /** @type {string} */ (value)) + this.#setSettingAttr(name, /** @type {string} */ (value)) } return value @@ -83,7 +96,7 @@ class Settings { * @param {string} value * @returns {void} */ - _setSettingAttr(name, value) { + #setSettingAttr(name, value) { document.documentElement.setAttribute( getCustomAttributeName(name), value, @@ -94,11 +107,11 @@ class Settings { * @param {SettingButtonPayload} settings * @returns {HTMLButtonElement} */ - appendNewBtn(settings) { + appendNewButton(settings) { let { isDefaultChecked, label, name, onClick } = settings let newSettingItemNode = getSettingItemElement({ label, name }) - let settingListNode = /** @type {HTMLElement} */ (this._settingListNode) + let settingListNode = /** @type {HTMLElement} */ (this.#settingListNode) settingListNode.prepend(newSettingItemNode) @@ -111,12 +124,12 @@ class Settings { /** @returns {void} */ init() { - this._settingListNode = /** @type {HTMLElement} */ ( + this.#settingListNode = /** @type {HTMLElement} */ ( document.querySelector(`.settings`) ) - this._initControl(SettingName.THEME) - this._initControl(SettingName.MOTION) + this.#initControl(SettingName.THEME) + this.#initControl(SettingName.MOTION) } } diff --git a/source/scripts/pages/home/libs/components/timeline/libs/components/timeline-form/timeline-form.js b/source/scripts/pages/home/libs/components/timeline/libs/components/timeline-form/timeline-form.js index edacedcb..a02eb737 100644 --- a/source/scripts/pages/home/libs/components/timeline/libs/components/timeline-form/timeline-form.js +++ b/source/scripts/pages/home/libs/components/timeline/libs/components/timeline-form/timeline-form.js @@ -5,47 +5,55 @@ import { DEBOUNCE_DELAY } from './libs/constants/constants.js' /** @typedef {import('~/packages/timeline/timeline').TimelineFilter} TimelineFilter */ class TimelineForm { + /** @type {HTMLFormElement | undefined} */ + #formNode + + /** @type {() => void} */ + #handleFormChange + + /** @type {(formValues: TimelineFilter) => void} */ + #onChange + /** * @param {{ * onChange: (formValues: TimelineFilter) => void * }} constructor */ constructor({ onChange }) { - this._onChange = onChange + this.#onChange = onChange - /** @type {HTMLFormElement | undefined} */ - this._formNode = undefined + this.#formNode = undefined - this._handleFormChange = this._handleFormChange.bind(this) + this.#handleFormChange = this.#changeFormHandler.bind(this) } /** @returns {void} */ - _handleFormChange() { - this._onChange(this.formValues) + #changeFormHandler() { + this.#onChange(this.formValues) } /** @returns {void} */ - _initListeners() { - let formNode = /** @type {HTMLFormElement} */ (this._formNode) + #initListeners() { + let formNode = /** @type {HTMLFormElement} */ (this.#formNode) formNode.addEventListener( `change`, - initDebounce(this._handleFormChange, DEBOUNCE_DELAY), + initDebounce(this.#handleFormChange, DEBOUNCE_DELAY), ) } /** @returns {void} */ init() { - this._formNode = /** @type {HTMLFormElement} */ ( + this.#formNode = /** @type {HTMLFormElement} */ ( document.querySelector(`.timeline__filter`) ) - this._initListeners() + this.#initListeners() } /** @returns {TimelineFilter} */ get formValues() { - return getFormValues(/** @type {HTMLFormElement} */ (this._formNode)) + return getFormValues(/** @type {HTMLFormElement} */ (this.#formNode)) } } diff --git a/source/scripts/pages/home/libs/components/timeline/libs/components/timeline-list/timeline-list.js b/source/scripts/pages/home/libs/components/timeline/libs/components/timeline-list/timeline-list.js index 0d46fe42..14f9dbe6 100644 --- a/source/scripts/pages/home/libs/components/timeline/libs/components/timeline-list/timeline-list.js +++ b/source/scripts/pages/home/libs/components/timeline/libs/components/timeline-list/timeline-list.js @@ -3,14 +3,16 @@ import { getTimelineTemplates } from './libs/helpers/helpers.js' /** @typedef {import('~/packages/timeline/timeline').Timeline} Timeline */ class TimelineList { + /** @type {HTMLElement | undefined} */ + #listNode + constructor() { - /** @type {HTMLElement | undefined} */ - this._listNode = undefined + this.#listNode = undefined } /** @returns {void} */ init() { - this._listNode = /** @type {HTMLElement} */ ( + this.#listNode = /** @type {HTMLElement} */ ( document.querySelector(`.timeline__list`) ) } @@ -20,7 +22,7 @@ class TimelineList { * @returns {void} */ renderTimelines(timelines) { - let listNode = /** @type {HTMLElement} */ (this._listNode) + let listNode = /** @type {HTMLElement} */ (this.#listNode) listNode.innerHTML = getTimelineTemplates(timelines) } diff --git a/source/scripts/pages/home/libs/components/timeline/timeline.js b/source/scripts/pages/home/libs/components/timeline/timeline.js index 2e96979f..b31499ba 100644 --- a/source/scripts/pages/home/libs/components/timeline/timeline.js +++ b/source/scripts/pages/home/libs/components/timeline/timeline.js @@ -9,93 +9,117 @@ import { getSuitTimelines } from './libs/helpers/helpers.js' /** @typedef {import('~/packages/timeline/timeline').TimelineApi} TimelineApi */ class Timeline { + /** @type {HTMLElement | undefined} */ + #containerNode + + /** @type {(formValues: TimelineFilter) => void} */ + #handleFormChange + + /** @type {() => Promise} */ + #handleTimelineShow + + /** @type {boolean} */ + #isLoading + + /** @type {Loader} */ + #loaderComponent + + /** @type {TimelineApi} */ + #timelineApi + + /** @type {TimelineForm} */ + #timelineFormComponent + + /** @type {TimelineList} */ + #timelineListComponent + + /** @type {TTimeline[]} */ + #timelines + /** * @param {{ * timelineApi: TimelineApi * }} constructor */ constructor({ timelineApi }) { - this._timelineApi = timelineApi + this.#timelineApi = timelineApi - /** @type {HTMLElement | undefined} */ - this._containerNode = undefined - /** @type {TTimeline[]} */ - this._timelines = [] - /** @type {boolean} */ - this._isLoading = false + this.#containerNode = undefined + this.#timelines = [] + this.#isLoading = false - this._handleFormChange = this._handleFormChange.bind(this) - this._handleTimelineShow = this._handleTimelineShow.bind(this) + this.#handleFormChange = this.#changeFormHandler.bind(this) + this.#handleTimelineShow = this.#showTimelineHandler.bind(this) - this._timelineFormComponent = new TimelineForm({ - onChange: this._handleFormChange, + this.#timelineFormComponent = new TimelineForm({ + onChange: this.#handleFormChange, }) - this._loaderComponent = new Loader({ + this.#loaderComponent = new Loader({ containerNode: /** @type {HTMLElement} */ ( document.querySelector(`.timeline__list-wrapper`) ), }) - this._timelineListComponent = new TimelineList() + this.#timelineListComponent = new TimelineList() + } + + /** + * @param {TimelineFilter} formValues + * @returns {void} + */ + #changeFormHandler(formValues) { + this.#renderTimelines(formValues) } /** @returns {Promise} */ - async _fetchTimelines() { - this._timelines = await this._timelineApi.getTimelines() + async #fetchTimelines() { + this.#timelines = await this.#timelineApi.getTimelines() + } + + /** @returns {void} */ + #initListeners() { + document.addEventListener(`scroll`, this.#handleTimelineShow) } /** * @param {TimelineFilter} formValues * @returns {void} */ - _handleFormChange(formValues) { - this._renderTimelines(formValues) + #renderTimelines(formValues) { + let timelines = getSuitTimelines(this.#timelines, formValues) + + this.#timelineListComponent.renderTimelines(timelines) } /** @returns {Promise} */ - async _handleTimelineShow() { + async #showTimelineHandler() { let shouldLoadTimelines = checkIsBeforeElement( - /** @type {HTMLElement} */ (this._containerNode).offsetTop, + /** @type {HTMLElement} */ (this.#containerNode).offsetTop, ) - if (shouldLoadTimelines && !this._isLoading) { - this._isLoading = true + if (shouldLoadTimelines && !this.#isLoading) { + this.#isLoading = true - await this._fetchTimelines() + await this.#fetchTimelines() - this._loaderComponent.remove() + this.#loaderComponent.remove() - this._renderTimelines(this._timelineFormComponent.formValues) + this.#renderTimelines(this.#timelineFormComponent.formValues) - document.removeEventListener(`scroll`, this._handleTimelineShow) + document.removeEventListener(`scroll`, this.#handleTimelineShow) } } - /** @returns {void} */ - _initListeners() { - document.addEventListener(`scroll`, this._handleTimelineShow) - } - - /** - * @param {TimelineFilter} formValues - * @returns {void} - */ - _renderTimelines(formValues) { - let timelines = getSuitTimelines(this._timelines, formValues) - - this._timelineListComponent.renderTimelines(timelines) - } - /** @returns {void} */ init() { - this._containerNode = /** @type {HTMLElement} */ ( + this.#containerNode = /** @type {HTMLElement} */ ( document.querySelector(`.timeline`) ) - this._loaderComponent.init() - this._timelineFormComponent.init() - this._timelineListComponent.init() + this.#loaderComponent.init() + this.#timelineFormComponent.init() + this.#timelineListComponent.init() - this._initListeners() + this.#initListeners() } }