diff --git a/index.html b/index.html index 88f6628..657bd06 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,7 @@ + Кекстаграм @@ -233,8 +234,9 @@

Изображение успешно загруже

Не удалось загрузить данные

+ - + diff --git a/js/check-hashtag-validity.js b/js/check-hashtag-validity.js index 5ebbc31..c4af41c 100644 --- a/js/check-hashtag-validity.js +++ b/js/check-hashtag-validity.js @@ -15,7 +15,11 @@ const hasValidHashtagLength = (value) => normalizeHashtags(value).every((hashtag) => hashtag.length <= MAX_HASHTAG_LENGTH); const hasValidHashtagFormat = (value) => - normalizeHashtags(value).every((hashtag) => HASHTAG_REGEXP.test(hashtag)); + normalizeHashtags(value).every((hashtag) => + hashtag.startsWith('#') && + hashtag.length > 1 && + HASHTAG_REGEXP.test(hashtag) + ); const hasUniqueHashtags = (value) => { const hashtags = normalizeHashtags(value); diff --git a/js/image-editor.js b/js/image-editor.js new file mode 100644 index 0000000..468d9d0 --- /dev/null +++ b/js/image-editor.js @@ -0,0 +1,171 @@ +const SCALE_STEP = 25; +const MIN_SCALE = 25; +const MAX_SCALE = 100; +const DEFAULT_SCALE = 100; +const DEFAULT_EFFECT = 'none'; + +const effectsConfig = { + none: { + range: { min: 0, max: 100 }, + start: 100, + step: 1, + filter: '', + unit: '' + }, + chrome: { + range: { min: 0, max: 1 }, + start: 1, + step: 0.1, + filter: 'grayscale', + unit: '' + }, + sepia: { + range: { min: 0, max: 1 }, + start: 1, + step: 0.1, + filter: 'sepia', + unit: '' + }, + marvin: { + range: { min: 0, max: 100 }, + start: 100, + step: 1, + filter: 'invert', + unit: '%' + }, + phobos: { + range: { min: 0, max: 3 }, + start: 3, + step: 0.1, + filter: 'blur', + unit: 'px' + }, + heat: { + range: { min: 1, max: 3 }, + start: 3, + step: 0.1, + filter: 'brightness', + unit: '' + } +}; + +const uploadFormNode = document.querySelector('.img-upload__form'); +const previewImageNode = uploadFormNode.querySelector('.img-upload__preview img'); + +const scaleValueNode = uploadFormNode.querySelector('.scale__control--value'); +const scaleSmallerButtonNode = uploadFormNode.querySelector('.scale__control--smaller'); +const scaleBiggerButtonNode = uploadFormNode.querySelector('.scale__control--bigger'); + +const effectsListNode = uploadFormNode.querySelector('.effects__list'); +const effectLevelNode = uploadFormNode.querySelector('.img-upload__effect-level'); +const effectLevelSliderNode = uploadFormNode.querySelector('.effect-level__slider'); +const effectLevelValueNode = uploadFormNode.querySelector('.effect-level__value'); + +const hideEffectSlider = () => { + effectLevelNode.classList.add('hidden'); +}; + +const showEffectSlider = () => { + effectLevelNode.classList.remove('hidden'); +}; + +const applyScale = (scaleValue) => { + scaleValueNode.value = `${scaleValue}%`; + previewImageNode.style.transform = `scale(${scaleValue / 100})`; +}; + +const getScaleValue = () => parseInt(scaleValueNode.value, 10); + +const getSelectedEffect = () => + uploadFormNode.querySelector('.effects__radio:checked').value; + +const applyEffect = (effectName, effectValue) => { + const currentEffect = effectsConfig[effectName]; + + if (effectName === DEFAULT_EFFECT) { + previewImageNode.style.filter = ''; + effectLevelValueNode.value = ''; + hideEffectSlider(); + return; + } + + previewImageNode.style.filter = `${currentEffect.filter}(${effectValue}${currentEffect.unit})`; + effectLevelValueNode.value = effectValue; + showEffectSlider(); +}; + +const updateSliderOptions = (effectName) => { + const currentEffect = effectsConfig[effectName]; + + effectLevelSliderNode.noUiSlider.updateOptions({ + range: currentEffect.range, + start: currentEffect.start, + step: currentEffect.step + }); + + effectLevelSliderNode.noUiSlider.set(currentEffect.start); +}; + +const onScaleSmallerButtonClick = () => { + const newScaleValue = Math.max(getScaleValue() - SCALE_STEP, MIN_SCALE); + applyScale(newScaleValue); +}; + +const onScaleBiggerButtonClick = () => { + const newScaleValue = Math.min(getScaleValue() + SCALE_STEP, MAX_SCALE); + applyScale(newScaleValue); +}; + +const onEffectsListChange = (evt) => { + if (!evt.target.matches('.effects__radio')) { + return; + } + + updateSliderOptions(evt.target.value); +}; + +const onEffectLevelSliderUpdate = () => { + const selectedEffectName = getSelectedEffect(); + const sliderValue = Number(effectLevelSliderNode.noUiSlider.get()); + + applyEffect(selectedEffectName, sliderValue); +}; + +const resetImageEditor = () => { + applyScale(DEFAULT_SCALE); + uploadFormNode.querySelector('#effect-none').checked = true; + previewImageNode.style.filter = ''; + effectLevelValueNode.value = ''; + hideEffectSlider(); + + effectLevelSliderNode.noUiSlider.updateOptions({ + range: effectsConfig[DEFAULT_EFFECT].range, + start: effectsConfig[DEFAULT_EFFECT].start, + step: effectsConfig[DEFAULT_EFFECT].step + }); + + effectLevelSliderNode.noUiSlider.set(effectsConfig[DEFAULT_EFFECT].start); +}; + +const initImageEditor = () => { + applyScale(DEFAULT_SCALE); + + if (!effectLevelSliderNode.noUiSlider) { + noUiSlider.create(effectLevelSliderNode, { + range: effectsConfig[DEFAULT_EFFECT].range, + start: effectsConfig[DEFAULT_EFFECT].start, + step: effectsConfig[DEFAULT_EFFECT].step, + connect: 'lower' + }); + + effectLevelSliderNode.noUiSlider.on('update', onEffectLevelSliderUpdate); + } + + hideEffectSlider(); + + scaleSmallerButtonNode.addEventListener('click', onScaleSmallerButtonClick); + scaleBiggerButtonNode.addEventListener('click', onScaleBiggerButtonClick); + effectsListNode.addEventListener('change', onEffectsListChange); +}; + +export { initImageEditor, resetImageEditor }; diff --git a/js/main.js b/js/main.js index ce58f80..ba393cc 100644 --- a/js/main.js +++ b/js/main.js @@ -1,7 +1,9 @@ import { photos } from './create-array-photos.js'; import { renderThumbnails } from './render-thumbnails.js'; import { initUploadPhotoForm } from './upload-photo-form.js'; +import { initImageEditor } from './image-editor.js'; renderThumbnails(photos); initUploadPhotoForm(); +initImageEditor(); diff --git a/js/upload-photo-form.js b/js/upload-photo-form.js index 2f97ffb..401ca62 100644 --- a/js/upload-photo-form.js +++ b/js/upload-photo-form.js @@ -5,6 +5,8 @@ import { hasUniqueHashtags } from './check-hashtag-validity.js'; +import { resetImageEditor } from './image-editor.js'; + const MAX_COMMENT_LENGTH = 140; const bodyNode = document.body; @@ -30,6 +32,7 @@ const resetUploadPhotoForm = () => { uploadFormNode.reset(); uploadFileControl.value = ''; pristine.reset(); + resetImageEditor(); }; const stopEscapePropagation = (evt) => { @@ -78,20 +81,20 @@ const onUploadFormSubmit = (evt) => { pristine.addValidator( hashtagsInputNode, - hasValidHashtagCount, - 'Нельзя указать больше пяти хэштегов' + hasValidHashtagFormat, + 'Хэштег должен начинаться с # и содержать только буквы и цифры' ); pristine.addValidator( hashtagsInputNode, - hasValidHashtagLength, - 'Максимальная длина одного хэштега — 20 символов' + hasValidHashtagCount, + 'Нельзя указать больше пяти хэштегов' ); pristine.addValidator( hashtagsInputNode, - hasValidHashtagFormat, - 'Хэштег должен начинаться с # и содержать только буквы и цифры' + hasValidHashtagLength, + 'Максимальная длина одного хэштега — 20 символов' ); pristine.addValidator(