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}")
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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">✔</span>`
|
||||
: `<span style="color:#666;cursor:pointer;" 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}">
|
||||
<span style="color:rgba(255,255,255,0.4);cursor:default;user-select:none;">☰</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">✔</span>`
|
||||
: `<span class="hifi-instance-toggle off" onclick="toggleHiFiInstance('${escapeHtml(inst.url)}')" title="Click to enable">✘</span>`;
|
||||
return `<div class="hifi-instance-item${inst.enabled ? '' : ' disabled'}" draggable="true" data-url="${escapeHtml(inst.url)}">
|
||||
<span class="hifi-instance-grip">☰</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">✖</span>
|
||||
<span class="hifi-instance-remove" onclick="removeHiFiInstance('${escapeHtml(inst.url)}')" title="Remove instance">✖</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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue