Update the mailbox list when we get changes.
This is a pretty big change. I'm now storing the mailboxes within the account as a map of IDs, which is more consistent with the rest of the client and makes it easier to update a single mailbox when we know it has changes. I've also added a new function for getting a single mailbox. This isn't really well coded - I need to refactor the client before I can do that properly. But for now the proof-of-concept works: I'm getting push events from the server, I'm getting the changes from my last update state, and I'm applying them to the UI. This is essentially the whole point for doing React and doing JMAP. Woot.
This commit is contained in:
parent
e93cbbaab2
commit
171a8aa1c0
|
@ -4,7 +4,7 @@ import Stack from "react-bootstrap/Stack";
|
|||
import { IAccount, IEmail, IMailbox } from "./client/types";
|
||||
import Client from "./client/Client";
|
||||
import EmailContent from "./EmailContent";
|
||||
import EmailList from "./EmailList";
|
||||
import Mailbox from "./Mailbox";
|
||||
|
||||
type EmailAreaProps = {
|
||||
account: IAccount | null;
|
||||
|
@ -17,7 +17,7 @@ type EmailAreaProps = {
|
|||
const EmailArea: React.FC<EmailAreaProps> = (props) => {
|
||||
if (props.emailId === "") {
|
||||
return (
|
||||
<EmailList
|
||||
<Mailbox
|
||||
account={props.account}
|
||||
client={props.client}
|
||||
mailbox={props.mailbox}
|
||||
|
@ -26,7 +26,7 @@ const EmailArea: React.FC<EmailAreaProps> = (props) => {
|
|||
} else {
|
||||
return (
|
||||
<Stack className="text-start">
|
||||
<EmailList
|
||||
<Mailbox
|
||||
account={props.account}
|
||||
client={props.client}
|
||||
mailbox={props.mailbox}
|
||||
|
|
|
@ -1,19 +1,37 @@
|
|||
import React from "react";
|
||||
|
||||
import { IAccount, IMailbox } from "./client/types";
|
||||
import Client from "./client/Client";
|
||||
import EmailList from "./EmailList";
|
||||
|
||||
type MailboxProps = {
|
||||
accountId: string;
|
||||
id: string;
|
||||
name: string;
|
||||
account: IAccount | null;
|
||||
client: Client;
|
||||
mailbox: IMailbox | null;
|
||||
};
|
||||
|
||||
const Mailbox: React.FC<MailboxProps> = ({ accountId, id, name }) => {
|
||||
const href = "#" + accountId + "/" + id;
|
||||
type MailboxState = {};
|
||||
|
||||
return (
|
||||
<a href={href} className="p-2" key={id}>
|
||||
{name}
|
||||
</a>
|
||||
class Mailbox extends React.Component<MailboxProps, MailboxState> {
|
||||
componentDidUpdate() {
|
||||
if (this.props.account == null) return;
|
||||
if (this.props.client == null) return;
|
||||
if (this.props.mailbox == null) return;
|
||||
this.props.client.ensureMailbox(
|
||||
this.props.account.id,
|
||||
this.props.mailbox.id,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EmailList
|
||||
account={this.props.account}
|
||||
client={this.props.client}
|
||||
mailbox={this.props.mailbox}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Mailbox;
|
||||
|
|
|
@ -3,7 +3,7 @@ import Stack from "react-bootstrap/Stack";
|
|||
|
||||
import Client from "./client/Client";
|
||||
import { IAccount } from "./client/types";
|
||||
import Mailbox from "./Mailbox";
|
||||
import MailboxSummary from "./MailboxSummary";
|
||||
|
||||
type MailboxListProps = {
|
||||
account: IAccount | null;
|
||||
|
@ -25,8 +25,8 @@ class MailboxList extends React.Component<MailboxListProps, MailboxListState> {
|
|||
<Stack />
|
||||
) : (
|
||||
<Stack>
|
||||
{this.props.account.mailboxes.map((m) => (
|
||||
<Mailbox
|
||||
{Object.values(this.props.account.mailboxes).map((m) => (
|
||||
<MailboxSummary
|
||||
accountId={this.props.account!.id}
|
||||
id={m.id}
|
||||
key={m.id}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import React from "react";
|
||||
|
||||
type MailboxProps = {
|
||||
accountId: string;
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
const Mailbox: React.FC<MailboxProps> = ({ accountId, id, name }) => {
|
||||
const href = "#" + accountId + "/" + id;
|
||||
|
||||
return (
|
||||
<a href={href} className="p-2" key={id}>
|
||||
{name}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default Mailbox;
|
|
@ -13,6 +13,7 @@ import {
|
|||
IEmailStub,
|
||||
IMailbox,
|
||||
ISession,
|
||||
MailboxIdMap,
|
||||
MailboxRole,
|
||||
PushMessage,
|
||||
} from "./types";
|
||||
|
@ -45,7 +46,7 @@ class Account implements IAccount {
|
|||
accountCapabilities: { [key: string]: jmaptypes.IMailCapabilities };
|
||||
isPersonal: boolean;
|
||||
isReadOnly: boolean;
|
||||
mailboxes: Array<IMailbox> | null;
|
||||
mailboxes: MailboxIdMap | null;
|
||||
name: string;
|
||||
|
||||
constructor(id: string, props: jmaptypes.IAccount) {
|
||||
|
@ -58,10 +59,9 @@ class Account implements IAccount {
|
|||
}
|
||||
mailboxByRole(role: MailboxRole): IMailbox | null {
|
||||
if (this.mailboxes === null) return null;
|
||||
for (let i = 0; i < this.mailboxes.length; i++) {
|
||||
const m = this.mailboxes[i];
|
||||
if (m.role === role) {
|
||||
return m;
|
||||
for (const mailbox of Object.values(this.mailboxes)) {
|
||||
if (mailbox.role === role) {
|
||||
return mailbox;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -180,6 +180,14 @@ export default class Client {
|
|||
this.doLogin(auth);
|
||||
}
|
||||
|
||||
// Ensure we have the full email content
|
||||
ensureMailbox(accountId: string, mailboxId: string) {
|
||||
if (this.state.session == null) return;
|
||||
const existing = this.state.session.mailboxes[mailboxId];
|
||||
if (existing != null) return;
|
||||
this.mailboxList(accountId, [mailboxId]);
|
||||
}
|
||||
|
||||
email(emailId: string): IEmail | null {
|
||||
if (this.state.session == null) return null;
|
||||
const result = this.state.session.emails[emailId];
|
||||
|
@ -303,12 +311,39 @@ export default class Client {
|
|||
if (this.state.session == null) return null;
|
||||
const account = this.state.session.accounts[accountId];
|
||||
if (account.mailboxes == null) return null;
|
||||
for (let i = 0; i < account.mailboxes.length; i++) {
|
||||
if (account.mailboxes[i].id === mailboxId) {
|
||||
return account.mailboxes[i];
|
||||
const mailbox = account.mailboxes[mailboxId];
|
||||
return mailbox;
|
||||
}
|
||||
mailboxGet(accountId: string, id: string) {
|
||||
if (this.jclient == null) return;
|
||||
if (this.state.session == null) return;
|
||||
// TODO: do this in a single request with 2 queries when the client can handle it.
|
||||
this.jclient
|
||||
.mailbox_get({
|
||||
accountId: accountId,
|
||||
ids: [id],
|
||||
})
|
||||
.then((response) => {
|
||||
if (this.state.session == null) return;
|
||||
const account = this.state.session.accounts[response.accountId];
|
||||
if (response.list.length !== 1) {
|
||||
console.error(
|
||||
"Should only get 1 mailbox in mailboxGet. Got ",
|
||||
response.list,
|
||||
);
|
||||
return;
|
||||
}
|
||||
return null;
|
||||
const m = response.list[0];
|
||||
if (account.mailboxes === null) {
|
||||
account.mailboxes = {};
|
||||
}
|
||||
account.mailboxes[m.id] = {
|
||||
...m,
|
||||
emailIds: null,
|
||||
};
|
||||
this.state.mailboxState = response.state;
|
||||
this.emailList(accountId, id, []);
|
||||
});
|
||||
}
|
||||
mailboxList(accountId: string, ids: Array<string>) {
|
||||
if (this.jclient == null) return;
|
||||
|
@ -323,14 +358,18 @@ export default class Client {
|
|||
.then((response) => {
|
||||
if (this.state.session == null) return;
|
||||
const account = this.state.session.accounts[response.accountId];
|
||||
const mailboxes: Array<IMailbox> = [];
|
||||
const mailboxes: MailboxIdMap = {};
|
||||
response.list.forEach((m) => {
|
||||
mailboxes.push({
|
||||
// If we already have a mailbox with emails then don't throw that data away
|
||||
const existing =
|
||||
account.mailboxes === null ? null : account.mailboxes[m.id];
|
||||
mailboxes[m.id] = {
|
||||
...m,
|
||||
emailIds: null,
|
||||
});
|
||||
emailIds: existing === null ? null : existing.emailIds,
|
||||
};
|
||||
});
|
||||
account.mailboxes = mailboxes;
|
||||
this.state.mailboxState = response.state;
|
||||
this._triggerChange("Mailboxes " + accountId);
|
||||
});
|
||||
}
|
||||
|
@ -369,10 +408,23 @@ export default class Client {
|
|||
if (this.state.mailboxState === null) return;
|
||||
const args = {
|
||||
accountId: accountId,
|
||||
sinceState: this.state.session.state,
|
||||
sinceState: this.state.mailboxState,
|
||||
};
|
||||
this.jclient.mailbox_changes(args).then((r) => {
|
||||
console.log("Handle mailbox changes ", r);
|
||||
this.jclient.mailbox_changes(args).then((response) => {
|
||||
console.log("Handle mailbox changes ", response);
|
||||
for (let i = 0; i < response.created.length; i++) {
|
||||
const mailboxId = response.created[i];
|
||||
this.mailboxGet(accountId, mailboxId);
|
||||
}
|
||||
for (let i = 0; i < response.updated.length; i++) {
|
||||
const mailboxId = response.updated[i];
|
||||
this.mailboxGet(accountId, mailboxId);
|
||||
}
|
||||
if (this.state.session === null) return;
|
||||
for (let i = 0; i < response.destroyed.length; i++) {
|
||||
const mailboxId = response.destroyed[i];
|
||||
delete this.state.session.mailboxes[mailboxId];
|
||||
}
|
||||
});
|
||||
}
|
||||
_onChangedThread(accountId: string, state: string) {
|
||||
|
@ -384,7 +436,7 @@ export default class Client {
|
|||
if (this.state.threadState === null) return;
|
||||
const args = {
|
||||
accountId: accountId,
|
||||
sinceState: this.state.session.state,
|
||||
sinceState: this.state.threadState,
|
||||
};
|
||||
this.jclient.thread_changes(args).then((r) => {
|
||||
console.log("Handle thread changes ", r);
|
||||
|
@ -410,6 +462,7 @@ export default class Client {
|
|||
),
|
||||
emails: {},
|
||||
emailStubs: {},
|
||||
mailboxes: {},
|
||||
};
|
||||
if (!this.state.session) return;
|
||||
this._triggerChange("Session");
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as client from "./jmap-client-ts/src/types";
|
||||
|
||||
export type MailboxIdMap = { [mailboxId: string]: boolean };
|
||||
export type MailboxIdMapPresence = { [mailboxId: string]: boolean };
|
||||
export interface IEmailStub {
|
||||
from: Array<client.IEmailAddress>;
|
||||
id: string;
|
||||
mailboxIds: MailboxIdMap;
|
||||
mailboxIds: MailboxIdMapPresence;
|
||||
receivedAt: string;
|
||||
subject: string;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ export interface IEmailStub {
|
|||
export interface IMailbox extends client.IMailboxProperties {
|
||||
emailIds: Array<string> | null;
|
||||
}
|
||||
export type MailboxIdMap = { [mailboxId: string]: IMailbox };
|
||||
|
||||
export interface IEmail extends client.IEmailProperties {
|
||||
//sentAt: IutcDate|null,
|
||||
|
@ -19,7 +20,7 @@ export interface IEmail extends client.IEmailProperties {
|
|||
|
||||
export interface IAccount extends client.IAccount {
|
||||
id: string;
|
||||
mailboxes: Array<IMailbox> | null;
|
||||
mailboxes: MailboxIdMap | null;
|
||||
|
||||
mailboxByRole(role: MailboxRole): IMailbox | null;
|
||||
}
|
||||
|
@ -42,4 +43,5 @@ export interface ISession extends client.ISession {
|
|||
accounts: AccountIdMap;
|
||||
emails: EmailIdMap;
|
||||
emailStubs: EmailStubIdMap;
|
||||
mailboxes: MailboxIdMap;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue