Skip to content

Commit

Permalink
added mini and resizable application sidenav (#639)
Browse files Browse the repository at this point in the history
* added mini and resizable application sidenav

- collapsing sidenav now has 2 modes
  - first collapse shows icons only
  - second collapse closes sidenav
- sidenav is now resizable
- sidenav preferences are saved to local storage on a per team basis
  - added a new menu item to reset saved ui preferences
- application buttons are now links that can be opened in new tabs natively
  • Loading branch information
sei-aschlackman authored Apr 1, 2024
1 parent 75b92f6 commit 3151172
Show file tree
Hide file tree
Showing 14 changed files with 344 additions and 115 deletions.
32 changes: 29 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@mdi/font": "^7.2.96",
"@microsoft/signalr": "^6.0.0",
"@popperjs/core": "^2.10.2",
"angular-resizable-element": "^7.0.2",
"babel-polyfill": "^6.26.0",
"bootstrap": "^5.2.3",
"core-js": "^3.18.3",
Expand Down
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ import { TableVirtualScrollModule } from 'ng-table-virtual-scroll';
import { AppAdminSubscriptionSearchComponent } from './components/admin-app/app-admin-subscription-search/app-admin-subscription-search.component';
import { EditSubscriptionComponent } from './components/admin-app/app-admin-subscription-search/edit-subscription/edit-subscription.component';
import { CreateApplicationDialogComponent } from './components/shared/create-application-dialog/create-application-dialog.component';
import { ResizableModule } from 'angular-resizable-element';

@NgModule({
exports: [
Expand Down Expand Up @@ -206,6 +207,7 @@ export const myCustomTooltipDefaults: MatTooltipDefaultOptions = {
ComnSettingsModule.forRoot(),
ComnAuthModule.forRoot(),
TableVirtualScrollModule,
ResizableModule,
],
providers: [
AppService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<mat-list class="appitems-container">
<mat-list-item
*ngFor="let app of applications$ | async; trackBy: trackByFn"
class=""
[ngClass]="mini ? 'list-item-mini' : 'list-item'"
>
<iframe
*ngIf="app.loadInBackground"
Expand All @@ -17,19 +17,28 @@
></iframe>

<div class="app-button-container">
<button
<a
mat-button
[title]="app.name"
class="px-0 w-100"
(click)="app.embeddable ? openInFocusedApp(app) : openInTab(app)"
[href]="app.themedUrl"
(click)="
$event.preventDefault();
app.embeddable ? openInFocusedApp(app) : openInTab(app)
"
>
<div class="d-flex align-items-center">
<div
class="d-flex align-items-center"
[ngClass]="mini ? 'justify-content-center' : null"
>
<img class="lefticon" src="{{ app.icon }}" alt="{{ app.name }}" />
<div>
<div *ngIf="!mini" class="app-name ps-2">
{{ app.name }}
</div>
</div>
</button>
</a>
<button
*ngIf="!mini"
mat-icon-button
[matMenuTriggerFor]="menu"
aria-label="{{ app.name }} Menu"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
.lefticon {
font-size: 75%;
padding: 0.5em;
padding-right: 1.5em;
height: 35px;
text-align: center;
}
Expand All @@ -41,3 +40,17 @@
justify-content: center;
width: 100%;
}

::ng-deep .list-item-mini > .mat-list-item-content {
padding: 0 !important;
}

::ng-deep .list-item > .mat-list-item-content {
padding-left: 16px !important;
padding-right: 0px !important;
}

.app-name {
width: 100%;
text-wrap: pretty;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { ComnAuthQuery, ComnAuthService, Theme } from '@cmusei/crucible-common';
import { Observable, Subject } from 'rxjs';
import { Observable, Subject, combineLatest } from 'rxjs';
import { map, takeUntil, tap } from 'rxjs/operators';
import { ApplicationData } from '../../../models/application-data';
import { TeamData } from '../../../models/team-data';
Expand All @@ -28,12 +28,12 @@ import { FocusedAppService } from '../../../services/focused-app/focused-app.ser
export class ApplicationListComponent implements OnInit, OnChanges, OnDestroy {
@Input() viewId: string;
@Input() teams: TeamData[];
@Input() mini: boolean;

public applications$: Observable<ApplicationData[]>;
public viewGUID: string;
public titleText: string;
private unsubscribe$: Subject<null> = new Subject<null>();
private currentTheme = Theme.LIGHT;
private currentApp: ApplicationData;

constructor(
Expand All @@ -42,11 +42,7 @@ export class ApplicationListComponent implements OnInit, OnChanges, OnDestroy {
private authService: ComnAuthService,
private sanitizer: DomSanitizer,
private authQuery: ComnAuthQuery
) {
authQuery.userTheme$
.pipe(takeUntil(this.unsubscribe$))
.subscribe((t) => (this.currentTheme = t));
}
) {}

ngOnInit() {
this.refreshApps();
Expand All @@ -60,33 +56,34 @@ export class ApplicationListComponent implements OnInit, OnChanges, OnDestroy {

// Local Component functions
openInTab(app: ApplicationData) {
const url = this.insertThemeToUrl(app.url);
window.open(url, '_blank');
window.open(app.themedUrl, '_blank');
}

refreshApps() {
this.applications$ = this.applicationsService
.getApplicationsByTeam(this.teams.find((t) => t.isPrimary).id)
.pipe(
map((apps) => ({ apps })),
map(({ apps }) => {
apps.forEach(
(app) =>
(app.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(
app.url
))
this.applications$ = combineLatest([
this.authQuery.userTheme$,
this.applicationsService.getApplicationsByTeam(
this.teams.find((t) => t.isPrimary).id
),
]).pipe(
map(([theme, apps]) => {
apps.forEach((app) => {
app.themedUrl = this.insertThemeToUrl(app.url, theme);
app.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(
app.themedUrl
);
return apps;
}),
tap((apps) => {
if (apps.length > 0) {
this.currentApp === undefined
? this.openInFocusedApp(apps[0])
: this.openInFocusedApp(this.currentApp);
}
}),
takeUntil(this.unsubscribe$)
);
});
return apps;
}),
tap((apps) => {
if (apps.length > 0) {
this.currentApp === undefined
? this.openInFocusedApp(apps[0])
: this.openInFocusedApp(this.currentApp);
}
}),
takeUntil(this.unsubscribe$)
);
}

openInFocusedApp(app: ApplicationData) {
Expand All @@ -98,20 +95,19 @@ export class ApplicationListComponent implements OnInit, OnChanges, OnDestroy {
);
window.location.reload();
} else {
const url = this.insertThemeToUrl(app.url);
this.focusedAppService.focusedAppUrl.next(url);
this.focusedAppService.focusedAppUrl.next(app.themedUrl);
}
});
}

insertThemeToUrl(url: string) {
insertThemeToUrl(url: string, theme: Theme) {
if (url.includes('{theme}')) {
if (url.includes('?')) {
url = url.replace('?{theme}', '?theme=' + this.currentTheme);
url = url.replace('&{theme}', '&theme=' + this.currentTheme);
url = url.replace('{theme}', '&theme=' + this.currentTheme);
url = url.replace('?{theme}', '?theme=' + theme);
url = url.replace('&{theme}', '&theme=' + theme);
url = url.replace('{theme}', '&theme=' + theme);
} else {
url = url.replace('{theme}', '?theme=' + this.currentTheme);
url = url.replace('{theme}', '?theme=' + theme);
}
}
return url;
Expand Down
86 changes: 56 additions & 30 deletions src/app/components/player/player.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,68 @@

<ng-container *ngIf="data$ | async as data; else loading">
<ng-container *ngIf="loaded" ; else loading>
<mat-sidenav-container class="appcontent-container" autosize>
<mat-sidenav-container
class="appcontent-container"
[autosize]="autosizeSidenav"
>
<mat-sidenav
#sidenav
class="appbarmenu-container"
mode="side"
[opened]="opened$ | async"
mwlResizable
(resizing)="resizingFn($event)"
(resizeEnd)="resizeEnd($event)"
[ngStyle]="resizeStyle"
>
<mat-list class="appitems-container">
<mat-list-item>
<a class="nolink" [routerLink]="['/']">
<div class="d-flex align-items-center">
<mat-icon
class="player-icon"
svgIcon="ic_crucible_player"
></mat-icon>
<h2>
<b>{{ data.title }}</b>
</h2>
</div>
</a>
<div class="grid">
<div>
<mat-list>
<mat-list-item
class="d-flex"
[ngClass]="
(mini$ | async) ? 'player-title-mini' : 'player-title'
"
>
<a class="nolink" [routerLink]="['/']">
<div class="d-flex align-items-center">
<mat-icon
[ngClass]="
(mini$ | async) ? 'player-icon-mini' : 'player-icon'
"
svgIcon="ic_crucible_player"
></mat-icon>
<h2 *ngIf="!(mini$ | async)">
<b>{{ data.title }}</b>
</h2>
</div>
</a>
</mat-list-item>
</mat-list>
<mat-divider></mat-divider>
</mat-list-item>
</mat-list>
<app-application-list
[viewId]="data.view.id"
[teams]="data.teams"
(toggleSideNavEvent)="sidenavToggleFn()"
></app-application-list>
<img
alt="crucible logo"
class="crucible-logo"
[src]="
(theme$ | async) === 'light-theme'
? 'assets/img/crucible-logo-light.png'
: 'assets/img/crucible-logo-dark.png'
"
/>
<app-application-list
[viewId]="data.view.id"
[teams]="data.teams"
[mini]="mini$ | async"
></app-application-list>
<img
*ngIf="!(mini$ | async)"
alt="crucible logo"
class="crucible-logo"
[src]="
(theme$ | async) === 'light-theme'
? 'assets/img/crucible-logo-light.png'
: 'assets/img/crucible-logo-dark.png'
"
/>
</div>
<div
*ngIf="!(mini$ | async)"
class="resize-handle-right"
mwlResizeHandle
[resizeEdges]="{ right: true }"
></div>
</div>
</mat-sidenav>
<mat-sidenav-content class="noscroll">
<app-topbar
Expand All @@ -52,6 +77,7 @@ <h2>
[teams]="data.teams"
[team]="data.team"
[viewId]="viewId"
[mini]="mini$ | async"
(setTeam)="setPrimaryTeam($event)"
(sidenavToggle)="sidenavToggleFn($event)"
(editView)="editViewFn($event)"
Expand Down
Loading

0 comments on commit 3151172

Please sign in to comment.