PDF_Maker / static /script.js
ministerchief's picture
Create script.js
a0ef4ce verified
Raw
History Blame Contribute Delete
7.72 kB
/* ──────────────────────────── Three.js 3D PDF icon ──── */
(function () {
const container = document.getElementById('three-header');
if (!container || typeof THREE === "undefined") return;
const W = 120, H = 120;
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(W, H);
container.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(38, W / H, 0.1, 100);
camera.position.set(0, 0, 5);
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const dirLight = new THREE.DirectionalLight(0xffffff, 0.9);
dirLight.position.set(4, 6, 4);
scene.add(dirLight);
const bodyGeo = new THREE.BoxGeometry(1.4, 1.8, 0.12);
const bodyMat = new THREE.MeshStandardMaterial({ color: 0xffffff });
const body = new THREE.Mesh(bodyGeo, bodyMat);
scene.add(body);
const cornerShape = new THREE.Shape();
cornerShape.moveTo(0, 0);
cornerShape.lineTo(0.38, 0);
cornerShape.lineTo(0, -0.38);
cornerShape.closePath();
const cornerGeo = new THREE.ExtrudeGeometry(cornerShape, { depth: 0.13, bevelEnabled: false });
const cornerMat = new THREE.MeshStandardMaterial({ color: 0x5048c8 });
const corner = new THREE.Mesh(cornerGeo, cornerMat);
corner.position.set(0.7, 0.9, 0);
scene.add(corner);
const stripeGeo = new THREE.BoxGeometry(0.9, 0.06, 0.14);
const stripeMat = new THREE.MeshStandardMaterial({ color: 0xe24b4a });
[-0.2, 0.1, 0.4].forEach(y => {
const s = new THREE.Mesh(stripeGeo, stripeMat);
s.position.set(-0.12, y, 0);
scene.add(s);
});
const planeGeo = new THREE.PlaneGeometry(1.45, 1.85);
const planeMat = new THREE.MeshStandardMaterial({ color: 0x5048c8 });
const plane = new THREE.Mesh(planeGeo, planeMat);
plane.position.set(0.10, -0.10, -0.20);
scene.add(plane);
function animate() {
requestAnimationFrame(animate);
body.rotation.y += 0.012;
corner.rotation.y = body.rotation.y;
plane.rotation.y = body.rotation.y;
scene.children.forEach(c => {
if (c.isMesh) c.rotation.y = body.rotation.y;
});
renderer.render(scene, camera);
}
animate();
})();
/* ──────────────────────────── App state ───────────────── */
const FREE_LIMIT = 15;
let selectedFiles = [];
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
const previewGrid = document.getElementById('preview-grid');
const counterBar = document.getElementById('counter-bar');
const countNum = document.getElementById('count-num');
const countDot = document.getElementById('count-dot');
const countLimit = document.getElementById('count-limit-text');
const convertBtn = document.getElementById('convert-btn');
const btnLabel = document.getElementById('btn-label');
const loadingBar = document.getElementById('loading-bar');
const clearBtn = document.getElementById('clear-btn');
const overlay = document.getElementById('overlay');
const toast = document.getElementById('toast');
if (dropZone && fileInput) {
dropZone.addEventListener('dragover', e => {
e.preventDefault();
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over'));
dropZone.addEventListener('drop', e => {
e.preventDefault();
dropZone.classList.remove('drag-over');
addFiles(e.dataTransfer.files);
});
fileInput.addEventListener('change', () => {
addFiles(fileInput.files);
fileInput.value = '';
});
}
function addFiles(fileList) {
const incoming = Array.from(fileList).filter(f => f.type.startsWith('image/'));
const newTotal = selectedFiles.length + incoming.length;
if (newTotal > FREE_LIMIT) {
const allowed = incoming.slice(0, FREE_LIMIT - selectedFiles.length);
allowed.forEach(pushFile);
updateUI();
showPremiumDialog();
return;
}
incoming.forEach(pushFile);
updateUI();
}
function pushFile(file) {
const url = URL.createObjectURL(file);
selectedFiles.push({ file, url });
}
function removeFile(index) {
URL.revokeObjectURL(selectedFiles[index].url);
selectedFiles.splice(index, 1);
updateUI();
}
function clearAll() {
selectedFiles.forEach(f => URL.revokeObjectURL(f.url));
selectedFiles = [];
updateUI();
}
function updateUI() {
const n = selectedFiles.length;
if (n > 0) {
counterBar.classList.add('visible');
countNum.textContent = n;
countLimit.textContent = `${n} / ${FREE_LIMIT} free slots used`;
if (countDot) {
countDot.className = 'count-dot' + (n >= FREE_LIMIT ? ' danger' : n >= 10 ? ' warn' : '');
}
} else {
counterBar.classList.remove('visible');
}
previewGrid.innerHTML = '';
selectedFiles.forEach(({ url }, i) => {
const item = document.createElement('div');
item.className = 'preview-item';
item.innerHTML = `
<img src="${url}" alt="preview ${i + 1}" loading="lazy" />
<span class="order-badge">${i + 1}</span>
<button class="remove-btn" onclick="removeFile(${i})" title="Remove">
<svg viewBox="0 0 24 24" fill="none" stroke-width="2.5" stroke-linecap="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
`;
previewGrid.appendChild(item);
});
convertBtn.disabled = n === 0;
clearBtn.style.display = n > 0 ? 'block' : 'none';
}
async function convertToPDF() {
if (selectedFiles.length === 0) {
showToast("Please select at least one image.");
return;
}
if (selectedFiles.length > FREE_LIMIT) {
showPremiumDialog();
return;
}
setLoading(true);
const formData = new FormData();
selectedFiles.forEach(({ file }) => {
formData.append('images', file);
});
try {
const res = await fetch('/convert', {
method: 'POST',
body: formData
});
const contentType = res.headers.get("content-type") || "";
if (!res.ok) {
let errorMessage = "Conversion failed.";
if (contentType.includes("application/json")) {
const json = await res.json();
if (json.error === "limit_exceeded") {
showPremiumDialog();
errorMessage = json.message || "Free limit reached.";
} else {
errorMessage = json.error || json.message || errorMessage;
}
}
showToast(errorMessage);
return;
}
const blob = await res.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'converted.pdf';
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
showToast('PDF downloaded successfully!');
} catch (err) {
console.error(err);
showToast('Server error. Please try again.');
} finally {
setLoading(false);
}
}
function setLoading(on) {
convertBtn.disabled = on;
btnLabel.style.display = on ? 'none' : 'inline';
loadingBar.style.display = on ? 'block' : 'none';
}
function showPremiumDialog() {
overlay.classList.add('show');
}
function closeDialog(e) {
if (!e || e.target === overlay) {
overlay.classList.remove('show');
}
}
function showToast(msg) {
toast.textContent = msg;
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 3000);
}
window.removeFile = removeFile;
window.clearAll = clearAll;
window.convertToPDF = convertToPDF;
window.closeDialog = closeDialog;
window.showToast = showToast;