From 6c9ba4b02099a4338528dc265700dc75e2514031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=A1=D0=BE=D0=BB=D0=BE=D0=B2=D1=8C=D0=B5=D0=B2?= Date: Thu, 16 Jan 2025 21:02:38 +0500 Subject: [PATCH] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D0=B7=D0=B0?= =?UTF-8?q?=D1=87=D1=91=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 6 - js/big-picture.js | 102 ++++++++++++++++ js/data.js | 88 +++++++------- js/filter.js | 126 ++++++++++++++++++++ js/form.js | 101 ++++++++++------ js/{loadPhotoForm.js => load-photo-form.js} | 5 - js/main.js | 12 +- js/mini-picture.js | 26 ++++ js/render.js | 28 +---- js/util.js | 23 +++- 10 files changed, 397 insertions(+), 120 deletions(-) create mode 100644 js/big-picture.js create mode 100644 js/filter.js rename js/{loadPhotoForm.js => load-photo-form.js} (99%) create mode 100644 js/mini-picture.js diff --git a/index.html b/index.html index fb4fae0..f7103c4 100644 --- a/index.html +++ b/index.html @@ -6,16 +6,10 @@ - - Кекстаграм - -
- -
diff --git a/js/big-picture.js b/js/big-picture.js new file mode 100644 index 0000000..2bd6307 --- /dev/null +++ b/js/big-picture.js @@ -0,0 +1,102 @@ +import { isEscapeKey } from './util.js'; + +const bigPictureForm = document.querySelector('.big-picture'); +const bigPictureComments = bigPictureForm.querySelector('.social__comments'); +const loadCommentButton = bigPictureForm.querySelector('.comments-loader'); +const COMMENTS_TO_SHOW = 5; + +function onStartCreateBigPicture(photosData) { + const bigPictureOpenForm = document.querySelector('.pictures'); + bigPictureOpenForm.addEventListener('click', (evt) => { + const idPicture = onFindIdPhoto(evt); + const photo = onFindPhoto(photosData, idPicture); + onOpenBigPicture(photo); + evt.preventDefault(); + }); +} + +function onFindIdPhoto(evt) { + const pictureElement = evt.target.closest('[data-id-picture]'); + if (!pictureElement) { + return; + } + return pictureElement.dataset.idPicture; +} + +function onFindPhoto(photosData, idPicture) { + return photosData.find((item) => item.id === Number(idPicture)); +} + +function onAddCommentsForBigPicture(commentsArray) { + bigPictureComments.innerHTML = ''; + commentsArray.forEach((comment) => { + const commentElement = document.createElement('li'); + commentElement.classList.add('social__comment'); + commentElement.innerHTML = ` + `; + bigPictureComments.appendChild(commentElement); + }); + + const commentsItems = bigPictureComments.children; + bigPictureForm.querySelector('.loaded-comments').textContent = commentsItems.length > COMMENTS_TO_SHOW ? COMMENTS_TO_SHOW : commentsItems.length; + for (let i = COMMENTS_TO_SHOW; i < commentsItems.length; i++) { + commentsItems[i].classList.add('hidden'); + } + + if (commentsItems.length > COMMENTS_TO_SHOW) { + loadCommentButton.classList.remove('hidden'); + loadCommentButton.addEventListener('click', onLoadComments); + } else { + loadCommentButton.classList.add('hidden'); + } +} + +function onLoadComments() { + const commentsItems = bigPictureComments.children; + const loadedCommentsCountElement = bigPictureForm.querySelector('.loaded-comments'); + const loadedCommentsCount = parseInt(loadedCommentsCountElement.textContent, 10); + const currentComments = loadedCommentsCount + COMMENTS_TO_SHOW > commentsItems.length ? commentsItems.length - loadedCommentsCount : COMMENTS_TO_SHOW; + for (let i = 0; i < currentComments; i++) { + document.querySelector('.social__comment.hidden').classList.remove('hidden'); + } + loadedCommentsCountElement.textContent = loadedCommentsCount + currentComments; + if (loadedCommentsCount + currentComments === commentsItems.length) { + loadCommentButton.classList.add('hidden'); + } +} + +function onOpenBigPicture(photo) { + const bigPictureImg = bigPictureForm.querySelector('.big-picture__img img'); + const bigPictureLikesCount = bigPictureForm.querySelector('.likes-count'); + const bigPictureCommentCount = bigPictureForm.querySelector('.comments-count'); + const bigPictureDesc = bigPictureForm.querySelector('.social__caption'); + + bigPictureImg.src = photo.url; + bigPictureImg.alt = photo.alt; + bigPictureLikesCount.textContent = photo.likes; + bigPictureCommentCount.textContent = photo.comments.length; + onAddCommentsForBigPicture(photo.comments); + bigPictureDesc.textContent = photo.description; + + bigPictureForm.classList.remove('hidden'); + document.body.classList.add('modal-open'); + + const bigPictureCloseForm = bigPictureForm.querySelector('.big-picture__cancel'); + bigPictureCloseForm.addEventListener('click', onCloseBigPicture); + + document.addEventListener('keydown', onDocumentEscKeyDown); +} + +function onDocumentEscKeyDown(evt) { + if (isEscapeKey(evt)) { + onCloseBigPicture(); + } +} + +function onCloseBigPicture() { + bigPictureForm.classList.add('hidden'); + document.body.classList.remove('modal-open'); + document.removeEventListener('keydown', onDocumentEscKeyDown); +} + +export { onStartCreateBigPicture }; diff --git a/js/data.js b/js/data.js index 4ad2041..9780f4f 100644 --- a/js/data.js +++ b/js/data.js @@ -1,14 +1,13 @@ -import { getRandomInt, getRandomElement } from './util.js'; - -const descriptions = [ - 'Прекрасный вид!', - 'Невероятная красота природы.', - 'Лучший момент дня.', - 'Воспоминания на всю жизнь.', - 'Удивительное приключение.' -]; +import { createIdGenerator, getRandomInteger, getRandomArrayElement } from './util.js'; + +const MAX_ID_FOR_PHOTOS = 25; + +const MAX_SCALE = 100; +const MIN_SCALE = 0; +const STEP_SCALE = 25; + -const messages = [ +const COMMENT_MESSAGES = [ 'Всё отлично!', 'В целом всё неплохо. Но не всё.', 'Когда вы делаете фотографию, хорошо бы убирать палец из кадра. В конце концов это просто непрофессионально.', @@ -17,39 +16,48 @@ const messages = [ 'Лица у людей на фотке перекошены, как будто их избивают. Как можно было поймать такой неудачный момент?!' ]; -const names = ['Артём', 'Иван', 'Ольга', 'Мария', 'Дмитрий', 'Елена']; - -function generateComments() { - const comments = []; - const commentCount = getRandomInt(0, 30); - for (let i = 0; i < commentCount; i++) { - comments.push({ - id: getRandomInt(1, 1000), - avatar: `img/avatar-${getRandomInt(1, 6)}.svg`, - message: getRandomElement(messages), - name: getRandomElement(names) - }); +const NAMES = ['Артём', 'Иван', 'Ольга', 'Мария', 'Дмитрий', 'Елена']; + +function createArrayComments(count) { + const commentsArray = []; + const generateCommentId = createIdGenerator(); + while (commentsArray.length < count) { + const comment = { + id: generateCommentId(), + avatar: `img/avatar-${getRandomInteger(1, 6)}.svg`, + message: getRandomArrayElement(COMMENT_MESSAGES), + name: getRandomArrayElement(NAMES) + }; + + if (getRandomInteger(1, 2) === 2) { + let secondMessage = getRandomArrayElement(COMMENT_MESSAGES); + while (comment.message === secondMessage) { + secondMessage = getRandomArrayElement(COMMENT_MESSAGES); + } + comment.message += ` ${secondMessage}`; + } + + commentsArray.push(comment); } - return comments; + + return commentsArray; } -export function generatePhotos() { - const photos = []; - for (let i = 1; i <= 25; i++) { - photos.push({ - id: i, - url: `photos/${i}.jpg`, - description: getRandomElement(descriptions), - likes: getRandomInt(15, 200), - comments: generateComments() - }); +function createArrayPhotos() { + const generatePhotoId = createIdGenerator(); + const photoIdForUrl = createIdGenerator(); + const photosArray = []; + while (photosArray.length < MAX_ID_FOR_PHOTOS) { + const photo = { + id: generatePhotoId(), + url: `photos/${photoIdForUrl()}.jpg`, + description: 'Еще не придумал что здесь написать', + likes: getRandomInteger(15, 200), + comments: createArrayComments(getRandomInteger(0, 30)) + }; + photosArray.push(photo); } - return photos; + return photosArray; } - -const MAX_SCALE = 100; -const MIN_SCALE = 0; -const STEP_SCALE = 25; - -export { MAX_SCALE, MIN_SCALE, STEP_SCALE }; +export { createArrayPhotos, MAX_SCALE, MIN_SCALE, STEP_SCALE }; diff --git a/js/filter.js b/js/filter.js new file mode 100644 index 0000000..7db6ee8 --- /dev/null +++ b/js/filter.js @@ -0,0 +1,126 @@ +const sliderOptions = { + chrome: { + MIN: 0, + MAX: 1, + STEP: 0.1 + }, + sepia: { + MIN: 0, + MAX: 1, + STEP: 0.1 + }, + marvin: { + MIN: 0, + MAX: 100, + STEP: 1 + }, + phobos: { + MIN: 0, + MAX: 3, + STEP: 0.1 + }, + heat: { + MIN: 1, + MAX: 3, + STEP: 0.1 + } +}; + +const filtersList = document.querySelector('.effects__list'); +const imageElement = document.querySelector('.img-upload__preview').children[0]; +const sliderField = document.querySelector('.img-upload__effect-level'); +const sliderElement = document.querySelector('.effect-level__slider'); +const effectLevel = document.querySelector('.effect-level__value'); + +const effects = { + none: () => { + sliderField.classList.add('visually-hidden'); + return ''; + }, + chrome: () => { + sliderField.classList.remove('visually-hidden'); + return `grayscale(${effectLevel.value})`; + }, + sepia: () => { + sliderField.classList.remove('visually-hidden'); + return `sepia(${effectLevel.value})`; + }, + marvin: () => { + sliderField.classList.remove('visually-hidden'); + return `invert(${effectLevel.value}%)`; + }, + phobos: () => { + sliderField.classList.remove('visually-hidden'); + return `blur(${effectLevel.value}px)`; + }, + heat: () => { + sliderField.classList.remove('visually-hidden'); + return `brightness(${effectLevel.value})`; + } +}; + +noUiSlider.create(sliderElement, { + range: { + min: 0, + max: 1 + }, + start: 1, + step: 1, + connect: 'lower' +}); + +sliderField.classList.add('visually-hidden'); +let currentEffect = ''; + +const setSliderOptions = (effect) => { + const options = sliderOptions[effect]; + sliderElement.noUiSlider.updateOptions({range: {min: options.MIN, max: options.MAX}, start: options.MAX, step: options.STEP}); + effectLevel.value = options.MAX; +}; + +const setDefaultFilter = () => { + const defaultRadio = filtersList.querySelector('#effect-none'); + defaultRadio.checked = true; + + if (currentEffect !== '') { + imageElement.classList.remove(currentEffect); + } + + currentEffect = ''; + + imageElement.style.filter = effects['none'](); +}; + +const onFilterListClick = (evt) => { + let target = evt.target; + + if (target.classList.contains('effects__radio')) { + return; + } + + if (target.classList.contains('effects__label')) { + target = target.querySelector('span'); + } + + if (target.classList.contains('effects__preview')) { + if (currentEffect !== '') { + imageElement.classList.remove(currentEffect); + } + } + + currentEffect = target.classList[1]; + imageElement.classList.add(currentEffect); + + setSliderOptions(currentEffect.replace('effects__preview--', '')); + imageElement.style.filter = effects[currentEffect.replace('effects__preview--', '')](); +}; + +const onSliderChange = () => { + effectLevel.value = sliderElement.noUiSlider.get(); + imageElement.style.filter = effects[currentEffect.replace('effects__preview--', '')](); +}; + +filtersList.addEventListener('click', onFilterListClick); +sliderElement.noUiSlider.on('change', onSliderChange); + +export {setDefaultFilter}; diff --git a/js/form.js b/js/form.js index 8846b6e..a4e0c07 100644 --- a/js/form.js +++ b/js/form.js @@ -1,51 +1,84 @@ -// form.js +const MAX_HASHTAG_LENGTH = 20; +const MAX_HASHTAGS = 5; -const form = document.querySelector('.img-upload__form'); -const hashtagsInput = form.querySelector('.text__hashtags'); -const commentInput = form.querySelector('.text__description'); -const submitButton = form.querySelector('.img-upload__submit'); +const inputForm = document.querySelector('.img-upload__form'); +const submitButton = document.querySelector('.img-upload__submit'); +const inputHashtag = document.querySelector('.text__hashtags'); -const pristine = new Pristine(form, { +const pristine = new Pristine(inputForm, { classTo: 'img-upload__field-wrapper', - errorClass: 'img-upload__field-wrapper--invalid', - successClass: 'img-upload__field-wrapper--valid', + errorClass: 'img-upload__item--invalid', + successClass: 'img-upload__item--valid', errorTextParent: 'img-upload__field-wrapper', errorTextTag: 'div', errorTextClass: 'img-upload__error' }); -function validateHashtags(value) { - const hashtags = value.trim().toLowerCase().split(/\s+/); - const uniqueHashtags = new Set(hashtags); +let errorMessage = ''; - if (hashtags.length > 5) { - return false; +const error = () => errorMessage; + +const hashtagsHandler = (text) => { + errorMessage = ''; + + text = text.toLowerCase().trim(); + if (!text) { + return true; } - for (const hashtag of hashtags) { - if (!/^#[a-zа-яё0-9]{1,19}$/i.test(hashtag)) { - return false; - } + const inputArray = text.split(/\s+/); + + if (inputArray.length === 0) { + return true; } - return uniqueHashtags.size === hashtags.length; -} + const rules = [ + { + check: inputArray.some((item) => item.indexOf('#') >= 1), + error: 'Хэш-теги должны быть разделены пробелами', + }, + { + check: inputArray.some((item) => item[0] !== '#'), + error: 'Хэш-тег должнен начинаться с символа #', + }, + { + check: inputArray.some((item) => item === '#'), + error: 'Хэш-тег не должнен состоять из одного символа #', + }, + { + check: inputArray.some((item) => inputArray.indexOf(item) !== inputArray.lastIndexOf(item)), + error: 'Хэш-теги не должны повторяться', + }, + { + check: inputArray.length > MAX_HASHTAGS, + error: `Хэш-тегов должно быть не больше ${MAX_HASHTAGS}`, + }, + { + check: inputArray.some((item) => item.length > MAX_HASHTAG_LENGTH), + error: `Длина хэш-тега не должна превышать ${MAX_HASHTAG_LENGTH} символов`, + }, + { + check: inputArray.some((item) => !/^#[a-zа-яё0-9]{1,19}$/i.test(item)), + error: 'Хэш-тег содержит недопустимые символы', + } + ]; + + return rules.every((rule) => { + const isInvalid = rule.check; + if (isInvalid) { + errorMessage = rule.error; + } + return !isInvalid; + }); +}; -function validateComment(value) { - return value.length <= 140; -} +pristine.addValidator(inputHashtag, hashtagsHandler, error, 2, false); -pristine.addValidator(hashtagsInput, validateHashtags, 'Неверный формат хэш-тега или превышено количество хэш-тегов'); -pristine.addValidator(commentInput, validateComment, 'Комментарий не может быть длиннее 140 символов'); +const onHashtagInput = () => { + submitButton.disabled = !pristine.validate(); +}; -form.addEventListener('submit', (evt) => { - evt.preventDefault(); - const isValid = pristine.validate(); - if (isValid) { - form.submit(); - } -}); +inputHashtag.addEventListener('input', onHashtagInput); +inputForm.addEventListener('submit', onHashtagInput); -submitButton.addEventListener('click', () => { - pristine.validate(); -}); +export {inputHashtag}; diff --git a/js/loadPhotoForm.js b/js/load-photo-form.js similarity index 99% rename from js/loadPhotoForm.js rename to js/load-photo-form.js index 9c90124..23417fd 100644 --- a/js/loadPhotoForm.js +++ b/js/load-photo-form.js @@ -10,7 +10,6 @@ const editFormTextHashtags = editForm.querySelector('.text__hashtags'); const editFormTextDescription = editForm.querySelector('.text__description'); const body = document.body; - const scaleField = editForm.querySelector('.img-upload__scale'); const scaleValue = scaleField.querySelector('.scale__control--value'); const smallerButton = scaleField.querySelector('.scale__control--smaller'); @@ -41,12 +40,10 @@ function closeEditForm() { loadPhotoForm.removeEventListener('keydown', onEscapeKeyDown); } - const onBiggerButtonClick = () => { const currentValue = parseInt(scaleValue.value.slice(0, -1), 10); const decreasedValue = currentValue + STEP_SCALE > MAX_SCALE ? MAX_SCALE : currentValue + STEP_SCALE; scaleValue.value = `${decreasedValue}%`; - imageElement.style = `transform: scale(${decreasedValue / 100})`; }; @@ -54,10 +51,8 @@ const onSmallerButtonClick = () => { const currentValue = parseInt(scaleValue.value.slice(0, -1), 10); const decreasedValue = currentValue - STEP_SCALE < MIN_SCALE ? MIN_SCALE : currentValue - STEP_SCALE; scaleValue.value = `${decreasedValue}%`; - imageElement.style = `transform: scale(${decreasedValue / 100})`; }; smallerButton.addEventListener('click', onSmallerButtonClick); - biggerButton.addEventListener('click', onBiggerButtonClick); diff --git a/js/main.js b/js/main.js index 38af400..8174867 100644 --- a/js/main.js +++ b/js/main.js @@ -1,8 +1,4 @@ -// main.js -import { generatePhotos } from './data.js'; -import { renderPhotos } from './render.js'; -import './form.js'; - - -const photos = generatePhotos(25); -renderPhotos(photos); +import './renderingPhoto.js'; +import './loadPhotoForm.js'; +import './validationForm.js'; +import './filters.js'; diff --git a/js/mini-picture.js b/js/mini-picture.js new file mode 100644 index 0000000..98c4d9b --- /dev/null +++ b/js/mini-picture.js @@ -0,0 +1,26 @@ +const pictureTemplate = document.querySelector('#picture').content.querySelector('.picture'); +const picturesFragment = document.querySelector('.pictures'); +const picturesTitle = document.querySelector('.pictures__title'); +picturesTitle.classList.remove('visually-hidden'); + +function createMiniPicture(currentPhoto) { + const clonePicture = pictureTemplate.cloneNode(true); + const imgClonePicture = clonePicture.querySelector('.picture__img'); + imgClonePicture.src = currentPhoto.url; + imgClonePicture.alt = currentPhoto.description; + clonePicture.querySelector('.picture__likes').textContent = currentPhoto.likes; + clonePicture.querySelector('.picture__comments').textContent = currentPhoto.comments.length; + clonePicture.dataset.idPicture = currentPhoto.id; + return clonePicture; +} + +function startCreateMiniPicture(photosArray) { + const pictureListFragment = document.createDocumentFragment(); + for (let i = 0; i < photosArray.length; i++) { + const clonePicture = createMiniPicture(photosArray[i]); + pictureListFragment.appendChild(clonePicture); + } + picturesFragment.appendChild(pictureListFragment); +} + +export { startCreateMiniPicture }; diff --git a/js/render.js b/js/render.js index b0b8ecb..877dff4 100644 --- a/js/render.js +++ b/js/render.js @@ -1,26 +1,10 @@ -// render.js -import { openFullSizePhoto } from './fullsize.js'; +import { createArrayPhotos } from './data.js'; +import { startCreateMiniPicture } from './miniaturePicture.js'; +import { startCreateBigPicture } from './bigPicture.js'; -export function renderPhotos(photos) { - const pictureTemplate = document.querySelector('#picture').content.querySelector('.picture'); - const picturesContainer = document.querySelector('.pictures'); - const fragment = document.createDocumentFragment(); +const photosData = createArrayPhotos(); - photos.forEach((photo) => { - const pictureElement = pictureTemplate.cloneNode(true); - pictureElement.querySelector('.picture__img').src = photo.url; - pictureElement.querySelector('.picture__img').alt = photo.description; - pictureElement.querySelector('.picture__likes').textContent = photo.likes; - pictureElement.querySelector('.picture__comments').textContent = photo.comments.length; - - pictureElement.addEventListener('click', () => { - openFullSizePhoto(photo); - }); - - fragment.appendChild(pictureElement); - }); - - picturesContainer.appendChild(fragment); -} +startCreateMiniPicture(photosData); +startCreateBigPicture(photosData); diff --git a/js/util.js b/js/util.js index 260ee1a..e2a1b1f 100644 --- a/js/util.js +++ b/js/util.js @@ -1,8 +1,21 @@ -export function getRandomInt(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; +function createIdGenerator() { + let lastGeneratedId = 1; + + return function() { + return lastGeneratedId++; + }; } -export function getRandomElement(array) { - return array[getRandomInt(0, array.length - 1)]; +function getRandomInteger(min, max) { + const lower = Math.ceil(Math.min(Math.abs(min), Math.abs(max))); + const upper = Math.floor(Math.max(Math.abs(min), Math.abs(max))); + const result = Math.random() * (upper - lower + 1) + lower; + return Math.floor(result); } -// + +const getRandomArrayElement = (elements) => + elements[getRandomInteger(0, elements.length - 1)]; + +const isEscapeKey = (evt) => evt.key === 'Escape'; + +export { createIdGenerator, getRandomInteger, getRandomArrayElement, isEscapeKey };