Skip to content

Commit 4cd64bb

Browse files
committed
perf: Optimize the timer display
1 parent e59d8e5 commit 4cd64bb

3 files changed

Lines changed: 215 additions & 25 deletions

File tree

src/app/pages/connect/connect.component.html

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
<app-face-monitor *ngIf="isAdminConnect"></app-face-monitor>
22
<elements-chat></elements-chat>
33

4+
<ng-template #timerWidget>
5+
<div
6+
*ngIf="isTimerVisible"
7+
class="timer-container"
8+
[class.dragging]="isTimerDragging"
9+
[ngStyle]="getTimerContainerStyle()"
10+
(pointerdown)="onTimerPointerDown($event)"
11+
(pointermove)="onTimerPointerMove($event)"
12+
(pointerup)="onTimerPointerUp($event)"
13+
(pointercancel)="onTimerPointerUp($event)"
14+
>
15+
<div class="timer">
16+
<div [class.active]="isActive" class="status-dot"></div>
17+
<span class="time-value">{{ totalConnectTime }}</span>
18+
<span
19+
class="timer-close"
20+
nz-icon
21+
nzType="close"
22+
nzTheme="outline"
23+
(pointerdown)="$event.stopPropagation()"
24+
(click)="hideTimer()"
25+
></span>
26+
</div>
27+
</div>
28+
</ng-template>
29+
430
<div #contentWindow *ngIf="isDirect">
531
<div class="terminal-connect" *ngIf="view && isTerminalComponent()">
632
<div *ngIf="isWebSftp()" style="height: 100%; width: 100%">
@@ -55,12 +81,7 @@
5581
</div>
5682
</div> -->
5783
</div>
58-
<div class="timer-container">
59-
<div class="timer">
60-
<div [class.active]="isActive" class="status-dot"></div>
61-
<span>{{ totalConnectTime }}</span>
62-
</div>
63-
</div>
84+
<ng-container *ngTemplateOutlet="timerWidget"></ng-container>
6485
<nz-card class="terminal-card" [nzBordered]="false">
6586
<div class="card-content" style="height: 100%; overflow: hidden">
6687
<elements-content-window #contentWindow *ngIf="view" [view]="view">
@@ -71,12 +92,7 @@
7192
</div>
7293

7394
<div class="gui" *ngIf="view && isGuiComponent()">
74-
<div class="timer-container">
75-
<div class="timer">
76-
<div [class.active]="isActive" class="status-dot"></div>
77-
<span>{{ totalConnectTime }}</span>
78-
</div>
79-
</div>
95+
<ng-container *ngTemplateOutlet="timerWidget"></ng-container>
8096

8197
<div class="action-icons" [class.show]="showActionIcons">
8298
<div class="close-icon" (click)="handleCloseConnect()">
@@ -156,12 +172,7 @@
156172
</div>
157173
</div>
158174
</div>
159-
<div class="timer-container">
160-
<div class="timer">
161-
<div [class.active]="isActive" class="status-dot"></div>
162-
<span>{{ totalConnectTime }}</span>
163-
</div>
164-
</div>
175+
<ng-container *ngTemplateOutlet="timerWidget"></ng-container>
165176
<nz-card class="terminal-card" [nzBordered]="false">
166177
<div class="card-content" style="height: 100%; overflow: hidden">
167178
<elements-connect
@@ -176,12 +187,7 @@
176187
</div>
177188

178189
<div class="gui" *ngIf="view && isGuiComponent()">
179-
<div class="timer-container">
180-
<div class="timer">
181-
<div [class.active]="isActive" class="status-dot"></div>
182-
<span>{{ totalConnectTime }}</span>
183-
</div>
184-
</div>
190+
<ng-container *ngTemplateOutlet="timerWidget"></ng-container>
185191

186192
<div class="action-icons" [class.show]="showActionIcons">
187193
<div class="close-icon" (click)="handleCloseConnect()">

