diff --git a/__init__.py b/__init__.py index 5263ba8..9d38800 100644 --- a/__init__.py +++ b/__init__.py @@ -187,12 +187,15 @@ def ui_rules(): Rule("model-preview-thumbnail-type", "AUTO", str), Rule("model-preview-fallback-search-safetensors-thumbnail", False, bool), + Rule("model-preview-thumbnail-width", 240, int, 150, 480), + Rule("model-preview-thumbnail-height", 360, int, 185, 480), Rule("model-show-label-extensions", False, bool), Rule("model-show-add-button", True, bool), Rule("model-show-copy-button", True, bool), Rule("model-show-load-workflow-button", True, bool), Rule("model-show-open-model-url-button", False, bool), Rule("model-info-button-on-left", False, bool), + Rule("model-buttons-only-on-hover", False, bool), Rule("model-add-embedding-extension", False, bool), Rule("model-add-drag-strict-on-field", False, bool), diff --git a/web/model-manager.css b/web/model-manager.css index 893e24a..07e1051 100644 --- a/web/model-manager.css +++ b/web/model-manager.css @@ -55,6 +55,9 @@ --model-manager-sidebar-height-top: 50vh; --model-manager-sidebar-height-bottom: 50vh; + --model-manager-thumbnail-width: 240px; + --model-manager-thumbnail-height: 360px; + --model-manager-left: 0; --model-manager-right: 0; --model-manager-top: 0; @@ -407,8 +410,8 @@ /* preview image */ .model-manager .item { position: relative; - width: 240px; - height: 360px; + width: var(--model-manager-thumbnail-width);; + height: var(--model-manager-thumbnail-height);; text-align: center; overflow: hidden; border-radius: 8px; @@ -500,7 +503,6 @@ .model-manager .comfy-grid .model-preview-top-right, .model-manager .comfy-grid .model-preview-top-left { position: absolute; - display: flex; flex-direction: column; gap: 8px; top: 8px; @@ -514,6 +516,15 @@ left: 8px; } +.model-manager .item .model-buttons-hidden { + display: none; +} + +.model-manager .item:hover .model-buttons-hidden, +.model-manager .comfy-grid .model-buttons-visible { + display: flex; +} + .model-manager .comfy-grid .model-button { opacity: 0.65; } diff --git a/web/model-manager.js b/web/model-manager.js index 48c7057..347aefc 100644 --- a/web/model-manager.js +++ b/web/model-manager.js @@ -312,8 +312,6 @@ class SearchPath { return uri; } const PREVIEW_NONE_URI = imageUri(); -const PREVIEW_THUMBNAIL_WIDTH = 320; -const PREVIEW_THUMBNAIL_HEIGHT = 480; /** * @@ -2132,6 +2130,12 @@ class ModelGrid { !settingsElements['model-add-embedding-extension'].checked; const previewThumbnailFormat = settingsElements['model-preview-thumbnail-type'].value; + const previewThumbnailWidth = + Math.round(settingsElements['model-preview-thumbnail-width'].value / 0.75); + const previewThumbnailHeight = + Math.round(settingsElements['model-preview-thumbnail-height'].value / 0.75); + const buttonsOnlyOnHover = + settingsElements['model-buttons-only-on-hover'].checked; if (models.length > 0) { const $overlay = IS_FIREFOX @@ -2173,6 +2177,8 @@ class ModelGrid { draggable: true, }); }); + const forHiddingButtonsClass = buttonsOnlyOnHover + ? 'model-buttons-hidden' : 'model-buttons-visible'; return models.map((item) => { const previewInfo = item.preview; @@ -2182,8 +2188,8 @@ class ModelGrid { src: imageUri( previewInfo?.path ? encodeURIComponent(previewInfo.path) : undefined, previewInfo?.dateModified ? encodeURIComponent(previewInfo.dateModified) : undefined, - PREVIEW_THUMBNAIL_WIDTH, - PREVIEW_THUMBNAIL_HEIGHT, + previewThumbnailWidth, + previewThumbnailHeight, previewThumbnailFormat, ), draggable: false, @@ -2293,14 +2299,14 @@ class ModelGrid { strictDragToAdd, ), $el( - 'div.model-preview-top-right', + 'div.model-preview-top-right.' + forHiddingButtonsClass, { draggable: false, }, modelInfoButtonOnLeft ? infoButtons : actionButtons, ), $el( - 'div.model-preview-top-left', + 'div.model-preview-top-left.' + forHiddingButtonsClass, { draggable: false, }, @@ -2432,7 +2438,7 @@ class ModelInfo { /** * @param {ModelData} modelData - * @param {() => Promise} updateModels + * @param {(withoutComfyRefresh?: boolean) => Promise} updateModels * @param {any} settingsElements * @param {() => Promise} tryHideModelInfo */ @@ -2511,7 +2517,7 @@ class ModelInfo { }); } if (updatedPreview) { - updateModels(); + updateModels(true); const previewSelect = this.previewSelect; previewSelect.elements.defaultUrl.dataset.noimage = PREVIEW_NONE_URI; @@ -2734,7 +2740,7 @@ class ModelInfo { /** * @param {string} searchPath - * @param {() => Promise} updateModels + * @param {(withoutComfyRefresh?: boolean) => Promise} updateModels * @param {string} searchSeparator */ async update(searchPath, updateModels, searchSeparator) { @@ -3901,13 +3907,13 @@ class DownloadView { /** @type {Object.} */ #settings = null; - /** @type {() => Promise} */ + /** @type {(withoutComfyRefresh?: boolean) => Promise} */ #updateModels = () => {}; /** * @param {ModelData} modelData * @param {Object.} settings - * @param {() => Promise} updateModels + * @param {(withoutComfyRefresh?: boolean) => Promise} updateModels */ constructor(modelData, settings, updateModels) { this.#domParser = new DOMParser(); @@ -4383,7 +4389,7 @@ class BrowseView { /** @type {ModelData} */ #modelData = null; - /** @type {@param {() => Promise}} */ + /** @type {(withoutComfyRefresh?: boolean) => Promise} */ #updateModels = null; /** */ @@ -4393,7 +4399,7 @@ class BrowseView { updateModelGrid = () => {}; /** - * @param {() => Promise} updateModels + * @param {(withoutComfyRefresh?: boolean) => Promise} updateModels * @param {ModelData} modelData * @param {(searchPath: string) => Promise} showModelInfo * @param {() => void} updateModelGridCallback @@ -4625,6 +4631,8 @@ class SettingsView { /** @type {HTMLInputElement} */ 'model-persistent-search': null, /** @type {HTMLInputElement} */ 'model-preview-thumbnail-type': null, + /** @type {HTMLInputElement} */ 'model-preview-thumbnail-width': null, + /** @type {HTMLInputElement} */ 'model-preview-thumbnail-height': null, /** @type {HTMLInputElement} */ 'model-preview-fallback-search-safetensors-thumbnail': null, /** @type {HTMLInputElement} */ 'model-show-label-extensions': null, @@ -4633,6 +4641,7 @@ class SettingsView { /** @type {HTMLInputElement} */ 'model-show-load-workflow-button': null, /** @type {HTMLInputElement} */ 'model-show-open-model-url-button': null, /** @type {HTMLInputElement} */ 'model-info-button-on-left': null, + /** @type {HTMLInputElement} */ 'model-buttons-only-on-hover': null, /** @type {HTMLInputElement} */ 'model-add-embedding-extension': null, /** @type {HTMLInputElement} */ 'model-add-drag-strict-on-field': null, @@ -4657,7 +4666,7 @@ class SettingsView { }, }; - /** @return {() => Promise} */ + /** @return {(withoutComfyRefresh?: boolean) => Promise} */ #updateModels = async () => {}; /** @return {() => void} */ @@ -4665,9 +4674,9 @@ class SettingsView { /** * @param {Object} settingsData - * @param {boolean} updateModels + * @param {boolean} withoutComfyRefresh */ - async #setSettings(settingsData, updateModels) { + async #setSettings(settingsData, withoutComfyRefresh) { const settings = this.elements.settings; for (const [key, value] of Object.entries(settingsData)) { const setting = settings[key]; @@ -4695,22 +4704,18 @@ class SettingsView { console.warn(`Unknown settings input type '${type}'!`); } } - this.#updateSidebarSettings(settings); - - if (updateModels) { - await this.#updateModels(); // Is this slow? - } + await this.#updateModels(withoutComfyRefresh); } /** - * @param {boolean} updateModels + * @param {boolean} withoutComfyRefresh * @returns {Promise} */ - async reload(updateModels) { + async reload(withoutComfyRefresh) { const data = await comfyRequest('/model-manager/settings/load'); const settingsData = data['settings']; - await this.#setSettings(settingsData, updateModels); + await this.#setSettings(settingsData, withoutComfyRefresh); comfyButtonAlert(this.elements.reloadButton, true); } @@ -4760,7 +4765,7 @@ class SettingsView { } /** - * @param {() => Promise} updateModels + * @param {(withoutComfyRefresh?: boolean) => Promise} updateModels * @param {() => void} updateSidebarButtons * @param {(settings: Object) => void} updateSidebarSettings */ @@ -4956,6 +4961,34 @@ class SettingsView { textContent: 'Preview thumbnail type', options: ['AUTO', 'JPEG'], // should use AUTO to avoid artifacts from changing between formats; use JPEG for backward compatibility }), + $el('label', [ + 'Preview thumbnail width', + $el('input', { + $: (el) => (settings['model-preview-thumbnail-width'] = el), + type: 'range', + name: 'default thumbnail width', + value: 240, + min: 150, + max: 480, + step: 5, + oninput: function(){ this.nextElementSibling.textContent = this.value + 'px'}, + }), + $el('span'), + ]), + $el('label', [ + 'Preview thumbnail height', + $el('input', { + $: (el) => (settings['model-preview-thumbnail-height'] = el), + type: 'range', + name: 'default thumbnail height', + value: 360, + min: 185, + max: 480, + step: 5, + oninput: function(){ this.nextElementSibling.textContent = this.value + 'px'}, + }), + $el('span'), + ]), $checkbox({ $: (el) => (settings['model-preview-fallback-search-safetensors-thumbnail'] = @@ -4985,6 +5018,10 @@ class SettingsView { $: (el) => (settings['model-info-button-on-left'] = el), textContent: '"Model Info" button on left', }), + $checkbox({ + $: (el) => (settings['model-buttons-only-on-hover'] = el), + textContent: 'Show buttons on hover only', + }), $el('h2', ['Node Graph']), $checkbox({ $: (el) => (settings['model-add-embedding-extension'] = el), @@ -5707,8 +5744,7 @@ class ModelManager extends ComfyDialog { } async #init() { - await this.#settingsView.reload(false); - await this.#refreshModels(); + await this.#settingsView.reload(true) const settings = this.#settingsView.elements.settings; @@ -5783,13 +5819,31 @@ class ModelManager extends ComfyDialog { this.#downloadView.elements.clearSearchButton.style.display = hideClearSearchButtons ? 'none' : ''; } + + { + // update thumbnail widths & heights + const thumbnailWidthEl = settings['model-preview-thumbnail-width']; + const thumbnailHeightEl = settings['model-preview-thumbnail-height']; + + this.element.style.setProperty( + '--model-manager-thumbnail-width', + thumbnailWidthEl.value.toString() + 'px', + ); + thumbnailWidthEl.dispatchEvent(new Event('input')); + + this.element.style.setProperty( + '--model-manager-thumbnail-height', + thumbnailHeightEl.value.toString() + 'px', + ); + thumbnailHeightEl.dispatchEvent(new Event('input')); + } } #resetManagerContentsScroll = () => { this.#tabManagerContents.scrollTop = 0; }; - #refreshModels = async () => { + #refreshModels = async (withoutComfyRefresh = false) => { const modelData = this.#modelData; modelData.systemSeparator = await comfyRequest( '/model-manager/system-separator', @@ -5803,8 +5857,9 @@ class ModelManager extends ComfyDialog { this.#browseView.updateModelGrid(); await this.#tryHideModelInfo(false); - - document.getElementById('comfy-refresh-button')?.click(); + if (!withoutComfyRefresh){ + document.getElementById('comfy-refresh-button')?.click(); + } }; /**