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
6 changes: 6 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="./vendor/nouislider/nouislider.css" />
<link rel="stylesheet" href="css/normalize.css" />
<link rel="stylesheet" href="css/style.css" />
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
Expand Down Expand Up @@ -55,6 +56,9 @@ <h2 class="img-upload__title visually-hidden">
<form
class="img-upload__form"
id="upload-select-image"
method="post"
action="https://31.javascript.htmlacademy.pro/kekstagram"
enctype="multipart/form-data"
autocomplete="off"
>
<!-- Изначальное состояние поля для загрузки изображения -->
Expand Down Expand Up @@ -454,6 +458,8 @@ <h2 class="data-error__title">Не удалось загрузить данны
</section>
</template>

<script src="./vendor/pristine/pristine.min.js" type="module"></script>
<script src="./vendor/nouislider/nouislider.js" type="module"></script>
<script src="./js/main.js" type="module"></script>
</body>
</html>
65 changes: 40 additions & 25 deletions js/form.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import '../vendor/pristine/pristine.min.js';
import { Modal } from './modal.js';
import * as imageProcessing from './image-proccessing.js';

const uploadInputEl = document.querySelector('input.img-upload__input');
const uploadOverlayEl = document.querySelector('.img-upload__overlay');
Expand All @@ -11,26 +11,17 @@ const descriptionEl = document.querySelector('.text__description');

const submitEl = document.querySelector('.img-upload__submit');

formEl.method = 'post';
formEl.action = 'https://31.javascript.htmlacademy.pro/kekstagram';
formEl.enctype = 'multipart/form-data';
imageProcessing.initialize();

const pristine = new Pristine(formEl);

const ERROR_CLASSNAME = 'pristine-error img-upload__field-wrapper--error';

