ministerchief commited on
Commit
a0ef4ce
Β·
verified Β·
1 Parent(s): 9e17462

Create script.js

Browse files
Files changed (1) hide show
  1. static/script.js +266 -0
static/script.js ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ──────────────────────────── Three.js 3D PDF icon ──── */
2
+ (function () {
3
+ const container = document.getElementById('three-header');
4
+ if (!container || typeof THREE === "undefined") return;
5
+
6
+ const W = 120, H = 120;
7
+
8
+ const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
9
+ renderer.setPixelRatio(window.devicePixelRatio);
10
+ renderer.setSize(W, H);
11
+ container.appendChild(renderer.domElement);
12
+
13
+ const scene = new THREE.Scene();
14
+ const camera = new THREE.PerspectiveCamera(38, W / H, 0.1, 100);
15
+ camera.position.set(0, 0, 5);
16
+
17
+ scene.add(new THREE.AmbientLight(0xffffff, 0.6));
18
+ const dirLight = new THREE.DirectionalLight(0xffffff, 0.9);
19
+ dirLight.position.set(4, 6, 4);
20
+ scene.add(dirLight);
21
+
22
+ const bodyGeo = new THREE.BoxGeometry(1.4, 1.8, 0.12);
23
+ const bodyMat = new THREE.MeshStandardMaterial({ color: 0xffffff });
24
+ const body = new THREE.Mesh(bodyGeo, bodyMat);
25
+ scene.add(body);
26
+
27
+ const cornerShape = new THREE.Shape();
28
+ cornerShape.moveTo(0, 0);
29
+ cornerShape.lineTo(0.38, 0);
30
+ cornerShape.lineTo(0, -0.38);
31
+ cornerShape.closePath();
32
+
33
+ const cornerGeo = new THREE.ExtrudeGeometry(cornerShape, { depth: 0.13, bevelEnabled: false });
34
+ const cornerMat = new THREE.MeshStandardMaterial({ color: 0x5048c8 });
35
+ const corner = new THREE.Mesh(cornerGeo, cornerMat);
36
+ corner.position.set(0.7, 0.9, 0);
37
+ scene.add(corner);
38
+
39
+ const stripeGeo = new THREE.BoxGeometry(0.9, 0.06, 0.14);
40
+ const stripeMat = new THREE.MeshStandardMaterial({ color: 0xe24b4a });
41
+
42
+ [-0.2, 0.1, 0.4].forEach(y => {
43
+ const s = new THREE.Mesh(stripeGeo, stripeMat);
44
+ s.position.set(-0.12, y, 0);
45
+ scene.add(s);
46
+ });
47
+
48
+ const planeGeo = new THREE.PlaneGeometry(1.45, 1.85);
49
+ const planeMat = new THREE.MeshStandardMaterial({ color: 0x5048c8 });
50
+ const plane = new THREE.Mesh(planeGeo, planeMat);
51
+ plane.position.set(0.10, -0.10, -0.20);
52
+ scene.add(plane);
53
+
54
+ function animate() {
55
+ requestAnimationFrame(animate);
56
+ body.rotation.y += 0.012;
57
+ corner.rotation.y = body.rotation.y;
58
+ plane.rotation.y = body.rotation.y;
59
+ scene.children.forEach(c => {
60
+ if (c.isMesh) c.rotation.y = body.rotation.y;
61
+ });
62
+ renderer.render(scene, camera);
63
+ }
64
+
65
+ animate();
66
+ })();
67
+
68
+ /* ──────────────────────────── App state ───────────────── */
69
+ const FREE_LIMIT = 15;
70
+ let selectedFiles = [];
71
+
72
+ const dropZone = document.getElementById('drop-zone');
73
+ const fileInput = document.getElementById('file-input');
74
+ const previewGrid = document.getElementById('preview-grid');
75
+ const counterBar = document.getElementById('counter-bar');
76
+ const countNum = document.getElementById('count-num');
77
+ const countDot = document.getElementById('count-dot');
78
+ const countLimit = document.getElementById('count-limit-text');
79
+ const convertBtn = document.getElementById('convert-btn');
80
+ const btnLabel = document.getElementById('btn-label');
81
+ const loadingBar = document.getElementById('loading-bar');
82
+ const clearBtn = document.getElementById('clear-btn');
83
+ const overlay = document.getElementById('overlay');
84
+ const toast = document.getElementById('toast');
85
+
86
+ if (dropZone && fileInput) {
87
+ dropZone.addEventListener('dragover', e => {
88
+ e.preventDefault();
89
+ dropZone.classList.add('drag-over');
90
+ });
91
+
92
+ dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over'));
93
+
94
+ dropZone.addEventListener('drop', e => {
95
+ e.preventDefault();
96
+ dropZone.classList.remove('drag-over');
97
+ addFiles(e.dataTransfer.files);
98
+ });
99
+
100
+ fileInput.addEventListener('change', () => {
101
+ addFiles(fileInput.files);
102
+ fileInput.value = '';
103
+ });
104
+ }
105
+
106
+ function addFiles(fileList) {
107
+ const incoming = Array.from(fileList).filter(f => f.type.startsWith('image/'));
108
+ const newTotal = selectedFiles.length + incoming.length;
109
+
110
+ if (newTotal > FREE_LIMIT) {
111
+ const allowed = incoming.slice(0, FREE_LIMIT - selectedFiles.length);
112
+ allowed.forEach(pushFile);
113
+ updateUI();
114
+ showPremiumDialog();
115
+ return;
116
+ }
117
+
118
+ incoming.forEach(pushFile);
119
+ updateUI();
120
+ }
121
+
122
+ function pushFile(file) {
123
+ const url = URL.createObjectURL(file);
124
+ selectedFiles.push({ file, url });
125
+ }
126
+
127
+ function removeFile(index) {
128
+ URL.revokeObjectURL(selectedFiles[index].url);
129
+ selectedFiles.splice(index, 1);
130
+ updateUI();
131
+ }
132
+
133
+ function clearAll() {
134
+ selectedFiles.forEach(f => URL.revokeObjectURL(f.url));
135
+ selectedFiles = [];
136
+ updateUI();
137
+ }
138
+
139
+ function updateUI() {
140
+ const n = selectedFiles.length;
141
+
142
+ if (n > 0) {
143
+ counterBar.classList.add('visible');
144
+ countNum.textContent = n;
145
+ countLimit.textContent = `${n} / ${FREE_LIMIT} free slots used`;
146
+
147
+ if (countDot) {
148
+ countDot.className = 'count-dot' + (n >= FREE_LIMIT ? ' danger' : n >= 10 ? ' warn' : '');
149
+ }
150
+ } else {
151
+ counterBar.classList.remove('visible');
152
+ }
153
+
154
+ previewGrid.innerHTML = '';
155
+
156
+ selectedFiles.forEach(({ url }, i) => {
157
+ const item = document.createElement('div');
158
+ item.className = 'preview-item';
159
+ item.innerHTML = `
160
+ <img src="${url}" alt="preview ${i + 1}" loading="lazy" />
161
+ <span class="order-badge">${i + 1}</span>
162
+ <button class="remove-btn" onclick="removeFile(${i})" title="Remove">
163
+ <svg viewBox="0 0 24 24" fill="none" stroke-width="2.5" stroke-linecap="round">
164
+ <line x1="18" y1="6" x2="6" y2="18"></line>
165
+ <line x1="6" y1="6" x2="18" y2="18"></line>
166
+ </svg>
167
+ </button>
168
+ `;
169
+ previewGrid.appendChild(item);
170
+ });
171
+
172
+ convertBtn.disabled = n === 0;
173
+ clearBtn.style.display = n > 0 ? 'block' : 'none';
174
+ }
175
+
176
+ async function convertToPDF() {
177
+ if (selectedFiles.length === 0) {
178
+ showToast("Please select at least one image.");
179
+ return;
180
+ }
181
+
182
+ if (selectedFiles.length > FREE_LIMIT) {
183
+ showPremiumDialog();
184
+ return;
185
+ }
186
+
187
+ setLoading(true);
188
+
189
+ const formData = new FormData();
190
+ selectedFiles.forEach(({ file }) => {
191
+ formData.append('images', file);
192
+ });
193
+
194
+ try {
195
+ const res = await fetch('/convert', {
196
+ method: 'POST',
197
+ body: formData
198
+ });
199
+
200
+ const contentType = res.headers.get("content-type") || "";
201
+
202
+ if (!res.ok) {
203
+ let errorMessage = "Conversion failed.";
204
+
205
+ if (contentType.includes("application/json")) {
206
+ const json = await res.json();
207
+
208
+ if (json.error === "limit_exceeded") {
209
+ showPremiumDialog();
210
+ errorMessage = json.message || "Free limit reached.";
211
+ } else {
212
+ errorMessage = json.error || json.message || errorMessage;
213
+ }
214
+ }
215
+
216
+ showToast(errorMessage);
217
+ return;
218
+ }
219
+
220
+ const blob = await res.blob();
221
+ const url = window.URL.createObjectURL(blob);
222
+
223
+ const a = document.createElement('a');
224
+ a.href = url;
225
+ a.download = 'converted.pdf';
226
+ document.body.appendChild(a);
227
+ a.click();
228
+ a.remove();
229
+
230
+ window.URL.revokeObjectURL(url);
231
+ showToast('PDF downloaded successfully!');
232
+ } catch (err) {
233
+ console.error(err);
234
+ showToast('Server error. Please try again.');
235
+ } finally {
236
+ setLoading(false);
237
+ }
238
+ }
239
+
240
+ function setLoading(on) {
241
+ convertBtn.disabled = on;
242
+ btnLabel.style.display = on ? 'none' : 'inline';
243
+ loadingBar.style.display = on ? 'block' : 'none';
244
+ }
245
+
246
+ function showPremiumDialog() {
247
+ overlay.classList.add('show');
248
+ }
249
+
250
+ function closeDialog(e) {
251
+ if (!e || e.target === overlay) {
252
+ overlay.classList.remove('show');
253
+ }
254
+ }
255
+
256
+ function showToast(msg) {
257
+ toast.textContent = msg;
258
+ toast.classList.add('show');
259
+ setTimeout(() => toast.classList.remove('show'), 3000);
260
+ }
261
+
262
+ window.removeFile = removeFile;
263
+ window.clearAll = clearAll;
264
+ window.convertToPDF = convertToPDF;
265
+ window.closeDialog = closeDialog;
266
+ window.showToast = showToast;