Skip to content

Commit

Permalink
feat(updates): Moves application update dialog to non-modal snackbar
Browse files Browse the repository at this point in the history
Fixes: #704
  • Loading branch information
castaway committed Feb 19, 2025
1 parent 7a47aa0 commit f828a50
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 56 deletions.
7 changes: 7 additions & 0 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@
<p style="text-align: center; font-size: 12px">
Runbox 7 build time: {{buildtimestampstring}} <br>
<a routerLink="/changelog"> Read the changelog </a>
<ng-container *ngIf="updateService.updateIsReady | async">
<br><span>An update of the application is available, and you may reload the app now to use the latest version.</span>
<br>
<button mat-raised-button (click)="reload()" color="primary">
Reload app
</button>
</ng-container>
</p>
</mat-sidenav>
<mat-sidenav *ngIf="mailViewerOnRightSide" position="end" mode="side"
Expand Down
55 changes: 30 additions & 25 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ import { StorageService } from './storage.service';
import { SearchMessageDisplay } from './xapian/searchmessagedisplay';
import { UsageReportsService } from './common/usage-reports.service';
import { objectEqualWithKeys } from './common/util';
import { UpdateAlertService } from './updatealert/updatealert.service';
import { UpdateAlertComponent } from './updatealert/updatealert.component';

const LOCAL_STORAGE_SETTING_MAILVIEWER_ON_RIGHT_SIDE_IF_MOBILE = 'mailViewerOnRightSideIfMobile';
const LOCAL_STORAGE_SETTING_MAILVIEWER_ON_RIGHT_SIDE = 'mailViewerOnRightSide';
Expand Down Expand Up @@ -165,7 +167,8 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis

morelistbuttonindex = 7;

constructor(public searchService: SearchService,
constructor(
public searchService: SearchService,
public rmmapi: RunboxWebmailAPI,
public rmm: RMM,
public snackBar: MatSnackBar,
Expand All @@ -190,6 +193,7 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
private storage: StorageService,
private savedSearchService: SavedSearchesService,
private usage: UsageReportsService,
public updateService: UpdateAlertService,
) {
this.hotkeysService.add(
new Hotkey(['j', 'k'],
Expand Down Expand Up @@ -434,6 +438,16 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
}
);

this.updateService.updateIsReady.subscribe(res => {
if(res) {
this.updateService.updateIsReady.next(false);
// Update is available, ask user if they want to restart:
this.snackBar.openFromComponent(UpdateAlertComponent, {
data: this.updateService.updateStatus,
duration: 10000,
});
}
});
}

ngAfterViewInit() {
Expand Down Expand Up @@ -481,14 +495,18 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
});
});

if ('serviceWorker' in navigator) {
try {
Notification.requestPermission();
} catch (e) {}
}
if ('serviceWorker' in navigator) {
try {
Notification.requestPermission();
} catch (e) {}
}

this.subscribeToNotifications();
this.calculateWidthDependentElements();
this.subscribeToNotifications();
this.calculateWidthDependentElements();
}

reload(): void {
location.reload();
}

selectMessageFromFragment(fragment: string): void {
Expand Down Expand Up @@ -670,7 +688,7 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis

public trainSpam(params) {
const msg = params.is_spam ? 'Reporting spam' : 'Reporting not spam';
const snackBarRef = this.snackBar.open( msg );
this.snackBar.open( msg );
const unfilteredMessageIds = this.canvastable.rows.selectedMessageIds();
// ensure valid IDs
const messageIds = unfilteredMessageIds.filter(id => Number.isInteger(id));
Expand Down Expand Up @@ -706,17 +724,12 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
const res = this.rmmapi.trainSpam({is_spam: params.is_spam, from_folder_id: currentFolderId, messages: messageIds});
res.subscribe(data => {
if ( data.status === 'error' ) {
snackBarRef.dismiss();
this.snackBar.open('There was an error with Spam functionality. Please select the messages and try again.', 'Dismiss');
}
snackBarRef.dismiss();
}, (err) => {
console.error('Error reporting spam', err);
this.snackBar.open('There was an error with Spam functionality.', 'Dismiss');
},
() => {
snackBarRef.dismiss();
});
});
return res;
}
});
Expand All @@ -741,7 +754,7 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
}

