fix hifi instance reorder and enable/disable
This commit is contained in:
parent
7c2edeb16a
commit
788b7011d0
4 changed files with 197 additions and 7 deletions
|
|
@ -11656,6 +11656,18 @@ class MusicDatabase:
|
||||||
logger.error(f"Error removing HiFi instance: {e}")
|
logger.error(f"Error removing HiFi instance: {e}")
|
||||||
return False
|
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:
|
def reorder_hifi_instances(self, urls: List[str]) -> bool:
|
||||||
"""Update priorities based on the given URL order."""
|
"""Update priorities based on the given URL order."""
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -24750,6 +24750,27 @@ def hifi_remove_instance(url):
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
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'])
|
@app.route('/api/hifi/instances/reorder', methods=['POST'])
|
||||||
@admin_only
|
@admin_only
|
||||||
def hifi_reorder_instances():
|
def hifi_reorder_instances():
|
||||||
|
|
|
||||||
|
|
@ -3289,22 +3289,102 @@ async function loadHiFiInstances() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
listEl.innerHTML = data.instances.map((inst, i) => {
|
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
|
const checkHtml = inst.enabled
|
||||||
? `<span style="color:#4caf50;cursor:pointer;" onclick="toggleHiFiInstance('${escapeHtml(inst.url)}')" title="Click to disable">✔</span>`
|
? `<span class="hifi-instance-toggle on" onclick="toggleHiFiInstance('${escapeHtml(inst.url)}')" title="Click to disable">✔</span>`
|
||||||
: `<span style="color:#666;cursor:pointer;" onclick="toggleHiFiInstance('${escapeHtml(inst.url)}')" title="Click to enable">✘</span>`;
|
: `<span class="hifi-instance-toggle off" onclick="toggleHiFiInstance('${escapeHtml(inst.url)}')" title="Click to enable">✘</span>`;
|
||||||
return `<div style="display:flex;align-items:center;gap:8px;padding:4px 0;font-size:0.82em;${enabledClass}">
|
return `<div class="hifi-instance-item${inst.enabled ? '' : ' disabled'}" draggable="true" data-url="${escapeHtml(inst.url)}">
|
||||||
<span style="color:rgba(255,255,255,0.4);cursor:default;user-select:none;">☰</span>
|
<span class="hifi-instance-grip">☰</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-url">${escapeHtml(inst.url)}</span>
|
||||||
${checkHtml}
|
${checkHtml}
|
||||||
<span style="color:#f44336;cursor:pointer;font-size:0.9em;" onclick="removeHiFiInstance('${escapeHtml(inst.url)}')" title="Remove instance">✖</span>
|
<span class="hifi-instance-remove" onclick="removeHiFiInstance('${escapeHtml(inst.url)}')" title="Remove instance">✖</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
_initHiFiDragDrop();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
listEl.innerHTML = `<div style="color:#f44336;font-size:0.85em;">Error loading instances: ${escapeHtml(e.message)}</div>`;
|
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() {
|
async function addHiFiInstance() {
|
||||||
const input = document.getElementById('hifi-new-instance');
|
const input = document.getElementById('hifi-new-instance');
|
||||||
if (!input) return;
|
if (!input) return;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
#artist-detail-page .release-card.album-card .mb-card-icon:hover {
|
||||||
opacity: 1;
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue