diff --git a/index.html b/index.html index 07156d3..2ff8451 100644 --- a/index.html +++ b/index.html @@ -15,12 +15,7 @@ Фильтр фотографий - + + `${BASE_URL}/${endpoint[0] === '/' ? endpoint.slice(1) : endpoint}`; + +const isSuccess = (response) => { + // Статусы 200..299 считаем успешными (https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status) + if (response.status >= 200 && response.status < 300) { + return true; + } + return false; +}; + +const isError = (response) => { + // 400 и далее - ошибка (всё та же спецификация) + if (response.status >= 400) { + return true; + } + return false; +}; + +const dataHandler = async (response) => { + if (isSuccess(response)) { + return response.json(); + } else if (isError(response)) { + const text = await response.text(); + throw new Error(text); + } +}; + +/** axios at home */ +const api = { + get: (endpoint, config) => fetch(getUrl(endpoint), config).then(dataHandler), + post: (endpoint, body, config) => + fetch(getUrl(endpoint), { + method: 'POST', + body, + ...config, + }).then(dataHandler), +}; + +export { api }; diff --git a/js/form.js b/js/form.js index 53a9ad5..e51a252 100644 --- a/js/form.js +++ b/js/form.js @@ -1,5 +1,7 @@ import { Modal } from './modal.js'; import * as imageProcessing from './image-proccessing.js'; +import { api } from './api.js'; +import { notification } from './notification.js'; const uploadInputEl = document.querySelector('input.img-upload__input'); const uploadOverlayEl = document.querySelector('.img-upload__overlay'); @@ -28,19 +30,32 @@ const addValidator = (inputEl, callback, message) => { ); }; -submitEl.addEventListener('click', (evt) => { - if (!pristine.validate()) { - evt.preventDefault(); - } -}); - const modal = new Modal(uploadOverlayEl, uploadCancelEl, { onClose: () => { uploadInputEl.value = ''; imageProcessing.reset(); + formEl.reset(); + resetErrors(); }, }); +submitEl.addEventListener('click', (evt) => { + evt.preventDefault(); + if (!pristine.validate()) { + return; + } + const body = new FormData(formEl); + api + .post('/', body) + .then(() => { + notification.success(); + modal.close(); + }) + .catch(() => { + notification.error(); + }); +}); + const initializeForm = () => { uploadInputEl.addEventListener('change', () => { modal.open(); @@ -110,4 +125,9 @@ addValidator( '' ); +function resetErrors() { + descriptionError.style.display = 'none'; + hashtagError.style.display = 'none'; +} + export { initializeForm }; diff --git a/js/get-posts.js b/js/get-posts.js index bc989ed..4f67efa 100644 --- a/js/get-posts.js +++ b/js/get-posts.js @@ -1,92 +1,9 @@ -import { getRandomArrayElement, getRandomInteger } from './util.js'; +import { api } from './api.js'; +import { notification } from './notification.js'; -const DESCRIPTION = [ - '5 минут, полет нормальный', - 'Терпение и труд-инфаркт и инсульт', - 'Никогда такого не было и вот опять', - 'Утро началось с третьей попытки', - 'В 20:31 прибыл Годжо Сатору', - 'Сидим с бобром за столом', -]; -const AVATAR = [1, 2, 3, 4, 5, 6]; -const MESSAGE = [ - 'Всё отлично!', - 'В целом всё неплохо. Но не всё.', - 'Когда вы делаете фотографию, хорошо бы убирать палец из кадра. В конце концов это просто непрофессионально.', - 'Моя бабушка случайно чихнула с фотоаппаратом в руках и у неё получилась фотография лучше.', - 'Я поскользнулся на банановой кожуре и уронил фотоаппарат на кота и у меня получилась фотография лучше.', - 'Лица у людей на фотке перекошены, как будто их избивают.', - 'Как можно было поймать такой неудачный момент?!', -]; -const NAME = [ - 'Александр', - 'Егор', - 'Леонид', - 'Иван', - 'Максим', - 'Владислав', - 'Фёдор', - 'Алексей', - 'Матвей', - 'Никита', - 'Илья', - 'Евгений', - 'Михаил', - 'Даниил', - 'Кирилл', - 'Ксения', - 'Матвей', - 'Юлия', - 'Татьяна', - 'Дарья', - 'Галима', - 'Маргарита', - 'Алиса', - 'Виктория', - 'Мила', - 'Елизавета', - 'Ольга', - 'Кира', - 'Нина', - 'Злата', -]; - -const LIKES = []; -for (let i = 15; i <= 200; ++i) { - LIKES.push(i); -} - -const NUMBER_COMMENTS_MIN = 0; -const NUMBER_COMMENTS_MAX = 30; - -const ID = [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, -]; - -let lastCommentId = 1; - -const createComment = () => ({ - id: ++lastCommentId, // 1 - avatar: `img/avatar-${getRandomArrayElement(AVATAR)}.svg`, - message: getRandomArrayElement(MESSAGE), - name: getRandomArrayElement(NAME), -}); - -const getRandomSimilarComments = () => - Array.from( - { length: getRandomInteger(NUMBER_COMMENTS_MIN, NUMBER_COMMENTS_MAX) }, - createComment - ); - -const createPost = () => ({ - id: getRandomArrayElement(ID), - url: `photos/${getRandomArrayElement(ID)}.jpg`, - description: getRandomArrayElement(DESCRIPTION), - likes: getRandomArrayElement(LIKES), - comments: getRandomSimilarComments(), -}); - -const getPosts = (length = 25) => Array.from({ length }, createPost); +const getPosts = () => + api.get('data').catch(() => { + notification.dataError(); + }); export { getPosts }; diff --git a/js/image-proccessing.js b/js/image-proccessing.js index 026e6bf..8fe5439 100644 --- a/js/image-proccessing.js +++ b/js/image-proccessing.js @@ -1,7 +1,7 @@ const fieldScaleControl = document.querySelector('.scale__control--value'); const scaleSmaller = document.querySelector('.scale__control--smaller'); const scaleBigger = document.querySelector('.scale__control--bigger'); -const imgPreview = document.querySelector('.img-upload__preview'); +const imgPreview = document.querySelector('.img-upload__preview > img'); const effectSliderContainer = document.querySelector( '.img-upload__effect-level' diff --git a/js/main.js b/js/main.js index f73dd17..f76d45b 100644 --- a/js/main.js +++ b/js/main.js @@ -2,8 +2,10 @@ import { drawMiniatures } from './draw-miniatures.js'; import { initializeForm } from './form.js'; import { getPosts } from './get-posts.js'; -const posts = getPosts(); - -drawMiniatures(posts); +getPosts() + .then((posts) => { + drawMiniatures(posts); + }) + .catch(() => {}); initializeForm(); diff --git a/js/modal.js b/js/modal.js index d88d4ef..b350868 100644 --- a/js/modal.js +++ b/js/modal.js @@ -22,14 +22,14 @@ class Modal { open() { this.modalEl.classList.remove('hidden'); document.body.classList.add('modal-open'); - document.addEventListener('keydown', this.onDocumentKeydown); + document.onkeydown = this.onDocumentKeydown; this.onOpen?.(); } close() { this.modalEl.classList.add('hidden'); document.body.classList.remove('modal-open'); - document.removeEventListener('keydown', this.onDocumentKeydown); + document.onkeydown = undefined; this.onClose?.(); } } diff --git a/js/notification.js b/js/notification.js new file mode 100644 index 0000000..f28dc19 --- /dev/null +++ b/js/notification.js @@ -0,0 +1,60 @@ +import { isEscapeKey } from './util.js'; + +const MILLISECONDS_PER_SECOND = 1000; +const DEFAULT_TIMEOUT = 5; // в секундах + +const dataErrorTemplate = document.getElementById('data-error').content; +const errorTemplate = document.getElementById('error').content; +const successTemplate = document.getElementById('success').content; + +const showMessage = (template, config = {}) => { + const timeout = (config.timeout ?? DEFAULT_TIMEOUT) * MILLISECONDS_PER_SECOND; + const message = template.cloneNode(true).children[0]; + document.body.appendChild(message); + const previousOnkeydown = document.onkeydown; + const close = () => { + message?.remove(); + document.onkeydown = previousOnkeydown; + }; + document.onkeydown = (evt) => { + if (!isEscapeKey(evt)) { + return; + } + close(); + }; + message.onclick = (evt) => { + if (evt.target === message) { + close(); + } + }; + setTimeout(() => { + close(); + }, timeout); + return message; +}; + +const notification = { + dataError: (config = {}) => { + showMessage(dataErrorTemplate, config); + }, + error: (config = {}) => { + const message = showMessage(errorTemplate, config); + const button = message.querySelector('button.error__button'); + button.onclick = (evt) => { + if (!config.onRetry?.(evt)) { + message.remove(); + } + }; + }, + success: (config = {}) => { + const message = showMessage(successTemplate, config); + const button = message.querySelector('button.success__button'); + button.onclick = (evt) => { + if (!config.onClose?.(evt)) { + message.remove(); + } + }; + }, +}; + +export { notification }; diff --git a/js/util.js b/js/util.js index a44e962..90ffa7f 100644 --- a/js/util.js +++ b/js/util.js @@ -1,13 +1,3 @@ -const getRandomInteger = (a, b) => { - const lower = Math.ceil(Math.min(a, b)); - const upper = Math.floor(Math.max(a, b)); - const result = Math.random() * (upper - lower + 1) + lower; - return Math.floor(result); -}; - -const getRandomArrayElement = (elements) => - elements[getRandomInteger(0, elements.length - 1)]; - const isEscapeKey = (evt) => evt.key === 'Escape'; -export { getRandomInteger, getRandomArrayElement, isEscapeKey }; +export { isEscapeKey };