upgrade cal
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "chrono-snap",
|
"name": "chrono-snap",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.2.2",
|
"version": "0.2.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@@ -667,7 +667,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono-snap"
|
name = "chrono-snap"
|
||||||
version = "0.2.2"
|
version = "0.2.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "chrono-snap"
|
name = "chrono-snap"
|
||||||
version = "0.2.2"
|
version = "0.2.3"
|
||||||
description = "An app to record screens and events"
|
description = "An app to record screens and events"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -227,3 +227,37 @@ pub fn get_overdue_reminders_count(path: &str, date: &str, minute: i32) -> anyho
|
|||||||
let count: i32 = stmt.query_row(params![date, minute], |row| row.get(0))?;
|
let count: i32 = stmt.query_row(params![date, minute], |row| row.get(0))?;
|
||||||
Ok(count)
|
Ok(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct DayStatus {
|
||||||
|
pub date: String,
|
||||||
|
pub has_overdue: bool,
|
||||||
|
pub has_upcoming: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_reminders_by_month(path: &str, year_month: &str, today: &str, now_minute: i32) -> anyhow::Result<Vec<DayStatus>> {
|
||||||
|
let conn = Connection::open(path)?;
|
||||||
|
// 查找该月份内有提醒的所有日期
|
||||||
|
let mut stmt = conn.prepare("
|
||||||
|
SELECT date,
|
||||||
|
MAX(CASE WHEN is_completed = 0 AND (date < ?2 OR (date = ?2 AND minute < ?3)) THEN 1 ELSE 0 END) as has_overdue,
|
||||||
|
MAX(CASE WHEN is_completed = 0 AND (date > ?2 OR (date = ?2 AND minute >= ?3)) THEN 1 ELSE 0 END) as has_upcoming
|
||||||
|
FROM reminders
|
||||||
|
WHERE date LIKE ?1
|
||||||
|
GROUP BY date
|
||||||
|
")?;
|
||||||
|
|
||||||
|
let rows = stmt.query_map(params![format!("{}%", year_month), today, now_minute], |row| {
|
||||||
|
Ok(DayStatus {
|
||||||
|
date: row.get(0)?,
|
||||||
|
has_overdue: row.get::<_, i32>(1)? == 1,
|
||||||
|
has_upcoming: row.get::<_, i32>(2)? == 1,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
for row in rows {
|
||||||
|
results.push(row?);
|
||||||
|
}
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|||||||
@@ -107,6 +107,13 @@ pub fn get_overdue_reminders_count(state: tauri::State<'_, AppState>, date: Stri
|
|||||||
crate::db::get_overdue_reminders_count(path, &date, minute).map_err(|e| e.to_string())
|
crate::db::get_overdue_reminders_count(path, &date, minute).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_reminders_by_month(state: tauri::State<'_, AppState>, year_month: String, today: String, now_minute: i32) -> Result<Vec<crate::db::DayStatus>, String> {
|
||||||
|
let path = state.db_path.lock().unwrap();
|
||||||
|
let path = path.as_ref().ok_or("Database path not set")?;
|
||||||
|
crate::db::get_reminders_by_month(path, &year_month, &today, now_minute).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn update_interval(state: tauri::State<'_, AppState>, seconds: u64) {
|
pub fn update_interval(state: tauri::State<'_, AppState>, seconds: u64) {
|
||||||
state.capture_interval_secs.store(seconds, Ordering::SeqCst);
|
state.capture_interval_secs.store(seconds, Ordering::SeqCst);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ pub fn run() {
|
|||||||
engine::delete_reminder,
|
engine::delete_reminder,
|
||||||
engine::toggle_reminder,
|
engine::toggle_reminder,
|
||||||
engine::get_overdue_reminders_count,
|
engine::get_overdue_reminders_count,
|
||||||
|
engine::get_reminders_by_month,
|
||||||
engine::write_file
|
engine::write_file
|
||||||
])
|
])
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "ChronoSnap",
|
"productName": "ChronoSnap",
|
||||||
"version": "0.2.2",
|
"version": "0.2.3",
|
||||||
"identifier": "top.volan.chrono-snap",
|
"identifier": "top.volan.chrono-snap",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "瞬影 - 时间记录 v0.2.2",
|
"title": "瞬影 - 时间记录 v0.2.3",
|
||||||
"width": 1760,
|
"width": 1760,
|
||||||
"height": 1100
|
"height": 1100
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/App.vue
33
src/App.vue
@@ -14,7 +14,8 @@ import {
|
|||||||
retainDays, captureInterval,
|
retainDays, captureInterval,
|
||||||
TIME_OFFSET_MINUTES, TOTAL_MINUTES, getTagColor, getTagName, mainTags, getSubTags,
|
TIME_OFFSET_MINUTES, TOTAL_MINUTES, getTagColor, getTagName, mainTags, getSubTags,
|
||||||
logicalMinutesToTime, logicalMinutesFromTime, formatDuration,
|
logicalMinutesToTime, logicalMinutesFromTime, formatDuration,
|
||||||
loadTags, loadEvents, loadReminders, toISODate, refreshBadgeCount
|
loadTags, loadEvents, loadReminders, toISODate, refreshBadgeCount,
|
||||||
|
calendarStatus, loadCalendarStatus, currentCalendarMonthStr
|
||||||
} from "./store";
|
} from "./store";
|
||||||
import { DBEvent, TimelineItem } from "./types";
|
import { DBEvent, TimelineItem } from "./types";
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ const checkReminders = async (currentMin: number) => {
|
|||||||
|
|
||||||
for (const r of dueReminders) {
|
for (const r of dueReminders) {
|
||||||
if (hasPermission) {
|
if (hasPermission) {
|
||||||
sendNotification({ title: '', body: r.content });
|
sendNotification({ title: '瞬影 提醒', body: r.content });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +109,7 @@ const updateCurrentMinute = () => {
|
|||||||
} else {
|
} else {
|
||||||
currentLogicalMinute.value = -1;
|
currentLogicalMinute.value = -1;
|
||||||
}
|
}
|
||||||
// 无论是否看今天,每分钟都尝试刷新全局徽章
|
// 无论是否看今天,每分钟都尝试刷新全局徽章和日历状态点
|
||||||
refreshBadgeCount();
|
refreshBadgeCount();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -126,6 +127,13 @@ const startMinuteTimer = () => {
|
|||||||
}, msUntilNextMinute);
|
}, msUntilNextMinute);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(calendarMonth, (newMonth) => {
|
||||||
|
const y = newMonth.getFullYear();
|
||||||
|
const m = String(newMonth.getMonth() + 1).padStart(2, '0');
|
||||||
|
currentCalendarMonthStr.value = `${y}-${m}`;
|
||||||
|
loadCalendarStatus(currentCalendarMonthStr.value);
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
watch(currentDate, () => {
|
watch(currentDate, () => {
|
||||||
updateCurrentMinute();
|
updateCurrentMinute();
|
||||||
});
|
});
|
||||||
@@ -392,8 +400,23 @@ const togglePause = async () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-7 gap-1 text-center mb-2"><div v-for="d in ['一','二','三','四','五','六','日']" :key="d" class="text-[10px] font-bold text-text-sec">{{d}}</div></div>
|
<div class="grid grid-cols-7 gap-1 text-center mb-2"><div v-for="d in ['一','二','三','四','五','六','日']" :key="d" class="text-[10px] font-bold text-text-sec">{{d}}</div></div>
|
||||||
<div class="grid grid-cols-7 gap-1">
|
<div class="grid grid-cols-7 gap-1">
|
||||||
<div v-for="(date, i) in calendarDays" :key="i" class="aspect-square flex items-center justify-center">
|
<div v-for="(date, i) in calendarDays" :key="i" class="aspect-square flex flex-col items-center justify-center relative">
|
||||||
<button v-if="date" @click="selectCalendarDate(date)" class="w-8 h-8 rounded-full text-xs font-medium transition-all" :class="date.toLocaleDateString('sv') === currentDate ? 'bg-[#007AFF] text-white font-bold' : 'hover:bg-bg-input text-main'">{{ date.getDate() }}</button>
|
<button v-if="date" @click="selectCalendarDate(date)"
|
||||||
|
class="w-8 h-8 rounded-full text-xs font-medium transition-all flex items-center justify-center relative"
|
||||||
|
:class="[
|
||||||
|
date.toLocaleDateString('sv') === getLogicDateStr()
|
||||||
|
? 'bg-[#007AFF] text-white font-black shadow-[0_4px_12px_rgba(0,122,255,0.4)] ' + (date.toLocaleDateString('sv') === currentDate ? 'ring-2 ring-offset-2 ring-[#007AFF]' : '')
|
||||||
|
: (date.toLocaleDateString('sv') === currentDate
|
||||||
|
? 'bg-bg-input text-[#007AFF] font-black ring-1 ring-border-main shadow-sm'
|
||||||
|
: 'hover:bg-bg-input text-main')
|
||||||
|
]">
|
||||||
|
{{ date.getDate() }}
|
||||||
|
</button>
|
||||||
|
<!-- Status Dots -->
|
||||||
|
<div v-if="date && calendarStatus[date.toLocaleDateString('sv')]" class="absolute -bottom-1.5 flex gap-0.5">
|
||||||
|
<div v-if="calendarStatus[date.toLocaleDateString('sv')].has_overdue" class="w-1.5 h-1.5 rounded-full bg-[#FF3B30] border-[1.5px] border-bg-card shadow-sm"></div>
|
||||||
|
<div v-if="calendarStatus[date.toLocaleDateString('sv')].has_upcoming" class="w-1.5 h-1.5 rounded-full bg-[#007AFF] border-[1.5px] border-bg-card shadow-sm"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export const lockedImage = ref<TimelineItem | null>(null);
|
|||||||
export const tags = ref<Tag[]>([]);
|
export const tags = ref<Tag[]>([]);
|
||||||
export const dayEvents = ref<DBEvent[]>([]);
|
export const dayEvents = ref<DBEvent[]>([]);
|
||||||
export const reminders = ref<Reminder[]>([]);
|
export const reminders = ref<Reminder[]>([]);
|
||||||
|
export const calendarStatus = ref<Record<string, { has_overdue: boolean, has_upcoming: boolean }>>({});
|
||||||
export const timelineImages = ref<TimelineItem[]>([]);
|
export const timelineImages = ref<TimelineItem[]>([]);
|
||||||
export const refreshSignal = ref(0); // Counter to trigger dashboard refreshes
|
export const refreshSignal = ref(0); // Counter to trigger dashboard refreshes
|
||||||
|
|
||||||
@@ -114,6 +115,33 @@ export const loadEvents = async () => {
|
|||||||
export const loadReminders = async () => {
|
export const loadReminders = async () => {
|
||||||
reminders.value = await invoke("get_reminders", { date: currentDate.value });
|
reminders.value = await invoke("get_reminders", { date: currentDate.value });
|
||||||
refreshBadgeCount();
|
refreshBadgeCount();
|
||||||
|
refreshCalendarStatus(); // 提醒更新时也刷新日历状态
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadCalendarStatus = async (yearMonth: string) => {
|
||||||
|
if (!dbPath.value) return;
|
||||||
|
try {
|
||||||
|
const { date, minute } = getRealNowLogicalTime();
|
||||||
|
const statuses: any[] = await invoke("get_reminders_by_month", {
|
||||||
|
yearMonth,
|
||||||
|
today: date,
|
||||||
|
nowMinute: minute
|
||||||
|
});
|
||||||
|
const map: Record<string, any> = {};
|
||||||
|
statuses.forEach(s => { map[s.date] = s; });
|
||||||
|
calendarStatus.value = map;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to load calendar status:", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当前显示的日历月份,用于自动刷新状态
|
||||||
|
export const currentCalendarMonthStr = ref("");
|
||||||
|
|
||||||
|
export const refreshCalendarStatus = () => {
|
||||||
|
if (currentCalendarMonthStr.value) {
|
||||||
|
loadCalendarStatus(currentCalendarMonthStr.value);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const overdueCount = ref(0);
|
export const overdueCount = ref(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user