const addValidator = (inputEl, callback, message) => {
pristine.addValidator(
inputEl,
(value) => {
const res = callback(value) ?? true;
if (!res) {
inputEl.style.borderColor = 'red';
inputEl.style.outlineColor = 'red';
inputEl.title = message;
} else {
inputEl.style.borderColor = 'unset';
inputEl.style.outlineColor = 'unset';
inputEl.title = '';
}
return res;
},
message
Expand All @@ -43,19 +34,10 @@ submitEl.addEventListener('click', (evt) => {
}
});

addValidator(
descriptionEl,
(value) => {
if (value?.length > 140) {
return false;
}
},
'Длина комментария не может составлять больше 140 символов'
);

const modal = new Modal(uploadOverlayEl, uploadCancelEl, {
onClose: () => {
uploadInputEl.value = '';
imageProcessing.reset();
},
});

Expand All @@ -72,27 +54,60 @@ const stopPropagation = (evt) => {
hashtagsEl.addEventListener('keydown', stopPropagation);
descriptionEl.addEventListener('keydown', stopPropagation);

const descriptionError = document.createElement('div');
descriptionError.className = ERROR_CLASSNAME;
descriptionError.style.display = 'none';
descriptionEl.parentElement.appendChild(descriptionError);

addValidator(
descriptionEl,
(value) => {
if (value?.length > 140) {
descriptionError.textContent =
'Длина комментария не может составлять больше 140 символов';
descriptionError.style.display = 'block';
return false;
}
descriptionError.style.display = 'none';
},
''
);

const hashtagRegex = /^#[a-zа-яё0-9]{1,19}$/i;

const hashtagError = document.createElement('div');
hashtagError.className = ERROR_CLASSNAME;
hashtagError.style.display = 'none';
hashtagsEl.parentElement.appendChild(hashtagError);

addValidator(
hashtagsEl,
(value) => {
const hashtags = value.trim().toLowerCase().split(' ');
const hashtags = value.trim().toLowerCase().split(/\s+/);
if (hashtags.length > 5) {
hashtagError.textContent = 'Кол-во хештегов не должно быть больше 5-ти';
hashtagError.style.display = 'block';
return false;
}

for (const hashtag of hashtags) {
if (hashtag && !hashtagRegex.test(hashtag)) {
hashtagError.textContent =
'Хештег должен начинаться с #, иметь длину не более 20 символов и не состоять лишь из одного символа #';
hashtagError.style.display = 'block';
return false;
}
}
const unique = new Set(hashtags);
if (unique.size < hashtags.length) {
hashtagError.textContent = 'Не должно быть повторяющихся хештегов';
hashtagError.style.display = 'block';
return false;
}

hashtagError.style.display = 'none';
},
'Кол-во хештегов не должно быть больше 5-ти, не должно быть повторяющихся хештегов, хештег должен начинаться с #, иметь длину не более 20 символов и не состоять лишь из одного символа #'
''
);

export { initializeForm };
119 changes: 119 additions & 0 deletions js/image-proccessing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
const fieldScaleControl = document.querySelector('.scale__control--value');
const scaleSmaller = document.querySelector('.scale__control--smaller');
const scaleBigger = document.querySelector('.scale__control--bigger');
const imgPreview = document.querySelector('.img-upload__preview');

const effectSliderContainer = document.querySelector(
'.img-upload__effect-level'
);
const effectSlider = document.querySelector('.effect-level__slider');
const effectLevelValue = document.querySelector('.effect-level__value');
const effectNoneButton = document.getElementById('effect-none');
const effectChromeButton = document.getElementById('effect-chrome');
const effectSepiaButton = document.getElementById('effect-sepia');
const effectMarvinButton = document.getElementById('effect-marvin');
const effectPhobosButton = document.getElementById('effect-phobos');
const effectHeatButton = document.getElementById('effect-heat');

const effectButtons = {
none: effectNoneButton,
chrome: effectChromeButton,
sepia: effectSepiaButton,
marvin: effectMarvinButton,
phobos: effectPhobosButton,
heat: effectHeatButton,
};

const SCALE_MIN = 25;
const SCALE_MAX = 100;
const SCALE_STEP = 25;
const SCALE_DEFAULT = parseFloat(fieldScaleControl.value);

let scale = SCALE_DEFAULT;
const setScale = (value) => {
scale = Math.max(Math.min(value, SCALE_MAX), SCALE_MIN);
fieldScaleControl.value = `${scale}%`;
imgPreview.style.transform = `scale(${scale}%)`;
};

noUiSlider.create(effectSlider, {
start: 100,
connect: [true, false],
range: {
min: 0,
max: 100,
},
});

let intensity = 100;
let effect = 'none';

const updateEffect = () => {
switch (effect) {
case 'chrome':
imgPreview.style.filter = `grayscale(${intensity / 100})`;
break;
case 'sepia':
imgPreview.style.filter = `sepia(${intensity / 100})`;
break;
case 'marvin':
imgPreview.style.filter = `invert(${intensity}%)`;
break;
case 'phobos':
imgPreview.style.filter = `blur(${(intensity / 100) * 3}px)`;
break;
case 'heat':
imgPreview.style.filter = `brightness(${(intensity / 100) * 2 + 1})`;
break;
case 'none':
default:
imgPreview.style.filter = 'none';
}
};

const setIntensity = (value) => {
effectSlider.noUiSlider.set(value);
};
effectSlider.noUiSlider.on('update', () => {
intensity = effectSlider.noUiSlider.get();
effectLevelValue.value = intensity;
updateEffect();
});

const setEffect = (value) => {
effect = value && value in effectButtons ? value : 'none';
if (effect === 'none') {
effectSliderContainer.style.display = 'none';
} else {
effectSliderContainer.style.display = 'unset';
}
Object.entries(effectButtons).forEach(([key, button]) => {
button.checked = value === key;
});
setIntensity(100);
};

const initialize = () => {
scaleSmaller.onclick = function () {
setScale(scale - SCALE_STEP);
};

scaleBigger.onclick = function () {
setScale(scale + SCALE_STEP);
};

Object.entries(effectButtons).forEach(([key, button]) => {
button.onclick = () => {
setEffect(key);
};
});

setEffect('none');
};

const reset = () => {
setScale(SCALE_DEFAULT);
setEffect('none');
};

export { initialize, reset };