Skip to content

Commit

Permalink
Прошел тесты в Chrome и Firefox
Browse files Browse the repository at this point in the history
  • Loading branch information
Talurion committed Aug 7, 2024
1 parent 800dcee commit c267a3e
Show file tree
Hide file tree
Showing 14 changed files with 739 additions and 3 deletions.
11 changes: 8 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
<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">
<link rel="stylesheet" href="vendor/nouislider/nouislider.css">
<script src="vendor/pristine/pristine.min.js" type="text/javascript"></script>
<script src="vendor/nouislider/nouislider.js" type="text/javascript"></script>
<title>Кекстаграм</title>
</head>

Expand All @@ -30,7 +33,7 @@ <h2 class="pictures__title visually-hidden">Фотографии других
<section class="img-upload">
<div class="img-upload__wrapper">
<h2 class="img-upload__title visually-hidden">Загрузка фотографии</h2>
<form class="img-upload__form" id="upload-select-image" autocomplete="off">
<form class="img-upload__form" id="upload-select-image" autocomplete="off" method="post" enctype="multipart/form-data" action="https://32.javascript.htmlacademy.pro/kekstagram">

<!-- Изначальное состояние поля для загрузки изображения -->
<fieldset class="img-upload__start">
Expand Down Expand Up @@ -116,10 +119,10 @@ <h2 class="img-upload__title visually-hidden">Загрузка фотограф
<!-- Добавление хэш-тегов и комментария к изображению -->
<fieldset class="img-upload__text text">
<div class="img-upload__field-wrapper">
<input class="text__hashtags" name="hashtags" placeholder="#ХэшТег">
<input type="text" class="text__hashtags" name="hashtags" placeholder="#ХэшТег">
</div>
<div class="img-upload__field-wrapper">
<textarea class="text__description" name="description" placeholder="Ваш комментарий..."></textarea>
<textarea type="text" class="text__description" name="description" placeholder="Ваш комментарий..."></textarea>
</div>
</fieldset>

Expand Down Expand Up @@ -234,5 +237,7 @@ <h2 class="data-error__title">Не удалось загрузить данны
</section>
</template>

<script src="js/main.js" type="module"></script>

</body>
</html>
24 changes: 24 additions & 0 deletions js/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const BASE_URL = 'https://32.javascript.htmlacademy.pro/kekstagram';
const API_ROUTE = {
GET: `${BASE_URL}/data`,
POST: `${BASE_URL}/`,
};


const getData = (successCallback, errorCallback) => fetch(API_ROUTE.GET)
.then((response) => {
if (!response.ok) {
throw new Error(`Ошибка сети: ${response.status} ${response.statusText}`);
}
return response.json();
})
.then(successCallback)
.catch(errorCallback);

const sendData = (formData) =>
fetch(API_ROUTE.POST, {
method: 'POST',
body: formData,
});

export { getData, sendData };
59 changes: 59 additions & 0 deletions js/filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { sortArrayDescending, shuffleArray, debounce } from './util.js';
import { renderThumbnailListWithRetry } from './render-thumbnails.js';

const RERENDER_DELAY = 500;
const PICTURE_COUNT = 10;

const imgFilters = document.querySelector('.img-filters');

const clearThumbnailList = () => {
const thumbnails = document.querySelectorAll('.picture');
thumbnails.forEach((element) => {
element.remove();
});
};

const changeThumbnailList = (evt, data) => {
const filterActions = {
'filter-default': () => data,
'filter-random': () => shuffleArray(data.slice()).slice(0, PICTURE_COUNT),
'filter-discussed': () => sortArrayDescending(data.slice(), (item) => item.comments.length),
};

const filterButton = Object.keys(filterActions).find((filter) => evt.target.closest(`#${filter}`));

if (filterButton) {
clearThumbnailList();
const filteredPhotoData = filterActions[filterButton]();
renderThumbnailListWithRetry(filteredPhotoData);
}
};

