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

Introduce Notification Center #2469

Merged
merged 8 commits into from
Dec 2, 2021
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
7 changes: 6 additions & 1 deletion helper/config.schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ const configSchema = {
'preheatButton',
'printTimeGenius',
'psuControl',
'ophom',
'ophom',
'tpLinkSmartPlug',
'tasmota',
'tasmotaMqtt',
Expand Down Expand Up @@ -294,6 +294,7 @@ const configSchema = {
'screenSleepCommand',
'screenWakeupCommand',
'showExtruderControl',
'showNotificationCenterIcon',
],
properties: {
customActions: {
Expand Down Expand Up @@ -404,6 +405,10 @@ const configSchema = {
$id: '#/properties/octodash/properties/showExtruderControl',
type: 'boolean',
},
showNotificationCenterIcon: {
$id: '#/properties/octodash/properties/showNotificationCenterIcon',
type: 'boolean',
},
},
},
},
Expand Down
44 changes: 37 additions & 7 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,28 +1,58 @@
<app-notification></app-notification>
<div class="container">
<app-notification-center
*ngIf="initialized"
[ngStyle]="{ top: notificationCenterTop }"
(setNotificationCenterAnimation)="setNotificationCenterAnimation($event)"
(setNotificationCenterPosition)="setNotificationCenterPosition($event)"
[ngClass]="{ animateNotificationCenter: animateNotificationCenter }"></app-notification-center>
<div
class="container"
(touchstart)="onTouchStart($event)"
(touchmove)="onTouchMove($event)"
(touchend)="onTouchEnd($event)">
<div *ngIf="!activated">
<img src="assets/icon/icon-main.svg" class="splash-screen__icon-small" />
<span class="splash-screen__text"
[ngClass]="{ 'loading-dots': status === 'connecting' || status === 'initializing' }">{{ status }}</span>
<span
class="splash-screen__text"
[ngClass]="{ 'loading-dots': status === 'connecting' || status === 'initializing' }"
>{{ status }}</span
>
<span class="splash-screen__hint" *ngIf="showConnectionHint" i18n="@@long-init">
Initializing is taking longer than usual. <br />
Please make sure that OctoPrint is running and that CORS is enabled for the API.
</span>
</div>

<fa-icon
*ngIf="initialized && showNotificationCenterIcon()"
[icon]="['fas', 'caret-down']"
(click)="showNotificationCenter()"
matRipple
[matRippleUnbounded]="true"
[matRippleCentered]="true"></fa-icon>
<router-outlet (activate)="activated = true"></router-outlet>
</div>

<ng-container *ngIf="!loadingAnimationCached">
<ng-lottie id="loadingAnimationCache" [hidden]="true" [options]="loadingOptionsCache"
<ng-lottie
id="loadingAnimationCache"
[hidden]="true"
[options]="loadingOptionsCache"
(dataReady)="loadingAnimationCacheDone()"></ng-lottie>
</ng-container>

<ng-container *ngIf="!checkmarkAnimationCached">
<ng-lottie id="checkmarkAnimationCache" [hidden]="true" [options]="checkmarkOptionsCache"
<ng-lottie
id="checkmarkAnimationCache"
[hidden]="true"
[options]="checkmarkOptionsCache"
(dataReady)="checkmarkAnimationCacheDone()"></ng-lottie>
</ng-container>

<ng-container *ngIf="!toggleSwitchAnimationCached">
<ng-lottie id="toggleSwitchAnimationCache" [hidden]="true" [options]="toggleSwitchOptionsCache"
<ng-lottie
id="toggleSwitchAnimationCache"
[hidden]="true"
[options]="toggleSwitchOptionsCache"
(dataReady)="toggleSwitchAnimationCacheDone()"></ng-lottie>
</ng-container>
</ng-container>
11 changes: 11 additions & 0 deletions src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,14 @@
display: block;
position: relative;
}

fa-icon {
position: fixed;
left: calc(50% - 10px);
top: -8px;
opacity: 0.4;
}

.animateNotificationCenter {
transition: top 0.3s ease-in-out;
}
52 changes: 52 additions & 0 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ import { SocketService } from './services/socket/socket.service';
})
export class AppComponent implements OnInit {
public activated = false;
public initialized = false;
public status = $localize`:@@initializing:initializing`;
public showConnectionHint = false;
public notificationCenterTop = '-100%';
public animateNotificationCenter = true;
public startSwipe: Touch | undefined;

public loadingOptionsCache: AnimationOptions = {
path: 'assets/animations/loading.json',
Expand Down Expand Up @@ -83,6 +87,7 @@ export class AppComponent implements OnInit {
} else {
this.router.navigate(['/main-screen-no-touch']);
}
this.initialized = true;
})
.finally(() => clearTimeout(showPrinterConnectedTimeout));
}
Expand All @@ -95,7 +100,54 @@ export class AppComponent implements OnInit {
this.checkmarkAnimationCached = true;
}

