-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Keks
committed
Apr 8, 2024
1 parent
3202f5f
commit 569bbd6
Showing
8 changed files
with
459 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/** | ||
* Класс для отправки запросов к серверу | ||
*/ | ||
export default class ApiService { | ||
/** | ||
* @param {string} endPoint Адрес сервера | ||
* @param {string} authorization Авторизационный токен | ||
*/ | ||
constructor(endPoint, authorization) { | ||
this._endPoint = endPoint; | ||
this._authorization = authorization; | ||
} | ||
|
||
/** | ||
* Метод для отправки запроса к серверу | ||
* @param {Object} config Объект с настройками | ||
* @param {string} config.url Адрес относительно сервера | ||
* @param {string} [config.method] Метод запроса | ||
* @param {string} [config.body] Тело запроса | ||
* @param {Headers} [config.headers] Заголовки запроса | ||
* @returns {Promise<Response>} | ||
*/ | ||
async _load({ | ||
url, | ||
method = 'GET', | ||
body = null, | ||
headers = new Headers(), | ||
}) { | ||
headers.append('Authorization', this._authorization); | ||
|
||
const response = await fetch( | ||
`${this._endPoint}/${url}`, | ||
{method, body, headers}, | ||
); | ||
|
||
try { | ||
ApiService.checkStatus(response); | ||
return response; | ||
} catch (err) { | ||
ApiService.catchError(err); | ||
} | ||
} | ||
|
||
/** | ||
* Метод для обработки ответа | ||
* @param {Response} response Объект ответа | ||
* @returns {Promise<JSON>} | ||
*/ | ||
static parseResponse(response) { | ||
return response.json(); | ||
} | ||
|
||
/** | ||
* Метод для проверки ответа | ||
* @param {Response} response Объект ответа | ||
*/ | ||
static checkStatus(response) { | ||
if (!response.ok) { | ||
throw new Error(`${response.status}: ${response.statusText}`); | ||
} | ||
} | ||
|
||
/** | ||
* Метод для обработки ошибок | ||
* @param {Error} err Объект ошибки | ||
*/ | ||
static catchError(err) { | ||
throw err; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/** | ||
* Класс, реализующий паттерн Наблюдатель. | ||
*/ | ||
export default class Observable { | ||
/** @type {Set<observerCallback>} Множество функций типа observerCallback */ | ||
#observers = new Set(); | ||
|
||
/** | ||
* Метод, позволяющий подписаться на событие | ||
* @param {observerCallback} observer Функция, которая будет вызвана при наступлении события | ||
*/ | ||
addObserver(observer) { | ||
this.#observers.add(observer); | ||
} | ||
|
||
/** | ||
* Метод, позволяющий отписаться от события | ||
* @param {observerCallback} observer Функция, которую больше не нужно вызывать при наступлении события | ||
*/ | ||
removeObserver(observer) { | ||
this.#observers.delete(observer); | ||
} | ||
|
||
/** | ||
* Метод для оповещения подписчиков о наступлении события | ||
* @param {*} event Тип события | ||
* @param {*} payload Дополнительная информация | ||
*/ | ||
_notify(event, payload) { | ||
this.#observers.forEach((observer) => observer(event, payload)); | ||
} | ||
} | ||
|
||
/** | ||
* Функция, которая будет вызвана при наступлении события | ||
* @callback observerCallback | ||
* @param {*} event Тип события | ||
* @param {*} [payload] Дополнительная информация | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import AbstractView from './view/abstract-view.js'; | ||
|
||
/** @enum {string} Перечисление возможных позиций для отрисовки */ | ||
const RenderPosition = { | ||
BEFOREBEGIN: 'beforebegin', | ||
AFTERBEGIN: 'afterbegin', | ||
BEFOREEND: 'beforeend', | ||
AFTEREND: 'afterend', | ||
}; | ||
|
||
/** | ||
* Функция для создания элемента на основе разметки | ||
* @param {string} template Разметка в виде строки | ||
* @returns {HTMLElement} Созданный элемент | ||
*/ | ||
function createElement(template) { | ||
const newElement = document.createElement('div'); | ||
newElement.innerHTML = template; | ||
|
||
return newElement.firstElementChild; | ||
} | ||
|
||
/** | ||
* Функция для отрисовки элемента | ||
* @param {AbstractView} component Компонент, который должен был отрисован | ||
* @param {HTMLElement} container Элемент в котором будет отрисован компонент | ||
* @param {string} place Позиция компонента относительно контейнера. По умолчанию - `beforeend` | ||
*/ | ||
function render(component, container, place = RenderPosition.BEFOREEND) { | ||
if (!(component instanceof AbstractView)) { | ||
throw new Error('Can render only components'); | ||
} | ||
|
||
if (container === null) { | ||
throw new Error('Container element doesn\'t exist'); | ||
} | ||
|
||
container.insertAdjacentElement(place, component.element); | ||
} | ||
|
||
/** | ||
* Функция для замены одного компонента на другой | ||
* @param {AbstractView} newComponent Компонент, который нужно показать | ||
* @param {AbstractView} oldComponent Компонент, который нужно скрыть | ||
*/ | ||
function replace(newComponent, oldComponent) { | ||
if (!(newComponent instanceof AbstractView && oldComponent instanceof AbstractView)) { | ||
throw new Error('Can replace only components'); | ||
} | ||
|
||
const newElement = newComponent.element; | ||
const oldElement = oldComponent.element; | ||
|
||
const parent = oldElement.parentElement; | ||
|
||
if (parent === null) { | ||
throw new Error('Parent element doesn\'t exist'); | ||
} | ||
|
||
parent.replaceChild(newElement, oldElement); | ||
} | ||
|
||
/** | ||
* Функция для удаления компонента | ||
* @param {AbstractView} component Компонент, который нужно удалить | ||
*/ | ||
function remove(component) { | ||
if (component === null) { | ||
return; | ||
} | ||
|
||
if (!(component instanceof AbstractView)) { | ||
throw new Error('Can remove only components'); | ||
} | ||
|
||
component.element.remove(); | ||
component.removeElement(); | ||
} | ||
|
||
export {RenderPosition, createElement, render, replace, remove}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
.ui-blocker { | ||
display: none; | ||
place-content: center; | ||
position: absolute; | ||
top: 0; | ||
right: 0; | ||
bottom: 0; | ||
left: 0; | ||
z-index: 1000; | ||
cursor: wait; | ||
background-color: rgba(255, 255, 255, 0.5); | ||
} | ||
|
||
.ui-blocker::before { | ||
content: ""; | ||
display: block; | ||
border-radius: 50%; | ||
border: 6px solid #4285F4; | ||
box-sizing: border-box; | ||
animation: sweep 1s linear alternate infinite, | ||
rotate 0.8s linear infinite; | ||
width: 65px; | ||
height: 65px; | ||
} | ||
|
||
.ui-blocker--on { | ||
display: grid; | ||
} | ||
|
||
@keyframes rotate { | ||
from { | ||
transform: rotate(0deg); | ||
} | ||
to { | ||
transform: rotate(360deg); | ||
} | ||
} | ||
|
||
@keyframes sweep { | ||
0% { | ||
clip-path: polygon(0% 0%, 0% 0%, 0% 0%, 50% 50%, 0% 0%, 0% 0%, 0% 0%); | ||
} | ||
50% { | ||
clip-path: polygon(0% 0%, 0% 100%, 0% 100%, 50% 50%, 100% 0%, 100% 0%, 0% 0%); | ||
} | ||
100% { | ||
clip-path: polygon(0% 0%, 0% 100%, 100% 100%, 50% 50%, 100% 100%, 100% 0%, 0% 0%); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import './ui-blocker.css'; | ||
|
||
/** | ||
* Класс для блокировки интерфейса | ||
*/ | ||
export default class UiBlocker { | ||
/** @type {number} Время до блокировки интерфейса в миллисекундах */ | ||
#lowerLimit; | ||
|
||
/** @type {number} Минимальное время блокировки интерфейса в миллисекундах */ | ||
#upperLimit; | ||
|
||
/** @type {HTMLElement|null} Элемент, блокирующий интерфейс */ | ||
#element; | ||
|
||
/** @type {number} Время вызова метода block */ | ||
#startTime; | ||
|
||
/** @type {number} Время вызова метода unblock */ | ||
#endTime; | ||
|
||
/** @type {number} Идентификатор таймера */ | ||
#timerId; | ||
|
||
/** | ||
* @param {Object} config Объект с настройками блокировщика | ||
* @param {number} config.lowerLimit Время до блокировки интерфейса в миллисекундах. Если вызвать метод unblock раньше, то интерфейс заблокирован не будет | ||
* @param {number} config.upperLimit Минимальное время блокировки в миллисекундах. Минимальная длительность блокировки | ||
*/ | ||
constructor({lowerLimit, upperLimit}) { | ||
this.#lowerLimit = lowerLimit; | ||
this.#upperLimit = upperLimit; | ||
|
||
this.#element = document.createElement('div'); | ||
this.#element.classList.add('ui-blocker'); | ||
document.body.append(this.#element); | ||
} | ||
|
||
/** Метод для блокировки интерфейса */ | ||
block() { | ||
this.#startTime = Date.now(); | ||
this.#timerId = setTimeout(() => { | ||
this.#addClass(); | ||
}, this.#lowerLimit); | ||
} | ||
|
||
/** Метод для разблокировки интерфейса */ | ||
unblock() { | ||
this.#endTime = Date.now(); | ||
const duration = this.#endTime - this.#startTime; | ||
|
||
if (duration < this.#lowerLimit) { | ||
clearTimeout(this.#timerId); | ||
return; | ||
} | ||
|
||
if (duration >= this.#upperLimit) { | ||
this.#removeClass(); | ||
return; | ||
} | ||
|
||
setTimeout(this.#removeClass, this.#upperLimit - duration); | ||
} | ||
|
||
/** Метод, добавляющий CSS-класс элементу */ | ||
#addClass = () => { | ||
this.#element.classList.add('ui-blocker--on'); | ||
}; | ||
|
||
/** Метод, убирающий CSS-класс с элемента */ | ||
#removeClass = () => { | ||
this.#element.classList.remove('ui-blocker--on'); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import AbstractView from './abstract-view.js'; | ||
|
||
/** | ||
* Абстрактный класс представления с состоянием | ||
*/ | ||
export default class AbstractStatefulView extends AbstractView { | ||
/** @type {Object} Объект состояния */ | ||
_state = {}; | ||
|
||
/** | ||
* Метод для обновления состояния и перерисовки элемента | ||
* @param {Object} update Объект с обновлённой частью состояния | ||
*/ | ||
updateElement(update) { | ||
if (!update) { | ||
return; | ||
} | ||
|
||
this._setState(update); | ||
|
||
this.#rerenderElement(); | ||
} | ||
|
||
/** | ||
* Метод для восстановления обработчиков после перерисовки элемента | ||
* @abstract | ||
*/ | ||
_restoreHandlers() { | ||
throw new Error('Abstract method not implemented: restoreHandlers'); | ||
} | ||
|
||
/** | ||
* Метод для обновления состояния | ||
* @param {Object} update Объект с обновлённой частью состояния | ||
*/ | ||
_setState(update) { | ||
this._state = structuredClone({...this._state, ...update}); | ||
} | ||
|
||
/** Метод для перерисовки элемента */ | ||
#rerenderElement() { | ||
const prevElement = this.element; | ||
const parent = prevElement.parentElement; | ||
this.removeElement(); | ||
|
||
const newElement = this.element; | ||
|
||
parent.replaceChild(newElement, prevElement); | ||
|
||
this._restoreHandlers(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
.shake { | ||
animation: shake 0.6s; | ||
position: relative; | ||
z-index: 10; | ||
} | ||
|
||
@keyframes shake { | ||
0%, | ||
100% { | ||
transform: translateX(0); | ||
} | ||
|
||
10%, | ||
30%, | ||
50%, | ||
70%, | ||
90% { | ||
transform: translateX(-5px); | ||
} | ||
|
||
20%, | ||
40%, | ||
60%, | ||
80% { | ||
transform: translateX(5px); | ||
} | ||
} |
Oops, something went wrong.