fix: keep todo scrolling simple and visible

This commit is contained in:
JOJO 2025-12-14 22:07:21 +08:00
parent 8b250c5c6b
commit d5e6c9c077
2 changed files with 79 additions and 25 deletions

View File

@ -2774,22 +2774,27 @@ export class MonitorDirector implements MonitorDriver {
payload?.arguments?.title ||
payload?.arguments?.task ||
'待办摘要';
const tasks = this.normalizeTodoTasks(payload?.arguments?.tasks || summary);
await this.ensureTodoWindowVisible();
await this.typeTodoSummary(summary);
for (const task of tasks) {
await this.animateTodoAppend(task);
}
await sleep(320);
};
const tasks = this.normalizeTodoTasks(payload?.arguments?.tasks || summary);
await this.ensureTodoWindowVisible();
this.resetTodoBoard({ summary: true, list: true });
await this.typeTodoSummary(summary);
for (const task of tasks) {
await this.animateTodoAppend(task, { scrollIntoView: false });
}
if (this.elements.todoList) {
this.elements.todoList.scrollTop = 0;
}
await sleep(320);
};
this.sceneHandlers.todoUpdate = async (payload, runtime) => {
this.applySceneStatus(runtime, 'todoUpdate', '正在调整待办');
await this.ensureTodoWindowVisible();
const targetText = payload?.arguments?.title || payload?.arguments?.task || null;
const targetIndex = Number(payload?.arguments?.task_index || payload?.arguments?.index || 0) || null;
const completed =
payload?.arguments?.completed ?? payload?.arguments?.done ?? payload?.arguments?.checked ?? true;
await this.toggleTodoItem(targetText, !!completed);
await this.toggleTodoItem(targetText, !!completed, targetIndex);
await sleep(260);
};
@ -2801,6 +2806,7 @@ export class MonitorDirector implements MonitorDriver {
await this.click();
}
this.closeWindow(this.elements.todoWindow);
this.resetTodoBoard({ summary: true, list: true });
await sleep(320);
};
@ -5015,11 +5021,18 @@ export class MonitorDirector implements MonitorDriver {
}
private findTodoItemByText(text?: string | null): HTMLElement | null {
if (!text) return this.getTodoItems()[0] || null;
if (!text) return null;
const target = (text || '').trim();
return this.getTodoItems().find(item => item.querySelector('.todo-text')?.textContent?.trim() === target) || null;
}
private findTodoItemByIndex(index?: number | null): HTMLElement | null {
if (!index || index < 1) return null;
const items = this.getTodoItems();
if (index > items.length) return null;
return items[index - 1];
}
private normalizeTodoTasks(raw: any): Array<{ text: string; done?: boolean }> {
if (Array.isArray(raw)) {
return raw
@ -5027,7 +5040,14 @@ export class MonitorDirector implements MonitorDriver {
if (typeof item === 'string') return { text: item, done: false };
if (item && typeof item === 'object') {
const text = String(item.title || item.task || item.text || '').trim();
const done = typeof item.completed === 'boolean' ? item.completed : !!item.done;
const done =
typeof item.completed === 'boolean'
? item.completed
: typeof item.done === 'boolean'
? item.done
: typeof item.checked === 'boolean'
? item.checked
: false;
if (!text) return null;
return { text, done };
}
@ -5068,6 +5088,17 @@ export class MonitorDirector implements MonitorDriver {
});
}
private resetTodoBoard(options: { summary?: boolean; list?: boolean } = {}) {
const { summary = true, list = true } = options;
if (summary && this.elements.todoSummary) {
this.elements.todoSummary.textContent = '';
}
if (list && this.elements.todoList) {
this.elements.todoList.innerHTML = '';
this.elements.todoList.scrollTop = 0;
}
}
private async ensureTodoWindowVisible() {
if (this.isWindowVisible(this.elements.todoWindow)) {
this.showWindow(this.elements.todoWindow);
@ -5076,6 +5107,7 @@ export class MonitorDirector implements MonitorDriver {
await this.movePointerToApp('todo');
await this.click({ count: 2 });
this.showWindow(this.elements.todoWindow);
this.resetTodoBoard({ summary: true, list: true });
}
private async typeTodoSummary(text: string) {
@ -5090,26 +5122,44 @@ export class MonitorDirector implements MonitorDriver {
}
}
private async animateTodoAppend(task: { text: string; done?: boolean }) {
private async animateTodoAppend(task: { text: string; done?: boolean }, options: { scrollIntoView?: boolean } = {}) {
if (!this.elements.todoList) return;
const { scrollIntoView = false } = options;
const card = this.createTodoItem(task.text, !!task.done);
this.elements.todoList.appendChild(card);
requestAnimationFrame(() => card.classList.add('visible'));
await this.scrollTodoItemIntoView(card);
if (scrollIntoView) {
await this.scrollTodoItemIntoView(card);
}
await sleep(180);
}
private async scrollTodoItemIntoView(card: HTMLElement | null) {
if (!card || !this.elements.todoList) return;
const body = this.elements.todoList;
const targetTop = card.offsetTop - body.clientHeight * 0.2;
const clamped = Math.max(0, targetTop);
body.scrollTo({ top: clamped, behavior: 'smooth' });
await this.waitForScrollSettled(body, clamped);
const cardTop = card.offsetTop;
const cardBottom = cardTop + card.offsetHeight;
const viewTop = body.scrollTop;
const viewBottom = viewTop + body.clientHeight;
// 如果完整可见,直接返回
if (cardTop >= viewTop && cardBottom <= viewBottom) {
return;
}
// 让整个卡片进入视口:若底部被遮挡,滚到卡片底部露出;否则滚到卡片顶部
const targetTop =
cardBottom > viewBottom ? cardBottom - body.clientHeight + 4 : Math.max(0, cardTop - 4);
body.scrollTop = Math.max(0, targetTop);
await sleep(30);
}
private async toggleTodoItem(text?: string | null, done?: boolean) {
const card = this.findTodoItemByText(text);
private async toggleTodoItem(text?: string | null, done?: boolean, index?: number | null) {
const card =
this.findTodoItemByIndex(index) ||
this.findTodoItemByText(text) ||
(!text && !index ? this.getTodoItems()[0] : null);
if (!card) return;
await this.scrollTodoItemIntoView(card);
const check = card.querySelector('.todo-check') as HTMLElement | null;

View File

@ -1139,10 +1139,10 @@
/* Todo Window */
.virtual-monitor-surface .todo-window {
width: 520px;
height: 360px;
top: 180px;
left: 520px;
width: 360px;
height: 300px;
top: 110px;
left: 640px;
display: flex;
flex-direction: column;
}
@ -1153,6 +1153,7 @@
flex-direction: column;
gap: 12px;
padding: 12px;
overflow: hidden;
}
.virtual-monitor-surface .todo-summary {
@ -1168,13 +1169,16 @@
}
.virtual-monitor-surface .todo-progress {
flex: 1 1 auto;
flex: 1 1 0%;
min-height: 0;
max-height: 100%;
display: flex;
flex-direction: column;
gap: 10px;
overflow-y: auto;
height: 100%;
overflow-y: auto !important;
padding-right: 2px;
overscroll-behavior: contain;
}
.virtual-monitor-surface .todo-summary,