Skip to content
Merged
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
47 changes: 44 additions & 3 deletions photomap/frontend/static/javascript/album-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export class AlbumManager {
slideshowTitle: document.getElementById("slideshow_title"),
albumManagementContent: document.querySelector("#albumManagementContent"),
// InvokeAI board-album source controls (add form)
newAlbumSourceGroup: document.getElementById("newAlbumSourceGroup"),
newAlbumDirectorySection: document.getElementById("newAlbumDirectorySection"),
newAlbumInvokeAISection: document.getElementById("newAlbumInvokeAISection"),
newAlbumInvokeUrl: document.getElementById("newAlbumInvokeUrl"),
Expand Down Expand Up @@ -323,6 +324,16 @@ export class AlbumManager {
return checked ? checked.value : "directory";
}

// Whether a global InvokeAI backend URL is configured in settings.
async _isInvokeAIConfigured() {
try {
const config = await fetchJson("invokeai/config");
return !!(config.url && config.url.trim());
} catch {
return false;
}
}

setupNewAlbumSourceControls() {
document.querySelectorAll('input[name="newAlbumSourceType"]').forEach((radio) => {
radio.addEventListener("change", () => this.toggleNewAlbumSourceSections());
Expand Down Expand Up @@ -366,6 +377,22 @@ export class AlbumManager {
}
}

// Show or hide the album-source chooser based on whether a global InvokeAI
// backend is configured. With no backend the board option is meaningless, so
// we hide the radios and force the directory source.
_setNewAlbumInvokeAvailable(available) {
if (this.elements.newAlbumSourceGroup) {
this.elements.newAlbumSourceGroup.hidden = !available;
}
if (!available) {
const directoryRadio = document.querySelector('input[name="newAlbumSourceType"][value="directory"]');
if (directoryRadio) {
directoryRadio.checked = true;
}
this.toggleNewAlbumSourceSections();
}
}

// Build the InvokeAI-root input row: a text input plus the same 📁
// filetree-picker button used by the image-path rows.
_createInvokeRootRow(container, initialValue = "") {
Expand Down Expand Up @@ -499,10 +526,12 @@ export class AlbumManager {
// Prefill the URL — and surface the credential fallback — from the
// global InvokeAI settings when available.
this._settingsInvokeDefaults = null;
let hasBackend = false;
if (this.elements.newAlbumInvokeUrl) {
this.elements.newAlbumInvokeUrl.value = "";
try {
const config = await fetchJson("invokeai/config");
hasBackend = !!(config.url && config.url.trim());
if (config.url) {
this.elements.newAlbumInvokeUrl.value = config.url;
}
Expand All @@ -516,6 +545,8 @@ export class AlbumManager {
}
this._applySettingsCredentialDefaults();
}
// The board-album option only makes sense with a configured backend.
this._setNewAlbumInvokeAvailable(hasBackend);
}

// The backend reuses the settings-panel credentials when the album form
Expand Down Expand Up @@ -1261,9 +1292,19 @@ export class AlbumManager {

// Reflect the (immutable) source type and show the matching section
const isBoardAlbum = album.source_type === "invokeai_board";
editForm.querySelectorAll(".edit-album-source-radio").forEach((radio) => {
radio.checked = radio.value === (album.source_type || "directory");
});
const sourceDisplay = editForm.querySelector(".edit-album-source-display");
if (sourceDisplay) {
sourceDisplay.textContent = isBoardAlbum ? "An InvokeAI Image Gallery Board" : "Directory of Image Files";
}
// Show the (immutable) source line only when an InvokeAI backend is
// configured — or when this album is itself a board album, so its type is
// never silently hidden.
const sourceGroup = editForm.querySelector(".edit-album-source-group");
if (sourceGroup) {
this._isInvokeAIConfigured().then((available) => {
sourceGroup.hidden = !(available || isBoardAlbum);
});
}
const directorySection = editForm.querySelector(".edit-album-directory-section");
const invokeSection = editForm.querySelector(".edit-album-invokeai-section");
if (directorySection) {
Expand Down
18 changes: 4 additions & 14 deletions photomap/frontend/templates/modules/album-manager.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ <h3>Add New Album</h3>
<input id="newAlbumDescription" type="text" placeholder="Optional description" />
</div>

<div class="form-group">
<div class="form-group" id="newAlbumSourceGroup" hidden>
<label>Album Source</label>
<div class="album-source-radios">
<label class="radio-label">
Expand Down Expand Up @@ -166,19 +166,9 @@ <h4 class="edit-album-title">Edit Album</h4>
<input class="edit-album-description" type="text" />
</div>
</div>
<div class="form-group">
<label>Album Source</label>
<div class="album-source-radios">
<label class="radio-label">
<input type="radio" class="edit-album-source-radio" value="directory" disabled />
Directory of Image Files
</label>
<label class="radio-label">
<input type="radio" class="edit-album-source-radio" value="invokeai_board" disabled />
An InvokeAI Image Gallery Board
</label>
</div>
<small class="form-hint"> An album's source type cannot be changed after creation. </small>
<div class="form-group edit-album-source-group" hidden>
<label>Album Source: <span class="edit-album-source-display"></span></label>
<small class="form-hint"> An album's source cannot be changed after creation. </small>
</div>
<div class="form-group edit-album-directory-section">
<label>Image Folder(s)</label>
Expand Down
31 changes: 31 additions & 0 deletions tests/frontend/invokeai-album-source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@ describe("AlbumManager add-album payloads", () => {
describe("AlbumManager settings-credential surfacing", () => {
function makeInvokeSectionStub() {
document.body.innerHTML = `
<div id="newAlbumSourceGroup" hidden>
<label><input type="radio" name="newAlbumSourceType" value="directory" /></label>
<label><input type="radio" name="newAlbumSourceType" value="invokeai_board" checked /></label>
</div>
<div id="newAlbumDirectorySection"></div>
<div id="newAlbumInvokeAISection"></div>
<input id="newAlbumInvokeUrl" />
<input id="newAlbumInvokeUsername" />
<input id="newAlbumInvokePassword" />
Expand All @@ -231,6 +237,9 @@ describe("AlbumManager settings-credential surfacing", () => {
`;
const elements = {};
[
"newAlbumSourceGroup",
"newAlbumDirectorySection",
"newAlbumInvokeAISection",
"newAlbumInvokeUrl",
"newAlbumInvokeUsername",
"newAlbumInvokePassword",
Expand All @@ -246,6 +255,9 @@ describe("AlbumManager settings-credential surfacing", () => {
_setInvokeHint: AlbumManager.prototype._setInvokeHint,
_createInvokeRootRow: AlbumManager.prototype._createInvokeRootRow,
_applySettingsCredentialDefaults: AlbumManager.prototype._applySettingsCredentialDefaults,
_setNewAlbumInvokeAvailable: AlbumManager.prototype._setNewAlbumInvokeAvailable,
getNewAlbumSourceType: AlbumManager.prototype.getNewAlbumSourceType,
toggleNewAlbumSourceSections: AlbumManager.prototype.toggleNewAlbumSourceSections,
initializeNewAlbumInvokeSection: AlbumManager.prototype.initializeNewAlbumInvokeSection,
};
}
Expand Down Expand Up @@ -311,6 +323,25 @@ describe("AlbumManager settings-credential surfacing", () => {
manager._applySettingsCredentialDefaults();
expect(manager.elements.newAlbumInvokeUsername.value).toBe("bob");
});

test("reveals the album-source chooser when a backend is configured", async () => {
const manager = makeInvokeSectionStub();
fetchJson.mockResolvedValue({ url: "http://localhost:9090", username: "", has_password: false, board_id: "" });

await manager.initializeNewAlbumInvokeSection();

expect(manager.elements.newAlbumSourceGroup.hidden).toBe(false);
});

test("hides the chooser and forces the directory source with no backend", async () => {
const manager = makeInvokeSectionStub();
fetchJson.mockResolvedValue({ url: "", username: "", has_password: false, board_id: "" });

await manager.initializeNewAlbumInvokeSection();

expect(manager.elements.newAlbumSourceGroup.hidden).toBe(true);
expect(document.querySelector('input[name="newAlbumSourceType"][value="directory"]').checked).toBe(true);
});
});

describe("AlbumManager indexing-progress robustness", () => {
Expand Down
Loading