Update structure
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
// upload.php
|
// upload.php
|
||||||
|
|
||||||
// Налаштування папок (Ustawienia katalogów)
|
// Ustawienia katalogów
|
||||||
$uploadDir = 'product_uploads/' . $_POST['product_id'] . '/';
|
$uploadDir = 'product_uploads/' . $_POST['product_id'] . '/';
|
||||||
$originalDir = $uploadDir . 'originals/';
|
$originalDir = $uploadDir . 'originals/';
|
||||||
$croppedDir = $uploadDir . 'cropped/';
|
$croppedDir = $uploadDir . 'cropped/';
|
||||||
|
|
||||||
// Створюємо папки, якщо немає (Tworzenie katalogów, jeśli nie istnieją)
|
// Tworzenie katalogów, jeśli nie istnieją
|
||||||
if (!file_exists($originalDir)) mkdir($originalDir, 0777, true);
|
if (!file_exists($originalDir)) mkdir($originalDir, 0777, true);
|
||||||
if (!file_exists($croppedDir)) mkdir($croppedDir, 0777, true);
|
if (!file_exists($croppedDir)) mkdir($croppedDir, 0777, true);
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Відповідь клієнту (Odpowiedź)
|
// Odpowiedź
|
||||||
if ($successOriginal && $successCropped) {
|
if ($successOriginal && $successCropped) {
|
||||||
echo "<div style='color:green; border: 1px solid green; padding: 10px;'>";
|
echo "<div style='color:green; border: 1px solid green; padding: 10px;'>";
|
||||||
echo "<strong>Zapisano pomyślnie!</strong><br>";
|
echo "<strong>Zapisano pomyślnie!</strong><br>";
|
||||||
@@ -40,9 +40,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
echo "Wykadrowane: " . $croppedPath;
|
echo "Wykadrowane: " . $croppedPath;
|
||||||
echo "</div>";
|
echo "</div>";
|
||||||
|
|
||||||
// Тут можна додати SQL запит до БД (Tutaj można dodać zapytanie SQL do bazy danych)
|
// Tutaj można dodać zapytanie SQL do bazy danych)
|
||||||
// Np.: INSERT INTO product_images (original, cropped) VALUES ('$originalPath', '$croppedPath');
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
echo "<div style='color:red;'>Wystąpił błąd podczas zapisywania plików.</div>";
|
echo "<div style='color:red;'>Wystąpił błąd podczas zapisywania plików.</div>";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,180 +3,239 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Przesyłanie i kadrowanie zdjęć</title>
|
<title>Edytor Zdjęć Produktowych</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body { font-family: sans-serif; padding: 20px; }
|
/* --- GŁÓWNE STYLE --- */
|
||||||
|
body { font-family: sans-serif; padding: 20px; color: #333; }
|
||||||
|
h2 { margin-bottom: 20px; }
|
||||||
|
|
||||||
|
/* --- WRAPPER CROPPERA --- */
|
||||||
.cropper-wrapper {
|
.cropper-wrapper {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 500px;
|
height: 500px;
|
||||||
background: #333;
|
background: #222;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 15px;
|
||||||
display: none;
|
border-radius: 8px;
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 6px;
|
display: none; /* Ukryty domyślnie */
|
||||||
user-select: none;
|
user-select: none;
|
||||||
touch-action: none; /* важливо для drag на мобільних */
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
cropper-canvas { width: 100%; height: 100%; }
|
cropper-canvas { width: 100%; height: 100%; }
|
||||||
|
|
||||||
.btn {
|
/* Stała ramka wyboru */
|
||||||
padding: 10px 16px;
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
.btn:disabled { background-color: #ccc; cursor: not-allowed; }
|
|
||||||
|
|
||||||
/* Червона рамка і не блокує drag */
|
|
||||||
cropper-selection {
|
cropper-selection {
|
||||||
outline: 3px solid red !important;
|
outline: 2px solid #e74c3c !important; /* Czerwona ramka */
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- OVERLAY I UCHWYTY (HANDLES) --- */
|
||||||
|
.photo-handles {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none; /* Kluczowe: przepuszcza kliki do obrazka */
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-outline {
|
||||||
|
position: absolute;
|
||||||
|
inset: 6px;
|
||||||
|
border: 2px dashed rgba(255, 255, 255, 0.6);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle {
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #007bff;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.4);
|
||||||
|
pointer-events: auto; /* Tylko kropki są klikalne */
|
||||||
|
transition: transform 0.1s;
|
||||||
|
}
|
||||||
|
.handle:active { transform: scale(1.2); }
|
||||||
|
|
||||||
|
/* Pozycje uchwytów */
|
||||||
|
.handle.tl { left: 10px; top: 10px; cursor: nwse-resize; }
|
||||||
|
.handle.tr { right: 10px; top: 10px; cursor: nesw-resize; }
|
||||||
|
.handle.bl { left: 10px; bottom: 10px; cursor: nesw-resize; }
|
||||||
|
.handle.br { right: 10px; bottom: 10px; cursor: nwse-resize; }
|
||||||
|
|
||||||
|
/* --- PASEK NARZĘDZI --- */
|
||||||
#toolbar {
|
#toolbar {
|
||||||
display: none;
|
display: none;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 10px 0 15px 0;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
#toolbar.show { display: flex; }
|
#toolbar.show { display: flex; }
|
||||||
#toolbar .btn { padding: 8px 14px; font-size: 16px; }
|
|
||||||
|
|
||||||
#hint { color: #444; font-size: 14px; margin-left: 6px; }
|
.btn {
|
||||||
.chip {
|
padding: 8px 16px;
|
||||||
padding: 6px 10px;
|
background-color: #007bff;
|
||||||
border: 1px solid #ddd;
|
color: white;
|
||||||
border-radius: 999px;
|
border: none;
|
||||||
font-size: 13px;
|
cursor: pointer;
|
||||||
background: #f7f7f7;
|
font-size: 15px;
|
||||||
}
|
|
||||||
|
|
||||||
/* ====== РУЧКИ "RESIZE FOTO" (overlay) ====== */
|
|
||||||
.photo-handles {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
pointer-events: none; /* включимо лише коли активні */
|
|
||||||
z-index: 20;
|
|
||||||
}
|
|
||||||
.photo-handles.active {
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
.handle {
|
|
||||||
position: absolute;
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #fff;
|
|
||||||
border: 2px solid #007bff;
|
|
||||||
box-shadow: 0 1px 4px rgba(0,0,0,0.35);
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
.handle.nw { left: 10px; top: 10px; cursor: nwse-resize; }
|
|
||||||
.handle.ne { right: 10px; top: 10px; cursor: nesw-resize; }
|
|
||||||
.handle.sw { left: 10px; bottom: 10px; cursor: nesw-resize; }
|
|
||||||
.handle.se { right: 10px; bottom: 10px; cursor: nwse-resize; }
|
|
||||||
|
|
||||||
/* маленька рамка для “фото-режиму” */
|
|
||||||
.photo-outline {
|
|
||||||
position: absolute;
|
|
||||||
inset: 6px;
|
|
||||||
border: 2px dashed rgba(255,255,255,0.5);
|
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
pointer-events: none;
|
transition: background 0.2s;
|
||||||
}
|
}
|
||||||
|
.btn:hover { background-color: #0056b3; }
|
||||||
|
.btn:disabled { background-color: #ccc; cursor: not-allowed; }
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hint { font-size: 13px; color: #666; margin-left: auto; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<h2>Przesyłanie zdjęcia produktu</h2>
|
<h2>Przesyłanie zdjęcia produktu</h2>
|
||||||
|
|
||||||
<div style="margin-bottom: 20px;">
|
<div style="margin-bottom: 20px;">
|
||||||
<label for="inputImage">Wybierz zdjęcie:</label>
|
<label class="btn" for="inputImage">📂 Wybierz zdjęcie</label>
|
||||||
<input type="file" id="inputImage" accept="image/*" />
|
<input type="file" id="inputImage" accept="image/*" style="display:none" />
|
||||||
|
<span id="fileName" style="margin-left: 10px; color:#555;"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cropper-wrapper" id="cropperWrapper">
|
<div class="cropper-wrapper" id="cropperWrapper">
|
||||||
<cropper-canvas id="my-cropper" background>
|
<cropper-canvas id="my-cropper" background>
|
||||||
<cropper-image
|
<cropper-image
|
||||||
id="cropperImage"
|
id="cropperImage"
|
||||||
initial-center-size="cover"
|
initial-center-size="cover"
|
||||||
translatable
|
translatable
|
||||||
scalable
|
scalable
|
||||||
></cropper-image>
|
></cropper-image>
|
||||||
|
|
||||||
<!-- Drag obrazu -->
|
|
||||||
<cropper-handle action="move" plain></cropper-handle>
|
<cropper-handle action="move" plain></cropper-handle>
|
||||||
|
|
||||||
<!-- Stała ramka (format admina ustawiamy w JS) -->
|
|
||||||
<cropper-selection id="cropperSelection" initial-coverage="0.6"></cropper-selection>
|
<cropper-selection id="cropperSelection" initial-coverage="0.6"></cropper-selection>
|
||||||
</cropper-canvas>
|
</cropper-canvas>
|
||||||
|
|
||||||
<!-- Overlay: “resize foto za rogi” -->
|
<div class="photo-handles" id="photoHandles">
|
||||||
<div class="photo-handles active" id="photoHandles">
|
|
||||||
<div class="photo-outline"></div>
|
<div class="photo-outline"></div>
|
||||||
<div class="handle nw" data-corner="nw" title="Zmień rozmiar zdjęcia"></div>
|
<div class="handle tl" data-pos="top" title="Zmień rozmiar"></div>
|
||||||
<div class="handle ne" data-corner="ne" title="Zmień rozmiar zdjęcia"></div>
|
<div class="handle tr" data-pos="top" title="Zmień rozmiar"></div>
|
||||||
<div class="handle sw" data-corner="sw" title="Zmień rozmiar zdjęcia"></div>
|
<div class="handle bl" data-pos="bottom" title="Zmień rozmiar"></div>
|
||||||
<div class="handle se" data-corner="se" title="Zmień rozmiar zdjęcia"></div>
|
<div class="handle br" data-pos="bottom" title="Zmień rozmiar"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="toolbar">
|
<div id="toolbar">
|
||||||
<button type="button" class="btn" id="zoomIn">+</button>
|
<button type="button" class="btn" id="btnZoomIn">+</button>
|
||||||
<button type="button" class="btn" id="zoomOut">-</button>
|
<button type="button" class="btn" id="btnZoomOut">-</button>
|
||||||
<button type="button" class="btn" id="resetImg">Reset</button>
|
<button type="button" class="btn" id="btnReset">Reset</button>
|
||||||
<span class="chip" id="formatInfo"></span>
|
<span class="chip" id="formatInfo"></span>
|
||||||
<span id="hint">Przesuwaj zdjęcie myszą/palcem, zoom kółkiem lub złap za rogi</span>
|
<span id="hint">Mysz/Dotyk: Przesuń | Kółko/Rogi: Zoom</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="btnUpload" class="btn" style="display:none;">Przytnij i Zapisz</button>
|
<button id="btnUpload" class="btn" style="display:none; width: 100%; padding: 12px;">Przytnij i Zapisz</button>
|
||||||
<div id="result" style="margin-top: 20px;"></div>
|
<div id="result" style="margin-top: 20px;"></div>
|
||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
<script src="https://unpkg.com/cropperjs@2.1.0/dist/cropper.js"></script>
|
<script src="https://unpkg.com/cropperjs@2.1.0/dist/cropper.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
/**
|
||||||
const $inputImage = $("#inputImage");
|
* KONFIGURACJA
|
||||||
const $cropperWrapper = $("#cropperWrapper");
|
* Wszystkie stałe w jednym miejscu dla łatwej edycji.
|
||||||
const $btnUpload = $("#btnUpload");
|
*/
|
||||||
const $toolbar = $("#toolbar");
|
const CONFIG = {
|
||||||
|
dims: {
|
||||||
|
adminW: 8, // cm
|
||||||
|
adminH: 12, // cm
|
||||||
|
outputW: 800, // px
|
||||||
|
outputH: 1200 // px
|
||||||
|
},
|
||||||
|
zoomSensitivity: 0.004,
|
||||||
|
selectors: {
|
||||||
|
input: '#inputImage',
|
||||||
|
fileName: '#fileName',
|
||||||
|
wrapper: '#cropperWrapper',
|
||||||
|
uploadBtn: '#btnUpload',
|
||||||
|
toolbar: '#toolbar',
|
||||||
|
result: '#result',
|
||||||
|
cropperCanvas: 'my-cropper',
|
||||||
|
cropperImage: 'cropperImage',
|
||||||
|
cropperSelection: 'cropperSelection',
|
||||||
|
handles: '.handle'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const cropperCanvas = document.getElementById("my-cropper");
|
/**
|
||||||
const cropperSelection = document.getElementById("cropperSelection");
|
* STATE
|
||||||
const cropperImage = document.getElementById("cropperImage");
|
* Przechowywanie stanu aplikacji
|
||||||
|
*/
|
||||||
|
const State = {
|
||||||
|
isResizing: false,
|
||||||
|
startY: 0,
|
||||||
|
isInverted: false // Czy używamy dolnych uchwytów (odwrócona logika)
|
||||||
|
};
|
||||||
|
|
||||||
const photoHandles = document.getElementById("photoHandles");
|
/**
|
||||||
|
* DOM MANAGER
|
||||||
|
* Cache'owanie elementów jQuery i JS
|
||||||
|
*/
|
||||||
|
const DOM = {
|
||||||
|
$input: $(CONFIG.selectors.input),
|
||||||
|
$name: $(CONFIG.selectors.fileName),
|
||||||
|
$wrapper: $(CONFIG.selectors.wrapper),
|
||||||
|
$uploadBtn: $(CONFIG.selectors.uploadBtn),
|
||||||
|
$toolbar: $(CONFIG.selectors.toolbar),
|
||||||
|
$result: $(CONFIG.selectors.result),
|
||||||
|
elCanvas: document.getElementById(CONFIG.selectors.cropperCanvas),
|
||||||
|
elImage: document.getElementById(CONFIG.selectors.cropperImage),
|
||||||
|
elSelection: document.getElementById(CONFIG.selectors.cropperSelection),
|
||||||
|
elsHandles: document.querySelectorAll(CONFIG.selectors.handles)
|
||||||
|
};
|
||||||
|
|
||||||
// ADMIN (z serwera)
|
/**
|
||||||
const ADMIN_W_CM = 8;
|
* GŁÓWNY KONTROLER APLIKACJI
|
||||||
const ADMIN_H_CM = 12;
|
*/
|
||||||
|
const App = {
|
||||||
|
init() {
|
||||||
|
this.renderInfo();
|
||||||
|
this.bindGlobalEvents();
|
||||||
|
this.bindZoomHandlers();
|
||||||
|
},
|
||||||
|
|
||||||
// wynik px
|
// Wyświetlenie formatu na toolbarze
|
||||||
const OUT_W_PX = 800;
|
renderInfo() {
|
||||||
const OUT_H_PX = 1200;
|
const { adminW, adminH } = CONFIG.dims;
|
||||||
|
$('#formatInfo').text(`Format: ${adminW}×${adminH} cm`);
|
||||||
|
},
|
||||||
|
|
||||||
$("#formatInfo").text(`Format: ${ADMIN_W_CM}×${ADMIN_H_CM} cm`);
|
// Obliczanie i centrowanie ramki wyboru
|
||||||
|
centerSelection() {
|
||||||
|
const { adminW, adminH } = CONFIG.dims;
|
||||||
|
const ratio = adminW / adminH;
|
||||||
|
|
||||||
|
DOM.elSelection.aspectRatio = ratio;
|
||||||
|
|
||||||
function centerFixedSelection() {
|
const rect = DOM.elCanvas.getBoundingClientRect();
|
||||||
const ratio = ADMIN_W_CM / ADMIN_H_CM;
|
|
||||||
cropperSelection.aspectRatio = ratio;
|
|
||||||
|
|
||||||
const rect = cropperCanvas.getBoundingClientRect();
|
|
||||||
if (!rect.width || !rect.height) return;
|
if (!rect.width || !rect.height) return;
|
||||||
|
|
||||||
|
// Maksymalnie 70% obszaru
|
||||||
const maxW = rect.width * 0.7;
|
const maxW = rect.width * 0.7;
|
||||||
const maxH = rect.height * 0.7;
|
const maxH = rect.height * 0.7;
|
||||||
|
|
||||||
let selW, selH;
|
let selW, selH;
|
||||||
|
|
||||||
|
// Dopasowanie do węższego wymiaru
|
||||||
if (maxW / maxH > ratio) {
|
if (maxW / maxH > ratio) {
|
||||||
selH = maxH;
|
selH = maxH;
|
||||||
selW = selH * ratio;
|
selW = selH * ratio;
|
||||||
@@ -188,136 +247,156 @@
|
|||||||
const x = (rect.width - selW) / 2;
|
const x = (rect.width - selW) / 2;
|
||||||
const y = (rect.height - selH) / 2;
|
const y = (rect.height - selH) / 2;
|
||||||
|
|
||||||
cropperSelection.$change(x, y, selW, selH, ratio);
|
DOM.elSelection.$change(x, y, selW, selH, ratio);
|
||||||
}
|
},
|
||||||
|
|
||||||
function bindToolbar() {
|
// Obsługa zdarzeń globalnych (pliki, przyciski)
|
||||||
$("#zoomIn").off("click").on("click", () => cropperImage.$zoom(0.1));
|
bindGlobalEvents() {
|
||||||
$("#zoomOut").off("click").on("click", () => cropperImage.$zoom(-0.1));
|
// 1. Wczytanie pliku
|
||||||
$("#resetImg").off("click").on("click", () => cropperImage.$center("cover"));
|
DOM.$input.on('change', function() {
|
||||||
}
|
const [file] = this.files;
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
// Wheel zoom
|
if (!/^image\/\w+$/.test(file.type)) {
|
||||||
$cropperWrapper.on("wheel", function (e) {
|
alert("To nie jest plik graficzny!");
|
||||||
e.preventDefault();
|
return;
|
||||||
const delta = e.originalEvent.deltaY;
|
}
|
||||||
cropperImage.$zoom(delta < 0 ? 0.08 : -0.08);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ===== "Resize foto za rogi" через zoom =====
|
DOM.$name.text(file.name);
|
||||||
let isResizingPhoto = false;
|
DOM.elImage.src = URL.createObjectURL(file);
|
||||||
let startY = 0;
|
|
||||||
|
|
||||||
function onPointerDown(e) {
|
// Pokaż UI
|
||||||
isResizingPhoto = true;
|
DOM.$wrapper.fadeIn();
|
||||||
startY = e.clientY;
|
DOM.$uploadBtn.fadeIn();
|
||||||
e.preventDefault();
|
DOM.$toolbar.addClass('show');
|
||||||
e.stopPropagation();
|
|
||||||
document.addEventListener("pointermove", onPointerMove, { passive: false });
|
|
||||||
document.addEventListener("pointerup", onPointerUp, { passive: false, once: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPointerMove(e) {
|
// Inicjalizacja croppera
|
||||||
if (!isResizingPhoto) return;
|
DOM.elImage.$ready(() => {
|
||||||
|
DOM.elImage.$center('cover');
|
||||||
const dy = e.clientY - startY;
|
App.centerSelection();
|
||||||
startY = e.clientY;
|
});
|
||||||
|
|
||||||
// dy < 0 => zoom in, dy > 0 => zoom out
|
|
||||||
const step = (-dy) * 0.004; // czułość
|
|
||||||
if (step !== 0) cropperImage.$zoom(step);
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPointerUp() {
|
|
||||||
isResizingPhoto = false;
|
|
||||||
document.removeEventListener("pointermove", onPointerMove);
|
|
||||||
}
|
|
||||||
|
|
||||||
// podepnij do 4 uchwytów
|
|
||||||
photoHandles.querySelectorAll(".handle").forEach((el) => {
|
|
||||||
el.addEventListener("pointerdown", onPointerDown, { passive: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Upload
|
|
||||||
$inputImage.change(function () {
|
|
||||||
const files = this.files;
|
|
||||||
if (!files || !files.length) return;
|
|
||||||
|
|
||||||
const file = files[0];
|
|
||||||
if (!/^image\/\w+$/.test(file.type)) {
|
|
||||||
alert("Proszę wybrać plik obrazu.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadedImageURL = URL.createObjectURL(file);
|
|
||||||
cropperImage.src = uploadedImageURL;
|
|
||||||
|
|
||||||
$cropperWrapper.show();
|
|
||||||
$btnUpload.show();
|
|
||||||
$toolbar.addClass("show");
|
|
||||||
|
|
||||||
bindToolbar();
|
|
||||||
|
|
||||||
cropperImage.$ready(() => {
|
|
||||||
cropperImage.$center("cover");
|
|
||||||
centerFixedSelection();
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("resize", () => {
|
// 2. Toolbar - Przyciski
|
||||||
if ($cropperWrapper.is(":visible")) centerFixedSelection();
|
$('#btnZoomIn').on('click', () => DOM.elImage.$zoom(0.1));
|
||||||
});
|
$('#btnZoomOut').on('click', () => DOM.elImage.$zoom(-0.1));
|
||||||
|
$('#btnReset').on('click', () => DOM.elImage.$center('cover'));
|
||||||
|
|
||||||
// Save
|
// 3. Zoom kółkiem myszy
|
||||||
$btnUpload.click(function () {
|
DOM.$wrapper.on('wheel', (e) => {
|
||||||
$btnUpload.prop("disabled", true).text("Przetwarzanie...");
|
e.preventDefault();
|
||||||
|
const delta = e.originalEvent.deltaY;
|
||||||
|
DOM.elImage.$zoom(delta < 0 ? 0.08 : -0.08);
|
||||||
|
});
|
||||||
|
|
||||||
cropperSelection
|
// 4. Resize okna przeglądarki
|
||||||
.$toCanvas({ width: OUT_W_PX, height: OUT_H_PX })
|
window.addEventListener('resize', () => {
|
||||||
.then((canvas) => {
|
if (DOM.$wrapper.is(':visible')) App.centerSelection();
|
||||||
canvas.toBlob(
|
});
|
||||||
(blob) => {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("original_image", $inputImage[0].files[0]);
|
|
||||||
formData.append("cropped_image", blob, "cropped.jpg");
|
|
||||||
formData.append("product_id", "123");
|
|
||||||
|
|
||||||
formData.append("admin_w_cm", ADMIN_W_CM);
|
// 5. Upload (Zapisz)
|
||||||
formData.append("admin_h_cm", ADMIN_H_CM);
|
DOM.$uploadBtn.on('click', this.handleUpload);
|
||||||
formData.append("out_w_px", OUT_W_PX);
|
},
|
||||||
formData.append("out_h_px", OUT_H_PX);
|
|
||||||
|
|
||||||
$.ajax({
|
// Logika "Ciągnij za rogi by zmienić zoom"
|
||||||
url: "product-img-save.php",
|
bindZoomHandlers() {
|
||||||
method: "POST",
|
const onPointerDown = (e) => {
|
||||||
data: formData,
|
State.isResizing = true;
|
||||||
processData: false,
|
State.startY = e.clientY;
|
||||||
contentType: false,
|
// Sprawdzamy czy to dolny uchwyt (bl, br) używając dataset lub klasy
|
||||||
success: function (response) {
|
State.isInverted = e.target.dataset.pos === 'bottom';
|
||||||
$("#result").html(response);
|
|
||||||
$cropperWrapper.hide();
|
e.preventDefault();
|
||||||
$toolbar.removeClass("show");
|
e.stopPropagation();
|
||||||
$btnUpload.hide().prop("disabled", false).text("Przytnij i Zapisz");
|
|
||||||
$inputImage.val("");
|
document.addEventListener('pointermove', onPointerMove, { passive: false });
|
||||||
},
|
document.addEventListener('pointerup', onPointerUp, { passive: false, once: true });
|
||||||
error: function () {
|
};
|
||||||
alert("Błąd podczas przesyłania.");
|
|
||||||
$btnUpload.prop("disabled", false).text("Przytnij i Zapisz");
|
const onPointerMove = (e) => {
|
||||||
},
|
if (!State.isResizing) return;
|
||||||
});
|
|
||||||
},
|
let dy = e.clientY - State.startY;
|
||||||
"image/jpeg",
|
State.startY = e.clientY;
|
||||||
0.92
|
|
||||||
);
|
// Jeśli dolny uchwyt -> odwracamy znak
|
||||||
|
if (State.isInverted) dy = -dy;
|
||||||
|
|
||||||
|
// Oblicz zoom
|
||||||
|
const step = (-dy) * CONFIG.zoomSensitivity;
|
||||||
|
if (step !== 0) DOM.elImage.$zoom(step);
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPointerUp = () => {
|
||||||
|
State.isResizing = false;
|
||||||
|
document.removeEventListener('pointermove', onPointerMove);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Podpięcie pod wszystkie uchwyty
|
||||||
|
DOM.elsHandles.forEach(el => {
|
||||||
|
el.addEventListener('pointerdown', onPointerDown, { passive: false });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Logika wysyłki na serwer
|
||||||
|
handleUpload() {
|
||||||
|
const btn = $(this);
|
||||||
|
btn.prop('disabled', true).text('Przetwarzanie...');
|
||||||
|
|
||||||
|
const { outputW, outputH, adminW, adminH } = CONFIG.dims;
|
||||||
|
|
||||||
|
// Generowanie canvasu
|
||||||
|
DOM.elSelection.$toCanvas({ width: outputW, height: outputH })
|
||||||
|
.then(canvas => {
|
||||||
|
canvas.toBlob(blob => {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
// Destrukturyzacja do dodania danych
|
||||||
|
formData.append("original_image", DOM.$input[0].files[0]);
|
||||||
|
formData.append("cropped_image", blob, "cropped.jpg");
|
||||||
|
|
||||||
|
// Metadane
|
||||||
|
const meta = { product_id: 123, admin_w_cm: adminW, admin_h_cm: adminH, out_w_px: outputW, out_h_px: outputH };
|
||||||
|
Object.entries(meta).forEach(([key, val]) => formData.append(key, val));
|
||||||
|
|
||||||
|
// AJAX
|
||||||
|
$.ajax({
|
||||||
|
url: "product-img-save.php",
|
||||||
|
method: "POST",
|
||||||
|
data: formData,
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
success: (resp) => {
|
||||||
|
DOM.$result.html(resp);
|
||||||
|
App.resetUI();
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
alert("Błąd zapisu!");
|
||||||
|
btn.prop('disabled', false).text('Przytnij i Zapisz');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, "image/jpeg", 0.92);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
alert("Nie udało się przygotować obrazu do zapisu.");
|
alert("Błąd generowania obrazu.");
|
||||||
$btnUpload.prop("disabled", false).text("Przytnij i Zapisz");
|
btn.prop('disabled', false).text('Przytnij i Zapisz');
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
});
|
|
||||||
|
// Reset po udanym wgraniu
|
||||||
|
resetUI() {
|
||||||
|
DOM.$wrapper.hide();
|
||||||
|
DOM.$toolbar.removeClass('show');
|
||||||
|
DOM.$uploadBtn.hide().prop('disabled', false).text('Przytnij i Zapisz');
|
||||||
|
DOM.$input.val('');
|
||||||
|
DOM.$name.text('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// START
|
||||||
|
$(document).ready(() => App.init());
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user