Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add tiling window manager for training mode #1150

Merged
merged 2 commits into from
Nov 27, 2023
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
6 changes: 6 additions & 0 deletions frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,10 @@ import { ProjectOverviewComponent } from './projects/project-overview/project-ov
import { ProjectWrapperComponent } from './projects/project-wrapper/project-wrapper.component';
import { WhitespaceUrlInterceptor } from './services/encoder/encoder.interceptor';
import { DeleteSessionDialogComponent } from './sessions/delete-session-dialog/delete-session-dialog.component';
import { FloatingWindowManagerComponent } from './sessions/session/floating-window-manager/floating-window-manager.component';
import { SessionIFrameComponent } from './sessions/session/session-iframe/session-iframe.component';
import { SessionComponent } from './sessions/session/session.component';
import { TilingWindowManagerComponent } from './sessions/session/tiling-window-manager/tiling-window-manager.component';
import { SessionOverviewComponent } from './sessions/session-overview/session-overview.component';
import { SessionsComponent } from './sessions/sessions.component';
import { ActiveSessionsComponent } from './sessions/user-sessions-wrapper/active-sessions/active-sessions.component';
Expand Down Expand Up @@ -172,6 +175,7 @@ import { SettingsComponent } from './settings/settings.component';
EventsComponent,
FileBrowserDialogComponent,
FileExistsDialogComponent,
FloatingWindowManagerComponent,
FooterComponent,
FormFieldSkeletonLoaderComponent,
GitSettingsComponent,
Expand Down Expand Up @@ -211,6 +215,7 @@ import { SettingsComponent } from './settings/settings.component';
ProjectWrapperComponent,
PureVariantsComponent,
SessionComponent,
SessionIFrameComponent,
SessionOverviewComponent,
SessionsComponent,
SettingsComponent,
Expand All @@ -220,6 +225,7 @@ import { SettingsComponent } from './settings/settings.component';
T4CSettingsComponent,
T4CSettingsWrapperComponent,
TextLineSkeletonLoaderComponent,
TilingWindowManagerComponent,
ToolDeletionDialogComponent,
ToolDetailsComponent,
ToolIntegrationsComponent,
Expand Down
4 changes: 0 additions & 4 deletions frontend/src/app/schemes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { SafeResourceUrl } from '@angular/platform-browser';
import { User } from 'src/app/services/user/user.service';
import { ToolVersionWithTool } from 'src/app/settings/core/tools-settings/tool.service';
import { Project } from './projects/service/project.service';
Expand All @@ -24,9 +23,6 @@ export interface Session {
owner: User;
t4c_password: string;
download_in_progress: boolean;
safeResourceURL?: SafeResourceUrl;
focused: boolean;
reloadToResize: boolean;
}

export interface ReadonlySession extends Session {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!--
MoritzWeber0 marked this conversation as resolved.
Show resolved Hide resolved
~ SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
~ SPDX-License-Identifier: Apache-2.0
-->

<ng-container *ngIf="sessionViewerService.sessions$ | async as viewerSessions">
<div class="flex h-full gap-0.5">
<div
class="flex w-full flex-col active:z-30"
[ngClass]="{
'z-20': session.focused,
'z-10': !session.focused
}"
(click)="sessionViewerService.focusSession(session)"
*ngFor="let session of viewerSessions; trackBy: trackBySessionId"
cdkDrag
(cdkDragStarted)="dragStart()"
(cdkDragEnded)="dragStop()"
>
<div
class="flex cursor-grab items-center justify-between gap-2 rounded-t p-1 active:cursor-grabbing"
[ngClass]="session.focused ? 'bg-slate-100' : 'bg-slate-300'"
cdkDragHandle
>
<div class="flex items-center gap-2">
<mat-icon>control_camera</mat-icon>
<span>
{{ session.version?.tool?.name }} {{ session.version?.name }},
{{ session.type }}
<span *ngIf="session.type === 'readonly'">
(project {{ session.project!.name }})
</span>
</span>
</div>

<div>
<div *ngIf="session.focused" class="flex items-center gap-2">
<span>Focused</span><mat-icon>phonelink</mat-icon>
</div>
<div *ngIf="!session.focused" class="flex items-center gap-2">
<span>Not focused</span>
<mat-icon>phonelink_off</mat-icon>
</div>
</div>

<button
class="m-0 flex items-center"
(click)="sessionViewerService.toggleFullscreen(session)"
>
<mat-icon *ngIf="!session.fullscreen">fullscreen</mat-icon>
<mat-icon *ngIf="session.fullscreen">fullscreen_exit</mat-icon>
</button>
</div>

<app-session-iframe class="flex h-full" [session]="session" />
</div>
</div>
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { Component, OnInit } from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { debounceTime, fromEvent } from 'rxjs';
import { SessionViewerService, ViewerSession } from '../session-viewer.service';

