Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions js/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const BASE_URL = 'https://31.javascript.htmlacademy.pro/kekstagram';
const Route = {
GET_DATA: '/data',
SEND_DATA: '/',
};
const Method = {
GET: 'GET',
POST: 'POST',
};
const ErrorText = {
GET_DATA: 'Не удалось загрузить данные. Попробуйте обновить страницу',
SEND_DATA: 'Не удалось отправить форму. Попробуйте ещё раз',
};

const load = (route, errorText, method = Method.GET, body = null) =>
fetch(`${BASE_URL}${route}`, {method, body})
.then((response) => {
if (!response.ok) {
throw new Error();
}
return response.json();
})
.catch(() => {
throw new Error(errorText);
});

export const getPhotos = () => load(Route.GET_DATA, ErrorText.GET_DATA);
export const sendPhoto = (body) => load(Route.SEND_DATA, ErrorText.SEND_DATA, Method.POST, body);
89 changes: 89 additions & 0 deletions js/dialogs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { isEscapeKey } from './utils.js';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Этот модуль нужно переделать:
1 - функция открытия диалогов и закрытия у тебя очень похожи, не считая errorMessageGetPhotos, поэтому вынеси общую логику, постарайся оставить 2 функции openDialog / closeDialog, ты можешь указывать параметром template который открыть, явно передавая его, за них отвечает messageElement
2 - все поиски элементов сделай за переделами функций
const messageElement = document.querySelector('.error');
и подобное, нам не нужно каждый раз при открытии искать элемент, ты их знаешь заранее, достаточно клонировать
3 - функции - это глагол, сейчас у тебя это не так: errorMessageSendPhoto, errorMessageGetPhotos и тд
4 - ты можешь запоминать последний открытый dialog в переменную, и потом этим воспользоваться в onOutsideClick, тебе не нужно искать постоянно элемент, ты его знаешь, а по закрытию очищаешь

dialog.remove();
dialog = null

5 - чтобы не делать проверку isErrorMessageOpen, ты можешь в onDocumentKeydown добавить вызов дополнительной функции, которая отвечает за отмену всплытия события


const onDocumentKeydown = (value) => (evt) => {
if (isEscapeKey(evt.key)) {
evt.preventDefault();
value();
}
};

const onOutsideClick = (value, sendingSuccess) => (evt) => {
const Element = document.querySelector(`.${value}`);
const InnerElement = document.querySelector(`.${value}__inner`);

if (Element && !InnerElement.contains(evt.target)) {
sendingSuccess();
}
};

const onCloseErrorButtonClick = () =>
closeErrorMessageSendPhoto();

function closeErrorMessageSendPhoto () {
const messageElement = document.querySelector('.error');
const errorButtonMessageSend = document.querySelector('.error__button');

if (messageElement) {
messageElement.remove();
document.removeEventListener('keydown', onDocumentKeydown(closeErrorMessageSendPhoto));
document.removeEventListener('click', onOutsideClick('error', closeErrorMessageSendPhoto));
errorButtonMessageSend.removeEventListener('click', onCloseErrorButtonClick);
}
}

export const errorMessageSendPhoto = () => {
const errorTemplate = document.querySelector('#error');
const errorElement = errorTemplate.content.cloneNode(true);

document.body.appendChild(errorElement);

const errorButtonMessageSend = document.querySelector('.error__button');

document.addEventListener('keydown', onDocumentKeydown(closeErrorMessageSendPhoto));
document.addEventListener('click', onOutsideClick('error', closeErrorMessageSendPhoto));
errorButtonMessageSend.addEventListener('click', onCloseErrorButtonClick);
};

export const errorMessageGetPhotos = () => {
const errorTemplate = document.querySelector('#data-error');
const errorElement = errorTemplate.content.cloneNode(true);

document.body.appendChild(errorElement);

const errorMessage = document.body.lastElementChild;

setTimeout(() => {
if (errorMessage && errorMessage.parentNode) {
errorMessage.remove();
}
}, 5000);
};

const onCloseSuccessButtonClick = () =>
closeSuccessMessageSendPhoto();

function closeSuccessMessageSendPhoto () {
const messageElement = document.querySelector('.success');
const successButtonMessageSend = document.querySelector('.success__button');

if (messageElement) {
messageElement.remove();
document.removeEventListener('keydown', onDocumentKeydown(closeSuccessMessageSendPhoto));
document.removeEventListener('click', onOutsideClick('success', closeSuccessMessageSendPhoto));
successButtonMessageSend.removeEventListener('click', onCloseSuccessButtonClick);
}
}

