Skip to content

Commit 7144cab

Browse files
authored
Merge pull request #25 from bojrodev/dev-merger
Dev merged new function
2 parents 1e3bc3a + 2238f97 commit 7144cab

4 files changed

Lines changed: 341 additions & 22 deletions

File tree

www/index.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,9 @@ <h2 id="appTitle" data-text="RESOLVER">RESOLVER</h2>
713713
<button onclick="document.getElementById('comfyWorkflowFile').click()" class="btn-small dashed">
714714
LOAD NEW TEMPLATE
715715
</button>
716+
<button onclick="openComfyTemplateModal()" class="btn-small" style="margin-top:10px; background:var(--bg-input); width:100%;">
717+
<i data-lucide="folder-heart" size="14" style="margin-right:8px;"></i> LOAD SAVED TEMPLATE
718+
</button>
716719
</div>
717720

718721
<div id="comfy-dynamic-controls" class="col" style="gap: 10px;"></div>
@@ -1147,6 +1150,34 @@ <h3>Magic Prompt</h3>
11471150
</div>
11481151
</div>
11491152

1153+
<div id="comfyTemplateModal" class="modal hidden">
1154+
<div class="modal-content">
1155+
<div class="modal-header">
1156+
<h3>Saved Workflow</h3>
1157+
<button onclick="closeComfyTemplateModal()" class="close-btn">×</button>
1158+
</div>
1159+
1160+
<div class="row" style="gap:8px; margin-bottom:15px;">
1161+
<button id="tmplSelectBtn" onclick="toggleTmplSelectionMode()" class="btn-small" style="flex:1; font-size: 10px;">SELECT</button>
1162+
1163+
<input type="file" id="tmplImportFile" multiple accept=".json" class="hidden" onchange="importMultipleTemplates(event)">
1164+
1165+
<button onclick="document.getElementById('tmplImportFile').click()" class="btn-small" style="flex:1; font-size: 10px;">IMPORT</button>
1166+
1167+
<button onclick="clearAllTemplates()" class="btn-small" style="flex:1; color:var(--error); border-color:var(--error); font-size: 10px;">CLEAR ALL</button>
1168+
</div>
1169+
<div id="tmplList" style="flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:8px; min-height:100px;">
1170+
<div style="text-align:center; color:var(--text-muted); margin-top:20px;">No templates saved yet.</div>
1171+
</div>
1172+
1173+
<button id="tmplDeleteBtn" onclick="deleteSelectedTemplates()" class="btn-main hidden" style="margin-top:15px; background:var(--error) !important; height:44px; font-size:11px !important; white-space:nowrap; padding:0 5px;">
1174+
DELETE SELECTED (<span id="tmplSelCount">0</span>)
1175+
</button>
1176+
</div>
1177+
</div>
1178+
1179+
1180+
11501181
<script src="js/lora.js"></script>
11511182
<script src="js/neo.js"></script>
11521183
<script src="js/globals.js"></script>

www/js/comfy_logic.js

Lines changed: 229 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ let selectedComfyImages = new Set(); // Stores the URLs of selected images
1616

1717
let isComfyGenerating = false;
1818

19+
let selectedTemplates = new Set();
20+
let isTmplSelectionMode = false;
21+
1922
// --- 1. CONNECTION & SETUP ---
2023