public setReadStatus(status: boolean) {
const snackBarRef = this.snackBar.open('Toggling read status...');
this.snackBar.open('Toggling read status...');
const messageIds = this.canvastable.rows.selectedMessageIds();

this.messageActionsHandler.updateMessages({
Expand All @@ -766,15 +779,11 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
},
ids: msgIds
})
,
afterwards: (result) => {
snackBarRef.dismiss();
}
});
}

public setFlaggedStatus(status: boolean) {
const snackBarRef = this.snackBar.open('Toggling flags...');
this.snackBar.open('Toggling flags...');
const messageIds = this.canvastable.rows.selectedMessageIds();

this.messageActionsHandler.updateMessages({
Expand All @@ -799,10 +808,6 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
},
ids: msgIds
})
,
afterwards: (result) => {
snackBarRef.dismiss();
}
});
}

Expand Down
3 changes: 1 addition & 2 deletions src/app/compose/compose.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,11 +609,10 @@ export class ComposeComponent implements AfterViewInit, OnDestroy, OnInit {
}

public trashDraft() {
const snackBarRef = this.snackBar.open('Deleting');
this.snackBar.open('Deleting');
this.draftDeskservice.isEditing = -1;
this.draftDeskservice.composingNewDraft = null;
this.rmmapi.deleteMessages([this.model.mid]).subscribe(() => {
snackBarRef.dismiss();
this.draftDeleted.emit(this.model.mid);
this.exitIfNeeded();
});
Expand Down
9 changes: 3 additions & 6 deletions src/app/mailviewer/rmm7messageactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export class RMM7MessageActions implements MessageActions {
// FIXME: How does the view change? close mailview, show "next" email or?
trainSpam(params) {
const msg = params.is_spam ? 'Reporting spam' : 'Reporting not spam';
const snackBarRef = this.snackBar.open(msg);
this.snackBar.open(msg);
this.updateMessages({
messageIds: [this.mailViewerComponent.messageId],
updateLocal: (msgIds: number[]) => {
Expand All @@ -182,7 +182,6 @@ export class RMM7MessageActions implements MessageActions {
const res = this.rmmapi.trainSpam({is_spam: params.is_spam, messages: msgIds});
res.subscribe(data => {
if ( data.status === 'error' ) {
snackBarRef.dismiss();
this.snackBar.open('There was an error with Spam functionality. Please try again.', 'Dismiss');
}
}, (err) => {
Expand All @@ -192,15 +191,14 @@ export class RMM7MessageActions implements MessageActions {
return res;
},
afterwards: (result) => {
snackBarRef.dismiss();
this.mailViewerComponent.close();
}
});
}

blockSender(param) {
const msg = `Blocking sender: ${param}`;
const snackBarRef = this.snackBar.open(msg);
this.snackBar.open(msg);
this.rmmapi.blockSender(param).subscribe((res) => {
snackBarRef.dismiss();
if ( res.status === 'error' ) {
Expand All @@ -213,10 +211,9 @@ export class RMM7MessageActions implements MessageActions {

allowListSender(param) {
const msg = `AllowListing sender: ${param}`;
const snackBarRef = this.snackBar.open(msg);
this.snackBar.open(msg);
this.rmmapi.allowListSender(param).subscribe((res) => {
if ( res.status === 'error' ) {
snackBarRef.dismiss();
this.snackBar.open('There was an error with Sender allowlisting functionality. Please try again.', 'Dismiss');
} else {
this.snackBar.open(`${param} added to allowlist.`, 'Dismiss');
Expand Down
15 changes: 7 additions & 8 deletions src/app/updatealert/updatealert.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<h1 mat-dialog-title>App update available!</h1>
<mat-dialog-content>
<div matSnackBarLabel>
<p>
An update of the application is available, and you may reload the app now to use the latest version.
</p>
Expand All @@ -11,10 +10,10 @@ <h1 mat-dialog-title>App update available!</h1>
and the changes can be seen in the Runbox 7 changelog
<a href="/app/changelog?since={{ data.current.appData.build_epoch }}">here</a>.<br />
To provide feedback on Runbox 7 features, please visit our <a href="https://community.runbox.com/c/runbox-7/runbox-7-webmail/10" target="forum">community forum</a>.
</p>
</mat-dialog-content>
<mat-dialog-actions style="display: flex">
</p>
</div>
<div style="display: flex" matSnackBarActions>
<span style="flex-grow: 1"></span>
<button mat-button (click)="reload()">Reload now</button>
<button mat-button (click)="close()">Later</button>
</mat-dialog-actions>
<button mat-button matSnackBarAction (click)="reload()">Reload now</button>
<button mat-button matSnackBarAction (click)="close()">Later</button>
</div>
9 changes: 4 additions & 5 deletions src/app/updatealert/updatealert.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,21 @@
// ---------- END RUNBOX LICENSE ----------

import { Component, Inject } from '@angular/core';
import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog';
import { MatSnackBarRef, MAT_SNACK_BAR_DATA } from "@angular/material/snack-bar";

@Component({
templateUrl: 'updatealert.component.html'
})
export class UpdateAlertComponent {
constructor(
private dialogRef: MatDialogRef<UpdateAlertComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
@Inject(MAT_SNACK_BAR_DATA) public data: any,
public snackBarRef: MatSnackBarRef<UpdateAlertComponent>,
) {
console.log('Update details:', data);
}


close() {
this.dialogRef.close();
this.snackBarRef.dismiss();
}

reload() {
Expand Down
44 changes: 34 additions & 10 deletions src/app/updatealert/updatealert.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,35 @@
// along with Runbox 7. If not, see <https://www.gnu.org/licenses/>.
// ---------- END RUNBOX LICENSE ----------

import { ApplicationRef, Injectable, NgZone } from '@angular/core';
import { ApplicationRef, Injectable } from '@angular/core';
import { SwUpdate, VersionReadyEvent } from '@angular/service-worker';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { UpdateAlertComponent } from './updatealert.component';
import { environment } from '../../environments/environment';
import { concat, timer } from 'rxjs';
import { concat, interval, BehaviorSubject } from 'rxjs';
import { filter, map, first } from 'rxjs/operators';

interface UpdateStatus {
type: string;
current: {
hash: string;
appData?: object;
};
available: {
hash: string;
appData?: object;
};
}

@Injectable()
export class UpdateAlertService {
public updateIsReady: BehaviorSubject<boolean> = new BehaviorSubject(false);
public updateStatus: UpdateStatus = {
'type':'UPDATE_AVAILABLE',
'current':
{'hash':'blah', 'appData':{'build_epoch':'XX'}},
'available':
{'hash':'blah', 'appData':{'commit':'test', 'build_time': 'time', 'build_epoch':'XX'}},
};
constructor(
private appRef: ApplicationRef,
private swupdate: SwUpdate,
Expand All @@ -37,17 +56,22 @@ export class UpdateAlertService {

const updatesAvailable = swupdate.versionUpdates.pipe(
filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'),
map(evt => ({
type: 'UPDATE_AVAILABLE',
current: evt.currentVersion,
available: evt.latestVersion,
})));
map(evt => {
const update: UpdateStatus = {
type: 'UPDATE_AVAILABLE',
current: evt.currentVersion,
available: evt.latestVersion,
};
return update;
})
);
updatesAvailable.subscribe(ev => {
dialog.open(UpdateAlertComponent, { data: ev });
this.updateStatus = ev;
this.updateIsReady.next(true);
});

const appIsStable = this.appRef.isStable.pipe(first(isStable => isStable === true));
const everyFiveMins = timer(0, 5 * 60 * 1000);
const everyFiveMins = interval(5 * 60 * 1000);
const everyFiveMinsOnceAppIsStable = concat(appIsStable, everyFiveMins);
everyFiveMinsOnceAppIsStable.subscribe(() =>
this.checkForUpdates()
Expand Down

0 comments on commit f828a50

Please sign in to comment.