fix: render monitor desktop immediately on load
This commit is contained in:
parent
fce6fb0eb8
commit
fc88a22272
@ -228,13 +228,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useMonitorStore } from '@/stores/monitor';
|
||||
import { MonitorDirector, type MonitorElements } from '@/components/chat/monitor/MonitorDirector';
|
||||
|
||||
const monitorStore = useMonitorStore();
|
||||
const { statusLabel, progressIndicator } = storeToRefs(monitorStore);
|
||||
const { statusLabel, progressIndicator, lastTreeSnapshot } = storeToRefs(monitorStore);
|
||||
const currentProgressLabel = computed(() => progressIndicator.value?.label || '');
|
||||
|
||||
const screenEl = ref<HTMLElement | null>(null);
|
||||
@ -384,12 +384,27 @@ const mountDirector = async () => {
|
||||
const instance = new MonitorDirector(elements, assets);
|
||||
director.value = instance;
|
||||
monitorStore.registerDriver(instance);
|
||||
// 确保初次挂载或刷新后切换到监视器时桌面根目录立即渲染
|
||||
if (Array.isArray(monitorStore.lastTreeSnapshot) && monitorStore.lastTreeSnapshot.length) {
|
||||
instance.setDesktopRoots(monitorStore.lastTreeSnapshot, { immediate: true });
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
mountDirector();
|
||||
});
|
||||
|
||||
// 监听桌面根目录变化,确保文件/文件夹实时渲染(包括刷新后首帧)
|
||||
watch(
|
||||
lastTreeSnapshot,
|
||||
roots => {
|
||||
if (director.value && Array.isArray(roots)) {
|
||||
director.value.setDesktopRoots(roots, { immediate: true });
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
monitorStore.unregisterDriver();
|
||||
director.value?.destroy();
|
||||
|
||||
@ -195,6 +195,8 @@ export class MonitorDirector implements MonitorDriver {
|
||||
private desktopRoots: string[] = [];
|
||||
private screenRect: DOMRect;
|
||||
private pointerBase = { x: 60, y: 120 };
|
||||
private pendingPointerTransform: { x: number; y: number; duration: number } | null = null;
|
||||
private screenObserver: ResizeObserver | null = null;
|
||||
private bubbleTimer: number | null = null;
|
||||
private windowAnchors = new Map<HTMLElement, { x: number; y: number }>();
|
||||
private destroyFns: Array<() => void> = [];
|
||||
@ -254,6 +256,10 @@ export class MonitorDirector implements MonitorDriver {
|
||||
private refreshScreenRect() {
|
||||
const prev = this.screenRect;
|
||||
const rect = this.elements.screen.getBoundingClientRect();
|
||||
// 隐藏状态下 rect 可能为 0,避免把指针归零
|
||||
if (rect.width < 1 || rect.height < 1) {
|
||||
return;
|
||||
}
|
||||
this.screenRect = rect;
|
||||
if (prev && prev.width > 0 && prev.height > 0) {
|
||||
const relX = this.pointerBase.x / prev.width;
|
||||
@ -285,6 +291,7 @@ export class MonitorDirector implements MonitorDriver {
|
||||
this.setupScenes();
|
||||
this.bindTerminalInteractions();
|
||||
this.populateDesktop();
|
||||
this.setupScreenObserver();
|
||||
this.extractionAnchor = this.windowAnchors.get(this.elements.extractionWindow) || this.extractionAnchor;
|
||||
this.prepareExtractionTemplate();
|
||||
this.layoutFloatingWindows();
|
||||
@ -294,6 +301,7 @@ export class MonitorDirector implements MonitorDriver {
|
||||
const resizeHandler = () => {
|
||||
this.refreshScreenRect();
|
||||
this.layoutFloatingWindows();
|
||||
this.flushPendingPointerTransform();
|
||||
};
|
||||
window.addEventListener('resize', resizeHandler, { passive: true });
|
||||
this.destroyFns.push(() => window.removeEventListener('resize', resizeHandler));
|
||||
@ -312,7 +320,10 @@ export class MonitorDirector implements MonitorDriver {
|
||||
preservePointer?: boolean;
|
||||
preserveWindows?: boolean;
|
||||
}) {
|
||||
// 清理挂起的指针位置,避免后续尺寸变化时覆盖当前重置
|
||||
this.pendingPointerTransform = null;
|
||||
const preserveWindows = !!options?.preserveWindows;
|
||||
const preservePointer = options?.preservePointer !== false; // 默认保留指针位置
|
||||
this.cancelManualDrag();
|
||||
if (!preserveWindows) {
|
||||
this.resetManualPositions();
|
||||
@ -327,7 +338,7 @@ export class MonitorDirector implements MonitorDriver {
|
||||
} else {
|
||||
this.stopBubbleTimers();
|
||||
}
|
||||
if (!options?.preservePointer) {
|
||||
if (!preservePointer) {
|
||||
this.pointerBase = { x: 60, y: 120 };
|
||||
this.elements.mousePointer.style.transform = 'translate3d(60px, 120px, 0)';
|
||||
}
|
||||
@ -357,6 +368,9 @@ export class MonitorDirector implements MonitorDriver {
|
||||
}
|
||||
if (Array.isArray(options?.desktopRoots) && options?.desktopRoots.length) {
|
||||
this.setDesktopRoots(options.desktopRoots, { immediate: true });
|
||||
} else if (this.desktopRoots.length) {
|
||||
// 保持当前根目录重新渲染,避免在重置后桌面空白
|
||||
this.setDesktopRoots(this.desktopRoots, { immediate: true });
|
||||
}
|
||||
this.renderTerminalTabs();
|
||||
}
|
||||
@ -724,6 +738,10 @@ export class MonitorDirector implements MonitorDriver {
|
||||
}
|
||||
|
||||
private positionBubble() {
|
||||
// 隐藏状态(display: none)时不更新气泡与指针,避免坐标被重置到原点
|
||||
if (this.elements.screen.clientWidth < 1 || this.elements.screen.clientHeight < 1) {
|
||||
return;
|
||||
}
|
||||
const bubble = this.elements.speechBubble;
|
||||
const bubbleRect = bubble.getBoundingClientRect();
|
||||
const width = bubbleRect.width || 220;
|
||||
@ -1306,9 +1324,12 @@ export class MonitorDirector implements MonitorDriver {
|
||||
await this.focusCommandInput();
|
||||
const toDelete = this.commandCurrentText;
|
||||
if (toDelete) {
|
||||
for (let i = toDelete.length; i > 0; i -= 1) {
|
||||
const len = toDelete.length;
|
||||
const boost = 1 + Math.min(len / 40, 6); // 行越长删除越快
|
||||
const interval = Math.max(6, Math.floor(18 / boost));
|
||||
for (let i = len; i > 0; i -= 1) {
|
||||
target.textContent = toDelete.slice(0, i - 1);
|
||||
await sleep(18);
|
||||
await sleep(interval);
|
||||
}
|
||||
this.commandCurrentText = '';
|
||||
}
|
||||
@ -2533,11 +2554,14 @@ export class MonitorDirector implements MonitorDriver {
|
||||
execution: payload?.executionId || payload?.id,
|
||||
path: rawPath
|
||||
});
|
||||
const targetEntry = await this.revealFileTarget(rawPath, { spawnDesktopFile: true });
|
||||
if (targetEntry?.element) {
|
||||
await this.openFileMenuAction(targetEntry.element, 'edit');
|
||||
} else {
|
||||
await this.movePointerToDesktop();
|
||||
const editorAlreadyVisible = this.isWindowVisible(this.elements.editorWindow);
|
||||
if (!editorAlreadyVisible) {
|
||||
const targetEntry = await this.revealFileTarget(rawPath, { spawnDesktopFile: true });
|
||||
if (targetEntry?.element) {
|
||||
await this.openFileMenuAction(targetEntry.element, 'edit');
|
||||
} else {
|
||||
await this.movePointerToDesktop();
|
||||
}
|
||||
}
|
||||
this.openEditorWindow(filename);
|
||||
this.renderEditorPlaceholder('正在读取文件内容...');
|
||||
@ -3409,6 +3433,9 @@ export class MonitorDirector implements MonitorDriver {
|
||||
return;
|
||||
}
|
||||
this.refreshScreenRect();
|
||||
if (this.elements.screen.clientWidth < 1 || this.elements.screen.clientHeight < 1) {
|
||||
return;
|
||||
}
|
||||
this.raiseWindowForTarget(target);
|
||||
if (!this.progressBubbleBase) {
|
||||
this.dismissBubble(true);
|
||||
@ -3419,6 +3446,9 @@ export class MonitorDirector implements MonitorDriver {
|
||||
}
|
||||
const { offsetX = 0, offsetY = 0, duration = 900 } = options;
|
||||
const rect = target.getBoundingClientRect();
|
||||
if (rect.width < 1 && rect.height < 1) {
|
||||
return;
|
||||
}
|
||||
const desiredX = rect.left - this.screenRect.left + rect.width / 2 + offsetX;
|
||||
const desiredY = rect.top - this.screenRect.top + rect.height / 2 + offsetY;
|
||||
const pointerX = desiredX - POINTER_TIP_OFFSET.x;
|
||||
@ -3494,9 +3524,26 @@ export class MonitorDirector implements MonitorDriver {
|
||||
};
|
||||
}
|
||||
|
||||
private flushPendingPointerTransform() {
|
||||
if (!this.pendingPointerTransform) {
|
||||
return;
|
||||
}
|
||||
const pending = this.pendingPointerTransform;
|
||||
this.pendingPointerTransform = null;
|
||||
this.updatePointerTransform(pending.x, pending.y, pending.duration);
|
||||
}
|
||||
|
||||
private updatePointerTransform(x: number, y: number, duration = 0) {
|
||||
const clampedX = Math.min(Math.max(0, x), Math.max(0, this.elements.screen.clientWidth - 10));
|
||||
const clampedY = Math.min(Math.max(0, y), Math.max(0, this.elements.screen.clientHeight - 10));
|
||||
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
||||
return;
|
||||
}
|
||||
const screenWidth = this.elements.screen.clientWidth || this.screenRect?.width || 0;
|
||||
const screenHeight = this.elements.screen.clientHeight || this.screenRect?.height || 0;
|
||||
if (screenWidth < 1 || screenHeight < 1) {
|
||||
return;
|
||||
}
|
||||
const clampedX = Math.min(Math.max(0, x), Math.max(0, screenWidth - 10));
|
||||
const clampedY = Math.min(Math.max(0, y), Math.max(0, screenHeight - 10));
|
||||
this.elements.mousePointer.style.setProperty('--mouse-duration', `${Math.max(duration, 0)}ms`);
|
||||
this.elements.mousePointer.style.transform = `translate3d(${clampedX}px, ${clampedY}px, 0)`;
|
||||
this.pointerBase = { x: clampedX, y: clampedY };
|
||||
@ -3511,6 +3558,24 @@ export class MonitorDirector implements MonitorDriver {
|
||||
};
|
||||
}
|
||||
|
||||
private setupScreenObserver() {
|
||||
if (typeof ResizeObserver === 'undefined') {
|
||||
return;
|
||||
}
|
||||
this.screenObserver = new ResizeObserver(entries => {
|
||||
const entry = Array.isArray(entries) ? entries[0] : null;
|
||||
const width = entry?.contentRect?.width || this.elements.screen.clientWidth;
|
||||
const height = entry?.contentRect?.height || this.elements.screen.clientHeight;
|
||||
if (width < 1 || height < 1) {
|
||||
return;
|
||||
}
|
||||
this.refreshScreenRect();
|
||||
this.flushPendingPointerTransform();
|
||||
});
|
||||
this.screenObserver.observe(this.elements.screen);
|
||||
this.destroyFns.push(() => this.screenObserver?.disconnect());
|
||||
}
|
||||
|
||||
private showWindow(el: HTMLElement) {
|
||||
el.classList.remove('closing');
|
||||
el.classList.add('visible');
|
||||
@ -4230,15 +4295,30 @@ export class MonitorDirector implements MonitorDriver {
|
||||
private async animateEditorTransition(nextLines: any) {
|
||||
const currentLines = this.editorScene.lines.slice();
|
||||
const targetLines = this.sanitizeEditorLines(nextLines);
|
||||
const lineCount = targetLines.length;
|
||||
editorDebug('animate:start', {
|
||||
currentLines: currentLines.length,
|
||||
targetLines: targetLines.length
|
||||
targetLines: lineCount
|
||||
});
|
||||
if (!currentLines.length && !targetLines.length) {
|
||||
editorDebug('animate:both-empty');
|
||||
this.renderEditorPlaceholder('(文件当前为空)');
|
||||
return;
|
||||
}
|
||||
// 规则:
|
||||
// - > 30 行:直接瞬间渲染
|
||||
if (lineCount > 30) {
|
||||
editorDebug('animate:skip-gt-30', { lineCount });
|
||||
this.renderEditorSnapshot(targetLines);
|
||||
return;
|
||||
}
|
||||
// - 9~30 行:逐行动画(不逐字符)
|
||||
if (lineCount > 8) {
|
||||
editorDebug('animate:line-fill', { lineCount });
|
||||
await this.animateEditorLineFill(targetLines);
|
||||
return;
|
||||
}
|
||||
// - ≤8 行:保留原逐字符/行内阈值逻辑
|
||||
if (
|
||||
currentLines.length > EDITOR_DIFF_LIMIT ||
|
||||
targetLines.length > EDITOR_DIFF_LIMIT
|
||||
@ -4296,6 +4376,35 @@ export class MonitorDirector implements MonitorDriver {
|
||||
this.editorSpeedBoost = 1;
|
||||
}
|
||||
|
||||
private async animateEditorLineFill(lines: string[]) {
|
||||
const normalized = this.sanitizeEditorLines(lines);
|
||||
if (!normalized.length) {
|
||||
this.renderEditorPlaceholder('(文件当前为空)');
|
||||
return;
|
||||
}
|
||||
const container = this.elements.editorBody;
|
||||
container.innerHTML = '';
|
||||
// 先放空行占位,再逐行填充文本,避免逐字符动画
|
||||
normalized.forEach((_, index) => {
|
||||
const row = this.buildEditorLineElement('', index, undefined, { prefill: false });
|
||||
container.appendChild(row);
|
||||
});
|
||||
container.scrollTo({ top: 0 });
|
||||
const delay = 28;
|
||||
const rows = Array.from(container.children) as HTMLElement[];
|
||||
for (let i = 0; i < rows.length; i += 1) {
|
||||
const row = rows[i];
|
||||
row.textContent = normalized[i] || ' ';
|
||||
row.classList.add('visible');
|
||||
this.adjustEditorScrollForLine(row);
|
||||
await sleep(delay);
|
||||
}
|
||||
this.editorScene.lines = normalized.slice();
|
||||
this.editorScene.placeholder = false;
|
||||
this.syncEditorIndices();
|
||||
this.editorSpeedBoost = 1;
|
||||
}
|
||||
|
||||
private buildEditorDiff(before: string[], after: string[]): EditorOperation[] {
|
||||
const m = before.length;
|
||||
const n = after.length;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user