diff --git a/web/css/resolver-main.css b/web/css/resolver-main.css index 98c648d..55a364e 100644 --- a/web/css/resolver-main.css +++ b/web/css/resolver-main.css @@ -6694,36 +6694,6 @@ a.mr-image-preview-resource-name:hover { transition: width 0.2s ease; } -.mr-options-history-toolbar { - display: flex; - align-items: center; - flex: 0 0 auto; - gap: 8px; - min-height: 32px; - margin-bottom: 10px; -} - -.mr-options-history-filter-label { - color: var(--mr-text-secondary); - font-size: 12px; - font-weight: 700; -} - -#model-resolver-modal .mr-options-history-filter-select { - width: auto; - min-width: 160px; - min-height: 32px; - padding: 6px 10px; - border-radius: 6px; - font-size: 12px; -} - -.mr-options-history-filter-info { - margin-left: auto; - color: var(--mr-text-muted); - font-size: 12px; -} - .mr-options-history-action { display: inline-flex; align-items: center; @@ -6761,6 +6731,141 @@ a.mr-image-preview-resource-name:hover { background: rgba(255, 255, 255, 0.06); } +.mr-options-history-action-filter-cell { + overflow: visible; +} + +.mr-options-history-action-filter-wrap { + position: relative; + display: inline-flex; + min-width: 0; +} + +.mr-options-history-action-filter-button { + display: inline-flex; + align-items: center; + gap: 5px; + max-width: 100%; + min-height: 20px; + padding: 0 15px 0 0; + border: 0 !important; + border-radius: 0; + color: var(--mr-text); + background: transparent !important; + box-shadow: none !important; + font: inherit; + font-weight: 700; + line-height: 1.1; + cursor: pointer; + appearance: none; + -webkit-appearance: none; +} + +.mr-options-history-action-filter-button::after { + content: ""; + position: absolute; + right: 1px; + width: 0; + height: 0; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + border-top: 5px solid currentColor; + opacity: 0.72; +} + +.mr-options-history-action-filter-button:hover, +.mr-options-history-action-filter-button:focus-visible { + color: var(--mr-text); + outline: none; +} + +.mr-options-history-filter-chip { + display: inline-flex; + align-items: center; + min-width: 0; + max-width: 78px; + height: auto; + padding: 0; + border-radius: 0; + color: var(--mr-text-muted); + background: transparent; + font-size: 10.5px; + font-weight: 700; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.mr-options-history-filter-menu { + position: absolute; + top: calc(100% + 5px); + left: -9px; + z-index: 8; + display: grid; + gap: 0; + min-width: 160px; + padding: 5px; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 6px; + background: #121212; + box-shadow: 0 18px 36px rgba(0, 0, 0, 0.38); +} + +.mr-options-history-filter-menu[hidden] { + display: none; +} + +#model-resolver-modal .mr-options-history-filter-option { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + min-height: 26px; + padding: 5px 7px; + border: 0 !important; + border-radius: 4px; + color: var(--mr-text-secondary); + background: transparent !important; + box-shadow: none !important; + font-size: 12px; + font-weight: 500; + line-height: 1.1; + text-align: left; + cursor: pointer; + appearance: none; + -webkit-appearance: none; +} + +#model-resolver-modal .mr-options-history-filter-option:hover, +#model-resolver-modal .mr-options-history-filter-option:focus-visible { + color: var(--mr-text); + background: rgba(255, 255, 255, 0.055) !important; + outline: none; +} + +#model-resolver-modal .mr-options-history-filter-option.is-active { + color: var(--mr-text); + background: rgba(255, 255, 255, 0.075) !important; +} + +#model-resolver-modal .mr-options-history-filter-option span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} + +#model-resolver-modal .mr-options-history-filter-option strong { + min-width: 34px; + color: var(--mr-text-muted); + font-size: 11px; + font-weight: 700; + text-align: right; +} + +#model-resolver-modal .mr-options-history-filter-option.is-active strong { + color: var(--mr-text); +} + .mr-options-build-tab-panel .mr-options-audit-results:not([hidden]) { display: flex; flex: 1 1 auto; @@ -6866,6 +6971,10 @@ a.mr-image-preview-resource-name:hover { min-width: 900px; } +.mr-options-history-table tr[hidden] { + display: none; +} + .mr-options-history-table th:nth-child(1), .mr-options-history-table td:nth-child(1) { width: 30%; diff --git a/web/resolver/views/options_methods.js b/web/resolver/views/options_methods.js index 286d792..e986080 100644 --- a/web/resolver/views/options_methods.js +++ b/web/resolver/views/options_methods.js @@ -860,20 +860,6 @@ export const optionsMethods = { @@ -984,8 +970,6 @@ export const optionsMethods = { const metadataBuildProgressPanel = this.contentElement.querySelector('#mr-options-metadata-build-progress-panel'); const metadataBuildHistoryPanel = this.contentElement.querySelector('#mr-options-metadata-build-history-panel'); const metadataBuildHistoryCountEl = this.contentElement.querySelector('#mr-options-metadata-build-history-count'); - const metadataBuildHistoryFilterInput = this.contentElement.querySelector('#mr-options-metadata-build-history-filter'); - const metadataBuildHistoryFilterInfo = this.contentElement.querySelector('#mr-options-metadata-build-history-filter-info'); const metadataBuildCpuEl = this.contentElement.querySelector('#mr-options-metadata-build-cpu'); const metadataBuildWorkersEl = this.contentElement.querySelector('#mr-options-metadata-build-workers'); const metadataBuildWorkerInput = this.contentElement.querySelector('#mr-options-metadata-build-worker-count'); @@ -1003,9 +987,7 @@ export const optionsMethods = { if (!metadataBuildHistoryFilterValues.includes(this.metadataBuildHistoryFilter)) { this.metadataBuildHistoryFilter = 'all'; } - if (metadataBuildHistoryFilterInput) { - metadataBuildHistoryFilterInput.value = this.metadataBuildHistoryFilter; - } + this.metadataBuildHistoryFilterMenuOpen = false; if (this.metadataBuildPollTimer) { window.clearTimeout(this.metadataBuildPollTimer); this.metadataBuildPollTimer = null; @@ -1596,11 +1578,14 @@ export const optionsMethods = { const metadataBuildDoneActions = new Set(['created', 'updated', 'skipped', 'checked']); const getMetadataBuildHistoryAction = (item = {}) => String(item.action || 'checked').toLowerCase(); const getMetadataBuildHistoryFilter = () => { - const value = metadataBuildHistoryFilterInput?.value || this.metadataBuildHistoryFilter || 'all'; + const value = this.metadataBuildHistoryFilter || 'all'; return metadataBuildHistoryFilterValues.includes(value) ? value : 'all'; }; const metadataBuildHistoryMatchesFilter = (item, filter) => { const action = getMetadataBuildHistoryAction(item); + return metadataBuildHistoryActionMatchesFilter(action, filter); + }; + const metadataBuildHistoryActionMatchesFilter = (action, filter) => { if (filter === 'all') return true; if (filter === 'done') return metadataBuildDoneActions.has(action); return action === filter; @@ -1617,16 +1602,82 @@ export const optionsMethods = { }; const updateMetadataBuildHistoryFilterUi = (allItems = [], filteredItems = [], filter = 'all') => { const counts = getMetadataBuildHistoryFilterCounts(allItems); - if (metadataBuildHistoryFilterInput) { - Array.from(metadataBuildHistoryFilterInput.options).forEach((option) => { - const label = metadataBuildHistoryFilterLabels[option.value] || option.value; - option.textContent = `${label} (${Number(counts[option.value] || 0).toLocaleString()})`; - }); - metadataBuildHistoryFilterInput.value = filter; - } - if (metadataBuildHistoryFilterInfo) { - metadataBuildHistoryFilterInfo.textContent = `${filteredItems.length.toLocaleString()} shown of ${allItems.length.toLocaleString()}`; + this.metadataBuildHistoryFilterCounts = counts; + this.metadataBuildHistoryFilteredCount = filteredItems.length; + this.metadataBuildHistoryTotalCount = allItems.length; + this.metadataBuildHistoryFilter = filter; + return counts; + }; + const renderMetadataBuildActionFilterHeader = (counts = {}, filter = 'all', filteredCount = 0, totalCount = 0) => { + const activeLabel = metadataBuildHistoryFilterLabels[filter] || metadataBuildHistoryFilterLabels.all; + const menuOpen = Boolean(this.metadataBuildHistoryFilterMenuOpen); + const options = metadataBuildHistoryFilterValues.map((value) => { + const label = metadataBuildHistoryFilterLabels[value] || value; + const count = Number(counts[value] || 0); + const isActive = value === filter; + return ` + + `; + }).join(''); + return ` + +
+ + +
+ + `; + }; + const setMetadataBuildHistoryFilterMenuOpen = (open) => { + this.metadataBuildHistoryFilterMenuOpen = Boolean(open); + const menu = metadataBuildResults?.querySelector?.('.mr-options-history-filter-menu'); + const toggle = metadataBuildResults?.querySelector?.('[data-metadata-history-filter-toggle]'); + if (menu) menu.hidden = !this.metadataBuildHistoryFilterMenuOpen; + if (toggle) toggle.setAttribute('aria-expanded', this.metadataBuildHistoryFilterMenuOpen ? 'true' : 'false'); + }; + const updateMetadataBuildActionFilterControl = (filter = 'all') => { + if (!metadataBuildResults) return; + const normalizedFilter = metadataBuildHistoryFilterValues.includes(filter) ? filter : 'all'; + const counts = this.metadataBuildHistoryFilterCounts || {}; + const toggle = metadataBuildResults.querySelector('[data-metadata-history-filter-toggle]'); + const chip = metadataBuildResults.querySelector('[data-metadata-history-filter-chip]'); + if (toggle) toggle.classList.toggle('is-filtered', normalizedFilter !== 'all'); + if (chip) { + chip.textContent = metadataBuildHistoryFilterLabels[normalizedFilter] || normalizedFilter; + chip.hidden = normalizedFilter === 'all'; } + metadataBuildResults.querySelectorAll('[data-metadata-history-filter-option]').forEach((option) => { + const value = option.dataset.metadataHistoryFilterOption || 'all'; + const isActive = value === normalizedFilter; + option.classList.toggle('is-active', isActive); + option.setAttribute('aria-checked', isActive ? 'true' : 'false'); + const countEl = option.querySelector('strong'); + if (countEl) countEl.textContent = Number(counts[value] || 0).toLocaleString(); + }); + }; + const applyMetadataBuildHistoryFilter = (filter = 'all') => { + if (!metadataBuildResults) return; + const normalizedFilter = metadataBuildHistoryFilterValues.includes(filter) ? filter : 'all'; + this.metadataBuildHistoryFilter = normalizedFilter; + let shown = 0; + metadataBuildResults.querySelectorAll('[data-metadata-history-row]').forEach((row) => { + const action = row.dataset.historyAction || 'checked'; + const matches = metadataBuildHistoryActionMatchesFilter(action, normalizedFilter); + row.hidden = !matches; + if (matches) shown += 1; + }); + const emptyRow = metadataBuildResults.querySelector('[data-metadata-history-empty-row]'); + if (emptyRow) emptyRow.hidden = shown > 0; + this.metadataBuildHistoryFilteredCount = shown; + updateMetadataBuildActionFilterControl(normalizedFilter); }; const renderMetadataBuildResults = (data = undefined) => { @@ -1654,11 +1705,11 @@ export const optionsMethods = { const activeHistoryFilter = getMetadataBuildHistoryFilter(); this.metadataBuildHistoryFilter = activeHistoryFilter; const filteredItems = displayItems.filter(item => metadataBuildHistoryMatchesFilter(item, activeHistoryFilter)); - updateMetadataBuildHistoryFilterUi(displayItems, filteredItems, activeHistoryFilter); - const visibleItems = filteredItems; + const filterCounts = updateMetadataBuildHistoryFilterUi(displayItems, filteredItems, activeHistoryFilter); + const visibleItems = displayItems; let html = ''; - if (visibleItems.length) { + if (displayItems.length) { const rows = visibleItems.map((item) => { const modelLabel = item.relative_path || item.filename || item.model_path || 'Model'; const modelPath = item.model_path || ''; @@ -1674,8 +1725,10 @@ export const optionsMethods = { : ''; const changedFieldsTitle = changedFields ? ` title="${this.escapeHtml(changedFields)}"` : ''; const action = item.action || 'checked'; - const actionClass = String(action).toLowerCase().replace(/[^a-z0-9_-]+/g, '-'); + const actionValue = String(action).toLowerCase(); + const actionClass = actionValue.replace(/[^a-z0-9_-]+/g, '-'); const message = item.message || changedFieldsSummary; + const rowMatchesFilter = metadataBuildHistoryActionMatchesFilter(actionValue, activeHistoryFilter); const buildContext = modelPath ? { context_scope: 'local_model', open_folder_label: 'Show File in Folder', @@ -1697,7 +1750,7 @@ export const optionsMethods = { ? this.getContextMenuAttrs(buildContext, 'Right-click for model options') : ''; return ` - +
${this.escapeHtml(modelLabel)}
${this.escapeHtml(modelPath)}
@@ -1718,26 +1771,30 @@ export const optionsMethods = { `; }).join(''); + const emptyRow = ` + + +
No history items match the ${this.escapeHtml(metadataBuildHistoryFilterLabels[activeHistoryFilter] || activeHistoryFilter)} filter.
+ + + `; html += `
- + ${renderMetadataBuildActionFilterHeader(filterCounts, activeHistoryFilter, filteredItems.length, displayItems.length)} - ${rows} + ${rows}${emptyRow}
ModelActionFolder SHA256 Size Metadata file
`; - } else if (displayItems.length) { - const label = metadataBuildHistoryFilterLabels[activeHistoryFilter] || activeHistoryFilter; - html += `
No history items match the ${this.escapeHtml(label)} filter.
`; } else if (source?.cancelled) { html += '
Metadata build was cancelled before any history item was captured.
'; } else { @@ -2977,10 +3034,38 @@ export const optionsMethods = { }); }); - if (metadataBuildHistoryFilterInput) { - metadataBuildHistoryFilterInput.addEventListener('change', () => { - this.metadataBuildHistoryFilter = getMetadataBuildHistoryFilter(); - renderMetadataBuildResults(); + if (metadataBuildResults) { + metadataBuildResults.addEventListener('click', (event) => { + const filterToggle = event.target?.closest?.('[data-metadata-history-filter-toggle]'); + if (filterToggle) { + event.preventDefault(); + event.stopPropagation(); + setMetadataBuildHistoryFilterMenuOpen(!this.metadataBuildHistoryFilterMenuOpen); + return; + } + + const filterOption = event.target?.closest?.('[data-metadata-history-filter-option]'); + if (filterOption) { + event.preventDefault(); + event.stopPropagation(); + const nextFilter = filterOption.dataset.metadataHistoryFilterOption || 'all'; + applyMetadataBuildHistoryFilter(nextFilter); + setMetadataBuildHistoryFilterMenuOpen(false); + } + }); + } + + this.contentElement.addEventListener('click', (event) => { + if (!this.metadataBuildHistoryFilterMenuOpen) return; + if (event.target?.closest?.('.mr-options-history-action-filter-wrap')) return; + setMetadataBuildHistoryFilterMenuOpen(false); + }); + + if (metadataBuildResults) { + metadataBuildResults.addEventListener('keydown', (event) => { + if (event.key !== 'Escape' || !this.metadataBuildHistoryFilterMenuOpen) return; + event.preventDefault(); + setMetadataBuildHistoryFilterMenuOpen(false); }); }