PageSourceSearch

https://svgoptimizer.com/shared/js/utils.js?v=mtzzl1bp

js svgoptimizer.com collected 2026-10-04 13:25:07 UTC 10,072 bytes, 313 lines download raw bytes

1function generateId() {
2  return Math.random().toString(36).substring(2, 10);
3}
4
5function truncateName(name, maxLength = 12) {
6  if (name.length <= maxLength) return name;
7  const ext = name.lastIndexOf('.');
8  if (ext > 0 && name.length - ext <= 5) {
9    const base = name.substring(0, ext);
10    const extension = name.substring(ext);
11    const available = maxLength - extension.length - 1;
12    if (available > 3) {
13      return base.substring(0, available) + '…' + extension;
14    }
15  }
16  return name.substring(0, maxLength - 1) + '…';
17}
18
19function formatFileSize(bytes) {
20  if (bytes == null || isNaN(bytes)) return '0 B';
21  if (bytes < 1024) return bytes + ' B';
22  if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
23  return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
24}
25
26function getFileExtension(filename) {
27  const ext = filename.split('.').pop().toLowerCase();
28  if (ext === 'jpeg') return 'jpg';
29  if (ext === 'heif') return 'heic';
30  return ext;
31}
32
33function calculateSavings(original, compressed) {
34  if (!original || original === 0) return 0;
35  return Math.round((1 - compressed / original) * 100);
36}
37
38function shimmerHint(element, shimmeredElements) {
39  if (!element || shimmeredElements.has(element)) return;
40  shimmeredElements.add(element);
41  element.classList.remove('hint-shimmer');
42  void element.offsetWidth;
43  element.classList.add('hint-shimmer');
44  element.addEventListener('animationend', () => {
45    element.classList.remove('hint-shimmer');
46  }, { once: true });
47}
48
49function createDragDropHandlers(dropZone, onFiles) {
50  function handleDragOver(e) {
51    e.preventDefault();
52    e.stopPropagation();
53    dropZone.classList.add('files__list-outer_dragover');
54  }
55
56  function handleDragLeave(e) {
57    e.preventDefault();
58    e.stopPropagation();
59    dropZone.classList.remove('files__list-outer_dragover');
60  }
61
62  function handleDrop(e) {
63    e.preventDefault();
64    e.stopPropagation();
65    dropZone.classList.remove('files__list-outer_dragover');
66    const droppedFiles = e.dataTransfer.files;
67    if (droppedFiles && droppedFiles.length > 0) {
68      onFiles(droppedFiles);
69    }
70  }
71
72  return { handleDragOver, handleDragLeave, handleDrop };
73}
74
75function createScrollControls(scrollContainer, prevBtn, nextBtn, scrollAmount = 200) {
76  function updateScrollButtons() {
77    if (!scrollContainer) return;
78    const { scrollLeft, scrollWidth, clientWidth } = scrollContainer;
79    const canScrollLeft = scrollLeft > 0;
80    const canScrollRight = scrollLeft + clientWidth < scrollWidth - 1;
81    if (prevBtn) prevBtn.disabled = !canScrollLeft;
82    if (nextBtn) nextBtn.disabled = !canScrollRight;
83  }
84
85  function scrollToEnd() {
86    if (!scrollContainer) return;
87    setTimeout(() => {
88      scrollContainer.scrollTo({
89        left: scrollContainer.scrollWidth,
90        behavior: 'smooth'
91      });
92    }, 100);
93  }
94
95  function scrollBy(amount) {
96    if (!scrollContainer) return;
97
98    const maxScroll = scrollContainer.scrollWidth - scrollContainer.clientWidth;
99
100    if (amount < 0 && scrollContainer.scrollLeft < Math.abs(amount)) {
101      scrollContainer.scrollTo({ left: 0, behavior: 'smooth' });
102      return;
103    }
104
105    if (amount > 0 && scrollContainer.scrollLeft + amount > maxScroll - 10) {
106      scrollContainer.scrollTo({ left: maxScroll, behavior: 'smooth' });
107      return;
108    }
109
110    scrollContainer.scrollBy({
111      left: amount,
112      behavior: 'smooth'
113    });
114  }
115
116  if (prevBtn) prevBtn.addEventListener('click', () => scrollBy(-scrollAmount));
117  if (nextBtn) nextBtn.addEventListener('click', () => scrollBy(scrollAmount));
118  if (scrollContainer) scrollContainer.addEventListener('scroll', updateScrollButtons);
119
120  return { updateScrollButtons, scrollToEnd, scrollBy };
121}
122
123function downloadBlob(url, filename) {
124  const a = document.createElement('a');
125  a.href = url;
126  a.download = filename;
127  document.body.appendChild(a);
128  a.click();
129  document.body.removeChild(a);
130}
131
132function downloadMultiple(files, delay = 100) {
133  return new Promise((resolve) => {
134    let completed = 0;
135    files.forEach((file, index) => {
136      setTimeout(() => {
137        downloadBlob(file.url, file.filename);
138        completed++;
139        if (completed >= files.length) {
140          resolve();
141        }
142      }, index * delay);
143    });
144  });
145}
146
147function imageDims(source) {
148  if (!source) return null;
149  const w = source.imageWidth;
150  const h = source.imageHeight;
151  if (typeof w === 'number' && w > 0 && typeof h === 'number' && h > 0) {
152    return { width: w, height: h };
153  }
154  return null;
155}
156
157function reportError(error, context = {}) {
158  try {
159    const err = error instanceof Error ? error : new Error(String(error));
160    const config = window.APP_CONFIG || {};
161
162    const errDims = imageDims(err);
163    const ctx = (errDims && context.width === undefined) ? { ...context, ...errDims } : context;
164
165    const payload = JSON.stringify({
166      error: err.message,
167      stack: err.stack || '',
168      context: ctx,
169      site: config.siteKey || '',
170      ua: navigator.userAgent,
171      url: location.pathname
172    });
173
174    if (navigator.sendBeacon) {
175      navigator.sendBeacon('/api/error', new Blob([payload], { type: 'application/json' }));
176    } else {
177      fetch('/api/error', {
178        method: 'POST',
179        headers: { 'Content-Type': 'application/json' },
180        body: payload,
181        keepalive: true
182      }).catch(() => {});
183    }
184  } catch (_) {}
185}
186
187function successRate(setting) {
188  if (setting === true) return 1;
189  if (typeof setting === 'number' && setting > 0) return Math.min(setting, 1);
190  return 0;
191}
192
193function reportSuccess(context = {}) {
194  try {
195    const config = window.APP_CONFIG || {};
196    const rate = successRate(config.trackSuccess);
197    if (rate <= 0) return;
198    if (rate < 1 && Math.random() >= rate) return;
199
200    const payload = JSON.stringify({
201      site: config.siteKey || '',
202      context: rate < 1 ? { ...context, rate } : context,
203      url: location.pathname
204    });
205
206    if (navigator.sendBeacon) {
207      navigator.sendBeacon('/api/success', new Blob([payload], { type: 'application/json' }));
208    } else {
209      fetch('/api/success', {
210        method: 'POST',
211        headers: { 'Content-Type': 'application/json' },
212        body: payload,
213        keepalive: true
214      }).catch(() => {});
215    }
216  } catch (_) {}
217}
218
219async function detectFormat(file) {
220  try {
221    const buf = await file.slice(0, 16).arrayBuffer();
222    const b = new Uint8Array(buf);
223    if (b.length < 4) return null;
224
225    if (b[0] === 0xFF && b[1] === 0xD8 && b[2] === 0xFF) return 'jpg';
226
227    if (b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4E && b[3] === 0x47) return 'png';
228
229    if (b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x38) return 'gif';
230
231    if (b[0] === 0x42 && b[1] === 0x4D) return 'bmp';
232
233    if ((b[0] === 0x49 && b[1] === 0x49 && b[2] === 0x2A && b[3] === 0x00) ||
234        (b[0] === 0x4D && b[1] === 0x4D && b[2] === 0x00 && b[3] === 0x2A)) return 'tiff';
235
236    if (b[0] === 0x25 && b[1] === 0x50 && b[2] === 0x44 && b[3] === 0x46) return 'pdf';
237
238    if (b[0] === 0x1F && b[1] === 0x8B) return 'svgz';
239
240    if (b[0] === 0x38 && b[1] === 0x42 && b[2] === 0x50 && b[3] === 0x53) return 'psd';
241
242    if (b.length >= 12 && b[0] === 0x52 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x46 &&
243        b[8] === 0x57 && b[9] === 0x45 && b[10] === 0x42 && b[11] === 0x50) return 'webp';
244
245    if (b.length >= 12 && b[4] === 0x66 && b[5] === 0x74 && b[6] === 0x79 && b[7] === 0x70) {
246      const brand = String.fromCharCode(b[8], b[9], b[10], b[11]);
247      if (brand === 'avif' || brand === 'avis') return 'avif';
248      return 'heic';
249    }
250
251    const textBuf = await file.slice(0, 256).arrayBuffer();
252    const text = new TextDecoder().decode(new Uint8Array(textBuf));
253    const trimmed = text.trimStart();
254    if (trimmed.startsWith('<svg') || (trimmed.startsWith('<?xml') && text.includes('<svg'))) return 'svg';
255
256    return null;
257  } catch {
258    return null;
259  }
260}
261
262function isWasmCrashError(errorMessage) {
263  if (!errorMessage) return false;
264  const msg = String(errorMessage).toLowerCase();
265  return msg.includes('memory access out of bounds') ||
266    msg.includes('out of bounds memory access') ||
267    msg.includes('exit(1)') ||
268    msg.includes('abort(') ||
269    msg.includes('unreachable') ||
270    msg.includes('runtimeerror');
271}
272
273function classifyError(msg, errors) {
274  if (!errors) errors = {};
275  if (!msg) return errors.processing || '';
276  const m = msg.toLowerCase();
277  if (m.includes('could not be decoded') || m.includes('cannot decode') || m.includes('size of offscreencanvas is zero') || m.includes('readback') || m.includes('createimagebitmap') || m.includes('encoding') || m.includes('invalid image') || m.includes('not a valid') || m.includes('invalid state')) return errors.corrupt || '';
278  if (m.includes('memory access') || m.includes('out of memory') || m.includes('abort(') || m.includes('exit(1)') || m.includes('unreachable') || m.includes('wasm worker crashed')) return errors.tooLarge || '';
279  if (m.includes('failed to fetch') || m.includes('dynamically imported module') || m.includes('load failed')) return errors.loading || '';
280  if (m.includes('max buffer length') || m.includes('non-whitespace before first tag') || m.includes('invalid svg') || m.includes('entity reference')) return errors.svg || '';
281  return errors.processing || '';
282}
283
284window.SharedUtils = {
285  generateId,
286  truncateName,
287  formatFileSize,
288  getFileExtension,
289  calculateSavings,
290  shimmerHint,
291  createDragDropHandlers,
292  createScrollControls,
293  downloadBlob,
294  downloadMultiple,
295  reportError,
296  reportSuccess,
297  imageDims,
298  detectFormat,
299  isWasmCrashError,
300  classifyError
301};
302
303window.generateId = generateId;
304window.truncateName = truncateName;
305window.formatFileSize = formatFileSize;
306window.getFileExtension = getFileExtension;
307window.calculateSavings = calculateSavings;
308window.reportError = reportError;
309window.reportSuccess = reportSuccess;
310window.imageDims = imageDims;
311window.detectFormat = detectFormat;
312window.isWasmCrashError = isWasmCrashError;
313window.classifyError = classifyError;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.