From a44fcefdad42e98eece51d1c85c9cbab524d08c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Mon, 8 Apr 2024 22:16:42 +0500 Subject: [PATCH 01/14] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D1=83=D1=87=D0=B0?= =?UTF-8?q?=D0=B5=D0=BC=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BE=D1=82=20=D0=9A=D0=B5=D0=BA=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/framework/api-service.js | 70 +++++++++++++++++ src/framework/observable.js | 39 ++++++++++ src/framework/render.js | 80 ++++++++++++++++++++ src/framework/ui-blocker/ui-blocker.css | 49 ++++++++++++ src/framework/ui-blocker/ui-blocker.js | 74 ++++++++++++++++++ src/framework/view/abstract-stateful-view.js | 52 +++++++++++++ src/framework/view/abstract-view.css | 27 +++++++ src/framework/view/abstract-view.js | 65 ++++++++++++++++ 8 files changed, 456 insertions(+) create mode 100644 src/framework/api-service.js create mode 100644 src/framework/observable.js create mode 100644 src/framework/render.js create mode 100644 src/framework/ui-blocker/ui-blocker.css create mode 100644 src/framework/ui-blocker/ui-blocker.js create mode 100644 src/framework/view/abstract-stateful-view.js create mode 100644 src/framework/view/abstract-view.css create mode 100644 src/framework/view/abstract-view.js diff --git a/src/framework/api-service.js b/src/framework/api-service.js new file mode 100644 index 0000000..ef9f928 --- /dev/null +++ b/src/framework/api-service.js @@ -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} + */ + 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} + */ + 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; + } +} diff --git a/src/framework/observable.js b/src/framework/observable.js new file mode 100644 index 0000000..026045d --- /dev/null +++ b/src/framework/observable.js @@ -0,0 +1,39 @@ +/** + * Класс, реализующий паттерн Наблюдатель. + */ +export default class Observable { + /** @type {Set} Множество функций типа 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] Дополнительная информация + */ diff --git a/src/framework/render.js b/src/framework/render.js new file mode 100644 index 0000000..2e089db --- /dev/null +++ b/src/framework/render.js @@ -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}; diff --git a/src/framework/ui-blocker/ui-blocker.css b/src/framework/ui-blocker/ui-blocker.css new file mode 100644 index 0000000..489756d --- /dev/null +++ b/src/framework/ui-blocker/ui-blocker.css @@ -0,0 +1,49 @@ +.ui-blocker { + display: none; + place-content: center; + position: fixed; + top: 0; + left: 0; + min-width: 100%; + min-height: 100%; + 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%); + } +} diff --git a/src/framework/ui-blocker/ui-blocker.js b/src/framework/ui-blocker/ui-blocker.js new file mode 100644 index 0000000..db9ab7b --- /dev/null +++ b/src/framework/ui-blocker/ui-blocker.js @@ -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'); + }; +} diff --git a/src/framework/view/abstract-stateful-view.js b/src/framework/view/abstract-stateful-view.js new file mode 100644 index 0000000..66f68ae --- /dev/null +++ b/src/framework/view/abstract-stateful-view.js @@ -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(); + } +} diff --git a/src/framework/view/abstract-view.css b/src/framework/view/abstract-view.css new file mode 100644 index 0000000..04070e2 --- /dev/null +++ b/src/framework/view/abstract-view.css @@ -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); + } +} diff --git a/src/framework/view/abstract-view.js b/src/framework/view/abstract-view.js new file mode 100644 index 0000000..fa2a552 --- /dev/null +++ b/src/framework/view/abstract-view.js @@ -0,0 +1,65 @@ +import {createElement} from '../render.js'; +import './abstract-view.css'; + +/** @const {string} Класс, реализующий эффект "покачивания головой" */ +const SHAKE_CLASS_NAME = 'shake'; + +/** @const {number} Время анимации в миллисекундах */ +const SHAKE_ANIMATION_TIMEOUT = 600; + +/** + * Абстрактный класс представления + */ +export default class AbstractView { + /** @type {HTMLElement|null} Элемент представления */ + #element = null; + + constructor() { + if (new.target === AbstractView) { + throw new Error('Can\'t instantiate AbstractView, only concrete one.'); + } + } + + /** + * Геттер для получения элемента + * @returns {HTMLElement} Элемент представления + */ + get element() { + if (!this.#element) { + this.#element = createElement(this.template); + } + + return this.#element; + } + + /** + * Геттер для получения разметки элемента + * @abstract + * @returns {string} Разметка элемента в виде строки + */ + get template() { + throw new Error('Abstract method not implemented: get template'); + } + + /** Метод для удаления элемента */ + removeElement() { + this.#element = null; + } + + /** + * Метод, реализующий эффект "покачивания головой" + * @param {shakeCallback} [callback] Функция, которая будет вызвана после завершения анимации + */ + shake(callback) { + this.element.classList.add(SHAKE_CLASS_NAME); + setTimeout(() => { + this.element.classList.remove(SHAKE_CLASS_NAME); + callback?.(); + }, SHAKE_ANIMATION_TIMEOUT); + } +} + +/** + * Функция, которая будет вызвана методом shake после завершения анимации + * @callback shakeCallback + */ From 002876c40bccf1cca98d7928ece2cb7b079d7475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Wed, 10 Apr 2024 21:05:16 +0500 Subject: [PATCH 02/14] =?UTF-8?q?=D0=92=D0=BE=D1=81=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB=20=D0=B8=D0=B7=20module2-task1?= =?UTF-8?q?=20=D1=81=D0=BB=D1=83=D1=87=D0=B0=D0=B9=D0=BD=D0=BE=20=D1=83?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=BF=D1=80=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=D1=82=20=D0=9A=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/const.js | 69 ++++++++++++ src/main.js | 31 +++++ src/mock/destinations.js | 36 ++++++ src/mock/event-points.js | 26 +++++ src/mock/offers.js | 106 +++++++++++++++++ src/model/destinations-model.js | 15 +++ src/model/event-points-model.js | 12 ++ src/model/offers-model.js | 15 +++ src/presenter/board-presenter.js | 34 ++++++ src/utils.js | 54 +++++++++ src/view/create-filter.js | 49 ++++++++ src/view/create-item.js | 63 +++++++++++ src/view/create-sort.js | 52 +++++++++ src/view/create-trip.js | 35 ++++++ src/view/creator-view.js | 188 +++++++++++++++++++++++++++++++ src/view/editor-view.js | 181 +++++++++++++++++++++++++++++ src/view/events-list.js | 25 ++++ 17 files changed, 991 insertions(+) create mode 100644 src/const.js create mode 100644 src/mock/destinations.js create mode 100644 src/mock/event-points.js create mode 100644 src/mock/offers.js create mode 100644 src/model/destinations-model.js create mode 100644 src/model/event-points-model.js create mode 100644 src/model/offers-model.js create mode 100644 src/presenter/board-presenter.js create mode 100644 src/utils.js create mode 100644 src/view/create-filter.js create mode 100644 src/view/create-item.js create mode 100644 src/view/create-sort.js create mode 100644 src/view/create-trip.js create mode 100644 src/view/creator-view.js create mode 100644 src/view/editor-view.js create mode 100644 src/view/events-list.js diff --git a/src/const.js b/src/const.js new file mode 100644 index 0000000..cf5a475 --- /dev/null +++ b/src/const.js @@ -0,0 +1,69 @@ +const POINTS_COUNT = 5; +const MSEC_IN_SEC = 1000; +const SEC_IN_MIN = 60; +const MIN_IN_HOUR = 60; +const HOUR_IN_DAY = 24; +const MAX_PRICE_VALUE = 200; +const MSEC_IN_HOUR = MSEC_IN_SEC * SEC_IN_MIN * MIN_IN_HOUR; +const MSEC_IN_DAY = MSEC_IN_HOUR * HOUR_IN_DAY; + +const EVENT_TYPES = [ + 'Taxi', + 'Bus', + 'Train', + 'Ship', 'Drive', + 'Flight', + 'Check-in', + 'Sightseeing', + 'Restaurant']; + + +const CITIES = [ + 'Paris', + 'London', + 'Chicago', + 'Tokio', + 'New York', + 'Moscow', + 'Amsterdam', + 'San-Francisco' +]; + + +const DESCCRIPTIONS = [ + 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Incidunt architecto labore atque!', + 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Voluptatem exercitationem culpa, molestias qui eveniet corrupti?', + 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Eius, dolorem.', + 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit ad eaque cupiditate praesentium maxime.', +]; + + +const DATE_FORMATS = { + time: 'HH:mm', + shortDate: 'MMM DD', + formDateTime: 'DD/MM/YY HH:mm' +}; + + +const DURATION_FORMATS = { + days: 'DD[D] HH[H] mm[M]', + hours: 'HH[H] mm[M]', + mins: 'mm[M]' +}; + + +export { +POINTS_COUNT, +MSEC_IN_SEC, +SEC_IN_MIN, +MIN_IN_HOUR, +HOUR_IN_DAY, +MAX_PRICE_VALUE, +MSEC_IN_HOUR, +MSEC_IN_DAY, +EVENT_TYPES, +CITIES, +DESCCRIPTIONS, +DATE_FORMATS, +DURATION_FORMATS, +}; diff --git a/src/main.js b/src/main.js index e69de29..90858d7 100644 --- a/src/main.js +++ b/src/main.js @@ -0,0 +1,31 @@ +import { + render, + RenderPosition +} from './render.js'; +import TripInfoView from './view/create-trip.js'; +import FiltersView from './view/create-filter.js'; +import PointsPresenter from './presenter/board-presenter.js'; + +const tripMainContainer = document.querySelector('.trip-main'); +const tripEventsContainer = document.querySelector('.trip-events'); +const filtersContainer = tripMainContainer.querySelector('.trip-controls__filters'); + +import PointsModel from './model/event-points-model.js'; +import OffersModel from './model/offers-model.js'; +import DestinationsModel from './model/destinations-model.js'; + +const pointsModel = new PointsModel(); +const offersModel = new OffersModel(); +const destinationsModel = new DestinationsModel(); + +const pointsPresenter = new PointsPresenter({ + tripEventsContainer, + pointsModel, + offersModel, + destinationsModel +}); + +render(new TripInfoView(), tripMainContainer, RenderPosition.AFTERBEGIN); +render(new FiltersView(), filtersContainer); + +pointsPresenter.init(); diff --git a/src/mock/destinations.js b/src/mock/destinations.js new file mode 100644 index 0000000..091aa77 --- /dev/null +++ b/src/mock/destinations.js @@ -0,0 +1,36 @@ +import { + DESCCRIPTIONS, + CITIES, +} from '../const.js'; + +import { + incrementCounter, + getRandomArrayElement, + getRandomPositiveNumber +} from '../utils.js'; +const MAX_IMAGES_COUNT = 5; +const START_ID_COUNTER = 1; +const getCityID = incrementCounter(START_ID_COUNTER); + +const setupDestination = () => { + const ID = getCityID(); + return ({ + id: (ID).toString(), + description: getRandomArrayElement(DESCCRIPTIONS), + name: CITIES[ID - 1], + pictures: Array.from({ + length: getRandomPositiveNumber(MAX_IMAGES_COUNT) + }, () => ({ + src: `https://loremflickr.com/248/152?121`, + description: getRandomArrayElement(DESCCRIPTIONS) + })) + }); +}; + +const getDestinations = () => Array.from({ + length: CITIES.length +}, setupDestination); + +export { + getDestinations +}; diff --git a/src/mock/event-points.js b/src/mock/event-points.js new file mode 100644 index 0000000..f4c1d5f --- /dev/null +++ b/src/mock/event-points.js @@ -0,0 +1,26 @@ +import { EVENT_TYPES, MAX_PRICE_VALUE, CITIES } from '../const.js'; +import { getRandomArrayElement, getRandomPositiveNumber } from '../utils.js'; + +// Определяем функцию для генерации моковой точки события +const generateMockEventPoint = () => { + const randomType = getRandomArrayElement(EVENT_TYPES); // Случайный тип события + const randomCityIndex = getRandomPositiveNumber(CITIES.length); // Случайный индекс города из списка + + return { + basePrice: getRandomPositiveNumber(MAX_PRICE_VALUE), // Случайная цена + dateFrom: new Date(Date.now() + Math.random() * 1000000000), // Случайная дата начала события в будущем + dateTo: new Date(Date.now() + Math.random() * 2000000000), // Случайная дата окончания события в будущем + destination: CITIES[randomCityIndex], // Случайный город + isFavorite: Math.random() < 0.5, // Случайное булевое значение для избранного + offers: [ '1', '2' ], // Предложения (в данном случае - значения по умолчанию) + type: randomType // Тип события + }; +}; + +// Генерируем массив моковых точек событий +const mockEventPoints = Array.from({ length: 5 }, generateMockEventPoint); + +// Определяем функцию для получения случайной точки события из моковых данных +const getRandomEventPoint = () => getRandomArrayElement(mockEventPoints); + +export { getRandomEventPoint }; diff --git a/src/mock/offers.js b/src/mock/offers.js new file mode 100644 index 0000000..3fe425d --- /dev/null +++ b/src/mock/offers.js @@ -0,0 +1,106 @@ +import { + MAX_PRICE_VALUE +} from '../const.js'; + +import { + getRandomPositiveNumber, +} from '../utils.js'; + +const mockOffers = [{ + type: 'taxi', + offers: [{ + id: '1', + title: 'Upgrade to a business class', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }, { + id: '2', + title: 'Drive slowly', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }] +}, { + type: 'bus', + offers: [{ + id: '1', + title: 'Order meal', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }, { + id: '2', + title: 'Choose seats', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }] +}, { + type: 'train', + offers: [{ + id: '1', + title: 'Book a taxi at the arrival point', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }, { + id: '2', + title: 'Choose seats', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }] +}, { + type: 'flight', + offers: [{ + id: '1', + title: 'Choose meal', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }, { + id: '2', + title: 'Choose seats', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }] +}, { + type: 'check-in', + offers: [{ + id: '1', + title: 'Choose the time of check-in', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }, { + id: '2', + title: 'Choose the time of check-out', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }] +}, { + type: 'sightseeing', + offers: [] +}, { + type: 'ship', + offers: [{ + id: '1', + title: 'Upgrade to comfort class', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }, { + id: '2', + title: 'Upgrade to business class', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }] +}, { + type: 'drive', + offers: [{ + id: '1', + title: 'With automatic transmission', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }, { + id: '2', + title: 'With air conditioning', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }] +}, { + type: 'restaurant', + offers: [{ + id: '1', + title: 'Choose live music', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }, { + id: '2', + title: 'Choose VIP area', + price: getRandomPositiveNumber(MAX_PRICE_VALUE), + }] +}]; + +const getOffers = () => mockOffers; + +export { + getOffers +}; diff --git a/src/model/destinations-model.js b/src/model/destinations-model.js new file mode 100644 index 0000000..eb046b2 --- /dev/null +++ b/src/model/destinations-model.js @@ -0,0 +1,15 @@ +import { getDestinations } from '../mock/destinations.js'; + +export default class DestinationsModel { + constructor() { + this.destinations = getDestinations(); + } + + get() { + return this.destinations; + } + + getById(id) { + return this.destinations.find((destination) => destination.id === id); + } +} diff --git a/src/model/event-points-model.js b/src/model/event-points-model.js new file mode 100644 index 0000000..312faf3 --- /dev/null +++ b/src/model/event-points-model.js @@ -0,0 +1,12 @@ +import { getRandomEventPoint } from '../mock/event-points.js'; +import { POINTS_COUNT } from '../const.js'; + +export default class PointsModel { + constructor() { + this.points = Array.from({ length: POINTS_COUNT }, getRandomEventPoint); + } + + getEventPoints() { + return this.points; + } +} diff --git a/src/model/offers-model.js b/src/model/offers-model.js new file mode 100644 index 0000000..7d4d36f --- /dev/null +++ b/src/model/offers-model.js @@ -0,0 +1,15 @@ +import { getOffers } from '../mock/offers.js'; + +export default class OffersModel { + constructor() { + this.offers = getOffers(); + } + + get() { + return this.offers; + } + + getByType(type) { + return this.offers.find((offers) => offers.type === type.toLowerCase()).offers; + } +} diff --git a/src/presenter/board-presenter.js b/src/presenter/board-presenter.js new file mode 100644 index 0000000..fedb24d --- /dev/null +++ b/src/presenter/board-presenter.js @@ -0,0 +1,34 @@ +import { render } from '../render.js'; +import EventListView from '../view/events-list.js'; +import EventItemView from '../view/create-item.js'; +import PointEditorView from '../view/editor-view.js'; +import SortingView from '../view/create-sort.js'; + +export default class PointsPresenter { + listComponent = new EventListView(); + sortingComponent = new SortingView(); + + constructor({ tripEventsContainer, pointsModel, offersModel, destinationsModel }) { + this.tripEventsContainer = tripEventsContainer; + this.pointsModel = pointsModel; + this.offersModel = offersModel; + this.destinationsModel = destinationsModel; + } + + init() { + this.eventPoints = this.pointsModel.getEventPoints(); + render(this.sortingComponent, this.tripEventsContainer); + render(this.listComponent, this.tripEventsContainer); + render(new PointEditorView(), this.listComponent.getElement()); + + for (let i = 0; i < this.eventPoints.length; i++) { + const offers = this.offersModel.getByType(this.eventPoints[i].type); + const destination = this.destinationsModel.getById(this.eventPoints[i].destination); + render(new EventItemView({ + eventPoint: this.eventPoints[i], + offers, + destination + }), this.listComponent.getElement()); + } + } +} diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..b60f2f7 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,54 @@ +import dayjs from 'dayjs'; +import duration from 'dayjs/plugin/duration'; +dayjs.extend(duration); +import { + MSEC_IN_HOUR, + MSEC_IN_DAY, + DURATION_FORMATS +} from './const.js'; + +// Функция для получения случайного элемента из массива +const getRandomArrayElement = (items) => items[Math.floor(Math.random() * items.length)]; + +// Функция для генерации случайного положительного числа +const getRandomPositiveNumber = (max) => Math.ceil(Math.random() * max); + +// Функция для форматирования даты в указанном формате +const humanizeDate = (currentDate, format) => currentDate ? dayjs(currentDate).format(format) : ''; + +// Функция для вычисления продолжительности между двумя датами +const calculateDuration = (dateFrom, dateTo) => { + const diff = dayjs(dateTo).diff(dayjs(dateFrom)); + + let pointDuration; + + switch (true) { + case (diff >= MSEC_IN_DAY): + pointDuration = dayjs.duration(diff).format(DURATION_FORMATS.days); + break; + case (diff >= MSEC_IN_HOUR): + pointDuration = dayjs.duration(diff).format(DURATION_FORMATS.hours); + break; + case (diff < MSEC_IN_HOUR): + pointDuration = dayjs.duration(diff).format(DURATION_FORMATS.mins); + break; + } + + return pointDuration; +}; + +// Функция для инкрементации счетчика +const incrementCounter = (START_FROM) => { + let counterStart = START_FROM; + return function() { + return counterStart++; + }; +}; + +export { + getRandomArrayElement, + getRandomPositiveNumber, + humanizeDate, + calculateDuration, + incrementCounter +}; diff --git a/src/view/create-filter.js b/src/view/create-filter.js new file mode 100644 index 0000000..de10c10 --- /dev/null +++ b/src/view/create-filter.js @@ -0,0 +1,49 @@ +import { + createElement +} from '../render.js'; + +function createFiltersTemplate() { + return ( + `
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
` + ); +} + +export default class FiltersView { + getTemplate() { + return createFiltersTemplate(); + } + + getElement() { + if (!this.element) { + this.element = createElement(this.getTemplate()); + } + + return this.element; + } + + removeElement() { + this.element = null; + } +} diff --git a/src/view/create-item.js b/src/view/create-item.js new file mode 100644 index 0000000..2e0f0db --- /dev/null +++ b/src/view/create-item.js @@ -0,0 +1,63 @@ +import { + createElement +} from '../render.js'; + +function createEventItemTemplate() { + return ( + `
  • +
    + +
    + Event type icon +
    +

    Taxi Amsterdam

    +
    +

    + + — + +

    +

    30M

    +
    +

    + € 20 +

    +

    Offers:

    +
      +
    • + Order Uber + +€  + 20 +
    • +
    + + +
    +
  • ` + ); +} + +export default class EventItemView { + getTemplate() { + return createEventItemTemplate(); + } + + getElement() { + if (!this.element) { + this.element = createElement(this.getTemplate()); + } + + return this.element; + } + + removeElement() { + this.element = null; + } +} diff --git a/src/view/create-sort.js b/src/view/create-sort.js new file mode 100644 index 0000000..1f139a8 --- /dev/null +++ b/src/view/create-sort.js @@ -0,0 +1,52 @@ +import { + createElement +} from '../render.js'; + +function createSortingTemplate() { + return ( + `
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    +
    ` + ); +} + +export default class SortingView { + getTemplate() { + return createSortingTemplate(); + } + + getElement() { + if (!this.element) { + this.element = createElement(this.getTemplate()); + } + + return this.element; + } + + removeElement() { + this.element = null; + } +} diff --git a/src/view/create-trip.js b/src/view/create-trip.js new file mode 100644 index 0000000..8c5168f --- /dev/null +++ b/src/view/create-trip.js @@ -0,0 +1,35 @@ +import { + createElement +} from '../render.js'; + +function createTripInfoTemplate() { + return ( + `
    +
    +

    Amsterdam — Chamonix — Geneva

    +

    Mar 18 — 20

    +
    +

    + Total: € 1230 +

    +
    ` + ); +} + +export default class TripInfoView { + getTemplate() { + return createTripInfoTemplate(); + } + + getElement() { + if (!this.element) { + this.element = createElement(this.getTemplate()); + } + + return this.element; + } + + removeElement() { + this.element = null; + } +} diff --git a/src/view/creator-view.js b/src/view/creator-view.js new file mode 100644 index 0000000..70426e2 --- /dev/null +++ b/src/view/creator-view.js @@ -0,0 +1,188 @@ +import { + createElement +} from '../render.js'; + +function createNewPointTemplate() { + return ( + `
  • +
    +
    +
    + + + +
    +
    + Event type + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    +
    +
    +
    + +
    + + + + + + + +
    + +
    + + + — + + +
    + +
    + + +
    + + + +
    +
    +
    +

    Offers

    + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    +
    +
    + +
    +

    Destination

    +

    Geneva is a city in Switzerland that lies at the southern tip of expansive Lac Léman (Lake Geneva). Surrounded by the Alps and Jura mountains, the city has views of dramatic Mont Blanc.

    + +
    +
    + Event photo + Event photo + Event photo + Event photo + Event photo +
    +
    +
    +
    +
    +
  • ` + ); +} + +export default class NewPointView { + getTemplate() { + return createNewPointTemplate(); + } + + getElement() { + if (!this.element) { + this.element = createElement(this.getTemplate()); + } + + return this.element; + } + + removeElement() { + this.element = null; + } +} diff --git a/src/view/editor-view.js b/src/view/editor-view.js new file mode 100644 index 0000000..373c1f3 --- /dev/null +++ b/src/view/editor-view.js @@ -0,0 +1,181 @@ +import { + createElement +} from '../render.js'; + +function createPointEditorTemplate() { + return ( + `
  • +
    +
    +
    + + + +
    +
    + Event type + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    +
    +
    +
    + +
    + + + + + + + +
    + +
    + + + — + + +
    + +
    + + +
    + + + + +
    +
    +
    +

    Offers

    + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    +
    +
    + +
    +

    Destination

    +

    Chamonix-Mont-Blanc (usually shortened to Chamonix) is a resort area near the junction of France, Switzerland and Italy. At the base of Mont Blanc, the highest summit in the Alps, it's renowned for its skiing.

    +
    +
    +
    +
  • ` + ); +} + +export default class PointEditorView { + getTemplate() { + return createPointEditorTemplate(); + } + + getElement() { + if (!this.element) { + this.element = createElement(this.getTemplate()); + } + + return this.element; + } + + removeElement() { + this.element = null; + } +} diff --git a/src/view/events-list.js b/src/view/events-list.js new file mode 100644 index 0000000..964a599 --- /dev/null +++ b/src/view/events-list.js @@ -0,0 +1,25 @@ +import { + createElement +} from '../render.js'; + +function createEventListTemplate() { + return '
      '; +} + +export default class EventListView { + getTemplate() { + return createEventListTemplate(); + } + + getElement() { + if (!this.element) { + this.element = createElement(this.getTemplate()); + } + + return this.element; + } + + removeElement() { + this.element = null; + } +} From 18aa4ce59b4fc48d091d405c710c0d9e6a1a1b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Wed, 10 Apr 2024 21:29:31 +0500 Subject: [PATCH 03/14] =?UTF-8?q?=D0=9D=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B9=D0=BA=D0=B0=20webpack=20(=D0=9B=D0=BE=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D1=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 380 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 + webpack.config.js | 4 + 3 files changed, 386 insertions(+) diff --git a/package-lock.json b/package-lock.json index 0fb3df8..dd44d2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,11 @@ "@babel/preset-env": "^7.24.4", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", + "css-loader": "^7.1.1", "eslint": "8.28.0", "eslint-config-htmlacademy": "8.0.0", "html-webpack-plugin": "^5.6.0", + "style-loader": "^4.0.0", "webpack": "^5.91.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" @@ -3321,6 +3323,74 @@ "node": ">= 8" } }, + "node_modules/css-loader": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.1.tgz", + "integrity": "sha512-OxIR5P2mjO1PSXk44bWuQ8XtMK4dpEqpIyERCx3ewOo3I8EmbcxMPUc5ScLtQfgXtOojoMv57So4V/C02HQLsw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-loader/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-loader/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/css-select": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", @@ -3349,6 +3419,18 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4981,6 +5063,18 @@ "node": ">=0.10.0" } }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -5813,6 +5907,24 @@ "multicast-dns": "cli.js" } }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6330,6 +6442,112 @@ "node": ">=8" } }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7090,6 +7308,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -7304,6 +7531,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -10726,6 +10969,48 @@ "which": "^2.0.1" } }, + "css-loader": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.1.tgz", + "integrity": "sha512-OxIR5P2mjO1PSXk44bWuQ8XtMK4dpEqpIyERCx3ewOo3I8EmbcxMPUc5ScLtQfgXtOojoMv57So4V/C02HQLsw==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "css-select": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", @@ -10745,6 +11030,12 @@ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "dev": true }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -11935,6 +12226,13 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "requires": {} + }, "ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -12510,6 +12808,12 @@ "thunky": "^1.0.2" } }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -12888,6 +13192,69 @@ } } }, + "postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + } + }, + "postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-selector-parser": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -13465,6 +13832,12 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, + "source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true + }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -13630,6 +14003,13 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "requires": {} + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index af36ef9..bcfa74d 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,11 @@ "@babel/preset-env": "^7.24.4", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", + "css-loader": "^7.1.1", "eslint": "8.28.0", "eslint-config-htmlacademy": "8.0.0", "html-webpack-plugin": "^5.6.0", + "style-loader": "^4.0.0", "webpack": "^5.91.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" diff --git a/webpack.config.js b/webpack.config.js index 5dd8e50..394091c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -37,6 +37,10 @@ module.exports = { }, }, }, + { + test: /\.css$/i, + use: ['style-loader', 'css-loader'] + }, ] } }; From e2bf93bc1b879ec69ff13a3e37f06aa9b7292e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Wed, 10 Apr 2024 21:36:43 +0500 Subject: [PATCH 04/14] =?UTF-8?q?=D0=92=D0=BE=D1=81=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=BA=D0=B0=20dayjs=20=D0=B8=20duration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 199 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 + 2 files changed, 203 insertions(+) diff --git a/package-lock.json b/package-lock.json index dd44d2b..7c38823 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,10 @@ "": { "name": "big-trip", "version": "19.0.0", + "dependencies": { + "dayjs": "^1.11.10", + "duration": "^0.2.2" + }, "devDependencies": { "@babel/core": "^7.24.4", "@babel/preset-env": "^7.24.4", @@ -3431,6 +3435,23 @@ "node": ">=4" } }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3645,6 +3666,15 @@ "tslib": "^2.0.3" } }, + "node_modules/duration": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/duration/-/duration-0.2.2.tgz", + "integrity": "sha512-06kgtea+bGreF5eKYgI/36A6pLXggY7oR4p1pq4SmdFBn1ReOL5D8RhG64VrqfTTKNucqqtBAwEj8aB88mcqrg==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.46" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3778,6 +3808,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -4063,6 +4130,20 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", @@ -4131,6 +4212,15 @@ "node": ">= 0.6" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -4226,6 +4316,14 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5946,6 +6044,11 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -7680,6 +7783,11 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, + "node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -11036,6 +11144,20 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, + "d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "requires": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + } + }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -11184,6 +11306,15 @@ "tslib": "^2.0.3" } }, + "duration": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/duration/-/duration-0.2.2.tgz", + "integrity": "sha512-06kgtea+bGreF5eKYgI/36A6pLXggY7oR4p1pq4SmdFBn1ReOL5D8RhG64VrqfTTKNucqqtBAwEj8aB88mcqrg==", + "requires": { + "d": "1", + "es5-ext": "~0.10.46" + } + }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -11290,6 +11421,36 @@ "is-symbol": "^1.0.2" } }, + "es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "requires": { + "d": "^1.0.2", + "ext": "^1.7.0" + } + }, "escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -11499,6 +11660,17 @@ "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true }, + "esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + } + }, "espree": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", @@ -11546,6 +11718,15 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -11631,6 +11812,14 @@ } } }, + "ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "requires": { + "type": "^2.7.2" + } + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -12832,6 +13021,11 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, "no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -14095,6 +14289,11 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, + "type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index bcfa74d..aa3505a 100644 --- a/package.json +++ b/package.json @@ -31,5 +31,9 @@ }, "engines": { "node": "18" + }, + "dependencies": { + "dayjs": "^1.11.10", + "duration": "^0.2.2" } } From 8c99fad255eda69c4fb2900539b5d3eefba04cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Wed, 10 Apr 2024 21:42:43 +0500 Subject: [PATCH 05/14] =?UTF-8?q?Rename=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=B4=D0=BB=D1=8F=20=D1=83=D0=B4=D0=BE=D0=B1=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.js | 2 +- src/mock/{destinations.js => destinations-mock.js} | 0 src/mock/{event-points.js => event-points-mock.js} | 0 src/mock/{offers.js => offers-mock.js} | 0 src/model/destinations-model.js | 2 +- src/model/event-points-model.js | 2 +- src/model/offers-model.js | 2 +- src/presenter/{board-presenter.js => trip-presenter.js} | 0 8 files changed, 4 insertions(+), 4 deletions(-) rename src/mock/{destinations.js => destinations-mock.js} (100%) rename src/mock/{event-points.js => event-points-mock.js} (100%) rename src/mock/{offers.js => offers-mock.js} (100%) rename src/presenter/{board-presenter.js => trip-presenter.js} (100%) diff --git a/src/main.js b/src/main.js index 90858d7..b77ccfc 100644 --- a/src/main.js +++ b/src/main.js @@ -4,7 +4,7 @@ import { } from './render.js'; import TripInfoView from './view/create-trip.js'; import FiltersView from './view/create-filter.js'; -import PointsPresenter from './presenter/board-presenter.js'; +import PointsPresenter from './presenter/trip-presenter.js'; const tripMainContainer = document.querySelector('.trip-main'); const tripEventsContainer = document.querySelector('.trip-events'); diff --git a/src/mock/destinations.js b/src/mock/destinations-mock.js similarity index 100% rename from src/mock/destinations.js rename to src/mock/destinations-mock.js diff --git a/src/mock/event-points.js b/src/mock/event-points-mock.js similarity index 100% rename from src/mock/event-points.js rename to src/mock/event-points-mock.js diff --git a/src/mock/offers.js b/src/mock/offers-mock.js similarity index 100% rename from src/mock/offers.js rename to src/mock/offers-mock.js diff --git a/src/model/destinations-model.js b/src/model/destinations-model.js index eb046b2..8eea505 100644 --- a/src/model/destinations-model.js +++ b/src/model/destinations-model.js @@ -1,4 +1,4 @@ -import { getDestinations } from '../mock/destinations.js'; +import { getDestinations } from '../mock/destinations-mock.js'; export default class DestinationsModel { constructor() { diff --git a/src/model/event-points-model.js b/src/model/event-points-model.js index 312faf3..f7a5597 100644 --- a/src/model/event-points-model.js +++ b/src/model/event-points-model.js @@ -1,4 +1,4 @@ -import { getRandomEventPoint } from '../mock/event-points.js'; +import { getRandomEventPoint } from '../mock/event-points-mock.js'; import { POINTS_COUNT } from '../const.js'; export default class PointsModel { diff --git a/src/model/offers-model.js b/src/model/offers-model.js index 7d4d36f..b8d1386 100644 --- a/src/model/offers-model.js +++ b/src/model/offers-model.js @@ -1,4 +1,4 @@ -import { getOffers } from '../mock/offers.js'; +import { getOffers } from '../mock/offers-mock.js'; export default class OffersModel { constructor() { diff --git a/src/presenter/board-presenter.js b/src/presenter/trip-presenter.js similarity index 100% rename from src/presenter/board-presenter.js rename to src/presenter/trip-presenter.js From be59692f65b6e865d8b7950d23ccd7ad4874ba2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Thu, 11 Apr 2024 21:49:35 +0500 Subject: [PATCH 06/14] =?UTF-8?q?=D0=A0=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D1=8F=D0=B9=20=D0=B8=20=D0=B2=D0=BB=D0=B0=D1=81=D1=82=D0=B2?= =?UTF-8?q?=D1=83=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/const.js | 58 ++++--- src/main.js | 33 ++-- src/mock/destinations-mock.js | 28 ++-- src/mock/event-points-mock.js | 40 ++--- src/mock/offers-mock.js | 103 +----------- src/model/destinations-model.js | 14 +- src/model/event-points-model.js | 16 +- src/model/offers-model.js | 14 +- src/presenter/filters-presenter.js | 14 ++ src/presenter/points-presenter.js | 97 +++++++++++ src/presenter/trip-presenter.js | 36 +---- src/service/mock-service.js | 80 +++++++++ src/utils.js | 33 ++-- src/view/create-filter.js | 20 +-- src/view/create-item.js | 63 -------- src/view/create-sort.js | 20 +-- src/view/create-trip.js | 20 +-- src/view/creator-view.js | 22 +-- src/view/editor-view.js | 249 ++++++++++++++--------------- src/view/events-list.js | 20 +-- src/view/point-view.js | 100 ++++++++++++ 21 files changed, 572 insertions(+), 508 deletions(-) create mode 100644 src/presenter/filters-presenter.js create mode 100644 src/presenter/points-presenter.js create mode 100644 src/service/mock-service.js delete mode 100644 src/view/create-item.js create mode 100644 src/view/point-view.js diff --git a/src/const.js b/src/const.js index cf5a475..b5d4f3b 100644 --- a/src/const.js +++ b/src/const.js @@ -6,16 +6,18 @@ const HOUR_IN_DAY = 24; const MAX_PRICE_VALUE = 200; const MSEC_IN_HOUR = MSEC_IN_SEC * SEC_IN_MIN * MIN_IN_HOUR; const MSEC_IN_DAY = MSEC_IN_HOUR * HOUR_IN_DAY; +const MAX_IMAGES_COUNT = 5; const EVENT_TYPES = [ - 'Taxi', - 'Bus', - 'Train', - 'Ship', 'Drive', - 'Flight', - 'Check-in', - 'Sightseeing', - 'Restaurant']; + 'taxi', + 'bus', + 'train', + 'ship', + 'drive', + 'flight', + 'check-in', + 'sightseeing', + 'restaurant',]; const CITIES = [ @@ -30,7 +32,7 @@ const CITIES = [ ]; -const DESCCRIPTIONS = [ +const DESCRIPTIONS = [ 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Incidunt architecto labore atque!', 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Voluptatem exercitationem culpa, molestias qui eveniet corrupti?', 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Eius, dolorem.', @@ -38,17 +40,30 @@ const DESCCRIPTIONS = [ ]; -const DATE_FORMATS = { - time: 'HH:mm', - shortDate: 'MMM DD', - formDateTime: 'DD/MM/YY HH:mm' +const Price = { + MIN: 1, + MAX: 500, }; -const DURATION_FORMATS = { - days: 'DD[D] HH[H] mm[M]', - hours: 'HH[H] mm[M]', - mins: 'mm[M]' +const MocksMaxCount = { + OFFERS: 7, + POINTS: 5, +}; + + +const DateFormat = { + TIME: 'HH:mm', + SHORT: 'MMM DD', + FULL: 'YYYY-MM-DDTHH:mm', + WITH_DELIMITER: 'DD/MM/YY HH:mm', +}; + + +const DurationFormat = { + DAYS: 'DD[D] HH[H] mm[M]', + HOURS: 'HH[H] mm[M]', + MINS: 'mm[M]', }; @@ -61,9 +76,12 @@ HOUR_IN_DAY, MAX_PRICE_VALUE, MSEC_IN_HOUR, MSEC_IN_DAY, +MAX_IMAGES_COUNT, EVENT_TYPES, CITIES, -DESCCRIPTIONS, -DATE_FORMATS, -DURATION_FORMATS, +DESCRIPTIONS, +Price, +MocksMaxCount, +DateFormat, +DurationFormat, }; diff --git a/src/main.js b/src/main.js index b77ccfc..d4036b8 100644 --- a/src/main.js +++ b/src/main.js @@ -1,31 +1,28 @@ -import { - render, - RenderPosition -} from './render.js'; -import TripInfoView from './view/create-trip.js'; -import FiltersView from './view/create-filter.js'; -import PointsPresenter from './presenter/trip-presenter.js'; - -const tripMainContainer = document.querySelector('.trip-main'); -const tripEventsContainer = document.querySelector('.trip-events'); -const filtersContainer = tripMainContainer.querySelector('.trip-controls__filters'); - import PointsModel from './model/event-points-model.js'; import OffersModel from './model/offers-model.js'; import DestinationsModel from './model/destinations-model.js'; +import PointsPresenter from './presenter/points-presenter.js'; +import FiltersPresenter from './presenter/filters-presenter.js'; +import TripInfoPresenter from './presenter/trip-presenter.js'; +import MockService from './service/mock-service.js'; + +const mockService = new MockService(); +const pointsModel = new PointsModel(mockService); +const offersModel = new OffersModel(mockService); +const destinationsModel = new DestinationsModel(mockService); -const pointsModel = new PointsModel(); -const offersModel = new OffersModel(); -const destinationsModel = new DestinationsModel(); +const pointsContainer = document.querySelector('.trip-events'); const pointsPresenter = new PointsPresenter({ - tripEventsContainer, + pointsContainer, pointsModel, offersModel, destinationsModel }); -render(new TripInfoView(), tripMainContainer, RenderPosition.AFTERBEGIN); -render(new FiltersView(), filtersContainer); +const filtersPresenter = new FiltersPresenter(); +const tripInfoPresenter = new TripInfoPresenter(); pointsPresenter.init(); +filtersPresenter.init(); +tripInfoPresenter.init(); diff --git a/src/mock/destinations-mock.js b/src/mock/destinations-mock.js index 091aa77..afda769 100644 --- a/src/mock/destinations-mock.js +++ b/src/mock/destinations-mock.js @@ -1,36 +1,28 @@ import { - DESCCRIPTIONS, - CITIES, + DESCRIPTIONS, + MAX_IMAGES_COUNT, } from '../const.js'; import { - incrementCounter, getRandomArrayElement, getRandomPositiveNumber } from '../utils.js'; -const MAX_IMAGES_COUNT = 5; -const START_ID_COUNTER = 1; -const getCityID = incrementCounter(START_ID_COUNTER); -const setupDestination = () => { - const ID = getCityID(); + +const generateDestination = (city) => { return ({ - id: (ID).toString(), - description: getRandomArrayElement(DESCCRIPTIONS), - name: CITIES[ID - 1], + id: crypto.randomUUID(), + description: getRandomArrayElement(DESCRIPTIONS), + name: city, pictures: Array.from({ length: getRandomPositiveNumber(MAX_IMAGES_COUNT) }, () => ({ - src: `https://loremflickr.com/248/152?121`, - description: getRandomArrayElement(DESCCRIPTIONS) + 'src': `https://loremflickr.com/248/152?random=${crypto.randomUUID()}`, + description: getRandomArrayElement(DESCRIPTIONS) })) }); }; -const getDestinations = () => Array.from({ - length: CITIES.length -}, setupDestination); - export { - getDestinations + generateDestination, }; diff --git a/src/mock/event-points-mock.js b/src/mock/event-points-mock.js index f4c1d5f..6e45900 100644 --- a/src/mock/event-points-mock.js +++ b/src/mock/event-points-mock.js @@ -1,26 +1,26 @@ -import { EVENT_TYPES, MAX_PRICE_VALUE, CITIES } from '../const.js'; -import { getRandomArrayElement, getRandomPositiveNumber } from '../utils.js'; +import { + Price, +} from '../const.js'; -// Определяем функцию для генерации моковой точки события -const generateMockEventPoint = () => { - const randomType = getRandomArrayElement(EVENT_TYPES); // Случайный тип события - const randomCityIndex = getRandomPositiveNumber(CITIES.length); // Случайный индекс города из списка +import { + getRandomDate, + getRandomPositiveNumber, +} from '../utils.js'; +const generatePoint = (type, destinationId, offerIds) => { + const dateFrom = getRandomDate(); return { - basePrice: getRandomPositiveNumber(MAX_PRICE_VALUE), // Случайная цена - dateFrom: new Date(Date.now() + Math.random() * 1000000000), // Случайная дата начала события в будущем - dateTo: new Date(Date.now() + Math.random() * 2000000000), // Случайная дата окончания события в будущем - destination: CITIES[randomCityIndex], // Случайный город - isFavorite: Math.random() < 0.5, // Случайное булевое значение для избранного - offers: [ '1', '2' ], // Предложения (в данном случае - значения по умолчанию) - type: randomType // Тип события + id: crypto.randomUUID(), + basePrice: getRandomPositiveNumber(Price.MIN, Price.MAX), + dateFrom: dateFrom.toISOString(), + dateTo: getRandomDate(dateFrom).toISOString(), + destination: destinationId, + isFavorite: Boolean(getRandomPositiveNumber(0, 1)), + offers: offerIds, + type }; }; -// Генерируем массив моковых точек событий -const mockEventPoints = Array.from({ length: 5 }, generateMockEventPoint); - -// Определяем функцию для получения случайной точки события из моковых данных -const getRandomEventPoint = () => getRandomArrayElement(mockEventPoints); - -export { getRandomEventPoint }; +export { + generatePoint, +}; diff --git a/src/mock/offers-mock.js b/src/mock/offers-mock.js index 3fe425d..58b873f 100644 --- a/src/mock/offers-mock.js +++ b/src/mock/offers-mock.js @@ -1,106 +1,17 @@ import { - MAX_PRICE_VALUE + Price } from '../const.js'; import { getRandomPositiveNumber, } from '../utils.js'; -const mockOffers = [{ - type: 'taxi', - offers: [{ - id: '1', - title: 'Upgrade to a business class', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }, { - id: '2', - title: 'Drive slowly', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }] -}, { - type: 'bus', - offers: [{ - id: '1', - title: 'Order meal', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }, { - id: '2', - title: 'Choose seats', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }] -}, { - type: 'train', - offers: [{ - id: '1', - title: 'Book a taxi at the arrival point', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }, { - id: '2', - title: 'Choose seats', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }] -}, { - type: 'flight', - offers: [{ - id: '1', - title: 'Choose meal', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }, { - id: '2', - title: 'Choose seats', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }] -}, { - type: 'check-in', - offers: [{ - id: '1', - title: 'Choose the time of check-in', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }, { - id: '2', - title: 'Choose the time of check-out', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }] -}, { - type: 'sightseeing', - offers: [] -}, { - type: 'ship', - offers: [{ - id: '1', - title: 'Upgrade to comfort class', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }, { - id: '2', - title: 'Upgrade to business class', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }] -}, { - type: 'drive', - offers: [{ - id: '1', - title: 'With automatic transmission', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }, { - id: '2', - title: 'With air conditioning', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }] -}, { - type: 'restaurant', - offers: [{ - id: '1', - title: 'Choose live music', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }, { - id: '2', - title: 'Choose VIP area', - price: getRandomPositiveNumber(MAX_PRICE_VALUE), - }] -}]; - -const getOffers = () => mockOffers; +const generateOffer = (type) => ({ + id: crypto.randomUUID(), + title: `Offer ${type}`, + price: getRandomPositiveNumber(Price.MIN, Price.MAX), +}); export { - getOffers + generateOffer }; diff --git a/src/model/destinations-model.js b/src/model/destinations-model.js index 8eea505..336033d 100644 --- a/src/model/destinations-model.js +++ b/src/model/destinations-model.js @@ -1,15 +1,17 @@ -import { getDestinations } from '../mock/destinations-mock.js'; - export default class DestinationsModel { - constructor() { - this.destinations = getDestinations(); + #service = null; + #destinations = null; + + constructor(service) { + this.#service = service; + this.#destinations = this.#service.destinations; } get() { - return this.destinations; + return this.#destinations; } getById(id) { - return this.destinations.find((destination) => destination.id === id); + return this.#destinations.find((destination) => destination.id === id); } } diff --git a/src/model/event-points-model.js b/src/model/event-points-model.js index f7a5597..5edf058 100644 --- a/src/model/event-points-model.js +++ b/src/model/event-points-model.js @@ -1,12 +1,14 @@ -import { getRandomEventPoint } from '../mock/event-points-mock.js'; -import { POINTS_COUNT } from '../const.js'; - export default class PointsModel { - constructor() { - this.points = Array.from({ length: POINTS_COUNT }, getRandomEventPoint); + #service = null; + #points = null; + + constructor(service) { + this.#service = service; + this.#points = this.#service.points; } - getEventPoints() { - return this.points; + get() { + return this.#points; } } + diff --git a/src/model/offers-model.js b/src/model/offers-model.js index b8d1386..0aac115 100644 --- a/src/model/offers-model.js +++ b/src/model/offers-model.js @@ -1,15 +1,17 @@ -import { getOffers } from '../mock/offers-mock.js'; - export default class OffersModel { - constructor() { - this.offers = getOffers(); + #service = null; + #offers = null; + + constructor(service) { + this.#service = service; + this.#offers = this.#service.offers; } get() { - return this.offers; + return this.#offers; } getByType(type) { - return this.offers.find((offers) => offers.type === type.toLowerCase()).offers; + return this.#offers.find((offers) => offers.type === type.toLowerCase()).offers; } } diff --git a/src/presenter/filters-presenter.js b/src/presenter/filters-presenter.js new file mode 100644 index 0000000..a836cea --- /dev/null +++ b/src/presenter/filters-presenter.js @@ -0,0 +1,14 @@ +import { + render +} from '../framework/render.js'; + +import FiltersView from '../view/create-filter.js'; + +const filtersContainer = document.querySelector('.trip-controls__filters'); + +export default class FiltersPresenter { + + init() { + render(new FiltersView(), filtersContainer); + } +} diff --git a/src/presenter/points-presenter.js b/src/presenter/points-presenter.js new file mode 100644 index 0000000..e11d23d --- /dev/null +++ b/src/presenter/points-presenter.js @@ -0,0 +1,97 @@ +import { + render, + replace +} from '../framework/render.js'; +import EventsListView from '../view/events-list.js'; +import EventsListEmptyView from '../view/events-list.js'; +import PointView from '../view/point-view.js'; +import PointEditorView from '../view/editor-view.js'; +import SortingView from '../view/create-sort.js'; + +export default class PointsPresenter { + #pointsContainer = null; + #pointsModel = null; + #destinationsModel = null; + #offersModel = null; + #points = []; + + #listComponent = new EventsListView(); + #sortingComponent = new SortingView(); + + constructor({ + pointsContainer, + pointsModel, + offersModel, + destinationsModel + }) { + this.#pointsContainer = pointsContainer; + this.#pointsModel = pointsModel; + this.#offersModel = offersModel; + this.#destinationsModel = destinationsModel; + this.#points = [...this.#pointsModel.get()]; + } + + init() { + if (this.#points.length === 0) { + render(new EventsListEmptyView(), this.#pointsContainer); + return; + } + + render(this.#sortingComponent, this.#pointsContainer); + render(this.#listComponent, this.#pointsContainer); + + for (let i = 0; i < this.#points.length; i++) { + this.#renderPoint(this.#points[i]); + } + } + + #renderPoint = (point) => { + const pointComponent = new PointView({ + point, + destination: this.#destinationsModel.getById(point.destination), + offers: this.#offersModel.getByType(point.type), + onEditClick: pointEditHandler, + }); + + const pointEditorComponent = new PointEditorView({ + point, + destination: this.#destinationsModel.getById(point.destination), + offers: this.#offersModel.getByType(point.type), + onCloseClick: pointCloseHandler, + onSubmitForm: pointSubmitHandler + }); + + const escKeyDownHandler = (evt) => { + if (evt.key === 'Escape') { + evt.preventDefault(); + replaceEditorToPoint(); + document.removeEventListener('keydown', escKeyDownHandler); + } + }; + + function replacePointToEditor() { + replace(pointEditorComponent, pointComponent); + } + + function replaceEditorToPoint() { + replace(pointComponent, pointEditorComponent); + } + + function pointEditHandler () { + replacePointToEditor(); + document.addEventListener('keydown', escKeyDownHandler); + } + + function pointSubmitHandler() { + replaceEditorToPoint(); + document.removeEventListener('keydown', escKeyDownHandler); + } + + function pointCloseHandler() { + replaceEditorToPoint(); + document.removeEventListener('keydown', escKeyDownHandler); + } + + render(pointComponent, this.#listComponent.element); + }; +} diff --git a/src/presenter/trip-presenter.js b/src/presenter/trip-presenter.js index fedb24d..3cc8cc4 100644 --- a/src/presenter/trip-presenter.js +++ b/src/presenter/trip-presenter.js @@ -1,34 +1,14 @@ -import { render } from '../render.js'; -import EventListView from '../view/events-list.js'; -import EventItemView from '../view/create-item.js'; -import PointEditorView from '../view/editor-view.js'; -import SortingView from '../view/create-sort.js'; +import { + render, + RenderPosition +} from '../framework/render.js'; -export default class PointsPresenter { - listComponent = new EventListView(); - sortingComponent = new SortingView(); +import TripInfoView from '../view/create-trip.js'; - constructor({ tripEventsContainer, pointsModel, offersModel, destinationsModel }) { - this.tripEventsContainer = tripEventsContainer; - this.pointsModel = pointsModel; - this.offersModel = offersModel; - this.destinationsModel = destinationsModel; - } +const tripMainContainer = document.querySelector('.trip-main'); +export default class TripInfoPresenter { init() { - this.eventPoints = this.pointsModel.getEventPoints(); - render(this.sortingComponent, this.tripEventsContainer); - render(this.listComponent, this.tripEventsContainer); - render(new PointEditorView(), this.listComponent.getElement()); - - for (let i = 0; i < this.eventPoints.length; i++) { - const offers = this.offersModel.getByType(this.eventPoints[i].type); - const destination = this.destinationsModel.getById(this.eventPoints[i].destination); - render(new EventItemView({ - eventPoint: this.eventPoints[i], - offers, - destination - }), this.listComponent.getElement()); - } + render(new TripInfoView(), tripMainContainer, RenderPosition.AFTERBEGIN); } } diff --git a/src/service/mock-service.js b/src/service/mock-service.js new file mode 100644 index 0000000..85a70b3 --- /dev/null +++ b/src/service/mock-service.js @@ -0,0 +1,80 @@ +import { + generateDestination, +} from '../mock/destinations-mock.js'; +import { + generateOffer +} from '../mock/offers-mock.js'; +import { + generatePoint +} from '../mock/event-points-mock.js'; + +import { + CITIES, + EVENT_TYPES, + MocksMaxCount +} from '../const.js'; + +import { + getRandomPositiveNumber, + getRandomArrayElement, +} from '../utils.js'; + + +export default class MockService { + #destinations = []; + #offers = []; + #points = []; + + constructor() { + this.#destinations = this.#generateDestinations(); + this.#offers = this.#generateOffers(); + this.#points = this.#generatePoints(); + } + + get points() { + return this.#points; + } + + get destinations() { + return this.#destinations; + } + + get offers() { + return this.#offers; + } + + #generateDestinations(){ + return CITIES.map(generateDestination); + } + + #generateOffers(){ + return EVENT_TYPES.map((type) => ({ + type, + offers: Array.from({ + length: getRandomPositiveNumber(1, MocksMaxCount.OFFERS) + }, () => generateOffer(type)) + })); + } + + #generatePoints() { + return Array.from({ + length: MocksMaxCount.POINTS + }, () => { + const type = getRandomArrayElement(EVENT_TYPES); + const destination = getRandomArrayElement(this.#destinations); + const offersByType = this.#offers.find((offerByType) => offerByType.type === type); + + const randomOffers = Array.from({ + length: getRandomPositiveNumber(0, offersByType.offers.length) + }, () => getRandomArrayElement(offersByType.offers)); + + const uniqueOffersIds = randomOffers.reduce((acc, current) => { + if (acc.includes(current.id)) { + return acc; + } + return [...acc, current.id]; + }, []); + return generatePoint(type, destination.id, uniqueOffersIds); + }); + } +} diff --git a/src/utils.js b/src/utils.js index b60f2f7..945dcf0 100644 --- a/src/utils.js +++ b/src/utils.js @@ -4,19 +4,19 @@ dayjs.extend(duration); import { MSEC_IN_HOUR, MSEC_IN_DAY, - DURATION_FORMATS + DateFormat, + DurationFormat } from './const.js'; -// Функция для получения случайного элемента из массива const getRandomArrayElement = (items) => items[Math.floor(Math.random() * items.length)]; +const getRandomPositiveNumber = (min = 0, max = 1) => { + const lower = Math.ceil(Math.min(min, max)); + const upper = Math.floor(Math.max(min, max)); + return Math.floor(lower + Math.random() * (upper - lower + 1)); +}; +const getRandomDate = (start = new Date(2022, 0, 1), end = new Date()) => new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); +const formatDate = (currentDate, format = DateFormat.FULL) => dayjs(currentDate).format(format); -// Функция для генерации случайного положительного числа -const getRandomPositiveNumber = (max) => Math.ceil(Math.random() * max); - -// Функция для форматирования даты в указанном формате -const humanizeDate = (currentDate, format) => currentDate ? dayjs(currentDate).format(format) : ''; - -// Функция для вычисления продолжительности между двумя датами const calculateDuration = (dateFrom, dateTo) => { const diff = dayjs(dateTo).diff(dayjs(dateFrom)); @@ -24,20 +24,19 @@ const calculateDuration = (dateFrom, dateTo) => { switch (true) { case (diff >= MSEC_IN_DAY): - pointDuration = dayjs.duration(diff).format(DURATION_FORMATS.days); + pointDuration = dayjs.duration(diff).format(DurationFormat.DAYS); break; case (diff >= MSEC_IN_HOUR): - pointDuration = dayjs.duration(diff).format(DURATION_FORMATS.hours); + pointDuration = dayjs.duration(diff).format(DurationFormat.HOURS); break; case (diff < MSEC_IN_HOUR): - pointDuration = dayjs.duration(diff).format(DURATION_FORMATS.mins); + pointDuration = dayjs.duration(diff).format(DurationFormat.MINS); break; } return pointDuration; }; -// Функция для инкрементации счетчика const incrementCounter = (START_FROM) => { let counterStart = START_FROM; return function() { @@ -45,10 +44,14 @@ const incrementCounter = (START_FROM) => { }; }; +const toCapitalize = (str) => `${str[0].toUpperCase()}${str.slice(1)}`; + export { getRandomArrayElement, getRandomPositiveNumber, - humanizeDate, + getRandomDate, + formatDate, calculateDuration, - incrementCounter + incrementCounter, + toCapitalize, }; diff --git a/src/view/create-filter.js b/src/view/create-filter.js index de10c10..ee56713 100644 --- a/src/view/create-filter.js +++ b/src/view/create-filter.js @@ -1,6 +1,4 @@ -import { - createElement -} from '../render.js'; +import AbstractView from '../framework/view/abstract-view.js'; function createFiltersTemplate() { return ( @@ -30,20 +28,8 @@ function createFiltersTemplate() { ); } -export default class FiltersView { - getTemplate() { +export default class FiltersView extends AbstractView { + get template() { return createFiltersTemplate(); } - - getElement() { - if (!this.element) { - this.element = createElement(this.getTemplate()); - } - - return this.element; - } - - removeElement() { - this.element = null; - } } diff --git a/src/view/create-item.js b/src/view/create-item.js deleted file mode 100644 index 2e0f0db..0000000 --- a/src/view/create-item.js +++ /dev/null @@ -1,63 +0,0 @@ -import { - createElement -} from '../render.js'; - -function createEventItemTemplate() { - return ( - `
    • -
      - -
      - Event type icon -
      -

      Taxi Amsterdam

      -
      -

      - - — - -

      -

      30M

      -
      -

      - € 20 -

      -

      Offers:

      -
        -
      • - Order Uber - +€  - 20 -
      • -
      - - -
      -
    • ` - ); -} - -export default class EventItemView { - getTemplate() { - return createEventItemTemplate(); - } - - getElement() { - if (!this.element) { - this.element = createElement(this.getTemplate()); - } - - return this.element; - } - - removeElement() { - this.element = null; - } -} diff --git a/src/view/create-sort.js b/src/view/create-sort.js index 1f139a8..2bbeefb 100644 --- a/src/view/create-sort.js +++ b/src/view/create-sort.js @@ -1,6 +1,4 @@ -import { - createElement -} from '../render.js'; +import AbstractView from '../framework/view/abstract-view.js'; function createSortingTemplate() { return ( @@ -33,20 +31,8 @@ function createSortingTemplate() { ); } -export default class SortingView { - getTemplate() { +export default class SortingView extends AbstractView { + get template() { return createSortingTemplate(); } - - getElement() { - if (!this.element) { - this.element = createElement(this.getTemplate()); - } - - return this.element; - } - - removeElement() { - this.element = null; - } } diff --git a/src/view/create-trip.js b/src/view/create-trip.js index 8c5168f..cc0c7f3 100644 --- a/src/view/create-trip.js +++ b/src/view/create-trip.js @@ -1,6 +1,4 @@ -import { - createElement -} from '../render.js'; +import AbstractView from '../framework/view/abstract-view.js'; function createTripInfoTemplate() { return ( @@ -16,20 +14,8 @@ function createTripInfoTemplate() { ); } -export default class TripInfoView { - getTemplate() { +export default class TripInfoView extends AbstractView { + get template() { return createTripInfoTemplate(); } - - getElement() { - if (!this.element) { - this.element = createElement(this.getTemplate()); - } - - return this.element; - } - - removeElement() { - this.element = null; - } } diff --git a/src/view/creator-view.js b/src/view/creator-view.js index 70426e2..dd92c1a 100644 --- a/src/view/creator-view.js +++ b/src/view/creator-view.js @@ -1,6 +1,4 @@ -import { - createElement -} from '../render.js'; +import AbstractView from '../framework/view/abstract-view.js'; function createNewPointTemplate() { return ( @@ -169,20 +167,8 @@ function createNewPointTemplate() { ); } -export default class NewPointView { - getTemplate() { - return createNewPointTemplate(); - } - - getElement() { - if (!this.element) { - this.element = createElement(this.getTemplate()); - } - - return this.element; - } - - removeElement() { - this.element = null; +export default class PointCreatorView extends AbstractView { + get template() { + return createPointCreatorTemplate(); } } diff --git a/src/view/editor-view.js b/src/view/editor-view.js index 373c1f3..215f142 100644 --- a/src/view/editor-view.js +++ b/src/view/editor-view.js @@ -1,89 +1,111 @@ +import AbstractView from '../framework/view/abstract-view.js'; + import { - createElement -} from '../render.js'; + EVENT_TYPES, + CITIES, + DateFormat +} from '../const.js'; -function createPointEditorTemplate() { - return ( - `
    • +import { + toCapitalize, + formatDate +} from '../utils.js'; + +const createEventTypeTemplate = (types, currentType) => types.reduce((markup, type)=>`${markup} +
      + + +
      `, ''); + +const createCitiesTemplate = (cities) => cities.reduce((markup, city)=>`${markup}`, ''); + +const createOffersTemplate = (offers, pointOffers) => { + const items = offers.reduce((markup, {id, title, price})=>`${markup} +
      + offer === id) ? 'checked' : ''}> + +
      `, ''); + if (offers.length > 0) { + return ` +
      +

      Offers

      +
      + ${items} +
      +
      + `; + } else { + return ''; + } +}; + +const createDestinationPhotosTemplate = (destination) => { + const photos = destination.pictures.reduce((markup, {src, description})=>`${markup} + ${description}`, ''); + + if (destination.pictures.length > 0) { + return ` +
      +
      + ${photos} +
      +
      + `; + } else { + return ''; + } +}; + +function createPointEditorTemplate({ + point, + pointDestination, + pointOffers +}) { + const { + type, + basePrice, + dateFrom, + dateTo, + offers + } = point; + return `
    • Event type - -
      - - -
      - -
      - - -
      - -
      - - -
      - -
      - - -
      - -
      - - -
      - -
      - - -
      - -
      - - -
      - -
      - - -
      - -
      - - -
      + ${createEventTypeTemplate(EVENT_TYPES, type)}
      - + - - - + ${createCitiesTemplate(CITIES)}
      - + - +
      @@ -91,7 +113,7 @@ function createPointEditorTemplate() { Price € - +
      @@ -101,81 +123,58 @@ function createPointEditorTemplate() {
      -
      -

      Offers

      - -
      -
      - - -
      - -
      - - -
      - -
      - - -
      - -
      - - -
      - -
      - - -
      -
      -
      - + ${createOffersTemplate(pointOffers, offers)}

      Destination

      -

      Chamonix-Mont-Blanc (usually shortened to Chamonix) is a resort area near the junction of France, Switzerland and Italy. At the base of Mont Blanc, the highest summit in the Alps, it's renowned for its skiing.

      +

      ${pointDestination.description}

      + + ${createDestinationPhotosTemplate(pointDestination)}
      -
    • ` - ); + `; } -export default class PointEditorView { - getTemplate() { - return createPointEditorTemplate(); - } - - getElement() { - if (!this.element) { - this.element = createElement(this.getTemplate()); - } - return this.element; +export default class PointEditorView extends AbstractView { + #point = null; + #destination = null; + #offers = null; + #onCloseClick = null; + #onSubmitForm = null; + + constructor({ + point, + destination, + offers, + onCloseClick, + onSubmitForm + }) { + super(); + this.#point = point; + this.#destination = destination; + this.#offers = offers; + this.#onCloseClick = onCloseClick; + this.#onSubmitForm = onSubmitForm; + this.element.querySelector('.event__rollup-btn').addEventListener('click', this.#closeClickHandler); + this.element.querySelector('.event.event--edit').addEventListener('submit', this.#saveClickHandler); } - removeElement() { - this.element = null; + get template() { + return createPointEditorTemplate({ + point: this.#point, + pointDestination: this.#destination, + pointOffers: this.#offers + }); } + + #closeClickHandler = (evt) => { + evt.preventDefault(); + this.#onCloseClick(); + }; + + #saveClickHandler = (evt) => { + evt.preventDefault(); + this.#onSubmitForm(); + }; } diff --git a/src/view/events-list.js b/src/view/events-list.js index 964a599..c262059 100644 --- a/src/view/events-list.js +++ b/src/view/events-list.js @@ -1,25 +1,11 @@ -import { - createElement -} from '../render.js'; +import AbstractView from '../framework/view/abstract-view.js'; function createEventListTemplate() { return '
        '; } -export default class EventListView { - getTemplate() { +export default class EventListView extends AbstractView { + get template() { return createEventListTemplate(); } - - getElement() { - if (!this.element) { - this.element = createElement(this.getTemplate()); - } - - return this.element; - } - - removeElement() { - this.element = null; - } } diff --git a/src/view/point-view.js b/src/view/point-view.js new file mode 100644 index 0000000..002a5fa --- /dev/null +++ b/src/view/point-view.js @@ -0,0 +1,100 @@ +import { + DateFormat, +} from '../const.js'; +import { + calculateDuration, + formatDate +} from '../utils.js'; +import AbstractView from '../framework/view/abstract-view.js'; + +const createOffersListMarkup = (pointOffers, typeOffers) => pointOffers + .map((pointOfferId) => { + if (pointOffers.length && typeOffers.length) { + const selectedOffer = typeOffers.filter((offer) => offer.id === pointOfferId)[0]; + return `
      • + ${selectedOffer.title} + +€  + ${selectedOffer.price} +
      • `; + } + }) + .join(''); + + +function createPointTemplate(eventPoint, typeOffers, destination) { + const { + type, + basePrice, + isFavorite, + dateFrom, + dateTo, + offers + } = eventPoint; + const duration = calculateDuration(dateFrom, dateTo); + return ( + `
      • +
        + +
        + Event type icon +
        +

        ${type} ${destination.name}

        +
        +

        + + — + +

        +

        ${duration}

        +
        +

        + € ${basePrice} +

        +

        Offers:

        +
          + ${createOffersListMarkup(offers, typeOffers)} +
        + + +
        +
      • ` + ); +} + +export default class PointView extends AbstractView { + #eventPoint = null; + #destination = null; + #offers = null; + #onEditClick = null; + + constructor({ + point, + offers, + destination, + onEditClick, + }) { + super(); + this.#eventPoint = point; + this.#offers = offers; + this.#destination = destination; + this.#onEditClick = onEditClick; + this.element.querySelector('.event__rollup-btn').addEventListener('click', this.#editClickHandler); + } + + get template() { + return createPointTemplate(this.#eventPoint, this.#offers, this.#destination); + } + + + #editClickHandler = (evt) => { + evt.preventDefault(); + this.#onEditClick(); + }; +} From b98029886bc90936fd1de74d68db2e156c793a84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Thu, 11 Apr 2024 22:16:14 +0500 Subject: [PATCH 07/14] Rename2 --- src/const.js | 1 + src/main.js | 17 +++++++++++------ src/presenter/filters-presenter.js | 2 +- src/presenter/points-presenter.js | 8 ++++---- src/presenter/trip-presenter.js | 2 +- .../{create-sort.js => create-sort-view.js} | 0 .../{create-trip.js => create-trip-view.js} | 0 .../{creator-view.js => creator-point-view.js} | 0 .../{editor-view.js => editor-point-view.js} | 0 .../{events-list.js => events-list-view.js} | 0 src/view/{create-filter.js => filters-view.js} | 0 11 files changed, 18 insertions(+), 12 deletions(-) rename src/view/{create-sort.js => create-sort-view.js} (100%) rename src/view/{create-trip.js => create-trip-view.js} (100%) rename src/view/{creator-view.js => creator-point-view.js} (100%) rename src/view/{editor-view.js => editor-point-view.js} (100%) rename src/view/{events-list.js => events-list-view.js} (100%) rename src/view/{create-filter.js => filters-view.js} (100%) diff --git a/src/const.js b/src/const.js index b5d4f3b..f97b8fc 100644 --- a/src/const.js +++ b/src/const.js @@ -8,6 +8,7 @@ const MSEC_IN_HOUR = MSEC_IN_SEC * SEC_IN_MIN * MIN_IN_HOUR; const MSEC_IN_DAY = MSEC_IN_HOUR * HOUR_IN_DAY; const MAX_IMAGES_COUNT = 5; + const EVENT_TYPES = [ 'taxi', 'bus', diff --git a/src/main.js b/src/main.js index d4036b8..d74bdea 100644 --- a/src/main.js +++ b/src/main.js @@ -1,16 +1,21 @@ import PointsModel from './model/event-points-model.js'; import OffersModel from './model/offers-model.js'; import DestinationsModel from './model/destinations-model.js'; -import PointsPresenter from './presenter/points-presenter.js'; -import FiltersPresenter from './presenter/filters-presenter.js'; -import TripInfoPresenter from './presenter/trip-presenter.js'; -import MockService from './service/mock-service.js'; -const mockService = new MockService(); const pointsModel = new PointsModel(mockService); const offersModel = new OffersModel(mockService); const destinationsModel = new DestinationsModel(mockService); + +import MockService from './service/mock-service.js'; + +const mockService = new MockService(); + + +import PointsPresenter from './presenter/points-presenter.js'; +import FiltersPresenter from './presenter/filters-presenter.js'; +import TripInfoPresenter from './presenter/trip-presenter.js'; + const pointsContainer = document.querySelector('.trip-events'); const pointsPresenter = new PointsPresenter({ @@ -20,7 +25,7 @@ const pointsPresenter = new PointsPresenter({ destinationsModel }); -const filtersPresenter = new FiltersPresenter(); +const filtersPresenter = new FiltersPresenter({pointsModel}); const tripInfoPresenter = new TripInfoPresenter(); pointsPresenter.init(); diff --git a/src/presenter/filters-presenter.js b/src/presenter/filters-presenter.js index a836cea..fa959de 100644 --- a/src/presenter/filters-presenter.js +++ b/src/presenter/filters-presenter.js @@ -2,7 +2,7 @@ import { render } from '../framework/render.js'; -import FiltersView from '../view/create-filter.js'; +import FiltersView from '../view/filters-view.js'; const filtersContainer = document.querySelector('.trip-controls__filters'); diff --git a/src/presenter/points-presenter.js b/src/presenter/points-presenter.js index e11d23d..09231c1 100644 --- a/src/presenter/points-presenter.js +++ b/src/presenter/points-presenter.js @@ -2,11 +2,11 @@ import { render, replace } from '../framework/render.js'; -import EventsListView from '../view/events-list.js'; -import EventsListEmptyView from '../view/events-list.js'; +import EventsListView from '../view/events-list-view.js'; +import EventsListEmptyView from '../view/events-list-empty-view.js'; import PointView from '../view/point-view.js'; -import PointEditorView from '../view/editor-view.js'; -import SortingView from '../view/create-sort.js'; +import PointEditorView from '../view/editor-point-view.js'; +import SortingView from '../view/create-sort-view.js'; export default class PointsPresenter { #pointsContainer = null; diff --git a/src/presenter/trip-presenter.js b/src/presenter/trip-presenter.js index 3cc8cc4..8cfac76 100644 --- a/src/presenter/trip-presenter.js +++ b/src/presenter/trip-presenter.js @@ -3,7 +3,7 @@ import { RenderPosition } from '../framework/render.js'; -import TripInfoView from '../view/create-trip.js'; +import TripInfoView from '../view/create-trip-view.js'; const tripMainContainer = document.querySelector('.trip-main'); diff --git a/src/view/create-sort.js b/src/view/create-sort-view.js similarity index 100% rename from src/view/create-sort.js rename to src/view/create-sort-view.js diff --git a/src/view/create-trip.js b/src/view/create-trip-view.js similarity index 100% rename from src/view/create-trip.js rename to src/view/create-trip-view.js diff --git a/src/view/creator-view.js b/src/view/creator-point-view.js similarity index 100% rename from src/view/creator-view.js rename to src/view/creator-point-view.js diff --git a/src/view/editor-view.js b/src/view/editor-point-view.js similarity index 100% rename from src/view/editor-view.js rename to src/view/editor-point-view.js diff --git a/src/view/events-list.js b/src/view/events-list-view.js similarity index 100% rename from src/view/events-list.js rename to src/view/events-list-view.js diff --git a/src/view/create-filter.js b/src/view/filters-view.js similarity index 100% rename from src/view/create-filter.js rename to src/view/filters-view.js From 3dc253ae96055aa8fcf82c6248e65eaf4c8acc4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Thu, 11 Apr 2024 22:17:13 +0500 Subject: [PATCH 08/14] Rename2 --- src/main.js | 15 +++++---------- src/view/events-list-empty-view.js | 11 +++++++++++ 2 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 src/view/events-list-empty-view.js diff --git a/src/main.js b/src/main.js index d74bdea..53d5c13 100644 --- a/src/main.js +++ b/src/main.js @@ -1,21 +1,16 @@ import PointsModel from './model/event-points-model.js'; import OffersModel from './model/offers-model.js'; import DestinationsModel from './model/destinations-model.js'; +import PointsPresenter from './presenter/points-presenter.js'; +import FiltersPresenter from './presenter/filters-presenter.js'; +import TripInfoPresenter from './presenter/trip-presenter.js'; +import MockService from './service/mock-service.js'; +const mockService = new MockService(); const pointsModel = new PointsModel(mockService); const offersModel = new OffersModel(mockService); const destinationsModel = new DestinationsModel(mockService); - -import MockService from './service/mock-service.js'; - -const mockService = new MockService(); - - -import PointsPresenter from './presenter/points-presenter.js'; -import FiltersPresenter from './presenter/filters-presenter.js'; -import TripInfoPresenter from './presenter/trip-presenter.js'; - const pointsContainer = document.querySelector('.trip-events'); const pointsPresenter = new PointsPresenter({ diff --git a/src/view/events-list-empty-view.js b/src/view/events-list-empty-view.js new file mode 100644 index 0000000..76b4b0b --- /dev/null +++ b/src/view/events-list-empty-view.js @@ -0,0 +1,11 @@ +import AbstractView from '../framework/view/abstract-view.js'; + +function createEventListEmptyTemplate() { + return '

        Click "New Event" to create your first point

        '; +} + +export default class EventListEmptyView extends AbstractView { + get template() { + return createEventListEmptyTemplate(); + } +} From 9c6dad102c8358dcc2c1fbb7aaac608eb8679c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Thu, 11 Apr 2024 22:39:34 +0500 Subject: [PATCH 09/14] test merge --- src/const.js | 43 ++++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/src/const.js b/src/const.js index f97b8fc..e045e84 100644 --- a/src/const.js +++ b/src/const.js @@ -8,7 +8,6 @@ const MSEC_IN_HOUR = MSEC_IN_SEC * SEC_IN_MIN * MIN_IN_HOUR; const MSEC_IN_DAY = MSEC_IN_HOUR * HOUR_IN_DAY; const MAX_IMAGES_COUNT = 5; - const EVENT_TYPES = [ 'taxi', 'bus', @@ -18,8 +17,8 @@ const EVENT_TYPES = [ 'flight', 'check-in', 'sightseeing', - 'restaurant',]; - + 'restaurant', +]; const CITIES = [ 'Paris', @@ -32,7 +31,6 @@ const CITIES = [ 'San-Francisco' ]; - const DESCRIPTIONS = [ 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Incidunt architecto labore atque!', 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Voluptatem exercitationem culpa, molestias qui eveniet corrupti?', @@ -40,19 +38,16 @@ const DESCRIPTIONS = [ 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit ad eaque cupiditate praesentium maxime.', ]; - const Price = { MIN: 1, MAX: 500, }; - const MocksMaxCount = { OFFERS: 7, POINTS: 5, }; - const DateFormat = { TIME: 'HH:mm', SHORT: 'MMM DD', @@ -60,29 +55,27 @@ const DateFormat = { WITH_DELIMITER: 'DD/MM/YY HH:mm', }; - const DurationFormat = { DAYS: 'DD[D] HH[H] mm[M]', HOURS: 'HH[H] mm[M]', MINS: 'mm[M]', }; - export { -POINTS_COUNT, -MSEC_IN_SEC, -SEC_IN_MIN, -MIN_IN_HOUR, -HOUR_IN_DAY, -MAX_PRICE_VALUE, -MSEC_IN_HOUR, -MSEC_IN_DAY, -MAX_IMAGES_COUNT, -EVENT_TYPES, -CITIES, -DESCRIPTIONS, -Price, -MocksMaxCount, -DateFormat, -DurationFormat, + POINTS_COUNT, + MSEC_IN_SEC, + SEC_IN_MIN, + MIN_IN_HOUR, + HOUR_IN_DAY, + MAX_PRICE_VALUE, + MSEC_IN_HOUR, + MSEC_IN_DAY, + MAX_IMAGES_COUNT, + EVENT_TYPES, + CITIES, + DESCRIPTIONS, + Price, + MocksMaxCount, + DateFormat, + DurationFormat, }; From 6396ce35f9c9f99e17ac316dbdb0084bed287b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Sun, 14 Apr 2024 20:22:46 +0500 Subject: [PATCH 10/14] =?UTF-8?q?=D0=A8=D0=B0=D0=B1=D0=BB=D0=BE=D0=BD?= =?UTF-8?q?=D0=B8=D0=B7=D0=B8=D1=80=D1=83=D0=B9=20=D1=82=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/const.js | 58 ++++++++++++++++++++++++++++++ src/presenter/filters-presenter.js | 20 +++++++++-- src/service/mock-service.js | 2 +- src/utils.js | 17 +++++++-- src/view/Item-list-view.js | 21 +++++++++++ src/view/create-sort-view.js | 49 ++++++++++--------------- src/view/filters-view.js | 39 ++++++++------------ 7 files changed, 147 insertions(+), 59 deletions(-) create mode 100644 src/view/Item-list-view.js diff --git a/src/const.js b/src/const.js index e045e84..570623b 100644 --- a/src/const.js +++ b/src/const.js @@ -61,6 +61,60 @@ const DurationFormat = { MINS: 'mm[M]', }; +const FilterType = { + ANY: 'any', + FUTURE: 'future', + PRESENT: 'present', + PAST: 'past' +}; + +const SortType = { + DAY: 'day', + EVENT: 'event', + TIME: 'time', + PRICE: 'price', + OFFER: 'offer', +}; + +const FilterSettings = { + [FilterType.ANY]: { + label: 'Everything', + defaultSelected: true, + }, + [FilterType.FUTURE]: { label: 'Future' }, + [FilterType.PRESENT]: { label: 'Present' }, + [FilterType.PAST]: { label: 'Past' }, +}; + +const SORTING_COLUMNS = [ + { + type: SortType.DAY, + label: 'Day', + active: true, + defaultSelected: true, + }, + { + type: SortType.EVENT, + label: 'Event', + active: false, + }, + { + type: SortType.TIME, + label: 'Time', + active: true, + }, + { + type: SortType.PRICE, + label: 'Price', + active: true, + }, + { + type: SortType.OFFER, + label: 'Offer', + active: false, + }, +]; + export { POINTS_COUNT, MSEC_IN_SEC, @@ -78,4 +132,8 @@ export { MocksMaxCount, DateFormat, DurationFormat, + FilterType, + FilterSettings, + SortType, + SORTING_COLUMNS, }; diff --git a/src/presenter/filters-presenter.js b/src/presenter/filters-presenter.js index fa959de..bc52099 100644 --- a/src/presenter/filters-presenter.js +++ b/src/presenter/filters-presenter.js @@ -1,14 +1,30 @@ import { render } from '../framework/render.js'; - import FiltersView from '../view/filters-view.js'; +import { FilterSettings } from '../const.js'; +import { filterByType } from '../utils'; const filtersContainer = document.querySelector('.trip-controls__filters'); export default class FiltersPresenter { + #pointsModel = null; + #filters = []; + + constructor({pointsModel}) { + this.#pointsModel = pointsModel; + + this.#filters = Object.entries(filterByType) + .map(([type, filter]) => ({ + ...FilterSettings[type], + type, + disabled: filter(this.#pointsModel.get()).length === 0 + })); + } init() { - render(new FiltersView(), filtersContainer); + render(new FiltersView({ + items: this.#filters + }), filtersContainer); } } diff --git a/src/service/mock-service.js b/src/service/mock-service.js index 85a70b3..1e1785a 100644 --- a/src/service/mock-service.js +++ b/src/service/mock-service.js @@ -58,7 +58,7 @@ export default class MockService { #generatePoints() { return Array.from({ - length: MocksMaxCount.POINTS + length: getRandomPositiveNumber(0, MocksMaxCount.POINTS) }, () => { const type = getRandomArrayElement(EVENT_TYPES); const destination = getRandomArrayElement(this.#destinations); diff --git a/src/utils.js b/src/utils.js index 945dcf0..5a4c397 100644 --- a/src/utils.js +++ b/src/utils.js @@ -5,7 +5,8 @@ import { MSEC_IN_HOUR, MSEC_IN_DAY, DateFormat, - DurationFormat + DurationFormat, + FilterType } from './const.js'; const getRandomArrayElement = (items) => items[Math.floor(Math.random() * items.length)]; @@ -14,7 +15,7 @@ const getRandomPositiveNumber = (min = 0, max = 1) => { const upper = Math.floor(Math.max(min, max)); return Math.floor(lower + Math.random() * (upper - lower + 1)); }; -const getRandomDate = (start = new Date(2022, 0, 1), end = new Date()) => new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); +const getRandomDate = (start = new Date(2022, 0, 1), end = new Date(2025, 0, 1)) => new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); const formatDate = (currentDate, format = DateFormat.FULL) => dayjs(currentDate).format(format); const calculateDuration = (dateFrom, dateTo) => { @@ -46,6 +47,17 @@ const incrementCounter = (START_FROM) => { const toCapitalize = (str) => `${str[0].toUpperCase()}${str.slice(1)}`; +const isPointFuture = (point) => dayjs().isBefore(point.dateFrom); +const isPointPresent = (point) => dayjs().isAfter(point.dateFrom) && dayjs().isBefore(point.dateTo); +const isPointPast = (point) => dayjs().isAfter(point.dateTo); + +const filterByType = { + [FilterType.ANY]: (points) => [...points], + [FilterType.FUTURE]: (points) => points.filter((point) => isPointFuture(point)), + [FilterType.PRESENT]: (points) => points.filter((point) => isPointPresent(point)), + [FilterType.PAST]: (points) => points.filter((point) => isPointPast(point)) +}; + export { getRandomArrayElement, getRandomPositiveNumber, @@ -54,4 +66,5 @@ export { calculateDuration, incrementCounter, toCapitalize, + filterByType, }; diff --git a/src/view/Item-list-view.js b/src/view/Item-list-view.js new file mode 100644 index 0000000..4ed69ae --- /dev/null +++ b/src/view/Item-list-view.js @@ -0,0 +1,21 @@ +import AbstractView from '../framework/view/abstract-view.js'; + +export default class ItemListView extends AbstractView { + _items = []; + _handleItemChange = null; + + constructor ({ + items, + onItemChange + }) { + super(); + this._items = items; + this._handleItemChange = onItemChange; + this.element.addEventListener('change', this.#itemChangeHandler); + } + + #itemChangeHandler = (evt) => { + evt.preventDefault(); + this._handleItemChange?.(evt.target.dataset.item); + }; +} diff --git a/src/view/create-sort-view.js b/src/view/create-sort-view.js index 2bbeefb..e013206 100644 --- a/src/view/create-sort-view.js +++ b/src/view/create-sort-view.js @@ -1,37 +1,26 @@ -import AbstractView from '../framework/view/abstract-view.js'; +import { SORTING_COLUMNS } from '../const.js'; +import ItemListView from './item-list-view.js'; -function createSortingTemplate() { - return ( - `
        -
        - - -
        - -
        - - -
        - -
        - - -
        - -
        - - -
        +function createSortingItemTemplate(column) { + const { type, label, active, defaultSelected } = column; + return ` +
        + + +
        `; +} -
        - - -
        -
        ` - ); +function createSortingTemplate() { + return `
        + ${SORTING_COLUMNS.map(createSortingItemTemplate).join('')} +
        `; } -export default class SortingView extends AbstractView { +export default class SortingView extends ItemListView { + constructor() { + super({ items: SORTING_COLUMNS }); + } + get template() { return createSortingTemplate(); } diff --git a/src/view/filters-view.js b/src/view/filters-view.js index ee56713..b8611d0 100644 --- a/src/view/filters-view.js +++ b/src/view/filters-view.js @@ -1,35 +1,26 @@ -import AbstractView from '../framework/view/abstract-view.js'; +import ItemListView from './item-list-view.js'; -function createFiltersTemplate() { +function createFilterItemTemplate(filter) { + const { type, label, defaultSelected, disabled } = filter; return ( - `
        -
        - - -
        - -
        - - -
        - -
        - - -
        - -
        - - -
        + `
        + + +
        ` + ); +} +function createFiltersTemplate(filters) { + return ( + ` + ${filters.map(createFilterItemTemplate).join('')}
        ` ); } -export default class FiltersView extends AbstractView { +export default class FiltersView extends ItemListView { get template() { - return createFiltersTemplate(); + return createFiltersTemplate(this._items); } } From f946cee4b8ee6aa0ccee823c1a648ec464c18904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Sun, 14 Apr 2024 20:39:07 +0500 Subject: [PATCH 11/14] test --- package-lock.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7c38823..7c615b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11138,12 +11138,6 @@ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "dev": true }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, "d": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", From 7fbe5c54cf9e06a4d2d55c62412909b4c354f012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Sun, 14 Apr 2024 20:40:55 +0500 Subject: [PATCH 12/14] test2 --- package-lock.json | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7c615b9..a3b9f94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3423,18 +3423,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/d": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", From 6047947b9d26dbb2e40af6f8154a3b46172f210f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Sun, 14 Apr 2024 20:48:31 +0500 Subject: [PATCH 13/14] test3 fix merge pull requests --- src/const.js | 134 ++++++------------------ src/framework/api-service.js | 2 +- src/framework/ui-blocker/ui-blocker.css | 6 +- src/framework/view/abstract-view.js | 96 ++++++----------- src/model/destinations-model.js | 14 ++- src/model/event-points-model.js | 16 ++- src/model/offers-model.js | 14 ++- src/utils.js | 46 +++----- 8 files changed, 101 insertions(+), 227 deletions(-) diff --git a/src/const.js b/src/const.js index 570623b..cf5a475 100644 --- a/src/const.js +++ b/src/const.js @@ -6,19 +6,17 @@ const HOUR_IN_DAY = 24; const MAX_PRICE_VALUE = 200; const MSEC_IN_HOUR = MSEC_IN_SEC * SEC_IN_MIN * MIN_IN_HOUR; const MSEC_IN_DAY = MSEC_IN_HOUR * HOUR_IN_DAY; -const MAX_IMAGES_COUNT = 5; const EVENT_TYPES = [ - 'taxi', - 'bus', - 'train', - 'ship', - 'drive', - 'flight', - 'check-in', - 'sightseeing', - 'restaurant', -]; + 'Taxi', + 'Bus', + 'Train', + 'Ship', 'Drive', + 'Flight', + 'Check-in', + 'Sightseeing', + 'Restaurant']; + const CITIES = [ 'Paris', @@ -31,109 +29,41 @@ const CITIES = [ 'San-Francisco' ]; -const DESCRIPTIONS = [ + +const DESCCRIPTIONS = [ 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Incidunt architecto labore atque!', 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Voluptatem exercitationem culpa, molestias qui eveniet corrupti?', 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Eius, dolorem.', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit ad eaque cupiditate praesentium maxime.', ]; -const Price = { - MIN: 1, - MAX: 500, -}; - -const MocksMaxCount = { - OFFERS: 7, - POINTS: 5, -}; - -const DateFormat = { - TIME: 'HH:mm', - SHORT: 'MMM DD', - FULL: 'YYYY-MM-DDTHH:mm', - WITH_DELIMITER: 'DD/MM/YY HH:mm', -}; - -const DurationFormat = { - DAYS: 'DD[D] HH[H] mm[M]', - HOURS: 'HH[H] mm[M]', - MINS: 'mm[M]', -}; -const FilterType = { - ANY: 'any', - FUTURE: 'future', - PRESENT: 'present', - PAST: 'past' +const DATE_FORMATS = { + time: 'HH:mm', + shortDate: 'MMM DD', + formDateTime: 'DD/MM/YY HH:mm' }; -const SortType = { - DAY: 'day', - EVENT: 'event', - TIME: 'time', - PRICE: 'price', - OFFER: 'offer', -}; -const FilterSettings = { - [FilterType.ANY]: { - label: 'Everything', - defaultSelected: true, - }, - [FilterType.FUTURE]: { label: 'Future' }, - [FilterType.PRESENT]: { label: 'Present' }, - [FilterType.PAST]: { label: 'Past' }, +const DURATION_FORMATS = { + days: 'DD[D] HH[H] mm[M]', + hours: 'HH[H] mm[M]', + mins: 'mm[M]' }; -const SORTING_COLUMNS = [ - { - type: SortType.DAY, - label: 'Day', - active: true, - defaultSelected: true, - }, - { - type: SortType.EVENT, - label: 'Event', - active: false, - }, - { - type: SortType.TIME, - label: 'Time', - active: true, - }, - { - type: SortType.PRICE, - label: 'Price', - active: true, - }, - { - type: SortType.OFFER, - label: 'Offer', - active: false, - }, -]; export { - POINTS_COUNT, - MSEC_IN_SEC, - SEC_IN_MIN, - MIN_IN_HOUR, - HOUR_IN_DAY, - MAX_PRICE_VALUE, - MSEC_IN_HOUR, - MSEC_IN_DAY, - MAX_IMAGES_COUNT, - EVENT_TYPES, - CITIES, - DESCRIPTIONS, - Price, - MocksMaxCount, - DateFormat, - DurationFormat, - FilterType, - FilterSettings, - SortType, - SORTING_COLUMNS, +POINTS_COUNT, +MSEC_IN_SEC, +SEC_IN_MIN, +MIN_IN_HOUR, +HOUR_IN_DAY, +MAX_PRICE_VALUE, +MSEC_IN_HOUR, +MSEC_IN_DAY, +EVENT_TYPES, +CITIES, +DESCCRIPTIONS, +DATE_FORMATS, +DURATION_FORMATS, }; diff --git a/src/framework/api-service.js b/src/framework/api-service.js index ef9f928..ff97aee 100644 --- a/src/framework/api-service.js +++ b/src/framework/api-service.js @@ -44,7 +44,7 @@ export default class ApiService { /** * Метод для обработки ответа * @param {Response} response Объект ответа - * @returns {Promise} + * @returns {Promise} */ static parseResponse(response) { return response.json(); diff --git a/src/framework/ui-blocker/ui-blocker.css b/src/framework/ui-blocker/ui-blocker.css index 489756d..6bcc6d5 100644 --- a/src/framework/ui-blocker/ui-blocker.css +++ b/src/framework/ui-blocker/ui-blocker.css @@ -1,11 +1,11 @@ .ui-blocker { display: none; place-content: center; - position: fixed; + position: absolute; top: 0; + right: 0; + bottom: 0; left: 0; - min-width: 100%; - min-height: 100%; z-index: 1000; cursor: wait; background-color: rgba(255, 255, 255, 0.5); diff --git a/src/framework/view/abstract-view.js b/src/framework/view/abstract-view.js index fa2a552..90858d7 100644 --- a/src/framework/view/abstract-view.js +++ b/src/framework/view/abstract-view.js @@ -1,65 +1,31 @@ -import {createElement} from '../render.js'; -import './abstract-view.css'; - -/** @const {string} Класс, реализующий эффект "покачивания головой" */ -const SHAKE_CLASS_NAME = 'shake'; - -/** @const {number} Время анимации в миллисекундах */ -const SHAKE_ANIMATION_TIMEOUT = 600; - -/** - * Абстрактный класс представления - */ -export default class AbstractView { - /** @type {HTMLElement|null} Элемент представления */ - #element = null; - - constructor() { - if (new.target === AbstractView) { - throw new Error('Can\'t instantiate AbstractView, only concrete one.'); - } - } - - /** - * Геттер для получения элемента - * @returns {HTMLElement} Элемент представления - */ - get element() { - if (!this.#element) { - this.#element = createElement(this.template); - } - - return this.#element; - } - - /** - * Геттер для получения разметки элемента - * @abstract - * @returns {string} Разметка элемента в виде строки - */ - get template() { - throw new Error('Abstract method not implemented: get template'); - } - - /** Метод для удаления элемента */ - removeElement() { - this.#element = null; - } - - /** - * Метод, реализующий эффект "покачивания головой" - * @param {shakeCallback} [callback] Функция, которая будет вызвана после завершения анимации - */ - shake(callback) { - this.element.classList.add(SHAKE_CLASS_NAME); - setTimeout(() => { - this.element.classList.remove(SHAKE_CLASS_NAME); - callback?.(); - }, SHAKE_ANIMATION_TIMEOUT); - } -} - -/** - * Функция, которая будет вызвана методом shake после завершения анимации - * @callback shakeCallback - */ +import { + render, + RenderPosition +} from './render.js'; +import TripInfoView from './view/create-trip.js'; +import FiltersView from './view/create-filter.js'; +import PointsPresenter from './presenter/board-presenter.js'; + +const tripMainContainer = document.querySelector('.trip-main'); +const tripEventsContainer = document.querySelector('.trip-events'); +const filtersContainer = tripMainContainer.querySelector('.trip-controls__filters'); + +import PointsModel from './model/event-points-model.js'; +import OffersModel from './model/offers-model.js'; +import DestinationsModel from './model/destinations-model.js'; + +const pointsModel = new PointsModel(); +const offersModel = new OffersModel(); +const destinationsModel = new DestinationsModel(); + +const pointsPresenter = new PointsPresenter({ + tripEventsContainer, + pointsModel, + offersModel, + destinationsModel +}); + +render(new TripInfoView(), tripMainContainer, RenderPosition.AFTERBEGIN); +render(new FiltersView(), filtersContainer); + +pointsPresenter.init(); diff --git a/src/model/destinations-model.js b/src/model/destinations-model.js index 336033d..eb046b2 100644 --- a/src/model/destinations-model.js +++ b/src/model/destinations-model.js @@ -1,17 +1,15 @@ -export default class DestinationsModel { - #service = null; - #destinations = null; +import { getDestinations } from '../mock/destinations.js'; - constructor(service) { - this.#service = service; - this.#destinations = this.#service.destinations; +export default class DestinationsModel { + constructor() { + this.destinations = getDestinations(); } get() { - return this.#destinations; + return this.destinations; } getById(id) { - return this.#destinations.find((destination) => destination.id === id); + return this.destinations.find((destination) => destination.id === id); } } diff --git a/src/model/event-points-model.js b/src/model/event-points-model.js index 5edf058..312faf3 100644 --- a/src/model/event-points-model.js +++ b/src/model/event-points-model.js @@ -1,14 +1,12 @@ -export default class PointsModel { - #service = null; - #points = null; +import { getRandomEventPoint } from '../mock/event-points.js'; +import { POINTS_COUNT } from '../const.js'; - constructor(service) { - this.#service = service; - this.#points = this.#service.points; +export default class PointsModel { + constructor() { + this.points = Array.from({ length: POINTS_COUNT }, getRandomEventPoint); } - get() { - return this.#points; + getEventPoints() { + return this.points; } } - diff --git a/src/model/offers-model.js b/src/model/offers-model.js index 0aac115..7d4d36f 100644 --- a/src/model/offers-model.js +++ b/src/model/offers-model.js @@ -1,17 +1,15 @@ -export default class OffersModel { - #service = null; - #offers = null; +import { getOffers } from '../mock/offers.js'; - constructor(service) { - this.#service = service; - this.#offers = this.#service.offers; +export default class OffersModel { + constructor() { + this.offers = getOffers(); } get() { - return this.#offers; + return this.offers; } getByType(type) { - return this.#offers.find((offers) => offers.type === type.toLowerCase()).offers; + return this.offers.find((offers) => offers.type === type.toLowerCase()).offers; } } diff --git a/src/utils.js b/src/utils.js index 5a4c397..b60f2f7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -4,20 +4,19 @@ dayjs.extend(duration); import { MSEC_IN_HOUR, MSEC_IN_DAY, - DateFormat, - DurationFormat, - FilterType + DURATION_FORMATS } from './const.js'; +// Функция для получения случайного элемента из массива const getRandomArrayElement = (items) => items[Math.floor(Math.random() * items.length)]; -const getRandomPositiveNumber = (min = 0, max = 1) => { - const lower = Math.ceil(Math.min(min, max)); - const upper = Math.floor(Math.max(min, max)); - return Math.floor(lower + Math.random() * (upper - lower + 1)); -}; -const getRandomDate = (start = new Date(2022, 0, 1), end = new Date(2025, 0, 1)) => new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); -const formatDate = (currentDate, format = DateFormat.FULL) => dayjs(currentDate).format(format); +// Функция для генерации случайного положительного числа +const getRandomPositiveNumber = (max) => Math.ceil(Math.random() * max); + +// Функция для форматирования даты в указанном формате +const humanizeDate = (currentDate, format) => currentDate ? dayjs(currentDate).format(format) : ''; + +// Функция для вычисления продолжительности между двумя датами const calculateDuration = (dateFrom, dateTo) => { const diff = dayjs(dateTo).diff(dayjs(dateFrom)); @@ -25,19 +24,20 @@ const calculateDuration = (dateFrom, dateTo) => { switch (true) { case (diff >= MSEC_IN_DAY): - pointDuration = dayjs.duration(diff).format(DurationFormat.DAYS); + pointDuration = dayjs.duration(diff).format(DURATION_FORMATS.days); break; case (diff >= MSEC_IN_HOUR): - pointDuration = dayjs.duration(diff).format(DurationFormat.HOURS); + pointDuration = dayjs.duration(diff).format(DURATION_FORMATS.hours); break; case (diff < MSEC_IN_HOUR): - pointDuration = dayjs.duration(diff).format(DurationFormat.MINS); + pointDuration = dayjs.duration(diff).format(DURATION_FORMATS.mins); break; } return pointDuration; }; +// Функция для инкрементации счетчика const incrementCounter = (START_FROM) => { let counterStart = START_FROM; return function() { @@ -45,26 +45,10 @@ const incrementCounter = (START_FROM) => { }; }; -const toCapitalize = (str) => `${str[0].toUpperCase()}${str.slice(1)}`; - -const isPointFuture = (point) => dayjs().isBefore(point.dateFrom); -const isPointPresent = (point) => dayjs().isAfter(point.dateFrom) && dayjs().isBefore(point.dateTo); -const isPointPast = (point) => dayjs().isAfter(point.dateTo); - -const filterByType = { - [FilterType.ANY]: (points) => [...points], - [FilterType.FUTURE]: (points) => points.filter((point) => isPointFuture(point)), - [FilterType.PRESENT]: (points) => points.filter((point) => isPointPresent(point)), - [FilterType.PAST]: (points) => points.filter((point) => isPointPast(point)) -}; - export { getRandomArrayElement, getRandomPositiveNumber, - getRandomDate, - formatDate, + humanizeDate, calculateDuration, - incrementCounter, - toCapitalize, - filterByType, + incrementCounter }; From eac4033f69d0ef2e4ffcf68607f45bb83fff85f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=95=D0=B3=D0=BE=D1=80?= Date: Sun, 14 Apr 2024 20:49:56 +0500 Subject: [PATCH 14/14] test4 fix merge pull requests --- src/framework/view/abstract-view.js | 99 ++++++++++++++++++++--------- src/main.js | 33 +++++----- 2 files changed, 86 insertions(+), 46 deletions(-) diff --git a/src/framework/view/abstract-view.js b/src/framework/view/abstract-view.js index 90858d7..131f2d8 100644 --- a/src/framework/view/abstract-view.js +++ b/src/framework/view/abstract-view.js @@ -1,31 +1,68 @@ -import { - render, - RenderPosition -} from './render.js'; -import TripInfoView from './view/create-trip.js'; -import FiltersView from './view/create-filter.js'; -import PointsPresenter from './presenter/board-presenter.js'; - -const tripMainContainer = document.querySelector('.trip-main'); -const tripEventsContainer = document.querySelector('.trip-events'); -const filtersContainer = tripMainContainer.querySelector('.trip-controls__filters'); - -import PointsModel from './model/event-points-model.js'; -import OffersModel from './model/offers-model.js'; -import DestinationsModel from './model/destinations-model.js'; - -const pointsModel = new PointsModel(); -const offersModel = new OffersModel(); -const destinationsModel = new DestinationsModel(); - -const pointsPresenter = new PointsPresenter({ - tripEventsContainer, - pointsModel, - offersModel, - destinationsModel -}); - -render(new TripInfoView(), tripMainContainer, RenderPosition.AFTERBEGIN); -render(new FiltersView(), filtersContainer); - -pointsPresenter.init(); +import {createElement} from '../render.js'; +import './abstract-view.css'; + +/** @const {string} Класс, реализующий эффект "покачивания головой" */ +const SHAKE_CLASS_NAME = 'shake'; + +/** @const {number} Время анимации в миллисекундах */ +const SHAKE_ANIMATION_TIMEOUT = 600; + +/** + * Абстрактный класс представления + */ +export default class AbstractView { + /** @type {HTMLElement|null} Элемент представления */ + #element = null; + + /** @type {Object} Объект с колбэками. Может использоваться для хранения обработчиков событий */ + _callback = {}; + + constructor() { + if (new.target === AbstractView) { + throw new Error('Can\'t instantiate AbstractView, only concrete one.'); + } + } + + /** + * Геттер для получения элемента + * @returns {HTMLElement} Элемент представления + */ + get element() { + if (!this.#element) { + this.#element = createElement(this.template); + } + + return this.#element; + } + + /** + * Геттер для получения разметки элемента + * @abstract + * @returns {string} Разметка элемента в виде строки + */ + get template() { + throw new Error('Abstract method not implemented: get template'); + } + + /** Метод для удаления элемента */ + removeElement() { + this.#element = null; + } + + /** + * Метод, реализующий эффект "покачивания головой" + * @param {shakeCallback} [callback] Функция, которая будет вызвана после завершения анимации + */ + shake(callback) { + this.element.classList.add(SHAKE_CLASS_NAME); + setTimeout(() => { + this.element.classList.remove(SHAKE_CLASS_NAME); + callback?.(); + }, SHAKE_ANIMATION_TIMEOUT); + } +} + +/** + * Функция, которая будет вызвана методом shake после завершения анимации + * @callback shakeCallback + */ diff --git a/src/main.js b/src/main.js index 53d5c13..90858d7 100644 --- a/src/main.js +++ b/src/main.js @@ -1,28 +1,31 @@ +import { + render, + RenderPosition +} from './render.js'; +import TripInfoView from './view/create-trip.js'; +import FiltersView from './view/create-filter.js'; +import PointsPresenter from './presenter/board-presenter.js'; + +const tripMainContainer = document.querySelector('.trip-main'); +const tripEventsContainer = document.querySelector('.trip-events'); +const filtersContainer = tripMainContainer.querySelector('.trip-controls__filters'); + import PointsModel from './model/event-points-model.js'; import OffersModel from './model/offers-model.js'; import DestinationsModel from './model/destinations-model.js'; -import PointsPresenter from './presenter/points-presenter.js'; -import FiltersPresenter from './presenter/filters-presenter.js'; -import TripInfoPresenter from './presenter/trip-presenter.js'; -import MockService from './service/mock-service.js'; - -const mockService = new MockService(); -const pointsModel = new PointsModel(mockService); -const offersModel = new OffersModel(mockService); -const destinationsModel = new DestinationsModel(mockService); -const pointsContainer = document.querySelector('.trip-events'); +const pointsModel = new PointsModel(); +const offersModel = new OffersModel(); +const destinationsModel = new DestinationsModel(); const pointsPresenter = new PointsPresenter({ - pointsContainer, + tripEventsContainer, pointsModel, offersModel, destinationsModel }); -const filtersPresenter = new FiltersPresenter({pointsModel}); -const tripInfoPresenter = new TripInfoPresenter(); +render(new TripInfoView(), tripMainContainer, RenderPosition.AFTERBEGIN); +render(new FiltersView(), filtersContainer); pointsPresenter.init(); -filtersPresenter.init(); -tripInfoPresenter.init();