Jeffgold commited on
Commit
c171f35
1 Parent(s): 94e4b50

Update templates/index.js

Browse files
Files changed (1) hide show
  1. templates/index.js +260 -0
templates/index.js CHANGED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Image Slider Animation with Server-Side GIF Generation</title>
7
+ <link href="https://unpkg.com/cropperjs/dist/cropper.css" rel="stylesheet">
8
+ <style>
9
+ body {
10
+ font-family: Arial, sans-serif;
11
+ display: flex;
12
+ flex-direction: column;
13
+ align-items: center;
14
+ padding: 20px;
15
+ background-color: #f0f0f0;
16
+ }
17
+ .input-group {
18
+ margin-bottom: 10px;
19
+ display: flex;
20
+ align-items: center;
21
+ }
22
+ input[type="file"] {
23
+ margin-left: 10px;
24
+ }
25
+ img.thumbnail {
26
+ margin-left: 10px;
27
+ width: 100px;
28
+ height: 100px;
29
+ object-fit: cover;
30
+ border: 1px solid #ccc;
31
+ }
32
+ button {
33
+ margin-top: 10px;
34
+ padding: 10px 20px;
35
+ }
36
+ #imageEditorModal {
37
+ display: none;
38
+ position: fixed;
39
+ left: 50%;
40
+ transform: translateX(-50%);
41
+ bottom: 3rem;
42
+ width: 70%;
43
+ background: #fff;
44
+ box-shadow: 0 0 10px rgba(0,0,0,0.5);
45
+ padding: 20px;
46
+ z-index: 1000;
47
+ text-align: center;
48
+ }
49
+ #cropImage {
50
+ max-width: 100%;
51
+ height: auto;
52
+ }
53
+ #generatedGif {
54
+ margin-top: 20px;
55
+ }
56
+ </style>
57
+ </head>
58
+ <body>
59
+ <div class="input-group">
60
+ <label>Image 1:</label>
61
+ <input type="file" id="image1Input" accept="image/*">
62
+ <img id="image1Thumbnail" class="thumbnail" alt="Image 1 Thumbnail">
63
+ </div>
64
+ <div class="input-group">
65
+ <label>Image 2:</label>
66
+ <input type="file" id="image2Input" accept="image/*">
67
+ <img id="image2Thumbnail" class="thumbnail" alt="Image 2 Thumbnail">
68
+ </div>
69
+ <div class="input-group">
70
+ <label>Transition Type:</label>
71
+ <select id="transitionType">
72
+ <option value="default">Sliding</option>
73
+ <option value="rotate">Rotating</option>
74
+ </select>
75
+ </div>
76
+ <canvas id="canvas" width="256" height="256" style="display: none;"></canvas>
77
+ <img id="generatedGif" alt="Generated GIF" style="display: none;">
78
+ <button id="downloadGif" style="display: none;">Download GIF</button>
79
+ <div id="status"></div>
80
+
81
+ <!-- Image editor modal -->
82
+ <div id="imageEditorModal">
83
+ <img id="cropImage" src="" alt="Image to crop">
84
+ <br>
85
+ <button id="cropButton">Crop</button>
86
+ </div>
87
+
88
+ <script src="https://unpkg.com/cropperjs"></script>
89
+ <script>
90
+ const canvas = document.getElementById('canvas');
91
+ const ctx = canvas.getContext('2d');
92
+ const statusDiv = document.getElementById('status');
93
+ const imageEditorModal = document.getElementById('imageEditorModal');
94
+ const cropImage = document.getElementById('cropImage');
95
+ const cropButton = document.getElementById('cropButton');
96
+ const image1Thumbnail = document.getElementById('image1Thumbnail');
97
+ const image2Thumbnail = document.getElementById('image2Thumbnail');
98
+ const generatedGif = document.getElementById('generatedGif');
99
+ const downloadGifButton = document.getElementById('downloadGif');
100
+ const transitionTypeSelect = document.getElementById('transitionType');
101
+
102
+ let cropper;
103
+ let currentImageInput;
104
+ let currentThumbnail;
105
+ let croppedImages = {
106
+ 'image1': null,
107
+ 'image2': null
108
+ };
109
+ const defaultImages = {
110
+ 'image1': 'https://th.bing.com/th/id/OIG1.jQk9obHLZDzKMIQQi_p3?pid=ImgGn',
111
+ 'image2': 'https://th.bing.com/th/id/OIG4.Pyn6Qnls3A8X.VgxaLIn?pid=ImgGn'
112
+ };
113
+
114
+ function showImageEditor(imageSrc) {
115
+ cropImage.src = imageSrc;
116
+ imageEditorModal.style.display = 'block';
117
+ cropper = new Cropper(cropImage, {
118
+ aspectRatio: 1,
119
+ viewMode: 1
120
+ });
121
+ }
122
+
123
+ function hideImageEditor() {
124
+ if (cropper) {
125
+ cropper.destroy();
126
+ }
127
+ imageEditorModal.style.display = 'none';
128
+ }
129
+
130
+ function dataURLtoFile(dataurl, filename) {
131
+ const arr = dataurl.split(',');
132
+ const mime = arr[0].match(/:(.*?);/)[1];
133
+ const bstr = atob(arr[1]);
134
+ let n = bstr.length;
135
+ const u8arr = new Uint8Array(n);
136
+ while (n--) {
137
+ u8arr[n] = bstr.charCodeAt(n);
138
+ }
139
+ return new File([u8arr], filename, { type: mime });
140
+ }
141
+
142
+ function loadImage(input, callback) {
143
+ if (input.files && input.files[0]) {
144
+ const reader = new FileReader();
145
+ reader.onload = function (e) {
146
+ callback(e.target.result);
147
+ }
148
+ reader.readAsDataURL(input.files[0]);
149
+ }
150
+ }
151
+
152
+ function handleImageUpload(input, thumbnail, key) {
153
+ loadImage(input, (imageSrc) => {
154
+ currentImageInput = input;
155
+ currentThumbnail = thumbnail;
156
+ showImageEditor(imageSrc);
157
+ currentThumbnail.dataset.key = key;
158
+ });
159
+ }
160
+
161
+ image1Input.addEventListener('change', function () {
162
+ handleImageUpload(image1Input, image1Thumbnail, 'image1');
163
+ });
164
+
165
+ image2Input.addEventListener('change', function () {
166
+ handleImageUpload(image2Input, image2Thumbnail, 'image2');
167
+ });
168
+
169
+ cropButton.addEventListener('click', function () {
170
+ const croppedCanvas = cropper.getCroppedCanvas();
171
+ const croppedImage = croppedCanvas.toDataURL('image/png');
172
+ const originalFileName = currentImageInput.files[0].name;
173
+ const croppedFileName = originalFileName.replace(/\.[^/.]+$/, "") + "_crop.png";
174
+ const file = dataURLtoFile(croppedImage, croppedFileName);
175
+
176
+ const key = currentThumbnail.dataset.key;
177
+ croppedImages[key] = file;
178
+ currentThumbnail.src = croppedImage;
179
+
180
+ hideImageEditor();
181
+
182
+ // Replace the current image input file with the cropped file
183
+ const dataTransfer = new DataTransfer();
184
+ dataTransfer.items.add(file);
185
+ currentImageInput.files = dataTransfer.files;
186
+
187
+ generateGif();
188
+ });
189
+
190
+ transitionTypeSelect.addEventListener('change', function () {
191
+ generateGif();
192
+ });
193
+
194
+ function generateGif() {
195
+ statusDiv.textContent = 'Generating GIF...';
196
+
197
+ const formData = new FormData();
198
+ formData.append('images', croppedImages['image1'] || dataURLtoFile(defaultImages['image1'], 'image1_default.png'));
199
+ formData.append('images', croppedImages['image2'] || dataURLtoFile(defaultImages['image2'], 'image2_default.png'));
200
+ formData.append('transition_type', transitionTypeSelect.value);
201
+
202
+ fetch('/generate_gif', {
203
+ method: 'POST',
204
+ body: formData
205
+ })
206
+ .then(response => {
207
+ if (response.ok) {
208
+ return response.blob();
209
+ } else {
210
+ return response.json().then(errorData => { throw new Error(errorData.error); });
211
+ }
212
+ })
213
+ .then(blob => {
214
+ const url = URL.createObjectURL(blob);
215
+ generatedGif.src = url;
216
+ generatedGif.style.display = 'block';
217
+ downloadGifButton.style.display = 'block';
218
+ downloadGifButton.onclick = () => {
219
+ const a = document.createElement('a');
220
+ a.href = url;
221
+ a.download = 'animation.gif';
222
+ document.body.appendChild(a);
223
+ a.click();
224
+ document.body.removeChild(a);
225
+ };
226
+ statusDiv.textContent = '';
227
+ })
228
+ .catch(error => {
229
+ statusDiv.textContent = 'Error generating GIF: ' + error.message;
230
+ console.error('Error generating GIF:', error);
231
+ });
232
+ }
233
+
234
+ // Load default images
235
+ function loadDefaultImages() {
236
+ image1Thumbnail.src = defaultImages['image1'];
237
+ image2Thumbnail.src = defaultImages['image2'];
238
+
239
+ fetch(defaultImages['image1'])
240
+ .then(response => response.blob())
241
+ .then(blob => {
242
+ const file = new File([blob], 'image1_default.png', { type: 'image/png' });
243
+ croppedImages['image1'] = file;
244
+ });
245
+
246
+ fetch(defaultImages['image2'])
247
+ .then(response => response.blob())
248
+ .then(blob => {
249
+ const file = new File([blob], 'image2_default.png', { type: 'image/png' });
250
+ croppedImages['image2'] = file;
251
+ })
252
+ .then(() => {
253
+ generateGif();
254
+ });
255
+ }
256
+
257
+ window.onload = loadDefaultImages;
258
+ </script>
259
+ </body>
260
+ </html>