26 |
27 | Получится вот так:
28 |
29 |
30 |
31 | #### 3. Клонируйте репозиторий на свой компьютер
32 |
33 | Будьте внимательны: нужно клонировать свой репозиторий (форк), а не репозиторий Академии. Также обратите внимание, что клонировать репозиторий нужно через SSH, а не через HTTPS. Нажмите зелёную кнопку в правой части экрана, чтобы скопировать SSH-адрес вашего репозитория:
34 |
35 |
36 |
37 | Клонировать репозиторий можно так:
38 |
39 | ```
40 | git clone SSH-адрес_вашего_форка
41 | ```
42 |
43 | Команда клонирует репозиторий на ваш компьютер и подготовит всё необходимое для старта работы.
44 |
45 | #### 4. Начинайте обучение!
46 |
--------------------------------------------------------------------------------
/js/util.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | export function createSystemMessage (downloadResult) {
4 | const body = document.querySelector('body');
5 | const successTemplate = document.querySelector(`#${downloadResult}`).content.cloneNode(true);
6 | body.appendChild(successTemplate);
7 |
8 | const message = document.querySelector(`.${downloadResult}`);
9 | const closeButton = message.querySelector('button');
10 |
11 |
12 | const onPopupEscKeydown = (evt) => {
13 | if (evt.key === 'Escape') {
14 | evt.preventDefault();
15 | message.remove();
16 | removeListeners();
17 | }
18 | };
19 |
20 | document.addEventListener('keydown', onPopupEscKeydown);
21 |
22 | const onOutsideOrButton = (evt) => {
23 | const clickButton = evt.composedPath().includes(closeButton);
24 | const clickInside = evt.composedPath().includes(message.querySelector('div'));
25 | if (clickButton || !clickInside) {
26 | message.remove();
27 | removeListeners();
28 | }
29 | };
30 |
31 | document.addEventListener('click', onOutsideOrButton);
32 |
33 | function removeListeners () {
34 | document.removeEventListener('click', onOutsideOrButton);
35 | document.removeEventListener('keydown', onPopupEscKeydown);
36 | }
37 | }
38 |
39 | export const showAlert = (message) => {
40 | const alertContainer = document.createElement('div');
41 | alertContainer.style.zIndex = '100';
42 | alertContainer.style.position = 'absolute';
43 | alertContainer.style.left = '0';
44 | alertContainer.style.top = '0';
45 | alertContainer.style.right = '0';
46 | alertContainer.style.padding = '10px 3px';
47 | alertContainer.style.fontSize = '20px';
48 | alertContainer.style.textAlign = 'center';
49 | alertContainer.style.backgroundColor = 'red';
50 |
51 | alertContainer.textContent = message;
52 | document.body.append(alertContainer);
53 |
54 | setTimeout(() => {
55 | alertContainer.remove();
56 | }, 5000);
57 | };
58 |
--------------------------------------------------------------------------------
/Workflow.md:
--------------------------------------------------------------------------------
1 | # Как работать над проектом
2 |
3 | ## Окружение
4 |
5 | Для удобства работы над проектом используются инструменты из **Node.js** и **npm**. Все необходимые настройки произведены. Всё, что от вас требуется,— это убедиться, что на рабочем компьютере установлена **Node.js**, а после в терминале перейти в директорию с проектом и _единожды_ запустить команду:
6 |
7 | ```bash
8 | npm install
9 | ```
10 |
11 | Данная команда запустит процесс установки зависимостей проекта из **npm**.
12 |
13 | После успешной установки зависимостей вы сможете использовать инструменты для разработки, вроде **ESLint** и **Browsersync**, которые идут с проектом. Для этого в файле `package.json` предусмотрены следующие сценарии...
14 |
15 | ### `npm run start`
16 |
17 | Запускает локальный сервер с помощью **Browsersync**. После запуска сайт будет доступен для просмотра в браузере по адресу `http://localhost:3001`.
18 |
19 | При сохранении изменений в любом js-файле в директории `/js` страница автоматически перезагрузится в браузере. Таким образом, вы можете следить за разработкой проекта в режиме реального времени.
20 |
21 | > Обратите внимание, после запуска **Browsersync** продолжит работу, пока вы самостоятельно не остановите его, нажав в терминале сочетание клавиш `Ctrl` + `C`.
22 |
23 | ### `npm run lint`
24 |
25 | Запускает **ESLint** для линтинга js-файлов в директории `/js` по правилам и требованиям к JavaScript-коду, принятым в Академии.
26 |
27 | ## Структура проекта
28 |
29 | ### `css/`, `fonts/`, `img/`
30 |
31 | Директории со статическими файлами проекта: стилями, изображениями, шрифтами и т.д.
32 |
33 | ### `photos/`
34 |
35 | Директория с фотографиями, которые нужно будет отобразить на странице в одном из заданий.
36 |
37 | ### `js/`
38 |
39 | Ваша главная рабочая директория, в которой будут храниться все скрипты проекта.
40 |
41 | ### `index.html`
42 |
43 | Главная страница проекта.
44 |
45 | ### Остальное
46 |
47 | Все остальные файлы в проекте являются служебными. Пожалуйста, не удаляйте и не изменяйте их самовольно. Только если того требует задание или наставник.
48 |
--------------------------------------------------------------------------------
/js/effect.js:
--------------------------------------------------------------------------------
1 | import {img} from './scale.js';
2 | import {settings} from './data.js';
3 |
4 | const sliderElement = document.querySelector('.effect-level__slider');
5 | const valueElement = document.querySelector('.effect-level__value');
6 | const effects = document.querySelector('.effects__list');
7 | const defaultEffect = effects.querySelector('#effect-none');
8 |
9 | let currentSettings = settings[0];
10 | let effect = 'none';
11 |
12 | noUiSlider.create(sliderElement, {
13 | range: {
14 | min: 0,
15 | max: 1,
16 | },
17 | start: 1,
18 | connect: 'lower',
19 | });
20 |
21 | function updateEffect (currentEffect) {
22 | sliderElement.noUiSlider.updateOptions({
23 | range: {
24 | min: currentEffect.min,
25 | max: currentEffect.max,
26 | },
27 | step: currentEffect.step,
28 | start: currentEffect.start,
29 | });
30 | }
31 |
32 | sliderElement.noUiSlider.on('update', () => {
33 | if (effect === 'none') {
34 | valueElement.value = '';
35 | img.style.filter = effect;
36 | sliderElement.classList.add('hidden');
37 | } else {
38 | valueElement.value = sliderElement.noUiSlider.get();
39 | let style;
40 | if (currentSettings.symbol) {
41 | style = `${currentSettings.filter}(${valueElement.value}${currentSettings.symbol})`;
42 | } else {
43 | style = `${currentSettings.filter}(${valueElement.value})`;
44 | }
45 | img.style.filter = style;
46 | }
47 | });
48 |
49 | effects.addEventListener('click', (evt) => {
50 | if (evt.target.matches('input[type="radio"]')) {
51 | img.classList.remove(`effects__preview--${effect}`);
52 | effect = evt.target.value;
53 | img.classList.add(`effects__preview--${effect}`);
54 |
55 | if (effect === 'none') {
56 | updateEffect(currentSettings);
57 | } else {
58 | settings.forEach((element) => {
59 | if (element.name === effect) {
60 | currentSettings = element;
61 | updateEffect(currentSettings);
62 | sliderElement.classList.remove('hidden');
63 | }
64 | });
65 | }
66 | }
67 | });
68 |
69 | export function resetEffects () {
70 | img.classList.remove(`effects__preview--${effect}`);
71 | defaultEffect.checked = true;
72 | valueElement.value = '';
73 | img.style.filter = 'none';
74 | sliderElement.classList.add('hidden');
75 | }
76 |
--------------------------------------------------------------------------------
/img/avatar-4.svg:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/img/avatar-6.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/js/api.js:
--------------------------------------------------------------------------------
1 | const INITIAL_EFFECT = 'none';
2 |
3 | const imgUploadForm = document.querySelector('.img-upload__form');
4 | const picture = imgUploadForm.querySelector('.img-upload__preview img');
5 | const effectsList = imgUploadForm.querySelector('.effects__list');
6 | const buttons = effectsList.querySelectorAll('.effects__radio');
7 | const effectLevelSlider = imgUploadForm.querySelector('.effect-level__slider');
8 | const effectLevelValue = imgUploadForm.querySelector('.effect-level__value');
9 |
10 | const PICTURES_EFFECTS = {
11 | chrome: {
12 | filter: 'grayscale',
13 | range: {min: 0, max: 1.0},
14 | step: 0.1,
15 | measurementUnit: ''},
16 | sepia: {
17 | filter: 'sepia',
18 | range: {min: 0, max: 1.0},
19 | step: 0.1,
20 | measurementUnit: ''},
21 | marvin: {
22 | filter: 'invert',
23 | range: {min: 0, max: 100},
24 | step: 1,
25 | measurementUnit: '%'},
26 | phobos: {
27 | filter: 'blur',
28 | range: {min: 0, max: 3.0},
29 | step: 0.1,
30 | measurementUnit: 'px'},
31 | heat: {
32 | filter: 'brightness',
33 | range: {min: 1, max: 3.0},
34 | step: 0.1,
35 | measurementUnit: ''}
36 | };
37 |
38 | let currentEffect = INITIAL_EFFECT;
39 |
40 | const createEffectSlider = () => {
41 | noUiSlider.create(effectLevelSlider, {
42 | range: {
43 | min: 0,
44 | max: 1.0,
45 | },
46 | start: 1.0,
47 | step: 0.1,
48 | connect: 'lower',
49 | });
50 | };
51 |
52 | const sliderConnector = () => {
53 | if (currentEffect !== 'none') {
54 | const effect = PICTURES_EFFECTS[currentEffect];
55 | picture.style.filter = `${effect.filter}(${effectLevelSlider.noUiSlider.get()}${effect.measurementUnit})`;
56 | effectLevelValue.value = `${effectLevelSlider.noUiSlider.get()}${effect.measurementUnit}`;
57 | } else {
58 | picture.style.filter = '';
59 | }
60 | };
61 |
62 | const changeEffect = (effect) => {
63 | if ((currentEffect === 'none') !== (effectLevelSlider.classList.contains('hidden'))){
64 | effectLevelSlider.classList.toggle('hidden');
65 | }
66 | if (currentEffect !== 'none') {
67 | const effectObj = PICTURES_EFFECTS[effect];
68 | effectLevelSlider.noUiSlider.updateOptions({
69 | range: {
70 | min: effectObj.range.min,
71 | max: effectObj.range.max,
72 | },
73 | start: effectObj.range.max,
74 | step: effectObj.step
75 | });
76 | }
77 | else {
78 | picture.style.filter = '';
79 | }
80 | };
81 |
82 | const effectRadiosListener = () => {
83 | picture.classList.remove(`effects__preview--${currentEffect}`);
84 | buttons.forEach((button) => {
85 | if (button.checked) {
86 | currentEffect = button.value;
87 | changeEffect(currentEffect);
88 | }
89 | });
90 | picture.classList.add(`effects__preview--${currentEffect}`);
91 | };
92 |
93 | export const enableEffectPreview = () => {
94 | effectsList.addEventListener('click', effectRadiosListener);
95 | createEffectSlider();
96 | changeEffect(currentEffect);
97 | effectLevelSlider.noUiSlider.set(parseFloat(effectLevelValue.value));
98 | effectLevelSlider.noUiSlider.on('update', sliderConnector);
99 | if (currentEffect === 'none') {
100 | effectLevelSlider.classList.add('hidden');
101 | }
102 | };
103 |
104 | export const disableEffectPreview = () => {
105 | picture.classList.remove(`effects__preview--${currentEffect}`);
106 | effectsList.removeEventListener('click', effectRadiosListener);
107 | effectLevelSlider.noUiSlider.destroy();
108 | };
109 |
110 | export const resetEffect = () => {
111 | currentEffect = INITIAL_EFFECT;
112 | imgUploadForm.reset();
113 | };
--------------------------------------------------------------------------------
/js/addEffect.js:
--------------------------------------------------------------------------------
1 | const INITIAL_EFFECT = 'none';
2 |
3 | const imgUploadForm = document.querySelector('.img-upload__form');
4 | const picture = imgUploadForm.querySelector('.img-upload__preview img');
5 | const effectsList = imgUploadForm.querySelector('.effects__list');
6 | const buttons = effectsList.querySelectorAll('.effects__radio');
7 | const effectLevelSlider = imgUploadForm.querySelector('.effect-level__slider');
8 | const effectLevelValue = imgUploadForm.querySelector('.effect-level__value');
9 |
10 | const PICTURES_EFFECTS = {
11 | chrome: {
12 | filter: 'grayscale',
13 | range: {min: 0, max: 1.0},
14 | step: 0.1,
15 | measurementUnit: ''},
16 | sepia: {
17 | filter: 'sepia',
18 | range: {min: 0, max: 1.0},
19 | step: 0.1,
20 | measurementUnit: ''},
21 | marvin: {
22 | filter: 'invert',
23 | range: {min: 0, max: 100},
24 | step: 1,
25 | measurementUnit: '%'},
26 | phobos: {
27 | filter: 'blur',
28 | range: {min: 0, max: 3.0},
29 | step: 0.1,
30 | measurementUnit: 'px'},
31 | heat: {
32 | filter: 'brightness',
33 | range: {min: 1, max: 3.0},
34 | step: 0.1,
35 | measurementUnit: ''}
36 | };
37 |
38 | let currentEffect = INITIAL_EFFECT;
39 |
40 | const createEffectSlider = () => {
41 | noUiSlider.create(effectLevelSlider, {
42 | range: {
43 | min: 0,
44 | max: 1.0,
45 | },
46 | start: 1.0,
47 | step: 0.1,
48 | connect: 'lower',
49 | });
50 | };
51 |
52 | const sliderConnector = () => {
53 | if (currentEffect !== 'none') {
54 | const effect = PICTURES_EFFECTS[currentEffect];
55 | picture.style.filter = `${effect.filter}(${effectLevelSlider.noUiSlider.get()}${effect.measurementUnit})`;
56 | effectLevelValue.value = `${effectLevelSlider.noUiSlider.get()}${effect.measurementUnit}`;
57 | } else {
58 | picture.style.filter = '';
59 | }
60 | };
61 |
62 | const changeEffect = (effect) => {
63 | if ((currentEffect === 'none') !== (effectLevelSlider.classList.contains('hidden'))){
64 | effectLevelSlider.classList.toggle('hidden');
65 | }
66 | if (currentEffect !== 'none') {
67 | const effectObj = PICTURES_EFFECTS[effect];
68 | effectLevelSlider.noUiSlider.updateOptions({
69 | range: {
70 | min: effectObj.range.min,
71 | max: effectObj.range.max,
72 | },
73 | start: effectObj.range.max,
74 | step: effectObj.step
75 | });
76 | }
77 | else {
78 | picture.style.filter = '';
79 | }
80 | };
81 |
82 | const effectRadiosListener = () => {
83 | picture.classList.remove(`effects__preview--${currentEffect}`);
84 | buttons.forEach((button) => {
85 | if (button.checked) {
86 | currentEffect = button.value;
87 | changeEffect(currentEffect);
88 | }
89 | });
90 | picture.classList.add(`effects__preview--${currentEffect}`);
91 | };
92 |
93 | export const enableEffectPreview = () => {
94 | effectsList.addEventListener('click', effectRadiosListener);
95 | createEffectSlider();
96 | changeEffect(currentEffect);
97 | effectLevelSlider.noUiSlider.set(parseFloat(effectLevelValue.value));
98 | effectLevelSlider.noUiSlider.on('update', sliderConnector);
99 | if (currentEffect === 'none') {
100 | effectLevelSlider.classList.add('hidden');
101 | }
102 | };
103 |
104 | export const disableEffectPreview = () => {
105 | picture.classList.remove(`effects__preview--${currentEffect}`);
106 | effectsList.removeEventListener('click', effectRadiosListener);
107 | effectLevelSlider.noUiSlider.destroy();
108 | };
109 |
110 | export const resetEffect = () => {
111 | currentEffect = INITIAL_EFFECT;
112 | imgUploadForm.reset();
113 | };
--------------------------------------------------------------------------------
/img/spinner-blue.svg:
--------------------------------------------------------------------------------
1 |
73 |
--------------------------------------------------------------------------------
/js/bigPicture.js:
--------------------------------------------------------------------------------
1 | import {closeModal, isEscapeKey, openModal} from './util.js';
2 |
3 | const COMMENTS_NUMBER_LOAD = 5;
4 |
5 | const bigPicture = document.querySelector('.big-picture');
6 | const fullSizePictureImage = bigPicture.querySelector('.big-picture__img').querySelector('img');
7 | const bigPictureCloseButton = bigPicture.querySelector('.big-picture__cancel');
8 | const commentsCount = bigPicture.querySelector('.comments-count');
9 | const likesCount = bigPicture.querySelector('span.likes-count');
10 | const socialComments = bigPicture.querySelector('.social__comments');
11 | const photoDescription = bigPicture.querySelector('.social__caption');
12 | const socialCommentsCounter = bigPicture.querySelector('.social__comment-count');
13 | const commentsLoader = bigPicture.querySelector('.comments-loader');
14 | const bodyContainer = document.querySelector('body');
15 |
16 | let shownCommentsCounter = 0;
17 | let actualComments = [];
18 |
19 |
20 | const closeBigPicture = () => {
21 | shownCommentsCounter = 0;
22 | closeModal(bigPicture, bodyContainer);
23 | };
24 |
25 | const getClosedByEscape = (evt) => {
26 | if (isEscapeKey(evt)) {
27 | evt.preventDefault();
28 | shownCommentsCounter = 0;
29 | closeBigPicture();
30 | }
31 | };
32 |
33 | const removeCommentsLoader = () => {
34 | commentsLoader.classList.add('hidden');
35 | };
36 |
37 | const removeDefaultSocialComments = () => {
38 | while (socialComments.firstChild) {
39 | socialComments.removeChild(socialComments.lastChild);
40 | }
41 | };
42 |
43 | const makeElementTemplate = (tagName, className) => {
44 | const element = document.createElement(tagName);
45 | element.classList.add(className);
46 | return element;
47 | };
48 |
49 | const createSocialCommentsTemplate = (data) => {
50 | const AVATAR_WIDTH = '35';
51 | const AVATAR_HEIGHT = '35';
52 | const socialComment = makeElementTemplate('li', 'social__comment');
53 | const avatarImage = makeElementTemplate('img', 'social__picture');
54 | avatarImage.src = data.avatar;
55 | avatarImage.alt = data.name;
56 | avatarImage.width = AVATAR_WIDTH;
57 | avatarImage.height = AVATAR_HEIGHT;
58 | socialComment.append(avatarImage);
59 | const paragraphElement = makeElementTemplate('p', 'social__text');
60 | paragraphElement.textContent = data.message;
61 | socialComment.appendChild(paragraphElement);
62 | return socialComment;
63 | };
64 |
65 | const getCommentsNumber = (count, totalComments) => {
66 | let commentsDeclination;
67 | if (totalComments % 10 === 1 && totalComments % 100 !== 11) {
68 | commentsDeclination = 'комментария';
69 | } else {
70 | commentsDeclination = 'комментариев';
71 | }
72 | socialCommentsCounter.innerHTML = `${count} из ${totalComments} ${commentsDeclination}`;
73 | };
74 |
75 | const getComment = () => {
76 | const commentFragment = document.createDocumentFragment();
77 | const currentComments = actualComments.splice(0, COMMENTS_NUMBER_LOAD);
78 | shownCommentsCounter += currentComments.length;
79 |
80 | currentComments.forEach((comment) => commentFragment.append(createSocialCommentsTemplate(comment)));
81 | if (actualComments.length < 1) {
82 | removeCommentsLoader();
83 | }
84 | getCommentsNumber(shownCommentsCounter, commentsCount.textContent);
85 | socialComments.append(commentFragment);
86 | };
87 |
88 | export const getBigPicture = (picture) => {
89 | openModal(bigPicture, bodyContainer);
90 | commentsLoader.classList.remove('hidden');
91 | commentsLoader.addEventListener('click', getComment);
92 | bigPictureCloseButton.addEventListener('click', closeBigPicture);
93 | document.addEventListener('keydown', getClosedByEscape);
94 | removeDefaultSocialComments();
95 | photoDescription.textContent = picture.description;
96 | fullSizePictureImage.src = picture.url;
97 | likesCount.textContent = picture.likes;
98 | commentsCount.textContent = String(picture.comments.length);
99 | actualComments = picture.comments.slice();
100 | getComment(actualComments);
101 | };
--------------------------------------------------------------------------------
/img/spinner.svg:
--------------------------------------------------------------------------------
1 |
81 |
--------------------------------------------------------------------------------
/js/loaderForm.js:
--------------------------------------------------------------------------------
1 | import {openModal, closeModal, isEscapeKey} from './util.js';
2 | import {pristine, validateForm} from './validetion.js';
3 | import {controlScaleButtonHandler, getScaleDecrease, getScaleIncrease,
4 | scaleControlBiggerElement, scaleControlSmallerElement, resetScaleSettings} from './imageScale.js';
5 | import {enableEffectPreview, disableEffectPreview, resetEffect} from './addEffect.js';
6 | import {sendData} from './api.js';
7 |
8 | const body = document.querySelector('body');
9 | const imgUploadForm = document.querySelector('.img-upload__form');
10 | const uploadFile = imgUploadForm.querySelector('.img-upload__input');
11 | const imgUploadOverlay = imgUploadForm.querySelector('.img-upload__overlay');
12 | const closeButton = imgUploadForm.querySelector('.img-upload__cancel');
13 | const textHashtagsInput = imgUploadForm.querySelector('.text__hashtags');
14 | const textDescriptionInput = imgUploadForm.querySelector('.text__description');
15 | const uploadFormSubmitButtonElement = imgUploadForm.querySelector('.img-upload__submit');
16 | const successTemplate = document.querySelector('#success').content.querySelector('section');
17 | const errorTemplate = document.querySelector('#error').content.querySelector('section');
18 |
19 | const propagationStopper = (evt) => evt.stopPropagation();
20 |
21 | const closeOverlay = () => {
22 | closeModal(imgUploadOverlay, body);
23 | closeButton.removeEventListener('click', closeButtonListener);
24 | document.removeEventListener('keydown', escListener);
25 | textHashtagsInput.removeEventListener('keydown', propagationStopper);
26 | textDescriptionInput.removeEventListener('keydown', propagationStopper);
27 | resetScaleSettings();
28 | disableEffectPreview();
29 | };
30 |
31 | const renderImageEditor = () => {
32 | openModal(imgUploadOverlay, body);
33 | textHashtagsInput.addEventListener('keydown', propagationStopper);
34 | textHashtagsInput.addEventListener('input', validateForm);
35 | textDescriptionInput.addEventListener('keydown', propagationStopper);
36 | closeButton.addEventListener('click', closeButtonListener);
37 | document.addEventListener('keydown', escListener);
38 | controlScaleButtonHandler(scaleControlSmallerElement, getScaleDecrease);
39 | controlScaleButtonHandler(scaleControlBiggerElement, getScaleIncrease);
40 | enableEffectPreview();
41 | };
42 |
43 | function closeButtonListener() {
44 | closeOverlay();
45 | resetEffect();
46 | }
47 |
48 | function escListener(evt) {
49 | if (isEscapeKey(evt)) {
50 | closeOverlay();
51 | resetEffect();
52 | }
53 | }
54 |
55 | const blockSubmitButton = () => {
56 | uploadFormSubmitButtonElement.disabled = true;
57 | };
58 |
59 | const unblockSubmitButton = () => {
60 | uploadFormSubmitButtonElement.disabled = false;
61 | };
62 |
63 | const openOrCloseMessage = (message) => {
64 | body.appendChild(message);
65 | document.addEventListener('keydown', closeByEsc);
66 | const closeMessage = () => {
67 | message.remove();
68 | document.removeEventListener('keydown', closeByEsc);
69 | };
70 | message.addEventListener('click', (evt) => {
71 | if (evt.target.tagName !== 'DIV' && evt.target.tagName !== 'H2'){
72 | closeMessage();
73 | }
74 | });
75 | function closeByEsc(evt) {
76 | if (isEscapeKey(evt)) {
77 | closeMessage();
78 | }
79 | }
80 | };
81 |
82 | const showErrorMessageModal = () => {
83 | const message = errorTemplate.cloneNode(true);
84 | openOrCloseMessage(message);
85 | };
86 |
87 | const showSuccessMessageModal = () => {
88 | const message = successTemplate.cloneNode(true);
89 | openOrCloseMessage(message);
90 | };
91 |
92 | imgUploadForm.addEventListener('submit', (evt) => {
93 | evt.preventDefault();
94 | if (pristine.validate()) {
95 | blockSubmitButton();
96 | sendData (
97 | () => {
98 | unblockSubmitButton();
99 | closeOverlay();
100 | showSuccessMessageModal();
101 | resetEffect();
102 | },
103 | () => {
104 | showErrorMessageModal();
105 | closeOverlay();
106 | unblockSubmitButton();
107 | uploadFile.value = '';
108 | },
109 | new FormData(imgUploadForm)
110 | );
111 | }
112 | });
113 |
114 | uploadFile.addEventListener('change', (evt) => {
115 | evt.preventDefault();
116 | renderImageEditor();
117 | });
--------------------------------------------------------------------------------
/img/htmla-logo.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/pristine/pristine.min.js:
--------------------------------------------------------------------------------
1 | !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):(e="undefined"!=typeof globalThis?globalThis:e||self).Pristine=r()}(this,(function(){"use strict";var e={en:{required:"This field is required",email:"This field requires a valid e-mail address",number:"This field requires a number",integer:"This field requires an integer value",url:"This field requires a valid website URL",tel:"This field requires a valid telephone number",maxlength:"This fields length must be < ${1}",minlength:"This fields length must be > ${1}",min:"Minimum value for this field is ${1}",max:"Maximum value for this field is ${1}",pattern:"Please match the requested format",equals:"The two fields do not match"}};function r(e){var r=arguments;return this.replace(/\${([^{}]*)}/g,(function(e,t){return r[t]}))}function t(e){return e.pristine.self.form.querySelectorAll('input[name="'+e.getAttribute("name")+'"]:checked').length}var n={classTo:"form-group",errorClass:"has-danger",successClass:"has-success",errorTextParent:"form-group",errorTextTag:"div",errorTextClass:"text-help"},i=["required","min","max","minlength","maxlength","pattern"],s=/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,a=/-message(?:-([a-z]{2}(?:_[A-Z]{2})?))?/,o="en",l={},u=function(e,r){r.name=e,void 0===r.priority&&(r.priority=1),l[e]=r};function f(t,s,u){var f=this;function c(e,r,t,n){var i=l[t];if(i&&(e.push(i),n)){var s="pattern"===t?[n]:n.split(",");s.unshift(null),r[t]=s}}function p(t){for(var n=[],i=!0,s=0;t.validators[s];s++){var a=t.validators[s],l=t.params[a.name]?t.params[a.name]:[];if(l[0]=t.input.value,!a.fn.apply(t.input,l)&&(i=!1,"function"==typeof a.msg?n.push(a.msg(t.input.value,l)):"string"==typeof a.msg?n.push(r.apply(a.msg,l)):a.msg===Object(a.msg)&&a.msg[o]?n.push(r.apply(a.msg[o],l)):t.messages[o]&&t.messages[o][a.name]?n.push(r.apply(t.messages[o][a.name],l)):e[o]&&e[o][a.name]&&n.push(r.apply(e[o][a.name],l)),!0===a.halt))break}return t.errors=n,i}function m(e){if(e.errorElements)return e.errorElements;var r=function(e,r){for(;(e=e.parentElement)&&!e.classList.contains(r););return e}(e.input,f.config.classTo),t=null,n=null;return(t=f.config.classTo===f.config.errorTextParent?r:r.querySelector("."+f.config.errorTextParent))&&((n=t.querySelector(".pristine-error"))||((n=document.createElement(f.config.errorTextTag)).className="pristine-error "+f.config.errorTextClass,t.appendChild(n),n.pristineDisplay=n.style.display)),e.errorElements=[r,n]}function d(e){var r=m(e),t=r[0],n=r[1];t&&(t.classList.remove(f.config.successClass),t.classList.add(f.config.errorClass)),n&&(n.innerHTML=e.errors.join("208 | 209 | 210 |
211 | 212 | 213 | 214 | 215 | 216 |