Skip to content

Commit 6f00672

Browse files
authored
Merge pull request #14 from Musa-Ismailov/module12-task1
2 parents a5de013 + 65d0ece commit 6f00672

File tree

11 files changed

+219
-119
lines changed

11 files changed

+219
-119
lines changed

index.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,19 +210,19 @@ <h2 class="big-picture__title visually-hidden">Просмотр фотогра
210210
<!-- Сообщение с ошибкой загрузки изображения -->
211211
<template id="error">
212212
<section class="error">
213-
<div class="error__inner">
213+
<div class="error__inner" data-dialog-content>
214214
<h2 class="error__title">Ошибка загрузки файла</h2>
215-
<button type="button" class="error__button">Попробовать ещё раз</button>
215+
<button type="button" class="error__button" data-dialog-close>Попробовать ещё раз</button>
216216
</div>
217217
</section>
218218
</template>
219219

220220
<!-- Сообщение об успешной загрузке изображения -->
221221
<template id="success">
222222
<section class="success">
223-
<div class="success__inner">
223+
<div class="success__inner" data-dialog-content>
224224
<h2 class="success__title">Изображение успешно загружено</h2>
225-
<button type="button" class="success__button">Круто!</button>
225+
<button type="button" class="success__button" data-dialog-close>Круто!</button>
226226
</div>
227227
</section>
228228
</template>

js/api.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const BASE_URL = 'https://31.javascript.htmlacademy.pro/kekstagram';
2+
const Route = {
3+
GET_DATA: '/data',
4+
SEND_DATA: '/',
5+
};
6+
const Method = {
7+
GET: 'GET',
8+
POST: 'POST',
9+
};
10+
const ErrorText = {
11+
GET_DATA: 'Не удалось загрузить данные. Попробуйте обновить страницу',
12+
SEND_DATA: 'Не удалось отправить форму. Попробуйте ещё раз',
13+
};
14+
15+
const load = (route, errorText, method = Method.GET, body = null) =>
16+
fetch(`${BASE_URL}${route}`, {method, body})
17+
.then((response) => {
18+
if (!response.ok) {
19+
throw new Error();
20+
}
21+
return response.json();
22+
})
23+
.catch(() => {
24+
throw new Error(errorText);
25+
});
26+
27+
export const getPhotos = () => load(Route.GET_DATA, ErrorText.GET_DATA);
28+
export const sendPhoto = (body) => load(Route.SEND_DATA, ErrorText.SEND_DATA, Method.POST, body);

js/dialogs.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { isEscapeKey } from './utils.js';
2+
3+
const ERROR_DISPLAY_DURATION = 5000;
4+
5+
let currentDialog = null;
6+
7+
const errorTemplate = document.querySelector('#error');
8+
const successTemplate = document.querySelector('#success');
9+
const dataErrorTemplate = document.querySelector('#data-error');
10+
11+
const onCloseKeydown = (evt) => {
12+
if (isEscapeKey(evt.key) && currentDialog) {
13+
evt.preventDefault();
14+
closeDialog();
15+
evt.stopPropagation();
16+
}
17+
};
18+
19+
function closeDialog () {
20+
if (currentDialog) {
21+
currentDialog.remove();
22+
currentDialog = null;
23+
24+
document.removeEventListener('keydown', onCloseKeydown, true);
25+
}
26+
}
27+
28+
const onCloseClick = (evt) => {
29+
if (evt.target.closest('[data-dialog-close]') || !evt.target.closest('[data-dialog-content]')) {
30+
closeDialog();
31+
}
32+
};
33+
34+
const openDialog = (template) => {
35+
const dialogElement = template.content.firstElementChild.cloneNode(true);
36+
37+
document.body.appendChild(dialogElement);
38+
39+
currentDialog = dialogElement;
40+
41+
document.addEventListener('keydown', onCloseKeydown, true);
42+
};
43+
44+
document.addEventListener('click', onCloseClick);
45+
46+
export const showError = () => {
47+
openDialog(errorTemplate);
48+
};
49+
50+
export const showSuccess = () => {
51+
openDialog(successTemplate);
52+
};
53+
54+
export const showDataError = () => {
55+
const errorElement = dataErrorTemplate.content.firstElementChild.cloneNode(true);
56+
57+
document.body.appendChild(errorElement);
58+
59+
setTimeout(() => {
60+
if (errorElement) {
61+
errorElement.remove();
62+
}
63+
}, ERROR_DISPLAY_DURATION);
64+
};