public showNotificationCenterIcon(): boolean {
return this.configService.showNotificationCenterIcon();
}

public toggleSwitchAnimationCacheDone(): void {
this.toggleSwitchAnimationCached = true;
}

public hideNotificationCenter() {
this.notificationCenterTop = '-100%';
}

public showNotificationCenter() {
this.notificationCenterTop = '0%';
}

public setNotificationCenterPosition(position: string) {
this.notificationCenterTop = position;
}

public setNotificationCenterAnimation(enabled: boolean) {
this.animateNotificationCenter = enabled;
}

public onTouchStart(event: TouchEvent) {
if (event.changedTouches[0].clientY < event.view.innerHeight / 8) {
this.startSwipe = event.changedTouches[0];
this.animateNotificationCenter = false;
} else {
this.startSwipe = undefined;
}
}

public onTouchMove(event: TouchEvent) {
if (this.startSwipe) {
this.notificationCenterTop = `${(100 - (event.changedTouches[0].clientY / event.view.innerHeight) * 100) * -1}%`;
}
}

public onTouchEnd(event: TouchEvent) {
if (this.startSwipe) {
this.animateNotificationCenter = true;
const endSwipe = event.changedTouches[0];
if (endSwipe.clientY > event.view.innerHeight / 3) {
this.showNotificationCenter();
} else {
this.hideNotificationCenter();
}
}
}
}
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { MainScreenComponent } from './main-screen/main-screen.component';
import { MainScreenNoTouchComponent } from './main-screen/no-touch/main-screen-no-touch.component';
import { NotificationComponent } from './notification/notification.component';
import { NotificationService } from './notification/notification.service';
import { NotificationCenterComponent } from './notification-center/notification-center.component';
import { PrintControlComponent } from './print-control/print-control.component';
import { PrinterStatusComponent } from './printer-status/printer-status.component';
import { EnclosureOctoprintService } from './services/enclosure/enclosure.octoprint.service';
Expand Down Expand Up @@ -103,6 +104,7 @@ export function playerFactory(): LottiePlayer {
PurgeFilamentComponent,
CustomActionsComponent,
ToggleSwitchComponent,
NotificationCenterComponent,
],
imports: [
AppRoutingModule,
Expand Down
10 changes: 9 additions & 1 deletion src/app/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import _ from 'lodash-es';
import { Config } from './config/config.model';
import { ConfigService } from './config/config.service';
import { ElectronService } from './electron.service';
import { NotificationType } from './model';
import { NotificationService } from './notification/notification.service';

@Injectable()
Expand Down Expand Up @@ -54,6 +55,8 @@ export class AppService {
"/plugins must have required property 'spoolManager'": config =>
(config.plugins.spoolManager = { enabled: false }),
"/plugins must have required property 'ophom'": config => (config.plugins.ophom = { enabled: false }),
"/octodash must have required property 'showNotificationCenterIcon'": config =>
(config.octodash.showNotificationCenterIcon = true),
};
}

Expand Down Expand Up @@ -107,7 +110,12 @@ export class AppService {
});

this.electronService.on('customStylesError', (_, customCSSError: string): void => {
this.notificationService.setError($localize`:@@error-load-style:Can't load custom styles!`, customCSSError);
this.notificationService.setNotification({
heading: $localize`:@@error-load-style:Can't load custom styles!`,
text: customCSSError,
type: NotificationType.ERROR,
time: new Date(),
});
});
}

Expand Down
12 changes: 7 additions & 5 deletions src/app/bottom-bar/bottom-bar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Component, OnDestroy } from '@angular/core';
import { Subscription, timer } from 'rxjs';

import { ConfigService } from '../config/config.service';
import { PrinterState, PrinterStatus, TemperatureReading } from '../model';
import { NotificationType, PrinterState, PrinterStatus, TemperatureReading } from '../model';
import { NotificationService } from '../notification/notification.service';
import { EnclosureService } from '../services/enclosure/enclosure.service';
import { SocketService } from '../services/socket/socket.service';
Expand Down Expand Up @@ -34,10 +34,12 @@ export class BottomBarComponent implements OnDestroy {
this.enclosureService.getEnclosureTemperature().subscribe({
next: (temperatureReading: TemperatureReading) => (this.enclosureTemperature = temperatureReading),
error: (error: HttpErrorResponse) => {
this.notificationService.setError(
$localize`:@@error-enclosure-temp:Can't retrieve enclosure temperature!`,
error.message,
);
this.notificationService.setNotification({
heading: $localize`:@@error-enclosure-temp:Can't retrieve enclosure temperature!`,
text: error.message,
type: NotificationType.ERROR,
time: new Date(),
});
},
});
}
Expand Down
1 change: 1 addition & 0 deletions src/app/config/config.default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,6 @@ export const defaultConfig: Config = {
screenSleepCommand: 'xset dpms force standby',
screenWakeupCommand: 'xset s off && xset -dpms && xset s noblank',
showExtruderControl: true,
showNotificationCenterIcon: true,
},
};
1 change: 1 addition & 0 deletions src/app/config/config.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ interface OctoDash {
screenSleepCommand: string;
screenWakeupCommand: string;
showExtruderControl: boolean;
showNotificationCenterIcon: boolean;
}

