Skip to content

Commit d0ac10f

Browse files
PythonSmall-QCopilot
andcommitted
Refactor code structure for improved readability and maintainability
Co-authored-by: Copilot <copilot@github.com>
1 parent 7b8e51c commit d0ac10f

15 files changed

Lines changed: 350 additions & 66 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,4 @@ src-tauri/windows/msix-staging/AppxManifest.xml
5454
src-tauri/windows/msix-staging/timelens.exe
5555
browser-extension.zip
5656
src-tauri/windows/msix-staging/timelens.exe
57+
vscode-extension/timelens-vscode-extension-0.1.0.vsix

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
"tauri": "tauri",
1111
"tauri:dev": "tauri dev",
1212
"tauri:build": "tauri build",
13+
"tauri:build:msi": "tauri build --bundles msi",
14+
"tauri:build:nsis": "tauri build --bundles nsis",
15+
"tauri:build:all": "npm run tauri:build:msi && npm run tauri:build:nsis",
1316
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
1417
"typecheck": "tsc --noEmit",
1518
"test": "vitest run",

src-tauri/src/api_server/mod.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use tower_http::cors::{Any, CorsLayer};
2222

2323
use crate::db;
2424
use crate::monitor::SharedMonitorStatus;
25-
use crate::models::{BrowserSession, VsCodeLanguageDuration, VsCodeSession};
25+
use crate::models::{AppUsageSummary, BrowserSession, VsCodeLanguageDuration, VsCodeSession};
2626

