diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index 99d8fcf77f..cbb6e5c5c8 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -63,6 +63,7 @@ import { VersionComponent } from './general/metadata/version/version.component';
import { NavBarMenuComponent } from './general/nav-bar-menu/nav-bar-menu.component';
import { NoticeComponent } from './general/notice/notice.component';
import { ConfirmationDialogComponent } from './helpers/confirmation-dialog/confirmation-dialog.component';
+import { DefaultValuePipe } from './helpers/default-value.pipe';
import { DisplayValueComponent } from './helpers/display-value/display-value.component';
import { InputDialogComponent } from './helpers/input-dialog/input-dialog.component';
import { MatIconComponent } from './helpers/mat-icon/mat-icon.component';
@@ -109,6 +110,7 @@ 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 { 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';
@@ -164,6 +166,7 @@ import { SettingsComponent } from './settings/settings.component';
CreateReadonlySessionComponent,
CreateReadonlySessionDialogComponent,
CreateT4cModelNewRepositoryComponent,
+ DefaultValuePipe,
DeleteGitSettingsDialogComponent,
DeleteSessionDialogComponent,
DisplayValueComponent,
@@ -222,6 +225,7 @@ import { SettingsComponent } from './settings/settings.component';
T4CSettingsComponent,
T4CSettingsWrapperComponent,
TextLineSkeletonLoaderComponent,
+ TilingWindowManagerComponent,
ToolDeletionDialogComponent,
ToolDetailsComponent,
ToolIntegrationsComponent,
diff --git a/frontend/src/app/helpers/default-value.pipe.ts b/frontend/src/app/helpers/default-value.pipe.ts
new file mode 100644
index 0000000000..ab31513185
--- /dev/null
+++ b/frontend/src/app/helpers/default-value.pipe.ts
@@ -0,0 +1,15 @@
+/*
+ * SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+ name: 'defaultValue',
+})
+export class DefaultValuePipe implements PipeTransform {
+ transform(value: string | undefined, defaultValue: string): string {
+ return value ?? defaultValue;
+ }
+}
diff --git a/frontend/src/app/schemes.ts b/frontend/src/app/schemes.ts
index 9e71d84612..cf37f6e06e 100644
--- a/frontend/src/app/schemes.ts
+++ b/frontend/src/app/schemes.ts
@@ -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';
@@ -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 {
diff --git a/frontend/src/app/sessions/session/floating-window-manager/floating-window-manager.component.html b/frontend/src/app/sessions/session/floating-window-manager/floating-window-manager.component.html
index f82e079b10..eb56d61071 100644
--- a/frontend/src/app/sessions/session/floating-window-manager/floating-window-manager.component.html
+++ b/frontend/src/app/sessions/session/floating-window-manager/floating-window-manager.component.html
@@ -3,57 +3,71 @@
~ SPDX-License-Identifier: Apache-2.0
-->
-
-
+
+
-
- control_camera
-
- {{ session.version?.tool?.name }} {{ session.version?.name }},
- {{ session.type }}
- (project {{ session.project!.name }})
-
-
-
- Focused phonelink
-
-
- Not focused
- phonelink_off
-
-
-
-
-
-
+
+ control_camera
+
+ {{ session.version?.tool?.name }} {{ session.version?.name }},
+ {{ session.type }}
+ (project {{ session.project!.name }})
+
+
+
+ Focused phonelink
+
+
+ Not focused
+ phonelink_off
+
+
+
+
-
+
diff --git a/frontend/src/app/sessions/session/floating-window-manager/floating-window-manager.component.ts b/frontend/src/app/sessions/session/floating-window-manager/floating-window-manager.component.ts
index dfdc2cb375..90ea9d8927 100644
--- a/frontend/src/app/sessions/session/floating-window-manager/floating-window-manager.component.ts
+++ b/frontend/src/app/sessions/session/floating-window-manager/floating-window-manager.component.ts
@@ -3,10 +3,11 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { Component, HostListener, Input, OnInit } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
-import { Session } from 'src/app/schemes';
+import { debounceTime, fromEvent } from 'rxjs';
import { FullscreenService } from '../../service/fullscreen.service';
+import { SessionViewerService } from '../session-viewer.service';
@Component({
selector: 'app-floating-window-manager',
@@ -15,31 +16,17 @@ import { FullscreenService } from '../../service/fullscreen.service';
})
@UntilDestroy()
export class FloatingWindowManagerComponent implements OnInit {
- @Input() sessions: Session[] = [];
-
draggingActive = false;
- private debounceTimer?: number;
-
- constructor(public fullscreenService: FullscreenService) {}
+ constructor(
+ public sessionViewerService: SessionViewerService,
+ public fullscreenService: FullscreenService,
+ ) {}
ngOnInit(): void {
- this.fullscreenService.isFullscreen$
- .pipe(untilDestroyed(this))
- .subscribe(() => this.resizeSessions());
- }
-
- focusSession(session: Session) {
- this.unfocusSession(session);
-
- document.getElementById('session-' + session.id)?.focus();
- session.focused = true;
- }
-
- unfocusSession(focusedSession: Session) {
- this.sessions
- .filter((session) => session !== focusedSession)
- .map((session) => (session.focused = false));
+ fromEvent(window, 'resize')
+ .pipe(untilDestroyed(this), debounceTime(250))
+ .subscribe(() => this.sessionViewerService.resizeSessions());
}
dragStart() {
@@ -49,35 +36,4 @@ export class FloatingWindowManagerComponent implements OnInit {
dragStop() {
this.draggingActive = false;
}
-
- @HostListener('window:resize', ['$event'])
- onResize() {
- window.clearTimeout(this.debounceTimer);
-
- this.debounceTimer = window.setTimeout(() => {
- this.resizeSessions();
- }, 250);
- }
-
- resizeSessions() {
- Array.from(document.getElementsByTagName('iframe')).forEach((iframe) => {
- const session = this.sessions.find(
- (session) => 'session-' + session.id === iframe.id,
- );
-
- if (session?.reloadToResize) {
- this.reloadIFrame(iframe);
- }
- });
- }
-
- reloadIFrame(iframe: HTMLIFrameElement) {
- const src = iframe.src;
-
- iframe.removeAttribute('src');
-
- setTimeout(() => {
- iframe.src = src;
- }, 100);
- }
}
diff --git a/frontend/src/app/sessions/session/session-viewer.service.ts b/frontend/src/app/sessions/session/session-viewer.service.ts
new file mode 100644
index 0000000000..1a6654cff0
--- /dev/null
+++ b/frontend/src/app/sessions/session/session-viewer.service.ts
@@ -0,0 +1,117 @@
+/*
+ * 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 } 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(
+ undefined,
+ );
+
+ public readonly sessions$ = this._sessions.asObservable();
+
+ 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() {
+ document.querySelectorAll('iframe').forEach((iframe) => {
+ const session = this._sessions.value?.find(
+ (session) => `session-${session.id}` === iframe.id,
+ );
+
+ if (session?.reloadToResize) {
+ this.reloadIFrame(iframe);
+ }
+ });
+ }
+
+ trackBySessionId(_: number, session: ViewerSession): string {
+ return session.id;
+ }
+
+ 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;
+};
diff --git a/frontend/src/app/sessions/session/session.component.html b/frontend/src/app/sessions/session/session.component.html
index 1485192ff7..e6a7b1ee58 100644
--- a/frontend/src/app/sessions/session/session.component.html
+++ b/frontend/src/app/sessions/session/session.component.html
@@ -3,7 +3,12 @@
~ SPDX-License-Identifier: Apache-2.0
-->
-
+
Please select the sessions that you'd like to open in the session
viewer: {{ session.version?.tool?.name }} {{ session.version?.name }},
{{ session.type
@@ -51,8 +56,10 @@
- Floating window
- Tailing window
+ Floating window manager
+ Tiling window manager
@@ -69,22 +76,31 @@
-
-
-
-
- fullscreen
+
+
- fullscreen_exit
+
+
+
+
+
+
+
+
+
-
-
+ fullscreen
+ fullscreen_exit
+
+
+
diff --git a/frontend/src/app/sessions/session/session.component.ts b/frontend/src/app/sessions/session/session.component.ts
index d2350426a7..6a110d378d 100644
--- a/frontend/src/app/sessions/session/session.component.ts
+++ b/frontend/src/app/sessions/session/session.component.ts
@@ -3,58 +3,58 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { Component, OnInit } from '@angular/core';
-import { MatLegacyCheckboxChange as MatCheckboxChange } from '@angular/material/legacy-checkbox';
-import { DomSanitizer } from '@angular/platform-browser';
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { filter, take } 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';
import { FullscreenService } from 'src/app/sessions/service/fullscreen.service';
import { SessionService } from 'src/app/sessions/service/session.service';
import { UserSessionService } from 'src/app/sessions/service/user-session.service';
+import { SessionViewerService } from './session-viewer.service';
@Component({
selector: 'app-session',
templateUrl: './session.component.html',
})
-export class SessionComponent implements OnInit {
+@UntilDestroy()
+export class SessionComponent implements OnInit, OnDestroy {
cachedSessions?: CachedSession[] = undefined;
- selectedSessions: Session[] = [];
selectedWindowType: string = 'floating';
constructor(
public userSessionService: UserSessionService,
public sessionService: SessionService,
+ public sessionViewerService: SessionViewerService,
public fullscreenService: FullscreenService,
- private guacamoleService: GuacamoleService,
- private localStorageService: LocalStorageService,
- private domSanitizer: DomSanitizer,
) {
this.userSessionService.loadSessions();
+
+ this.fullscreenService.isFullscreen$
+ .pipe(untilDestroyed(this))
+ .subscribe(() => this.sessionViewerService.resizeSessions());
}
get checkedSessions(): undefined | CachedSession[] {
return this.cachedSessions?.filter((session) => session.checked);
}
- get isTailingWindow(): boolean {
- return this.selectedWindowType === 'tailing';
+ get isTilingWindowManager(): boolean {
+ return this.selectedWindowType === 'tiling';
}
- get isFloatingWindow() {
+ get isFloatingWindowManager(): boolean {
return this.selectedWindowType === 'floating';
}
- changeSessionSelection(event: MatCheckboxChange, session: CachedSession) {
- session.checked = event.checked;
- }
-
ngOnInit(): void {
this.initializeCachedSessions();
}
+ ngOnDestroy(): void {
+ this.sessionViewerService.clearSessions();
+ }
+
initializeCachedSessions() {
this.userSessionService.sessions$
.pipe(
@@ -71,20 +71,10 @@ export class SessionComponent implements OnInit {
selectSessions() {
this.checkedSessions?.forEach((session) => {
- session.focused = false;
if (session.jupyter_uri) {
- session.safeResourceURL =
- this.domSanitizer.bypassSecurityTrustResourceUrl(session.jupyter_uri);
- session.reloadToResize = false;
- this.selectedSessions.push(session);
+ this.sessionViewerService.pushJupyterSession(session);
} else {
- this.guacamoleService.getGucamoleToken(session?.id).subscribe((res) => {
- this.localStorageService.setValue('GUAC_AUTH', res.token);
- session.safeResourceURL =
- this.domSanitizer.bypassSecurityTrustResourceUrl(res.url);
- session.reloadToResize = true;
- this.selectedSessions.push(session);
- });
+ this.sessionViewerService.pushGuacamoleSession(session);
}
});
}
diff --git a/frontend/src/app/sessions/session/tiling-window-manager/tiling-window-manager.component.css b/frontend/src/app/sessions/session/tiling-window-manager/tiling-window-manager.component.css
new file mode 100644
index 0000000000..782c0293cc
--- /dev/null
+++ b/frontend/src/app/sessions/session/tiling-window-manager/tiling-window-manager.component.css
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+.height {
+ height: calc(100vh - 2vh - 65px - 110px);
+}
+
+.iframe-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+
+ cursor: col-resize;
+}
diff --git a/frontend/src/app/sessions/session/tiling-window-manager/tiling-window-manager.component.html b/frontend/src/app/sessions/session/tiling-window-manager/tiling-window-manager.component.html
new file mode 100644
index 0000000000..bdc4aee489
--- /dev/null
+++ b/frontend/src/app/sessions/session/tiling-window-manager/tiling-window-manager.component.html
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+ {{ session.version?.tool?.name }} {{ session.version?.name }},
+ {{ session.type }}
+ (project {{ session.project!.name }})
+
+
+
+
+ arrow_back
+
+
+
+ arrow_forward
+
+
+
+ Focused phonelink
+
+
+ Not focused
+ phonelink_off
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/app/sessions/session/tiling-window-manager/tiling-window-manager.component.ts b/frontend/src/app/sessions/session/tiling-window-manager/tiling-window-manager.component.ts
new file mode 100644
index 0000000000..e0fdc65c55
--- /dev/null
+++ b/frontend/src/app/sessions/session/tiling-window-manager/tiling-window-manager.component.ts
@@ -0,0 +1,210 @@
+/*
+ * SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { Component, HostListener, OnInit } from '@angular/core';
+import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
+import { filter } from 'rxjs';
+import { FullscreenService } from '../../service/fullscreen.service';
+import { SessionViewerService, ViewerSession } from '../session-viewer.service';
+
+@Component({
+ selector: 'app-tiling-window-manager',
+ templateUrl: './tiling-window-manager.component.html',
+ styleUrls: ['./tiling-window-manager.component.css'],
+})
+@UntilDestroy()
+export class TilingWindowManagerComponent implements OnInit {
+ private _tilingSessions: TilingSession[] = [];
+
+ get sessions(): TilingSession[] {
+ return this._tilingSessions.sort((a, b) => a.index - b.index);
+ }
+
+ public resizeState: ResizeState = {};
+
+ private minimumSessionWidth = 0;
+ private existingMargin = 0;
+
+ constructor(
+ public sessionViewerService: SessionViewerService,
+ public fullscreenService: FullscreenService,
+ ) {}
+
+ ngOnInit(): void {
+ this.sessionViewerService.sessions$
+ .pipe(untilDestroyed(this), filter(Boolean))
+ .subscribe((viewerSessions) => {
+ this._tilingSessions = viewerSessions.map((session, index) => ({
+ ...session,
+ index: index,
+ width: -1,
+ fullscreen: false,
+ }));
+ this.resetWidths();
+ });
+
+ this.fullscreenService.isFullscreen$.subscribe((isFullscreen) => {
+ this.existingMargin = isFullscreen ? 0 : 27.48;
+ this.resetWidths();
+ });
+ }
+
+ onMouseDown(event: MouseEvent, index: number): void {
+ const leftSession = this.getSessionByIndex(index);
+ const rightSession = this.getSessionByIndex(index + 1);
+
+ if (leftSession && rightSession) {
+ this.resizeState = {
+ index: index,
+ startX: event.clientX,
+ leftSession: leftSession,
+ rightSession: rightSession,
+ startWidthLeft: leftSession.width,
+ startWidthRight: rightSession.width,
+ };
+ this.toggleIFrameEvents(false);
+ }
+ }
+
+ @HostListener('window:mousemove', ['$event'])
+ onMouseMove(event: MouseEvent): void {
+ if (this.isValidResizeState(this.resizeState)) {
+ const delta = event.clientX - this.resizeState.startX;
+ const [newWidthLeft, newWidthRight] = this.calculateNewWidths(
+ this.resizeState,
+ delta,
+ );
+
+ this.resizeState.leftSession.width = newWidthLeft;
+ this.resizeState.rightSession.width = newWidthRight;
+ }
+ }
+
+ @HostListener('window:mouseup')
+ onMouseUp(): void {
+ if (this.resizeState) {
+ this.resizeState = {};
+ this.sessionViewerService.resizeSessions();
+ this.toggleIFrameEvents(true);
+ }
+ }
+
+ @HostListener('window:resize')
+ onResize() {
+ this.resetWidths();
+ }
+
+ onLeftClick(session: TilingSession) {
+ const leftIndexSession = this.getSessionByIndex(session.index - 1);
+
+ if (leftIndexSession) {
+ this.swapSessionIndices(session, leftIndexSession);
+ }
+ }
+
+ onRightClick(session: TilingSession) {
+ const rightIndexSession = this.getSessionByIndex(session.index + 1);
+
+ if (rightIndexSession) {
+ this.swapSessionIndices(session, rightIndexSession);
+ }
+ }
+
+ isValidResizeState(state: ResizeState): state is ValidResizeState {
+ return (
+ state.index !== undefined &&
+ state.startX !== undefined &&
+ state.leftSession !== undefined &&
+ state.rightSession !== undefined &&
+ state.startWidthLeft !== undefined &&
+ state.startWidthRight !== undefined
+ );
+ }
+
+ private calculateNewWidths(
+ validResizeState: ValidResizeState,
+ delta: number,
+ ): [number, number] {
+ let newWidthLeft = validResizeState.startWidthLeft + delta;
+ let newWidthRight = validResizeState.startWidthRight - delta;
+
+ [newWidthLeft, newWidthRight] = this.adjustWidthsWithMinimum(
+ validResizeState,
+ newWidthLeft,
+ newWidthRight,
+ );
+ return [newWidthLeft, newWidthRight];
+ }
+
+ private adjustWidthsWithMinimum(
+ validResizeState: ValidResizeState,
+ newWidthLeft: number,
+ newWidthRight: number,
+ ): [number, number] {
+ if (newWidthLeft < this.minimumSessionWidth) {
+ newWidthLeft = this.minimumSessionWidth;
+ newWidthRight =
+ validResizeState.startWidthLeft +
+ validResizeState.startWidthRight -
+ newWidthLeft;
+ } else if (newWidthRight < this.minimumSessionWidth) {
+ newWidthRight = this.minimumSessionWidth;
+ newWidthLeft =
+ validResizeState.startWidthLeft +
+ validResizeState.startWidthRight -
+ newWidthRight;
+ }
+ return [newWidthLeft, newWidthRight];
+ }
+
+ private resetWidths() {
+ this.minimumSessionWidth = (window.innerWidth - this.existingMargin) * 0.15;
+ this._tilingSessions.forEach(
+ (session) =>
+ (session.width =
+ (window.innerWidth - this.existingMargin) /
+ this._tilingSessions.length),
+ );
+ }
+
+ private getSessionByIndex(index: number): TilingSession | undefined {
+ return this._tilingSessions.find((session) => session.index === index);
+ }
+
+ private toggleIFrameEvents(enable: boolean): void {
+ const action = enable ? 'auto' : 'none';
+ document.querySelectorAll('iframe').forEach((iframe) => {
+ iframe.style.pointerEvents = action;
+ iframe.style.userSelect = action;
+ });
+ }
+
+ private swapSessionIndices(
+ firstSession: TilingSession,
+ secondSession: TilingSession,
+ ) {
+ const firstSessionIndex = firstSession.index;
+
+ firstSession.index = secondSession.index;
+ secondSession.index = firstSessionIndex;
+ }
+}
+
+type ResizeState = {
+ index?: number;
+ startX?: number;
+ leftSession?: TilingSession;
+ rightSession?: TilingSession;
+ startWidthLeft?: number;
+ startWidthRight?: number;
+};
+
+type ValidResizeState = Required;
+
+type TilingSession = ViewerSession & {
+ index: number;
+ width: number;
+ fullscreen: boolean;
+};