Layout Update (#4514) * init * mostly working messy version * css layout, mobile is fixed * revert changes * more of the above * cleanup * revert breaking fixes, cleanup dead code * dead code * delete fallback * spacing * css tweaks and removed getChatBackgroundsList * mobile sizing * revert unrelated changes * use name instead of context * debounce from constants.js * replace if/else * pass images directly * more of the above * buttons * buttons functionality * add default column counts * remove .mobile-only-menu-toggle when clicked (layering fix) * lint * universal column default of 3 * sacrifice firefox for chrome * Restore media query This reverts commit 295a5a81908e58c0bfa5e01fd90fcd62c05471b4. * Add disabled attribute styles for menu_button * use opacity 0, pointer events none instead of display:none * Format styles * Fix type errors * Add blur event handler to close mobile menu when focus is lost --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -88,6 +88,24 @@ body.reduced-motion #bg_custom { | ||
| 88 | 88 | gap: 5px; |
| 89 | 89 | } |
| 90 | 90 | |
| 91 | +/* Control buttons in header */ | |
| 92 | +.heading-container-with-controls { | |
| 93 | + position: relative; | |
| 94 | +} | |
| 95 | + | |
| 96 | +.heading-container-with-controls .heading-text { | |
| 97 | + margin: 10px 0; | |
| 98 | +} | |
| 99 | + | |
| 100 | +.heading-container-with-controls .heading-controls { | |
| 101 | + position: absolute; | |
| 102 | + right: 5px; | |
| 103 | + top: 50%; | |
| 104 | + transform: translateY(-50%); | |
| 105 | + display: flex; | |
| 106 | + gap: 5px; | |
| 107 | +} | |
| 108 | + | |
| 91 | 109 | #bg-scrollable-content { |
| 92 | 110 | flex-grow: 1; |
| 93 | 111 | overflow-y: auto; |
| @@ -95,46 +113,43 @@ body.reduced-motion #bg_custom { | ||
| 95 | 113 | padding: 0 5px 15px; |
| 96 | 114 | } |
| 97 | 115 | |
| 116 | +#bg_menu_content, | |
| 117 | +#bg_custom_content { | |
| 118 | + display: grid; | |
| 119 | + gap: 5px; | |
| 120 | + width: 100%; | |
| 121 | + grid-template-columns: repeat(var(--bg-thumb-columns, 5), 1fr); | |
| 122 | +} | |
| 123 | + | |
| 98 | 124 | #bg-filter { |
| 99 | 125 | font-size: calc(var(--mainFontSize) * 0.95); |
| 100 | 126 | } |
| 101 | 127 | |
| 102 | 128 | /* Thumbnail Menu & ButtonsThumbnails */ |
| 129 | +.bg_example:hover .BGSampleTitle { | |
| 130 | + opacity: 1; | |
| 131 | +} | |
| 132 | + | |
| 103 | 133 | .bg_example .mobile-only-menu-toggle { |
| 104 | 134 | display: none; |
| 105 | 135 | } |
| 106 | 136 | |
| 107 | 137 | .bg_example.flex-container { |
| 108 | - width: 30%; | |
| 109 | - max-width: 200px; | |
| 110 | - margin: 5px; | |
| 111 | - aspect-ratio: 16/9; | |
| 112 | 138 | cursor: pointer; |
| 113 | 139 | box-shadow: 0 0 7px var(--black50a); |
| 114 | - | |
| 115 | 140 | position: relative; |
| 116 | 141 | overflow: hidden; |
| 117 | 142 | border-radius: 8px; |
| 118 | 143 | border: 0px solid transparent; |
| 119 | 144 | outline: 2px solid var(--SmartThemeBorderColor); |
| 120 | 145 | outline-offset: -1px; |
| 121 | -} | |
| 122 | 146 | |
| 123 | -.bg_example.flex-container:focus-visible { | |
| 147 | + height: auto; | |
| 124 | 148 | outlineaspect-offsetratio: inherit16 / 9; |
| 125 | 149 | } |
| 126 | 150 | |
| 127 | 151 | .bg_example_imgbg_example:focus-visible { |
| 128 | 152 | positionoutline-offset: absoluteinherit; |
| 129 | - top: -2px; | |
| 130 | - left: -2px; | |
| 131 | - right: -2px; | |
| 132 | - bottom: -2px; | |
| 133 | - | |
| 134 | - background-image: inherit; | |
| 135 | - | |
| 136 | - background-size: cover; | |
| 137 | - background-position: center; | |
| 138 | 153 | } |
| 139 | 154 | |
| 140 | 155 | .bg_example .jg-menu { |
| @@ -143,9 +158,8 @@ body.reduced-motion #bg_custom { | ||
| 143 | 158 | top: 2px; |
| 144 | 159 | right: 2px; |
| 145 | 160 | background-color: rgba(0, 0, 0, 0.5); |
| 146 | 161 | border-radius: 8px5px; |
| 147 | 162 | gappadding: 3px 3px; |
| 148 | - padding: 3px 5px; | |
| 149 | 163 | z-index: 3; |
| 150 | 164 | backdrop-filter: blur(4px); |
| 151 | 165 | border: 1px solid var(--SmartThemeBorderColor); |
| @@ -168,14 +182,14 @@ body.reduced-motion #bg_custom { | ||
| 168 | 182 | |
| 169 | 183 | .bg_example .jg-button { |
| 170 | 184 | display: flex; |
| 171 | 185 | width: 30px24px; |
| 172 | 186 | height: 30px24px; |
| 173 | 187 | align-items: center; |
| 174 | 188 | justify-content: center; |
| 175 | 189 | color: white; |
| 176 | 190 | padding: 5px; |
| 177 | 191 | font-size: 1.1em; |
| 178 | 192 | border-radius: 6px5px; |
| 179 | 193 | transition: background-color var(--animation-duration) ease; |
| 180 | 194 | } |
| 181 | 195 | |
| @@ -195,6 +209,18 @@ body.reduced-motion #bg_custom { | ||
| 195 | 209 | display: flex; |
| 196 | 210 | } |
| 197 | 211 | |
| 212 | +.thumbnail-clipper { | |
| 213 | + position: absolute; | |
| 214 | + top: -2px; | |
| 215 | + left: -2px; | |
| 216 | + right: -2px; | |
| 217 | + bottom: -2px; | |
| 218 | + overflow: hidden; | |
| 219 | + border-radius: inherit; | |
| 220 | + background-size: cover; | |
| 221 | + background-position: center; | |
| 222 | +} | |
| 223 | + | |
| 198 | 224 | .bg_example:not([custom="true"]) .jg-copy, |
| 199 | 225 | .bg_example[custom="true"] .jg-edit { |
| 200 | 226 | display: none; |
| @@ -21,6 +21,14 @@ | ||
| 21 | 21 | display: none; |
| 22 | 22 | } |
| 23 | 23 | |
| 24 | + #bg_menu_content, #bg_custom_content { | |
| 25 | + grid-template-columns: repeat(var(--bg-thumb-columns, 3), 1fr); | |
| 26 | + } | |
| 27 | + | |
| 28 | + .bg_list { | |
| 29 | + width: unset; | |
| 30 | + } | |
| 31 | + | |
| 24 | 32 | .bg_button { |
| 25 | 33 | font-size: 15px; |
| 26 | 34 | } |
| @@ -40,6 +48,11 @@ | ||
| 40 | 48 | z-index: 4; |
| 41 | 49 | } |
| 42 | 50 | |
| 51 | + .bg_example.mobile-menu-open .mobile-only-menu-toggle { | |
| 52 | + opacity: 0; | |
| 53 | + pointer-events: none; | |
| 54 | + } | |
| 55 | + | |
| 43 | 56 | .bg_example .mobile-only-menu-toggle { |
| 44 | 57 | display: flex; |
| 45 | 58 | align-items: center; |
| @@ -57,6 +70,11 @@ | ||
| 57 | 70 | backdrop-filter: blur(2px); |
| 58 | 71 | } |
| 59 | 72 | |
| 73 | + .bg_example .jg-button { | |
| 74 | + width: 30px; | |
| 75 | + height: 30px; | |
| 76 | + } | |
| 77 | + | |
| 60 | 78 | #bg-header-controls { |
| 61 | 79 | flex-wrap: wrap; |
| 62 | 80 | row-gap: 10px; |
| @@ -429,10 +447,6 @@ | ||
| 429 | 447 | .horde_multiple_hint { |
| 430 | 448 | display: none; |
| 431 | 449 | } |
| 432 | - | |
| 433 | - .bg_list { | |
| 434 | - width: unset; | |
| 435 | - } | |
| 436 | 450 | } |
| 437 | 451 | |
| 438 | 452 | /*landscape mode phones and ipads*/ |
| @@ -5345,9 +5345,19 @@ | ||
| 5345 | 5345 | </div> |
| 5346 | 5346 | </div> |
| 5347 | 5347 | <div id="bg-scrollable-content"> |
| 5348 | - <h3 data-i18n="System Backgrounds" class="wide100p textAlignCenter"> | |
| 5348 | + <div class="heading-container-with-controls"> | |
| 5349 | - System Backgrounds | |
| 5349 | + <h3 data-i18n="System Backgrounds" class="wide100p textAlignCenter heading-text"> | |
| 5350 | - </h3> | |
| 5350 | + System Backgrounds | |
| 5351 | + </h3> | |
| 5352 | + <div class="heading-controls"> | |
| 5353 | + <button id="bg_thumb_zoom_out" class="menu_button menu_button_icon" title="Make thumbnails smaller" data-i18n="[title]Make thumbnails smaller"> | |
| 5354 | + <i class="fa-solid fa-minus"></i> | |
| 5355 | + </button> | |
| 5356 | + <button id="bg_thumb_zoom_in" class="menu_button menu_button_icon" title="Make thumbnails larger" data-i18n="[title]Make thumbnails larger"> | |
| 5357 | + <i class="fa-solid fa-plus"></i> | |
| 5358 | + </button> | |
| 5359 | + </div> | |
| 5360 | + </div> | |
| 5351 | 5361 | <div id="bg_menu_content" class="bg_list"> |
| 5352 | 5362 | </div> |
| 5353 | 5363 | <h3 data-i18n="Chat Backgrounds" class="wide100p textAlignCenter"> |
| @@ -3,7 +3,8 @@ import { chat_metadata, eventSource, event_types, generateQuietPrompt, getCurren | ||
| 3 | 3 | import { openThirdPartyExtensionMenu, saveMetadataDebounced } from './extensions.js'; |
| 4 | 4 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 5 | 5 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 6 | 6 | import { createThumbnail, flashHighlight, getBase64Async, stringFormat, debounce } from './utils.js'; |
| 7 | +import { debounce_timeout } from './constants.js'; | |
| 7 | 8 | import { t } from './i18n.js'; |
| 8 | 9 | import { Popup } from './popup.js'; |
| 9 | 10 | |
| @@ -15,6 +16,11 @@ const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkY | ||
| 15 | 16 | const PNG_PIXEL_BLOB = new Blob([Uint8Array.from(atob(PNG_PIXEL), c => c.charCodeAt(0))], { type: 'image/png' }); |
| 16 | 17 | const PLACEHOLDER_IMAGE = `url('data:image/png;base64,${PNG_PIXEL}')`; |
| 17 | 18 | |
| 19 | +const THUMBNAIL_COLUMNS_MIN = 2; | |
| 20 | +const THUMBNAIL_COLUMNS_MAX = 8; | |
| 21 | +const THUMBNAIL_COLUMNS_DEFAULT_DESKTOP = 5; | |
| 22 | +const THUMBNAIL_COLUMNS_DEFAULT_MOBILE = 3; | |
| 23 | + | |
| 18 | 24 | /** |
| 19 | 25 | * Storage for frontend-generated background thumbnails. |
| 20 | 26 | * This is used to store thumbnails for backgrounds that cannot be generated on the server. |
| @@ -45,6 +51,53 @@ export let background_settings = { | ||
| 45 | 51 | animation: false, |
| 46 | 52 | }; |
| 47 | 53 | |
| 54 | +/** | |
| 55 | + * Creates a single thumbnail DOM element. The CSS now handles all sizing. | |
| 56 | + * @param {object} imageData - Data for the image (filename, isCustom). | |
| 57 | + * @returns {HTMLElement} The created thumbnail element. | |
| 58 | + */ | |
| 59 | +function createThumbnailElement(imageData) { | |
| 60 | + const bg = imageData.filename; | |
| 61 | + const isCustom = imageData.isCustom; | |
| 62 | + | |
| 63 | + const thumbnail = $('#background_template .bg_example').clone(); | |
| 64 | + | |
| 65 | + const clipper = document.createElement('div'); | |
| 66 | + clipper.className = 'thumbnail-clipper lazy-load-background'; | |
| 67 | + clipper.style.backgroundImage = PLACEHOLDER_IMAGE; | |
| 68 | + | |
| 69 | + const titleElement = thumbnail.find('.BGSampleTitle'); | |
| 70 | + clipper.appendChild(titleElement.get(0)); | |
| 71 | + thumbnail.append(clipper); | |
| 72 | + | |
| 73 | + const url = generateUrlParameter(bg, isCustom); | |
| 74 | + const title = isCustom ? bg.split('/').pop() : bg; | |
| 75 | + const friendlyTitle = title.slice(0, title.lastIndexOf('.')); | |
| 76 | + | |
| 77 | + thumbnail.attr('title', title); | |
| 78 | + thumbnail.attr('bgfile', bg); | |
| 79 | + thumbnail.attr('custom', String(isCustom)); | |
| 80 | + thumbnail.data('url', url); | |
| 81 | + titleElement.text(friendlyTitle); | |
| 82 | + | |
| 83 | + return thumbnail.get(0); | |
| 84 | +} | |
| 85 | + | |
| 86 | +/** | |
| 87 | + * Applies the thumbnail column count to the CSS and updates button states. | |
| 88 | + * @param {number} count - The number of columns to display. | |
| 89 | + */ | |
| 90 | +function applyThumbnailColumns(count) { | |
| 91 | + const newCount = Math.max(THUMBNAIL_COLUMNS_MIN, Math.min(count, THUMBNAIL_COLUMNS_MAX)); | |
| 92 | + background_settings.thumbnailColumns = newCount; | |
| 93 | + document.documentElement.style.setProperty('--bg-thumb-columns', newCount.toString()); | |
| 94 | + | |
| 95 | + $('#bg_thumb_zoom_in').prop('disabled', newCount <= THUMBNAIL_COLUMNS_MIN); | |
| 96 | + $('#bg_thumb_zoom_out').prop('disabled', newCount >= THUMBNAIL_COLUMNS_MAX); | |
| 97 | + | |
| 98 | + saveSettingsDebounced(); | |
| 99 | +} | |
| 100 | + | |
| 48 | 101 | export function loadBackgroundSettings(settings) { |
| 49 | 102 | let backgroundSettings = settings.background; |
| 50 | 103 | if (!backgroundSettings || !backgroundSettings.name || !backgroundSettings.url) { |
| @@ -56,6 +109,16 @@ export function loadBackgroundSettings(settings) { | ||
| 56 | 109 | if (!Object.hasOwn(backgroundSettings, 'animation')) { |
| 57 | 110 | backgroundSettings.animation = false; |
| 58 | 111 | } |
| 112 | + | |
| 113 | + // If a value is already saved, use it. Otherwise, determine default based on screen size. | |
| 114 | + let columns = backgroundSettings.thumbnailColumns; | |
| 115 | + if (!columns) { | |
| 116 | + const isNarrowScreen = window.matchMedia('(max-width: 480px)').matches; | |
| 117 | + columns = isNarrowScreen ? THUMBNAIL_COLUMNS_DEFAULT_MOBILE : THUMBNAIL_COLUMNS_DEFAULT_DESKTOP; | |
| 118 | + } | |
| 119 | + background_settings.thumbnailColumns = columns; | |
| 120 | + applyThumbnailColumns(background_settings.thumbnailColumns); | |
| 121 | + | |
| 59 | 122 | setBackground(backgroundSettings.name, backgroundSettings.url); |
| 60 | 123 | setFittingClass(backgroundSettings.fitting); |
| 61 | 124 | $('#background_fitting').val(backgroundSettings.fitting); |
| @@ -75,7 +138,7 @@ async function forceSetBackground(backgroundInfo) { | ||
| 75 | 138 | list.push(bg); |
| 76 | 139 | chat_metadata[LIST_METADATA_KEY] = list; |
| 77 | 140 | saveMetadataDebounced(); |
| 78 | 141 | await getChatBackgroundsListrenderChatBackgrounds(); |
| 79 | 142 | highlightNewBackground(bg); |
| 80 | 143 | highlightLockedBackground(); |
| 81 | 144 | } |
| @@ -88,28 +151,10 @@ async function onChatChanged() { | ||
| 88 | 151 | unsetCustomBackground(); |
| 89 | 152 | } |
| 90 | 153 | |
| 91 | 154 | await getChatBackgroundsListrenderChatBackgrounds(); |
| 92 | 155 | highlightLockedBackground(); |
| 93 | 156 | } |
| 94 | 157 | |
| 95 | -async function getChatBackgroundsList() { | |
| 96 | - const list = chat_metadata[LIST_METADATA_KEY]; | |
| 97 | - const listEmpty = !Array.isArray(list) || list.length === 0; | |
| 98 | - | |
| 99 | - $('#bg_custom_content').empty(); | |
| 100 | - $('#bg_chat_hint').toggle(listEmpty); | |
| 101 | - | |
| 102 | - if (listEmpty) { | |
| 103 | - return; | |
| 104 | - } | |
| 105 | - | |
| 106 | - for (const bg of list) { | |
| 107 | - const template = await getBackgroundFromTemplate(bg, true); | |
| 108 | - $('#bg_custom_content').append(template); | |
| 109 | - } | |
| 110 | - activateLazyLoader(); | |
| 111 | -} | |
| 112 | - | |
| 113 | 158 | function getBackgroundPath(fileUrl) { |
| 114 | 159 | return `backgrounds/${encodeURIComponent(fileUrl)}`; |
| 115 | 160 | } |
| @@ -257,7 +302,7 @@ async function onCopyToSystemBackgroundClick(e) { | ||
| 257 | 302 | const index = list.indexOf(bgNames.oldBg); |
| 258 | 303 | list.splice(index, 1); |
| 259 | 304 | saveMetadataDebounced(); |
| 260 | 305 | await getChatBackgroundsListrenderChatBackgrounds(); |
| 261 | 306 | } |
| 262 | 307 | |
| 263 | 308 | /** |
| @@ -382,17 +427,21 @@ async function onDeleteBackgroundClick(e) { | ||
| 382 | 427 | list.splice(index, 1); |
| 383 | 428 | } |
| 384 | 429 | |
| 385 | - const siblingSelector = '.bg_example:not(#form_bg_download)'; | |
| 430 | + if (bg === background_settings.name) { | |
| 386 | 431 | const nextBgsiblingSelector = bgToDelete'.next(siblingSelector)bg_example'; |
| 387 | 432 | const prevBgnextBg = bgToDelete.prevnext(siblingSelector); |
| 388 | 433 | const anyBgprevBg = $bgToDelete.prev(siblingSelector); |
| 389 | 434 | |
| 390 | 435 | if (nextBg.length > 0) { |
| 391 | 436 | nextBg.trigger('click'); |
| 392 | 437 | } else if (prevBg.length > 0) { |
| 393 | 438 | prevBg.trigger('click'); |
| 394 | 439 | } else { |
| 395 | - $(anyBg[Math.floor(Math.random() * anyBg.length)]).trigger('click'); | |
| 440 | + const anyOtherBg = $('.bg_example').not(bgToDelete).first(); | |
| 441 | + if (anyOtherBg.length > 0) { | |
| 442 | + anyOtherBg.trigger('click'); | |
| 443 | + } | |
| 444 | + } | |
| 396 | 445 | } |
| 397 | 446 | |
| 398 | 447 | bgToDelete.remove(); |
| @@ -404,7 +453,7 @@ async function onDeleteBackgroundClick(e) { | ||
| 404 | 453 | } |
| 405 | 454 | |
| 406 | 455 | if (isCustom) { |
| 407 | 456 | await getChatBackgroundsListrenderChatBackgrounds(); |
| 408 | 457 | saveMetadataDebounced(); |
| 409 | 458 | } |
| 410 | 459 | } |
| @@ -445,6 +494,47 @@ async function autoBackgroundCommand() { | ||
| 445 | 494 | return ''; |
| 446 | 495 | } |
| 447 | 496 | |
| 497 | +/** | |
| 498 | + * Renders the system backgrounds gallery. | |
| 499 | + * @param {string[]} [backgrounds] - Optional filtered list of backgrounds. | |
| 500 | + */ | |
| 501 | +function renderSystemBackgrounds(backgrounds) { | |
| 502 | + const sourceList = backgrounds || []; | |
| 503 | + const container = $('#bg_menu_content'); | |
| 504 | + container.empty(); | |
| 505 | + | |
| 506 | + if (sourceList.length === 0) return; | |
| 507 | + | |
| 508 | + sourceList.forEach(bg => { | |
| 509 | + const imageData = { filename: bg, isCustom: false }; | |
| 510 | + const thumbnail = createThumbnailElement(imageData); | |
| 511 | + container.append(thumbnail); | |
| 512 | + }); | |
| 513 | + | |
| 514 | + activateLazyLoader(); | |
| 515 | +} | |
| 516 | + | |
| 517 | +/** | |
| 518 | + * Renders the chat-specific (custom) backgrounds gallery. | |
| 519 | + * @param {string[]} [backgrounds] - Optional filtered list of backgrounds. | |
| 520 | + */ | |
| 521 | +function renderChatBackgrounds(backgrounds) { | |
| 522 | + const sourceList = backgrounds ?? (chat_metadata[LIST_METADATA_KEY] || []); | |
| 523 | + const container = $('#bg_custom_content'); | |
| 524 | + container.empty(); | |
| 525 | + $('#bg_chat_hint').toggle(!sourceList.length); | |
| 526 | + | |
| 527 | + if (sourceList.length === 0) return; | |
| 528 | + | |
| 529 | + sourceList.forEach(bg => { | |
| 530 | + const imageData = { filename: bg, isCustom: true }; | |
| 531 | + const thumbnail = createThumbnailElement(imageData); | |
| 532 | + container.append(thumbnail); | |
| 533 | + }); | |
| 534 | + | |
| 535 | + activateLazyLoader(); | |
| 536 | +} | |
| 537 | + | |
| 448 | 538 | export async function getBackgrounds() { |
| 449 | 539 | const response = await fetch('/api/backgrounds/all', { |
| 450 | 540 | method: 'POST', |
| @@ -454,12 +544,7 @@ export async function getBackgrounds() { | ||
| 454 | 544 | if (response.ok) { |
| 455 | 545 | const { images, config } = await response.json(); |
| 456 | 546 | Object.assign(THUMBNAIL_CONFIG, config); |
| 457 | - $('#bg_menu_content').children('div').remove(); | |
| 547 | + renderSystemBackgrounds(images); | |
| 458 | - for (const bg of images) { | |
| 459 | - const template = await getBackgroundFromTemplate(bg, false); | |
| 460 | - $('#bg_menu_content').append(template); | |
| 461 | - } | |
| 462 | - activateLazyLoader(); | |
| 463 | 548 | } |
| 464 | 549 | } |
| 465 | 550 | |
| @@ -481,14 +566,19 @@ function activateLazyLoader() { | ||
| 481 | 566 | lazyLoadObserver = new IntersectionObserver((entries, observer) => { |
| 482 | 567 | entries.forEach(entry => { |
| 483 | 568 | if (entry.target instanceof HTMLElement && entry.isIntersecting) { |
| 484 | 569 | const targetclipper = entry.target; |
| 485 | 570 | const bgparentThumbnail = targetclipper.getAttributeclosest('bgfile.bg_example'); |
| 486 | - const isCustom = target.getAttribute('custom') === 'true'; | |
| 571 | + | |
| 487 | - resolveImageUrl(bg, isCustom) | |
| 572 | + if (parentThumbnail) { | |
| 488 | - .then(url => { target.style.backgroundImage = url; }) | |
| 573 | + const bg = parentThumbnail.getAttribute('bgfile'); | |
| 489 | - .catch(() => { target.style.backgroundImage = PLACEHOLDER_IMAGE; }); | |
| 574 | + const isCustom = parentThumbnail.getAttribute('custom') === 'true'; | |
| 490 | - target.classList.remove('lazy-load-background'); | |
| 575 | + resolveImageUrl(bg, isCustom) | |
| 491 | - observer.unobserve(target); | |
| 576 | + .then(url => { clipper.style.backgroundImage = url; }) | |
| 577 | + .catch(() => { clipper.style.backgroundImage = PLACEHOLDER_IMAGE; }); | |
| 578 | + } | |
| 579 | + | |
| 580 | + clipper.classList.remove('lazy-load-background'); | |
| 581 | + observer.unobserve(clipper); | |
| 492 | 582 | } |
| 493 | 583 | }); |
| 494 | 584 | }, options); |
| @@ -529,28 +619,6 @@ async function resolveImageUrl(bg, isCustom) { | ||
| 529 | 619 | return `url("${thumbnailUrl}")`; |
| 530 | 620 | } |
| 531 | 621 | |
| 532 | -/** | |
| 533 | - * Instantiates a background template | |
| 534 | - * @param {string} bg Path to background | |
| 535 | - * @param {boolean} isCustom Whether the background is custom | |
| 536 | - * @returns {Promise<JQuery<HTMLElement>>} Background template | |
| 537 | - */ | |
| 538 | -async function getBackgroundFromTemplate(bg, isCustom) { | |
| 539 | - const template = $('#background_template .bg_example').clone(); | |
| 540 | - const url = generateUrlParameter(bg, isCustom); | |
| 541 | - const title = isCustom ? bg.split('/').pop() : bg; | |
| 542 | - const friendlyTitle = title.slice(0, title.lastIndexOf('.')); | |
| 543 | - | |
| 544 | - template.attr('title', title); | |
| 545 | - template.attr('bgfile', bg); | |
| 546 | - template.attr('custom', String(isCustom)); | |
| 547 | - template.data('url', url); | |
| 548 | - template.addClass('lazy-load-background'); | |
| 549 | - template.css('background-image', PLACEHOLDER_IMAGE); | |
| 550 | - template.find('.BGSampleTitle').text(friendlyTitle); | |
| 551 | - return template; | |
| 552 | -} | |
| 553 | - | |
| 554 | 622 | async function setBackground(bg, url) { |
| 555 | 623 | $('#bg1').css('background-image', url); |
| 556 | 624 | background_settings.name = bg; |
| @@ -688,17 +756,17 @@ function setFittingClass(fitting) { | ||
| 688 | 756 | } |
| 689 | 757 | |
| 690 | 758 | function onBackgroundFilterInput() { |
| 691 | 759 | const filterValue = String($(this'#bg-filter').val()).toLowerCase(); |
| 692 | 760 | $('#bg_menu_content > div.bg_example, #bg_custom_content > .bg_example').each(function () { |
| 693 | 761 | const $bgContentbg = $(this); |
| 694 | - if ($bgContent.attr('title').toLowerCase().includes(filterValue)) { | |
| 762 | + const title = $bg.attr('title') || ''; | |
| 695 | - $bgContent.show(); | |
| 763 | + const hasMatch = title.toLowerCase().includes(filterValue); | |
| 696 | - } else { | |
| 764 | + $bg.toggle(hasMatch); | |
| 697 | - $bgContent.hide(); | |
| 698 | - } | |
| 699 | 765 | }); |
| 700 | 766 | } |
| 701 | 767 | |
| 768 | +const debouncedOnBackgroundFilterInput = debounce(onBackgroundFilterInput, debounce_timeout.standard); | |
| 769 | + | |
| 702 | 770 | export function initBackgrounds() { |
| 703 | 771 | eventSource.on(event_types.CHAT_CHANGED, onChatChanged); |
| 704 | 772 | eventSource.on(event_types.FORCE_SET_BACKGROUND, forceSetBackground); |
| @@ -715,6 +783,11 @@ export function initBackgrounds() { | ||
| 715 | 783 | $context.addClass('mobile-menu-open'); |
| 716 | 784 | } |
| 717 | 785 | }) |
| 786 | + .off('blur', '.bg_example.mobile-menu-open').on('blur', '.bg_example.mobile-menu-open', function () { | |
| 787 | + if (!$(this).is(':focus-within')) { | |
| 788 | + $(this).removeClass('mobile-menu-open'); | |
| 789 | + } | |
| 790 | + }) | |
| 718 | 791 | .off('click', '.jg-button').on('click', '.jg-button', function (e) { |
| 719 | 792 | e.stopPropagation(); |
| 720 | 793 | const action = $(this).data('action'); |
| @@ -738,9 +811,15 @@ export function initBackgrounds() { | ||
| 738 | 811 | } |
| 739 | 812 | }); |
| 740 | 813 | |
| 814 | + $('#bg_thumb_zoom_in').on('click', () => { | |
| 815 | + applyThumbnailColumns(background_settings.thumbnailColumns - 1); | |
| 816 | + }); | |
| 817 | + $('#bg_thumb_zoom_out').on('click', () => { | |
| 818 | + applyThumbnailColumns(background_settings.thumbnailColumns + 1); | |
| 819 | + }); | |
| 741 | 820 | $('#auto_background').on('click', autoBackgroundCommand); |
| 742 | 821 | $('#add_bg_button').on('change', onBackgroundUploadSelected); |
| 743 | 822 | $('#bg-filter').on('input', onBackgroundFilterInput() => debouncedOnBackgroundFilterInput()); |
| 744 | 823 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 745 | 824 | name: 'lockbg', |
| 746 | 825 | callback: () => onLockBackgroundClick(new CustomEvent('click')), |
| @@ -2940,6 +2940,7 @@ select option:not(:checked) { | ||
| 2940 | 2940 | text-align: center; |
| 2941 | 2941 | } |
| 2942 | 2942 | |
| 2943 | +.menu_button[disabled], | |
| 2943 | 2944 | .menu_button.disabled { |
| 2944 | 2945 | filter: brightness(75%) grayscale(1); |
| 2945 | 2946 | opacity: 0.5; |
| @@ -3675,8 +3676,8 @@ grammarly-extension { | ||
| 3675 | 3676 | min-width: calc(1.25em + 12px); |
| 3676 | 3677 | } |
| 3677 | 3678 | |
| 3678 | 3679 | .menu_button:not(.disabled):not([disabled]):hover, |
| 3679 | 3680 | .menu_button:not(.disabled):not([disabled]).active { |
| 3680 | 3681 | background-color: var(--white30a); |
| 3681 | 3682 | } |
| 3682 | 3683 | |