first commit
This commit is contained in:
commit
792676e974
169 files changed
+6195
No files matched your search
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
$demoBasePath = rtrim($assetsUriPrefix, '/') . "/images/demo/{$indpic}";
|
||||
$demoLocalPath = __DIR__ . "/../images/demo/{$indpic}";
|
||||
|
||||
$getAssetUrl = function (string $relativePath) use ($demoBasePath, $demoLocalPath): string {
|
||||
$fullPath = $demoLocalPath . '/' . ltrim($relativePath, '/');
|
||||
$version = file_exists($fullPath) ? filemtime($fullPath) : '1';
|
||||
return $demoBasePath . '/' . ltrim($relativePath, '/') . '?v=' . $version;
|
||||
};
|
||||
|
||||
$labelBefore = Yii::t('app', 'До');
|
||||
$labelAfter = Yii::t('app', 'После');
|
||||
|
||||
$sourceMob = $getAssetUrl('1/source_mob.avif');
|
||||
$sourceOrig = $getAssetUrl('1/source.avif');
|
||||
|
||||
$resultMob = $getAssetUrl('1/result_mob.avif');
|
||||
$resultOrig = $getAssetUrl('1/result.avif');
|
||||
|
||||
?>
|
||||
|
||||
<div id="examples">
|
||||
|
||||
<div class="image-compare" id="image-compare">
|
||||
<img src="<?= $sourceMob ?>"
|
||||
srcset="<?= $sourceMob ?> 768w, <?= $sourceOrig ?> 1696w"
|
||||
sizes="(max-width: 767.98px) 100vw, 728px"
|
||||
id="image-compare-source"
|
||||
fetchpriority="high"
|
||||
loading="eager"
|
||||
width="728"
|
||||
height="395"
|
||||
alt="<?= htmlspecialchars($labelBefore) ?>">
|
||||
|
||||
<img src="<?= $resultMob ?>"
|
||||
srcset="<?= $resultMob ?> 768w, <?= $resultOrig ?> 1696w"
|
||||
sizes="(max-width: 767.98px) 100vw, 728px"
|
||||
id="image-compare-result"
|
||||
fetchpriority="high"
|
||||
loading="eager"
|
||||
width="728"
|
||||
height="395"
|
||||
alt="<?= htmlspecialchars($labelAfter) ?>">
|
||||
</div>
|
||||
|
||||
<div class="image-examples">
|
||||
<div class="image-examples-label"><?= Yii::t('app', 'Примеры изображений') ?></div>
|
||||
<div class="image-examples-list">
|
||||
<?php for ($i = 1; $i <= 5; $i++):
|
||||
$thumbMob = $getAssetUrl("{$i}/thumb_mob.avif");
|
||||
$thumbOrig = $getAssetUrl("{$i}/thumb.avif");
|
||||
?>
|
||||
<div class="image-examples-item <?= $i === 1 ? 'active' : '' ?>"
|
||||
data-index="<?= $i ?>"
|
||||
data-source="<?= $getAssetUrl("{$i}/source.avif") ?>"
|
||||
data-result="<?= $getAssetUrl("{$i}/result.avif") ?>"
|
||||
data-source-mob="<?= $getAssetUrl("{$i}/source_mob.avif") ?>"
|
||||
data-result-mob="<?= $getAssetUrl("{$i}/result_mob.avif") ?>">
|
||||
<div class="image-examples-image">
|
||||
<?php if ($i === 1): ?>
|
||||
<picture>
|
||||
<source media="(max-width: 767.98px)" srcset="<?= $thumbMob ?>">
|
||||
<img src="<?= $thumbOrig ?>" width="53" height="53" alt="Example 1">
|
||||
</picture>
|
||||
<?php else: ?>
|
||||
<picture class="deferred-picture">
|
||||
<source media="(max-width: 767.98px)" data-srcset="<?= $thumbMob ?>">
|
||||
<img data-src="<?= $thumbOrig ?>" width="53" height="53" alt="Example <?= $i ?>" class="deferred-thumb">
|
||||
</picture>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const examples = {
|
||||
el: document.getElementById('examples'),
|
||||
titleEl: document.getElementById('form-title'),
|
||||
|
||||
hide() {
|
||||
window.scrollTo(0, 0);
|
||||
window.scrollTo({ top: 0, behavior: 'instant' });
|
||||
if (this.el) this.el.style.display = 'none';
|
||||
},
|
||||
show() {
|
||||
if (this.el) this.el.style.display = '';
|
||||
},
|
||||
setTitle(html) {
|
||||
if (this.titleEl) this.titleEl.innerHTML = html;
|
||||
}
|
||||
};
|
||||
|
||||
function setupNativeCompare(element, labelBefore, labelAfter) {
|
||||
if (!element || element.classList.contains('native-icv') || element.classList.contains('icv')) return null;
|
||||
|
||||
const sourceImg = element.querySelector('#image-compare-source');
|
||||
const resultImg = element.querySelector('#image-compare-result');
|
||||
if (!sourceImg || !resultImg) return null;
|
||||
|
||||
element.classList.add('native-icv');
|
||||
|
||||
const beforeWrap = document.createElement('div');
|
||||
beforeWrap.className = 'icv__before-wrap';
|
||||
|
||||
resultImg.className = 'icv__after-img';
|
||||
|
||||
const handle = document.createElement('div');
|
||||
handle.className = 'icv__handle';
|
||||
const circle = document.createElement('div');
|
||||
circle.className = 'icv__handle-circle';
|
||||
handle.appendChild(circle);
|
||||
|
||||
const lblBefore = document.createElement('div');
|
||||
lblBefore.className = 'icv__label icv__label-before';
|
||||
lblBefore.textContent = labelBefore;
|
||||
|
||||
const lblAfter = document.createElement('div');
|
||||
lblAfter.className = 'icv__label icv__label-after';
|
||||
lblAfter.textContent = labelAfter;
|
||||
|
||||
beforeWrap.appendChild(sourceImg);
|
||||
|
||||
element.appendChild(resultImg);
|
||||
element.appendChild(beforeWrap);
|
||||
element.appendChild(handle);
|
||||
element.appendChild(lblBefore);
|
||||
element.appendChild(lblAfter);
|
||||
|
||||
function updateSlider(percent) {
|
||||
percent = Math.max(0, Math.min(100, percent));
|
||||
beforeWrap.style.width = percent + '%';
|
||||
handle.style.left = percent + '%';
|
||||
sourceImg.style.width = element.clientWidth + 'px';
|
||||
}
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
sourceImg.style.width = element.clientWidth + 'px';
|
||||
});
|
||||
|
||||
let isDragging = false;
|
||||
function getPos(e) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
||||
return ((clientX - rect.left) / rect.width) * 100;
|
||||
}
|
||||
|
||||
function onStart(e) {
|
||||
isDragging = true;
|
||||
updateSlider(getPos(e));
|
||||
}
|
||||
|
||||
function onMove(e) {
|
||||
if (!isDragging) return;
|
||||
updateSlider(getPos(e));
|
||||
}
|
||||
|
||||
function onEnd() {
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
element.addEventListener('mousedown', onStart);
|
||||
element.addEventListener('touchstart', onStart, { passive: true });
|
||||
|
||||
window.addEventListener('mousemove', onMove);
|
||||
window.addEventListener('touchmove', onMove, { passive: true });
|
||||
|
||||
window.addEventListener('mouseup', onEnd);
|
||||
window.addEventListener('touchend', onEnd);
|
||||
|
||||
updateSlider(50);
|
||||
|
||||
return {
|
||||
setImages(sourceUrl, resultUrl) {
|
||||
sourceImg.removeAttribute('srcset');
|
||||
resultImg.removeAttribute('srcset');
|
||||
sourceImg.src = sourceUrl;
|
||||
resultImg.src = resultUrl;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function initCompareViewer() {
|
||||
const element = document.getElementById('image-compare');
|
||||
const beforeTxt = '<?= addslashes($labelBefore) ?>';
|
||||
const afterTxt = '<?= addslashes($labelAfter) ?>';
|
||||
|
||||
if (element && typeof ImageCompare !== 'undefined') {
|
||||
const options = {
|
||||
controlColor: "#FFFFFF",
|
||||
controlShadow: true,
|
||||
addCircle: true,
|
||||
addCircleBlur: true,
|
||||
showLabels: true,
|
||||
labelOptions: { before: beforeTxt, after: afterTxt, onHover: false },
|
||||
smoothing: true,
|
||||
smoothingAmount: 100,
|
||||
hoverStart: false,
|
||||
verticalMode: false,
|
||||
startingPoint: 50,
|
||||
fluidMode: false
|
||||
};
|
||||
window.icvInstance = new ImageCompare(element, options).mount();
|
||||
} else if (element) {
|
||||
window.nativeCompareInstance = setupNativeCompare(element, beforeTxt, afterTxt);
|
||||
}
|
||||
|
||||
const exampleItems = document.querySelectorAll('.image-examples-item');
|
||||
exampleItems.forEach(item => {
|
||||
item.onclick = function() {
|
||||
const isMobile = window.innerWidth < 768;
|
||||
// На мобильном берем source_mob, на ПК — полный оригинал
|
||||
const source = (isMobile && this.getAttribute('data-source-mob'))
|
||||
? this.getAttribute('data-source-mob')
|
||||
: this.getAttribute('data-source');
|
||||
const result = (isMobile && this.getAttribute('data-result-mob'))
|
||||
? this.getAttribute('data-result-mob')
|
||||
: this.getAttribute('data-result');
|
||||
|
||||
if (window.icvInstance) {
|
||||
window.icvInstance.images.start.element.removeAttribute('srcset');
|
||||
window.icvInstance.images.end.element.removeAttribute('srcset');
|
||||
window.icvInstance.images.start.element.src = source;
|
||||
window.icvInstance.images.end.element.src = result;
|
||||
} else if (window.nativeCompareInstance) {
|
||||
window.nativeCompareInstance.setImages(source, result);
|
||||
} else {
|
||||
const sourceEl = document.getElementById('image-compare-source');
|
||||
const resultEl = document.getElementById('image-compare-result');
|
||||
if (sourceEl) {
|
||||
sourceEl.removeAttribute('srcset');
|
||||
sourceEl.src = source;
|
||||
}
|
||||
if (resultEl) {
|
||||
resultEl.removeAttribute('srcset');
|
||||
resultEl.src = result;
|
||||
}
|
||||
}
|
||||
|
||||
exampleItems.forEach(el => el.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function initStrictImagePipeline() {
|
||||
initCompareViewer();
|
||||
|
||||
const preloadUrl = (url) => {
|
||||
return new Promise(resolve => {
|
||||
const img = new Image();
|
||||
img.onload = img.onerror = resolve;
|
||||
img.src = url;
|
||||
});
|
||||
};
|
||||
|
||||
const deferredPics = document.querySelectorAll('.deferred-picture');
|
||||
const thumbPromises = [];
|
||||
|
||||
deferredPics.forEach(pic => {
|
||||
const source = pic.querySelector('source');
|
||||
const img = pic.querySelector('img');
|
||||
|
||||
if (source && source.getAttribute('data-srcset')) {
|
||||
source.srcset = source.getAttribute('data-srcset');
|
||||
}
|
||||
if (img && img.getAttribute('data-src')) {
|
||||
const p = new Promise(resolve => {
|
||||
img.onload = img.onerror = resolve;
|
||||
img.src = img.getAttribute('data-src');
|
||||
});
|
||||
thumbPromises.push(p);
|
||||
}
|
||||
});
|
||||
|
||||
Promise.all(thumbPromises).then(() => {
|
||||
const queue = [2, 3, 4, 5];
|
||||
|
||||
async function loadSequentially() {
|
||||
const isMobile = window.innerWidth < 768;
|
||||
for (const num of queue) {
|
||||
const item = document.querySelector(`.image-examples-item[data-index="${num}"]`);
|
||||
if (!item) continue;
|
||||
|
||||
const s = (isMobile && item.getAttribute('data-source-mob'))
|
||||
? item.getAttribute('data-source-mob')
|
||||
: item.getAttribute('data-source');
|
||||
const r = (isMobile && item.getAttribute('data-result-mob'))
|
||||
? item.getAttribute('data-result-mob')
|
||||
: item.getAttribute('data-result');
|
||||
|
||||
await Promise.all([preloadUrl(s), preloadUrl(r)]);
|
||||
}
|
||||
}
|
||||
|
||||
if ('requestIdleCallback' in window) {
|
||||
requestIdleCallback(() => loadSequentially());
|
||||
} else {
|
||||
setTimeout(loadSequentially, 150);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initStrictImagePipeline);
|
||||
} else {
|
||||
initStrictImagePipeline();
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
$maxSide = 6000;
|
||||
|
||||
$errorSizeMessage = Yii::t('app', 'Изображение слишком большое ({width}x{height}px). Максимальный размер — {maxSide}px по большей стороне.', [
|
||||
'maxSide' => $maxSide,
|
||||
]);
|
||||
$errorMessage = Yii::t('app', 'Возникла ошибка');
|
||||
$utmJson = json_encode($UTM ?? [], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$createApiUrl = ($apiHost ?? '') . '/api/images/public/create';
|
||||
$redirectUrlBase = $redirect_url_base ?? '';
|
||||
$redirectUrlUtm = $redirect_url_utm ?? '';
|
||||
|
||||
?>
|
||||
|
||||
<form action="<?= htmlspecialchars($createApiUrl, ENT_QUOTES, 'UTF-8') ?>" class="dropzone-form gradient-dashed-border" id="dropzone-form" method="POST" style="background: transparent; border: unset; display: <?= ($show ?? true) ? '' : 'none'; ?>; cursor: pointer;">
|
||||
|
||||
<div class="dropzone-form-inner">
|
||||
<div class="dropzone-form-icon">
|
||||
<img src="<?= $assetsUriPrefix; ?>/images/dropzone-icon.svg" alt="" height="64" width="64">
|
||||
</div>
|
||||
<div id="form-title" class="dropzone-form-text"><?= Yii::t('app', 'Загрузите изображение') ?> <br class="d-md-none"><?= Yii::t('app', 'для улучшения') ?></div>
|
||||
<div class="dropzone-form-format">
|
||||
<div class="dropzone-form-format-label"><?= Yii::t('app', 'Поддерживаемые форматы') ?>:</div>
|
||||
<div class="dropzone-form-format-list">
|
||||
<div class="dropzone-form-format-item">png</div>
|
||||
<div class="dropzone-form-format-item">jpeg</div>
|
||||
<div class="dropzone-form-format-item">jpg</div>
|
||||
<div class="dropzone-form-format-item">webp</div>
|
||||
<div class="dropzone-form-format-item">heic</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropzone-form-action">
|
||||
<button type="button" class="dropzone-form-button">
|
||||
<span><?= Yii::t('app', 'Загрузить изображение') ?></span>
|
||||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.3333 14.6667C15.6345 14.6667 17.5 12.8012 17.5 10.5C17.5 8.43079 15.9917 6.71387 14.0145 6.38874C14.0113 6.38821 14.0086 6.38583 14.0078 6.38267C13.4802 4.43373 11.6992 3 9.58333 3C7.05203 3 5 5.05203 5 7.58333C5 7.75477 5.00941 7.924 5.02775 8.09055C5.0282 8.0947 5.0255 8.09856 5.02145 8.09957C3.57289 8.4621 2.5 9.77242 2.5 11.3333C2.5 13.1743 3.99238 14.6667 5.83333 14.6667H6.66667M10 8.83726V18M10 8.83726L7.5 11.3333M10 8.83726L12.5 11.3333" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="file" id="dropzone-file-input" name="file" accept="image/jpeg,image/png,image/jpg,image/webp,image/heic,.heic" style="display: none;">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const form = {
|
||||
el: document.getElementById('dropzone-form'),
|
||||
titleEl: document.getElementById('form-title'),
|
||||
|
||||
hide() {
|
||||
window.scrollTo(0, 0);
|
||||
window.scrollTo({ top: 0, behavior: 'instant' });
|
||||
if (this.el) this.el.style.display = 'none';
|
||||
},
|
||||
show() {
|
||||
if (this.el) this.el.style.display = '';
|
||||
},
|
||||
setTitle(html) {
|
||||
if (this.titleEl) this.titleEl.innerHTML = html;
|
||||
}
|
||||
};
|
||||
|
||||
const utmData = <?= $utmJson ?>;
|
||||
const redirectUrlBase = <?= json_encode($redirectUrlBase, JSON_UNESCAPED_UNICODE) ?>;
|
||||
const redirectUrlUtm = <?= json_encode($redirectUrlUtm, JSON_UNESCAPED_UNICODE) ?>;
|
||||
const createApiUrl = <?= json_encode($createApiUrl, JSON_UNESCAPED_UNICODE) ?>;
|
||||
|
||||
const errorSizeTpl = <?= json_encode($errorSizeMessage, JSON_UNESCAPED_UNICODE) ?>;
|
||||
const genericErrorMsg = <?= json_encode($errorMessage, JSON_UNESCAPED_UNICODE) ?>;
|
||||
|
||||
(function initNativeUploader() {
|
||||
const formEl = document.getElementById('dropzone-form');
|
||||
const fileInput = document.getElementById('dropzone-file-input');
|
||||
if (!formEl || !fileInput) return;
|
||||
|
||||
let columnsSwapped = false;
|
||||
|
||||
formEl.addEventListener('click', (e) => {
|
||||
if (e.target !== fileInput) {
|
||||
fileInput.click();
|
||||
}
|
||||
});
|
||||
|
||||
['dragenter', 'dragover'].forEach(eventName => {
|
||||
formEl.addEventListener(eventName, (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
formEl.classList.add('dragover');
|
||||
});
|
||||
});
|
||||
|
||||
['dragleave', 'dragend', 'drop'].forEach(eventName => {
|
||||
formEl.addEventListener(eventName, (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
formEl.classList.remove('dragover');
|
||||
});
|
||||
});
|
||||
|
||||
formEl.addEventListener('drop', (e) => {
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
handleSelectedFile(files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', () => {
|
||||
if (fileInput.files && fileInput.files.length > 0) {
|
||||
handleSelectedFile(fileInput.files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
function swapColumns() {
|
||||
if (columnsSwapped) return;
|
||||
const col5 = document.querySelector('.col-md-5');
|
||||
const col7 = document.querySelector('.col-md-7');
|
||||
if (col5 && col7) {
|
||||
const temp = document.createElement('div');
|
||||
col5.replaceWith(temp);
|
||||
col7.replaceWith(col5);
|
||||
temp.replaceWith(col7);
|
||||
columnsSwapped = true;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelectedFile(file) {
|
||||
const maxSide = 6000;
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
const img = new Image();
|
||||
|
||||
img.onload = () => {
|
||||
const width = img.naturalWidth;
|
||||
const height = img.naturalHeight;
|
||||
|
||||
if (width > maxSide || height > maxSide) {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
fileInput.value = '';
|
||||
|
||||
const msg = errorSizeTpl
|
||||
.replace('{width}', width)
|
||||
.replace('{height}', height);
|
||||
|
||||
form.show();
|
||||
if (typeof preview !== 'undefined' && preview) {
|
||||
const col7 = document.querySelector('.col-md-7');
|
||||
if (col7) col7.appendChild(preview.el);
|
||||
preview.show();
|
||||
preview.showError(msg);
|
||||
}
|
||||
if (typeof process !== 'undefined' && process) process.hide();
|
||||
if (typeof examples !== 'undefined' && examples) examples.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
form.hide();
|
||||
swapColumns();
|
||||
|
||||
if (typeof preview !== 'undefined' && preview) {
|
||||
const col7 = document.querySelector('.col-md-7');
|
||||
if (col7) col7.appendChild(preview.el);
|
||||
preview.hide();
|
||||
preview.setSource(objectUrl);
|
||||
preview.show();
|
||||
}
|
||||
|
||||
if (typeof process !== 'undefined' && process) {
|
||||
const col5 = document.querySelector('.col-md-5');
|
||||
if (col5) col5.appendChild(process.el);
|
||||
process.setFilename(file.name);
|
||||
process.setSize(file.size);
|
||||
process.setDimensions(width, height);
|
||||
process.setStage(0);
|
||||
process.setStatus(0);
|
||||
process.show();
|
||||
}
|
||||
|
||||
if (typeof examples !== 'undefined' && examples) {
|
||||
examples.hide();
|
||||
}
|
||||
|
||||
uploadFile(file);
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
if (typeof preview !== 'undefined' && preview) {
|
||||
preview.show();
|
||||
preview.showError(genericErrorMsg);
|
||||
}
|
||||
};
|
||||
|
||||
img.src = objectUrl;
|
||||
}
|
||||
|
||||
function uploadFile(file) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const actionUrl = createApiUrl || formEl.getAttribute('action') || '/api/images/public/create';
|
||||
xhr.open('POST', actionUrl, true);
|
||||
xhr.withCredentials = true;
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
|
||||
xhr.upload.onprogress = (e) => {
|
||||
if (e.lengthComputable && typeof process !== 'undefined' && process) {
|
||||
const progress = Math.round((e.loaded / e.total) * 100);
|
||||
process.setStatus(progress);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(xhr.responseText);
|
||||
} catch (e) {
|
||||
showUploadError(genericErrorMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
const imageId = response?.id || response?.data?.id || response?.image?.id || response?.image_id;
|
||||
if (!imageId) {
|
||||
showUploadError(genericErrorMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof process !== 'undefined' && process) {
|
||||
process.setStatus(100);
|
||||
process.setStage(1);
|
||||
}
|
||||
|
||||
// Build redirect target URL: $redirect_url_base . $imageId . ($redirect_url_utm ? '&' . $redirect_url_utm : '')
|
||||
let targetUrl = redirectUrlBase + encodeURIComponent(imageId);
|
||||
if (redirectUrlUtm) {
|
||||
// const sep = targetUrl.includes('?') ? '&' : '?';
|
||||
targetUrl += '&' + redirectUrlUtm;
|
||||
}
|
||||
|
||||
window.location.href = targetUrl;
|
||||
} else {
|
||||
showUploadError(genericErrorMsg);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
showUploadError(genericErrorMsg);
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
}
|
||||
|
||||
function showUploadError(msg) {
|
||||
if (typeof process !== 'undefined' && process) process.hide();
|
||||
form.show();
|
||||
if (typeof preview !== 'undefined' && preview) preview.showError(msg);
|
||||
fileInput.value = '';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,99 @@
|
||||
<style>
|
||||
.image-loading {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
border-radius: 24px;
|
||||
height: 100%;
|
||||
place-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.image-loading-progress::before {
|
||||
background-image: var(--source);
|
||||
filter: blur(15px) brightness(0.7);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.image-loading-progress #source {
|
||||
/*height: 100%;*/
|
||||
z-index: 3;
|
||||
position: absolute;
|
||||
filter: brightness(0.7);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.image-loading-error {
|
||||
display: none;
|
||||
}
|
||||
.image-loading-error.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image-loading-progress:has(.image-loading-error.show) .image-loading-icon {
|
||||
opacity: 0;
|
||||
}
|
||||
.image-loading-progress:has(.image-loading-error.show) #source {
|
||||
filter: brightness(0.4);
|
||||
}
|
||||
.image-loading-progress:has(.image-loading-error.show):before {
|
||||
filter: blur(15px) brightness(0.4);
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.image-loading-progress {
|
||||
height: 35dvh !important;
|
||||
}
|
||||
.image-loading-progress #source {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="preview" class="image-loading image-loading-progress" style="--source: url('<?= $source ?? '' ?>'); display: <?= ($show ?? true) ? '' : 'none'; ?>" >
|
||||
<img id="source" src="<?= $source ?? '' ?>" style="height: 100%; width: 100%; object-fit: contain; flex-shrink: 0;">
|
||||
|
||||
<div class="image-loading-icon">
|
||||
<img class="sandglass" src="<?= $assetsUriPrefix ?>/images/sandglass.svg" alt="">
|
||||
<img class="spinner" src="<?= $assetsUriPrefix ?>/images/spinner.svg" alt="">
|
||||
</div>
|
||||
|
||||
<div id="upscale-error-overlay" class="image-loading-error">
|
||||
<div class="image-loading-error-icon">
|
||||
<img src="<?= $assetsUriPrefix ?>/images/confused.svg" alt="">
|
||||
</div>
|
||||
<div class="image-loading-error-title"><?= Yii::t('app', 'Извините, что-то пошло не так') ?></div>
|
||||
<div id="upscale-error-message" class="image-loading-error-text"><?= Yii::t('app', 'Максимальный допустимый размер файла 20 Мб') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const preview = {
|
||||
el: document.getElementById('preview'),
|
||||
|
||||
upscaleErrorOverlay: document.getElementById('upscale-error-overlay'),
|
||||
upscaleErrorMessage: document.getElementById('upscale-error-message'),
|
||||
sourceEl: document.getElementById('source'),
|
||||
|
||||
hide() {
|
||||
this.el.style.display = 'none';
|
||||
},
|
||||
show() {
|
||||
this.el.style.display = '';
|
||||
this.upscaleErrorOverlay.classList.remove('show');
|
||||
},
|
||||
setSource(url) {
|
||||
this.sourceEl.src = url;
|
||||
this.el.style.setProperty('--source', "url('" + url + "')");
|
||||
},
|
||||
setDimensions(w, h) {
|
||||
console.log('setSize', w, h);
|
||||
},
|
||||
setStage(stage) {
|
||||
|
||||
},
|
||||
showError(message) {
|
||||
this.upscaleErrorMessage.innerText = message || 'Ошибка обработки запроса.';
|
||||
this.upscaleErrorOverlay.classList.add('show');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
$labels = [
|
||||
'stage0' => \Yii::t('app', 'Загружаем файл на сервер'),
|
||||
'stage1' => \Yii::t('app', 'Улучшаем и увеличиваем'),
|
||||
'stage2' => \Yii::t('app', 'Загружаем полученный результат'),
|
||||
];
|
||||
$labelsJson = json_encode($labels, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
?>
|
||||
|
||||
<div id="process" class="dropzone-form dropzone-form-loading gradient-dashed-border" style="display: <?= ($show ?? true) ? '' : 'none'; ?>">
|
||||
<div class="dropzone-form-inner">
|
||||
<div id="process-title" class="dropzone-form-text"><?= \Yii::t('app', 'Загружаем файл на сервер') ?></div>
|
||||
<div id="process-filename" class="dropzone-form-filename"><?= htmlspecialchars($filename ?? '', ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<div class="dropzone-form-loading-info">
|
||||
<div class="dropzone-form-loading-info-col">
|
||||
<div class="dropzone-form-loading-info-item">
|
||||
<div class="dropzone-form-loading-info-icon">
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 4.5V6.5H4M6 20.5V18.5H4M18 4.5V6.5H20M18 20.5V18.5H20M7 7.5H17V17.5H7V7.5Z" stroke="currentColor" stroke-linecap="square"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="dropzone-form-loading-info-text">
|
||||
<span id="origin_width"><?= htmlspecialchars($origin_width ?? '', ENT_QUOTES, 'UTF-8') ?></span> x <span id="origin_height"><?= htmlspecialchars($origin_height ?? '', ENT_QUOTES, 'UTF-8') ?></span> <span>px</span></div>
|
||||
</div>
|
||||
<div class="dropzone-form-loading-info-item">
|
||||
<div class="dropzone-form-loading-info-icon">
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 17.5C18.7614 17.5 21 15.2614 21 12.5C21 10.017 19.19 7.95665 16.8174 7.56649C16.8136 7.56585 16.8104 7.56299 16.8094 7.5592C16.1762 5.22047 14.039 3.5 11.5 3.5C8.46243 3.5 6 5.96243 6 9C6 9.20572 6.01129 9.4088 6.0333 9.60866C6.03385 9.61364 6.0306 9.61827 6.02574 9.61949C4.28747 10.0545 3 11.6269 3 13.5C3 15.7091 4.79086 17.5 7 17.5H8M12 10.5047V21.5M12 10.5047L9 13.5M12 10.5047L15 13.5" stroke="currentColor" stroke-linecap="square"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="dropzone-form-loading-info-text">
|
||||
<span id="origin_size"><?= htmlspecialchars($origin_size ?? '', ENT_QUOTES, 'UTF-8') ?></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropzone-form-loading-info-col dropzone-form-loading-info-arrow">
|
||||
<svg width="37" height="24" viewBox="0 0 37 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 17L12 12L7 7M13 17L18 12L13 7" stroke="currentColor" stroke-linecap="square"/>
|
||||
<path d="M20 17L25 12L20 7M26 17L31 12L26 7" stroke="currentColor" stroke-linecap="square"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="dropzone-form-loading-info-col">
|
||||
<div class="dropzone-form-loading-info-item">
|
||||
<div class="dropzone-form-loading-info-icon">
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 4.5V6.5H4M6 20.5V18.5H4M18 4.5V6.5H20M18 20.5V18.5H20M7 7.5H17V17.5H7V7.5Z" stroke="currentColor" stroke-linecap="square"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="dropzone-form-loading-info-text">
|
||||
<span id="upscale_width"><?= htmlspecialchars($upscale_width ?? '', ENT_QUOTES, 'UTF-8') ?></span> x <span id="upscale_height"><?= htmlspecialchars($upscale_height ?? '', ENT_QUOTES, 'UTF-8') ?></span> <span>px</span></div>
|
||||
</div>
|
||||
<div class="dropzone-form-loading-info-item">
|
||||
<div class="dropzone-form-loading-info-icon">
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 17.5C18.7614 17.5 21 15.2614 21 12.5C21 10.017 19.19 7.95665 16.8174 7.56649C16.8136 7.56585 16.8104 7.56299 16.8094 7.5592C16.1762 5.22047 14.039 3.5 11.5 3.5C8.46243 3.5 6 5.96243 6 9C6 9.20572 6.01129 9.4088 6.0333 9.60866C6.03385 9.61364 6.0306 9.61827 6.02574 9.61949C4.28747 10.0545 3 11.6269 3 13.5C3 15.7091 4.79086 17.5 7 17.5H8M12 10.5047V21.5M12 10.5047L9 13.5M12 10.5047L15 13.5" stroke="currentColor" stroke-linecap="square"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="dropzone-form-loading-info-text">
|
||||
≈ <span id="upscale_size"><?= htmlspecialchars($upscale_size ?? '', ENT_QUOTES, 'UTF-8') ?></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dropzone-form-loading-progress">
|
||||
<div class="progress">
|
||||
<div class="progress-label">0%</div>
|
||||
<div class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dropzone-form-note"><?= Yii::t('app', 'Увеличение 4х доступно после регистрации') ?></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const labels = <?= $labelsJson ?>;
|
||||
|
||||
const process = {
|
||||
el: document.getElementById('process'),
|
||||
|
||||
barEl: document.querySelector('.progress-bar'),
|
||||
labelEl: document.querySelector('.progress-label'),
|
||||
|
||||
titleEl: document.getElementById('process-title'),
|
||||
filenameEl: document.getElementById('process-filename'),
|
||||
|
||||
originSizeEl: document.getElementById('origin_size'),
|
||||
upscaleSizeEl: document.getElementById('upscale_size'),
|
||||
originWidthEl: document.getElementById('origin_width'),
|
||||
originHeightEl: document.getElementById('origin_height'),
|
||||
upscaleWidthEl: document.getElementById('upscale_width'),
|
||||
upscaleHeightEl: document.getElementById('upscale_height'),
|
||||
|
||||
status: {
|
||||
current: 0,
|
||||
stage: 0,
|
||||
},
|
||||
|
||||
hide() {
|
||||
if (this.el) this.el.style.display = 'none';
|
||||
},
|
||||
show() {
|
||||
if (this.el) this.el.style.display = '';
|
||||
},
|
||||
setFilename(filename) {
|
||||
if (this.filenameEl) this.filenameEl.innerText = filename;
|
||||
},
|
||||
setSize(size) {
|
||||
if (this.originSizeEl) this.originSizeEl.innerText = this.formatB(size);
|
||||
if (this.upscaleSizeEl) this.upscaleSizeEl.innerText = this.formatB(size * 3);
|
||||
},
|
||||
setDimensions(w, h) {
|
||||
if (this.originWidthEl) this.originWidthEl.innerText = w;
|
||||
if (this.originHeightEl) this.originHeightEl.innerText = h;
|
||||
if (this.upscaleWidthEl) this.upscaleWidthEl.innerText = w * 2;
|
||||
if (this.upscaleHeightEl) this.upscaleHeightEl.innerText = h * 2;
|
||||
},
|
||||
setStage(stage) {
|
||||
if (!this.titleEl) return;
|
||||
this.status.stage = stage;
|
||||
this.titleEl.innerText = labels['stage' + stage] || '...';
|
||||
},
|
||||
setStatus(status) {
|
||||
const val = Math.min(100, Math.max(0, status));
|
||||
this.status.current = val;
|
||||
if (this.barEl) this.barEl.style.width = val + '%';
|
||||
if (this.labelEl) this.labelEl.innerText = val + '%';
|
||||
},
|
||||
|
||||
formatB(bytes) {
|
||||
if (bytes === 0 || isNaN(bytes)) return '0 B';
|
||||
const units = ['B', 'Kb', 'Mb', 'Gb', 'Tb'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
const formattedValue = (bytes / Math.pow(1024, i)).toFixed(2);
|
||||
return parseFloat(formattedValue) + ' ' + units[i];
|
||||
}
|
||||
};
|
||||
</script>
|
||||
Reference in new issue
Block a user