Skip to content

Commit 765c9ed

Browse files
authored
Add LED settings, theme name, custom theme height and minor refactoring (BruceDevices#1645)
* Add LED settings and minor refactoring * Add ethernet menu item * Change T-LoRa-Pager height to 192px
1 parent d2a453e commit 765c9ed

File tree

1 file changed

+198
-83
lines changed

1 file changed

+198
-83
lines changed

static/build_theme.html

Lines changed: 198 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<!DOCTYPE html>
22
<html lang="en">
3+
34
<head>
45
<meta charset="UTF-8">
56
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -11,7 +12,7 @@
1112
--background-color: #000000;
1213
--text-color: #ffffff;
1314
--border-radius: 4px;
14-
--box-shadow: 0 2px 4px rgba(0,0,0,0.3);
15+
--box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
1516
}
1617

1718
* {
@@ -212,7 +213,26 @@
212213
border: none;
213214
}
214215

215-
.quality-value {
216+
input[type="range"]:disabled {
217+
background: color-mix(in srgb, var(--background-color), white 25%);
218+
;
219+
}
220+
221+
input[type="range"]:disabled::-webkit-slider-thumb {
222+
background: color-mix(in srgb, var(--primary-color), black 75%);
223+
;
224+
cursor: not-allowed;
225+
}
226+
227+
input[type="range"]:disabled::-moz-range-thumb {
228+
background: color-mix(in srgb, var(--primary-color), black 75%);
229+
;
230+
cursor: not-allowed;
231+
}
232+
233+
.quality-value,
234+
.ledBrightness-value,
235+
.ledEffectSpeed-value {
216236
text-align: center;
217237
font-size: 0.9rem;
218238
color: var(--primary-color);
@@ -431,7 +451,8 @@
431451
color: var(--primary-color);
432452
}
433453

434-
.form-select {
454+
.form-select,
455+
.form-text {
435456
width: 100%;
436457
padding: 8px;
437458
border: 1px solid var(--primary-color);
@@ -442,11 +463,13 @@
442463
cursor: pointer;
443464
}
444465

445-
.form-select:hover {
446-
background: rgba(0, 255, 0, 0.1);
466+
.form-select:hover,
467+
.form-text:hover {
468+
background: rgba(153, 0, 255, 0.1);
447469
}
448470

449-
.form-select:focus {
471+
.form-select:focus,
472+
.form-text:focus {
450473
outline: none;
451474
border-color: var(--primary-color);
452475
box-shadow: 0 0 10px var(--primary-color);
@@ -480,6 +503,7 @@
480503
}
481504
</style>
482505
</head>
506+
483507
<body>
484508
<div class="container">
485509
<header class="header">
@@ -498,19 +522,30 @@ <h1>Bruce Theme Builder</h1>
498522

499523
<div class="controls-section">
500524
<div class="control-group">
525+
<div class="control-item">
526+
<label for="themeNameInput">Theme Name:</label>
527+
<input type="text" id="themeNameInput" class="form-text" />
528+
</div>
501529
<div class="control-item">
502530
<label for="heightInput">Target Device:</label>
503-
<select id="heightInput" class="form-select">
504-
<option value="0">All devices</option>
531+
<select id="heightInput" class="form-select"
532+
onchange="document.getElementById('heightInputCustom').parentElement.style.display = this.value === 'CUSTOM' ? 'block' : 'none';">
533+
<option value="105,140,180,192">All devices</option>
505534
<option value="105">StickCPlus and Cardputer (105px)</option>
506535
<option value="140">Lilygo T-Embed (140px)</option>
507536
<option value="180">CYD, Core, T-Deck (180px)</option>
508-
<option value="222">T-LoRa Pager (222px)</option>
537+
<option value="192">T-LoRa-Pager (192px)</option>
538+
<option value="CUSTOM">Custom</option>
509539
</select>
510540
</div>
541+
<div class="control-item" style="display: none;">
542+
<label for="heightInputCustom">Custom Height(s):</label>
543+
<input type="text" id="heightInputCustom" class="form-text" value="105,140,180,222" />
544+
</div>
511545
<div class="control-item">
512546
<label for="qualityInput">Quality:</label>
513-
<input type="range" id="qualityInput" min="0" max="1" step="0.1" value="0.8" oninput="updateQualityValue(this.value)">
547+
<input type="range" id="qualityInput" min="0" max="1" step="0.1" value="0.8"
548+
oninput="updateQualityValue(this.value)">
514549
<div id="qualityValue" class="quality-value">0.8</div>
515550
</div>
516551
</div>
@@ -541,6 +576,49 @@ <h1>Bruce Theme Builder</h1>
541576
</div>
542577
</div>
543578

579+
<div class="controls-section">
580+
<div class="control-group">
581+
<div class="control-item">
582+
<label for="ledColor">LED Color:</label>
583+
<input type="color" id="ledColor" value="#ad007b">
584+
</div>
585+
<div class="control-item">
586+
<label for="ledBrightnessInput">LED Brightness:</label>
587+
<input type="range" id="ledBrightnessInput" min="0" max="100" step="5" value="50"
588+
oninput="updateLEDBrightnessValue(this.value)">
589+
<div id="ledBrightnessValue" class="ledBrightness-value">50</div>
590+
</div>
591+
</div>
592+
<div class="control-group">
593+
<div class="control-item">
594+
595+
<label for="ledEffectInput">LED Effect:</label>
596+
<select id="ledEffectInput" class="form-select" onchange="updateLEDEffectControls(this.value)">
597+
<option value="0">Solid Color</option>
598+
<option value="1">Breathe</option>
599+
<option value="2">Color Cycle</option>
600+
<option value="3">Color Wheel</option>
601+
<option value="4">Chase</option>
602+
<option value="5">Chase Tail</option>
603+
</select>
604+
</div>
605+
<div class="control-item">
606+
<label for="ledEffectSpeedInput">Effect Speed:</label>
607+
<input type="range" id="ledEffectSpeedInput" min="0" max="10" step="1" value="5"
608+
oninput="updateLEDEffectSpeedValue(this.value)" disabled>
609+
<div id="ledEffectSpeedValue" class="ledEffectSpeed-value">5</div>
610+
</div>
611+
<div class="border-control">
612+
<input type="checkbox" id="ledEffectDirection" disabled>
613+
<label for="ledEffectDirection">Reverse Effect Direction</label>
614+
</div>
615+
<div class="border-control">
616+
<input type="checkbox" id="ledSyncToEncoder" disabled>
617+
<label for="ledSyncToEncoder">Sync Effect to Encoder Wheel (where available)</label>
618+
</div>
619+
</div>
620+
</div>
621+
544622
<div class="bulk-upload">
545623
<div class="bulk-drop-zone" id="bulkDropZone">
546624
<div class="bulk-upload-text">
@@ -553,12 +631,12 @@ <h1>Bruce Theme Builder</h1>
553631

554632
<div class="image-inputs" id="imageInputs"></div>
555633

556-
<button onclick="resizeImages()">Resize and Download</button>
634+
<button onclick="generateAndDownload()">Generate and Download</button>
557635
</div>
558636

559637
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script>
560638
<script>
561-
const fileNames = ["wifi", "ble", "rf", "rfid", "ir", "fm", "files", "gps", "nrf", "interpreter", "others", "clock", "connect", "config"];
639+
const fileNames = ["wifi", "ble", "ethernet", "rf", "rfid", "ir", "fm", "files", "gps", "nrf", "interpreter", "others", "clock", "connect", "config"];
562640
//const fileNames = ["wifi", "ble", "rf"];
563641
const selectedImages = {};
564642
const placeholderUrl = "https://via.placeholder.com/150";
@@ -685,104 +763,140 @@ <h1>Bruce Theme Builder</h1>
685763
});
686764
}
687765

688-
function rgbToRGB565(hex) {
766+
function rgbToRGB565(hex) {
689767
let r = parseInt(hex.substring(1, 3), 16) >> 3;
690768
let g = parseInt(hex.substring(3, 5), 16) >> 2;
691769
let b = parseInt(hex.substring(5, 7), 16) >> 3;
692770
return ((r << 11) | (g << 5) | b).toString(16).padStart(4, '0');
693771
}
694772

695-
function resizeImages() {
773+
async function downloadZip(zip, themeName) {
774+
const content = await zip.generateAsync({ type: "blob" });
775+
const link = document.createElement('a');
776+
link.href = URL.createObjectURL(content);
777+
link.download = `Theme_${themeName}.zip`;
778+
document.body.appendChild(link);
779+
link.click();
780+
document.body.removeChild(link);
781+
}
782+
783+
function getCommonMapping() {
784+
return {
785+
priColor: rgbToRGB565(document.getElementById('priColor').value),
786+
secColor: rgbToRGB565(document.getElementById('secColor').value),
787+
bgColor: rgbToRGB565(document.getElementById('bgColor').value),
788+
ledBright: document.getElementById('ledBrightnessInput').value,
789+
ledColor: document.getElementById('ledColor').value.substring(0, 7),
790+
ledEffect: document.getElementById('ledEffectInput').value,
791+
ledEffectSpeed: document.getElementById('ledSyncToEncoder').checked ? 11 : document.getElementById('ledEffectSpeedInput').value,
792+
ledEffectDirection: document.getElementById('ledEffectDirection').checked ? -1 : 1
793+
};
794+
}
795+
796+
async function generateAndDownload() {
696797
const zip = new JSZip();
697-
const randomId = Math.floor(100 + Math.random() * 900);
698-
const targetHeight = parseInt(document.getElementById('heightInput').value);
798+
const themeName = document.getElementById("themeNameInput").value.replace(" ", "_") || "Theme_" + Math.floor(100 + Math.random() * 900);
799+
800+
const heightInput = document.getElementById('heightInput').value.trim();
801+
const customHeights = document.getElementById('heightInputCustom').value.trim();
802+
const heightsToUse = heightInput === 'CUSTOM' ? customHeights : heightInput;
803+
const heights = heightsToUse.split(',').map(h => parseInt(h.trim())).filter(h => !isNaN(h) && h > 0);
804+
699805
const quality = parseFloat(document.getElementById('qualityInput').value);
806+
700807
const hasLabels = document.getElementById('labels').checked;
701808
const hasBorder = document.getElementById('border').checked;
809+
const totalToProcess = heights.length * Object.keys(selectedImages).length;
810+
811+
if (totalToProcess === 0) {
812+
zip.file(`Theme_${themeName}.json`, JSON.stringify(getCommonMapping(), null, 2));
813+
await downloadZip(zip, themeName);
814+
return;
815+
}
702816

703-
// Define as alturas que precisamos processar
704-
const heights = targetHeight === 0 ? [105, 140, 180] : [targetHeight];
705817
let totalProcessed = 0;
706-
const totalToProcess = heights.length * Object.keys(selectedImages).length;
707818

708-
heights.forEach(height => {
709-
const folderName = `Theme_${randomId}/${height}px`;
819+
for (const height of heights) {
820+
const folderName = `Theme_${themeName}/${height}px`;
710821
const imageFolder = zip.folder(folderName);
711822
const jsonMapping = {};
712823

713-
fileNames.forEach(name => {
824+
await Promise.all(fileNames.map(async name => {
714825
if (!selectedImages[name]) return;
715826

716827
const file = selectedImages[name];
717828
const img = new Image();
718-
img.onload = function() {
719-
const canvas = document.createElement('canvas');
720-
const ctx = canvas.getContext('2d');
721-
722-
// Ajusta a altura baseado no checkbox Labels
723-
const effectiveHeight = hasLabels ? height - 40 : height;
724-
725-
// Só redimensiona se a imagem for maior que o alvo
726-
let newWidth, newHeight;
727-
if (img.height > effectiveHeight) {
728-
const aspectRatio = img.width / img.height;
729-
newWidth = Math.round(effectiveHeight * aspectRatio);
730-
newHeight = effectiveHeight;
731-
} else {
732-
newWidth = img.width;
733-
newHeight = img.height;
734-
}
829+
await new Promise(resolve => {
830+
img.onload = async function () {
831+
const canvas = document.createElement('canvas');
832+
const ctx = canvas.getContext('2d');
833+
const effectiveHeight = hasLabels ? height - 40 : height;
834+
let newWidth, newHeight;
835+
if (img.height > effectiveHeight) {
836+
const aspectRatio = img.width / img.height;
837+
newWidth = Math.round(effectiveHeight * aspectRatio);
838+
newHeight = effectiveHeight;
839+
} else {
840+
newWidth = img.width;
841+
newHeight = img.height;
842+
}
843+
canvas.width = newWidth;
844+
canvas.height = newHeight;
845+
ctx.drawImage(img, 0, 0, newWidth, newHeight);
846+
847+
let fileType = file.type;
848+
let fileExtension = fileType.split('/')[1];
849+
const baseName = file.name.substring(0, file.name.lastIndexOf(".")) || file.name;
850+
let imageDataUrl = canvas.toDataURL(fileType, quality);
851+
852+
const blob = await fetch(imageDataUrl).then(res => res.blob());
853+
const fileName = `${baseName}.${fileExtension}`;
854+
jsonMapping[name] = fileName;
855+
imageFolder.file(fileName, blob);
856+
totalProcessed++;
857+
resolve();
858+
};
859+
img.src = URL.createObjectURL(file);
860+
});
861+
}));
862+
863+
if (Object.keys(selectedImages).length === Object.keys(jsonMapping).length) {
864+
Object.assign(jsonMapping, {
865+
border: hasBorder ? 1 : 0,
866+
label: hasLabels ? 1 : 0,
867+
...getCommonMapping()
868+
});
869+
imageFolder.file(`Theme_${themeName}.json`, JSON.stringify(jsonMapping, null, 2));
870+
}
871+
}
735872

736-
canvas.width = newWidth;
737-
canvas.height = newHeight;
738-
ctx.drawImage(img, 0, 0, newWidth, newHeight);
739-
740-
let fileType = file.type;
741-
let fileExtension = fileType.split('/')[1];
742-
const baseName = file.name.substring(0, file.name.lastIndexOf(".")) || file.name;
743-
let imageDataUrl = canvas.toDataURL(fileType, quality);
744-
745-
fetch(imageDataUrl)
746-
.then(res => res.blob())
747-
.then(blob => {
748-
const fileName = `${baseName}.${fileExtension}`;
749-
jsonMapping[name] = fileName;
750-
imageFolder.file(fileName, blob);
751-
totalProcessed++;
752-
753-
// Verifica se é o último arquivo desta altura
754-
if (Object.keys(selectedImages).length === Object.keys(jsonMapping).length) {
755-
jsonMapping["priColor"] = rgbToRGB565(document.getElementById('priColor').value);
756-
jsonMapping["secColor"] = rgbToRGB565(document.getElementById('secColor').value);
757-
jsonMapping["bgColor"] = rgbToRGB565(document.getElementById('bgColor').value);
758-
jsonMapping["border"] = hasBorder ? 1 : 0;
759-
jsonMapping["label"] = hasLabels ? 1 : 0;
760-
imageFolder.file(`Theme_${randomId}.json`, JSON.stringify(jsonMapping, null, 2));
761-
}
762-
763-
// Verifica se é o último arquivo de todos
764-
if (totalProcessed === totalToProcess) {
765-
zip.generateAsync({ type: "blob" }).then(content => {
766-
// Cria um link temporário para download
767-
const link = document.createElement('a');
768-
link.href = URL.createObjectURL(content);
769-
link.download = 'theme.zip';
770-
document.body.appendChild(link);
771-
link.click();
772-
document.body.removeChild(link);
773-
});
774-
}
775-
});
776-
};
777-
img.src = URL.createObjectURL(file);
778-
});
779-
});
873+
if (totalProcessed === totalToProcess) {
874+
await downloadZip(zip, themeName);
875+
}
780876
}
781877

782878
function updateQualityValue(value) {
783879
document.getElementById('qualityValue').textContent = value;
784880
}
785881

882+
function updateLEDBrightnessValue(value) {
883+
document.getElementById('ledBrightnessValue').textContent = value;
884+
}
885+
886+
function updateLEDEffectSpeedValue(value) {
887+
document.getElementById('ledEffectSpeedValue').textContent = value;
888+
}
889+
890+
function updateLEDEffectControls(value) {
891+
const speedInput = document.getElementById('ledEffectSpeedInput');
892+
const directionCheckbox = document.getElementById('ledEffectDirection');
893+
const syncCheckbox = document.getElementById('ledSyncToEncoder');
894+
895+
speedInput.disabled = value === "0";
896+
directionCheckbox.disabled = value === "0";
897+
syncCheckbox.disabled = value === "0";
898+
}
899+
786900
function setupDragAndDrop() {
787901
// Setup para área de upload em massa
788902
const bulkDropZone = document.getElementById('bulkDropZone');
@@ -901,4 +1015,5 @@ <h1>Bruce Theme Builder</h1>
9011015
updateColorDisplay();
9021016
</script>
9031017
</body>
1018+
9041019
</html>

0 commit comments

Comments
 (0)