mirror of
https://github.com/cjo4m06/mcp-shrimp-task-manager.git
synced 2025-07-27 08:32:27 +08:00
feat: 增加reset按钮,便于在图像丢失时快速回归
This commit is contained in:
parent
d26782a633
commit
f9a90a8aa6
@ -46,6 +46,11 @@
|
|||||||
<div class="dependency-view">
|
<div class="dependency-view">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h2 data-i18n-key="dependency_view_title">Dependency View</h2>
|
<h2 data-i18n-key="dependency_view_title">Dependency View</h2>
|
||||||
|
<button id="reset-view-btn" class="reset-view-btn" title="Reset View" data-i18n-key="reset_view_btn_title">
|
||||||
|
<svg viewBox="0 0 24 24" width="16" height="16">
|
||||||
|
<path fill="currentColor" d="M12 5V2L8 6l4 4V7c3.31 0 6 2.69 6 6 0 2.97-2.17 5.43-5 5.91v2.02c3.95-.49 7-3.85 7-7.93 0-4.42-3.58-8-8-8zm-6 8c0-1.65.67-3.15 1.76-4.24L6.34 7.34C4.9 8.79 4 10.79 4 13c0 4.08 3.05 7.44 7 7.93v-2.02c-2.83-.48-5-2.94-5-5.91z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="dependency-graph" class="dependency-graph">
|
<div id="dependency-graph" class="dependency-graph">
|
||||||
<p class="placeholder"></p>
|
<p class="placeholder"></p>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"status_indicator_online": "ONLINE",
|
"status_indicator_online": "ONLINE",
|
||||||
"dependency_view_title": "Dependency View",
|
"dependency_view_title": "Dependency View",
|
||||||
"dependency_graph_placeholder": "Dependency relationship for all tasks",
|
"dependency_graph_placeholder": "Dependency relationship for all tasks",
|
||||||
|
"reset_view_btn_title": "Reset View",
|
||||||
"task_list_title": "Task List",
|
"task_list_title": "Task List",
|
||||||
"search_placeholder": "Search tasks...",
|
"search_placeholder": "Search tasks...",
|
||||||
"sort_option_date_desc": "Creation Time (New-Old)",
|
"sort_option_date_desc": "Creation Time (New-Old)",
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"status_indicator_online": "在線",
|
"status_indicator_online": "在線",
|
||||||
"dependency_view_title": "依賴關係",
|
"dependency_view_title": "依賴關係",
|
||||||
"dependency_graph_placeholder": "所有任務的依賴關係",
|
"dependency_graph_placeholder": "所有任務的依賴關係",
|
||||||
|
"reset_view_btn_title": "重置視圖",
|
||||||
"task_list_title": "任務列表",
|
"task_list_title": "任務列表",
|
||||||
"search_placeholder": "搜索任務...",
|
"search_placeholder": "搜索任務...",
|
||||||
"sort_option_date_desc": "創建時間 (新-舊)",
|
"sort_option_date_desc": "創建時間 (新-舊)",
|
||||||
|
@ -4,8 +4,10 @@ let selectedTaskId = null;
|
|||||||
let searchTerm = "";
|
let searchTerm = "";
|
||||||
let sortOption = "date-asc";
|
let sortOption = "date-asc";
|
||||||
let globalAnalysisResult = null; // 新增:儲存全局分析結果
|
let globalAnalysisResult = null; // 新增:儲存全局分析結果
|
||||||
let svg, g, simulation; // << 修改:定義 D3 相關變量
|
let svg, g, simulation;
|
||||||
|
let width, height; // << 新增:將寬高定義為全局變量
|
||||||
let isGraphInitialized = false; // << 新增:追蹤圖表是否已初始化
|
let isGraphInitialized = false; // << 新增:追蹤圖表是否已初始化
|
||||||
|
let zoom; // << 新增:保存縮放行為對象
|
||||||
|
|
||||||
// 新增:i18n 全局變量
|
// 新增:i18n 全局變量
|
||||||
let currentLang = "en"; // 預設語言
|
let currentLang = "en"; // 預設語言
|
||||||
@ -26,6 +28,7 @@ const globalAnalysisResultElement = document.getElementById(
|
|||||||
"global-analysis-result"
|
"global-analysis-result"
|
||||||
); // 假設 HTML 中有這個元素
|
); // 假設 HTML 中有這個元素
|
||||||
const langSwitcher = document.getElementById("lang-switcher"); // << 新增:獲取切換器元素
|
const langSwitcher = document.getElementById("lang-switcher"); // << 新增:獲取切換器元素
|
||||||
|
const resetViewBtn = document.getElementById("reset-view-btn"); // << 新增:獲取重置按鈕元素
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
@ -33,6 +36,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
initI18n(); // << 新增:初始化 i18n
|
initI18n(); // << 新增:初始化 i18n
|
||||||
updateCurrentTime();
|
updateCurrentTime();
|
||||||
setInterval(updateCurrentTime, 1000);
|
setInterval(updateCurrentTime, 1000);
|
||||||
|
updateDimensions(); // << 新增:初始化時更新尺寸
|
||||||
|
|
||||||
// 事件監聽器
|
// 事件監聽器
|
||||||
// statusFilter.addEventListener("change", renderTasks); // 將由 changeLanguage 觸發或在 applyTranslations 後觸發
|
// statusFilter.addEventListener("change", renderTasks); // 將由 changeLanguage 觸發或在 applyTranslations 後觸發
|
||||||
@ -40,6 +44,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
statusFilter.addEventListener("change", renderTasks);
|
statusFilter.addEventListener("change", renderTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增:重置視圖按鈕事件監聽
|
||||||
|
if (resetViewBtn) {
|
||||||
|
resetViewBtn.addEventListener("click", resetView);
|
||||||
|
}
|
||||||
|
|
||||||
// 新增:搜索和排序事件監聽
|
// 新增:搜索和排序事件監聽
|
||||||
const searchInput = document.getElementById("search-input");
|
const searchInput = document.getElementById("search-input");
|
||||||
const sortOptions = document.getElementById("sort-options");
|
const sortOptions = document.getElementById("sort-options");
|
||||||
@ -67,6 +76,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
changeLanguage(e.target.value)
|
changeLanguage(e.target.value)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增:視窗大小改變時更新尺寸
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
updateDimensions();
|
||||||
|
if (svg && simulation) {
|
||||||
|
svg.attr("viewBox", [0, 0, width, height]);
|
||||||
|
simulation.force("center", d3.forceCenter(width / 2, height / 2));
|
||||||
|
simulation.alpha(0.3).restart();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 新增:i18n 核心函數
|
// 新增:i18n 核心函數
|
||||||
@ -742,31 +761,75 @@ function selectTask(taskId) {
|
|||||||
highlightNode(taskId); // 只調用 highlightNode
|
highlightNode(taskId); // 只調用 highlightNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增:重置視圖功能
|
||||||
|
function resetView() {
|
||||||
|
if (!svg || !simulation) return;
|
||||||
|
|
||||||
|
// 添加重置動畫效果
|
||||||
|
resetViewBtn.classList.add("resetting");
|
||||||
|
|
||||||
|
// 計算視圖中心
|
||||||
|
const centerX = width / 2;
|
||||||
|
const centerY = height / 2;
|
||||||
|
|
||||||
|
// 重置縮放和平移(使用 transform 過渡)
|
||||||
|
svg.transition()
|
||||||
|
.duration(750)
|
||||||
|
.call(zoom.transform, d3.zoomIdentity);
|
||||||
|
|
||||||
|
// 重置所有節點位置到中心附近
|
||||||
|
simulation.nodes().forEach(node => {
|
||||||
|
node.x = centerX + (Math.random() - 0.5) * 50; // 在中心點附近隨機分佈
|
||||||
|
node.y = centerY + (Math.random() - 0.5) * 50;
|
||||||
|
node.fx = null; // 清除固定位置
|
||||||
|
node.fy = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 重置力導向模擬
|
||||||
|
simulation
|
||||||
|
.force("center", d3.forceCenter(centerX, centerY))
|
||||||
|
.alpha(1) // 完全重啟模擬
|
||||||
|
.restart();
|
||||||
|
|
||||||
|
// 750ms 後移除動畫類
|
||||||
|
setTimeout(() => {
|
||||||
|
resetViewBtn.classList.remove("resetting");
|
||||||
|
}, 750);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:初始化縮放行為
|
||||||
|
function initZoom() {
|
||||||
|
zoom = d3.zoom()
|
||||||
|
.scaleExtent([0.1, 4]) // 設置縮放範圍
|
||||||
|
.on("zoom", (event) => {
|
||||||
|
g.attr("transform", event.transform);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (svg) {
|
||||||
|
svg.call(zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 渲染依賴關係圖 - 修改為全局視圖和 enter/update/exit 模式
|
// 渲染依賴關係圖 - 修改為全局視圖和 enter/update/exit 模式
|
||||||
function renderDependencyGraph() {
|
function renderDependencyGraph() {
|
||||||
if (!dependencyGraphElement || !window.d3) {
|
if (!dependencyGraphElement || !window.d3) {
|
||||||
console.warn("D3 or dependency graph element not found.");
|
console.warn("D3 or dependency graph element not found.");
|
||||||
if (dependencyGraphElement) {
|
if (dependencyGraphElement) {
|
||||||
// 首次或D3丟失時顯示提示,不清空已有的圖
|
|
||||||
if (!dependencyGraphElement.querySelector("svg")) {
|
if (!dependencyGraphElement.querySelector("svg")) {
|
||||||
dependencyGraphElement.innerHTML = `<p class="placeholder">${translate(
|
dependencyGraphElement.innerHTML = `<p class="placeholder">${translate("error_loading_graph_d3")}</p>`;
|
||||||
"error_loading_graph_d3" // Use a specific key
|
|
||||||
)}</p>`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateDimensions();
|
||||||
|
|
||||||
// 如果沒有任務,清空圖表並顯示提示
|
// 如果沒有任務,清空圖表並顯示提示
|
||||||
if (tasks.length === 0) {
|
if (tasks.length === 0) {
|
||||||
dependencyGraphElement.innerHTML = `<p class="placeholder">${translate(
|
dependencyGraphElement.innerHTML = `<p class="placeholder">${translate("dependency_graph_placeholder_empty")}</p>`;
|
||||||
"dependency_graph_placeholder_empty"
|
|
||||||
)}</p>`;
|
|
||||||
// 重置 SVG 和 simulation 變數,以便下次正確初始化
|
|
||||||
svg = null;
|
svg = null;
|
||||||
g = null;
|
g = null;
|
||||||
simulation = null;
|
simulation = null;
|
||||||
isGraphInitialized = false; // << 新增:重置初始化標誌
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -775,10 +838,9 @@ function renderDependencyGraph() {
|
|||||||
id: task.id,
|
id: task.id,
|
||||||
name: task.name,
|
name: task.name,
|
||||||
status: task.status,
|
status: task.status,
|
||||||
// 保留現有位置以便平滑過渡
|
|
||||||
x: simulation?.nodes().find((n) => n.id === task.id)?.x,
|
x: simulation?.nodes().find((n) => n.id === task.id)?.x,
|
||||||
y: simulation?.nodes().find((n) => n.id === task.id)?.y,
|
y: simulation?.nodes().find((n) => n.id === task.id)?.y,
|
||||||
fx: simulation?.nodes().find((n) => n.id === task.id)?.fx, // 保留固定位置
|
fx: simulation?.nodes().find((n) => n.id === task.id)?.fx,
|
||||||
fy: simulation?.nodes().find((n) => n.id === task.id)?.fy,
|
fy: simulation?.nodes().find((n) => n.id === task.id)?.fy,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -788,44 +850,29 @@ function renderDependencyGraph() {
|
|||||||
task.dependencies.forEach((dep) => {
|
task.dependencies.forEach((dep) => {
|
||||||
const sourceId = typeof dep === "object" ? dep.taskId : dep;
|
const sourceId = typeof dep === "object" ? dep.taskId : dep;
|
||||||
const targetId = task.id;
|
const targetId = task.id;
|
||||||
if (
|
if (nodes.some((n) => n.id === sourceId) && nodes.some((n) => n.id === targetId)) {
|
||||||
nodes.some((n) => n.id === sourceId) &&
|
|
||||||
nodes.some((n) => n.id === targetId)
|
|
||||||
) {
|
|
||||||
// 確保 link 的 source/target 是 ID,以便力導向識別
|
|
||||||
links.push({ source: sourceId, target: targetId });
|
links.push({ source: sourceId, target: targetId });
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(`Dependency link ignored: Task ${sourceId} or ${targetId} not found in task list.`);
|
||||||
`Dependency link ignored: Task ${sourceId} or ${targetId} not found in task list.`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. D3 繪圖設置與更新
|
|
||||||
const width = dependencyGraphElement.clientWidth;
|
|
||||||
const height = dependencyGraphElement.clientHeight || 400;
|
|
||||||
|
|
||||||
if (!svg) {
|
if (!svg) {
|
||||||
// --- 首次渲染 ---
|
// --- 首次渲染 ---
|
||||||
console.log("First render of dependency graph");
|
console.log("First render of dependency graph");
|
||||||
dependencyGraphElement.innerHTML = ""; // 清空 placeholder
|
dependencyGraphElement.innerHTML = "";
|
||||||
|
|
||||||
svg = d3
|
svg = d3.select(dependencyGraphElement)
|
||||||
.select(dependencyGraphElement)
|
|
||||||
.append("svg")
|
.append("svg")
|
||||||
.attr("viewBox", [0, 0, width, height])
|
.attr("viewBox", [0, 0, width, height])
|
||||||
.attr("preserveAspectRatio", "xMidYMid meet");
|
.attr("preserveAspectRatio", "xMidYMid meet");
|
||||||
|
|
||||||
g = svg.append("g"); // 主要組,用於縮放和平移
|
g = svg.append("g");
|
||||||
|
|
||||||
// 添加縮放和平移
|
// 初始化並添加縮放行為
|
||||||
svg.call(
|
initZoom();
|
||||||
d3.zoom().on("zoom", (event) => {
|
|
||||||
g.attr("transform", event.transform);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// 添加箭頭定義
|
// 添加箭頭定義
|
||||||
g.append("defs")
|
g.append("defs")
|
||||||
@ -842,19 +889,12 @@ function renderDependencyGraph() {
|
|||||||
.attr("fill", "#999");
|
.attr("fill", "#999");
|
||||||
|
|
||||||
// 初始化力導向模擬
|
// 初始化力導向模擬
|
||||||
simulation = d3
|
simulation = d3.forceSimulation()
|
||||||
.forceSimulation() // 初始化時不傳入 nodes
|
.force("link", d3.forceLink().id((d) => d.id).distance(100))
|
||||||
.force(
|
|
||||||
"link",
|
|
||||||
d3
|
|
||||||
.forceLink()
|
|
||||||
.id((d) => d.id)
|
|
||||||
.distance(100) // 指定 id 訪問器
|
|
||||||
)
|
|
||||||
.force("charge", d3.forceManyBody().strength(-300))
|
.force("charge", d3.forceManyBody().strength(-300))
|
||||||
.force("center", d3.forceCenter(width / 2, height / 2))
|
.force("center", d3.forceCenter(width / 2, height / 2))
|
||||||
.force("collide", d3.forceCollide().radius(30))
|
.force("collide", d3.forceCollide().radius(30))
|
||||||
.on("tick", ticked); // 綁定 tick 事件處理函數
|
.on("tick", ticked);
|
||||||
|
|
||||||
// 添加用於存放連結和節點的組
|
// 添加用於存放連結和節點的組
|
||||||
g.append("g").attr("class", "links");
|
g.append("g").attr("class", "links");
|
||||||
@ -862,7 +902,6 @@ function renderDependencyGraph() {
|
|||||||
} else {
|
} else {
|
||||||
// --- 更新渲染 ---
|
// --- 更新渲染 ---
|
||||||
console.log("Updating dependency graph");
|
console.log("Updating dependency graph");
|
||||||
// 更新 SVG 尺寸和中心力 (如果窗口大小改變)
|
|
||||||
svg.attr("viewBox", [0, 0, width, height]);
|
svg.attr("viewBox", [0, 0, width, height]);
|
||||||
simulation.force("center", d3.forceCenter(width / 2, height / 2));
|
simulation.force("center", d3.forceCenter(width / 2, height / 2));
|
||||||
}
|
}
|
||||||
@ -1191,5 +1230,13 @@ function getStatusClass(status) {
|
|||||||
return status ? status.replace(/_/g, "-") : "unknown"; // 替換所有下劃線
|
return status ? status.replace(/_/g, "-") : "unknown"; // 替換所有下劃線
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增:更新寬高的函數
|
||||||
|
function updateDimensions() {
|
||||||
|
if (dependencyGraphElement) {
|
||||||
|
width = dependencyGraphElement.clientWidth;
|
||||||
|
height = dependencyGraphElement.clientHeight || 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 函數:啟用節點拖拽 (保持不變)
|
// 函數:啟用節點拖拽 (保持不變)
|
||||||
// ... drag ...
|
// ... drag ...
|
||||||
|
@ -123,6 +123,32 @@ main {
|
|||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reset-view-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-view-btn:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
transform: rotate(-30deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-view-btn svg {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-view-btn.resetting svg {
|
||||||
|
transform: rotate(-360deg);
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user