diff --git a/src/Core/Settings.cs b/src/Core/Settings.cs index 12697239a..5e9dfeaea 100644 --- a/src/Core/Settings.cs +++ b/src/Core/Settings.cs @@ -509,7 +509,9 @@ public class ThemesImpl : SettingsOptionsAttribute.AbstractImpl public bool AutoSwapImagesIncludesFullView = false; // TODO: UserUI [ConfigComment("A list of what buttons to include directly under images in the main prompt area of the Generate tab.\nOther buttons will be moved into the 'More' dropdown.\nThis should be a comma separated list." - + "\nThe following options are available: \"Use As Init\", \"Use As Image Prompt\", \"Edit Image\", \"Upscale 2x\", \"Star\", \"Reuse Parameters\", \"Open In Folder\", \"Delete\", \"Download\" \"View In History\", \"Refine Image\", \"Copy Path\"" + + "\nThe following options are available: \"Use As Init\", \"Use As Image Prompt\", \"Edit Image\", \"Upscale 2x\", \"Star\", \"Reuse Parameters\", \"Open In Folder\", \"Delete\", \"Download\", \"View In History\", \"Refine Image\", \"Copy Path\", \"Copy Raw Metadata\"" + + "\nSome buttons like 'Edit Image' may only apply to images and will not show for audio or video files." + + "\nExtensions can add their own button names here too." + "\nThe default is blank, which currently implies 'Use As Init,Edit Image,Star,Reuse Parameters'")] public string ButtonsUnderMainImages = ""; // TODO: UserUI diff --git a/src/wwwroot/js/genpage/gentab/currentimagehandler.js b/src/wwwroot/js/genpage/gentab/currentimagehandler.js index 420e186ff..5bf12712b 100644 --- a/src/wwwroot/js/genpage/gentab/currentimagehandler.js +++ b/src/wwwroot/js/genpage/gentab/currentimagehandler.js @@ -788,6 +788,13 @@ function toggleStar(path, rawSrc) { defaultButtonChoices = 'Use As Init,Edit Image,Star,Reuse Parameters'; +let registeredMediaButtons = []; + +/** Registers a media button for extensions. 'mediaTypes' filters by type eg ['audio'], null means all. 'isDefault' promotes to visible (vs More dropdown). 'showInHistory' controls whether button appears in the History panel. */ +function registerMediaButton(name, action, title = '', mediaTypes = null, isDefault = false, showInHistory = true) { + registeredMediaButtons.push({ name, action, title, mediaTypes, isDefault, showInHistory }); +} + function getImageFullSrc(src) { if (src == null) { return null; @@ -823,9 +830,8 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, forceShowWelcomeMessage(); return; } - let isVideo = isVideoExt(src); - let isAudio = isAudioExt(src); - if ((smoothAdd || !metadata) && canReparse && !isVideo && !isAudio) { + let mediaType = getMediaType(src); + if ((smoothAdd || !metadata) && canReparse && mediaType == 'image') { let image = new Image(); image.onload = () => { if (!metadata) { @@ -851,7 +857,7 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, let img; let isReuse = false; let srcTarget; - if (isVideo) { + if (mediaType == 'video') { container = createDiv(null, 'video-container current-image-img'); curImg.innerHTML = ''; img = document.createElement('video'); @@ -860,11 +866,11 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, img.autoplay = true; let sourceObj = document.createElement('source'); srcTarget = sourceObj; - sourceObj.type = isVideo; + sourceObj.type = isVideoExt(src); img.appendChild(sourceObj); container.appendChild(img); } - else if (isAudio) { + else if (mediaType == 'audio') { curImg.innerHTML = ''; container = createDiv(null, 'audio-container current-image-img'); img = document.createElement('audio'); @@ -888,10 +894,10 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, container = img; } function naturalDim() { - if (isVideo) { + if (mediaType == 'video') { return [img.videoWidth, img.videoHeight]; } - else if (isAudio) { + else if (mediaType == 'audio') { return [320, 140]; } else { @@ -907,7 +913,7 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, } alignImageDataFormat(); } - if (isVideo || isAudio) { + if (mediaType == 'video' || mediaType == 'audio') { img.addEventListener('loadeddata', function() { if (img) { img.onload(); @@ -926,7 +932,8 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, let buttons = createDiv(null, 'current-image-buttons'); let imagePathClean = getImageFullSrc(src); let buttonsChoice = getUserSetting('ButtonsUnderMainImages', ''); - if (buttonsChoice == '') { + let isUsingDefaults = buttonsChoice == ''; + if (isUsingDefaults) { buttonsChoice = defaultButtonChoices; } let buttonDefs = {}; @@ -939,17 +946,20 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, } return normalized; } - function includeButton(name, action, extraClass = '', title = '') { - buttonDefs[normalizeButtonKey(name)] = { name, action, extraClass, title }; + function includeButton(name, action, extraClass = '', title = '', mediaTypes = null) { + buttonDefs[normalizeButtonKey(name)] = { name, action, extraClass, title, mediaTypes }; } - function includeLinkButton(name, href, isDownload = false, title = '') { - buttonDefs[normalizeButtonKey(name)] = { name, href, is_download: isDownload, title: title }; + function includeLinkButton(name, href, isDownload = false, title = '', mediaTypes = null) { + buttonDefs[normalizeButtonKey(name)] = { name, href, is_download: isDownload, title, mediaTypes }; } function renderButtonsFromDefs() { for (let key of buttonsChoiceOrdered) { let def = buttonDefs[key]; if (def) { delete buttonDefs[key]; + if (def.mediaTypes && !def.mediaTypes.includes(mediaType)) { + continue; + } if (def.href) { let link = document.createElement('a'); link.className = `basic-button${def.extraClass || ''}`; @@ -967,6 +977,9 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, } } for (let def of Object.values(buttonDefs)) { + if (def.mediaTypes && !def.mediaTypes.includes(mediaType)) { + continue; + } if (def.href) { subButtons.push({ key: def.name, href: def.href, is_download: def.is_download, title: def.title }); } @@ -982,6 +995,16 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, buttonsChoiceOrdered.push(key); } } + if (isUsingDefaults) { + for (let reg of registeredMediaButtons) { + if (reg.isDefault) { + let key = normalizeButtonKey(reg.name); + if (key && !buttonsChoiceOrdered.includes(key)) { + buttonsChoiceOrdered.push(key); + } + } + } + } let isDataImage = src.startsWith('data:'); includeButton('Use As Init', () => { let initImageParam = document.getElementById('input_initimage'); @@ -1019,7 +1042,7 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, tmpImg.src = img.src; } } - }, '', 'Sets this image as the Init Image parameter input'); + }, '', 'Sets this image as the Init Image parameter input', ['image', 'video']); includeButton('Use As Image Prompt', () => { let altPromptRegion = document.getElementById('alt_prompt_region'); if (!altPromptRegion) { @@ -1040,7 +1063,7 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, }); }; tmpImg.src = img.src; - }, '', 'Uses this image as an Image Prompt input'); + }, '', 'Uses this image as an Image Prompt input', ['image']); includeButton('Edit Image', () => { let initImageGroupToggle = document.getElementById('input_group_content_initimage_toggle'); if (initImageGroupToggle) { @@ -1067,7 +1090,7 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, } imageEditor.setBaseImage(img); imageEditor.activate(); - }, '', 'Opens an Image Editor for this image'); + }, '', 'Opens an Image Editor for this image', ['image']); includeButton('Upscale 2x', () => { toDataURL(img.src, (url => { let [width, height] = naturalDim(); @@ -1080,7 +1103,7 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, }; mainGenHandler.doGenerate(input_overrides, { 'initimagecreativity': 0.4 }); })); - }, '', 'Runs an instant generation with this image as the input and scale doubled'); + }, '', 'Runs an instant generation with this image as the input and scale doubled', ['image', 'video']); includeButton('Refine Image', () => { toDataURL(img.src, (url => { let input_overrides = { @@ -1148,6 +1171,9 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, includeButton(added.label, added.onclick, '', added.title); } } + for (let reg of registeredMediaButtons) { + includeButton(reg.name, () => reg.action(src), '', reg.title, reg.mediaTypes); + } renderButtonsFromDefs(); quickAppendButton(buttons, 'More ⮟', (e, button) => { let rect = button.getBoundingClientRect(); @@ -1160,10 +1186,10 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false, if (!isReuse) { curImg.appendChild(container); curImg.appendChild(extrasWrapper); - if (isVideo) { + if (mediaType == 'video') { new VideoControls(img); } - else if (isAudio) { + else if (mediaType == 'audio') { new AudioControls(img); } } diff --git a/src/wwwroot/js/genpage/gentab/outputhistory.js b/src/wwwroot/js/genpage/gentab/outputhistory.js index b80c2a6e4..aa7ad0eec 100644 --- a/src/wwwroot/js/genpage/gentab/outputhistory.js +++ b/src/wwwroot/js/genpage/gentab/outputhistory.js @@ -55,6 +55,7 @@ function listOutputHistoryFolderAndFiles(path, isRefresh, callback, depth) { function buttonsForImage(fullsrc, src, metadata) { let isDataImage = src.startsWith('data:'); + let mediaType = getMediaType(src); buttons = []; if (permissions.hasPermission('user_star_images') && !isDataImage) { buttons.push({ @@ -139,6 +140,15 @@ function buttonsForImage(fullsrc, src, metadata) { } }); } + for (let reg of registeredMediaButtons) { + if (reg.showInHistory && (!reg.mediaTypes || reg.mediaTypes.includes(mediaType))) { + buttons.push({ + label: reg.name, + title: reg.title, + onclick: () => reg.action(src) + }); + } + } return buttons; } diff --git a/src/wwwroot/js/util.js b/src/wwwroot/js/util.js index eaecd4e68..f6bede703 100644 --- a/src/wwwroot/js/util.js +++ b/src/wwwroot/js/util.js @@ -1114,6 +1114,17 @@ function isAudioExt(filename) { return false; } +/** Returns 'video', 'audio', or 'image' based on the file source. */ +function getMediaType(src) { + if (isVideoExt(src)) { + return 'video'; + } + if (isAudioExt(src)) { + return 'audio'; + } + return 'image'; +} + /** 'string.split' with a count limit, and without the stupid misbehavior of the default JS 'string.split'. */ function splitWithTail(str, splitter, limit) { let parts = str.split(splitter);