diff --git a/index.html b/index.html index bd4593f..7f3e191 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,7 @@ + Кекстаграм @@ -31,7 +32,7 @@

Фотографии других

Загрузка фотографии

-
+
@@ -235,6 +236,8 @@

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

+ + diff --git a/js/add-effects/index.js b/js/add-effects/index.js new file mode 100644 index 0000000..e46f716 --- /dev/null +++ b/js/add-effects/index.js @@ -0,0 +1,31 @@ +const scaleControlSmaller = document.querySelector('.scale__control--smaller'); +const scaleControlBigger = document.querySelector('.scale__control--bigger'); +const scaleControlValue = document.querySelector('.scale__control--value'); +const uploaPreviewImage = document.querySelector('.img-upload__preview img'); + +const STEP_SCALE = 25; +const MIN_SCALE = 25; +const MAX_SCALE = 100; + +const onIncreaseScale = () => { + const scaleValue = parseInt(scaleControlValue.value, 10); + const scaleCount = scaleValue + STEP_SCALE; + const scaleLimited = scaleValue >= MAX_SCALE ? MAX_SCALE : scaleCount; + scaleControlValue.value = `${scaleLimited}%`; + uploaPreviewImage.style.transform = `scale(${scaleLimited / 100})`; +}; + +const onDecreaseScale = () => { + const scaleValue = parseInt(scaleControlValue.value, 10); + const scaleCount = scaleValue - STEP_SCALE; + const scaleLimited = scaleValue <= MIN_SCALE ? MIN_SCALE : scaleCount; + scaleControlValue.value = `${scaleLimited}%`; + uploaPreviewImage.style.transform = `scale(${scaleLimited / 100})`; +}; + +const scaleListener = () => { + scaleControlBigger.addEventListener('click', onIncreaseScale); + scaleControlSmaller.addEventListener('click', onDecreaseScale); +}; + +export {scaleListener}; diff --git a/js/create-thumbnails/index.js b/js/create-thumbnails/index.js index 439bef3..f37f71a 100644 --- a/js/create-thumbnails/index.js +++ b/js/create-thumbnails/index.js @@ -1,5 +1,5 @@ import {photoItems} from '../generate-data'; -import {onOpenBigPicture } from '../showLargePicture'; +import {onOpenBigPicture } from '../show-large-picture'; const pictureTemplate = document.querySelector('#picture').content.querySelector('.picture'); const pictureList = document.querySelector('.pictures'); @@ -7,7 +7,6 @@ const createPhotoUsers = photoItems(); const addPhotoThumbnailsUsers = () => { const listFragmentPhoto = document.createDocumentFragment(); - createPhotoUsers.forEach((element) => { const pictureItem = pictureTemplate.cloneNode(true); const pictureImage = pictureItem.querySelector('.picture__img'); @@ -18,9 +17,7 @@ const addPhotoThumbnailsUsers = () => { pictureItem.querySelector('.picture__comments').textContent = element.comments.length; listFragmentPhoto.appendChild(pictureItem); }); - return pictureList.appendChild(listFragmentPhoto); - }; pictureList.addEventListener('click', (evt) => { diff --git a/js/generate-data/createComments.js b/js/generate-data/createComments.js index 31a45d8..8bf885f 100644 --- a/js/generate-data/createComments.js +++ b/js/generate-data/createComments.js @@ -1,7 +1,6 @@ import {getRandomInteger, createId, getRandomArrayElement} from '../util'; import {NAMES, MESSAGES} from '../data'; - const AVATAR_MIN = 1; const AVATAR_MAX = 6; const COMMENTS_MIN = 0; diff --git a/js/main.js b/js/main.js index 1daee90..5cd9076 100644 --- a/js/main.js +++ b/js/main.js @@ -1,3 +1,11 @@ import {addPhotoThumbnailsUsers} from './create-thumbnails'; +import {onOpenChangePhotoListener} from './upload-photo'; +import {validateListener} from './validation-form'; +import {scaleListener} from './add-effects'; +import {effectCheckedListener} from './slider-effects'; +effectCheckedListener(); +scaleListener(); +validateListener(); +onOpenChangePhotoListener(); addPhotoThumbnailsUsers(); diff --git a/js/showLargePicture/createComments.js b/js/show-large-picture/createComments.js similarity index 99% rename from js/showLargePicture/createComments.js rename to js/show-large-picture/createComments.js index 4eb41ea..007be80 100644 --- a/js/showLargePicture/createComments.js +++ b/js/show-large-picture/createComments.js @@ -1,8 +1,8 @@ import {buttonShowMore, socialCommentsList, commentShownCount, socialCommentChild} from './elementVariables'; + const LIMITED_DISPLAY_COMMENTS = 5; const arrayComments = []; - const createComment = (comments) => { const socialComment = document.createElement('li'); socialComment.classList.add('social__comment'); @@ -42,15 +42,12 @@ const onShowMoreComments = () =>{ } }; - const renderComments = (element) => { arrayComments.splice(0, arrayComments.length) ; arrayComments.push(...element); socialCommentsList.innerHTML = ''; - buttonShowMore.addEventListener('click', onShowMoreComments); onShowMoreComments(); }; - export {renderComments, onShowMoreComments}; diff --git a/js/showLargePicture/elementVariables.js b/js/show-large-picture/elementVariables.js similarity index 100% rename from js/showLargePicture/elementVariables.js rename to js/show-large-picture/elementVariables.js diff --git a/js/showLargePicture/index.js b/js/show-large-picture/index.js similarity index 86% rename from js/showLargePicture/index.js rename to js/show-large-picture/index.js index d665d8d..4a64971 100644 --- a/js/showLargePicture/index.js +++ b/js/show-large-picture/index.js @@ -1,23 +1,16 @@ import { bigPictureBlock, bigPictureCancel, bigPictureImg, likesCount, commentTotalCount, socialCaption, socialCommentsList, buttonShowMore} from './elementVariables'; import {renderComments, onShowMoreComments} from './createComments'; - - -const toggleClass = (isOpened = true) =>{ - document.body.classList.toggle('modal-open'); - bigPictureBlock.classList.toggle('hidden', !isOpened); -}; +import {toggleClass} from '../util'; const onCloseBigPicture = () => { - toggleClass(false); + toggleClass(bigPictureBlock, false); socialCommentsList.innerHTML = ''; buttonShowMore.classList.remove('hidden'); - buttonShowMore.removeEventListener('click', onShowMoreComments); bigPictureCancel.removeEventListener('click', onCloseBigPicture); document.removeEventListener('keydown', onCloseBigPictureEsc); }; - const addInformation = ({url, description, likes, comments}) =>{ bigPictureImg.src = url; bigPictureImg.alt = description; @@ -28,9 +21,8 @@ const addInformation = ({url, description, likes, comments}) =>{ const onOpenBigPicture = (element) => { addInformation(element); - toggleClass(); + toggleClass(bigPictureBlock); renderComments(element.comments); - document.addEventListener('keydown', onCloseBigPictureEsc); bigPictureCancel.addEventListener('click', onCloseBigPicture); }; @@ -42,6 +34,5 @@ function onCloseBigPictureEsc(evt){ } } - export {onOpenBigPicture}; diff --git a/js/slider-effects/effect-data.js b/js/slider-effects/effect-data.js new file mode 100644 index 0000000..5252bf9 --- /dev/null +++ b/js/slider-effects/effect-data.js @@ -0,0 +1,51 @@ +export const DEFAULT_MIN = 0; +export const DEFAULT_MAX = 100; +export const DEFAULT_START = 0; + +export const EFFECTS = [ + { + name: 'chrome', + style: 'grayscale', + min: 0, + max: 1, + step: 0.1, + start: 1, + unit: ' ', + }, + { + name: 'sepia', + style: 'sepia', + min: 0, + max: 1, + step: 0.1, + start: 1, + unit: ' ', + }, + { + name: 'marvin', + style: 'invert', + min: 0, + max: 100, + step: 1, + start: 100, + unit: '%', + }, + { + name: 'phobos', + style: 'blur', + min: 0, + max: 3, + step: 0.1, + start: 3, + unit: 'px', + }, + { + name: 'heat', + style: 'brightness', + min: 1, + max: 3, + step: 0.1, + start: 3, + unit: ' ', + }, +]; diff --git a/js/slider-effects/index.js b/js/slider-effects/index.js new file mode 100644 index 0000000..6dbb6a8 --- /dev/null +++ b/js/slider-effects/index.js @@ -0,0 +1,49 @@ +import {effectLevelSliderParrent, effectLevelSlider, effectLevelInput, effectChecked, uploaPreviewImage} from './slider-variables'; +import {EFFECTS, DEFAULT_MIN, DEFAULT_MAX, DEFAULT_START } from './effect-data'; + +const sliderVisableToggle = (isShown = true) => { + effectLevelSliderParrent.classList.toggle('hidden', isShown); +}; + +const searhEffect = (value, array) => array.find((element) => element.name === value); + +noUiSlider.create(effectLevelSlider, { + range: { + min: DEFAULT_MIN, + max: DEFAULT_MAX, + }, + start: DEFAULT_START, + connect: 'lower' +}); + +const sliderUpdateOptions = (value) => { + const effect = searhEffect(value, EFFECTS); + if(!effect){ + sliderVisableToggle(); + uploaPreviewImage.style.removeProperty('filter'); + return; + } + const {min, max, start, step, unit} = effect; + sliderVisableToggle(false); + effectLevelSlider.noUiSlider.updateOptions({ + range: { + min: min, + max: max, + }, + start: start, + step: step + }); + effectLevelSlider.noUiSlider.on('update', () => { + const effectLevelInputValue = effectLevelSlider.noUiSlider.get(); + effectLevelInput.value = effectLevelInputValue; + uploaPreviewImage.style.filter = `${effect.style}(${effectLevelInputValue}${unit})`; + }); +}; + +export const effectCheckedListener = () => { + effectChecked.addEventListener('change', (evt) => { + if (evt.target.checked) { + sliderUpdateOptions(evt.target.value); + } + }); +}; diff --git a/js/slider-effects/slider-variables.js b/js/slider-effects/slider-variables.js new file mode 100644 index 0000000..75619b2 --- /dev/null +++ b/js/slider-effects/slider-variables.js @@ -0,0 +1,5 @@ +export const effectLevelSliderParrent = document.querySelector('.img-upload__effect-level'); +export const effectLevelSlider = effectLevelSliderParrent.querySelector('.effect-level__slider'); +export const effectLevelInput = document.querySelector('.effect-level__value'); +export const effectChecked = document.querySelector('.effects'); +export const uploaPreviewImage = document.querySelector('.img-upload__preview img'); diff --git a/js/upload-photo/index.js b/js/upload-photo/index.js new file mode 100644 index 0000000..5a3e5c8 --- /dev/null +++ b/js/upload-photo/index.js @@ -0,0 +1,64 @@ +import {toggleClass} from '../util'; +import {imgUploadInput, imgUpoadOverlay, imgUploadancel} from './uploadPhotoVariables'; +import {inputTextHashtag, commentForm} from '../validation-form'; +const effectsPreview = document.querySelectorAll('.effects__preview'); + +const effectLevelSliderParrent = document.querySelector('.img-upload__effect-level'); +const uploaPreviewImage = document.querySelector('.img-upload__preview img'); + +const onCloseChangePhoto = () => { + toggleClass(imgUpoadOverlay, false); + imgUploadInput.value = ''; + uploaPreviewImage.style.removeProperty('filter'); + uploaPreviewImage.style.removeProperty('transform'); + document.removeEventListener('keydown', onCloseChangePhotoEsc); + imgUploadancel.removeEventListener('click', onCloseChangePhoto); + +}; + +function onCloseChangePhotoEsc(evt){ + if (evt.key === 'Escape' && !(document.activeElement === inputTextHashtag || document.activeElement === commentForm)) { + onCloseChangePhoto(); + } else { + evt.stopPropagation(); + } +} + +const selectImage = (evt) => { + const files = evt.target.files; + const countFiles = files.length; + const selectedFile = files[0]; + const reader = new FileReader(); + + if (!countFiles) { + return; + } + + if (!/^image/.test(selectedFile.type)) { + return; + } + + reader.readAsDataURL(selectedFile); + + reader.addEventListener('load', (e) => { + uploaPreviewImage.src = e.target.result; + effectsPreview.forEach((element) => { + element.style.backgroundImage = `url(${e.target.result})`; + }); + }); +}; + +const onOpenChangePhoto = (evt) => { + toggleClass(imgUpoadOverlay); + selectImage(evt); + effectLevelSliderParrent.classList.add('hidden'); + document.addEventListener('keydown', onCloseChangePhotoEsc); + imgUploadancel.addEventListener('click', onCloseChangePhoto); + +}; + +const onOpenChangePhotoListener = () => { + imgUploadInput.addEventListener('change', onOpenChangePhoto); +}; + +export {onOpenChangePhotoListener}; diff --git a/js/upload-photo/uploadPhotoVariables.js b/js/upload-photo/uploadPhotoVariables.js new file mode 100644 index 0000000..e6628a7 --- /dev/null +++ b/js/upload-photo/uploadPhotoVariables.js @@ -0,0 +1,3 @@ +export const imgUploadInput = document.querySelector('.img-upload__input'); +export const imgUpoadOverlay = document.querySelector('.img-upload__overlay'); +export const imgUploadancel = imgUpoadOverlay.querySelector('.img-upload__cancel'); diff --git a/js/util/index.js b/js/util/index.js index 8e426c8..150517b 100644 --- a/js/util/index.js +++ b/js/util/index.js @@ -1,4 +1,3 @@ - const getRandomInteger = (a, b) => { const lower = Math.ceil(Math.min(a, b)); const upper = Math.floor(Math.max(a, b)); @@ -16,5 +15,9 @@ const createId = () => { const getRandomArrayElement = (elements) => elements[getRandomInteger(0, elements.length - 1)]; +const toggleClass = (className, isOpened = true) =>{ + className.classList.toggle('hidden', !isOpened); + document.body.classList.toggle('modal-open'); +}; -export {getRandomInteger, createId, getRandomArrayElement}; +export {getRandomInteger, createId, getRandomArrayElement, toggleClass}; diff --git a/js/validation-form/index.js b/js/validation-form/index.js new file mode 100644 index 0000000..740c1eb --- /dev/null +++ b/js/validation-form/index.js @@ -0,0 +1,51 @@ +const imgUploadForm = document.querySelector('.img-upload__form'); +export const inputTextHashtag = imgUploadForm.querySelector('.text__hashtags'); +export const commentForm = imgUploadForm.querySelector('.text__description'); + +const HASHTAGS_LIMIT = 5; +const COMMENTS_LIMIT = 140; +const hashtagRegex = /^#[a-zа-яё0-9]{1,19}$/i; + +const onValidateCommentForm = (value) => value.length <= COMMENTS_LIMIT; +const createHashtagArray = () => inputTextHashtag.value.replace(/\s+/g, ' ').trim().toLowerCase().split(' '); + +const onValidateHashtagRegex = () => { + const arrayHashtag = createHashtagArray(); + if (arrayHashtag.length === 1 && arrayHashtag[0] === '') { + return true; + } + return arrayHashtag.every((item) => hashtagRegex.test(item)); +}; + +const onUniqueHashtag = () => { + const arrayHashtag = createHashtagArray(); + return arrayHashtag.length === new Set(arrayHashtag).size; +}; + +const onHashtagLimitLength = () => { + const arrayHashtag = createHashtagArray(); + return arrayHashtag.length <= HASHTAGS_LIMIT; +}; + +const pristine = new Pristine(imgUploadForm, { + classTo: 'img-upload__field-wrapper', + errorClass: 'img-upload__field-wrapper--error', + successClass: 'img-upload__field-wrapper--valid', + errorTextParent: 'img-upload__field-wrapper', +}); + +pristine.addValidator(inputTextHashtag, onValidateHashtagRegex, 'Введён невалидный хэштег'); +pristine.addValidator(inputTextHashtag, onUniqueHashtag, 'Хэштеги повторяются'); +pristine.addValidator(inputTextHashtag, onHashtagLimitLength, 'Превышено количество хэштегов'); +pristine.addValidator(commentForm, onValidateCommentForm, 'Длина комментария больше 140 символов'); + +const validateListener = () => { + imgUploadForm.addEventListener('submit', (evt) => { + if(pristine.validate()) { + imgUploadForm.submit(); + } + evt.preventDefault(); + }); +}; + +export {validateListener};