Skip to content

Commit d191ed5

Browse files
authored
Merge pull request #10 from MadinaSatem/module9-task2
2 parents 5b23691 + dd0a831 commit d191ed5

File tree

6 files changed

+225
-175
lines changed

6 files changed

+225
-175
lines changed

index.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<meta charset="utf-8">
66
<meta name="viewport" content="width=device-width,initial-scale=1">
77
<link rel="stylesheet" href="css/normalize.css">
8+
<link rel="stylesheet" href="vendor/nouislider/nouislider.css">
89
<link rel="stylesheet" href="css/style.css">
910
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
1011
<title>Кекстаграм</title>
@@ -241,8 +242,10 @@ <h2 class="data-error__title">Не удалось загрузить данны
241242
<h2 class="data-error__title">Не удалось загрузить данные</h2>
242243
</section>
243244
</template>
244-
<script src="js/main.js" type="module" defer></script>
245+
<script src="vendor/nouislider/nouislider.js"></script>
245246
<script src="vendor/pristine/pristine.min.js"></script>
247+
<script src="js/main.js" type="module" defer></script>
248+
246249
</body>
247250

248251
</html>

js/effects.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
const SCALE_STEP = 25;
2+
const MIN_SCALE = 25;
3+
const MAX_SCALE = 100;
4+
const DEFAULT_SCALE = 100;
5+
const DEFAULT_EFFECT = 'none';
6+
7+
const scaleValueInput = document.querySelector('.scale__control--value');
8+
const scaleSmallerButton = document.querySelector('.scale__control--smaller');
9+
const scaleBiggerButton = document.querySelector('.scale__control--bigger');
10+
const imagePreview = document.querySelector('.img-upload__preview img');
11+
const effectSlider = document.querySelector('.effect-level__slider');
12+
const effectLevelInput = document.querySelector('input[name="effect-level"]');
13+
const effectsList = document.querySelector('.effects__list');
14+
15+
let currentScale = DEFAULT_SCALE;
16+
let currentEffect = DEFAULT_EFFECT;
17+
let effectSliderInitialized = false;
18+
19+
const effectSettings = {
20+
none: {
21+
range: { min: 0, max: 100 },
22+
start: 100,
23+
step: 1,
24+
filter: () => '',
25+
hidden: true
26+
},
27+
chrome: {
28+
range: { min: 0, max: 1 },
29+
start: 1,
30+
step: 0.1,
31+
filter: (value) => `grayscale(${value})`,
32+
hidden: false
33+
},
34+
sepia: {
35+
range: { min: 0, max: 1 },
36+
start: 1,
37+
step: 0.1,
38+
filter: (value) => `sepia(${value})`,
39+
hidden: false
40+
},
41+
marvin: {
42+
range: { min: 0, max: 100 },
43+
start: 100,
44+
step: 1,
45+
filter: (value) => `invert(${value}%)`,
46+
hidden: false
47+
},
48+
phobos: {
49+
range: { min: 0, max: 3 },
50+
start: 3,
51+
step: 0.1,
52+
filter: (value) => `blur(${value}px)`,
53+
hidden: false
54+
},
55+
heat: {
56+
range: { min: 1, max: 3 },
57+
start: 3,
58+
step: 0.1,
59+
filter: (value) => `brightness(${value})`,
60+
hidden: false
61+
}
62+
};
63+
64+
const setScale = (value) => {
65+
imagePreview.style.transform = `scale(${value / 100})`;
66+
scaleValueInput.value = `${value}%`;
67+
};
68+
69+
const changeScale = (direction) => {
70+
if (direction === 'smaller' && currentScale > MIN_SCALE) {
71+
currentScale -= SCALE_STEP;
72+
}
73+
if (direction === 'bigger' && currentScale < MAX_SCALE) {
74+
currentScale += SCALE_STEP;
75+
}
76+
setScale(currentScale);
77+
};
78+
79+
const initEffectSlider = () => {
80+
if (effectSliderInitialized || !effectSlider) {
81+
return;
82+
}
83+
84+
noUiSlider.create(effectSlider, {
85+
range: effectSettings.none.range,
86+
start: effectSettings.none.start,
87+
step: effectSettings.none.step,
88+
connect: 'lower'
89+
});
90+
91+
effectSlider.noUiSlider.on('update', (values) => {
92+
const value = values[0];
93+
const settings = effectSettings[currentEffect];
94+
imagePreview.style.filter = settings.filter(value);
95+
effectLevelInput.value = value;
96+
});
97+
98+
effectSliderInitialized = true;
99+
updateEffect(DEFAULT_EFFECT);
100+
};
101+
102+
const updateEffect = (effectName) => {
103+
currentEffect = effectName;
104+
const settings = effectSettings[effectName];
105+
106+
if (!effectSlider.noUiSlider) {
107+
return;
108+
}
109+
110+
effectSlider.noUiSlider.updateOptions({
111+
range: settings.range,
112+
start: settings.start,
113+
step: settings.step
114+
});
115+
116+
effectSlider.parentElement.classList.toggle('hidden', settings.hidden);
117+
118+
if (effectName === 'none') {
119+
imagePreview.style.filter = '';
120+
} else {
121+
imagePreview.style.filter = settings.filter(settings.start);
122+
}
123+
124+
effectLevelInput.value = settings.start;
125+
};
126+
127+
effectsList.addEventListener('change', (evt) => {
128+
if (evt.target.name === 'effect') {
129+
updateEffect(evt.target.value);
130+
}
131+
});
132+
133+
scaleSmallerButton.addEventListener('click', () => changeScale('smaller'));
134+
scaleBiggerButton.addEventListener('click', () => changeScale('bigger'));
135+
136+
const resetEffects = () => {
137+
currentScale = DEFAULT_SCALE;
138+
setScale(DEFAULT_SCALE);
139+
updateEffect(DEFAULT_EFFECT);
140+
};
141+
142+
export { initEffectSlider, resetEffects };

js/form.js

Lines changed: 20 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,137 +1,41 @@
11
import { isEscapeKey } from './util.js';
2-
import { showSuccessMessage, showErrorMessage } from './form-message.js';
2+
import { initEffectSlider, resetEffects } from './effects.js';
33

44
const fileInput = document.querySelector('#upload-file');
55
const formOverlay = document.querySelector('.img-upload__overlay');
66
const cancelButton = document.querySelector('.img-upload__cancel');
77
const form = document.querySelector('.img-upload__form');
8-
const body = document.body;
98

10-
const hashtagInput = form.querySelector('.text__hashtags');
11-
const commentInput = form.querySelector('.text__description');
12-
13-
const pristine = new Pristine(form, {
14-
classTo: 'img-upload__field-wrapper',
15-
errorClass: 'img-upload__field-wrapper--error',
16-
successClass: 'img-upload__field-wrapper--success',
17-
errorTextParent: 'img-upload__field-wrapper',
18-
errorTextTag: 'div',
19-
errorTextClass: 'pristine-error',
20-
});
21-
22-
// Валидация хэштегов
23-
const validateHashtags = (value) => {
24-
if (!value.trim()) {
25-
return true;
26-
}
27-
28-
const hashtags = value.trim().toLowerCase().split(/\s+/);
29-
if (hashtags.length > 5) {
30-
return false;
31-
}
32-
33-
const uniqueHashtags = new Set(hashtags);
34-
if (uniqueHashtags.size !== hashtags.length) {
35-
return false;
36-
}
37-
38-
return hashtags.every((tag) => /^#[a-zа-яё0-9]{1,19}$/i.test(tag));
39-
};
40-
41-
const getHashtagErrorMessage = (value) => {
42-
const hashtags = value.trim().toLowerCase().split(/\s+/);
43-
44-
if (hashtags.length > 5) {
45-
return 'Нельзя указать больше 5 хэштегов';
46-
}
47-
48-
const uniqueHashtags = new Set(hashtags);
49-
if (uniqueHashtags.size !== hashtags.length) {
50-
return 'Хэштеги не должны повторяться';
51-
}
52-
53-
if (!hashtags.every((tag) => /^#[a-zа-яё0-9]{1,19}$/i.test(tag))) {
54-
return 'Хэштег начинается с # и может содержать только буквы и цифры, не более 20 символов';
55-
}
56-
57-
return '';
58-
};
59-
60-
// Валидация комментария
61-
const validateComment = (value) => value.length <= 140;
62-
63-
pristine.addValidator(hashtagInput, validateHashtags, getHashtagErrorMessage);
64-
pristine.addValidator(commentInput, validateComment, 'Комментарий не может быть длиннее 140 символов');
65-
66-
// Обработчик Escape (с учётом фокуса)
67-
const onDocumentKeydown = (evt) => {
68-
if (isEscapeKey(evt)) {
69-
const isInHashtag = document.activeElement === hashtagInput;
70-
const isInComment = document.activeElement === commentInput;
71-
72-
if (isInHashtag || isInComment) {
73-
evt.stopPropagation(); // предотвращаем закрытие формы
74-
} else {
75-
evt.preventDefault();
76-
closeForm();
77-
}
78-
}
79-
};
80-
81-
// Показ формы
82-
const openForm = () => {
9+
const showUploadForm = () => {
8310
formOverlay.classList.remove('hidden');
84-
body.classList.add('modal-open');
11+
document.body.classList.add('modal-open');
8512
document.addEventListener('keydown', onDocumentKeydown);
13+
initEffectSlider();
8614
};
8715

88-
// Закрытие формы
89-
const closeForm = () => {
16+
const hideUploadForm = () => {
9017
formOverlay.classList.add('hidden');
91-
body.classList.remove('modal-open');
92-
form.reset(); // сброс полей
93-
pristine.reset(); // сброс ошибок
94-
fileInput.value = ''; // сброс выбранного файла
18+
document.body.classList.remove('modal-open');
9519
document.removeEventListener('keydown', onDocumentKeydown);
20+
form.reset();
21+
resetEffects();
9622
};
9723

98-
// Отправка формы
99-
form.addEventListener('submit', (evt) => {
100-
evt.preventDefault();
101-
102-
if (!pristine.validate()) {
103-
return;
24+
function onDocumentKeydown(evt) {
25+
if (isEscapeKey(evt)) {
26+
evt.preventDefault();
27+
hideUploadForm();
10428
}
29+
}
10530

106-
const submitButton = form.querySelector('#upload-submit');
107-
submitButton.disabled = true;
108-
109-
const formData = new FormData(form);
110-
111-
fetch('https://27.javascript.pages.academy/kekstagram', {
112-
method: 'POST',
113-
body: formData,
114-
})
115-
.then((response) => {
116-
if (!response.ok) {
117-
throw new Error('Не удалось отправить форму');
118-
}
119-
120-
closeForm();
121-
showSuccessMessage();
122-
})
123-
.catch(() => {
124-
showErrorMessage();
125-
})
126-
.finally(() => {
127-
submitButton.disabled = false;
128-
});
129-
});
130-
131-
// Слушатели
13231
const initFormListeners = () => {
133-
fileInput.addEventListener('change', openForm);
134-
cancelButton.addEventListener('click', closeForm);
32+
fileInput.addEventListener('change', () => {
33+
showUploadForm();
34+
});
35+
36+
cancelButton.addEventListener('click', () => {
37+
hideUploadForm();
38+
});
13539
};
13640

13741
export { initFormListeners };

js/main.js

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,23 @@ import { getPhotoDescriptions } from './data.js';
55
import { renderThumbnails, setThumbnailClickHandler } from './render.js';
66
import { openUserModal, initModalListeners } from './modal.js';
77
import { initFormListeners } from './form.js';
8+
import './effects.js';
89

9-
const photoDescriptions = getPhotoDescriptions();
10-
renderThumbnails(photoDescriptions);
10+
const init = () => {
11+
const photoDescriptions = getPhotoDescriptions();
1112

12-
// При клике по миниатюре — открываем модалку
13-
setThumbnailClickHandler((photoId) => {
14-
const photo = photoDescriptions.find((item) => item.id === photoId);
15-
if (photo) {
16-
openUserModal(photo);
17-
}
18-
});
13+
renderThumbnails(photoDescriptions);
1914

20-
// Инициализируем слушатели закрытия модального окна
21-
initModalListeners();
15+
setThumbnailClickHandler((photoId) => {
16+
const photo = photoDescriptions.find((item) => item.id === photoId);
17+
if (photo) {
18+
openUserModal(photo);
19+
}
20+
});
21+
22+
initModalListeners();
23+
initFormListeners();
24+
};
25+
26+
init();
2227

23-
// Инициализируем обработчики формы
24-
initFormListeners();

0 commit comments

Comments
 (0)