Compare commits
3 Commits
1d9a184140
...
171a8aa1c0
Author | SHA1 | Date |
---|---|---|
|
171a8aa1c0 | |
|
e93cbbaab2 | |
|
c686f5c8d9 |
|
@ -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";
|
||||
|
@ -25,6 +26,7 @@ export interface IAuth {
|
|||
}
|
||||
|
||||
export interface ClientState {
|
||||
emailState: string | null;
|
||||
inFlight: {
|
||||
emailContents: Set<string>;
|
||||
emailStubs: Set<string>;
|
||||
|
@ -33,7 +35,9 @@ export interface ClientState {
|
|||
mailboxes: {
|
||||
trash: IMailbox;
|
||||
} | null;
|
||||
mailboxState: string | null;
|
||||
session: ISession | null;
|
||||
threadState: string | null;
|
||||
}
|
||||
|
||||
class Account implements IAccount {
|
||||
|
@ -42,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) {
|
||||
|
@ -55,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;
|
||||
|
@ -69,13 +72,16 @@ export default class Client {
|
|||
callbacks: Array<Callback> = [];
|
||||
jclient: jmapclient.Client | null = null;
|
||||
state: ClientState = {
|
||||
emailState: null,
|
||||
inFlight: {
|
||||
emailContents: new Set(),
|
||||
emailStubs: new Set(),
|
||||
mailboxes: new Set(),
|
||||
},
|
||||
mailboxes: null,
|
||||
mailboxState: null,
|
||||
session: null,
|
||||
threadState: null,
|
||||
};
|
||||
|
||||
// Get the currently active account
|
||||
|
@ -174,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];
|
||||
|
@ -247,8 +261,10 @@ export default class Client {
|
|||
receivedAt: e.receivedAt,
|
||||
subject: e.subject,
|
||||
};
|
||||
this._triggerChange(msg + e.id);
|
||||
});
|
||||
this._triggerChange(msg);
|
||||
//this.state.session.state = response.sessionState;
|
||||
this.state.emailState = response.state;
|
||||
})
|
||||
.catch((x) => {
|
||||
console.error("Failed to get email stub", emailId, x);
|
||||
|
@ -295,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];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
@ -315,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);
|
||||
});
|
||||
}
|
||||
|
@ -337,13 +384,70 @@ export default class Client {
|
|||
c();
|
||||
});
|
||||
}
|
||||
_onChangedEmail(accountId: string, state: string) {
|
||||
console.log("Email changed", state);
|
||||
if (this.jclient === null) return;
|
||||
if (this.state.session === null) return;
|
||||
const account = this.account(accountId);
|
||||
if (account === null) return;
|
||||
if (this.state.emailState === null) return;
|
||||
const args = {
|
||||
accountId: accountId,
|
||||
sinceState: this.state.emailState,
|
||||
};
|
||||
this.jclient.email_changes(args).then((r) => {
|
||||
console.log("Handle email changes ", r);
|
||||
});
|
||||
}
|
||||
_onChangedMailbox(accountId: string, state: string) {
|
||||
console.log("Mailbox changed", state);
|
||||
if (this.jclient === null) return;
|
||||
if (this.state.session === null) return;
|
||||
const account = this.account(accountId);
|
||||
if (account === null) return;
|
||||
if (this.state.mailboxState === null) return;
|
||||
const args = {
|
||||
accountId: accountId,
|
||||
sinceState: this.state.mailboxState,
|
||||
};
|
||||
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) {
|
||||
console.log("Thread changed", state);
|
||||
if (this.jclient === null) return;
|
||||
if (this.state.session === null) return;
|
||||
const account = this.account(accountId);
|
||||
if (account === null) return;
|
||||
if (this.state.threadState === null) return;
|
||||
const args = {
|
||||
accountId: accountId,
|
||||
sinceState: this.state.threadState,
|
||||
};
|
||||
this.jclient.thread_changes(args).then((r) => {
|
||||
console.log("Handle thread changes ", r);
|
||||
});
|
||||
}
|
||||
_onSession() {
|
||||
console.log("Session received");
|
||||
|
||||
// For the type checker
|
||||
if (!this.jclient) return;
|
||||
|
||||
const session = this.jclient.getSession();
|
||||
console.log("Session received: ", session);
|
||||
// Subscribe to server-pushed events
|
||||
if (session.eventSourceUrl) {
|
||||
this._subscribeToEventSource(session.eventSourceUrl);
|
||||
|
@ -358,6 +462,7 @@ export default class Client {
|
|||
),
|
||||
emails: {},
|
||||
emailStubs: {},
|
||||
mailboxes: {},
|
||||
};
|
||||
if (!this.state.session) return;
|
||||
this._triggerChange("Session");
|
||||
|
@ -373,7 +478,28 @@ export default class Client {
|
|||
this.jclient.subscribeToEvents(
|
||||
eventSourceUrl,
|
||||
(type: string, message: PushMessage) => {
|
||||
console.log("Got an event!", type, message);
|
||||
if (type === "ping") {
|
||||
return;
|
||||
}
|
||||
if (type === "state") {
|
||||
console.log("Got an event!", type, message);
|
||||
const stateChange = message as jmaptypes.IStateChange;
|
||||
for (const [accountId, changes] of Object.entries(
|
||||
stateChange.changed,
|
||||
)) {
|
||||
for (const [changeType, state] of Object.entries(changes)) {
|
||||
if (changeType === "Email") {
|
||||
this._onChangedEmail(accountId, state);
|
||||
} else if (changeType === "Mailbox") {
|
||||
this._onChangedMailbox(accountId, state);
|
||||
} else if (changeType === "Thread") {
|
||||
this._onChangedThread(accountId, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("Not sure what to do with event", type);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 5cf6129a517224b90f79cb96f212d57c5bceb51f
|
||||
Subproject commit ee2af71a8e764c1a8153a7dcd71f4085f970243f
|
|
@ -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