From 9aa9d967864d8e0dada738c7524961be656ba275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=A0=D0=BE=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2?= <83275022+MaksimRozov@users.noreply.github.com> Date: Wed, 12 Jun 2024 19:45:51 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=B8=D1=88=D1=91=D0=BB,=20=D1=83?= =?UTF-8?q?=D0=B2=D0=B8=D0=B4=D0=B5=D0=BB,=20=D0=B7=D0=B0=D0=B3=D1=80?= =?UTF-8?q?=D1=83=D0=B7=D0=B8=D0=BB=20(=D1=87=D0=B0=D1=81=D1=82=D1=8C?= =?UTF-8?q?=C2=A01)=20(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * подключил API, работают поинты и форма редактирования * Заработала форма добавления поинта * Пришёл, увидел, загрузил --- src/main.js | 43 ++++-- src/mock/destination.js | 20 --- src/mock/offer-mock.js | 18 --- src/mock/trip-event.js | 29 ---- src/mock/variablies.js | 2 - src/model/destinations-model.js | 16 --- src/model/offers-model.js | 18 --- src/model/points-model.js | 132 ++++++++++++++---- src/points-api-service.js | 79 +++++++++++ src/presenter/presenter-new-point.js | 41 ++++-- src/presenter/presenter-waypoint.js | 66 +++++++-- src/presenter/presenter.js | 132 ++++++++++++------ src/utils.js/const.js | 3 +- src/utils.js/util.js | 18 ++- src/view/error-view.js | 10 ++ src/view/loading-view.js | 13 ++ src/view/sort-panel.js | 16 ++- src/view/waypoint-edit.js | 196 +++++++++++++++++---------- src/view/waypoint.js | 52 ++++--- 19 files changed, 589 insertions(+), 315 deletions(-) delete mode 100644 src/mock/destination.js delete mode 100644 src/mock/offer-mock.js delete mode 100644 src/mock/trip-event.js delete mode 100644 src/mock/variablies.js delete mode 100644 src/model/destinations-model.js delete mode 100644 src/model/offers-model.js create mode 100644 src/points-api-service.js create mode 100644 src/view/error-view.js create mode 100644 src/view/loading-view.js diff --git a/src/main.js b/src/main.js index f26235a..c037c7a 100644 --- a/src/main.js +++ b/src/main.js @@ -1,34 +1,57 @@ import Presenter from './presenter/presenter.js'; import PointsModel from './model/points-model.js'; -import DestinationsModel from './model/destinations-model.js'; -import OffersModel from './model/offers-model.js'; import FilterModel from './model/filter-model.js'; import FilterPresenter from './presenter/presenter-filter.js'; +import PointsApiService from './points-api-service.js'; +import CreationForm from './view/creation-form.js'; +import { render } from './framework/render.js'; const filterContainer = document.querySelector('.trip-controls__filters'); +const tripMainElement = document.querySelector('.trip-main'); -const pointsModel = new PointsModel(); -const destinationsModel = new DestinationsModel(); -const offersModel = new OffersModel(); -const filterModel = new FilterModel(); +const AUTHORIZATION = 'Basic hf7898sdfscv83'; +const END_POINT = 'https://23.objects.htmlacademy.pro/big-trip'; + +const pointsModel = new PointsModel({ + pointsApiService: new PointsApiService(END_POINT, AUTHORIZATION) +}); +const filterModel = new FilterModel(); const presenter = new Presenter({ pointsModel, - destinationsModel, - offersModel, filterModel, - + onNewPointDestroy: handleNewPointButtonClose }); - const filterPresenter = new FilterPresenter({ filterContainer: filterContainer, filterModel, pointsModel }); +const newPointButtonComponent = new CreationForm({ + onClick: handleNewPointButtonClick +}); + +function handleNewPointButtonClick() { + presenter.createPoint(); + newPointButtonComponent.element.disabled = true; +} + +function handleNewPointButtonClose() { + newPointButtonComponent.element.disabled = false; +} + +render(newPointButtonComponent, tripMainElement); + +newPointButtonComponent.element.disabled = true; + presenter.init(); filterPresenter.init(); +pointsModel.init() + .finally(() => { + newPointButtonComponent.element.disabled = false; + }); diff --git a/src/mock/destination.js b/src/mock/destination.js deleted file mode 100644 index 4124200..0000000 --- a/src/mock/destination.js +++ /dev/null @@ -1,20 +0,0 @@ -import { getRandomInt, getRandomArrayElement } from '../utils.js/util.js'; -import { CITIES } from './variablies.js'; - -const createDescription = (item) => - `${item} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras aliquet varius magna, non porta ligula feugiat eget. Fusce tristique felis at fermentum pharetra. Aliquam id orci ut lectus varius viverra. Nullam nunc ex, convallis sed finibus eget, sollicitudin eget ante. Phasellus eros mauris, condimentum sed nibh vitae, sodales efficitur ipsum. Sed blandit, eros vel aliquam faucibus, purus ex euismod diam, eu luctus nunc ante ut dui. Sed sed nisi sed augue convallis suscipit in sed felis. Aliquam erat volutpat. Nunc fermentum tortor ac porta dapibus. In rutrum ac purus sit amet tempus.`; - -const createPicture = (item) => ({ - src: `https://loremflickr.com/248/152?${getRandomInt(5)}`, - description: `${item} Lorem ipsum dolor sit amet, consectetur adipiscing elit.`, -}); - -const destination = CITIES.map((item) => ({ - id: crypto.randomUUID(), - name: item, - description: createDescription(item), - pictures: Array.from({ length: getRandomInt(8) }, createPicture), -})); - -export const destinationCreate = () => getRandomArrayElement(destination); -export const destinationCreateAll = () => destination; diff --git a/src/mock/offer-mock.js b/src/mock/offer-mock.js deleted file mode 100644 index bb9c272..0000000 --- a/src/mock/offer-mock.js +++ /dev/null @@ -1,18 +0,0 @@ -import { getRandomArrayElement, getRandomInt } from '../utils.js/util.js'; -import { EVENT_TYPES } from './variablies.js'; - -const OFFER_TITLES = ['Order meal', 'Infotainment system', 'Choose seats', 'Book a taxi at the arrival point', 'Wake up at a certain time']; - -const createOffer = () => ({ - id: crypto.randomUUID(), - title: getRandomArrayElement(OFFER_TITLES), - price: getRandomInt(500), -}); - -const offers = EVENT_TYPES.map((item) => ({ - type: item, - offer: Array.from({ length: getRandomInt(6) }, createOffer), -})); - -export const createRandomOffers = () => offers; -// export const createRandomOffer = () => getRandomArrayElement(offers); diff --git a/src/mock/trip-event.js b/src/mock/trip-event.js deleted file mode 100644 index 2fc76e3..0000000 --- a/src/mock/trip-event.js +++ /dev/null @@ -1,29 +0,0 @@ -import { createRandomOffers } from './offer-mock.js'; -import { destinationCreate } from './destination.js'; -import { getRandomInt, generateDates, getRandomArrayElement } from '../utils.js/util.js'; -import { EVENT_TYPES } from './variablies.js'; - -const EVENT_COUNT = 3; - -const generateTripEvent = () => { - const dates = generateDates(); - const { id: destinationId } = destinationCreate(); - const typeRandom = getRandomArrayElement(EVENT_TYPES); - const randomOffers = createRandomOffers(); - const getOffers = randomOffers.find((element) => element.type === typeRandom); - const getOffersId = getOffers.offer.map((item) => item.id); - - return { - id: crypto.randomUUID(), - basePrice: getRandomInt(100), - ...dates, - destination: destinationId, - isFavorite: false, - offers: getOffersId, - type: typeRandom, - }; -}; - -const MOCKED_EVENTS = Array.from({ length: EVENT_COUNT }, generateTripEvent); - -export { MOCKED_EVENTS }; diff --git a/src/mock/variablies.js b/src/mock/variablies.js deleted file mode 100644 index 36cc180..0000000 --- a/src/mock/variablies.js +++ /dev/null @@ -1,2 +0,0 @@ -export const EVENT_TYPES = ['Taxi', 'Bus', 'Train', 'Ship', 'Drive', 'Flight', 'Check-in', 'Sightseeing', 'Restaurant']; -export const CITIES = ['Amsterdam', 'Chamonix', 'Geneva', 'Paris', 'Milano']; diff --git a/src/model/destinations-model.js b/src/model/destinations-model.js deleted file mode 100644 index 43f8bd2..0000000 --- a/src/model/destinations-model.js +++ /dev/null @@ -1,16 +0,0 @@ -import { destinationCreateAll } from '../mock/destination.js'; - -export default class DestinationsModel { - #destinationAll = null; - constructor() { - this.#destinationAll = destinationCreateAll(); - } - - get destinationAll() { - return this.#destinationAll; - } - - getDestinationId(id) { - return this.destinationAll.find((item) => item.id === id); - } -} diff --git a/src/model/offers-model.js b/src/model/offers-model.js deleted file mode 100644 index 4139b3e..0000000 --- a/src/model/offers-model.js +++ /dev/null @@ -1,18 +0,0 @@ -import { createRandomOffers } from '../mock/offer-mock.js'; - -export default class OffersModel { - #offers = null; - constructor() { - this.#offers = createRandomOffers(); - } - - get offers() { - return this.#offers; - } - - getCurrentOffer(type) { - if (this.#offers) { - return this.#offers.find((item) => item.type === type); - } - } -} diff --git a/src/model/points-model.js b/src/model/points-model.js index 2cfed35..00d1b33 100644 --- a/src/model/points-model.js +++ b/src/model/points-model.js @@ -1,56 +1,132 @@ -import { MOCKED_EVENTS } from '../mock/trip-event.js'; +// import { MOCKED_EVENTS } from '../mock/trip-event.js'; import Observable from '../framework/observable.js'; +import { UpdateType } from '../utils.js/const.js'; export default class PointsModel extends Observable { - #tripEventAll = null; - constructor() { + #pointsApiService = null; + #points = []; + #offers = []; + #destinations = []; + + constructor({ pointsApiService }) { super(); - this.#tripEventAll = MOCKED_EVENTS; + this.#pointsApiService = pointsApiService; } get event() { - return this.#tripEventAll; + return this.#points; + } + + get offers() { + return this.#offers; } - updatePoint(updateType, update) { - const index = this.#tripEventAll.findIndex((point) => point.id === update.id); + get destinations() { + return this.#destinations; + } + + async init() { + try { + const points = await this.#pointsApiService.points; + const offers = await this.#pointsApiService.offers; + const destinations = await this.#pointsApiService.destinations; + + this.#points = points.map(this.#adaptToClient); + this.#offers = offers; + this.#destinations = destinations; + } catch (err) { + this.#points = []; + this.#offers = []; + this.#destinations = []; + } + + + this._notify(UpdateType.INIT); + } + + async updatePoint(updateType, update) { + const index = this.#points.findIndex((point) => point.id === update.id); if (index === -1) { throw new Error('Can\'t update unexisting point'); } + try { + const response = await this.#pointsApiService.updatePoint(update); + const updatedPoint = this.#adaptToClient(response); - this.#tripEventAll = [ - ...this.#tripEventAll.slice(0, index), - update, - ...this.#tripEventAll.slice(index + 1) - ]; + this.#points = [ + ...this.#points.slice(0, index), + updatedPoint, + ...this.#points.slice(index + 1) + ]; - this._notify(updateType, update); + this._notify(updateType, updatedPoint); + } catch (err) { + throw new Error('Can\'t update point'); + } } - addPoint(updateType, update) { - update.id = crypto.randomUUID(); - this.#tripEventAll = [ - update, - ...this.#tripEventAll, - ]; + async addPoint(updateType, update) { + try { + const response = await this.#pointsApiService.addPoint(update); + const newPoint = this.#adaptToClient(response); - this._notify(updateType, update); - } + this.#points = [ + newPoint, + ...this.#points + ]; - deletePoint(updateType, update) { - const index = this.#tripEventAll.findIndex((point) => point.id === update.id); + this._notify(updateType, newPoint); + } catch (err) { + throw new Error('Can\'t add point'); + } + + } + async deletePoint(updateType, update) { + const index = this.#points.findIndex((point) => point.id === update.id); if (index === -1) { throw new Error('Can\'t update unexisting point'); } - this.#tripEventAll = [ - ...this.#tripEventAll.slice(0, index), - ...this.#tripEventAll.slice(index + 1) - ]; + try { + await this.#pointsApiService.deletePoint(update); + this.#points = [ + ...this.#points.slice(0, index), + ...this.#points.slice(index + 1) + ]; + + this._notify(updateType); + } catch (err) { + throw new Error('Can\'t delete point'); + } + } + + #adaptToClient(point) { + const adaptedPoint = { + ...point, + basePrice: point['base_price'], + dateFrom: new Date(point['date_from']), + dateTo: new Date(point['date_to']), + isFavorite: point['is_favorite'] + }; + + delete adaptedPoint['base_price']; + delete adaptedPoint['date_from']; + delete adaptedPoint['date_to']; + delete adaptedPoint['is_favorite']; + + return adaptedPoint; + } + + getCurrentOffer(type) { + if (this.offers) { + return this.offers.find((item) => item.type === type); + } + } - this._notify(updateType); + getDestinationId(id) { + return this.destinations.find((item) => item.id === id); } } diff --git a/src/points-api-service.js b/src/points-api-service.js new file mode 100644 index 0000000..0874cc7 --- /dev/null +++ b/src/points-api-service.js @@ -0,0 +1,79 @@ +import ApiService from './framework/api-service.js'; + +const Method = { + GET: 'GET', + PUT: 'PUT', + POST: 'POST', + DELETE: 'DELETE' +}; + +export default class PointsApiService extends ApiService { + get points() { + return this._load({ url: 'points' }) + .then(ApiService.parseResponse); + } + + get offers() { + return this._load({ url: 'offers' }) + .then(ApiService.parseResponse); + } + + get destinations() { + return this._load({ url: 'destinations' }) + .then(ApiService.parseResponse); + } + + async updatePoint(point) { + const response = await this._load({ + url: `points/${point.id}`, + method: Method.PUT, + body: JSON.stringify(this.#adaptToServer(point)), + headers: new Headers({ 'Content-Type': 'application/json' }) + }); + + const parsedResponse = await ApiService.parseResponse(response); + + return parsedResponse; + } + + async addPoint(point) { + const response = await this._load({ + url: 'points', + method: Method.POST, + body: JSON.stringify(this.#adaptToServer(point)), + headers: new Headers({ 'Content-Type': 'application/json' }) + }); + + const parsedResponse = await ApiService.parseResponse(response); + + return parsedResponse; + } + + async deletePoint(point) { + const response = await this._load({ + url: `points/${point.id}`, + method: Method.DELETE + }); + + return response; + } + + + #adaptToServer(point) { + const adaptedPoint = { + ...point, + 'base_price': +point.basePrice, + 'date_from': new Date(point.dateFrom).toISOString(), + 'date_to': new Date(point.dateTo).toISOString(), + 'is_favorite': point.isFavorite + }; + + delete adaptedPoint.basePrice; + delete adaptedPoint.dateFrom; + delete adaptedPoint.dateTo; + delete adaptedPoint.isFavorite; + + return adaptedPoint; + } + +} diff --git a/src/presenter/presenter-new-point.js b/src/presenter/presenter-new-point.js index 01d9ad5..b4a460f 100644 --- a/src/presenter/presenter-new-point.js +++ b/src/presenter/presenter-new-point.js @@ -4,25 +4,18 @@ import { isEscapeKey } from '../utils.js/util.js'; import { UpdateType, UserAction } from '../utils.js/const.js'; export default class PresenterNewPoint { - #offersModel = null; - #destinationsModel = null; - - #containerList = null; - #pointListContainer = null; #onPointChange = null; #handleDestroy = null; + #pointsModel = null; #pointEditComponent = null; - constructor({ offersModel, destinationsModel, containerList, onPointChange, handleDestroy }) { - this.#offersModel = offersModel; - this.#destinationsModel = destinationsModel; + constructor({ containerList, onPointChange, pointsModel, onDestroy }) { + this.#pointsModel = pointsModel; this.#pointListContainer = containerList; this.#onPointChange = onPointChange; - this.#handleDestroy = handleDestroy; - - + this.#handleDestroy = onDestroy; } init() { @@ -31,8 +24,9 @@ export default class PresenterNewPoint { } this.#pointEditComponent = new WaypointEdit({ - offersModel: this.#offersModel, - destinationsModel: this.#destinationsModel, + pointsModel: this.#pointsModel, + offers: this.#pointsModel.offers, + destinations: this.#pointsModel.destinations, onEditFormSave: this.#handleFormSubmit, onDeleteForm: this.#handleFormCancelButtonClick }); @@ -55,6 +49,25 @@ export default class PresenterNewPoint { document.removeEventListener('keydown', this.#escapeKeydownHandler); } + setSaving() { + this.#pointEditComponent.updateElement({ + isDisabled: true, + isSaving: true + }); + } + + setAborting() { + const resetFormState = () => { + this.#pointEditComponent.updateElement({ + isDisabled: false, + isSaving: false, + isDeleting: false, + }); + }; + + this.#pointEditComponent.shake(resetFormState); + } + #escapeKeydownHandler = (evt) => { if (isEscapeKey(evt)) { evt.preventDefault(); @@ -66,7 +79,7 @@ export default class PresenterNewPoint { this.#onPointChange( UserAction.ADD_POINT, UpdateType.MINOR, - point + point, ); this.destroy(); }; diff --git a/src/presenter/presenter-waypoint.js b/src/presenter/presenter-waypoint.js index 34657de..b33ba77 100644 --- a/src/presenter/presenter-waypoint.js +++ b/src/presenter/presenter-waypoint.js @@ -9,9 +9,8 @@ const Mode = { }; export default class PresenterWaypoint { - #destinationsModel = null; - #offersModel = null; - #destinationsModelAll = []; + #destinations = null; + #offers = null; #pointListContainer = null; #eventView = null; #eventEditView = null; @@ -19,14 +18,19 @@ export default class PresenterWaypoint { #onPointChange = null; #mode = Mode.DEFAULT; #onModeChange = null; + #offersCurrent = null; + #destinationCurrent = null; + #pointsModel = null; - constructor({ pointListContainer, destinationsModel, offersModel, onPointChange, onModeChange }) { + constructor({ pointListContainer, onPointChange, onModeChange, offersCurrent, destinationCurrent, pointsModel }) { this.#pointListContainer = pointListContainer; - this.#destinationsModel = destinationsModel; - this.#destinationsModelAll = [...this.#destinationsModel.destinationAll]; - this.#offersModel = offersModel; + this.#destinations = pointsModel.destinations; + this.#destinationCurrent = destinationCurrent; + this.#offers = pointsModel.offers; this.#onPointChange = onPointChange; this.#onModeChange = onModeChange; + this.#offersCurrent = offersCurrent; + this.#pointsModel = pointsModel; } init(point) { @@ -38,18 +42,23 @@ export default class PresenterWaypoint { this.#eventView = new Waypoint({ waypoint: point, onClickButtonRollup: this.#onClickButtonRollup, - destinationsModel: this.#destinationsModel, + destinations: this.#destinations, + destinationCurrent: this.#destinationCurrent, onFavoriteClick: this.#onFavoriteClick, - offerCurrent: this.#offersModel.getCurrentOffer(point.type), + offerCurrent: this.#offersCurrent, + offers: this.#offers }); this.#eventEditView = new WaypointEdit({ waypoint: point, + pointsModel: this.#pointsModel, onEditFormRollupButtonClick: this.#onEditFormRollupButtonClick, - destinationsModel: this.#destinationsModel, + destinations: this.#destinations, onEditFormSave: this.#onEditFormSave, - offersModel: this.#offersModel, + offers: this.#offers, + offerCurrent: this.#offersCurrent, onDeleteForm: this.#handleEditFormDeleteButtonClick + }); if ((prevEventViewComponent === null) | (prevEventEditViewComponent === null)) { @@ -135,4 +144,39 @@ export default class PresenterWaypoint { { ...this.#point, isFavorite: !this.#point.isFavorite } ); }; + + setSaving() { + if (this.#mode === Mode.EDITING) { + this.#eventEditView.updateElement({ + isDisabled: true, + isSaving: true + }); + } + } + + setDeleting() { + if (this.#mode === Mode.EDITING) { + this.#eventEditView.updateElement({ + isDisabled: true, + isDeleting: true + }); + } + } + + setAborting() { + if (this.#mode === Mode.DEFAULT) { + this.#eventView.shake(); + return; + } + + const resetFormState = () => { + this.#eventEditView.updateElement({ + isDisabled: false, + isSaving: false, + isDeleting: false, + }); + }; + + this.#eventEditView.shake(resetFormState); + } } diff --git a/src/presenter/presenter.js b/src/presenter/presenter.js index 155a032..5d72e42 100644 --- a/src/presenter/presenter.js +++ b/src/presenter/presenter.js @@ -1,5 +1,4 @@ import { render, RenderPosition, remove } from '../framework/render.js'; -import CreationForm from '../view/creation-form.js'; import SortPanel from '../view/sort-panel.js'; import TripInfo from '../view/trip-info.js'; import WaypointList from '../view/waypoint-list.js'; @@ -11,6 +10,15 @@ import { sortByDay, sortByTime, sortByPrice } from '../utils.js/sort.js'; import { UserAction, UpdateType } from '../utils.js/const.js'; import { filter } from '../utils.js/filter.js'; import { FilterType } from '../utils.js/filter.js'; +import LoadingView from '../view/loading-view.js'; +import UiBlocker from '../framework/ui-blocker/ui-blocker.js'; +import ErrorView from '../view/error-view.js'; + +const TimeLimit = { + LOWER_LIMIT: 350, + UPPER_LIMIT: 1000 +}; + export default class Presenter { #creationForm = null; @@ -18,35 +26,47 @@ export default class Presenter { #tripInfo = new TripInfo(); #waypointList = new WaypointList(); #listEmpty = null; - #destinationsModel; + #destinations = null; #pointsModel = null; #pointPresenterMap = new Map(); - #currentSortType = null; + #currentSortType = SORT_TYPE.DAY; #filterModel = null; #listMessageComponent = null; #tripEventsElement = null; #presenterNewPoint = null; - #offersModel = null; + #offers = null; + #isLoading = true; + #tripMainElement = null; + #loadingComponent = new LoadingView(); + #uiBlocker = new UiBlocker({ + lowerLimit: TimeLimit.LOWER_LIMIT, + upperLimit: TimeLimit.UPPER_LIMIT + }); + + #errorComponent = new ErrorView(); + + #pointPresenter = null; - constructor({ pointsModel, destinationsModel, offersModel, filterModel }) { + + constructor({ pointsModel, filterModel, onNewPointDestroy }) { this.#pointsModel = pointsModel; - this.#destinationsModel = destinationsModel; - this.#offersModel = offersModel; this.#filterModel = filterModel; + this.#offers = pointsModel.offers; this.pageHeaderElement = document.querySelector('.page-header'); - this.tripMainElement = this.pageHeaderElement.querySelector('.trip-main'); + this.#tripMainElement = this.pageHeaderElement.querySelector('.trip-main'); this.pageMainElement = document.querySelector('.page-main'); this.#tripEventsElement = this.pageMainElement.querySelector('.trip-events'); this.#pointsModel.addObserver(this.#handlerModelEvent); this.#filterModel.addObserver(this.#handlerModelEvent); this.#presenterNewPoint = new PresenterNewPoint({ - offersModel: this.#offersModel, - destinationsModel: this.#destinationsModel, + pointsModel: this.#pointsModel, + destinations: this.#pointsModel.destinations, + offers: this.#pointsModel.offers, containerList: this.#waypointList.element, onPointChange: this.#handleViewAction, onModeChange: this.#onModeChange, - handleDestroy: this.#handleNewPointButtonClose + onDestroy: onNewPointDestroy }); } @@ -73,19 +93,38 @@ export default class Presenter { } - #handleViewAction = (actionType, updateType, update) => { + #handleViewAction = async (actionType, updateType, update) => { + this.#uiBlocker.block(); + switch (actionType) { case UserAction.UPDATE_POINT: - this.#pointsModel.updatePoint(updateType, update); + this.#pointPresenterMap.get(update.id).setSaving(); + try { + await this.#pointsModel.updatePoint(updateType, update); + } catch (err) { + this.#pointPresenterMap.get(update.id).setAborting(); + } break; case UserAction.ADD_POINT: - this.#pointsModel.addPoint(updateType, update); + this.#presenterNewPoint.setSaving(); + try { + await this.#pointsModel.addPoint(updateType, update); + } catch (err) { + this.#pointPresenter.setAborting(); + } break; case UserAction.DELETE_POINT: - this.#pointsModel.deletePoint(updateType, update); + this.#pointPresenterMap.get(update.id).setDeleting(); + try { + await this.#pointsModel.deletePoint(updateType, update); + } catch (err) { + this.#pointPresenterMap.get(update.id).setAborting(); + } break; } + this.#uiBlocker.unblock(); + }; #handlerModelEvent = (updateType, data) => { @@ -101,27 +140,22 @@ export default class Presenter { this.#clearPoinsList({ resetSortType: true }); this.#renderWaypointList(); break; + case UpdateType.INIT: + this.#isLoading = false; + remove(this.#loadingComponent); + this.#renderWaypointList(); + break; } }; - #renderCreationform() { - this.#creationForm = new CreationForm({ - onClick: this.#handleNewPointButtonClick - }); - render(this.#creationForm, this.tripMainElement); + #renderLoadingMessage() { + render(this.#loadingComponent, this.#tripEventsElement); } - #handleNewPointButtonClose = () => { - this.#creationForm.element.disabled = false; - }; - - - #handleNewPointButtonClick = () => { - this.createPoint(); - this.#creationForm.element.disabled = true; - }; - + #renderErrorMessage() { + render(this.#errorComponent, this.#tripEventsElement); + } #onSortTypeChange = (sortType) => { if (this.#currentSortType === sortType) { @@ -135,9 +169,9 @@ export default class Presenter { #renderSortPanel() { this.#sortPanel = new SortPanel({ onSortTypeChange: this.#onSortTypeChange, + currentSortType: this.#currentSortType, }); - render(this.#sortPanel, this.#tripEventsElement); - + render(this.#sortPanel, this.#tripEventsElement, RenderPosition.AFTERBEGIN); } #clearPoinsList(resetSortType = false) { @@ -145,15 +179,16 @@ export default class Presenter { this.#pointPresenterMap.forEach((presenter) => presenter.destroy()); this.#pointPresenterMap.clear(); + remove(this.#sortPanel); + remove(this.#loadingComponent); remove(this.#listMessageComponent); - if (resetSortType) { this.#currentSortType = SORT_TYPE.DAY; } } #renderTripInfo() { - render(this.#tripInfo, this.tripMainElement, RenderPosition.AFTERBEGIN); + render(this.#tripInfo, this.#tripMainElement, RenderPosition.AFTERBEGIN); } #listEmptyMessage() { @@ -167,27 +202,42 @@ export default class Presenter { } #renderWaypoint = (point) => { - const pointPresenter = new PresenterWaypoint({ + this.#pointPresenter = new PresenterWaypoint({ + pointsModel: this.#pointsModel, pointListContainer: this.#waypointList.element, - destinationsModel: this.#destinationsModel, - offersModel: this.#offersModel, + destinationCurrent: this.#pointsModel.getDestinationId(point.destination), + offers: this.#offers, + offersCurrent: this.#pointsModel.getCurrentOffer(point.type), onPointChange: this.#handleViewAction, onModeChange: this.#onModeChange, }); - pointPresenter.init(point); - this.#pointPresenterMap.set(point.id, pointPresenter); + this.#pointPresenter.init(point); + this.#pointPresenterMap.set(point.id, this.#pointPresenter); }; #renderWaypointList = () => { render(this.#waypointList, this.#tripEventsElement); + if (this.#isLoading) { + this.#renderLoadingMessage(); + return; + } + + if (!this.points.length && !this.#pointsModel.offers.length && !this.#pointsModel.destinations.length) { + this.#renderErrorMessage(); + return; + } + + this.#renderSortPanel(); + if (this.points.length) { + remove(this.#loadingComponent); remove(this.#listMessageComponent); this.#renderWaypoints(); } else { - remove(this.#sortPanel); this.#listEmptyMessage(); + remove(this.#sortPanel); } }; @@ -198,8 +248,6 @@ export default class Presenter { }; init() { - this.#renderCreationform(); - this.#renderSortPanel(); this.#renderTripInfo(); this.#renderWaypointList(); } diff --git a/src/utils.js/const.js b/src/utils.js/const.js index 2f8c044..5b82484 100644 --- a/src/utils.js/const.js +++ b/src/utils.js/const.js @@ -7,5 +7,6 @@ export const UserAction = { export const UpdateType = { PATCH: 'PATCH', MINOR: 'MINOR', - MAJOR: 'MAJOR' + MAJOR: 'MAJOR', + INIT: 'INIT' }; diff --git a/src/utils.js/util.js b/src/utils.js/util.js index 811dd43..584acce 100644 --- a/src/utils.js/util.js +++ b/src/utils.js/util.js @@ -7,9 +7,9 @@ const getRandomInteger = (a = 0, b = 1) => { return Math.floor(lower + Math.random() * (upper - lower + 1)); }; -const getRandomArrayElement = (elements) => elements[getRandomInteger(0, elements.length - 1)]; +// const getRandomArrayElement = (elements) => elements[getRandomInteger(0, elements.length - 1)]; -const getRandomInt = (max) => Math.round(Math.random() * max); +// const getRandomInt = (max) => Math.round(Math.random() * max); const generateDates = () => { const maxGap = 14; @@ -62,7 +62,15 @@ const getDuration = (beginISO, endISO) => { const isEscapeKey = (evt) => evt.key === 'Escape'; -const dataChange = (item, prop) => ({ ...item, ...prop }); -// const updateDate = (data, update) => data.map((item) => (item.id === update.id ? update : item)); +// const dataChange = (item, prop) => ({ ...item, ...prop }); -export { getRandomInteger, getRandomArrayElement, getRandomInt, generateDates, dataChange, getDuration, isEscapeKey }; +const PRICE_FIELD_PATTERN = /\D+/; + +const validatePriceField = (value) => { + if (PRICE_FIELD_PATTERN.test(value)) { + value = 0; + } + return +value; +}; + +export { getRandomInteger, generateDates, getDuration, isEscapeKey, validatePriceField }; diff --git a/src/view/error-view.js b/src/view/error-view.js new file mode 100644 index 0000000..4056abe --- /dev/null +++ b/src/view/error-view.js @@ -0,0 +1,10 @@ +import AbstractView from '../framework/view/abstract-view.js'; + +const createErrorTemplate = () => '
Data connection error. Please try again later.
'; + +export default class ErrorView extends AbstractView { + + get template() { + return createErrorTemplate(); + } +} diff --git a/src/view/loading-view.js b/src/view/loading-view.js new file mode 100644 index 0000000..df5e52c --- /dev/null +++ b/src/view/loading-view.js @@ -0,0 +1,13 @@ +import AbstractView from '../framework/view/abstract-view.js'; + +function createLoadingTemplate() { + return 'Loading...
'; +} + +export default class LoadingView extends AbstractView { + + get template() { + return createLoadingTemplate(); + } + +} diff --git a/src/view/sort-panel.js b/src/view/sort-panel.js index 3728510..f453c27 100644 --- a/src/view/sort-panel.js +++ b/src/view/sort-panel.js @@ -1,10 +1,10 @@ import AbstractView from '../framework/view/abstract-view.js'; import { SORT_TYPE } from '../utils.js/sort.js'; -const createSortPanel = () => +const createSortPanel = (currentSortType) => `