fix: tags deep link renders and admin modals close
admin.html loaded tags-page.js after admin.js, so opening the page at #/tags found no TagsPageManager and rendered nothing; the script order is now theme-bridge, html-escape, api-client, png-metadata, (list-state, sse-stream, data-actions), viewer, comfy-metadata, prompt-list-sort, view-router, tags-page, admin. Route resolution lives in the pure ViewRouter.resolveView(). The auto-tag modal's close buttons called a bare hideModal(), which only gallery.js defines; they call window.admin.hideModal(). PromptAdmin is constructed from startAdmin() on DOMContentLoaded (or immediately when the document is already parsed) instead of at script evaluation time.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
// Run with: node --test tests/js/
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const { resolveView, VIEWS } = require("../../web/js/view-router.js");
|
||||
|
||||
test("the tags deep link and its sub-routes resolve to the tags view", () => {
|
||||
assert.equal(resolveView("#/tags"), "tags");
|
||||
assert.equal(resolveView("#/tags/"), "tags");
|
||||
assert.equal(resolveView("#/tags/landscape,portrait?mode=or"), "tags");
|
||||
});
|
||||
|
||||
test("an empty, bare or unknown hash resolves to the prompts dashboard", () => {
|
||||
assert.equal(resolveView(""), "prompts");
|
||||
assert.equal(resolveView("#"), "prompts");
|
||||
assert.equal(resolveView("#/"), "prompts");
|
||||
assert.equal(resolveView("#/other"), "prompts");
|
||||
assert.equal(resolveView("#/tagsX"), "prompts", "prefix must end at a path boundary");
|
||||
assert.equal(resolveView("#tags"), "prompts");
|
||||
});
|
||||
|
||||
test("non-string input resolves to the prompts dashboard", () => {
|
||||
assert.equal(resolveView(undefined), "prompts");
|
||||
assert.equal(resolveView(null), "prompts");
|
||||
assert.equal(resolveView(42), "prompts");
|
||||
});
|
||||
|
||||
test("every resolved value is a listed view", () => {
|
||||
assert.ok(Object.isFrozen(VIEWS));
|
||||
for (const hash of ["#/tags", "", "#/x"]) assert.ok(VIEWS.includes(resolveView(hash)));
|
||||
});
|
||||
+4
-3
@@ -1043,7 +1043,7 @@
|
||||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-lg w-full mx-4 border border-pm shadow-pm">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-sm font-semibold text-pm">Auto Tag Images</h3>
|
||||
<button onclick="hideModal('autoTagModal')" class="p-1 rounded-pm-sm hover:bg-pm-hover transition-colors">
|
||||
<button onclick="window.admin.hideModal('autoTagModal')" class="p-1 rounded-pm-sm hover:bg-pm-hover transition-colors">
|
||||
<svg class="w-4 h-4 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
@@ -1157,7 +1157,7 @@ No sentences, no commentary, no captions. Only tags. Keep length short but descr
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-end gap-2">
|
||||
<button onclick="hideModal('autoTagModal')"
|
||||
<button onclick="window.admin.hideModal('autoTagModal')"
|
||||
class="px-3 py-1.5 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||||
Cancel
|
||||
</button>
|
||||
@@ -1399,7 +1399,8 @@ No sentences, no commentary, no captions. Only tags. Keep length short but descr
|
||||
|
||||
<script src="/prompt_manager/js/comfy-metadata.js"></script>
|
||||
<script src="/prompt_manager/js/prompt-list-sort.js"></script>
|
||||
<script src="/prompt_manager/js/admin.js"></script>
|
||||
<script src="/prompt_manager/js/view-router.js"></script>
|
||||
<script src="/prompt_manager/js/tags-page.js"></script>
|
||||
<script src="/prompt_manager/js/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+36
-28
@@ -60,8 +60,7 @@
|
||||
}
|
||||
|
||||
handleRoute() {
|
||||
const hash = window.location.hash;
|
||||
if (hash === '#/tags' || hash.startsWith('#/tags/')) {
|
||||
if (ViewRouter.resolveView(window.location.hash) === "tags") {
|
||||
this.showTagsPage();
|
||||
} else {
|
||||
this.showDashboard();
|
||||
@@ -3866,33 +3865,42 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the admin interface
|
||||
const admin = new PromptAdmin();
|
||||
window.admin = admin;
|
||||
/** Build the dashboard; needs the DOM, so it runs on DOMContentLoaded. */
|
||||
function startAdmin() {
|
||||
// Initialize the admin interface
|
||||
const admin = new PromptAdmin();
|
||||
window.admin = admin;
|
||||
|
||||
// Add event listeners for prompt selection
|
||||
document.addEventListener("change", function (e) {
|
||||
if (e.target.classList.contains("prompt-checkbox")) {
|
||||
const promptId = parseInt(e.target.dataset.id);
|
||||
if (e.target.checked) {
|
||||
admin.selectedPrompts.add(promptId);
|
||||
} else {
|
||||
admin.selectedPrompts.delete(promptId);
|
||||
// Add event listeners for prompt selection
|
||||
document.addEventListener("change", function (e) {
|
||||
if (e.target.classList.contains("prompt-checkbox")) {
|
||||
const promptId = parseInt(e.target.dataset.id);
|
||||
if (e.target.checked) {
|
||||
admin.selectedPrompts.add(promptId);
|
||||
} else {
|
||||
admin.selectedPrompts.delete(promptId);
|
||||
}
|
||||
admin.updateBulkActionButtons();
|
||||
|
||||
const allCheckboxes = document.querySelectorAll(".prompt-checkbox");
|
||||
const checkedCheckboxes = document.querySelectorAll(".prompt-checkbox:checked");
|
||||
const selectAllCheckbox = document.getElementById("selectAll");
|
||||
selectAllCheckbox.checked = allCheckboxes.length === checkedCheckboxes.length;
|
||||
selectAllCheckbox.indeterminate = checkedCheckboxes.length > 0 && checkedCheckboxes.length < allCheckboxes.length;
|
||||
}
|
||||
admin.updateBulkActionButtons();
|
||||
});
|
||||
|
||||
const allCheckboxes = document.querySelectorAll(".prompt-checkbox");
|
||||
const checkedCheckboxes = document.querySelectorAll(".prompt-checkbox:checked");
|
||||
const selectAllCheckbox = document.getElementById("selectAll");
|
||||
selectAllCheckbox.checked = allCheckboxes.length === checkedCheckboxes.length;
|
||||
selectAllCheckbox.indeterminate = checkedCheckboxes.length > 0 && checkedCheckboxes.length < allCheckboxes.length;
|
||||
}
|
||||
});
|
||||
// Escape closes the gallery modal
|
||||
document.addEventListener('keydown', function (e) {
|
||||
const galleryModal = document.getElementById('galleryModal');
|
||||
if (e.key === 'Escape' && galleryModal && !galleryModal.classList.contains('hidden')) {
|
||||
admin.closeGallery();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Escape closes the gallery modal
|
||||
document.addEventListener('keydown', function (e) {
|
||||
const galleryModal = document.getElementById('galleryModal');
|
||||
if (e.key === 'Escape' && galleryModal && !galleryModal.classList.contains('hidden')) {
|
||||
admin.closeGallery();
|
||||
}
|
||||
});
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", startAdmin);
|
||||
} else {
|
||||
startAdmin();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Hash-route resolution for the admin page: which top-level view a
|
||||
* location.hash selects. Kept pure so the #/tags deep link is testable.
|
||||
*
|
||||
* Loadable in the browser (window.ViewRouter) and in Node for tests.
|
||||
*/
|
||||
(function (root) {
|
||||
"use strict";
|
||||
|
||||
const VIEWS = Object.freeze(["prompts", "tags"]);
|
||||
const TAGS_PREFIX = "#/tags";
|
||||
|
||||
/**
|
||||
* @param {string} hash - window.location.hash, including the leading "#"
|
||||
* @returns {"prompts"|"tags"}
|
||||
*/
|
||||
function resolveView(hash) {
|
||||
if (typeof hash !== "string") return "prompts";
|
||||
if (hash === TAGS_PREFIX) return "tags";
|
||||
if (hash.startsWith(TAGS_PREFIX + "/")) return "tags";
|
||||
return "prompts";
|
||||
}
|
||||
|
||||
const api = { resolveView, VIEWS };
|
||||
if (typeof module !== "undefined" && module.exports) module.exports = api;
|
||||
else root.ViewRouter = api;
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
Reference in New Issue
Block a user