Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -210,19 +210,19 @@ <h2 class="big-picture__title visually-hidden">Просмотр фотогра
<!-- Сообщение с ошибкой загрузки изображения -->
<template id="error">
<section class="error">
<div class="error__inner">
<div class="error__inner" data-dialog-content>
<h2 class="error__title">Ошибка загрузки файла</h2>
<button type="button" class="error__button">Попробовать ещё раз</button>
<button type="button" class="error__button" data-dialog-close>Попробовать ещё раз</button>
</div>
</section>
</template>

<!-- Сообщение об успешной загрузке изображения -->
<template id="success">
<section class="success">
<div class="success__inner">
<div class="success__inner" data-dialog-content>
<h2 class="success__title">Изображение успешно загружено</h2>
<button type="button" class="success__button">Круто!</button>
<button type="button" class="success__button" data-dialog-close>Круто!</button>
</div>
</section>
</template>
Expand Down
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);
64 changes: 64 additions & 0 deletions js/dialogs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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 ERROR_DISPLAY_DURATION = 5000;

let currentDialog = null;

const errorTemplate = document.querySelector('#error');
const successTemplate = document.querySelector('#success');
const dataErrorTemplate = document.querySelector('#data-error');

const onCloseKeydown = (evt) => {
if (isEscapeKey(evt.key) && currentDialog) {
evt.preventDefault();
closeDialog();
evt.stopPropagation();
}
};

function closeDialog () {
if (currentDialog) {
currentDialog.remove();
currentDialog = null;

document.removeEventListener('keydown', onCloseKeydown, true);
}
}

const onCloseClick = (evt) => {
if (evt.target.closest('[data-dialog-close]') || !evt.target.closest('[data-dialog-content]')) {
closeDialog();
}
};

const openDialog = (template) => {
const dialogElement = template.content.firstElementChild.cloneNode(true);

document.body.appendChild(dialogElement);

currentDialog = dialogElement;

document.addEventListener('keydown', onCloseKeydown, true);
};

document.addEventListener('click', onCloseClick);

export const showError = () => {
openDialog(errorTemplate);
};

export const showSuccess = () => {
openDialog(successTemplate);
};

export const showDataError = () => {
const errorElement = dataErrorTemplate.content.firstElementChild.cloneNode(true);

document.body.appendChild(errorElement);

setTimeout(() => {
if (errorElement) {
errorElement.remove();
}
}, ERROR_DISPLAY_DURATION);
};
61 changes: 61 additions & 0 deletions js/filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { renderGallery, clearGallery, getGalleryData } from './gallery.js';
import { debounce } from './utils.js';

const RERENDER_DELAY = 500;

const filterName = {
FILTER_RANDOM: 'filter-random',
FILTER_DISCUSSED: 'filter-discussed',
FILTER_DEFAULT: 'filter-default'
};

const filtersContainer = document.querySelector('.img-filters');
let activeFilterButton = filtersContainer.querySelector('.img-filters__button--active');

const shuffleArray = (data) =>
data.toSorted(() => Math.random() - 0.5);

const sortByCommentsCount = (data) =>
data.toSorted((a, b) => b.comments.length - a.comments.length);

const getFilteredData = (filterId) => {
const galleryData = getGalleryData();

switch (filterId) {
case filterName.FILTER_RANDOM:
return shuffleArray(galleryData);

case filterName.FILTER_DISCUSSED:
return sortByCommentsCount(galleryData);

case filterName.FILTER_DEFAULT:
default:
return galleryData;
}
};

const handleFilterClick = (evt) => {
const button = evt.target.closest('.img-filters__button');

if (!button) {
return;
}

if (activeFilterButton) {
activeFilterButton.classList.remove('img-filters__button--active');
}

button.classList.add('img-filters__button--active');
activeFilterButton = button;

const filteredData = getFilteredData(button.id);

clearGallery();

debounce(() => renderGallery(filteredData), RERENDER_DELAY)();
};

export const initFilters = () => {
filtersContainer.classList.remove('img-filters--inactive');
filtersContainer.addEventListener('click', handleFilterClick);
};
100 changes: 29 additions & 71 deletions js/form.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,24 @@
import {renderGallery, setGalleryData} from './gallery.js';
import {showErrorMessage} from './utils.js';
import {showError, showSuccess} 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;
const MAX_DESCRIPTION_LEN = 140;
const imgUploadForm = document.querySelector('.img-upload__form');
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 errorMessage = {
INVALID_HASHTAG: 'Введён невалидный хэштег',
DESCRIPTION_TOO_LONG: 'Длина комментария больше 140 символов',
HASHTAGS_TOO_MUCH: `Не более ${MAX_HASHTAGS} хэштегов`,
HASHTAGS_UNIQUE:'Хэштеги повторяются.'
};
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 imgUploadForm = document.querySelector('.img-upload__form');
const imgUploadSubmit = imgUploadForm.querySelector('.img-upload__submit');
const descriptionInput = imgUploadForm.querySelector('.text__description');
const hashtagsInput = imgUploadForm.querySelector('.text__hashtags');

const toggleSubmitButton = (disabled) => {
imgUploadSubmit.disabled = disabled;
Expand All @@ -69,33 +33,31 @@ const pristine = new Pristine(imgUploadForm, {
errorTextClass: 'img-upload__field-wrapper--error'
});

const isHashtagsValid = (value) => {
const getHashtags = (value) => {
const trimmedValue = value.trim();
return trimmedValue === '' ? [] : trimmedValue.split(/\s+/);
};

if (trimmedValue === '') {
return true;
}

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

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

const uniqueHashtags = new Set(hashtagsArray.map((h) => h.toLowerCase()));
const isHashtagsCountValid = (value) =>
getHashtags(value).length <= MAX_HASHTAGS;

if (uniqueHashtags.size !== hashtagsArray.length) {
return false;
}
const isHashtagsUnique = (value) => {
const hashtags = getHashtags(value);
const uniqueHashtags = new Set(hashtags.map((h) => h.toLowerCase()));

return hashtagsArray.every((hashtag) => hashtagPattern.test(hashtag));
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, isHashtagsValid, errorMessage.INVALID_HASHTAG);
pristine.addValidator(descriptionInput, isDescriptionValid, errorMessage.DESCRIPTION_TOO_LONG);
pristine.addValidator(hashtagsInput, isHashtagsCountValid, errorMessage.HASHTAGS_TOO_MUCH);
pristine.addValidator(hashtagsInput, isHashtagsUnique, errorMessage.HASHTAGS_UNIQUE);

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

await sendData(new FormData(evt.target));
await sendPhoto(new FormData(evt.target));
showSuccess();
onCloseImageEditor();
} catch (err) {
showErrorMessage(err.message);
showError(err.message);
} finally {
toggleSubmitButton(false);
}
} else {
hashtagsInput.style.borderColor = 'red';

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
29 changes: 0 additions & 29 deletions js/functions.js

This file was deleted.

8 changes: 8 additions & 0 deletions js/gallery.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export const setGalleryData = (data) => {
galleryData = data;
};

export const getGalleryData = () => galleryData;

const getPhotoById = (id) => galleryData.find((photo) => photo.id === id);

export const renderGallery = (data) => {
Expand All @@ -46,3 +48,9 @@ pictureList.addEventListener('click', (evt) => {

openBigPicture(photoData);
});

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

galleryItems.forEach((item) => item.remove());
};
Loading