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