js/filters.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { renderGallery, clearGallery, getGalleryData } from './gallery.js';
2+
import { debounce } from './utils.js';
3+
4+
const RERENDER_DELAY = 500;
5+
6+
const filterName = {
7+
FILTER_RANDOM: 'filter-random',
8+
FILTER_DISCUSSED: 'filter-discussed',
9+
FILTER_DEFAULT: 'filter-default'
10+
};
11+
12+
const filtersContainer = document.querySelector('.img-filters');
13+
let activeFilterButton = filtersContainer.querySelector('.img-filters__button--active');
14+
15+
const shuffleArray = (data) =>
16+
data.toSorted(() => Math.random() - 0.5);
17+
18+
const sortByCommentsCount = (data) =>
19+
data.toSorted((a, b) => b.comments.length - a.comments.length);
20+
21+
const getFilteredData = (filterId) => {
22+
const galleryData = getGalleryData();
23+
24+
switch (filterId) {
25+
case filterName.FILTER_RANDOM:
26+
return shuffleArray(galleryData);
27+
28+
case filterName.FILTER_DISCUSSED:
29+
return sortByCommentsCount(galleryData);
30+
31+
case filterName.FILTER_DEFAULT:
32+
default:
33+
return galleryData;
34+
}
35+
};
36+
37+
const handleFilterClick = (evt) => {
38+
const button = evt.target.closest('.img-filters__button');
39+
40+
if (!button) {
41+
return;
42+
}
43+
44+
if (activeFilterButton) {
45+
activeFilterButton.classList.remove('img-filters__button--active');
46+
}
47+
48+
button.classList.add('img-filters__button--active');
49+
activeFilterButton = button;
50+
51+
const filteredData = getFilteredData(button.id);
52+
53+
clearGallery();
54+
55+
debounce(() => renderGallery(filteredData), RERENDER_DELAY)();
56+
};
57+
58+
export const initFilters = () => {
59+
filtersContainer.classList.remove('img-filters--inactive');
60+
filtersContainer.addEventListener('click', handleFilterClick);
61+
};

js/form.js

Lines changed: 29 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,24 @@
1-
import {renderGallery, setGalleryData} from './gallery.js';
2-
import {showErrorMessage} from './utils.js';
1+
import {showError, showSuccess} from './dialogs.js';
32
import { onCloseImageEditor } from './upload-picture.js';
3+
import { sendPhoto } from './api.js';
44