src/app/pages/connect/connect.component.scss

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
bottom: 40px;
44
right: 30px;
55
z-index: 999999;
6+
touch-action: none;
7+
8+
&.dragging {
9+
.timer {
10+
cursor: grabbing;
11+
}
12+
}
613

714
.timer {
815
display: flex;
@@ -14,8 +21,10 @@
1421
font-family: monospace;
1522
font-size: 14px;
1623
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
24+
cursor: grab;
25+
user-select: none;
1726

18-
span {
27+
.time-value {
1928
margin-left: 4px;
2029
}
2130

@@ -31,6 +40,23 @@
3140
animation: blink 1s infinite;
3241
}
3342
}
43+
44+
.timer-close {
45+
display: inline-flex;
46+
align-items: center;
47+
justify-content: center;
48+
margin-left: 8px;
49+
width: 18px;
50+
height: 18px;
51+
border-radius: 50%;
52+
cursor: pointer;
53+
color: rgba(255, 255, 255, 0.9);
54+
55+
&:hover {
56+
background: rgba(255, 255, 255, 0.15);
57+
color: rgba(255, 255, 255, 1);
58+
}
59+
}
3460
}
3561

3662
@media screen and (max-width: 768px) {

src/app/pages/connect/connect.component.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,17 @@ export class PagesConnectComponent implements OnInit, OnDestroy {
3333
public isActive: boolean = true;
3434
public isTimerStopped: boolean = false;
3535
public showActionIcons: boolean = false;
36+
public isTimerVisible: boolean = true;
37+
public isTimerDragging: boolean = false;
38+
public timerPosition: { x: number; y: number } | null = null;
3639
private readonly guiComponents: Set<string> = new Set(['lion', 'tinker', 'razor', 'panda']);
3740
private readonly terminalComponents: Set<string> = new Set(['koko', 'chen', 'magnus', 'nec']);
41+
private readonly timerPositionStorageKey = 'luna.connect.timer.position';
42+
private timerPointerId: number | null = null;
43+
private timerDragStart: { x: number; y: number } | null = null;
44+
private timerDragOffset: { x: number; y: number } | null = null;
45+
private timerDragSize: { width: number; height: number } | null = null;
46+
private timerStartRect: { left: number; top: number } | null = null;
3847

3948
// 人脸在线相关
4049
private faceMonitorToken: string;
@@ -85,6 +94,8 @@ export class PagesConnectComponent implements OnInit, OnDestroy {
8594
async ngOnInit() {
8695
this.view = null;
8796
this.isTimerStopped = false;
97+
this.isTimerVisible = true;
98+
this.loadTimerPosition();
8899

89100
this.checkDirectMode();
90101

@@ -507,6 +518,153 @@ export class PagesConnectComponent implements OnInit, OnDestroy {
507518
this.showActionIcons = event.clientY <= 65;
508519
}
509520

521+
public hideTimer(): void {
522+
this.isTimerVisible = false;
523+
}
524+
525+
public getTimerContainerStyle(): Record<string, string> {
526+
if (!this.timerPosition) {
527+
return {};
528+
}
529+
return {
530+
left: `${this.timerPosition.x}px`,
531+
top: `${this.timerPosition.y}px`,
532+
right: 'auto',
533+
bottom: 'auto',
534+
transform: 'none'
535+
};
536+
}
537+
538+
public onTimerPointerDown(event: PointerEvent): void {
539+
if (event.pointerType === 'mouse' && event.button !== 0) {
540+
return;
541+
}
542+
543+
const timerContainer = event.currentTarget as HTMLElement | null;
544+
if (!timerContainer) {
545+
return;
546+
}
547+
548+
const rect = timerContainer.getBoundingClientRect();
549+
550+
this.timerPointerId = event.pointerId;
551+
this.timerDragStart = { x: event.clientX, y: event.clientY };
552+
this.timerDragOffset = { x: event.clientX - rect.left, y: event.clientY - rect.top };
553+
this.timerDragSize = { width: rect.width, height: rect.height };
554+
this.timerStartRect = { left: rect.left, top: rect.top };
555+
this.isTimerDragging = false;
556+
557+
timerContainer.setPointerCapture(event.pointerId);
558+
}
559+
560+
public onTimerPointerMove(event: PointerEvent): void {
561+
if (this.timerPointerId !== event.pointerId) {
562+
return;
563+
}
564+
if (
565+
!this.timerDragStart ||
566+
!this.timerDragOffset ||
567+
!this.timerDragSize ||
568+
!this.timerStartRect
569+
) {
570+
return;
571+
}
572+
573+
const dx = event.clientX - this.timerDragStart.x;
574+
const dy = event.clientY - this.timerDragStart.y;
575+
const dragThresholdPx = 3;
576+
577+
if (!this.isTimerDragging) {
578+
if (Math.abs(dx) < dragThresholdPx && Math.abs(dy) < dragThresholdPx) {
579+
return;
580+
}
581+
this.isTimerDragging = true;
582+
583+
if (!this.timerPosition) {
584+
this.timerPosition = { x: this.timerStartRect.left, y: this.timerStartRect.top };
585+
}
586+
}
587+
588+
event.preventDefault();
589+
590+
const maxX = Math.max(0, window.innerWidth - this.timerDragSize.width);
591+
const maxY = Math.max(0, window.innerHeight - this.timerDragSize.height);
592+
593+
const unclampedX = event.clientX - this.timerDragOffset.x;
594+
const unclampedY = event.clientY - this.timerDragOffset.y;
595+
596+
this.timerPosition = {
597+
x: Math.min(Math.max(0, unclampedX), maxX),
598+
y: Math.min(Math.max(0, unclampedY), maxY)
599+
};
600+
}
601+
602+
public onTimerPointerUp(event: PointerEvent): void {
603+
if (this.timerPointerId !== event.pointerId) {
604+
return;
605+
}
606+
607+
const timerContainer = event.currentTarget as HTMLElement | null;
608+
if (timerContainer) {
609+
try {
610+
timerContainer.releasePointerCapture(event.pointerId);
611+
} catch {}
612+
}
613+
614+
if (this.isTimerDragging) {
615+
this.persistTimerPosition();
616+
}
617+
618+
this.timerPointerId = null;
619+
this.timerDragStart = null;
620+
this.timerDragOffset = null;
621+
this.timerDragSize = null;
622+
this.timerStartRect = null;
623+
this.isTimerDragging = false;
624+
}
625+
626+
private loadTimerPosition(): void {
627+
try {
628+
const raw = localStorage.getItem(this.timerPositionStorageKey);
629+
if (!raw) {
630+
return;
631+
}
632+
const parsed = JSON.parse(raw) as { x?: unknown; y?: unknown; w?: unknown; h?: unknown };
633+
if (typeof parsed.x !== 'number' || typeof parsed.y !== 'number') {
634+
return;
635+
}
636+
637+
const width = typeof parsed.w === 'number' && parsed.w > 0 ? parsed.w : 0;
638+
const height = typeof parsed.h === 'number' && parsed.h > 0 ? parsed.h : 0;
639+
640+
const maxX = width > 0 ? Math.max(0, window.innerWidth - width) : window.innerWidth;
641+
const maxY = height > 0 ? Math.max(0, window.innerHeight - height) : window.innerHeight;
642+
643+
this.timerPosition = {
644+
x: Math.min(Math.max(0, parsed.x), maxX),
645+
y: Math.min(Math.max(0, parsed.y), maxY)
646+
};
647+
} catch {}
648+
}
649+
650+
private persistTimerPosition(): void {
651+
if (!this.timerPosition) {
652+
return;
653+
}
654+
655+
try {
656+
localStorage.setItem(
657+
this.timerPositionStorageKey,
658+
JSON.stringify({
659+
x: this.timerPosition.x,
660+
y: this.timerPosition.y,
661+
w: this.timerDragSize?.width ?? 0,
662+
h: this.timerDragSize?.height ?? 0
663+
})
664+
);
665+
} catch {}
666+
}
667+
510668
/**
511669
* 开始计时器
512670
*/

0 commit comments

Comments
 (0)