const setFiltersClick = (data) => {
const handleThumbnailChange = debounce((evt) => {
changeThumbnailList(evt, data);
}, RERENDER_DELAY);

imgFilters.addEventListener('click', (evt) => {
if (evt.target.classList.contains('img-filters__button')) {
const buttons = imgFilters.querySelectorAll('.img-filters__button');
const button = evt.target.closest('.img-filters__button');

if (
(button && !button.classList.contains('img-filters__button--active'))
) {
buttons.forEach((btn) => btn.classList.remove('img-filters__button--active'));
button.classList.add('img-filters__button--active');

handleThumbnailChange(evt);
}
}
});
};

const showFilters = (data) => {
imgFilters.classList.remove('img-filters--inactive');
setFiltersClick(data);
};

export { showFilters };
32 changes: 32 additions & 0 deletions js/form-img-upload-scale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const imgUploadScale = document.querySelector('.img-upload__scale');
const controlValue = imgUploadScale.querySelector('.scale__control--value');
const imgUploadPreview = document.querySelector('.img-upload__preview img');

const STEP = 25;

const updateImageScale = (formStep) => {
const value = parseInt(controlValue.value.slice(0, -1), 10);
let scaleValue = value + formStep;

if (scaleValue < 25) {
scaleValue = 25;
} else if (scaleValue > 100) {
scaleValue = 100;
}

const transformValue = `scale(${scaleValue / 100})`;
imgUploadPreview.style.transform = transformValue;
controlValue.value = `${scaleValue}%`;
};

const onFormClickScaleButtons = (evt) => {
const clickedElementClassList = evt.target.classList;

if (clickedElementClassList.contains('scale__control--bigger')) {
updateImageScale(STEP);
} else if (clickedElementClassList.contains('scale__control--smaller')) {
updateImageScale(-STEP);
}
};

export { onFormClickScaleButtons, updateImageScale };
114 changes: 114 additions & 0 deletions js/form-img-upload-sending-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { validateHashtags, getErrorText, validateDescription } from './form-img-upload-validate.js';
import { isEscapeKey } from './util.js';
import { sendData } from './api.js';

const form = document.querySelector('.img-upload__form');
const formImgUploadOverlay = form.querySelector('.img-upload__overlay');
const templateSuccess = document.querySelector('#success');
const templateError = document.querySelector('#error');
const submitButton = form.querySelector('.img-upload__submit');

let notificationElement;
let notificationCancel;
let isResponseError;

const pristine = new Pristine(form, {
classTo: 'img-upload__field-wrapper',
errorClass: 'img-upload__field-wrapper--error',
errorTextParent: 'img-upload__field-wrapper',
});

pristine.addValidator(form.querySelector('.text__hashtags'), validateHashtags, getErrorText);
pristine.addValidator(form.querySelector('.text__description'), validateDescription, getErrorText);

const resetPristin = () => {
pristine.reset();
};


const closeNotification = () => {
if (isResponseError) {
formImgUploadOverlay.classList.remove('hidden');
}
if (notificationCancel) {
notificationCancel.removeEventListener('click', onNotificationClickCancel);
notificationElement.removeEventListener('click', onNotificationClickElsewhere);
}
document.removeEventListener('keydown', onNotificationEsc);
if (notificationElement) {
notificationElement.remove();
notificationElement = null;
}
};

const showNotification = (isError) => {
const clone = document.importNode(isError ? templateError.content : templateSuccess.content, true);

if (notificationElement) {
document.body.removeChild(notificationElement);
}

notificationElement = document.createElement('div');
notificationElement.appendChild(clone);
document.body.appendChild(notificationElement);

notificationCancel = document.querySelector(isError ? '.error__button' : '.success__button');
notificationCancel.addEventListener('click', onNotificationClickCancel);
document.addEventListener('keydown', onNotificationEsc);
notificationElement.addEventListener('click', onNotificationClickElsewhere);
};

const blockSubmitButton = () => {
submitButton.disabled = true;
submitButton.textContent = 'Публикую';
};

const unblockSubmitButton = () => {
submitButton.disabled = false;
submitButton.textContent = 'Опубликовать';
};