55
const hashtagPattern = /^#[a-zа-яё0-9]{1,19}$/i;
66
const MAX_HASHTAGS = 5;
77
const MAX_DESCRIPTION_LEN = 140;
8-
const imgUploadForm = document.querySelector('.img-upload__form');
9-
const imgUploadSubmit = imgUploadForm.querySelector('.img-upload__submit');
10-
const descriptionInput = imgUploadForm.querySelector('.text__description');
11-
const hashtagsInput = imgUploadForm.querySelector('.text__hashtags');
12-
13-
const BASE_URL = 'https://31.javascript.htmlacademy.pro/kekstagram';
14-
const Route = {
15-
GET_DATA: '/data',
16-
SEND_DATA: '/',
17-
};
18-
const Method = {
19-
GET: 'GET',
20-
POST: 'POST',
21-
};
22-
const ErrorText = {
23-
GET_DATA: 'Не удалось загрузить данные. Попробуйте обновить страницу',
24-
SEND_DATA: 'Не удалось отправить форму. Попробуйте ещё раз',
8+
const errorMessage = {
9+
INVALID_HASHTAG: 'Введён невалидный хэштег',
10+
DESCRIPTION_TOO_LONG: 'Длина комментария больше 140 символов',
11+
HASHTAGS_TOO_MUCH: `Не более ${MAX_HASHTAGS} хэштегов`,
12+
HASHTAGS_UNIQUE:'Хэштеги повторяются.'
2513
};
2614
const SubmitButtonText = {
2715
IDLE: 'Сохранить',
2816
SENDING: 'Сохраняю...'
2917
};
30-
31-
32-
const load = (route, errorText, method = Method.GET, body = null) =>
33-
fetch(`${BASE_URL}${route}`, {method, body})
34-
.then((response) => {
35-
if (!response.ok) {
36-
throw new Error();
37-
}
38-
return response.json();
39-
})
40-
.catch(() => {
41-
throw new Error(errorText);
42-
});
43-
44-
const getData = () => load(Route.GET_DATA, ErrorText.GET_DATA);
45-
46-
export const sendData = (body) => load(Route.SEND_DATA, ErrorText.SEND_DATA, Method.POST, body);
47-
48-
getData()
49-
.then((gallery) => {
50-
renderGallery(gallery);
51-
setGalleryData(gallery);
52-
})
53-
.catch(
54-
(err) => {
55-
showErrorMessage(err.message);
56-
}
57-
);
18+
const imgUploadForm = document.querySelector('.img-upload__form');
19+
const imgUploadSubmit = imgUploadForm.querySelector('.img-upload__submit');
20+
const descriptionInput = imgUploadForm.querySelector('.text__description');
21+
const hashtagsInput = imgUploadForm.querySelector('.text__hashtags');
5822

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

72-
const isHashtagsValid = (value) => {
36+
const getHashtags = (value) => {
7337
const trimmedValue = value.trim();
38+
return trimmedValue === '' ? [] : trimmedValue.split(/\s+/);
39+
};
7440

75-
if (trimmedValue === '') {
76-
return true;
77-
}
78-
79-
const hashtagsArray = trimmedValue.split(/\s+/);
80-
81-
if (hashtagsArray.length > MAX_HASHTAGS) {
82-
return false;
83-
}
41+
const isHashtagsValid = (value) =>
42+
getHashtags(value).every((hashtag) => hashtagPattern.test(hashtag));
8443

85-
const uniqueHashtags = new Set(hashtagsArray.map((h) => h.toLowerCase()));
44+
const isHashtagsCountValid = (value) =>
45+
getHashtags(value).length <= MAX_HASHTAGS;
8646

87-
if (uniqueHashtags.size !== hashtagsArray.length) {
88-
return false;
89-
}
47+
const isHashtagsUnique = (value) => {
48+
const hashtags = getHashtags(value);
49+
const uniqueHashtags = new Set(hashtags.map((h) => h.toLowerCase()));
9050

91-
return hashtagsArray.every((hashtag) => hashtagPattern.test(hashtag));
51+
return uniqueHashtags.size === hashtags.length;
9252
};
9353

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

97-
pristine.addValidator(hashtagsInput, isHashtagsValid, 'введён невалидный хэштег');
98-
pristine.addValidator(descriptionInput, isDescriptionValid, 'длина комментария больше 140 символов.');
57+
pristine.addValidator(hashtagsInput, isHashtagsValid, errorMessage.INVALID_HASHTAG);
58+
pristine.addValidator(descriptionInput, isDescriptionValid, errorMessage.DESCRIPTION_TOO_LONG);
59+
pristine.addValidator(hashtagsInput, isHashtagsCountValid, errorMessage.HASHTAGS_TOO_MUCH);
60+
pristine.addValidator(hashtagsInput, isHashtagsUnique, errorMessage.HASHTAGS_UNIQUE);
9961

10062
imgUploadForm.addEventListener('submit', async (evt) => {
10163
evt.preventDefault();
@@ -105,17 +67,13 @@ imgUploadForm.addEventListener('submit', async (evt) => {
10567
try {
10668
toggleSubmitButton(true);
10769

108-
await sendData(new FormData(evt.target));
70+
await sendPhoto(new FormData(evt.target));
71+
showSuccess();
10972
onCloseImageEditor();
11073
} catch (err) {
111-
showErrorMessage(err.message);
74+
showError(err.message);
11275
} finally {
11376
toggleSubmitButton(false);
11477
}
115-
} else {
116-
hashtagsInput.style.borderColor = 'red';
117-
118-
hashtagsInput.focus();
11978
}
12079
});
121-

js/fullscreen-picture.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ const bigPictureCommentsLoader = bigPicture.querySelector('.comments-loader');
1616
let visibleСomments = COMMENTS_SHOWN;
1717
let currentPhotoData = null;
1818

19-
function onDocumentKeydown (evt){
19+
const onDocumentKeydown = (evt) => {
2020
if (isEscapeKey(evt.key)) {
2121
evt.preventDefault();
2222
closePhotoModal();
2323
}
24-
}
24+
};
2525

2626
export function closePhotoModal () {
2727
bigPicture.classList.add('hidden');

js/functions.js

Lines changed: 0 additions & 29 deletions
This file was deleted.

js/gallery.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export const setGalleryData = (data) => {
2121
galleryData = data;
2222
};
2323

24+
export const getGalleryData = () => galleryData;
25+
2426
const getPhotoById = (id) => galleryData.find((photo) => photo.id === id);
2527

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

4749
openBigPicture(photoData);
4850
});
51+
52+
export const clearGallery = () => {
53+
const galleryItems = document.querySelectorAll('.picture');
54+
55+
galleryItems.forEach((item) => item.remove());
56+
};

0 commit comments

Comments
 (0)