More modern backgrounds buttons and filename (#4484) * init * move background styles, enlarge and clip image to get rid of antialising bug * move styles from style.css to backgrounds.css * Reformat backgrounds.css * Improve selector specificity, fix type errors, increase font size on thumbnails, decrease mobile toggle size * Fix webp chat backgrounds thumbnails * Respect reduced motion preference * Remove transition delay --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -1,3 +1,68 @@ | ||
| 1 | +/* Main Page Backgrounds */ | |
| 2 | +#bg1, | |
| 3 | +#bg_custom { | |
| 4 | + background-repeat: no-repeat; | |
| 5 | + background-attachment: fixed; | |
| 6 | + background-size: cover; | |
| 7 | + position: absolute; | |
| 8 | + width: 100%; | |
| 9 | + height: 100%; | |
| 10 | + transition: background-image var(--animation-duration-3x) ease-in-out; | |
| 11 | +} | |
| 12 | + | |
| 13 | +/* Fitting options */ | |
| 14 | +#background_fitting { | |
| 15 | + max-width: 6em; | |
| 16 | +} | |
| 17 | + | |
| 18 | +/* Fill/Cover - scales to fill width while maintaining aspect ratio */ | |
| 19 | +#bg1.cover, | |
| 20 | +#bg_custom.cover { | |
| 21 | + background-size: cover; | |
| 22 | + background-position: center; | |
| 23 | +} | |
| 24 | + | |
| 25 | +/* Fit/Contain - shows entire image maintaining aspect ratio */ | |
| 26 | +#bg1.contain, | |
| 27 | +#bg_custom.contain { | |
| 28 | + background-size: contain; | |
| 29 | + background-position: center; | |
| 30 | + background-repeat: no-repeat; | |
| 31 | +} | |
| 32 | + | |
| 33 | +/* Stretch - stretches to fill entire space */ | |
| 34 | +#bg1.stretch, | |
| 35 | +#bg_custom.stretch { | |
| 36 | + background-size: 100% 100%; | |
| 37 | +} | |
| 38 | + | |
| 39 | +/* Center - centers without scaling */ | |
| 40 | +#bg1.center, | |
| 41 | +#bg_custom.center { | |
| 42 | + background-size: auto; | |
| 43 | + background-position: center; | |
| 44 | + background-repeat: no-repeat; | |
| 45 | +} | |
| 46 | + | |
| 47 | +body.reduced-motion #bg1, | |
| 48 | +body.reduced-motion #bg_custom { | |
| 49 | + transition: none; | |
| 50 | +} | |
| 51 | + | |
| 52 | +#bg1 { | |
| 53 | + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='); | |
| 54 | + z-index: -3; | |
| 55 | +} | |
| 56 | + | |
| 57 | +#bg_custom { | |
| 58 | + background-image: none; | |
| 59 | + z-index: -2; | |
| 60 | +} | |
| 61 | + | |
| 62 | +.bg_example.locked { | |
| 63 | + outline: 2px solid var(--golden); | |
| 64 | +} | |
| 65 | + | |
| 1 | 66 | /* This is the main flex container for the entire drawer */ |
| 2 | 67 | #Backgrounds.drawer-content.openDrawer.bg-drawer-layout { |
| 3 | 68 | display: flex; |
| @@ -35,3 +100,113 @@ | ||
| 35 | 100 | #bg-filter { |
| 36 | 101 | font-size: calc(var(--mainFontSize) * 0.95); |
| 37 | 102 | } |
| 103 | + | |
| 104 | +/* Thumbnail Menu & Buttons */ | |
| 105 | +.bg_example .mobile-only-menu-toggle { | |
| 106 | + display: none; | |
| 107 | +} | |
| 108 | + | |
| 109 | +.bg_example.flex-container { | |
| 110 | + width: 30%; | |
| 111 | + max-width: 200px; | |
| 112 | + margin: 5px; | |
| 113 | + aspect-ratio: 16/9; | |
| 114 | + cursor: pointer; | |
| 115 | + box-shadow: 0 0 7px var(--black50a); | |
| 116 | + | |
| 117 | + position: relative; | |
| 118 | + overflow: hidden; | |
| 119 | + border-radius: 10px; | |
| 120 | + border: 1px solid var(--SmartThemeBorderColor); | |
| 121 | +} | |
| 122 | + | |
| 123 | +.bg_example_img { | |
| 124 | + position: absolute; | |
| 125 | + top: -2px; | |
| 126 | + left: -2px; | |
| 127 | + right: -2px; | |
| 128 | + bottom: -2px; | |
| 129 | + | |
| 130 | + background-image: inherit; | |
| 131 | + | |
| 132 | + background-size: cover; | |
| 133 | + background-position: center; | |
| 134 | +} | |
| 135 | + | |
| 136 | +.bg_example .jg-menu { | |
| 137 | + display: flex; | |
| 138 | + position: absolute; | |
| 139 | + top: 2px; | |
| 140 | + right: 2px; | |
| 141 | + background-color: rgba(0, 0, 0, 0.5); | |
| 142 | + border-radius: 8px; | |
| 143 | + gap: 2px; | |
| 144 | + padding: 3px; | |
| 145 | + z-index: 3; | |
| 146 | + backdrop-filter: blur(4px); | |
| 147 | + border: 1px solid var(--SmartThemeBorderColor); | |
| 148 | + | |
| 149 | + opacity: 0; | |
| 150 | + visibility: hidden; | |
| 151 | + transform: scale(0.9); | |
| 152 | + transform-origin: center; | |
| 153 | + transition: opacity var(--animation-duration) ease-out, visibility var(--animation-duration) ease-out, transform var(--animation-duration) ease-out; | |
| 154 | +} | |
| 155 | + | |
| 156 | +.bg_example:hover .jg-menu { | |
| 157 | + opacity: 1; | |
| 158 | + visibility: visible; | |
| 159 | + transform: scale(1); | |
| 160 | +} | |
| 161 | + | |
| 162 | +.bg_example .jg-button { | |
| 163 | + display: flex; | |
| 164 | + align-items: center; | |
| 165 | + justify-content: center; | |
| 166 | + color: white; | |
| 167 | + padding: 5px; | |
| 168 | + font-size: 1.2em; | |
| 169 | + border-radius: 6px; | |
| 170 | + transition: background-color var(--animation-duration) ease; | |
| 171 | +} | |
| 172 | + | |
| 173 | +.bg_example .jg-button:hover { | |
| 174 | + background-color: rgba(255, 255, 255, 0.2); | |
| 175 | +} | |
| 176 | + | |
| 177 | +.bg_example .jg-unlock { | |
| 178 | + display: none; | |
| 179 | +} | |
| 180 | + | |
| 181 | +.bg_example.locked .jg-lock { | |
| 182 | + display: none; | |
| 183 | +} | |
| 184 | + | |
| 185 | +.bg_example.locked .jg-unlock { | |
| 186 | + display: flex; | |
| 187 | +} | |
| 188 | + | |
| 189 | +/* Thumbnail Title */ | |
| 190 | +.bg_example .BGSampleTitle { | |
| 191 | + position: absolute; | |
| 192 | + bottom: 0; | |
| 193 | + left: 0; | |
| 194 | + right: 0; | |
| 195 | + background: linear-gradient(transparent, rgba(0, 0, 0, 0.9)); | |
| 196 | + color: var(--SmartThemeBodyColor); | |
| 197 | + font-size: 0.9em; | |
| 198 | + font-weight: 600; | |
| 199 | + padding: 0px 6px 2px; | |
| 200 | + text-align: center; | |
| 201 | + white-space: nowrap; | |
| 202 | + overflow: hidden; | |
| 203 | + text-overflow: ellipsis; | |
| 204 | + opacity: 0; | |
| 205 | + transition: opacity var(--animation-duration) ease-in-out; | |
| 206 | + pointer-events: none; | |
| 207 | + border-radius: 0 0 8px 8px; | |
| 208 | +} | |
| 209 | + | |
| 210 | +.bg_example:hover .BGSampleTitle { | |
| 211 | + opacity: 1; | |
| 212 | +} | |
| @@ -25,6 +25,37 @@ | ||
| 25 | 25 | font-size: 15px; |
| 26 | 26 | } |
| 27 | 27 | |
| 28 | + #Backgrounds .bg_example .BGSampleTitle { | |
| 29 | + opacity: 1; | |
| 30 | + bottom: 0px; | |
| 31 | + } | |
| 32 | + | |
| 33 | + .bg_example:hover .jg-menu { | |
| 34 | + display: none; | |
| 35 | + } | |
| 36 | + | |
| 37 | + .bg_example.mobile-menu-open .jg-menu { | |
| 38 | + display: flex; | |
| 39 | + z-index: 4; | |
| 40 | + } | |
| 41 | + | |
| 42 | + .bg_example .mobile-only-menu-toggle { | |
| 43 | + display: flex; | |
| 44 | + align-items: center; | |
| 45 | + justify-content: center; | |
| 46 | + position: absolute; | |
| 47 | + top: 5px; | |
| 48 | + right: 5px; | |
| 49 | + width: 28px; | |
| 50 | + height: 28px; | |
| 51 | + background-color: rgba(0, 0, 0, 0.4); | |
| 52 | + color: white; | |
| 53 | + border-radius: 6px; | |
| 54 | + z-index: 3; | |
| 55 | + cursor: pointer; | |
| 56 | + backdrop-filter: blur(2px); | |
| 57 | + } | |
| 58 | + | |
| 28 | 59 | #bg-header-controls { |
| 29 | 60 | flex-wrap: wrap; |
| 30 | 61 | row-gap: 10px; |
| @@ -461,10 +492,6 @@ | ||
| 461 | 492 | flex-basis: max(calc(100% / 2 - 10px), 180px); |
| 462 | 493 | } |
| 463 | 494 | |
| 464 | - .BGSampleTitle { | |
| 465 | - display: none; | |
| 466 | - } | |
| 467 | - | |
| 468 | 495 | .tag.excluded:after { |
| 469 | 496 | top: unset; |
| 470 | 497 | bottom: unset; |
| @@ -6257,14 +6257,20 @@ | ||
| 6257 | 6257 | </div> |
| 6258 | 6258 | </div> |
| 6259 | 6259 | <div id="background_template" class="template_element"> |
| 6260 | 6260 | <div class="bg_example flex-container" bgfile="" class="bg_example_img" title=""> |
| 6261 | + <div class="bg_example_img"></div> | |
| 6261 | 6262 | <div title="Copy to system backgrounds" data-i18n="[title]Copy to system backgrounds" class="bg_button bg_example_copy fa-solid fa-file-arrow-up"></div> |
| 6262 | - <div title="Rename background" data-i18n="[title]Rename background" class="bg_button bg_example_edit fa-solid fa-pencil"></div> | |
| 6263 | + <div class="jg-menu"> | |
| 6263 | - <div title="Lock" data-i18n="[title]Lock" class="bg_button bg_example_lock fa-solid fa-lock"></div> | |
| 6264 | + <!-- temporarily moved lock icon here (will be moved to header) --> | |
| 6264 | 6265 | <div title="Unlock" data-i18naction="[title]Unlocklock" class="bg_buttonjg-button bg_example_unlockjg-lock fa-solid fa-lock fa-openfw pointer" data-i18n="[title]Lock" title="Lock"></div> |
| 6265 | 6266 | <div title="Delete background" data-i18naction="[title]Delete backgroundunlock" class="bg_buttonjg-button bg_example_crossjg-unlock fa-solid fa-circlelock-xmarkopen fa-fw pointer" data-i18n="[title]Unlock" title="Unlock"></div> |
| 6266 | - <div class="BGSampleTitle"> | |
| 6267 | + <div data-action="edit" class="jg-button jg-edit fa-solid fa-pen-to-square fa-fw pointer" data-i18n="[title]Rename Background" title="Rename Background"></div> | |
| 6268 | + <div data-action="delete" class="jg-button jg-delete fa-solid fa-trash-can fa-fw pointer" data-i18n="[title]Delete Background" title="Delete Background"></div> | |
| 6267 | 6269 | </div> |
| 6270 | + <div class="mobile-only-menu-toggle"> | |
| 6271 | + <i class="fa-solid fa-ellipsis-vertical"></i> | |
| 6272 | + </div> | |
| 6273 | + <div class="BGSampleTitle"></div> | |
| 6268 | 6274 | </div> |
| 6269 | 6275 | </div> |
| 6270 | 6276 | <!-- templates for JS to reuse when needed --> |
| @@ -265,9 +265,10 @@ async function onCopyToSystemBackgroundClick(e) { | ||
| 265 | 265 | * It caches the thumbnail in local storage and returns a blob URL for the thumbnail. |
| 266 | 266 | * If the thumbnail cannot be fetched, it returns a transparent PNG pixel as a fallback. |
| 267 | 267 | * @param {string} bg Background URL |
| 268 | + * @param {boolean} isCustom Is the background custom? | |
| 268 | 269 | * @returns {Promise<string>} Blob URL of the thumbnail |
| 269 | 270 | */ |
| 270 | 271 | async function getThumbnailFromStorage(bg, isCustom) { |
| 271 | 272 | const cachedBlobUrl = THUMBNAIL_BLOBS.get(bg); |
| 272 | 273 | if (cachedBlobUrl) { |
| 273 | 274 | return cachedBlobUrl; |
| @@ -281,7 +282,8 @@ async function getThumbnailFromStorage(bg) { | ||
| 281 | 282 | } |
| 282 | 283 | |
| 283 | 284 | try { |
| 284 | 285 | const responseurl = awaitisCustom fetch(getBackgroundPath(bg),? {bg cache: 'force-cache' }getBackgroundPath(bg); |
| 286 | + const response = await fetch(url, { cache: 'force-cache' }); | |
| 285 | 287 | if (!response.ok) { |
| 286 | 288 | throw new Error('Fetch failed with status: ' + response.status); |
| 287 | 289 | } |
| @@ -519,7 +521,7 @@ async function resolveImageUrl(bg, isCustom) { | ||
| 519 | 521 | const fileExtension = bg.split('.').pop().toLowerCase(); |
| 520 | 522 | const isAnimated = ['mp4', 'webp'].includes(fileExtension); |
| 521 | 523 | const thumbnailUrl = isAnimated && !background_settings.animation |
| 522 | 524 | ? await getThumbnailFromStorage(bg, isCustom) |
| 523 | 525 | : isCustom |
| 524 | 526 | ? bg |
| 525 | 527 | : getThumbnailUrl('bg', bg); |
| @@ -583,7 +585,7 @@ async function onBackgroundUploadSelected() { | ||
| 583 | 585 | const formData = new FormData(form); |
| 584 | 586 | |
| 585 | 587 | const file = formData.get('avatar'); |
| 586 | 588 | if (!(file instanceof File) || file.size === 0) { |
| 587 | 589 | form.reset(); |
| 588 | 590 | return; |
| 589 | 591 | } |
| @@ -621,7 +623,7 @@ async function convertFileIfVideo(formData) { | ||
| 621 | 623 | const sourceBuffer = await file.arrayBuffer(); |
| 622 | 624 | const convertedBuffer = await globalThis.convertVideoToAnimatedWebp({ buffer: new Uint8Array(sourceBuffer), name: file.name }); |
| 623 | 625 | const convertedFileName = file.name.replace(/\.[^/.]+$/, '.webp'); |
| 624 | 626 | const convertedFile = new File([new Uint8Array(convertedBuffer)], convertedFileName, { type: 'image/webp' }); |
| 625 | 627 | formData.set('avatar', convertedFile); |
| 626 | 628 | toastMessage.remove(); |
| 627 | 629 | } catch (error) { |
| @@ -700,12 +702,40 @@ function onBackgroundFilterInput() { | ||
| 700 | 702 | export function initBackgrounds() { |
| 701 | 703 | eventSource.on(event_types.CHAT_CHANGED, onChatChanged); |
| 702 | 704 | eventSource.on(event_types.FORCE_SET_BACKGROUND, forceSetBackground); |
| 703 | - $(document).on('click', '.bg_example', onSelectBackgroundClick); | |
| 705 | + | |
| 704 | - $(document).on('click', '.bg_example_lock', onLockBackgroundClick); | |
| 706 | + $(document) | |
| 705 | 707 | $ .off(document'click', '.bg_example').on('click', '.bg_example_unlockbg_example', onUnlockBackgroundClickonSelectBackgroundClick); |
| 706 | 708 | $ .off(document'click', '.bg_example_copy').on('click', '.bg_example_editbg_example_copy', onRenameBackgroundClickonCopyToSystemBackgroundClick); |
| 707 | - $(document).on('click', '.bg_example_cross', onDeleteBackgroundClick); | |
| 709 | + .off('click', '.bg_example .mobile-only-menu-toggle').on('click', '.bg_example .mobile-only-menu-toggle', function (e) { | |
| 708 | - $(document).on('click', '.bg_example_copy', onCopyToSystemBackgroundClick); | |
| 710 | + e.stopPropagation(); | |
| 711 | + const $context = $(this).closest('.bg_example'); | |
| 712 | + const wasOpen = $context.hasClass('mobile-menu-open'); | |
| 713 | + // Close all other open menus before opening a new one. | |
| 714 | + $('.bg_example.mobile-menu-open').removeClass('mobile-menu-open'); | |
| 715 | + if (!wasOpen) { | |
| 716 | + $context.addClass('mobile-menu-open'); | |
| 717 | + } | |
| 718 | + }) | |
| 719 | + .off('click', '.jg-button').on('click', '.jg-button', function (e) { | |
| 720 | + e.stopPropagation(); | |
| 721 | + const action = $(this).data('action'); | |
| 722 | + | |
| 723 | + switch (action) { | |
| 724 | + case 'lock': | |
| 725 | + onLockBackgroundClick.call(this, e.originalEvent); | |
| 726 | + break; | |
| 727 | + case 'unlock': | |
| 728 | + onUnlockBackgroundClick.call(this, e.originalEvent); | |
| 729 | + break; | |
| 730 | + case 'edit': | |
| 731 | + onRenameBackgroundClick.call(this, e.originalEvent); | |
| 732 | + break; | |
| 733 | + case 'delete': | |
| 734 | + onDeleteBackgroundClick.call(this, e.originalEvent); | |
| 735 | + break; | |
| 736 | + } | |
| 737 | + }); | |
| 738 | + | |
| 709 | 739 | $('#auto_background').on('click', autoBackgroundCommand); |
| 710 | 740 | $('#add_bg_button').on('change', onBackgroundUploadSelected); |
| 711 | 741 | $('#bg-filter').on('input', onBackgroundFilterInput); |
| @@ -722,71 +722,11 @@ hr { | ||
| 722 | 722 | opacity: 0.2; |
| 723 | 723 | } |
| 724 | 724 | |
| 725 | -#bg1, | |
| 726 | -#bg_custom { | |
| 727 | - background-repeat: no-repeat; | |
| 728 | - background-attachment: fixed; | |
| 729 | - background-size: cover; | |
| 730 | - position: absolute; | |
| 731 | - width: 100%; | |
| 732 | - height: 100%; | |
| 733 | - transition: background-image var(--animation-duration-3x) ease-in-out; | |
| 734 | -} | |
| 735 | - | |
| 736 | -/* Background fitting options */ | |
| 737 | -#background_fitting { | |
| 738 | - max-width: 6em; | |
| 739 | -} | |
| 740 | - | |
| 741 | -/* Fill/Cover - scales to fill width while maintaining aspect ratio */ | |
| 742 | -#bg1.cover, | |
| 743 | -#bg_custom.cover { | |
| 744 | - background-size: cover; | |
| 745 | - background-position: center; | |
| 746 | -} | |
| 747 | - | |
| 748 | -/* Fit/Contain - shows entire image maintaining aspect ratio */ | |
| 749 | -#bg1.contain, | |
| 750 | -#bg_custom.contain { | |
| 751 | - background-size: contain; | |
| 752 | - background-position: center; | |
| 753 | - background-repeat: no-repeat; | |
| 754 | -} | |
| 755 | - | |
| 756 | -/* Stretch - stretches to fill entire space */ | |
| 757 | -#bg1.stretch, | |
| 758 | -#bg_custom.stretch { | |
| 759 | - background-size: 100% 100%; | |
| 760 | -} | |
| 761 | - | |
| 762 | -/* Center - centers without scaling */ | |
| 763 | -#bg1.center, | |
| 764 | -#bg_custom.center { | |
| 765 | - background-size: auto; | |
| 766 | - background-position: center; | |
| 767 | - background-repeat: no-repeat; | |
| 768 | -} | |
| 769 | - | |
| 770 | -body.reduced-motion #bg1, | |
| 771 | -body.reduced-motion #bg_custom { | |
| 772 | - transition: none; | |
| 773 | -} | |
| 774 | - | |
| 775 | 725 | #version_display { |
| 776 | 726 | padding: 5px; |
| 777 | 727 | opacity: 0.8; |
| 778 | 728 | } |
| 779 | 729 | |
| 780 | -#bg1 { | |
| 781 | - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='); | |
| 782 | - z-index: -3; | |
| 783 | -} | |
| 784 | - | |
| 785 | -#bg_custom { | |
| 786 | - background-image: none; | |
| 787 | - z-index: -2; | |
| 788 | -} | |
| 789 | - | |
| 790 | 730 | /*TOPPER margin*/ |
| 791 | 731 | |
| 792 | 732 | #top-bar { |
| @@ -3305,22 +3245,6 @@ input[type=search]:focus::-webkit-search-cancel-button { | ||
| 3305 | 3245 | justify-content: space-evenly; |
| 3306 | 3246 | } |
| 3307 | 3247 | |
| 3308 | -.bg_example { | |
| 3309 | - width: 30%; | |
| 3310 | - max-width: 200px; | |
| 3311 | - background-repeat: no-repeat; | |
| 3312 | - background-size: cover; | |
| 3313 | - background-position: center; | |
| 3314 | - border-radius: 10px; | |
| 3315 | - border: 1px solid var(--SmartThemeBorderColor); | |
| 3316 | - box-shadow: 0 0 7px var(--black50a); | |
| 3317 | - margin: 5px; | |
| 3318 | - cursor: pointer; | |
| 3319 | - aspect-ratio: 16/9; | |
| 3320 | - justify-content: flex-end; | |
| 3321 | - position: relative; | |
| 3322 | -} | |
| 3323 | - | |
| 3324 | 3248 | .bg_example.locked { |
| 3325 | 3249 | outline: 2px solid var(--golden); |
| 3326 | 3250 | } |
| @@ -3345,27 +3269,6 @@ input[type=search]:focus::-webkit-search-cancel-button { | ||
| 3345 | 3269 | display: none; |
| 3346 | 3270 | } |
| 3347 | 3271 | |
| 3348 | -.BGSampleTitle { | |
| 3349 | - display: flex; | |
| 3350 | - width: 100%; | |
| 3351 | - height: min-content; | |
| 3352 | - text-align: center; | |
| 3353 | - justify-content: center; | |
| 3354 | - align-self: flex-end; | |
| 3355 | - bottom: 0; | |
| 3356 | - position: relative; | |
| 3357 | - word-break: break-word; | |
| 3358 | - background-color: var(--SmartThemeBlurTintColor); | |
| 3359 | - font-size: calc(var(--fontScale) * 0.9em); | |
| 3360 | - max-height: 50%; | |
| 3361 | - overflow-y: clip; | |
| 3362 | - border-radius: 0 0 7px 7px; | |
| 3363 | -} | |
| 3364 | - | |
| 3365 | -.bg_example[custom="true"] .BGSampleTitle { | |
| 3366 | - display: none; | |
| 3367 | -} | |
| 3368 | - | |
| 3369 | 3272 | .bg_button { |
| 3370 | 3273 | padding: 4px; |
| 3371 | 3274 | position: absolute; |