export interface CustomAction {
Expand Down
16 changes: 12 additions & 4 deletions src/app/config/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Injectable, NgZone } from '@angular/core';
import _ from 'lodash-es';

import { ElectronService } from '../electron.service';
import { NotificationType } from '../model';
import { NotificationService } from '../notification/notification.service';
import { Config, CustomAction, HttpHeader, URLSplit } from './config.model';

Expand All @@ -26,10 +27,13 @@ export class ConfigService {
this.electronService.on('configRead', (_, config: Config) => this.initialize(config));
this.electronService.on('configSaved', (_, config: Config) => this.initialize(config));
this.electronService.on('configError', (_, error: string) => {
this.notificationService.setError(
error,
$localize`:@@error-restart:Please restart your system. If the issue persists open an issue on GitHub.`,
);
this.notificationService.setNotification({
heading: error,
text: $localize`:@@error-restart:Please restart your system. If the issue persists open an issue on GitHub.`,
type: NotificationType.ERROR,
time: new Date(),
sticky: true,
});
});

this.electronService.on('configPass', () => {
Expand Down Expand Up @@ -330,4 +334,8 @@ export class ConfigService {
this.config.octodash.fileSorting.order = order;
this.saveConfig(this.config);
}

public showNotificationCenterIcon(): boolean {
return this.config.octodash.showNotificationCenterIcon;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HttpErrorResponse, HttpResponse, HttpResponseBase } from '@angular/common/http';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { interval } from 'rxjs';
import { NotificationType } from 'src/app/model';

import { TokenSuccess } from '../../../model/octoprint/auth.model';
import { NotificationService } from '../../../notification/notification.service';
Expand Down Expand Up @@ -29,39 +30,45 @@ export class OctoprintAuthenticationComponent {
},
error: (error: HttpErrorResponse) => {
if (error.status === 0) {
this.notificationService.setError(
$localize`:@@octoprint-connection-failed:Can't connect to OctoPrint!`,
$localize`:@@octoprint-connection-failed-message:Check the URL/IP and make sure that your OctoPrint instance is reachable from this device.`,
);
this.notificationService.setNotification({
heading: $localize`:@@octoprint-connection-failed:Can't connect to OctoPrint!`,
text: $localize`:@@octoprint-connection-failed-message:Check the URL/IP and make sure that your OctoPrint instance is reachable from this device.`,
type: NotificationType.ERROR,
time: new Date(),
});
} else this.setAutologinWarning();
},
});
}

private setAutologinWarning(): void {
this.notificationService.setWarning(
$localize`:@@unsupported-autologin:Automatic login not supported!`,
$localize`:@@manually-create-api-key:Please create the API Key manually and paste it in the bottom field.`,
);
this.notificationService.setNotification({
heading: $localize`:@@unsupported-autologin:Automatic login not supported!`,
text: $localize`:@@manually-create-api-key:Please create the API Key manually and paste it in the bottom field.`,
type: NotificationType.WARN,
time: new Date(),
});
}

private sendLoginRequest(): void {
this.authService.startAuthProcess(this.octoprintURL).subscribe({
next: (token: string) => {
this.notificationService.setNotification(
$localize`:@@login-request-sent:Login request send!`,
$localize`:@@login-request-sent-message:Please confirm the request via the popup in the OctoPrint WebUI.`,
);
this.notificationService.setNotification({
heading: $localize`:@@login-request-sent:Login request send!`,
text: $localize`:@@login-request-sent-message:Please confirm the request via the popup in the OctoPrint WebUI.`,
type: NotificationType.INFO,
time: new Date(),
});
setTimeout(() => {
this.notificationService.closeNotification();
}, 10 * 1000);
this.pollResult(token);
},
error: () => this.setAutologinWarning(),
});
}

private pollResult(token: string): void {
setTimeout(() => {
this.notificationService.closeNotification();
}, 2000);
const pollInterval = interval(1000).subscribe(() => {
this.authService.pollAuthProcessStatus(this.octoprintURL, token).subscribe({
next: (result: HttpResponse<TokenSuccess>) => {
Expand Down
Loading