fix hifi instance reorder and enable/disable

This commit is contained in:
elmerohueso 2026-04-28 17:44:08 -06:00
parent 7c2edeb16a
commit 788b7011d0
4 changed files with 197 additions and 7 deletions

View file

@ -11656,6 +11656,18 @@ class MusicDatabase:
logger.error(f"Error removing HiFi instance: {e}")
return False
def toggle_hifi_instance(self, url: str, enabled: bool) -> bool:
"""Enable or disable a HiFi instance."""
try:
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute("UPDATE hifi_instances SET enabled = ? WHERE url = ?", (1 if enabled else 0, url))
conn.commit()
return cursor.rowcount > 0
except Exception as e:
logger.error(f"Error toggling HiFi instance: {e}")
return False
def reorder_hifi_instances(self, urls: List[str]) -> bool:
"""Update priorities based on the given URL order."""
try:

View file

@ -24750,6 +24750,27 @@ def hifi_remove_instance(url):
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/hifi/instances/toggle', methods=['POST'])
@admin_only
def hifi_toggle_instance():
"""Toggle enabled state of a HiFi API instance."""
try:
data = request.get_json() or {}
url = data.get('url', '').strip().rstrip('/')
enabled = data.get('enabled', True)
if not url:
return jsonify({'success': False, 'error': 'URL is required'}), 400
from database.music_database import get_database
db = get_database()
db.toggle_hifi_instance(url, enabled)
if soulseek_client and hasattr(soulseek_client, 'hifi') and soulseek_client.hifi:
soulseek_client.hifi.reload_instances()
return jsonify({'success': True})
except Exception as e:
logger.error(f"Error toggling HiFi instance: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/hifi/instances/reorder', methods=['POST'])
@admin_only
def hifi_reorder_instances():

View file

@ -3289,22 +3289,102 @@ async function loadHiFiInstances() {
return;
}
listEl.innerHTML = data.instances.map((inst, i) => {
const enabledClass = inst.enabled ? '' : 'opacity:0.4;';
const enabledClass = inst.enabled ? '' : 'hifi-instance-disabled';
const checkHtml = inst.enabled
? `<span style="color:#4caf50;cursor:pointer;" onclick="toggleHiFiInstance('${escapeHtml(inst.url)}')" title="Click to disable">&#x2714;</span>`
: `<span style="color:#666;cursor:pointer;" onclick="toggleHiFiInstance('${escapeHtml(inst.url)}')" title="Click to enable">&#x2718;</span>`;
return `<div style="display:flex;align-items:center;gap:8px;padding:4px 0;font-size:0.82em;${enabledClass}">
<span style="color:rgba(255,255,255,0.4);cursor:default;user-select:none;">&#x2630;</span>
<span style="flex:1;color:rgba(255,255,255,0.7);font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escapeHtml(inst.url)}</span>
? `<span class="hifi-instance-toggle on" onclick="toggleHiFiInstance('${escapeHtml(inst.url)}')" title="Click to disable">&#x2714;</span>`
: `<span class="hifi-instance-toggle off" onclick="toggleHiFiInstance('${escapeHtml(inst.url)}')" title="Click to enable">&#x2718;</span>`;
return `<div class="hifi-instance-item${inst.enabled ? '' : ' disabled'}" draggable="true" data-url="${escapeHtml(inst.url)}">
<span class="hifi-instance-grip">&#x2630;</span>
<span class="hifi-instance-url">${escapeHtml(inst.url)}</span>
${checkHtml}
<span style="color:#f44336;cursor:pointer;font-size:0.9em;" onclick="removeHiFiInstance('${escapeHtml(inst.url)}')" title="Remove instance">&#x2716;</span>
<span class="hifi-instance-remove" onclick="removeHiFiInstance('${escapeHtml(inst.url)}')" title="Remove instance">&#x2716;</span>
</div>`;
}).join('');
_initHiFiDragDrop();
} catch (e) {
listEl.innerHTML = `<div style="color:#f44336;font-size:0.85em;">Error loading instances: ${escapeHtml(e.message)}</div>`;
}
}
function _initHiFiDragDrop() {
const listEl = document.getElementById('hifi-instances-list');
if (!listEl) return;
let dragIdx = null;
listEl.querySelectorAll('.hifi-instance-item').forEach((item, idx) => {
item.addEventListener('dragstart', (e) => {
dragIdx = idx;
item.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
});
item.addEventListener('dragend', () => {
item.classList.remove('dragging');
dragIdx = null;
listEl.querySelectorAll('.hifi-instance-item').forEach(i => i.classList.remove('drag-over'));
});
item.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
item.classList.add('drag-over');
});
item.addEventListener('dragleave', () => {
item.classList.remove('drag-over');
});
item.addEventListener('drop', async (e) => {
e.preventDefault();
item.classList.remove('drag-over');
if (dragIdx === null) return;
const items = [...listEl.querySelectorAll('.hifi-instance-item')];
const dragged = items[dragIdx];
if (dragIdx !== idx) {
if (dragIdx < idx) {
item.after(dragged);
} else {
item.before(dragged);
}
const urls = [...listEl.querySelectorAll('.hifi-instance-item')].map(el => el.dataset.url);
await _saveHiFiInstanceOrder(urls);
}
});
});
}
async function _saveHiFiInstanceOrder(urls) {
try {
await fetch('/api/hifi/instances/reorder', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ urls })
});
} catch (e) {
console.error('Failed to save HiFi instance order:', e);
}
}
async function toggleHiFiInstance(url) {
const listEl = document.getElementById('hifi-instances-list');
if (!listEl) return;
const item = listEl.querySelector(`.hifi-instance-item[data-url="${url}"]`);
const toggle = item?.querySelector('.hifi-instance-toggle');
const currentlyEnabled = toggle?.classList.contains('on');
const newEnabled = !currentlyEnabled;
try {
const resp = await fetch('/api/hifi/instances/toggle', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url, enabled: newEnabled })
});
const data = await resp.json();
if (data.success) {
loadHiFiInstances();
} else {
alert(data.error || 'Failed to toggle instance');
}
} catch (e) {
alert(`Error: ${e.message}`);
}
}
async function addHiFiInstance() {
const input = document.getElementById('hifi-new-instance');
if (!input) return;

View file

@ -60262,3 +60262,80 @@ body[data-artist-source="source"] #artist-detail-page #library-artist-enhance-bt
#artist-detail-page .release-card.album-card .mb-card-icon:hover {
opacity: 1;
}
/* ── HiFi API instances list (drag and drop) ── */
#hifi-instances-list {
display: flex;
flex-direction: column;
gap: 4px;
}
.hifi-instance-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 8px;
font-size: 0.82em;
user-select: none;
transition: all 0.2s;
cursor: grab;
}
.hifi-instance-item:hover {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.1);
}
.hifi-instance-item.dragging {
opacity: 0.4;
border-color: rgb(var(--accent-rgb));
}
.hifi-instance-item.drag-over {
border-color: rgb(var(--accent-rgb));
background: rgba(var(--accent-rgb), 0.08);
}
.hifi-instance-item.disabled {
opacity: 0.4;
}
.hifi-instance-grip {
color: rgba(255, 255, 255, 0.3);
cursor: grab;
flex-shrink: 0;
font-size: 1em;
}
.hifi-instance-url {
flex: 1;
color: rgba(255, 255, 255, 0.7);
font-family: monospace;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hifi-instance-toggle {
flex-shrink: 0;
cursor: pointer;
font-size: 0.9em;
transition: color 0.15s;
}
.hifi-instance-toggle.on {
color: #4caf50;
}
.hifi-instance-toggle.on:hover {
color: #66bb6a;
}
.hifi-instance-toggle.off {
color: #666;
}
.hifi-instance-toggle.off:hover {
color: #999;
}
.hifi-instance-remove {
flex-shrink: 0;
color: #f44336;
cursor: pointer;
font-size: 0.9em;
transition: color 0.15s;
}
.hifi-instance-remove:hover {
color: #ef5350;
}