Skip to content

Commit b39cd38

Browse files
committed
add "make all read" button in database view mode
1 parent 098c72e commit b39cd38

24 files changed

+343
-180
lines changed

src/electron-preload/webview/protonmail/api/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ const endpoints: ProtonmailApi = {
116116
}
117117
},
118118

119+
async makeRead(input) {
120+
_logger.info("makeRead()", input.zoneName);
121+
122+
const {message} = await resolveProviderApi();
123+
124+
await message.read({IDs: input.messageIds});
125+
126+
// TODO consider triggering the "refresh" action (clicking the "refresh" button action)
127+
},
128+
119129
async fillLogin({login, zoneName}) {
120130
const logger = curryFunctionMembers(_logger, "fillLogin()", zoneName);
121131

src/electron-preload/webview/protonmail/lib/provider-api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export interface ProviderApi {
4242
query: (
4343
params?: Rest.Model.QueryParams & { LabelID?: Unpacked<Rest.Model.Message["LabelIDs"]> },
4444
) => Promise<Rest.Model.MessagesResponse>;
45+
read: (params: { IDs: ReadonlyArray<Rest.Model.Message["ID"]> }) => Promise<void>;
4546
};
4647
contact: {
4748
get: (id: Rest.Model.Contact["ID"]) => Promise<Rest.Model.ContactResponse["Contact"]>;
@@ -121,7 +122,7 @@ export async function resolveProviderApi(): Promise<ProviderApi> {
121122
}),
122123
message: resolveService<ProviderApi["message"]>(injector, "messageApi", {
123124
...rateLimiting,
124-
rateLimitedMethodNames: ["get", "query"],
125+
rateLimitedMethodNames: ["get", "query"/*, "read"*/],
125126
}),
126127
contact: resolveService<ProviderApi["contact"]>(injector, "Contact", {
127128
...rateLimiting,

src/electron-preload/webview/tutanota/api/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ function bootstrapEndpoints(api: Unpacked<ReturnType<typeof resolveProviderApi>>
6161

6262
async fetchSingleMail(input) {
6363
_logger.info("fetchSingleMail()", input.zoneName);
64+
throw new Error("Not yet supported for Tutanota");
65+
},
6466

67+
async makeRead(input) {
68+
_logger.info("makeRead()", input.zoneName);
6569
throw new Error("Not yet supported for Tutanota");
6670
},
6771

src/shared/api/webview/common.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import {AccountType} from "src/shared/model/account";
44
import {DbAccountPk, FsDbAccount, Mail} from "src/shared/model/database";
55
import {LoginFieldContainer, PasswordFieldContainer} from "src/shared/model/container";
66
import {PACKAGE_NAME} from "src/shared/constants";
7-
import {ProtonmailApi} from "src/shared/api/webview/protonmail";
87
import {ReadonlyDeep} from "type-fest";
9-
import {TutanotaApi} from "src/shared/api/webview/tutanota";
108
import {ZoneApiParameter} from "src/shared/api/common";
119

1210
export const channel = `${PACKAGE_NAME}:webview-api`;
@@ -33,13 +31,9 @@ export function buildWebViewApiDefinition<T extends AccountType, NotificationOut
3331
} & ZoneApiParameter>>(),
3432
fetchSingleMail:
3533
Promise<ReadonlyDeep<DbAccountPk & { mailPk: Mail["pk"] } & ZoneApiParameter>>(),
34+
makeRead:
35+
Promise<ReadonlyDeep<DbAccountPk & { messageIds: string[]; } & ZoneApiParameter>>(),
3636
notification:
3737
Observable<ReadonlyDeep<{ entryUrl: string; entryApiUrl: string; } & ZoneApiParameter>, NotificationOutput>(),
3838
};
3939
}
40-
41-
export type CommonWebViewApi<T extends AccountType> = T extends "protonmail"
42-
? ProtonmailApi
43-
: T extends "tutanota"
44-
? TutanotaApi
45-
: never;

src/web/browser-window/app/_accounts/account.component.ts

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import {
1515
ViewRef,
1616
} from "@angular/core";
1717
import {Deferred} from "ts-deferred";
18-
import {Subject, Subscription, combineLatest} from "rxjs";
19-
import {debounceTime, distinctUntilChanged, filter, map, mergeMap, pairwise, startWith, withLatestFrom} from "rxjs/operators";
18+
import {Observable, Subject, Subscription, combineLatest} from "rxjs";
19+
import {debounceTime, distinctUntilChanged, filter, map, mergeMap, pairwise, startWith, takeUntil, withLatestFrom} from "rxjs/operators";
2020
import {equals, pick} from "ramda";
2121

2222
import {ACCOUNTS_ACTIONS, NAVIGATION_ACTIONS} from "src/web/browser-window/app/store/actions";
@@ -58,7 +58,9 @@ const pickLoginDelayFields = ((fields: Array<keyof AccountConfig>) => {
5858
export class AccountComponent extends NgChangesObservableComponent implements OnInit, AfterViewInit, OnDestroy {
5959
@Input()
6060
account!: WebAccount;
61-
account$ = this.ngChangesObservable("account");
61+
account$: Observable<WebAccount> = this
62+
.ngChangesObservable("account")
63+
.pipe(takeUntil(this.ngOnDestroy$));
6264
afterFailedLoadWait: number = 0;
6365
didFailLoadErrorDescription?: string;
6466
private logger: ReturnType<typeof getZoneNameBoundWebLogger>;
@@ -134,7 +136,7 @@ export class AccountComponent extends NgChangesObservableComponent implements On
134136
this.setupOnWebViewDomReadyDeferred().promise
135137
.then(() => {
136138
this.onWebViewLoadedOnce(resolveWebView());
137-
// if ((BUILD_ENVIRONMENT === "development") {
139+
// if ((BUILD_ENVIRONMENT === "development")) {
138140
// resolveWebView().openDevTools();
139141
// }
140142
});
@@ -187,15 +189,15 @@ export class AccountComponent extends NgChangesObservableComponent implements On
187189
);
188190

189191
this.subscription.add(
190-
combineLatest(
192+
combineLatest([
191193
this.store.pipe(
192194
select(OptionsSelectors.FEATURED.electronLocations),
193195
mergeMap((electronLocations) => electronLocations ? [electronLocations] : []),
194196
),
195197
this.account$.pipe(
196198
distinctUntilChanged((prev, curr) => prev.accountConfig.entryUrl === curr.accountConfig.entryUrl),
197199
),
198-
).subscribe(([electronLocations, {accountConfig}]) => {
200+
]).subscribe(([electronLocations, {accountConfig}]) => {
199201
const {type} = accountConfig;
200202
const parsedEntryUrl = this.core.parseEntryUrl(accountConfig, electronLocations);
201203

@@ -274,7 +276,7 @@ export class AccountComponent extends NgChangesObservableComponent implements On
274276
);
275277

276278
this.subscription.add(
277-
combineLatest(
279+
combineLatest([
278280
this.store.pipe(
279281
select(AccountsSelectors.FEATURED.selectedLogin),
280282
),
@@ -283,7 +285,7 @@ export class AccountComponent extends NgChangesObservableComponent implements On
283285
startWith(IPC_MAIN_API_NOTIFICATION_ACTIONS.ActivateBrowserWindow()),
284286
filter(IPC_MAIN_API_NOTIFICATION_ACTIONS.is.ActivateBrowserWindow),
285287
),
286-
).pipe(
288+
]).pipe(
287289
filter(([selectedLogin]) => this.account.accountConfig.login === selectedLogin),
288290
debounceTime(ONE_SECOND_MS * 0.3),
289291
).subscribe(async () => {
@@ -298,19 +300,25 @@ export class AccountComponent extends NgChangesObservableComponent implements On
298300
distinctUntilChanged(),
299301
withLatestFrom(this.account$),
300302
).subscribe(([value, account]) => {
301-
const {accountConfig: {type, login}} = account;
302-
303-
if (
304-
!value
305-
||
306-
value.type !== type
307-
||
308-
value.login !== login
309-
) {
310-
return;
303+
if (value) {
304+
this.dispatchInLoggerZone(ACCOUNTS_ACTIONS.FetchSingleMail({account, webView, mailPk: value.mailPk}));
311305
}
306+
}),
307+
);
312308

313-
this.dispatchInLoggerZone(ACCOUNTS_ACTIONS.FetchSingleMail({account, webView, mailPk: value.mailPk}));
309+
this.subscription.add(
310+
this.account$.pipe(
311+
map((account) => account.makeReadMailParams),
312+
distinctUntilChanged(),
313+
withLatestFrom(this.account$),
314+
).subscribe(([value, account]) => {
315+
if (!value) {
316+
return;
317+
}
318+
const {messageIds, mailsBundleKey} = value;
319+
this.dispatchInLoggerZone(
320+
ACCOUNTS_ACTIONS.MakeMailRead({account, webView, messageIds, mailsBundleKey}),
321+
);
314322
}),
315323
);
316324
}

src/web/browser-window/app/_accounts/accounts.effects.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Actions, createEffect} from "@ngrx/effects";
2-
import {EMPTY, Observable, Subject, concat, from, fromEvent, merge, of, race, throwError, timer} from "rxjs";
2+
import {EMPTY, Observable, concat, from, fromEvent, merge, of, race, throwError, timer} from "rxjs";
33
import {Injectable} from "@angular/core";
44
import {Store, select} from "@ngrx/store";
55
import {
@@ -20,10 +20,10 @@ import {
2020
} from "rxjs/operators";
2121

2222
import {ACCOUNTS_ACTIONS, NOTIFICATION_ACTIONS, OPTIONS_ACTIONS, unionizeActionFilter} from "src/web/browser-window/app/store/actions";
23-
import {AccountTypeAndLoginFieldContainer} from "src/shared/model/container";
2423
import {AccountsSelectors, OptionsSelectors} from "src/web/browser-window/app/store/selectors";
2524
import {CoreService} from "src/web/browser-window/app/_core/core.service";
2625
import {ElectronService} from "src/web/browser-window/app/_core/electron.service";
26+
import {FIRE_SYNCING_ITERATION$} from "src/web/browser-window/app/app.constants";
2727
import {IPC_MAIN_API_NOTIFICATION_ACTIONS} from "src/shared/api/main";
2828
import {ONE_MINUTE_MS, ONE_SECOND_MS} from "src/shared/constants";
2929
import {State} from "src/web/browser-window/app/store/reducers/accounts";
@@ -44,8 +44,6 @@ export class AccountsEffects {
4444
maxInInterval: 2,
4545
});
4646

47-
fireSyncingIteration$ = new Subject<AccountTypeAndLoginFieldContainer>();
48-
4947
syncAccountsConfigs$ = createEffect(
5048
() => this.actions$.pipe(
5149
unionizeActionFilter(OPTIONS_ACTIONS.is.GetSettingsResponse),
@@ -85,7 +83,7 @@ export class AccountsEffects {
8583
withLatestFrom(this.store.pipe(select(AccountsSelectors.ACCOUNTS.pickAccount({login})))),
8684
mergeMap(([notification, account]) => {
8785
if (typeof notification.batchEntityUpdatesCounter === "number") {
88-
this.fireSyncingIteration$.next({type, login});
86+
FIRE_SYNCING_ITERATION$.next({type, login});
8987
return EMPTY;
9088
}
9189

@@ -174,9 +172,9 @@ export class AccountsEffects {
174172
timer(0, ONE_MINUTE_MS * 5).pipe(
175173
tap(() => logger.verbose(`triggered by: timer`)),
176174
),
177-
this.fireSyncingIteration$.pipe(
175+
FIRE_SYNCING_ITERATION$.pipe(
178176
filter((value) => value.type === type && value.login === login),
179-
tap(() => logger.verbose(`triggered by: fireSyncingIteration$`)),
177+
tap(() => logger.verbose(`triggered by: FIRE_SYNCING_ITERATION$`)),
180178
// user might be moving emails from here to there while syncing/"buildDbPatch" cycle is in progress
181179
// debounce call reduces 404 fetch errors as we don't trigger fetching until user got settled down
182180
debounceTime(ONE_SECOND_MS * 3),

src/web/browser-window/app/_db-view/db-view-abstract.component.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import {EMPTY, Observable, of} from "rxjs";
1+
import {EMPTY, Observable, combineLatest, fromEvent, merge, of} from "rxjs";
22
import {Input} from "@angular/core";
33
import {Store, select} from "@ngrx/store";
4-
import {distinctUntilChanged, mergeMap} from "rxjs/operators";
4+
import {distinctUntilChanged, map, mergeMap, startWith} from "rxjs/operators";
55

6+
import {AccountsSelectors, DbViewSelectors} from "src/web/browser-window/app/store/selectors";
67
import {DbAccountPk} from "src/shared/model/database";
7-
import {DbViewSelectors} from "src/web/browser-window/app/store/selectors";
88
import {NgChangesObservableComponent} from "src/web/browser-window/app/components/ng-changes-observable.component";
99
import {State} from "src/web/browser-window/app/store/reducers/db-view";
1010

@@ -16,6 +16,30 @@ export abstract class DbViewAbstractComponent extends NgChangesObservableCompone
1616
mergeMap((value) => value ? of(value) : EMPTY),
1717
);
1818

19+
account$ = this.dbAccountPk$.pipe(
20+
mergeMap(({login}) => this.store.pipe(
21+
select(AccountsSelectors.ACCOUNTS.pickAccount({login})),
22+
mergeMap((value) => value ? [value] : EMPTY),
23+
distinctUntilChanged(),
24+
)),
25+
);
26+
27+
onlineAndSignedIn$: Observable<boolean> = combineLatest([
28+
this.account$.pipe(
29+
map(({notifications}) => notifications.loggedIn),
30+
distinctUntilChanged(),
31+
),
32+
merge(
33+
fromEvent(window, "online"),
34+
fromEvent(window, "offline"),
35+
).pipe(
36+
map(() => navigator.onLine),
37+
startWith(navigator.onLine),
38+
),
39+
]).pipe(
40+
map(([signedIn, online]) => signedIn && online),
41+
);
42+
1943
instance$ = this.dbAccountPk$.pipe(
2044
mergeMap((pk) => this.store.pipe(
2145
select(DbViewSelectors.FEATURED.instance(), {pk}),

src/web/browser-window/app/_db-view/db-view-mail-body.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
</button>
4949
<button
5050
(click)="selectMailOnline()"
51-
[disabled]="(selectingMailOnline$ | async) || !(onlineAndLoggedIn$ | async)"
51+
[disabled]="(selectingMailOnline$ | async) || !(onlineAndSignedIn$ | async)"
5252
class="btn btn-primary mr-2"
5353
type="button"
5454
>
@@ -132,7 +132,7 @@ <h5>{{ selectedMail.conversationMail.subject }}</h5>
132132
</div>
133133
<button
134134
(click)="reDownload()"
135-
[disabled]="!(onlineAndLoggedIn$ | async) || (fetchingSingleMailParams$ | async) || false"
135+
[disabled]="!(onlineAndSignedIn$ | async) || (fetchingSingleMailParams$ | async) || false"
136136
class="btn btn-secondary"
137137
>
138138
<i *ngIf="fetchingSingleMailParams$ | async" class="fa fa-spinner fa-pulse fa-fw"></i>

src/web/browser-window/app/_db-view/db-view-mail-body.component.ts

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ import {
1010
QueryList,
1111
ViewChildren,
1212
} from "@angular/core";
13-
import {BehaviorSubject, EMPTY, Observable, Subject, Subscription, combineLatest, fromEvent, merge, race, throwError, timer} from "rxjs";
13+
import {BehaviorSubject, EMPTY, Observable, Subject, Subscription, combineLatest, race, throwError, timer} from "rxjs";
1414
import {Store, select} from "@ngrx/store";
15-
import {delay, distinctUntilChanged, filter, map, mergeMap, pairwise, startWith, take, withLatestFrom} from "rxjs/operators";
15+
import {delay, distinctUntilChanged, filter, map, mergeMap, pairwise, take, withLatestFrom} from "rxjs/operators";
1616
import {equals} from "ramda";
1717

1818
import {ACCOUNTS_ACTIONS, DB_VIEW_ACTIONS} from "src/web/browser-window/app/store/actions";
19-
import {AccountsSelectors, OptionsSelectors} from "src/web/browser-window/app/store/selectors";
2019
import {DbViewAbstractComponent} from "src/web/browser-window/app/_db-view/db-view-abstract.component";
2120
import {DbViewMailComponent} from "src/web/browser-window/app/_db-view/db-view-mail.component";
2221
import {Mail, View} from "src/shared/model/database";
2322
import {ONE_SECOND_MS} from "src/shared/constants";
23+
import {OptionsSelectors} from "src/web/browser-window/app/store/selectors";
2424
import {State} from "src/web/browser-window/app/store/reducers/db-view";
2525
import {getZoneNameBoundWebLogger} from "src/web/browser-window/util";
2626

@@ -55,30 +55,6 @@ export class DbViewMailBodyComponent extends DbViewAbstractComponent implements
5555
@ViewChildren(DbViewMailComponent, {read: ElementRef})
5656
dbViewMailElementRefs!: QueryList<ElementRef>;
5757

58-
account$ = this.dbAccountPk$.pipe(
59-
mergeMap(({login}) => this.store.pipe(
60-
select(AccountsSelectors.ACCOUNTS.pickAccount({login})),
61-
mergeMap((value) => value ? [value] : EMPTY),
62-
distinctUntilChanged(),
63-
)),
64-
);
65-
66-
onlineAndLoggedIn$: Observable<boolean> = combineLatest([
67-
this.account$.pipe(
68-
map(({notifications}) => notifications.loggedIn),
69-
distinctUntilChanged(),
70-
),
71-
merge(
72-
fromEvent(window, "online"),
73-
fromEvent(window, "offline"),
74-
).pipe(
75-
map(() => navigator.onLine),
76-
startWith(navigator.onLine),
77-
),
78-
]).pipe(
79-
map(([loggedIn, online]) => loggedIn && online),
80-
);
81-
8258
selectingMailOnline$ = this.account$.pipe(
8359
map(({progress}) => progress.selectingMailOnline),
8460
distinctUntilChanged(),
@@ -159,15 +135,15 @@ export class DbViewMailBodyComponent extends DbViewAbstractComponent implements
159135
);
160136

161137
this.subscription.add(
162-
combineLatest(
138+
combineLatest([
163139
this.conversationCollapsed$.pipe(
164140
distinctUntilChanged(),
165141
),
166142
this.selectedMail$.pipe(
167143
map((value) => value.rootNode),
168144
distinctUntilChanged(),
169145
),
170-
).pipe(
146+
]).pipe(
171147
filter(([conversationCollapsed]) => !conversationCollapsed),
172148
delay(ONE_SECOND_MS * 0.2),
173149
).subscribe(() => {
@@ -229,7 +205,7 @@ export class DbViewMailBodyComponent extends DbViewAbstractComponent implements
229205
),
230206
take(1),
231207
).subscribe(([pk, conversationMail]) => {
232-
this.store.dispatch(ACCOUNTS_ACTIONS.SetFetchSingleMailParams({pk, mailPk: conversationMail.pk}));
208+
this.store.dispatch(ACCOUNTS_ACTIONS.FetchSingleMailSetParams({pk, mailPk: conversationMail.pk}));
233209
});
234210
}
235211

src/web/browser-window/app/_db-view/db-view-mails-search.component.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import {
1010
QueryList,
1111
ViewChildren,
1212
} from "@angular/core";
13-
import {EMPTY, Observable, Subject, combineLatest} from "rxjs";
1413
import {FormControl, FormGroup, Validators} from "@angular/forms";
14+
import {Observable, Subject, combineLatest} from "rxjs";
1515
import {Store, select} from "@ngrx/store";
16-
import {distinctUntilChanged, map, mergeMap, takeUntil} from "rxjs/operators";
16+
import {distinctUntilChanged, map, takeUntil} from "rxjs/operators";
1717

1818
import {AccountsSelectors} from "src/web/browser-window/app/store/selectors";
1919
import {DB_VIEW_ACTIONS} from "src/web/browser-window/app/store/actions";
@@ -46,12 +46,9 @@ export class DbViewMailsSearchComponent extends DbViewAbstractComponent implemen
4646
map((value) => value.selectedMail),
4747
);
4848

49-
accountProgress$ = this.dbAccountPk$.pipe(
50-
mergeMap(({login}) => this.store.pipe(
51-
select(AccountsSelectors.ACCOUNTS.pickAccount({login})),
52-
mergeMap((value) => value ? [value.progress] : EMPTY),
53-
distinctUntilChanged(),
54-
)),
49+
accountProgress$ = this.account$.pipe(
50+
map((account) => account.progress),
51+
distinctUntilChanged(),
5552
);
5653

5754
searching$: Observable<boolean> = this.accountProgress$.pipe(

0 commit comments

Comments
 (0)