Skip to content

Commit a5de013

Browse files
authored
Merge pull request #13 from Musa-Ismailov/module11-task1
2 parents f690830 + 7d8e795 commit a5de013

File tree

9 files changed

+149
-153
lines changed

9 files changed

+149
-153
lines changed

js/data.js

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

js/effects.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ const SEPIA_MAX_RANGE = 1;
88
const PHOBOS_MAX_RANGE = 3;
99
const HEAT_MIN_RANGE = 1;
1010
const HEAT_MAX_RANGE = 3;
11-
const NONE = 'none';
1211
const EffectConfig = {
1312
chrome: {
1413
range: { min: DEFAULT_MIN_RANGE, max: CHROME_MAX_RANGE },
@@ -59,11 +58,10 @@ const EffectConfig = {
5958
};
6059

6160
const imgUploadSlider = document.querySelector('.effect-level__slider');
62-
const imgUploadValue = document.querySelector('.effect-level__value');
6361
const effectsList = document.querySelector('.effects__list');
6462
const imgPreview = document.querySelector('.img-upload__preview img');
6563
const effectLevelElement = document.querySelector('.effect-level');
66-
const noneRadio = document.querySelector('#effect-none');
64+
const form = document.querySelector('.img-upload__form');
6765

6866
let currentEffect = EffectConfig.none;
6967

@@ -77,11 +75,10 @@ noUiSlider.create(imgUploadSlider, {
7775
effectLevelElement.classList.add('hidden');
7876

7977
export const resetEffectAndSlider = () => {
80-
imgUploadValue.value = DEFAULT_MAX_VALUE;
81-
imgPreview.style.filter = NONE;
78+
imgPreview.removeAttribute('style');
8279
currentEffect = EffectConfig.none;
8380
effectLevelElement.classList.add('hidden');
84-
noneRadio.checked = true;
81+
form.reset();
8582
};
8683

8784
const applyEffect = (currentFilter, value) => {

js/form.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import {renderGallery, setGalleryData} from './gallery.js';
2+
import {showErrorMessage} from './utils.js';
3+
import { onCloseImageEditor } from './upload-picture.js';
4+
5+
const hashtagPattern = /^#[a-zа-яё0-9]{1,19}$/i;
6+
const MAX_HASHTAGS = 5;
7+
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: 'Не удалось отправить форму. Попробуйте ещё раз',
25+
};
26+
const SubmitButtonText = {
27+
IDLE: 'Сохранить',
28+
SENDING: 'Сохраняю...'
29+
};
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+
);
58+
59+
const toggleSubmitButton = (disabled) => {
60+
imgUploadSubmit.disabled = disabled;
61+
imgUploadSubmit.textContent = disabled
62+
? SubmitButtonText.SENDING
63+
: SubmitButtonText.IDLE;
64+
};
65+
66+
const pristine = new Pristine(imgUploadForm, {
67+
classTo: 'img-upload__field-wrapper',
68+
errorTextParent: 'img-upload__field-wrapper',
69+
errorTextClass: 'img-upload__field-wrapper--error'
70+
});
71+
72+
const isHashtagsValid = (value) => {
73+
const trimmedValue = value.trim();
74+
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+
}
84+
85+
const uniqueHashtags = new Set(hashtagsArray.map((h) => h.toLowerCase()));
86+
87+
if (uniqueHashtags.size !== hashtagsArray.length) {
88+
return false;
89+
}
90+
91+
return hashtagsArray.every((hashtag) => hashtagPattern.test(hashtag));
92+
};
93+
94+
const isDescriptionValid = (value) =>
95+
value.trim() === '' || value.length <= MAX_DESCRIPTION_LEN;
96+
97+
pristine.addValidator(hashtagsInput, isHashtagsValid, 'введён невалидный хэштег');
98+
pristine.addValidator(descriptionInput, isDescriptionValid, 'длина комментария больше 140 символов.');
99+
100+
imgUploadForm.addEventListener('submit', async (evt) => {
101+
evt.preventDefault();
102+
const isValid = pristine.validate();
103+
104+
if (isValid) {
105+
try {
106+
toggleSubmitButton(true);
107+
108+
await sendData(new FormData(evt.target));
109+
onCloseImageEditor();
110+
} catch (err) {
111+
showErrorMessage(err.message);
112+
} finally {
113+
toggleSubmitButton(false);
114+
}
115+
} else {
116+
hashtagsInput.style.borderColor = 'red';
117+
118+
hashtagsInput.focus();
119+
}
120+
});
121+

js/fullscreen-picture.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function onDocumentKeydown (evt){
2323
}
2424
}
2525

26-
function closePhotoModal () {
26+
export function closePhotoModal () {
2727
bigPicture.classList.add('hidden');
2828
document.body.classList.remove('modal-open');
2929
document.removeEventListener('keydown', onDocumentKeydown);
@@ -94,7 +94,6 @@ export const openBigPicture = (photoData) => {
9494
openPhotoModal();
9595
};
9696

97-
9897
bigPictureClosedElement.addEventListener('click', () => {
9998
closePhotoModal();
10099
});

js/gallery.js

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,8 @@
1-
import {NAMES, COMMENTS, DESCRIPTIONS, MIN_LIKES, MAX_LIKES, MIN_AVATAR_ID, MAX_AVATAR_ID, MIN_COMMENTS, MAX_COMMENTS, ID_INCREMENT} from './data.js';
2-
import {getRandomInteger, getRandomArrayElement} from './utils.js';
31
import {openBigPicture} from './fullscreen-picture.js';
42

53
const pictureList = document.querySelector('.pictures');
64
const pictureTemplate = document.querySelector('#picture').content.querySelector('.picture');
75

8-
const generateComments = (count) =>
9-
Array.from({ length: count }, (_, i) => ({
10-
id: i + ID_INCREMENT,
11-
avatar: `img/avatar-${getRandomInteger(MIN_AVATAR_ID, MAX_AVATAR_ID)}.svg`,
12-
message: getRandomArrayElement(COMMENTS),
13-
name: getRandomArrayElement(NAMES),
14-
}));
15-
16-
const createPhoto = (id) => ({
17-
id: id + ID_INCREMENT,
18-
url: `photos/${id + ID_INCREMENT}.jpg`,
19-
description: getRandomArrayElement(DESCRIPTIONS),
20-
likes: getRandomInteger(MIN_LIKES, MAX_LIKES),
21-
name: getRandomArrayElement(NAMES),
22-
avatar: `img/avatar-${getRandomInteger(MIN_AVATAR_ID, MAX_AVATAR_ID)}.svg`,
23-
comments: generateComments(getRandomInteger(MIN_COMMENTS, MAX_COMMENTS)),
24-
});
25-
26-
const createGallery = (maxPhotos) =>
27-
Array.from({ length: maxPhotos }, (_, index) => createPhoto(index));
28-
29-
const gallery = createGallery(20);
30-
316
const createPictureEl = (pictureData) => {
327
const galleryElement = pictureTemplate.cloneNode(true);
338

@@ -40,9 +15,15 @@ const createPictureEl = (pictureData) => {
4015
return galleryElement;
4116
};
4217

43-
const getPhotoById = (id) => gallery.find((photo) => photo.id === id);
18+
let galleryData = [];
4419

45-
const renderGallery = (data) => {
20+
export const setGalleryData = (data) => {
21+
galleryData = data;
22+
};
23+
24+
const getPhotoById = (id) => galleryData.find((photo) => photo.id === id);
25+
26+
export const renderGallery = (data) => {
4627
const fragment = document.createDocumentFragment();
4728

4829
data.forEach((picture) => {
@@ -53,8 +34,6 @@ const renderGallery = (data) => {
5334
pictureList.appendChild(fragment);
5435
};
5536

56-
renderGallery(gallery);
57-
5837
pictureList.addEventListener('click', (evt) => {
5938
const pictureElement = evt.target.closest('.picture');
6039

js/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import './gallery.js';
22
import './fullscreen-picture.js';
33
import './upload-picture.js';
4+
import './form.js';

js/upload-picture.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const resetUploadedImage = () => {
4747
imgUploadInput.value = '';
4848
};
4949

50-
const onCloseImageEditor = () => {
50+
export const onCloseImageEditor = () => {
5151
imgUploadOverlay.classList.add('hidden');
5252
document.body.classList.remove('modal-open');
5353
document.removeEventListener('keydown', onDocumentKeydown);
@@ -63,11 +63,7 @@ const isValidImageFile = (file) => {
6363
}
6464

6565
const fileName = file.name.toLowerCase();
66-
const validImage = FILE_TYPES.some((type) => fileName.endsWith(type));
67-
68-
if (!validImage) {
69-
return false;
70-
}
66+
return FILE_TYPES.some((type) => fileName.endsWith(type));
7167
};
7268

7369
const applyUploadedImage = () => {

js/utils.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
export const getRandomInteger = (a, b) => {
2-
const lower = Math.ceil(Math.min(a, b));
3-
const upper = Math.floor(Math.max(a, b));
4-
const result = Math.random() * (upper - lower + 1) + lower;
1+
export const isEscapeKey = (key) => key === 'Escape';
52

6-
return Math.floor(result);
7-
};
3+
export const showErrorMessage = () => {
4+
const errorTemplate = document.querySelector('#data-error');
5+
const errorElement = errorTemplate.content.cloneNode(true);
86

9-
export const getRandomArrayElement = (elements) =>
10-
elements[getRandomInteger(0, elements.length - 1)];
7+
document.body.appendChild(errorElement);
118

12-
export const isEscapeKey = (key) => key === 'Escape';
9+
const errorMessage = document.body.lastElementChild;
10+
11+
setTimeout(() => {
12+
if (errorMessage && errorMessage.parentNode) {
13+
errorMessage.remove();
14+
}
15+
}, 5000);
16+
};

vendor/pristine/pristine.min.js

Lines changed: 0 additions & 62 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)