Merge pull request #168 from blacksigkill/feature/reorder-sidebar
Feature : reorder/reorganize sidebar
This commit is contained in:
commit
f3810cb888
4 changed files with 268 additions and 15 deletions
|
|
@ -212,7 +212,7 @@ class AudioContextManager {
|
||||||
if (this.monoMergerNode) {
|
if (this.monoMergerNode) {
|
||||||
try {
|
try {
|
||||||
this.monoMergerNode.disconnect();
|
this.monoMergerNode.disconnect();
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Ignore if not connected
|
// Ignore if not connected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +220,7 @@ class AudioContextManager {
|
||||||
// Only disconnect destination from analyser to preserve other taps (like Butterchurn)
|
// Only disconnect destination from analyser to preserve other taps (like Butterchurn)
|
||||||
try {
|
try {
|
||||||
this.analyser.disconnect(this.audioContext.destination);
|
this.analyser.disconnect(this.audioContext.destination);
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Ignore if not connected
|
// Ignore if not connected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
104
js/settings.js
104
js/settings.js
|
|
@ -1362,11 +1362,9 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
|
|
||||||
const sidebarShowSettingsToggle = document.getElementById('sidebar-show-settings-toggle');
|
const sidebarShowSettingsToggle = document.getElementById('sidebar-show-settings-toggle');
|
||||||
if (sidebarShowSettingsToggle) {
|
if (sidebarShowSettingsToggle) {
|
||||||
sidebarShowSettingsToggle.checked = sidebarSectionSettings.shouldShowSettings();
|
sidebarShowSettingsToggle.checked = true;
|
||||||
sidebarShowSettingsToggle.addEventListener('change', (e) => {
|
sidebarShowSettingsToggle.disabled = true;
|
||||||
sidebarSectionSettings.setShowSettings(e.target.checked);
|
sidebarSectionSettings.setShowSettings(true);
|
||||||
sidebarSectionSettings.applySidebarVisibility();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sidebarShowAccountToggle = document.getElementById('sidebar-show-account-toggle');
|
const sidebarShowAccountToggle = document.getElementById('sidebar-show-account-toggle');
|
||||||
|
|
@ -1408,6 +1406,102 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
// Apply sidebar visibility on initialization
|
// Apply sidebar visibility on initialization
|
||||||
sidebarSectionSettings.applySidebarVisibility();
|
sidebarSectionSettings.applySidebarVisibility();
|
||||||
|
|
||||||
|
const sidebarSettingsGroup = sidebarShowHomeToggle?.closest('.settings-group');
|
||||||
|
if (sidebarSettingsGroup) {
|
||||||
|
const toggleIdFromSidebarId = (sidebarId) =>
|
||||||
|
sidebarId ? sidebarId.replace('sidebar-nav-', 'sidebar-show-') + '-toggle' : '';
|
||||||
|
|
||||||
|
const sidebarOrderConfig = sidebarSectionSettings.DEFAULT_ORDER.map((sidebarId) => ({
|
||||||
|
sidebarId,
|
||||||
|
toggleId: toggleIdFromSidebarId(sidebarId),
|
||||||
|
}));
|
||||||
|
|
||||||
|
sidebarOrderConfig.forEach(({ toggleId, sidebarId }) => {
|
||||||
|
const toggle = document.getElementById(toggleId);
|
||||||
|
const item = toggle?.closest('.setting-item');
|
||||||
|
if (!item) return;
|
||||||
|
item.dataset.sidebarId = sidebarId;
|
||||||
|
item.classList.add('sidebar-setting-item');
|
||||||
|
item.draggable = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getSidebarItems = () =>
|
||||||
|
Array.from(sidebarSettingsGroup.querySelectorAll('.sidebar-setting-item[data-sidebar-id]'));
|
||||||
|
|
||||||
|
const applySidebarSettingsOrder = () => {
|
||||||
|
const order = sidebarSectionSettings.getOrder();
|
||||||
|
const itemMap = new Map(getSidebarItems().map((item) => [item.dataset.sidebarId, item]));
|
||||||
|
|
||||||
|
order.forEach((id) => {
|
||||||
|
const item = itemMap.get(id);
|
||||||
|
if (item) {
|
||||||
|
sidebarSettingsGroup.appendChild(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
applySidebarSettingsOrder();
|
||||||
|
|
||||||
|
let draggedItem = null;
|
||||||
|
|
||||||
|
const saveSidebarOrder = () => {
|
||||||
|
const order = getSidebarItems().map((item) => item.dataset.sidebarId);
|
||||||
|
sidebarSectionSettings.setOrder(order);
|
||||||
|
sidebarSectionSettings.applySidebarVisibility();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragStart = (e) => {
|
||||||
|
const item = e.target.closest('.sidebar-setting-item');
|
||||||
|
if (!item) return;
|
||||||
|
draggedItem = item;
|
||||||
|
draggedItem.classList.add('dragging');
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
|
e.dataTransfer.setData('text/plain', item.dataset.sidebarId || '');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragEnd = () => {
|
||||||
|
if (!draggedItem) return;
|
||||||
|
draggedItem.classList.remove('dragging');
|
||||||
|
draggedItem = null;
|
||||||
|
saveSidebarOrder();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDragAfterElement = (container, y) => {
|
||||||
|
const draggableElements = [...container.querySelectorAll('.sidebar-setting-item:not(.dragging)')];
|
||||||
|
|
||||||
|
return draggableElements.reduce(
|
||||||
|
(closest, child) => {
|
||||||
|
const box = child.getBoundingClientRect();
|
||||||
|
const offset = y - box.top - box.height / 2;
|
||||||
|
if (offset < 0 && offset > closest.offset) {
|
||||||
|
return { offset, element: child };
|
||||||
|
}
|
||||||
|
return closest;
|
||||||
|
},
|
||||||
|
{ offset: Number.NEGATIVE_INFINITY }
|
||||||
|
).element;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!draggedItem) return;
|
||||||
|
const afterElement = getDragAfterElement(sidebarSettingsGroup, e.clientY);
|
||||||
|
if (afterElement === draggedItem) return;
|
||||||
|
if (afterElement) {
|
||||||
|
sidebarSettingsGroup.insertBefore(draggedItem, afterElement);
|
||||||
|
} else {
|
||||||
|
sidebarSettingsGroup.appendChild(draggedItem);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sidebarSettingsGroup.addEventListener('dragstart', handleDragStart);
|
||||||
|
sidebarSettingsGroup.addEventListener('dragend', handleDragEnd);
|
||||||
|
sidebarSettingsGroup.addEventListener('dragover', handleDragOver);
|
||||||
|
sidebarSettingsGroup.addEventListener('drop', (e) => e.preventDefault());
|
||||||
|
}
|
||||||
|
|
||||||
// Filename template setting
|
// Filename template setting
|
||||||
const filenameTemplate = document.getElementById('filename-template');
|
const filenameTemplate = document.getElementById('filename-template');
|
||||||
if (filenameTemplate) {
|
if (filenameTemplate) {
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ export const apiSettings = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async getInstances(type = 'api', sortBySpeed = false) {
|
async getInstances(type = 'api', _sortBySpeed = false) {
|
||||||
let instancesObj;
|
let instancesObj;
|
||||||
|
|
||||||
const stored = localStorage.getItem(this.STORAGE_KEY);
|
const stored = localStorage.getItem(this.STORAGE_KEY);
|
||||||
|
|
@ -1215,6 +1215,19 @@ export const sidebarSectionSettings = {
|
||||||
SHOW_ABOUT_KEY: 'sidebar-show-about',
|
SHOW_ABOUT_KEY: 'sidebar-show-about',
|
||||||
SHOW_DOWNLOAD_KEY: 'sidebar-show-download',
|
SHOW_DOWNLOAD_KEY: 'sidebar-show-download',
|
||||||
SHOW_DISCORD_KEY: 'sidebar-show-discord',
|
SHOW_DISCORD_KEY: 'sidebar-show-discord',
|
||||||
|
ORDER_KEY: 'sidebar-menu-order',
|
||||||
|
DEFAULT_ORDER: [
|
||||||
|
'sidebar-nav-home',
|
||||||
|
'sidebar-nav-library',
|
||||||
|
'sidebar-nav-recent',
|
||||||
|
'sidebar-nav-unreleased',
|
||||||
|
'sidebar-nav-donate',
|
||||||
|
'sidebar-nav-settings',
|
||||||
|
'sidebar-nav-account',
|
||||||
|
'sidebar-nav-about',
|
||||||
|
'sidebar-nav-download',
|
||||||
|
'sidebar-nav-discord',
|
||||||
|
],
|
||||||
|
|
||||||
shouldShowHome() {
|
shouldShowHome() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -1282,16 +1295,15 @@ export const sidebarSectionSettings = {
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldShowSettings() {
|
shouldShowSettings() {
|
||||||
try {
|
|
||||||
const val = localStorage.getItem(this.SHOW_SETTINGS_KEY);
|
|
||||||
return val === null ? true : val === 'true';
|
|
||||||
} catch {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setShowSettings(enabled) {
|
setShowSettings(enabled) {
|
||||||
localStorage.setItem(this.SHOW_SETTINGS_KEY, enabled ? 'true' : 'false');
|
if (enabled) {
|
||||||
|
localStorage.setItem(this.SHOW_SETTINGS_KEY, 'true');
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem(this.SHOW_SETTINGS_KEY);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldShowAccount() {
|
shouldShowAccount() {
|
||||||
|
|
@ -1346,7 +1358,69 @@ export const sidebarSectionSettings = {
|
||||||
localStorage.setItem(this.SHOW_DISCORD_KEY, enabled ? 'true' : 'false');
|
localStorage.setItem(this.SHOW_DISCORD_KEY, enabled ? 'true' : 'false');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
normalizeOrder(order) {
|
||||||
|
const baseOrder = this.DEFAULT_ORDER;
|
||||||
|
const safeOrder = Array.isArray(order) ? order.filter((id) => baseOrder.includes(id)) : [];
|
||||||
|
const uniqueOrder = [...new Set(safeOrder)];
|
||||||
|
const missing = baseOrder.filter((id) => !uniqueOrder.includes(id));
|
||||||
|
return [...uniqueOrder, ...missing];
|
||||||
|
},
|
||||||
|
|
||||||
|
getOrder() {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(this.ORDER_KEY);
|
||||||
|
if (stored) {
|
||||||
|
return this.normalizeOrder(JSON.parse(stored));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return this.normalizeOrder([]);
|
||||||
|
},
|
||||||
|
|
||||||
|
setOrder(order) {
|
||||||
|
const normalized = this.normalizeOrder(order);
|
||||||
|
localStorage.setItem(this.ORDER_KEY, JSON.stringify(normalized));
|
||||||
|
},
|
||||||
|
|
||||||
|
applySidebarOrder() {
|
||||||
|
const lists = document.querySelectorAll('.sidebar-nav ul');
|
||||||
|
const primaryList = lists[0];
|
||||||
|
if (!primaryList) return;
|
||||||
|
const secondaryList = lists[1];
|
||||||
|
|
||||||
|
const order = this.getOrder();
|
||||||
|
const secondaryCount = secondaryList ? secondaryList.children.length : 0;
|
||||||
|
const splitIndex = secondaryCount ? Math.max(0, order.length - secondaryCount) : order.length;
|
||||||
|
const primaryOrder = order.slice(0, splitIndex);
|
||||||
|
const secondaryOrder = order.slice(splitIndex);
|
||||||
|
|
||||||
|
primaryOrder.forEach((id) => {
|
||||||
|
const item = document.getElementById(id);
|
||||||
|
if (item) {
|
||||||
|
primaryList.appendChild(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (secondaryList) {
|
||||||
|
secondaryOrder.forEach((id) => {
|
||||||
|
const item = document.getElementById(id);
|
||||||
|
if (item) {
|
||||||
|
secondaryList.appendChild(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
secondaryOrder.forEach((id) => {
|
||||||
|
const item = document.getElementById(id);
|
||||||
|
if (item) {
|
||||||
|
primaryList.appendChild(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
applySidebarVisibility() {
|
applySidebarVisibility() {
|
||||||
|
this.applySidebarOrder();
|
||||||
const items = [
|
const items = [
|
||||||
{ id: 'sidebar-nav-home', check: this.shouldShowHome() },
|
{ id: 'sidebar-nav-home', check: this.shouldShowHome() },
|
||||||
{ id: 'sidebar-nav-library', check: this.shouldShowLibrary() },
|
{ id: 'sidebar-nav-library', check: this.shouldShowLibrary() },
|
||||||
|
|
|
||||||
85
styles.css
85
styles.css
|
|
@ -1742,6 +1742,40 @@ input[type='search']::-webkit-search-cancel-button {
|
||||||
gap: var(--spacing-lg);
|
gap: var(--spacing-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.setting-item.sidebar-setting-item {
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item.sidebar-setting-item::before {
|
||||||
|
content: '';
|
||||||
|
width: 14px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: inline-block;
|
||||||
|
background-image: radial-gradient(currentcolor 1.2px, transparent 1.3px);
|
||||||
|
background-size: 6px 6px;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: repeat;
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item.sidebar-setting-item .info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item.sidebar-setting-item .toggle-switch {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item.sidebar-setting-item.dragging {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
.setting-item .info {
|
.setting-item .info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -1981,6 +2015,28 @@ input:checked + .slider::before {
|
||||||
transform: translateX(16px) scale(1.1);
|
transform: translateX(16px) scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle-switch input:disabled + .slider {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input:disabled + .slider::before {
|
||||||
|
box-shadow: none;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input:disabled:checked + .slider::before {
|
||||||
|
transform: translateX(16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch:hover input:disabled + .slider::before {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch:hover input:disabled:checked + .slider::before {
|
||||||
|
transform: translateX(16px);
|
||||||
|
}
|
||||||
|
|
||||||
.track-info {
|
.track-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -4540,6 +4596,35 @@ img[src=''] {
|
||||||
gap: var(--spacing-md);
|
gap: var(--spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.setting-item:has(.toggle-switch) {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item:has(.toggle-switch) .info {
|
||||||
|
width: auto;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item:has(.toggle-switch) .label {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item:has(.toggle-switch) .description {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item:has(.toggle-switch) .toggle-switch {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.setting-item .info {
|
.setting-item .info {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue