Image uploader test
This commit is contained in:
53
product-img-save.php
Normal file
53
product-img-save.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
// upload.php
|
||||
|
||||
// Налаштування папок (Ustawienia katalogów)
|
||||
$uploadDir = 'product_uploads/' . $_POST['product_id'] . '/';
|
||||
$originalDir = $uploadDir . 'originals/';
|
||||
$croppedDir = $uploadDir . 'cropped/';
|
||||
|
||||
// Створюємо папки, якщо немає (Tworzenie katalogów, jeśli nie istnieją)
|
||||
if (!file_exists($originalDir)) mkdir($originalDir, 0777, true);
|
||||
if (!file_exists($croppedDir)) mkdir($croppedDir, 0777, true);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
$filename = uniqid() . '.jpg';
|
||||
$successOriginal = false;
|
||||
$successCropped = false;
|
||||
|
||||
// 1. Zapisywanie oryginału
|
||||
if (isset($_FILES['original_image'])) {
|
||||
$originalPath = $originalDir . $filename;
|
||||
if (move_uploaded_file($_FILES['original_image']['tmp_name'], $originalPath)) {
|
||||
$successOriginal = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Zapisywanie wykadrowanego zdjęcia
|
||||
if (isset($_FILES['cropped_image'])) {
|
||||
$croppedPath = $croppedDir . $filename;
|
||||
if (move_uploaded_file($_FILES['cropped_image']['tmp_name'], $croppedPath)) {
|
||||
$successCropped = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Відповідь клієнту (Odpowiedź)
|
||||
if ($successOriginal && $successCropped) {
|
||||
echo "<div style='color:green; border: 1px solid green; padding: 10px;'>";
|
||||
echo "<strong>Zapisano pomyślnie!</strong><br>";
|
||||
echo "Oryginał: " . $originalPath . "<br>";
|
||||
echo "Wykadrowane: " . $croppedPath;
|
||||
echo "</div>";
|
||||
|
||||
// Тут можна додати SQL запит до БД (Tutaj można dodać zapytanie SQL do bazy danych)
|
||||
// Np.: INSERT INTO product_images (original, cropped) VALUES ('$originalPath', '$croppedPath');
|
||||
|
||||
} else {
|
||||
echo "<div style='color:red;'>Wystąpił błąd podczas zapisywania plików.</div>";
|
||||
}
|
||||
|
||||
} else {
|
||||
echo "Nieprawidłowa metoda żądania.";
|
||||
}
|
||||
?>
|
||||
323
product-img-upl.php
Normal file
323
product-img-upl.php
Normal file
@@ -0,0 +1,323 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Przesyłanie i kadrowanie zdjęć</title>
|
||||
|
||||
<style>
|
||||
body { font-family: sans-serif; padding: 20px; }
|
||||
|
||||
.cropper-wrapper {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
background: #333;
|
||||
margin-bottom: 10px;
|
||||
display: none;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
user-select: none;
|
||||
touch-action: none; /* важливо для drag на мобільних */
|
||||
}
|
||||
|
||||
cropper-canvas { width: 100%; height: 100%; }
|
||||
|
||||
.btn {
|
||||
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 {
|
||||
outline: 3px solid red !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#toolbar {
|
||||
display: none;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin: 10px 0 15px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
#toolbar.show { display: flex; }
|
||||
#toolbar .btn { padding: 8px 14px; font-size: 16px; }
|
||||
|
||||
#hint { color: #444; font-size: 14px; margin-left: 6px; }
|
||||
.chip {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
/* ====== РУЧКИ "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;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Przesyłanie zdjęcia produktu</h2>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<label for="inputImage">Wybierz zdjęcie:</label>
|
||||
<input type="file" id="inputImage" accept="image/*" />
|
||||
</div>
|
||||
|
||||
<div class="cropper-wrapper" id="cropperWrapper">
|
||||
<cropper-canvas id="my-cropper" background>
|
||||
<cropper-image
|
||||
id="cropperImage"
|
||||
initial-center-size="cover"
|
||||
translatable
|
||||
scalable
|
||||
></cropper-image>
|
||||
|
||||
<!-- Drag obrazu -->
|
||||
<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-canvas>
|
||||
|
||||
<!-- Overlay: “resize foto za rogi” -->
|
||||
<div class="photo-handles active" id="photoHandles">
|
||||
<div class="photo-outline"></div>
|
||||
<div class="handle nw" data-corner="nw" title="Zmień rozmiar zdjęcia"></div>
|
||||
<div class="handle ne" data-corner="ne" title="Zmień rozmiar zdjęcia"></div>
|
||||
<div class="handle sw" data-corner="sw" title="Zmień rozmiar zdjęcia"></div>
|
||||
<div class="handle se" data-corner="se" title="Zmień rozmiar zdjęcia"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toolbar">
|
||||
<button type="button" class="btn" id="zoomIn">+</button>
|
||||
<button type="button" class="btn" id="zoomOut">-</button>
|
||||
<button type="button" class="btn" id="resetImg">Reset</button>
|
||||
<span class="chip" id="formatInfo"></span>
|
||||
<span id="hint">Przesuwaj zdjęcie myszą/palcem, zoom kółkiem lub złap za rogi</span>
|
||||
</div>
|
||||
|
||||
<button id="btnUpload" class="btn" style="display:none;">Przytnij i Zapisz</button>
|
||||
<div id="result" style="margin-top: 20px;"></div>
|
||||
|
||||
<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>
|
||||
$(document).ready(function () {
|
||||
const $inputImage = $("#inputImage");
|
||||
const $cropperWrapper = $("#cropperWrapper");
|
||||
const $btnUpload = $("#btnUpload");
|
||||
const $toolbar = $("#toolbar");
|
||||
|
||||
const cropperCanvas = document.getElementById("my-cropper");
|
||||
const cropperSelection = document.getElementById("cropperSelection");
|
||||
const cropperImage = document.getElementById("cropperImage");
|
||||
|
||||
const photoHandles = document.getElementById("photoHandles");
|
||||
|
||||
// ADMIN (z serwera)
|
||||
const ADMIN_W_CM = 8;
|
||||
const ADMIN_H_CM = 12;
|
||||
|
||||
// wynik px
|
||||
const OUT_W_PX = 800;
|
||||
const OUT_H_PX = 1200;
|
||||
|
||||
$("#formatInfo").text(`Format: ${ADMIN_W_CM}×${ADMIN_H_CM} cm`);
|
||||
|
||||
function centerFixedSelection() {
|
||||
const ratio = ADMIN_W_CM / ADMIN_H_CM;
|
||||
cropperSelection.aspectRatio = ratio;
|
||||
|
||||
const rect = cropperCanvas.getBoundingClientRect();
|
||||
if (!rect.width || !rect.height) return;
|
||||
|
||||
const maxW = rect.width * 0.7;
|
||||
const maxH = rect.height * 0.7;
|
||||
|
||||
let selW, selH;
|
||||
if (maxW / maxH > ratio) {
|
||||
selH = maxH;
|
||||
selW = selH * ratio;
|
||||
} else {
|
||||
selW = maxW;
|
||||
selH = selW / ratio;
|
||||
}
|
||||
|
||||
const x = (rect.width - selW) / 2;
|
||||
const y = (rect.height - selH) / 2;
|
||||
|
||||
cropperSelection.$change(x, y, selW, selH, ratio);
|
||||
}
|
||||
|
||||
function bindToolbar() {
|
||||
$("#zoomIn").off("click").on("click", () => cropperImage.$zoom(0.1));
|
||||
$("#zoomOut").off("click").on("click", () => cropperImage.$zoom(-0.1));
|
||||
$("#resetImg").off("click").on("click", () => cropperImage.$center("cover"));
|
||||
}
|
||||
|
||||
// Wheel zoom
|
||||
$cropperWrapper.on("wheel", function (e) {
|
||||
e.preventDefault();
|
||||
const delta = e.originalEvent.deltaY;
|
||||
cropperImage.$zoom(delta < 0 ? 0.08 : -0.08);
|
||||
});
|
||||
|
||||
// ===== "Resize foto za rogi" через zoom =====
|
||||
let isResizingPhoto = false;
|
||||
let startY = 0;
|
||||
|
||||
function onPointerDown(e) {
|
||||
isResizingPhoto = true;
|
||||
startY = e.clientY;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
document.addEventListener("pointermove", onPointerMove, { passive: false });
|
||||
document.addEventListener("pointerup", onPointerUp, { passive: false, once: true });
|
||||
}
|
||||
|
||||
function onPointerMove(e) {
|
||||
if (!isResizingPhoto) return;
|
||||
|
||||
const dy = e.clientY - startY;
|
||||
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", () => {
|
||||
if ($cropperWrapper.is(":visible")) centerFixedSelection();
|
||||
});
|
||||
|
||||
// Save
|
||||
$btnUpload.click(function () {
|
||||
$btnUpload.prop("disabled", true).text("Przetwarzanie...");
|
||||
|
||||
cropperSelection
|
||||
.$toCanvas({ width: OUT_W_PX, height: OUT_H_PX })
|
||||
.then((canvas) => {
|
||||
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);
|
||||
formData.append("admin_h_cm", ADMIN_H_CM);
|
||||
formData.append("out_w_px", OUT_W_PX);
|
||||
formData.append("out_h_px", OUT_H_PX);
|
||||
|
||||
$.ajax({
|
||||
url: "product-img-save.php",
|
||||
method: "POST",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function (response) {
|
||||
$("#result").html(response);
|
||||
$cropperWrapper.hide();
|
||||
$toolbar.removeClass("show");
|
||||
$btnUpload.hide().prop("disabled", false).text("Przytnij i Zapisz");
|
||||
$inputImage.val("");
|
||||
},
|
||||
error: function () {
|
||||
alert("Błąd podczas przesyłania.");
|
||||
$btnUpload.prop("disabled", false).text("Przytnij i Zapisz");
|
||||
},
|
||||
});
|
||||
},
|
||||
"image/jpeg",
|
||||
0.92
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
alert("Nie udało się przygotować obrazu do zapisu.");
|
||||
$btnUpload.prop("disabled", false).text("Przytnij i Zapisz");
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user