const setUserFormSubmit = (onSuccess) => {
form.addEventListener('submit', async (evt) => {
evt.preventDefault();

const isValid = pristine.validate();
if (!isValid) {
return;
}

const formData = new FormData(evt.target);
blockSubmitButton();

try {
const response = await sendData(formData);
isResponseError = !response.ok;
showNotification(isResponseError);
onSuccess(isResponseError);
} finally {
unblockSubmitButton();
}
});
};

function onNotificationClickCancel (evt) {
evt.preventDefault();
closeNotification();
}

function onNotificationClickElsewhere (evt) {
if (!evt.target.closest('.error__inner') && isResponseError) {
closeNotification();
} else if (!evt.target.closest('.success__inner') && !isResponseError) {
closeNotification();
}
}

function onNotificationEsc (evt){
if (isEscapeKey(evt)) {
evt.preventDefault();
closeNotification();
}
}

export { setUserFormSubmit, resetPristin };
78 changes: 78 additions & 0 deletions js/form-img-upload-slider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const filtersData = {
none: { range: { min: 0, max: 1 }, step: 0.1 },
chrome: { range: { min: 0, max: 1 }, step: 0.1 },
sepia: { range: { min: 0, max: 1 }, step: 0.1 },
marvin: { range: { min: 0, max: 100 }, step: 1 },
phobos: { range: { min: 0, max: 3 }, step: 0.1 },
heat: { range: { min: 1, max: 3 }, step: 0.1 }
};

let currentFilter;

const formImgUploadWrapper = document.querySelector('.img-upload__wrapper');
const slider = formImgUploadWrapper.querySelector('.img-upload__effect-level');
const levelSlider = formImgUploadWrapper.querySelector('.effect-level__slider');
const levelValue = formImgUploadWrapper.querySelector('.effect-level__value');
const imgUploadPreview = formImgUploadWrapper.querySelector('.img-upload__preview img');
const noneRadioButton = formImgUploadWrapper.querySelector('#effect-none');

const changeFilter = (value) => {
switch (currentFilter) {
case 'none': imgUploadPreview.style.filter = ''; break;
case 'chrome': imgUploadPreview.style.filter = `grayscale(${value})`; break;
case 'sepia': imgUploadPreview.style.filter = `sepia(${value})`; break;
case 'marvin': imgUploadPreview.style.filter = `invert(${value}%)`; break;
case 'phobos': imgUploadPreview.style.filter = `blur(${value}px)`; break;
case 'heat': imgUploadPreview.style.filter = `brightness(${value})`; break;
}
};

noUiSlider.create(levelSlider, {
range: {
min: filtersData.none.range.min,
max: filtersData.none.range.max
},
start: filtersData.none.range.max,
step: filtersData.none.step,
connect: 'lower',
format: {
to: (value) => (Number.isInteger(value) ? value.toFixed(0) : value.toFixed(1)),
from: (value) => parseFloat(value)
}
});

levelSlider.noUiSlider.on('update', () => {
levelValue.value = levelSlider.noUiSlider.get();
changeFilter(levelValue.value);
});

levelSlider.setAttribute('disabled', true);

const resetFilter = () => {
currentFilter = 'none';
noneRadioButton.checked = true;
levelSlider.noUiSlider.set(0);
levelSlider.setAttribute('disabled', true);
};

const onFormClickFilter = (evt) => {
slider.classList.remove('hidden');
const clickedElementId = evt.target.id.split('-')[1];
if (clickedElementId !== 'none') {
levelSlider.removeAttribute('disabled');
currentFilter = clickedElementId;
levelSlider.noUiSlider.updateOptions({
range: {
min: filtersData[clickedElementId].range.min,
max: filtersData[clickedElementId].range.max
},
start: filtersData[clickedElementId].range.max,
step: filtersData[clickedElementId].step
});
} else {
slider.classList.add('hidden');
resetFilter();
}
};

export { onFormClickFilter, resetFilter };
Loading

0 comments on commit c267a3e

Please sign in to comment.