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 1/7] =?UTF-8?q?=D0=A8=D0=B0=D0=B1=D0=BB=D0=BE=D0=BD=D0=B8?= =?UTF-8?q?=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 2/7] 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 3/7] 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 4/7] 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 5/7] 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(); From f7c0bdcf25c289233646db0b9f3db0e6ff59b207 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:58:35 +0500 Subject: [PATCH 6/7] test5 fix merge pull requests --- src/const.js | 134 ++++++++++++++++++++++++-------- src/main.js | 33 ++++---- src/model/destinations-model.js | 14 ++-- src/model/event-points-model.js | 16 ++-- src/model/offers-model.js | 14 ++-- src/utils.js | 46 +++++++---- src/view/Item-list-view.js | 21 ----- 7 files changed, 173 insertions(+), 105 deletions(-) delete mode 100644 src/view/Item-list-view.js diff --git a/src/const.js b/src/const.js index cf5a475..570623b 100644 --- a/src/const.js +++ b/src/const.js @@ -6,17 +6,19 @@ 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', @@ -29,41 +31,109 @@ const CITIES = [ 'San-Francisco' ]; - -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.', '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 DATE_FORMATS = { - time: 'HH:mm', - shortDate: 'MMM DD', - formDateTime: 'DD/MM/YY HH:mm' +const FilterType = { + ANY: 'any', + FUTURE: 'future', + PRESENT: 'present', + PAST: 'past' }; +const SortType = { + DAY: 'day', + EVENT: 'event', + TIME: 'time', + PRICE: 'price', + OFFER: 'offer', +}; -const DURATION_FORMATS = { - days: 'DD[D] HH[H] mm[M]', - hours: 'HH[H] mm[M]', - mins: 'mm[M]' +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, -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, + 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, }; diff --git a/src/main.js b/src/main.js index 90858d7..53d5c13 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/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 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({pointsModel}); +const tripInfoPresenter = new TripInfoPresenter(); pointsPresenter.init(); +filtersPresenter.init(); +tripInfoPresenter.init(); diff --git a/src/model/destinations-model.js b/src/model/destinations-model.js index eb046b2..336033d 100644 --- a/src/model/destinations-model.js +++ b/src/model/destinations-model.js @@ -1,15 +1,17 @@ -import { getDestinations } from '../mock/destinations.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 312faf3..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.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 7d4d36f..0aac115 100644 --- a/src/model/offers-model.js +++ b/src/model/offers-model.js @@ -1,15 +1,17 @@ -import { getOffers } from '../mock/offers.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/utils.js b/src/utils.js index b60f2f7..5a4c397 100644 --- a/src/utils.js +++ b/src/utils.js @@ -4,19 +4,20 @@ dayjs.extend(duration); import { MSEC_IN_HOUR, MSEC_IN_DAY, - DURATION_FORMATS + DateFormat, + DurationFormat, + FilterType } 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)); @@ -24,20 +25,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 +45,26 @@ 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, - humanizeDate, + getRandomDate, + formatDate, calculateDuration, - incrementCounter + incrementCounter, + toCapitalize, + filterByType, }; diff --git a/src/view/Item-list-view.js b/src/view/Item-list-view.js deleted file mode 100644 index 4ed69ae..0000000 --- a/src/view/Item-list-view.js +++ /dev/null @@ -1,21 +0,0 @@ -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); - }; -} From c3b08e7dd8df369cc0984c6f349fc26b5a9838fd 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 21:19:19 +0500 Subject: [PATCH 7/7] test7 fix merge pull requests --- src/view/item-list-view.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/view/item-list-view.js 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); + }; +}