Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,11 @@ const app = {
this.handleFileUpload(e.target.files[0]);
});

document.getElementById('importFile').addEventListener('change', (e) => {
this.handleFileUpload(e.target.files[0]);
e.target.value = ''; // Reset
});

// Add event listeners for all four color inputs
for (let i = 1; i <= 4; i++) {
document.getElementById(`colorHex${i}`).addEventListener('input', (e) => {
Expand Down Expand Up @@ -355,9 +360,13 @@ const app = {

nfcReader.stop();

// Determine context for error reporting
const isFormVisible = !document.getElementById('formSection').classList.contains('hidden');
const statusId = isFormVisible ? 'writeStatus' : 'readStatus';

const format = formats.detectFormatFromFilename(file.name);
if (!format) {
this.showStatus('readStatus', 'error', 'Unsupported file type');
this.showStatus(statusId, 'error', 'Unsupported file type');
return;
}

Expand All @@ -368,12 +377,19 @@ const app = {
try {
const data = formats.parseData(format, e.target.result);
this.populateForm(data, format);

output += `Format: ${formats.getDisplayName(format)}\n`;
output += `Material: ${data.materialType}\n`;
this.showDecodedData(output);
this.transitionToForm(format);

if (!isFormVisible) {
this.showDecodedData(output);
this.transitionToForm(format);
} else {
// Just update UI if already in form mode
this.showStatus(statusId, 'success', `Loaded ${formats.getDisplayName(format)}`);
}
} catch (err) {
this.showStatus('readStatus', 'error', `Invalid ${format} file`);
this.showStatus(statusId, 'error', `Invalid ${format} file`);
}
};

Expand Down
2 changes: 1 addition & 1 deletion public/formats.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const formats = {

detectFormatFromFilename(filename) {
for (const key in this.FORMATS) {
if (filename.endsWith(this.FORMATS[key].getFileExtension(key))) {
if (filename.endsWith(this.getFileExtension(key))) {
return key;
}
}
Expand Down
11 changes: 11 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,17 @@ <h2 class="section-title">📖 Read NFC Tag or Upload File</h2>
<div id="formSection" class="section hidden">
<h2 class="section-title">✏️ <span id="formTitle">Create New Tag</span></h2>

<div class="form-group" style="padding-bottom: 1.5rem; margin-bottom: 1.5rem; border-bottom: 1px solid var(--border);">
<label>Import From another profile format </label>
<div style="display: flex; gap: 0.5rem;">
<button class="btn-secondary" onclick="document.getElementById('importFile').click()" style="margin-top: 0;">📂 Upload File to Fill Form</button>
<input type="file" id="importFile" accept=".json,.bin" style="display: none;">
</div>
<small style="color: var(--text-secondary); font-size: 0.8rem; display: block; margin-top: 0.25rem;">
Supports .json (OpenSpool, Bambu/OrcaSlicer) and .bin (OpenPrintTag)
</small>
</div>

<div class="form-group">
<label>Tag Format</label>
<select id="formatSelect" onchange="app.updateFormat()"></select>
Expand Down
55 changes: 54 additions & 1 deletion public/openspool.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,64 @@ const OpenSpool = {
},

// Parse OpenSpool data
parseData: function(jsonData) {
parseData: function(input) {
// Handle ArrayBuffer or encoded string input
let jsonData = input;
if (input instanceof ArrayBuffer || typeof input === 'string') {
try {
const text = typeof input === 'string' ? input : new TextDecoder().decode(input);
jsonData = JSON.parse(text);
} catch (e) {
console.error('Error parsing JSON:', e);
throw new Error("Invalid JSON format");
}
}

// Check for Bambu/OrcaSlicer format (has filament_vendor/type arrays)
if (jsonData.filament_vendor && Array.isArray(jsonData.filament_vendor) &&
jsonData.filament_type && Array.isArray(jsonData.filament_type)) {
console.warn('Detected Bambu/OrcaSlicer format, mapping to OpenSpool extended');
return this._parseBambuFormat(jsonData);
}

if (jsonData.protocol !== "openspool") {
throw new Error("Not an OpenSpool format");
}

return this._parseOpenSpoolFormat(jsonData);
},

_parseBambuFormat: function(jsonData) {
// Helper to safely get first element of array or value
const getVal = (key) => {
const val = jsonData[key];
if (Array.isArray(val) && val.length > 0) return val[0];
return val;
};

return {
format: 'openspool_extended', // Map to extended allows more fields if needed
materialType: getVal('filament_type') || 'PLA',
brand: getVal('filament_vendor') || 'Generic',

// Map temperatures
// hot_plate_temp -> Min Bed Temp
bedTempMin: getVal('hot_plate_temp') || '',
// hot_plate_temp_initial_layer -> Max Bed Temp
bedTempMax: getVal('hot_plate_temp_initial_layer') || '',

minTemp: getVal('nozzle_temperature_range_low') || '',
maxTemp: getVal('nozzle_temperature_range_high') || '',

// Defaults for other required fields
colorHex: 'FFFFFF',
spoolmanId: 0,
lotNr: '',
extendedSubType: 'Basic'
};
},

_parseOpenSpoolFormat: function(jsonData) {
// Infer format by checking COMPATIBILITY_FIELDS against raw keys
let format = 'openspool_compat';
for (const [mode, fieldsObj] of Object.entries(this.COMPATIBILITY_FIELDS)) {
Expand Down