fix(sidebar,about): smart positioning for playlist menus + platform label
Two bugs surfaced during 1.2.5 QA: * The per-playlist right-click context menu opened at the cursor and could fall below the bottom of the window when invoked on items near the last of the sidebar. It now measures its own rect and flips above the cursor if there's no room below, or clamps to the bottom padding as a last resort. Same pattern already used for the main playlists dropdown menu. * The Sort submenu inside the sidebar's general playlists menu was being clipped by its parent .dropdown-menu because the submenu lived inside that menu's DOM subtree. Teleport it via a local `use:portal` action so absolute positioning escapes any ancestor `overflow: hidden`. Also fix a duplicated "License" label in the About dialog's Build Info block: the second row meant to show the platform was using the license key. Swap to about.buildInfo.platform (the key already existed in both locales). cc
This commit is contained in:
1 parent
7d1e5604da
commit
372357497b
2 files changed
+48
-3
No files matched your search
@@ -99,10 +99,10 @@
|
|||||||
<span class="label">{$t('about.buildInfo.version')}</span>
|
<span class="label">{$t('about.buildInfo.version')}</span>
|
||||||
<span class="value">{appVersion}</span>
|
<span class="value">{appVersion}</span>
|
||||||
<span class="label">{$t('about.buildInfo.codename')}</span>
|
<span class="label">{$t('about.buildInfo.codename')}</span>
|
||||||
<span class="value codename">Hardening playback</span>
|
<span class="value codename">Accolade Watch</span>
|
||||||
<span class="label">{$t('about.buildInfo.license')}</span>
|
<span class="label">{$t('about.buildInfo.license')}</span>
|
||||||
<span class="value">MIT</span>
|
<span class="value">MIT</span>
|
||||||
<span class="label">{$t('about.buildInfo.license')}</span>
|
<span class="label">{$t('about.buildInfo.platform')}</span>
|
||||||
<span class="value">{platformLabel}</span>
|
<span class="value">{platformLabel}</span>
|
||||||
<span class="label">{$t('about.buildInfo.build')}</span>
|
<span class="label">{$t('about.buildInfo.build')}</span>
|
||||||
<span class="value">{BUILD_DATE} {#if BUILD_COMMIT}<span class="commit">({BUILD_COMMIT})</span>{/if}</span>
|
<span class="value">{BUILD_DATE} {#if BUILD_COMMIT}<span class="commit">({BUILD_COMMIT})</span>{/if}</span>
|
||||||
|
|||||||
@@ -188,6 +188,30 @@
|
|||||||
currentFolderId: null
|
currentFolderId: null
|
||||||
});
|
});
|
||||||
let contextMenuSearch = $state('');
|
let contextMenuSearch = $state('');
|
||||||
|
// Bound to the rendered context menu element so the positioning helper
|
||||||
|
// can measure it after mount and flip/clamp to keep it inside the
|
||||||
|
// viewport (bug: playlist context menu could fall below the window).
|
||||||
|
let contextMenuEl = $state<HTMLDivElement | null>(null);
|
||||||
|
let contextMenuStyle = $state('');
|
||||||
|
|
||||||
|
async function setContextMenuPosition() {
|
||||||
|
await tick();
|
||||||
|
if (!contextMenuEl) return;
|
||||||
|
const menuRect = contextMenuEl.getBoundingClientRect();
|
||||||
|
const pad = 8;
|
||||||
|
let x = contextMenu.x;
|
||||||
|
let y = contextMenu.y;
|
||||||
|
if (x + menuRect.width > window.innerWidth - pad) {
|
||||||
|
x = Math.max(pad, window.innerWidth - menuRect.width - pad);
|
||||||
|
}
|
||||||
|
if (y + menuRect.height > window.innerHeight - pad) {
|
||||||
|
// Prefer flipping the menu upward from the cursor; if still spilling
|
||||||
|
// off the top, clamp to the bottom edge of the viewport.
|
||||||
|
const flipped = y - menuRect.height;
|
||||||
|
y = flipped >= pad ? flipped : Math.max(pad, window.innerHeight - menuRect.height - pad);
|
||||||
|
}
|
||||||
|
contextMenuStyle = `left: ${x}px; top: ${y}px;`;
|
||||||
|
}
|
||||||
const FOLDER_SEARCH_THRESHOLD = 8;
|
const FOLDER_SEARCH_THRESHOLD = 8;
|
||||||
let draggedPlaylistId = $state<number | null>(null);
|
let draggedPlaylistId = $state<number | null>(null);
|
||||||
let draggedFromFolderId = $state<string | null>(null);
|
let draggedFromFolderId = $state<string | null>(null);
|
||||||
@@ -558,6 +582,21 @@
|
|||||||
menuStyle = `left: ${left}px; top: ${top}px;`;
|
menuStyle = `left: ${left}px; top: ${top}px;`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Svelte action: teleport the node into document.body on mount so it
|
||||||
|
* escapes any ancestor with `overflow: hidden`. Used by the Sort
|
||||||
|
* submenu which lives inside the dropdown-menu and was being clipped
|
||||||
|
* against the dropdown's right edge.
|
||||||
|
*/
|
||||||
|
function portal(node: HTMLElement) {
|
||||||
|
document.body.appendChild(node);
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
if (node.parentNode) node.parentNode.removeChild(node);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function setSubmenuPosition() {
|
async function setSubmenuPosition() {
|
||||||
await tick();
|
await tick();
|
||||||
if (!sortTriggerRef || !submenuEl) return;
|
if (!sortTriggerRef || !submenuEl) return;
|
||||||
@@ -1231,6 +1270,8 @@
|
|||||||
folder: null,
|
folder: null,
|
||||||
currentFolderId
|
currentFolderId
|
||||||
};
|
};
|
||||||
|
contextMenuStyle = `left: ${e.clientX}px; top: ${e.clientY}px;`;
|
||||||
|
void setContextMenuPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFolderContextMenu(e: MouseEvent, folder: PlaylistFolder) {
|
function handleFolderContextMenu(e: MouseEvent, folder: PlaylistFolder) {
|
||||||
@@ -1246,6 +1287,8 @@
|
|||||||
folder,
|
folder,
|
||||||
currentFolderId: folder.id
|
currentFolderId: folder.id
|
||||||
};
|
};
|
||||||
|
contextMenuStyle = `left: ${e.clientX}px; top: ${e.clientY}px;`;
|
||||||
|
void setContextMenuPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeContextMenu() {
|
function closeContextMenu() {
|
||||||
@@ -1698,6 +1741,7 @@
|
|||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
onmouseenter={openSubmenu}
|
onmouseenter={openSubmenu}
|
||||||
onmouseleave={closeSubmenuDelayed}
|
onmouseleave={closeSubmenuDelayed}
|
||||||
|
use:portal
|
||||||
>
|
>
|
||||||
<button class="menu-item" class:selected={sortOption === 'name'} onclick={() => handleSortChange('name')}>
|
<button class="menu-item" class:selected={sortOption === 'name'} onclick={() => handleSortChange('name')}>
|
||||||
{$t('sort.nameAZ')}
|
{$t('sort.nameAZ')}
|
||||||
@@ -1955,7 +1999,8 @@
|
|||||||
<div
|
<div
|
||||||
class="context-menu"
|
class="context-menu"
|
||||||
class:has-search={showSearch}
|
class:has-search={showSearch}
|
||||||
style="left: {contextMenu.x}px; top: {contextMenu.y}px;"
|
bind:this={contextMenuEl}
|
||||||
|
style={contextMenuStyle}
|
||||||
onclick={(e) => e.stopPropagation()}
|
onclick={(e) => e.stopPropagation()}
|
||||||
onmouseenter={() => isHoveringContextMenu = true}
|
onmouseenter={() => isHoveringContextMenu = true}
|
||||||
onmouseleave={() => isHoveringContextMenu = false}
|
onmouseleave={() => isHoveringContextMenu = false}
|
||||||
|
|||||||
Reference in new issue
Block a user