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>

eec837cee60d97f69dc0bdbade1720bf0cdb7a37

L <123923688+Vibecoder9000@users.noreply.github.com>

Signed
5 files changed, +234 -104Showing whitespace changes
public/css/backgrounds.css+53 -27
@@ -88,6 +88,24 @@ body.reduced-motion #bg_custom {
88 gap: 5px;88 gap: 5px;
89}89}
9090
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#bg-scrollable-content {109#bg-scrollable-content {
92 flex-grow: 1;110 flex-grow: 1;
93 overflow-y: auto;111 overflow-y: auto;
@@ -95,46 +113,43 @@ body.reduced-motion #bg_custom {
95 padding: 0 5px 15px;113 padding: 0 5px 15px;
96}114}
97115
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#bg-filter {124#bg-filter {
99 font-size: calc(var(--mainFontSize) * 0.95);125 font-size: calc(var(--mainFontSize) * 0.95);
100}126}
101127
102/* Thumbnail Menu & Buttons */128/* Thumbnails */
129.bg_example:hover .BGSampleTitle {
130 opacity: 1;
131}
132
103.bg_example .mobile-only-menu-toggle {133.bg_example .mobile-only-menu-toggle {
104 display: none;134 display: none;
105}135}
106136
107.bg_example.flex-container {137.bg_example {
108 width: 30%;
109 max-width: 200px;
110 margin: 5px;
111 aspect-ratio: 16/9;
112 cursor: pointer;138 cursor: pointer;
113 box-shadow: 0 0 7px var(--black50a);139 box-shadow: 0 0 7px var(--black50a);
114
115 position: relative;140 position: relative;
116 overflow: hidden;141 overflow: hidden;
117 border-radius: 8px;142 border-radius: 8px;
118 border: 0px solid transparent;143 border: 0px solid transparent;
119 outline: 2px solid var(--SmartThemeBorderColor);144 outline: 2px solid var(--SmartThemeBorderColor);
120 outline-offset: -1px;145 outline-offset: -1px;
121}
122146
123.bg_example.flex-container:focus-visible {147 height: auto;
124 outline-offset: inherit;148 aspect-ratio: 16 / 9;
125}149}
126150
127.bg_example_img {151.bg_example:focus-visible {
128 position: absolute;152 outline-offset: inherit;
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}
139154
140.bg_example .jg-menu {155.bg_example .jg-menu {
@@ -143,9 +158,8 @@ body.reduced-motion #bg_custom {
143 top: 2px;158 top: 2px;
144 right: 2px;159 right: 2px;
145 background-color: rgba(0, 0, 0, 0.5);160 background-color: rgba(0, 0, 0, 0.5);
146 border-radius: 8px;161 border-radius: 5px;
147 gap: 3px;162 padding: 3px 3px;
148 padding: 3px 5px;
149 z-index: 3;163 z-index: 3;
150 backdrop-filter: blur(4px);164 backdrop-filter: blur(4px);
151 border: 1px solid var(--SmartThemeBorderColor);165 border: 1px solid var(--SmartThemeBorderColor);
@@ -168,14 +182,14 @@ body.reduced-motion #bg_custom {
168182
169.bg_example .jg-button {183.bg_example .jg-button {
170 display: flex;184 display: flex;
171 width: 30px;185 width: 24px;
172 height: 30px;186 height: 24px;
173 align-items: center;187 align-items: center;
174 justify-content: center;188 justify-content: center;
175 color: white;189 color: white;
176 padding: 5px;190 padding: 5px;
177 font-size: 1.1em;191 font-size: 1.1em;
178 border-radius: 6px;192 border-radius: 5px;
179 transition: background-color var(--animation-duration) ease;193 transition: background-color var(--animation-duration) ease;
180}194}
181195
@@ -195,6 +209,18 @@ body.reduced-motion #bg_custom {
195 display: flex;209 display: flex;
196}210}
197211
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.bg_example:not([custom="true"]) .jg-copy,224.bg_example:not([custom="true"]) .jg-copy,
199.bg_example[custom="true"] .jg-edit {225.bg_example[custom="true"] .jg-edit {
200 display: none;226 display: none;
public/css/mobile-styles.css+18 -4
@@ -21,6 +21,14 @@
21 display: none;21 display: none;
22 }22 }
2323
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 .bg_button {32 .bg_button {
25 font-size: 15px;33 font-size: 15px;
26 }34 }
@@ -40,6 +48,11 @@
40 z-index: 4;48 z-index: 4;
41 }49 }
4250
51 .bg_example.mobile-menu-open .mobile-only-menu-toggle {
52 opacity: 0;
53 pointer-events: none;
54 }
55
43 .bg_example .mobile-only-menu-toggle {56 .bg_example .mobile-only-menu-toggle {
44 display: flex;57 display: flex;
45 align-items: center;58 align-items: center;
@@ -57,6 +70,11 @@
57 backdrop-filter: blur(2px);70 backdrop-filter: blur(2px);
58 }71 }
5972
73 .bg_example .jg-button {
74 width: 30px;
75 height: 30px;
76 }
77
60 #bg-header-controls {78 #bg-header-controls {
61 flex-wrap: wrap;79 flex-wrap: wrap;
62 row-gap: 10px;80 row-gap: 10px;
@@ -429,10 +447,6 @@
429 .horde_multiple_hint {447 .horde_multiple_hint {
430 display: none;448 display: none;
431 }449 }
432
433 .bg_list {
434 width: unset;
435 }
436}450}
437451
438/*landscape mode phones and ipads*/452/*landscape mode phones and ipads*/
public/index.html+11 -1
@@ -5345,9 +5345,19 @@
5345 </div>5345 </div>
5346 </div>5346 </div>
5347 <div id="bg-scrollable-content">5347 <div id="bg-scrollable-content">
5348 <h3 data-i18n="System Backgrounds" class="wide100p textAlignCenter">5348 <div class="heading-container-with-controls">
5349 <h3 data-i18n="System Backgrounds" class="wide100p textAlignCenter heading-text">
5349 System Backgrounds5350 System Backgrounds
5350 </h3>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 <div id="bg_menu_content" class="bg_list">5361 <div id="bg_menu_content" class="bg_list">
5352 </div>5362 </div>
5353 <h3 data-i18n="Chat Backgrounds" class="wide100p textAlignCenter">5363 <h3 data-i18n="Chat Backgrounds" class="wide100p textAlignCenter">
public/scripts/backgrounds.js+149 -70
@@ -3,7 +3,8 @@ import { chat_metadata, eventSource, event_types, generateQuietPrompt, getCurren
3import { openThirdPartyExtensionMenu, saveMetadataDebounced } from './extensions.js';3import { openThirdPartyExtensionMenu, saveMetadataDebounced } from './extensions.js';
4import { SlashCommand } from './slash-commands/SlashCommand.js';4import { SlashCommand } from './slash-commands/SlashCommand.js';
5import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';5import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
6import { createThumbnail, flashHighlight, getBase64Async, stringFormat } from './utils.js';6import { createThumbnail, flashHighlight, getBase64Async, stringFormat, debounce } from './utils.js';
7import { debounce_timeout } from './constants.js';
7import { t } from './i18n.js';8import { t } from './i18n.js';
8import { Popup } from './popup.js';9import { Popup } from './popup.js';
910
@@ -15,6 +16,11 @@ const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkY
15const PNG_PIXEL_BLOB = new Blob([Uint8Array.from(atob(PNG_PIXEL), c => c.charCodeAt(0))], { type: 'image/png' });16const PNG_PIXEL_BLOB = new Blob([Uint8Array.from(atob(PNG_PIXEL), c => c.charCodeAt(0))], { type: 'image/png' });
16const PLACEHOLDER_IMAGE = `url('data:image/png;base64,${PNG_PIXEL}')`;17const PLACEHOLDER_IMAGE = `url('data:image/png;base64,${PNG_PIXEL}')`;
1718
19const THUMBNAIL_COLUMNS_MIN = 2;
20const THUMBNAIL_COLUMNS_MAX = 8;
21const THUMBNAIL_COLUMNS_DEFAULT_DESKTOP = 5;
22const THUMBNAIL_COLUMNS_DEFAULT_MOBILE = 3;
23
18/**24/**
19 * Storage for frontend-generated background thumbnails.25 * Storage for frontend-generated background thumbnails.
20 * This is used to store thumbnails for backgrounds that cannot be generated on the server.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 animation: false,51 animation: false,
46};52};
4753
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 */
59function 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 */
90function 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
48export function loadBackgroundSettings(settings) {101export function loadBackgroundSettings(settings) {
49 let backgroundSettings = settings.background;102 let backgroundSettings = settings.background;
50 if (!backgroundSettings || !backgroundSettings.name || !backgroundSettings.url) {103 if (!backgroundSettings || !backgroundSettings.name || !backgroundSettings.url) {
@@ -56,6 +109,16 @@ export function loadBackgroundSettings(settings) {
56 if (!Object.hasOwn(backgroundSettings, 'animation')) {109 if (!Object.hasOwn(backgroundSettings, 'animation')) {
57 backgroundSettings.animation = false;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 setBackground(backgroundSettings.name, backgroundSettings.url);122 setBackground(backgroundSettings.name, backgroundSettings.url);
60 setFittingClass(backgroundSettings.fitting);123 setFittingClass(backgroundSettings.fitting);
61 $('#background_fitting').val(backgroundSettings.fitting);124 $('#background_fitting').val(backgroundSettings.fitting);
@@ -75,7 +138,7 @@ async function forceSetBackground(backgroundInfo) {
75 list.push(bg);138 list.push(bg);
76 chat_metadata[LIST_METADATA_KEY] = list;139 chat_metadata[LIST_METADATA_KEY] = list;
77 saveMetadataDebounced();140 saveMetadataDebounced();
78 await getChatBackgroundsList();141 renderChatBackgrounds();
79 highlightNewBackground(bg);142 highlightNewBackground(bg);
80 highlightLockedBackground();143 highlightLockedBackground();
81}144}
@@ -88,28 +151,10 @@ async function onChatChanged() {
88 unsetCustomBackground();151 unsetCustomBackground();
89 }152 }
90153
91 await getChatBackgroundsList();154 renderChatBackgrounds();
92 highlightLockedBackground();155 highlightLockedBackground();
93}156}
94157
95async 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
113function getBackgroundPath(fileUrl) {158function getBackgroundPath(fileUrl) {
114 return `backgrounds/${encodeURIComponent(fileUrl)}`;159 return `backgrounds/${encodeURIComponent(fileUrl)}`;
115}160}
@@ -257,7 +302,7 @@ async function onCopyToSystemBackgroundClick(e) {
257 const index = list.indexOf(bgNames.oldBg);302 const index = list.indexOf(bgNames.oldBg);
258 list.splice(index, 1);303 list.splice(index, 1);
259 saveMetadataDebounced();304 saveMetadataDebounced();
260 await getChatBackgroundsList();305 renderChatBackgrounds();
261}306}
262307
263/**308/**
@@ -382,17 +427,21 @@ async function onDeleteBackgroundClick(e) {
382 list.splice(index, 1);427 list.splice(index, 1);
383 }428 }
384429
385 const siblingSelector = '.bg_example:not(#form_bg_download)';430 if (bg === background_settings.name) {
431 const siblingSelector = '.bg_example';
386 const nextBg = bgToDelete.next(siblingSelector);432 const nextBg = bgToDelete.next(siblingSelector);
387 const prevBg = bgToDelete.prev(siblingSelector);433 const prevBg = bgToDelete.prev(siblingSelector);
388 const anyBg = $(siblingSelector);
389434
390 if (nextBg.length > 0) {435 if (nextBg.length > 0) {
391 nextBg.trigger('click');436 nextBg.trigger('click');
392 } else if (prevBg.length > 0) {437 } else if (prevBg.length > 0) {
393 prevBg.trigger('click');438 prevBg.trigger('click');
394 } else {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 }
397446
398 bgToDelete.remove();447 bgToDelete.remove();
@@ -404,7 +453,7 @@ async function onDeleteBackgroundClick(e) {
404 }453 }
405454
406 if (isCustom) {455 if (isCustom) {
407 await getChatBackgroundsList();456 renderChatBackgrounds();
408 saveMetadataDebounced();457 saveMetadataDebounced();
409 }458 }
410 }459 }
@@ -445,6 +494,47 @@ async function autoBackgroundCommand() {
445 return '';494 return '';
446}495}
447496
497/**
498 * Renders the system backgrounds gallery.
499 * @param {string[]} [backgrounds] - Optional filtered list of backgrounds.
500 */
501function 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 */
521function 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
448export async function getBackgrounds() {538export async function getBackgrounds() {
449 const response = await fetch('/api/backgrounds/all', {539 const response = await fetch('/api/backgrounds/all', {
450 method: 'POST',540 method: 'POST',
@@ -454,12 +544,7 @@ export async function getBackgrounds() {
454 if (response.ok) {544 if (response.ok) {
455 const { images, config } = await response.json();545 const { images, config } = await response.json();
456 Object.assign(THUMBNAIL_CONFIG, config);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}
465550
@@ -481,14 +566,19 @@ function activateLazyLoader() {
481 lazyLoadObserver = new IntersectionObserver((entries, observer) => {566 lazyLoadObserver = new IntersectionObserver((entries, observer) => {
482 entries.forEach(entry => {567 entries.forEach(entry => {
483 if (entry.target instanceof HTMLElement && entry.isIntersecting) {568 if (entry.target instanceof HTMLElement && entry.isIntersecting) {
484 const target = entry.target;569 const clipper = entry.target;
485 const bg = target.getAttribute('bgfile');570 const parentThumbnail = clipper.closest('.bg_example');
486 const isCustom = target.getAttribute('custom') === 'true';571
572 if (parentThumbnail) {
573 const bg = parentThumbnail.getAttribute('bgfile');
574 const isCustom = parentThumbnail.getAttribute('custom') === 'true';
487 resolveImageUrl(bg, isCustom)575 resolveImageUrl(bg, isCustom)
488 .then(url => { target.style.backgroundImage = url; })576 .then(url => { clipper.style.backgroundImage = url; })
489 .catch(() => { target.style.backgroundImage = PLACEHOLDER_IMAGE; });577 .catch(() => { clipper.style.backgroundImage = PLACEHOLDER_IMAGE; });
490 target.classList.remove('lazy-load-background');578 }
491 observer.unobserve(target);579
580 clipper.classList.remove('lazy-load-background');
581 observer.unobserve(clipper);
492 }582 }
493 });583 });
494 }, options);584 }, options);
@@ -529,28 +619,6 @@ async function resolveImageUrl(bg, isCustom) {
529 return `url("${thumbnailUrl}")`;619 return `url("${thumbnailUrl}")`;
530}620}
531621
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 */
538async 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
554async function setBackground(bg, url) {622async function setBackground(bg, url) {
555 $('#bg1').css('background-image', url);623 $('#bg1').css('background-image', url);
556 background_settings.name = bg;624 background_settings.name = bg;
@@ -688,17 +756,17 @@ function setFittingClass(fitting) {
688}756}
689757
690function onBackgroundFilterInput() {758function onBackgroundFilterInput() {
691 const filterValue = String($(this).val()).toLowerCase();759 const filterValue = String($('#bg-filter').val()).toLowerCase();
692 $('#bg_menu_content > div').each(function () {760 $('#bg_menu_content > .bg_example, #bg_custom_content > .bg_example').each(function () {
693 const $bgContent = $(this);761 const $bg = $(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}
701767
768const debouncedOnBackgroundFilterInput = debounce(onBackgroundFilterInput, debounce_timeout.standard);
769
702export function initBackgrounds() {770export function initBackgrounds() {
703 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);771 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
704 eventSource.on(event_types.FORCE_SET_BACKGROUND, forceSetBackground);772 eventSource.on(event_types.FORCE_SET_BACKGROUND, forceSetBackground);
@@ -715,6 +783,11 @@ export function initBackgrounds() {
715 $context.addClass('mobile-menu-open');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 .off('click', '.jg-button').on('click', '.jg-button', function (e) {791 .off('click', '.jg-button').on('click', '.jg-button', function (e) {
719 e.stopPropagation();792 e.stopPropagation();
720 const action = $(this).data('action');793 const action = $(this).data('action');
@@ -738,9 +811,15 @@ export function initBackgrounds() {
738 }811 }
739 });812 });
740813
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 $('#auto_background').on('click', autoBackgroundCommand);820 $('#auto_background').on('click', autoBackgroundCommand);
742 $('#add_bg_button').on('change', onBackgroundUploadSelected);821 $('#add_bg_button').on('change', onBackgroundUploadSelected);
743 $('#bg-filter').on('input', onBackgroundFilterInput);822 $('#bg-filter').on('input', () => debouncedOnBackgroundFilterInput());
744 SlashCommandParser.addCommandObject(SlashCommand.fromProps({823 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
745 name: 'lockbg',824 name: 'lockbg',
746 callback: () => onLockBackgroundClick(new CustomEvent('click')),825 callback: () => onLockBackgroundClick(new CustomEvent('click')),
public/style.css+3 -2
@@ -2940,6 +2940,7 @@ select option:not(:checked) {
2940 text-align: center;2940 text-align: center;
2941}2941}
29422942
2943.menu_button[disabled],
2943.menu_button.disabled {2944.menu_button.disabled {
2944 filter: brightness(75%) grayscale(1);2945 filter: brightness(75%) grayscale(1);
2945 opacity: 0.5;2946 opacity: 0.5;
@@ -3675,8 +3676,8 @@ grammarly-extension {
3675 min-width: calc(1.25em + 12px);3676 min-width: calc(1.25em + 12px);
3676}3677}
36773678
3678.menu_button:not(.disabled):hover,3679.menu_button:not(.disabled):not([disabled]):hover,
3679.menu_button:not(.disabled).active {3680.menu_button:not(.disabled):not([disabled]).active {
3680 background-color: var(--white30a);3681 background-color: var(--white30a);
3681}3682}
36823683