Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions src/app/elements/replay/asciicast/asciicast.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,24 +48,26 @@
</div>

<!-- 命令列表 -->
<div class="commands-area" *ngIf="commands.length > 0">
<div class="commands-header">
<span>{{ "Commands" | translate }} ({{ commands.length }})</span>
</div>
<div class="commands-list"
infiniteScroll
[infiniteScrollDistance]="2"
[infiniteScrollThrottle]="50"
(scrolled)="onScroll()"
[scrollWindow]="false">
<div class="command-item"
*ngFor="let item of commands"
(click)="commandClick(item)"
[title]="item.input">
<div class="command-text">{{ item.input }}</div>
<div class="command-time">{{ item.atime }}</div>
@if (commands && commands.length > 0) {
<div class="commands-area">
<div class="commands-header">
<span>{{ "Commands" | translate }} ({{ commands.length }})</span>
</div>
<div class="commands-list"
infiniteScroll
[infiniteScrollDistance]="2"
[infiniteScrollThrottle]="50"
(scrolled)="onScroll()"
[scrollWindow]="false">
<div class="command-item"
*ngFor="let item of commands"
(click)="commandClick(item)"
[title]="item.input">
<div class="command-text">{{ item.input }}</div>
<div class="command-time">{{ item.atime }}</div>
</div>
</div>
</div>
</div>
}
</div>
</div>
65 changes: 52 additions & 13 deletions src/app/elements/replay/asciicast/asciicast.component.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { AfterViewInit, Component, HostListener, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { filter } from 'rxjs';
import { HttpService } from '@app/services';
import { Command, Replay } from '@app/model';
import { formatTime } from '@app/utils/common';
import { HttpService } from '@app/services';
import { filter } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { AfterViewInit, Component, HostListener, Input, OnInit } from '@angular/core';

@Component({
standalone: false,
Expand All @@ -22,12 +22,13 @@ export class ElementReplayAsciicastComponent implements OnInit, AfterViewInit {
rows: number;
duration: string;
position: string;
timer: any; // 多长时间播放下一个
timer: any;
startTime = null;
startTimeStamp = null;
commands: Command[];
page = 0;
private AsciinemaPlayer: any;
private playerCreated = false;

constructor(
private route: ActivatedRoute,
Expand All @@ -37,10 +38,10 @@ export class ElementReplayAsciicastComponent implements OnInit, AfterViewInit {
}

async ngOnInit() {
// 动态加载 asciinema-player
await this.loadAsciinemaPlayer();

this.commands = new Array<Command>();

const start = new Date(this.replay.date_start);
const end = new Date(this.replay.date_end);
const duration = end.getTime() - start.getTime();
Expand All @@ -56,32 +57,49 @@ export class ElementReplayAsciicastComponent implements OnInit, AfterViewInit {
if (this.startAt >= duration) {
this.startAt = duration;
}
/* */
});
this.rows = Math.min(25, Math.floor((window.innerHeight - 120) / 16)); // 限制最大25行,调整计算方式
this.position = '00:00';
this.duration = formatTime(duration);
this.getCommands(this.page);

this.createPlayerWhenReady();
}

private async loadAsciinemaPlayer() {
try {
// 动态导入 asciinema-player
const module = await import('asciinema-player');

this.AsciinemaPlayer = module.default || module;
} catch (error) {
console.error('Failed to load asciinema-player:', error);
// 如果动态导入失败,尝试从全局变量获取(兼容性)

this.AsciinemaPlayer = (window as any).AsciinemaPlayer;
}
}

ngAfterViewInit() {
this.createPlayerWhenReady();
}

private createPlayerWhenReady() {
if (this.playerCreated) {
return;
}

if (this.AsciinemaPlayer) {
this.player = this.createPlayer();
this.player.play();
this.isPlaying = true;
this.createTimer();

if (this.player) {
this.player.play();
this.isPlaying = true;
this.createTimer();
this.playerCreated = true;
} else {
console.error('Player creation failed');
}
} else {
setTimeout(() => this.createPlayerWhenReady(), 100);
}
}

Expand Down Expand Up @@ -176,9 +194,28 @@ export class ElementReplayAsciicastComponent implements OnInit, AfterViewInit {
console.error('AsciinemaPlayer not loaded');
return null;
}

const el = document.getElementById('screen');

if (!el) {
console.error('Screen element not found');
return null;
}

if (!this.replay?.src) {
console.error('Replay src not available');
return null;
}

const opt = this.getPlayerOptions();
return this.AsciinemaPlayer.create(this.replay.src, el, opt);

try {
const player = this.AsciinemaPlayer.create(this.replay.src, el, opt);
return player;
} catch (error) {
console.error('Error creating player:', error);
return null;
}
}

getUserLang() {
Expand All @@ -199,6 +236,7 @@ export class ElementReplayAsciicastComponent implements OnInit, AfterViewInit {
if (!this.startTimeStamp) {
return;
}

this._http.getCommandsData(this.replay.id, page).subscribe(
data => {
const results = data.results;
Expand All @@ -209,6 +247,7 @@ export class ElementReplayAsciicastComponent implements OnInit, AfterViewInit {
this.commands = this.commands.concat(results);
},
err => {
console.error('getCommandsData error:', err);
alert('没找到命令记录');
}
);
Expand Down
53 changes: 33 additions & 20 deletions src/app/pages/replay/replay.component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {HttpService, I18nService, LogService, SettingService} from '@app/services';
import {Asset, Replay, Session, User} from '@app/model';
import {getWaterMarkContent} from '@app/utils/common';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { HttpService, I18nService, LogService, SettingService } from '@app/services';
import { Asset, Replay, Session, User } from '@app/model';
import { getWaterMarkContent } from '@app/utils/common';

@Component({
standalone: false,
Expand All @@ -18,11 +18,13 @@ export class PagesReplayComponent implements OnInit {
sessionUser: User;
loading: boolean = true;

constructor(private route: ActivatedRoute,
private _http: HttpService,
private _settingSvc: SettingService,
private _i18n: I18nService,
private _logger: LogService) {
constructor(
private route: ActivatedRoute,
private _http: HttpService,
private _settingSvc: SettingService,
private _i18n: I18nService,
private _logger: LogService
) {
this.getCurrentUser();
}

Expand All @@ -34,12 +36,21 @@ export class PagesReplayComponent implements OnInit {

async ngOnInit() {
let sid = '';

this.route.params.subscribe(params => {
sid = params['sid'];
});

this.session = await this._http.getSessionDetail(sid);
this.asset = await this._http.getAssetDetail(this.session.asset_id).toPromise();

try {
this.asset = await this._http.getAssetDetail(this.session.asset_id).toPromise();
} catch (error) {
this.asset = new Asset();
}

this.sessionUser = await this._http.getUserDetail(this.session.user_id);

const interval = setInterval(() => {
this._http.getReplay(sid).subscribe(
data => {
Expand All @@ -55,11 +66,13 @@ export class PagesReplayComponent implements OnInit {
clearInterval(interval);
this.loading = false;
const auditorUser = `${this._i18n.instant('Viewer')}: ${this.user.name}(${this.user.username})`;
const sessionContent = getWaterMarkContent(this.sessionUser, this.asset, this._settingSvc);
const content = `${auditorUser}\n${sessionContent}`;
this._settingSvc.createWaterMarkIfNeed(
document.body, `${content}`
const sessionContent = getWaterMarkContent(
this.sessionUser,
this.asset,
this._settingSvc
);
const content = `${auditorUser}\n${sessionContent}`;
this._settingSvc.createWaterMarkIfNeed(document.body, `${content}`);
}
},
err => {
Expand All @@ -78,11 +91,11 @@ export class PagesReplayComponent implements OnInit {

const tp = this.replay.type;
const supportedType = {
'json': true,
'guacamole': true,
'asciicast': true,
'mp4': true,
'parts': true
json: true,
guacamole: true,
asciicast: true,
mp4: true,
parts: true
};
return !supportedType[tp];
}
Expand Down