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

Action commands and printer notifications #2586

Merged
merged 15 commits into from
Feb 22, 2022
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
2 changes: 1 addition & 1 deletion .github/workflows/manual.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Manual Build with Artifacts

on:
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
Expand Down
11 changes: 9 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,21 @@ export function playerFactory(): LottiePlayer {
[
{
provide: SocketService,
deps: [ConfigService, SystemService, ConversionService, HttpClient],
deps: [ConfigService, SystemService, ConversionService, NotificationService, HttpClient],
useFactory: (
configService: ConfigService,
systemService: SystemService,
conversionService: ConversionService,
notificationService: NotificationService,
httpClient: HttpClient,
) => {
return new OctoPrintSocketService(configService, systemService, conversionService, httpClient);
return new OctoPrintSocketService(
configService,
systemService,
conversionService,
notificationService,
httpClient,
);
},
},
],
Expand Down
46 changes: 24 additions & 22 deletions src/app/event.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,32 @@ export class EventService implements OnDestroy {
private router: Router,
) {
this.subscriptions.add(
this.socketService.getEventSubscribable().subscribe((event: PrinterEvent) => {
if (event === PrinterEvent.PRINTING || event === PrinterEvent.PAUSED) {
setTimeout(() => {
this.printing = true;
}, 500);
} else {
setTimeout(() => {
this.printing = false;
}, 1000);
}
this.socketService.getEventSubscribable().subscribe((event: PrinterEvent) => this.handlePrinterEvent(event)),
);
}

if (event === PrinterEvent.CLOSED) {
this.router.navigate(['/standby']);
} else if (event === PrinterEvent.CONNECTED) {
setTimeout(() => {
if (this.configService.isTouchscreen()) {
this.router.navigate(['/main-screen']);
} else {
this.router.navigate(['/main-screen-no-touch']);
}
}, 1000);
private handlePrinterEvent(event: PrinterEvent): void {
if (event === PrinterEvent.PRINTING || event === PrinterEvent.PAUSED) {
setTimeout(() => {
this.printing = true;
}, 500);
} else {
setTimeout(() => {
this.printing = false;
}, 1000);
}

if (event === PrinterEvent.CLOSED) {
this.router.navigate(['/standby']);
} else if (event === PrinterEvent.CONNECTED) {
setTimeout(() => {
if (this.configService.isTouchscreen()) {
this.router.navigate(['/main-screen']);
} else {
this.router.navigate(['/main-screen-no-touch']);
}
}),
);
}, 500);
}
}

ngOnDestroy(): void {
Expand Down
7 changes: 7 additions & 0 deletions src/app/model/event.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ export enum PrinterEvent {
IDLE,
UNKNOWN,
}

export interface PrinterNotification {
message?: string;
action?: string;
text?: string;
choices?: string[];
}
5 changes: 4 additions & 1 deletion src/app/model/octoprint/socket.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ export interface OctoprintSocketCurrent {
export interface OctoprintSocketEvent {
event: {
type: string;
payload: unknown;
payload: {
error: string;
reason: string;
};
};
}
export interface OctoprintPluginMessage {
Expand Down
3 changes: 3 additions & 0 deletions src/app/model/system.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ export interface Notification {
text: string;
type: NotificationType;
time: Date;
choices?: Array<string>;
callback?: (index: number) => void;
sticky?: boolean;
}

export enum NotificationType {
INFO,
WARN,
ERROR,
PROMPT,
}

export interface UpdateError {
Expand Down
13 changes: 11 additions & 2 deletions src/app/notification/notification.component.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
<div
class="notification"
[ngClass]="['notification__border-' + notification?.type, show ? 'notification__show' : '']"
(click)="hideNotification()">
(click)="hideNotification(true, true)">
<span class="notification__time">{{ notification?.time | date: 'HH:mm' }}</span>
<span class="notification__heading">{{ notification?.heading }}</span>
<span class="notification__text">{{ notification?.text }}</span>
<span class="notification__close" i18n="@@tap-close">tap this card to close it</span>
<span class="notification__close" i18n="@@tap-close" *ngIf="!notification?.choices">tap this card to close it</span>
<div class="notification-prompt__choices">
<div
class="notification-prompt__choice"
*ngFor="let choice of notification?.choices; index as i; first as isFirst"
[ngClass]="[isFirst ? 'notification-prompt__choice-first' : '']"
(click)="chooseAction(i, notification?.callback)">
{{ choice }}
</div>
</div>
</div>
27 changes: 27 additions & 0 deletions src/app/notification/notification.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,29 @@
z-index: 100;
border-right: 1vw solid #5a6675;

&-prompt {
&__choices {
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
}

&__choice {
display: block;
font-size: 2.7vw;
background-color: #2196f3;
text-align: center;
padding: 3vh;
border-radius: 0.8vw;
margin: 1vw;
flex-grow: 100;

&-first {
background-color: #44bd32;
}
}
}

&__show {
top: 5vh;
}
Expand Down Expand Up @@ -47,6 +70,10 @@
}

&__border {
&-3 {
border-left: 1vw solid #a1abb7;
}

&-2 {
border-left: 1vw solid #c23616;
}
Expand Down
17 changes: 12 additions & 5 deletions src/app/notification/notification.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,17 @@ export class NotificationComponent implements OnDestroy {
);
}

public hideNotification(removeFromStack = true): void {
this.show = false;
clearTimeout(this.notificationCloseTimeout);
if (removeFromStack) this.notificationService.removeNotification(this.notification);
public hideNotification(removeFromStack = true, userTriggered = false): void {
if (!userTriggered || (userTriggered && !this.notification.choices)) {
this.show = false;
clearTimeout(this.notificationCloseTimeout);
if (removeFromStack) this.notificationService.removeNotification(this.notification);
}
}

public chooseAction(index: number, callback: (index: number) => void): void {
callback(index);
this.hideNotification();
}

private setNotification(notification: Notification | 'close'): void {
Expand All @@ -41,7 +48,7 @@ export class NotificationComponent implements OnDestroy {

if (!notification.sticky) {
clearTimeout(this.notificationCloseTimeout);
this.notificationCloseTimeout = setTimeout(this.hideNotification.bind(this), 30 * 1000, false);
this.notificationCloseTimeout = setTimeout(this.hideNotification.bind(this), 15 * 1000, false);
}
}
});
Expand Down
112 changes: 100 additions & 12 deletions src/app/services/socket/socket.octoprint.service.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import _ from 'lodash-es';
import { Observable, ReplaySubject, Subject } from 'rxjs';
import { pluck, startWith } from 'rxjs/operators';
import { Observable, of, ReplaySubject, Subject } from 'rxjs';
import { catchError, pluck, startWith } from 'rxjs/operators';
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';

import { ConfigService } from '../../config/config.service';
import { ConversionService } from '../../conversion.service';
import { JobStatus, PrinterEvent, PrinterState, PrinterStatus, SocketAuth } from '../../model';
import {
JobStatus,
Notification,
NotificationType,
PrinterEvent,
PrinterNotification,
PrinterState,
PrinterStatus,
SocketAuth,
} from '../../model';
import {
DisplayLayerProgressData,
OctoprintFilament,
OctoprintPluginMessage,
OctoprintSocketCurrent,
OctoprintSocketEvent,
} from '../../model/octoprint';
import { NotificationService } from '../../notification/notification.service';
import { SystemService } from '../system/system.service';
import { SocketService } from './socket.service';

Expand All @@ -36,11 +46,12 @@ export class OctoPrintSocketService implements SocketService {
private configService: ConfigService,
private systemService: SystemService,
private conversionService: ConversionService,
private notificationService: NotificationService,
private http: HttpClient,
) {
this.printerStatusSubject = new ReplaySubject<PrinterStatus>(1);
this.jobStatusSubject = new Subject<JobStatus>();
this.eventSubject = new ReplaySubject<PrinterEvent>(1);
this.eventSubject = new ReplaySubject<PrinterEvent>();
}

//==== SETUP & AUTH ====//
Expand Down Expand Up @@ -118,6 +129,25 @@ export class OctoPrintSocketService implements SocketService {
this.socket.next(payload);
}

private handlePluginMessage(pluginMessage: OctoprintPluginMessage) {
const plugins = [
{
check: (plugin: string) =>
plugin === 'DisplayLayerProgress-websocket-payload' && this.configService.isDisplayLayerProgressEnabled(),
handler: (message: unknown) => {
this.extractFanSpeed(message as DisplayLayerProgressData);
this.extractLayerHeight(message as DisplayLayerProgressData);
},
},
{
check: (plugin: string) => ['action_command_prompt', 'action_command_notification'].includes(plugin),
handler: (message: unknown) => this.handlePrinterNotification(message as PrinterNotification),
},
];

plugins.forEach(plugin => plugin.check(pluginMessage.plugin.plugin) && plugin.handler(pluginMessage.plugin.data));
}

private setupSocket(resolve: () => void) {
this.socket.subscribe({
next: message => {
Expand All @@ -132,14 +162,7 @@ export class OctoPrintSocketService implements SocketService {
} else if (Object.hasOwnProperty.bind(message)('event')) {
this.extractPrinterEvent(message as OctoprintSocketEvent);
} else if (Object.hasOwnProperty.bind(message)('plugin')) {
const pluginMessage = message as OctoprintPluginMessage;
if (
pluginMessage.plugin.plugin === 'DisplayLayerProgress-websocket-payload' &&
this.configService.isDisplayLayerProgressEnabled()
) {
this.extractFanSpeed(pluginMessage.plugin.data as DisplayLayerProgressData);
this.extractLayerHeight(pluginMessage.plugin.data as DisplayLayerProgressData);
}
this.handlePluginMessage(message as OctoprintPluginMessage);
} else if (Object.hasOwnProperty.bind(message)('reauthRequired')) {
this.systemService.getSessionKey().subscribe(socketAuth => this.authenticateSocket(socketAuth));
} else if (Object.hasOwnProperty.bind(message)('connected')) {
Expand Down Expand Up @@ -300,6 +323,15 @@ export class OctoPrintSocketService implements SocketService {
break;
case 'Error':
newState = PrinterEvent.CLOSED;
if (state.event.payload) {
this.notificationService.setNotification({
heading: $localize`:@@printer-information:Printer error`,
text: state.event.payload.error,
type: NotificationType.ERROR,
time: new Date(),
sticky: true,
} as Notification);
}
break;
default:
break;
Expand All @@ -311,6 +343,62 @@ export class OctoPrintSocketService implements SocketService {
}
}

//==== Notifications ====//

private handlePrinterNotification(notification: PrinterNotification) {
if (Object.keys(notification).length > 0) {
if (notification.action === 'close') {
this.notificationService.closeNotification();
} else if (notification.choices?.length > 0) {
this.notificationService.setNotification({
heading: $localize`:@@action-required:Action required`,
text: notification.text ?? notification.message,
type: NotificationType.PROMPT,
time: new Date(),
choices: notification.choices,
callback: this.callbackFunction.bind(this),
sticky: true,
} as Notification);
} else if (notification.choices?.length == 0) {
this.notificationService.setNotification({
heading: $localize`:@@printer-information:Printer information`,
text: notification.text ?? notification.message,
type: NotificationType.WARN,
time: new Date(),
sticky: true,
} as Notification);
} else if (notification.text || notification.message) {
this.notificationService.setNotification({
heading: $localize`:@@printer-information:Printer information`,
text: notification.text ?? notification.message,
type: NotificationType.INFO,
time: new Date(),
} as Notification);
}
}
}

private callbackFunction(index: number) {
this.http
.post(
this.configService.getApiURL('plugin/action_command_prompt'),
{ command: 'select', choice: index },
this.configService.getHTTPHeaders(),
)
.pipe(
catchError(error => {
this.notificationService.setNotification({
heading: $localize`:@@error-answer-prompt:Can't answer prompt!`,
text: error.message,
type: NotificationType.ERROR,
time: new Date(),
});
return of(null);
}),
)
.subscribe();
}

//==== Subscribables ====//

public getPrinterStatusSubscribable(): Observable<PrinterStatus> {
Expand Down
Loading