@Component({
selector: 'app-floating-window-manager',
templateUrl: './floating-window-manager.component.html',
})
@UntilDestroy()
export class FloatingWindowManagerComponent implements OnInit {
draggingActive = false;

constructor(public sessionViewerService: SessionViewerService) {}

ngOnInit(): void {
fromEvent(window, 'resize')
.pipe(untilDestroyed(this), debounceTime(250))
.subscribe(() => this.sessionViewerService.resizeSessions());
}

dragStart(): void {
this.draggingActive = true;
}

dragStop(): void {
this.draggingActive = false;
}

trackBySessionId(_: number, session: ViewerSession): string {
return session.id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
* SPDX-License-Identifier: Apache-2.0
*/

.iframe-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;

z-index: 40;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!--
~ SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
~ SPDX-License-Identifier: Apache-2.0
-->

<div class="relative grow">
<div
*ngIf="!session.focused"
class="iframe-overlay bg-black opacity-10"
></div>
<iframe
[title]="
(session.version?.tool?.name ?? 'unknown tool name') +
'-' +
(session.version?.tool?.name ?? 'unknown tool name') +
'-' +
session.type
"
[id]="'session-' + session.id"
[src]="session.safeResourceURL"
class="h-full w-full"
allow="clipboard-read; clipboard-write"
>
</iframe>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { Component, Input } from '@angular/core';
import { ViewerSession } from '../session-viewer.service';

@Component({
selector: 'app-session-iframe',
templateUrl: './session-iframe.component.html',
styleUrls: ['./session-iframe.component.css'],
})
export class SessionIFrameComponent {
@Input({ required: true }) session!: ViewerSession;
}
140 changes: 140 additions & 0 deletions frontend/src/app/sessions/session/session-viewer.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { Injectable } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { BehaviorSubject, map } from 'rxjs';
import { LocalStorageService } from 'src/app/general/auth/local-storage/local-storage.service';
import { Session } from 'src/app/schemes';
import { GuacamoleService } from 'src/app/services/guacamole/guacamole.service';

@Injectable({
providedIn: 'root',
})
export class SessionViewerService {
constructor(
private guacamoleService: GuacamoleService,
private localStorageService: LocalStorageService,
private domSanitizer: DomSanitizer,
) {}

private _sessions = new BehaviorSubject<ViewerSession[] | undefined>(
undefined,
);

public readonly sessions$ = this._sessions.pipe(
map((sessions) => {
const fullscreenSession = sessions?.find((session) => session.fullscreen);
return fullscreenSession ? [fullscreenSession] : sessions;
}),
);

pushJupyterSession(session: Session): void {
if (session.jupyter_uri) {
const viewerSession = session as ViewerSession;

viewerSession.focused = false;
viewerSession.safeResourceURL =
this.domSanitizer.bypassSecurityTrustResourceUrl(session.jupyter_uri);
viewerSession.reloadToResize = false;

this.insertViewerSession(viewerSession);
}
}

pushGuacamoleSession(session: Session): void {
const viewerSession = session as ViewerSession;

this.guacamoleService.getGucamoleToken(session.id).subscribe({
next: (guacamoleAuthInfo) => {
this.localStorageService.setValue('GUAC_AUTH', guacamoleAuthInfo.token);

viewerSession.focused = false;
viewerSession.safeResourceURL =
this.domSanitizer.bypassSecurityTrustResourceUrl(
guacamoleAuthInfo.url,
);
viewerSession.reloadToResize = true;

this.insertViewerSession(viewerSession);
},
});
}

focusSession(session: Session): void {
document.getElementById('session-' + session.id)?.focus();

const updatedSessions = this._sessions.value?.map((curSession) => ({
...curSession,
focused: session.id === curSession.id,
}));

this._sessions.next(updatedSessions);
}

resizeSessions(): void {
document.querySelectorAll('iframe').forEach((iframe) => {
const session = this._sessions.value?.find(
(session) => `session-${session.id}` === iframe.id,
);

if (session?.reloadToResize) {
this.reloadIFrame(iframe);
}
});
}

resizeSession(session: ViewerSession): void {
const sessionIFrame = document.querySelector<HTMLIFrameElement>(
`iframe#session-${session.id}`,
);

if (sessionIFrame && session.reloadToResize) {
this.reloadIFrame(sessionIFrame);
}
}

toggleFullscreen(session: ViewerSession): void {
const updatedSessions = this._sessions.value?.map((curSession) => {
if (session.id === curSession.id) {
return { ...curSession, fullscreen: !curSession.fullscreen };
}
return curSession;
});
this._sessions.next(updatedSessions);
this.resizeSession(session);
}

clearSessions(): void {
this._sessions.next(undefined);
}

private reloadIFrame(iframe: HTMLIFrameElement) {
const src = iframe.src;

iframe.removeAttribute('src');

setTimeout(() => {
iframe.src = src;
}, 100);
}

private insertViewerSession(viewerSession: ViewerSession): void {
const currentSessions = this._sessions.value;

if (currentSessions === undefined) {
this._sessions.next([viewerSession]);
} else {
this._sessions.next([...currentSessions, viewerSession]);
}
}
}

export type ViewerSession = Session & {
safeResourceURL?: SafeResourceUrl;
focused: boolean;
reloadToResize: boolean;
fullscreen: boolean;
};
10 changes: 0 additions & 10 deletions frontend/src/app/sessions/session/session.component.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,3 @@
.height {
height: calc(100vh - 2vh - 65px - 110px);
}

.iframe-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;

z-index: 40;
}
Loading
Loading