diff --git a/.gitignore b/.gitignore
index 915fe8a1..0dd712bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
*.DS_Store
.opencode/
+doc-outside/
diff --git a/app.js b/app.js
index 90bf6fcb..e29cb17c 100644
--- a/app.js
+++ b/app.js
@@ -17,6 +17,22 @@ function escapeHtml(s) {
.replace(/'/g, ''');
}
+function getGaValue(value) {
+ if (value === null || value === undefined) return 'unknown';
+ if (typeof value === 'string' && value.trim() === '') return 'unknown';
+ return value;
+}
+
+function trackGaEvent(eventName, params) {
+ if (typeof window === 'undefined') return;
+ if (typeof window.gtag !== 'function') return;
+ try {
+ window.gtag('event', eventName, params || {});
+ } catch (e) {
+ console.warn('GA event failed:', eventName, e);
+ }
+}
+
function applyTheme(theme) {
var body = document.body;
if (theme === 'wiki') {
@@ -136,6 +152,17 @@ function init() {
return { effectiveSlicer: effectiveSlicer, effectiveBrand: effectiveBrand };
}
+ function getRowGaContext(row, fallbackSlicer) {
+ var cells = row ? row.querySelectorAll('td') : [];
+ var filters = getEffectiveFilters();
+ return {
+ slicer: getGaValue(fallbackSlicer || filters.effectiveSlicer || filterState.slicer),
+ material: getGaValue(cells[1] ? cells[1].textContent.trim() : ''),
+ brand: getGaValue(cells[2] ? cells[2].textContent.trim() : ''),
+ model: getGaValue(cells[3] ? cells[3].textContent.trim() : '')
+ };
+ }
+
function isBambuStudioSlicerSelected() {
var filters = getEffectiveFilters();
return filters.effectiveSlicer === 'BambuStudio';
@@ -508,6 +535,16 @@ function init() {
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
+ if (originalPresets && originalPresets.length) {
+ var firstPreset = originalPresets[0];
+ trackGaEvent('download_bundle', {
+ file_type: 'bbsflmt',
+ slicer: getGaValue(firstPreset.slicer || 'BambuStudio'),
+ material: getGaValue(firstPreset.material),
+ brand: getGaValue(firstPreset.brand),
+ model: getGaValue(firstPreset.model)
+ });
+ }
setTimeout(function() {
URL.revokeObjectURL(objectUrl);
}, 1000);
@@ -1008,6 +1045,15 @@ function init() {
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
+ var filters = getEffectiveFilters();
+ trackGaEvent('download_selected', {
+ file_type: 'zip',
+ selected_count: presetIds.length,
+ slicer: getGaValue(filters.effectiveSlicer || filterState.slicer),
+ series: getGaValue(filterState.series),
+ brand: getGaValue(filters.effectiveBrand || filterState.brand),
+ model: getGaValue(filterState.model)
+ });
// Delay revoking object URL to ensure download starts
setTimeout(function () {
URL.revokeObjectURL(objectUrl);
@@ -1223,6 +1269,11 @@ function init() {
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
+ trackGaEvent('download_bundle_batch', {
+ file_type: 'zip',
+ material_count: materials.length,
+ slicer: 'BambuStudio'
+ });
setTimeout(function() { URL.revokeObjectURL(objectUrl); }, 1000);
if (downloadBundleBtn) {
@@ -1748,6 +1799,46 @@ function init() {
}
tbody.addEventListener('click', function (e) {
+ // Handle non-Bambu direct JSON download with post-download tracking.
+ var directJsonLink = e.target.closest('a[data-download-url]');
+ if (directJsonLink) {
+ e.preventDefault();
+ var djUrl = directJsonLink.getAttribute('data-download-url');
+ var djFilename = directJsonLink.getAttribute('data-download-filename') || 'preset.json';
+ if (!djUrl || djUrl === '#') {
+ alert(t('alert.invalid.url'));
+ return;
+ }
+ fetch(djUrl, { mode: 'cors' })
+ .then(function (r) {
+ if (!r.ok) throw new Error('Failed to fetch preset: ' + r.statusText);
+ return r.blob();
+ })
+ .then(function (blob) {
+ var objectUrl = URL.createObjectURL(blob);
+ var a = document.createElement('a');
+ a.href = objectUrl;
+ a.download = djFilename;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ var rowContext = getRowGaContext(directJsonLink.closest('tr'));
+ trackGaEvent('download_single', {
+ file_type: 'json',
+ slicer: rowContext.slicer,
+ material: rowContext.material,
+ brand: rowContext.brand,
+ model: rowContext.model
+ });
+ setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 1000);
+ })
+ .catch(function (err) {
+ console.error('Error downloading JSON:', err);
+ alert(t('alert.error.preset', { msg: err.message }));
+ });
+ return;
+ }
+
// Handle BambuStudio JSON button click — fetch, expand per-printer,
// zip, and download (issue #14).
var bambuJsonLink = e.target.closest('a[data-bambu-json="1"]');
@@ -1787,6 +1878,14 @@ function init() {
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
+ var rowContext = getRowGaContext(bambuJsonLink.closest('tr'), 'BambuStudio');
+ trackGaEvent('download_single', {
+ file_type: 'json',
+ slicer: rowContext.slicer,
+ material: getGaValue(bjMaterial || rowContext.material),
+ brand: rowContext.brand,
+ model: rowContext.model
+ });
setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 1000);
});
})
diff --git a/index.html b/index.html
index 131f9abf..01b7e7e2 100644
--- a/index.html
+++ b/index.html
@@ -17,6 +17,17 @@
+
+
+