2124
function toggleComfyConfig() {
@@ -135,6 +138,7 @@ function loadWorkflowFile(event) {
135138

136139
// Save Persistence
137140
saveComfySession(fileName, jsonStr);
141+
saveTemplateToDB(fileName, jsonStr); // This saves the file to your new database box
138142

139143
if (comfySocket && comfySocket.readyState === WebSocket.OPEN) {
140144
document.getElementById('comfyQueueBtn').disabled = false;
@@ -628,18 +632,28 @@ function saveComfyToMainGallery(url) {
628632
xhr.onload = function() {
629633
const reader = new FileReader();
630634
reader.onloadend = function() {
631-
const request = indexedDB.open("BojroHybridDB", 1);
632-
request.onsuccess = function(event) {
633-
const db = event.target.result;
635+
// FIX: We use the 'db' variable that is already open at version 2
636+
if (!db) {
637+
console.error("Database connection not ready");
638+
return;
639+
}
640+
641+
try {
634642
const tx = db.transaction(["images"], "readwrite");
635643
const store = tx.objectStore("images");
636644
store.add({
637645
data: reader.result,
638646
date: new Date().toLocaleString()
639647
});
640-
console.log("Saved Comfy image to History");
641-
};
642-
}
648+
console.log("Saved to Gallery Successfully!");
649+
650+
// This tells the GAL tab to refresh so you see the image immediately
651+
if (typeof loadGallery === 'function') loadGallery();
652+
653+
} catch (e) {
654+
console.error("Database Error:", e);
655+
}
656+
};
643657
reader.readAsDataURL(xhr.response);
644658
};
645659
xhr.open('GET', url);
@@ -761,24 +775,20 @@ function toggleComfySelectionMode() {
761775
const gallery = document.getElementById('comfyGalleryContainer');
762776
const selectBtn = document.getElementById('comfySelectBtn');
763777
const saveBtn = document.getElementById('comfySaveSelectedBtn');
778+
779+
// 1. Toggle the CSS class (This uses your new style.css rules)
780+
selectBtn.classList.toggle('active', isComfySelectionMode);
781+
782+
// 2. Toggle the text
783+
selectBtn.innerText = isComfySelectionMode ? "CANCEL" : "SELECT";
764784

765785
if (isComfySelectionMode) {
766-
// Enter Selection Mode
767786
gallery.classList.add('selection-mode');
768-
selectBtn.style.background = 'var(--text-main)';
769-
selectBtn.style.color = 'var(--bg-glass)';
770-
selectBtn.innerText = "CANCEL";
771-
selectedComfyImages.clear(); // Reset selection
787+
selectedComfyImages.clear();
772788
updateComfySelectionUI();
773789
} else {
774-
// Exit Selection Mode
775790
gallery.classList.remove('selection-mode');
776-
// Remove visual selection from all items
777791
Array.from(gallery.children).forEach(el => el.classList.remove('selected'));
778-
779-
selectBtn.style.background = '';
780-
selectBtn.style.color = '';
781-
selectBtn.innerText = "SELECT";
782792
saveBtn.classList.add('hidden');
783793
}
784794
}
@@ -834,5 +844,207 @@ function saveSelectedComfyImages() {
834844
}, 1000);
835845
}
836846

847+
848+
// 1. Function to actually put the file in the database
849+
function saveTemplateToDB(name, json) {
850+
if (!db) return;
851+
const tx = db.transaction(["comfy_templates"], "readwrite");
852+
const store = tx.objectStore("comfy_templates");
853+
store.put({ name: name, data: json, date: new Date().toLocaleString() });
854+
}
855+
856+
// 2. Open the popup and show the list
857+
async function openComfyTemplateModal() {
858+
document.getElementById('comfyTemplateModal').classList.remove('hidden');
859+
renderTemplateList();
860+
if(window.lucide) lucide.createIcons();
861+
}
862+
863+
// 3. Close the popup and reset settings
864+
function closeComfyTemplateModal() {
865+
document.getElementById('comfyTemplateModal').classList.add('hidden');
866+
isTmplSelectionMode = false;
867+
selectedTemplates.clear();
868+
const btn = document.getElementById('tmplSelectBtn');
869+
btn.innerText = "SELECT";
870+
document.getElementById('tmplDeleteBtn').classList.add('hidden');
871+
}
872+
873+
// 4. Create the list items you see on screen
874+
async function renderTemplateList() {
875+
const list = document.getElementById('tmplList');
876+
list.innerHTML = "";
877+
878+
if (!db) return;
879+
const tx = db.transaction(["comfy_templates"], "readonly");
880+
const store = tx.objectStore("comfy_templates");
881+
882+
// Get everything from the box
883+
store.getAll().onsuccess = (e) => {
884+
const all = e.target.result;
885+
886+
if (all.length === 0) {
887+
list.innerHTML = '<div style="text-align:center; color:var(--text-muted); margin-top:20px;">No templates saved yet.</div>';
888+
return;
889+
}
890+
891+
all.forEach(tmpl => {
892+
const div = document.createElement('div');
893+
// If it's selected, give it the 'selected' look from your CSS
894+
div.className = `tmpl-item ${selectedTemplates.has(tmpl.name) ? 'selected' : ''}`;
895+
div.style.marginBottom = "8px";
896+
897+
div.innerHTML = `
898+
<div class="tmpl-info">
899+
<span class="tmpl-name">${tmpl.name}</span>
900+
<span style="font-size:9px; color:var(--text-muted);">${tmpl.date}</span>
901+
</div>
902+
${selectedTemplates.has(tmpl.name) ? '<i data-lucide="check-circle" size="14" style="color:var(--error);"></i>' : ''}
903+
`;
904+
905+
div.onclick = () => handleTmplClick(tmpl);
906+
list.appendChild(div);
907+
});
908+
if(window.lucide) lucide.createIcons();
909+
};
910+
}
911+
912+
// 5. What happens when you tap a template in the list
913+
function handleTmplClick(tmpl) {
914+
if (isTmplSelectionMode) {
915+
// Just highlight/unhighlight if we are in deleting mode
916+
if (selectedTemplates.has(tmpl.name)) {
917+
selectedTemplates.delete(tmpl.name);
918+
} else {
919+
selectedTemplates.add(tmpl.name);
920+
}
921+
updateTmplUI();
922+
} else {
923+
// Actually LOAD the template if we are in normal mode
924+
try {
925+
comfyLoadedWorkflow = JSON.parse(tmpl.data);
926+
document.getElementById('comfyLoadedFileName').innerText = tmpl.name;
927+
buildComfyUI(comfyLoadedWorkflow);
928+
929+
// Re-enable generate button if connected
930+
if (comfySocket && comfySocket.readyState === WebSocket.OPEN) {
931+
document.getElementById('comfyQueueBtn').disabled = false;
932+
}
933+
934+
closeComfyTemplateModal();
935+
} catch (e) {
936+
alert("Error loading template: " + e.message);
937+
}
938+
}
939+
}
940+
941+
// 6. Turn selection mode ON or OFF
942+
function toggleTmplSelectionMode() {
943+
isTmplSelectionMode = !isTmplSelectionMode;
944+
const btn = document.getElementById('tmplSelectBtn');
945+
946+
if (isTmplSelectionMode) {
947+
// We only change the text and add the CSS class
948+
btn.innerText = "CANCEL";
949+
btn.classList.add('btn-cancel-active');
950+
} else {
951+
// We change the text back and remove the CSS class
952+
btn.innerText = "SELECT";
953+
btn.classList.remove('btn-cancel-active');
954+
selectedTemplates.clear();
955+
}
956+
updateTmplUI();
957+
}
958+
959+
// 7. Refresh the screen and show/hide the Delete button
960+
function updateTmplUI() {
961+
renderTemplateList();
962+
const count = selectedTemplates.size;
963+
document.getElementById('tmplSelCount').innerText = count;
964+
965+
const delBtn = document.getElementById('tmplDeleteBtn');
966+
if (count > 0 && isTmplSelectionMode) {
967+
delBtn.classList.remove('hidden');
968+
} else {
969+
delBtn.classList.add('hidden');
970+
}
971+
}
972+
973+
// 8. Delete only the ones you checked
974+
function deleteSelectedTemplates() {
975+
if (selectedTemplates.size === 0) return;
976+
977+
if (confirm(`Delete ${selectedTemplates.size} selected templates?`)) {
978+
const tx = db.transaction(["comfy_templates"], "readwrite");
979+
const store = tx.objectStore("comfy_templates");
980+
981+
selectedTemplates.forEach(name => {
982+
store.delete(name);
983+
});
984+
985+
tx.oncomplete = () => {
986+
selectedTemplates.clear();
987+
isTmplSelectionMode = false;
988+
updateTmplUI();
989+
const btn = document.getElementById('tmplSelectBtn');
990+
btn.innerText = "SELECT";
991+
btn.style.background = "";
992+
btn.style.color = "";
993+
};
994+
}
995+
}
996+
997+
// 9. Wipe everything
998+
function clearAllTemplates() {
999+
if (confirm("Delete ALL saved templates? This cannot be undone.")) {
1000+
const tx = db.transaction(["comfy_templates"], "readwrite");
1001+
tx.objectStore("comfy_templates").clear();
1002+
tx.oncomplete = () => {
1003+
selectedTemplates.clear();
1004+
isTmplSelectionMode = false;
1005+
renderTemplateList();
1006+
updateTmplUI();
1007+
};
1008+
}
1009+
}
1010+
1011+
// Function to import many JSON files at once
1012+
// Upgraded Import Function
1013+
async function importMultipleTemplates(event) {
1014+
const files = event.target.files;
1015+
if (!files || files.length === 0) return;
1016+
1017+
// Create a list of "tasks" for every file
1018+
const tasks = Array.from(files).map(file => {
1019+
return new Promise((resolve) => {
1020+
const reader = new FileReader();
1021+
reader.onload = (e) => {
1022+
try {
1023+
const jsonStr = e.target.result;
1024+
JSON.parse(jsonStr); // Check if it's a real workflow
1025+
saveTemplateToDB(file.name.toUpperCase(), jsonStr);
1026+
} catch (err) {
1027+
console.error("Skipped bad file: " + file.name);
1028+
}
1029+
resolve(); // Always finish, even if the file was bad
1030+
};
1031+
reader.readAsText(file);
1032+
});
1033+
});
1034+
1035+
// Wait for ALL files to finish loading
1036+
await Promise.all(tasks);
1037+
1038+
// Refresh the list once at the end
1039+
renderTemplateList();
1040+
1041+
// Clear the button so you can import again
1042+
event.target.value = "";
1043+
1044+
if(typeof Toast !== 'undefined') {
1045+
Toast.show({ text: `Import process complete`, duration: 'short' });
1046+
}
1047+
}
1048+
8371049
// 8. AUTO-INIT ON LOAD
8381050
document.addEventListener('DOMContentLoaded', restoreComfySession);

www/js/utils.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,16 @@ function getVramMapping() {
198198

199199
// Wrapper function to initialize DB (called in boot.js)
200200
function initDatabase() {
201-
const request = indexedDB.open("BojroHybridDB", 1);
201+
const request = indexedDB.open("BojroHybridDB", 2)
202202
request.onupgradeneeded = e => {
203203
db = e.target.result;
204-
db.createObjectStore("images", {
205-
keyPath: "id",
206-
autoIncrement: true
207-
});
204+
if (!db.objectStoreNames.contains("images")) {
205+
db.createObjectStore("images", { keyPath: "id", autoIncrement: true });
206+
}
207+
208+
if (!db.objectStoreNames.contains("comfy_templates")) {
209+
db.createObjectStore("comfy_templates", { keyPath: "name" });
210+
}
208211
};
209212
request.onsuccess = e => {
210213
db = e.target.result;

0 commit comments

Comments
 (0)