2727
/// Shared state threaded through axum handlers.
2828
#[derive(Clone)]
@@ -98,8 +98,11 @@ async fn get_today(State(s): State<ApiState>) -> impl IntoResponse {
9898
let today = chrono::Local::now().format("%Y-%m-%d").to_string();
9999
match s.db.lock() {
100100
Ok(conn) => {
101-
let rows = db::get_app_totals_in_range(&conn, &today, &today)
102-
.unwrap_or_default();
101+
let rows: Vec<AppUsageSummary> = db::get_app_totals_in_range(&conn, &today, &today)
102+
.unwrap_or_default()
103+
.into_iter()
104+
.map(|(app_name, exe_path, total_seconds)| AppUsageSummary { app_name, exe_path, total_seconds })
105+
.collect();
103106
Json(rows).into_response()
104107
}
105108
Err(_) => axum::http::StatusCode::INTERNAL_SERVER_ERROR.into_response(),
@@ -115,8 +118,11 @@ async fn get_range(
115118
let end = p.end.as_deref().unwrap_or(&today).to_string();
116119
match s.db.lock() {
117120
Ok(conn) => {
118-
let rows = db::get_app_totals_in_range(&conn, &start, &end)
119-
.unwrap_or_default();
121+
let rows: Vec<AppUsageSummary> = db::get_app_totals_in_range(&conn, &start, &end)
122+
.unwrap_or_default()
123+
.into_iter()
124+
.map(|(app_name, exe_path, total_seconds)| AppUsageSummary { app_name, exe_path, total_seconds })
125+
.collect();
120126
Json(rows).into_response()
121127
}
122128
Err(_) => axum::http::StatusCode::INTERNAL_SERVER_ERROR.into_response(),

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
},
3030
"bundle": {
3131
"active": true,
32-
"targets": "all",
32+
"targets": ["nsis"],
3333
"publisher": "CN=67203FD7-3571-4CF3-9A8B-DE6BA454CE5B",
3434
"homepage": "https://github.com/PythonSmall-Q/TimeLens",
3535
"license": "MIT",

src/pages/Dashboard/index.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,15 @@ export default function Dashboard() {
131131
fetchForDate(selectedDate);
132132
}, [fetchForDate, fetchToday, periodMode, selectedDate]);
133133

134+
useEffect(() => {
135+
if (periodMode !== "day" || selectedDate !== todayString()) return;
136+
const id = setInterval(() => {
137+
void fetchToday();
138+
void fetchVsCodeStatsForRange(selectedDate, selectedDate);
139+
}, 10_000);
140+
return () => clearInterval(id);
141+
}, [periodMode, selectedDate, fetchToday, fetchVsCodeStatsForRange]);
142+
134143
useEffect(() => {
135144
const end = todayString();
136145
const startDate = new Date(`${end}T00:00:00`);

src/pages/VsCodeInsights/index.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ export default function VsCodeInsights() {
6464
fetchVsCodeStatsForRange(range.start, range.end);
6565
}, [fetchVsCodeStatsForRange, range.start, range.end]);
6666

67+
useEffect(() => {
68+
const today = todayString();
69+
if (range.start > today || range.end < today) return;
70+
const id = setInterval(() => {
71+
void fetchVsCodeStatsForRange(range.start, range.end);
72+
}, 10_000);
73+
return () => clearInterval(id);
74+
}, [fetchVsCodeStatsForRange, range.start, range.end]);
75+
6776
useEffect(() => {
6877
getVsCodeTrackingEnabled()
6978
.then((res) => {

vscode-extension/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ npm run build
3434
## Package VSIX
3535

3636
```bash
37-
npm --prefix vscode-extension run package
37+
npm run package
3838
```
3939

4040
Or from repository root:
Lines changed: 2 additions & 3 deletions
Loading

vscode-extension/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "TimeLens",
44
"description": "Track VS Code session time and send local analytics to TimeLens desktop app.",
55
"icon": "icons/icon.png",
6-
"version": "0.1.0",
6+
"version": "0.2.0",
77
"publisher": "ShanWenxiao",
88
"license": "MIT",
99
"repository": {
@@ -50,6 +50,10 @@
5050
]
5151
},
5252
"commands": [
53+
{
54+
"command": "timelens.openSidebar",
55+
"title": "TimeLens: Open Sidebar"
56+
},
5357
{
5458
"command": "timelens.enableTracking",
5559
"title": "TimeLens: Enable Tracking"

vscode-extension/src/dashboardPanel.ts

Lines changed: 105 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import * as vscode from "vscode";
2-
import * as path from "node:path";
3-
import * as fs from "node:fs";
42

53
/** Fetch JSON from the TimeLens local API, returns null on any error. */
64
async function fetchJson<T>(url: string): Promise<T | null> {
@@ -59,6 +57,8 @@ export class DashboardPanel {
5957
}
6058
);
6159

60+
panel.webview.html = buildLoadingHtml();
61+
6262
try {
6363
panel.iconPath = iconPath;
6464
} catch {
@@ -88,48 +88,56 @@ export class DashboardPanel {
8888
// Auto-refresh every 30 s while panel is visible
8989
this.refreshTimer = setInterval(() => {
9090
if (this.panel.visible) {
91-
this.refresh();
91+
void this.refresh();
9292
}
9393
}, 30_000);
9494

95-
this.refresh();
95+
void this.refresh().catch((error) => {
96+
this.panel.webview.html = buildErrorHtml(error);
97+
});
9698
}
9799

98100
public async refresh(): Promise<void> {
101+
this.panel.webview.html = buildLoadingHtml();
102+
99103
const apiBase = vscode.workspace
100104
.getConfiguration("timelens")
101105
.get<string>("apiBaseUrl", "http://127.0.0.1:49152");
102106

103-
const [todayStats, todayApps, langStats, projectStats, trackingStatus, appStatus] =
104-
await Promise.all([
105-
fetchJson<{ total_seconds: number; session_count: number }>(
106-
`${apiBase}/api/vscode/stats/today`
107-
),
108-
fetchJson<{ app_name: string; exe_name: string; total_seconds: number; category: string }[]>(
109-
`${apiBase}/api/screen-time/today`
110-
),
111-
fetchJson<{ language: string; total_seconds: number }[]>(
112-
`${apiBase}/api/vscode/languages/range?start=${today()}&end=${today()}`
113-
),
114-
fetchJson<{ project_name: string; project_path: string; total_seconds: number; session_count: number }[]>(
115-
`${apiBase}/api/vscode/projects/range?start=${today()}&end=${today()}`
116-
),
117-
fetchJson<{ enabled: boolean; tracking_level?: string }>(
118-
`${apiBase}/api/vscode/enabled`
119-
),
120-
fetchJson<{ version: string; focus_active: boolean }>(
121-
`${apiBase}/api/status`
122-
),
123-
]);
124-
125-
this.panel.webview.html = buildHtml({
126-
todayStats,
127-
todayApps: todayApps ?? [],
128-
langStats: langStats ?? [],
129-
projectStats: projectStats ?? [],
130-
trackingStatus,
131-
appStatus,
132-
});
107+
try {
108+
const [todayStats, todayApps, langStats, projectStats, trackingStatus, appStatus] =
109+
await Promise.all([
110+
fetchJson<{ total_seconds: number; session_count: number }>(
111+
`${apiBase}/api/vscode/stats/today`
112+
),
113+
fetchJson<{ app_name: string; exe_path: string; total_seconds: number }[]>(
114+
`${apiBase}/api/screen-time/today`
115+
),
116+
fetchJson<{ language: string; total_seconds: number }[]>(
117+
`${apiBase}/api/vscode/languages/range?start=${today()}&end=${today()}`
118+
),
119+
fetchJson<{ project_name: string; project_path: string; total_seconds: number; session_count: number }[]>(
120+
`${apiBase}/api/vscode/projects/range?start=${today()}&end=${today()}`
121+
),
122+
fetchJson<{ enabled: boolean; tracking_level?: string }>(
123+
`${apiBase}/api/vscode/enabled`
124+
),
125+
fetchJson<{ version: string; focus_active: boolean }>(
126+
`${apiBase}/api/status`
127+
),
128+
]);
129+
130+
this.panel.webview.html = buildHtml({
131+
todayStats,
132+
todayApps: todayApps ?? [],
133+
langStats: langStats ?? [],
134+
projectStats: projectStats ?? [],
135+
trackingStatus,
136+
appStatus,
137+
});
138+
} catch (error) {
139+
this.panel.webview.html = buildErrorHtml(error);
140+
}
133141
}
134142

135143
private async handleMessage(msg: { command: string; payload?: unknown }): Promise<void> {
@@ -193,7 +201,7 @@ function today(): string {
193201

194202
interface BuildHtmlOptions {
195203
todayStats: { total_seconds: number; session_count: number } | null;
196-
todayApps: { app_name: string; exe_name: string; total_seconds: number; category: string }[];
204+
todayApps: { app_name: string; exe_path: string; total_seconds: number }[];
197205
langStats: { language: string; total_seconds: number }[];
198206
projectStats: { project_name: string; project_path: string; total_seconds: number; session_count: number }[];
199207
trackingStatus: { enabled: boolean; tracking_level?: string } | null;
@@ -221,7 +229,7 @@ function buildHtml(opts: BuildHtmlOptions): string {
221229
const pct = totalAppSeconds > 0 ? Math.round((app.total_seconds / totalAppSeconds) * 100) : 0;
222230
return `<div class="bar-row">
223231
<div class="bar-label">
224-
<span class="app-name" title="${esc(app.exe_name)}">${esc(app.app_name || app.exe_name)}</span>
232+
<span class="app-name" title="${esc(app.exe_path)}">${esc(app.app_name || app.exe_path)}</span>
225233
<span class="bar-dur">${fmtDuration(app.total_seconds)}</span>
226234
</div>
227235
<div class="bar-track"><div class="bar-fill bar-fill-blue" style="width:${pct}%"></div></div>
@@ -419,6 +427,65 @@ ${topApps.length > 0 ? `
419427
</html>`;
420428
}
421429

422-
function esc(s: string): string {
423-
return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
430+
function esc(s: unknown): string {
431+
const value = typeof s === "string" ? s : String(s ?? "");
432+
return value
433+
.replace(/&/g, "&amp;")
434+
.replace(/</g, "&lt;")
435+
.replace(/>/g, "&gt;")
436+
.replace(/"/g, "&quot;");
437+
}
438+
439+
function buildLoadingHtml(): string {
440+
return `<!DOCTYPE html>
441+
<html lang="en">
442+
<head>
443+
<meta charset="UTF-8">
444+
<meta name="viewport" content="width=device-width,initial-scale=1">
445+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline';">
446+
<style>
447+
body { font-family: var(--vscode-font-family); padding: 16px; color: var(--vscode-foreground); background: var(--vscode-editor-background); }
448+
.card { border: 1px solid var(--vscode-panel-border); border-radius: 8px; padding: 12px; background: var(--vscode-sideBar-background); }
449+
.title { font-size: 16px; font-weight: 700; margin-bottom: 8px; }
450+
.muted { color: var(--vscode-descriptionForeground); font-size: 12px; }
451+
</style>
452+
</head>
453+
<body>
454+
<div class="card">
455+
<div class="title">TimeLens Dashboard</div>
456+
<div class="muted">Loading...</div>
457+
</div>
458+
</body>
459+
</html>`;
460+
}
461+
462+
function buildErrorHtml(error: unknown): string {
463+
const message = error instanceof Error ? error.message : "Unknown error";
464+
return `<!DOCTYPE html>
465+
<html lang="en">
466+
<head>
467+
<meta charset="UTF-8">
468+
<meta name="viewport" content="width=device-width,initial-scale=1">
469+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline';">
470+
<style>
471+
body { font-family: var(--vscode-font-family); padding: 16px; color: var(--vscode-foreground); background: var(--vscode-editor-background); }
472+
.card { border: 1px solid var(--vscode-panel-border); border-radius: 8px; padding: 12px; background: var(--vscode-sideBar-background); }
473+
.title { font-size: 16px; font-weight: 700; margin-bottom: 8px; }
474+
.muted { color: var(--vscode-descriptionForeground); font-size: 12px; }
475+
button { margin-top: 12px; border: 1px solid var(--vscode-panel-border); border-radius: 6px; padding: 6px 10px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); cursor: pointer; }
476+
</style>
477+
</head>
478+
<body>
479+
<div class="card">
480+
<div class="title">TimeLens Dashboard</div>
481+
<div class="muted">页面加载失败,但扩展仍在运行。</div>
482+
<div class="muted" style="margin-top:8px;">${esc(message)}</div>
483+
<button onclick="refresh()">重试</button>
484+
</div>
485+
<script>
486+
const vscode = acquireVsCodeApi();
487+
function refresh() { vscode.postMessage({ command: 'refresh' }); }
488+
</script>
489+
</body>
490+
</html>`;
424491
}

0 commit comments

Comments
 (0)