Add dashboard crosslinks and card drag-and-drop reordering
Dashboard cards (targets, auto-start, profiles) are now clickable, navigating to the full entity card on the appropriate tab. Card sections support drag-and-drop reordering via grip handles with localStorage persistence. Fix crosslink navigation scoping to avoid matching dashboard cards, and fix highlight race on rapid clicks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -447,7 +447,10 @@ export async function loadDashboard(forceFullRender = false) {
|
||||
? `<span class="dashboard-badge-active">${t('profiles.status.active')}</span>`
|
||||
: `<span class="dashboard-badge-stopped">${t('profiles.status.inactive')}</span>`;
|
||||
const subtitle = subtitleParts.length ? `<div class="dashboard-target-subtitle">${escapeHtml(subtitleParts.join(' · '))}</div>` : '';
|
||||
return `<div class="dashboard-target dashboard-autostart" data-target-id="${target.id}">
|
||||
const asNavSub = isLed ? 'led' : 'key_colors';
|
||||
const asNavSec = isLed ? 'led-targets' : 'kc-targets';
|
||||
const asNavAttr = isLed ? 'data-target-id' : 'data-kc-target-id';
|
||||
return `<div class="dashboard-target dashboard-autostart dashboard-card-link" data-target-id="${target.id}" onclick="if(!event.target.closest('button')){navigateToCard('targets','${asNavSub}','${asNavSec}','${asNavAttr}','${target.id}')}">
|
||||
<div class="dashboard-target-info">
|
||||
<span class="dashboard-target-icon">${ICON_AUTOSTART}</span>
|
||||
<div>
|
||||
@@ -551,6 +554,10 @@ function renderDashboardTarget(target, isRunning, devicesMap = {}, cssSourceMap
|
||||
const isLed = target.target_type === 'led' || target.target_type === 'wled';
|
||||
const icon = ICON_TARGET;
|
||||
const typeLabel = isLed ? t('dashboard.type.led') : t('dashboard.type.kc');
|
||||
const navSubTab = isLed ? 'led' : 'key_colors';
|
||||
const navSection = isLed ? 'led-targets' : 'kc-targets';
|
||||
const navAttr = isLed ? 'data-target-id' : 'data-kc-target-id';
|
||||
const navOnclick = `if(!event.target.closest('button')){navigateToCard('targets','${navSubTab}','${navSection}','${navAttr}','${target.id}')}`;
|
||||
|
||||
let subtitleParts = [typeLabel];
|
||||
if (isLed) {
|
||||
@@ -590,7 +597,7 @@ function renderDashboardTarget(target, isRunning, devicesMap = {}, cssSourceMap
|
||||
healthDot = `<span class="health-dot ${cls}"></span>`;
|
||||
}
|
||||
|
||||
return `<div class="dashboard-target" data-target-id="${target.id}">
|
||||
return `<div class="dashboard-target dashboard-card-link" data-target-id="${target.id}" onclick="${navOnclick}">
|
||||
<div class="dashboard-target-info">
|
||||
<span class="dashboard-target-icon">${icon}</span>
|
||||
<div>
|
||||
@@ -620,7 +627,7 @@ function renderDashboardTarget(target, isRunning, devicesMap = {}, cssSourceMap
|
||||
</div>
|
||||
</div>`;
|
||||
} else {
|
||||
return `<div class="dashboard-target">
|
||||
return `<div class="dashboard-target dashboard-card-link" onclick="${navOnclick}">
|
||||
<div class="dashboard-target-info">
|
||||
<span class="dashboard-target-icon">${icon}</span>
|
||||
<div>
|
||||
@@ -665,7 +672,7 @@ function renderDashboardProfile(profile) {
|
||||
const activeCount = (profile.active_target_ids || []).length;
|
||||
const targetsInfo = isActive ? `${activeCount}/${targetCount}` : `${targetCount}`;
|
||||
|
||||
return `<div class="dashboard-target dashboard-profile" data-profile-id="${profile.id}">
|
||||
return `<div class="dashboard-target dashboard-profile dashboard-card-link" data-profile-id="${profile.id}" onclick="if(!event.target.closest('button')){navigateToCard('profiles',null,'profiles','data-profile-id','${profile.id}')}">
|
||||
<div class="dashboard-target-info">
|
||||
<span class="dashboard-target-icon">${ICON_PROFILE}</span>
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user