export const successMessageSendPhoto = () => {
const SuccessTemplate = document.querySelector('#success');
const SuccessElement = SuccessTemplate.content.cloneNode(true);

document.body.appendChild(SuccessElement);

const successButtonMessageSend = document.querySelector('.success__button');

document.addEventListener('keydown', onDocumentKeydown(closeSuccessMessageSendPhoto));
document.addEventListener('click', onOutsideClick('success', closeSuccessMessageSendPhoto));
successButtonMessageSend.addEventListener('click', onCloseSuccessButtonClick);
};

23 changes: 23 additions & 0 deletions js/filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getRandomInteger } from './utils.js';

const MAX_RANDOM_PHOTO = 10;

export const shuffleArray = (array) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Как вариант, но есть проще, через toSorte, он не идеальный, но для наших задач подойдет.

const shuffled = [...array];

for (let i = shuffled.length - 1; i > 0; i--) {
const randomIndex = getRandomInteger(0, i);
[shuffled[i], shuffled[randomIndex]] = [shuffled[randomIndex], shuffled[i]];
}

return shuffled.slice(0, MAX_RANDOM_PHOTO);
};

export const resetActiveButtons = (arr) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ты можешь использовать прием, который у тебя с effects, и запоминать текущий фильтр, если же тебе он не нравится, то не нужно делать forEach, ты можешь найти по классу нужный фильтр и снять с него active

arr.forEach((button) => {
button.classList.remove('img-filters__button--active');
});
};

export const sortedByArray = (data) =>
[...data].sort((a, b) => b.comments.length - a.comments.length);
77 changes: 25 additions & 52 deletions js/form.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {renderGallery, setGalleryData} from './gallery.js';
import {showErrorMessage} from './utils.js';
import {errorMessageSendPhoto, successMessageSendPhoto} from './dialogs.js';
import { onCloseImageEditor } from './upload-picture.js';
import { sendPhoto } from './api.js';

const hashtagPattern = /^#[a-zа-яё0-9]{1,19}$/i;
const MAX_HASHTAGS = 5;
Expand All @@ -10,52 +10,11 @@ const imgUploadSubmit = imgUploadForm.querySelector('.img-upload__submit');
const descriptionInput = imgUploadForm.querySelector('.text__description');
const hashtagsInput = imgUploadForm.querySelector('.text__hashtags');

const BASE_URL = 'https://31.javascript.htmlacademy.pro/kekstagram';
const Route = {
GET_DATA: '/data',
SEND_DATA: '/',
};
const Method = {
GET: 'GET',
POST: 'POST',
};
const ErrorText = {
GET_DATA: 'Не удалось загрузить данные. Попробуйте обновить страницу',
SEND_DATA: 'Не удалось отправить форму. Попробуйте ещё раз',
};
const SubmitButtonText = {
IDLE: 'Сохранить',
SENDING: 'Сохраняю...'
};


const load = (route, errorText, method = Method.GET, body = null) =>
fetch(`${BASE_URL}${route}`, {method, body})
.then((response) => {
if (!response.ok) {
throw new Error();
}
return response.json();
})
.catch(() => {
throw new Error(errorText);
});

const getData = () => load(Route.GET_DATA, ErrorText.GET_DATA);

export const sendData = (body) => load(Route.SEND_DATA, ErrorText.SEND_DATA, Method.POST, body);

getData()
.then((gallery) => {
renderGallery(gallery);
setGalleryData(gallery);
})
.catch(
(err) => {
showErrorMessage(err.message);
}
);

const toggleSubmitButton = (disabled) => {
imgUploadSubmit.disabled = disabled;
imgUploadSubmit.textContent = disabled
Expand All @@ -78,24 +37,38 @@ const isHashtagsValid = (value) => {

const hashtagsArray = trimmedValue.split(/\s+/);

if (hashtagsArray.length > MAX_HASHTAGS) {
return false;
return hashtagsArray.every((hashtag) => hashtagPattern.test(hashtag));
};

const isMaxHashtagsValid = (value) => {
const trimmedValue = value.trim();
const hashtags = trimmedValue.split(/\s+/);
if (trimmedValue === '') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Стиль кода + упрости if

return true;
}

const uniqueHashtags = new Set(hashtagsArray.map((h) => h.toLowerCase()));
return hashtags.length <= MAX_HASHTAGS;
};

const isUniqueHashtagsValid = (value) => {
const trimmedValue = value.trim();

if (uniqueHashtags.size !== hashtagsArray.length) {
return false;
if (trimmedValue === '') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Упрости if

return true;
}

return hashtagsArray.every((hashtag) => hashtagPattern.test(hashtag));
const hashtags = trimmedValue.split(/\s+/);
const uniqueHashtags = new Set(hashtags.map((h) => h.toLowerCase()));
return uniqueHashtags.size === hashtags.length;
};

const isDescriptionValid = (value) =>
value.trim() === '' || value.length <= MAX_DESCRIPTION_LEN;

pristine.addValidator(hashtagsInput, isHashtagsValid, 'введён невалидный хэштег');
pristine.addValidator(descriptionInput, isDescriptionValid, 'длина комментария больше 140 символов.');
pristine.addValidator(hashtagsInput, isMaxHashtagsValid, `Не более ${MAX_HASHTAGS} хэштегов`);
pristine.addValidator(hashtagsInput, isUniqueHashtagsValid, 'Хэштеги повторяются.');

imgUploadForm.addEventListener('submit', async (evt) => {
evt.preventDefault();
Expand All @@ -105,10 +78,11 @@ imgUploadForm.addEventListener('submit', async (evt) => {
try {
toggleSubmitButton(true);

await sendData(new FormData(evt.target));
await sendPhoto(new FormData(evt.target));
onCloseImageEditor();
successMessageSendPhoto();
} catch (err) {
showErrorMessage(err.message);
errorMessageSendPhoto(err.message);
} finally {
toggleSubmitButton(false);
}
Expand All @@ -118,4 +92,3 @@ imgUploadForm.addEventListener('submit', async (evt) => {
hashtagsInput.focus();
}
});

4 changes: 2 additions & 2 deletions js/fullscreen-picture.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ const bigPictureCommentsLoader = bigPicture.querySelector('.comments-loader');
let visibleСomments = COMMENTS_SHOWN;
let currentPhotoData = null;

function onDocumentKeydown (evt){
const onDocumentKeydown = (evt) => {
if (isEscapeKey(evt.key)) {
evt.preventDefault();
closePhotoModal();
}
}
};

export function closePhotoModal () {
bigPicture.classList.add('hidden');
Expand Down
6 changes: 6 additions & 0 deletions js/gallery.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,9 @@ pictureList.addEventListener('click', (evt) => {

openBigPicture(photoData);
});

export const clearGallery = () => {
const galleryItems = document.querySelectorAll('.picture');

galleryItems.forEach((item) => item.remove());
};
58 changes: 58 additions & 0 deletions js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,61 @@ import './gallery.js';
import './fullscreen-picture.js';
import './upload-picture.js';
import './form.js';
import './api.js';

import { errorMessageGetPhotos } from './dialogs.js';
import {renderGallery, setGalleryData, clearGallery} from './gallery.js';
import { getPhotos } from './api.js';
import { shuffleArray, resetActiveButtons, sortedByArray } from './filter.js';

try {
const gallery = await getPhotos();
renderGallery(gallery);
setGalleryData(gallery);

const filtersContainer = document.querySelector('.img-filters');
const filterButtons = filtersContainer.querySelectorAll('.img-filters__button');
const filterRandom = filtersContainer.querySelector('#filter-random');
const defaultFilter = filtersContainer.querySelector('#filter-default');
const filterDiscussed = filtersContainer.querySelector('#filter-discussed');

filtersContainer.classList.remove('img-filters--inactive');

if (filterRandom) {
filterRandom.addEventListener('click', (evt) => {
clearGallery();
resetActiveButtons(filterButtons);

evt.target.classList.add('img-filters__button--active');

const shuffledGallery = shuffleArray(gallery);
renderGallery(shuffledGallery);
});
}

if (defaultFilter) {
defaultFilter.addEventListener('click', (evt) => {
clearGallery();
resetActiveButtons(filterButtons);

evt.target.classList.add('img-filters__button--active');

renderGallery(gallery);
});
}

if (filterDiscussed) {
filterDiscussed.addEventListener('click', (evt) => {
clearGallery();
resetActiveButtons(filterButtons);

evt.target.classList.add('img-filters__button--active');

const sortedByComments = sortedByArray(gallery);
renderGallery(sortedByComments);
});
}

} catch (err) {
errorMessageGetPhotos(err.message);
}
4 changes: 3 additions & 1 deletion js/upload-picture.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ imgUploadInput.addEventListener('change', onOpenImageEditor);
imgUploadCancel.addEventListener('click', onCloseImageEditor);

function onDocumentKeydown (evt) {
if (isEscapeKey(evt.key) && !isInputFocused()) {
const isErrorMessageOpen = document.querySelector('.error');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно избавиться от этой части, описано будет в dialogs


if (isEscapeKey(evt.key) && !isInputFocused() && !isErrorMessageOpen) {
evt.preventDefault();
onCloseImageEditor();
}
Expand Down
18 changes: 5 additions & 13 deletions js/utils.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
export const isEscapeKey = (key) => key === 'Escape';

export const showErrorMessage = () => {
const errorTemplate = document.querySelector('#data-error');
const errorElement = errorTemplate.content.cloneNode(true);

document.body.appendChild(errorElement);

const errorMessage = document.body.lastElementChild;

setTimeout(() => {
if (errorMessage && errorMessage.parentNode) {
errorMessage.remove();
}
}, 5000);
export 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);
};