diff --git a/app/internal_packages/composer-signature/lib/signature-photo-picker.jsx b/app/internal_packages/composer-signature/lib/signature-photo-picker.jsx
index 00ee8930e1..24b8d9567e 100644
--- a/app/internal_packages/composer-signature/lib/signature-photo-picker.jsx
+++ b/app/internal_packages/composer-signature/lib/signature-photo-picker.jsx
@@ -31,6 +31,9 @@ export default class SignaturePhotoPicker extends React.Component {
AppEnv.showOpenDialog(
{ title: 'Choose an image', buttonLabel: 'Choose', properties: ['openFile'] },
paths => {
+ if (!this._isMounted) {
+ return;
+ }
if (paths && paths.length > 0) {
this._onChooseImageFilePath(paths[0]);
}
diff --git a/app/internal_packages/composer-templates/lib/preferences-templates.jsx b/app/internal_packages/composer-templates/lib/preferences-templates.jsx
index d57214e609..f5ae26726d 100644
--- a/app/internal_packages/composer-templates/lib/preferences-templates.jsx
+++ b/app/internal_packages/composer-templates/lib/preferences-templates.jsx
@@ -41,6 +41,12 @@ class TemplateEditor extends React.Component {
readOnly: !props.template,
};
}
+ componentDidMount() {
+ this._mounted = true;
+ }
+ componentWillUnmount() {
+ this._mounted = false;
+ }
_onSave = () => {
if (this.state.readOnly) {
@@ -74,7 +80,7 @@ class TemplateEditor extends React.Component {
_onAddAttachment = () => {
AppEnv.cachePreferenceFiles(paths => {
- if (!paths || !paths.length) {
+ if (!paths || !paths.length || !this._mounted) {
return;
}
const addAttachments = paths.map(p => {
diff --git a/app/internal_packages/composer-templates/lib/template-store.es6 b/app/internal_packages/composer-templates/lib/template-store.es6
index f0df4b86af..dc09c7fc0c 100644
--- a/app/internal_packages/composer-templates/lib/template-store.es6
+++ b/app/internal_packages/composer-templates/lib/template-store.es6
@@ -432,7 +432,7 @@ class TemplateStore extends MailspringStore {
AppEnv.reportError(new Error('Template Creation Error'), {
errorData: message,
});
- remote.dialog.showErrorBox('Template Creation Error', message);
+ AppEnv.showErrorBox('Template Creation Error', message);
}
_getPureBodyForDraft(body) {
diff --git a/app/internal_packages/composer-translate/lib/main.jsx b/app/internal_packages/composer-translate/lib/main.jsx
index 7e86c47040..2b58a19928 100644
--- a/app/internal_packages/composer-translate/lib/main.jsx
+++ b/app/internal_packages/composer-translate/lib/main.jsx
@@ -54,7 +54,7 @@ class TranslateButton extends React.Component {
_onError(error) {
Actions.closePopover();
const dialog = require('electron').remote.dialog;
- dialog.showErrorBox('Language Conversion Failed', error.toString());
+ AppEnv.showErrorDialog({ title: 'Language Conversion Failed', message: error.toString() });
}
_onTranslate = async lang => {
diff --git a/app/internal_packages/edison-beijing-chat/components/messages/MessagesTopBar.jsx b/app/internal_packages/edison-beijing-chat/components/messages/MessagesTopBar.jsx
index 11df729365..1125e01037 100644
--- a/app/internal_packages/edison-beijing-chat/components/messages/MessagesTopBar.jsx
+++ b/app/internal_packages/edison-beijing-chat/components/messages/MessagesTopBar.jsx
@@ -72,7 +72,7 @@ export default class MessagesTopBar extends Component {
return;
}
if (!conversationName.trim()) {
- dialog.showMessageBox({
+ AppEnv.showMessageBox({
type: 'warning',
message: 'Group name should NOT be empty or blank.',
buttons: ['OK'],
diff --git a/app/internal_packages/edison-beijing-chat/utils/electron-utils.es6 b/app/internal_packages/edison-beijing-chat/utils/electron-utils.es6
index 5275eb9576..3eed8841b4 100644
--- a/app/internal_packages/edison-beijing-chat/utils/electron-utils.es6
+++ b/app/internal_packages/edison-beijing-chat/utils/electron-utils.es6
@@ -17,7 +17,7 @@ export const postNotification = (title, body, onActivate = () => {}) => {
};
export const alert = message => {
- dialog.showMessageBox({
+ AppEnv.showMessageBox({
type: 'warning',
message,
buttons: ['OK'],
diff --git a/app/internal_packages/jira-plugin/lib/jira-detail.jsx b/app/internal_packages/jira-plugin/lib/jira-detail.jsx
index a645a6116f..e5303ec687 100644
--- a/app/internal_packages/jira-plugin/lib/jira-detail.jsx
+++ b/app/internal_packages/jira-plugin/lib/jira-detail.jsx
@@ -265,7 +265,7 @@ export default class JiraDetail extends Component {
currentWin.previewFile(path);
};
_showDialog(message, type = 'info') {
- remote.dialog.showMessageBox({
+ AppEnv.showMessageBox({
type,
buttons: ['OK'],
message,
diff --git a/app/internal_packages/message-window/lib/main.es6 b/app/internal_packages/message-window/lib/main.es6
new file mode 100644
index 0000000000..7d4077cad3
--- /dev/null
+++ b/app/internal_packages/message-window/lib/main.es6
@@ -0,0 +1,14 @@
+import { WorkspaceStore, ComponentRegistry } from 'mailspring-exports';
+import MessageWindowRoot from './message-window-root';
+
+export function activate() {
+ WorkspaceStore.defineSheet('Main', { root: true }, { list: ['Center'] });
+
+ ComponentRegistry.register(MessageWindowRoot, {
+ location: WorkspaceStore.Location.Center,
+ });
+}
+
+export function deactivate() {}
+
+export function serialize() {}
diff --git a/app/internal_packages/message-window/lib/message-window-root.jsx b/app/internal_packages/message-window/lib/message-window-root.jsx
new file mode 100644
index 0000000000..dae36cb513
--- /dev/null
+++ b/app/internal_packages/message-window/lib/message-window-root.jsx
@@ -0,0 +1,137 @@
+import React from 'react';
+import { MessageWindow } from 'mailspring-component-kit';
+import { Actions, Constant } from 'mailspring-exports';
+import { ipcRenderer } from 'electron';
+const minimumDetailHeight = 28;
+export default class MessageWindowRoot extends React.PureComponent {
+ static displayName = 'MessageWindowRoot';
+ static containerRequired = false;
+
+ constructor(props) {
+ super(props);
+ this.state = this.defaultState();
+ this._mounted = false;
+ this._details = null;
+ }
+ defaultState = () => {
+ return {
+ title: '',
+ detail: '',
+ buttons: [
+ { label: 'Ok', order: 0, originalIndex: 0 },
+ { label: 'Cancel', order: 1, originalIndex: 1 },
+ ],
+ checkboxLabel: '',
+ checkboxChecked: false,
+ defaultId: 0,
+ cancelId: 1,
+ sourceWindowKey: '',
+ requestId: '',
+ };
+ };
+ componentDidMount() {
+ this._mounted = true;
+ ipcRenderer.on('reserveWindow-popout', this._onShowMessages);
+ }
+ componentDidUpdate(prevProps, prevState, snapshot) {
+ AppEnv.logDebug(`requestId ${this.state.requestId}`);
+ this._updateBrowserWindowHeight();
+ if (this.state.requestId.length > 0) {
+ this.windowShow();
+ }
+ }
+
+ componentWillUnmount() {
+ this._mounted = false;
+ ipcRenderer.removeListener('reserveWindow-popout', this._onShowMessages);
+ }
+ _onShowMessages = (
+ event,
+ {
+ title = '',
+ details = '',
+ checkLabel = '',
+ buttons,
+ defaultId,
+ cancelId,
+ targetWindowKey = '',
+ sourceWindowKey = '',
+ requestId = '',
+ } = {}
+ ) => {
+ // const currentKey = AppEnv.getCurrentWindowKey();
+ // if (targetWindowKey !== currentKey) {
+ // AppEnv.logDebug(
+ // `not for current window ${currentKey}, targetWindowKey ${targetWindowKey}, ignoring`
+ // );
+ // return;
+ // }
+ if (this._mounted) {
+ // this._updateBrowserWindowWidth(buttons.length, title.length);
+ this.setState({
+ title,
+ details,
+ checkLabel,
+ defaultId,
+ cancelId,
+ buttons,
+ sourceWindowKey,
+ requestId,
+ });
+ if (typeof title === 'string' && title.length > 0) {
+ AppEnv.setWindowTitle(title);
+ }
+ }
+ AppEnv.logDebug(`MessageWindow: on show Message ${requestId}`);
+ };
+ _updateBrowserWindowHeight = () => {
+ const currentSize = AppEnv.getSize();
+ if (this._details) {
+ const detailsHeight = this._details.getBoundingClientRect().height;
+ const extraHeight = detailsHeight - minimumDetailHeight;
+ // console.log(
+ // `update height detailHeight: ${detailsHeight}, current height: ${currentSize.height}, extraHeight: ${extraHeight}`
+ // );
+ if (
+ extraHeight > 0 &&
+ currentSize.height - detailsHeight <=
+ Constant.MessageWindowSize.height - minimumDetailHeight
+ ) {
+ currentSize.height += extraHeight;
+ AppEnv.setSize(currentSize.width, currentSize.height);
+ }
+ }
+ };
+ _updateBrowserWindowWidth = (buttonsSize, titleLength) => {
+ const currentSize = AppEnv.getSize();
+ const extraTitleLength = Math.ceil((titleLength - 55) * 5.5);
+ const extraButtonLength = (buttonsSize - 2) * 112;
+ if (extraTitleLength > extraButtonLength && extraTitleLength > 0) {
+ currentSize.width += extraTitleLength;
+ } else if (extraButtonLength >= extraTitleLength && extraButtonLength > 0) {
+ currentSize.width += extraButtonLength;
+ }
+ AppEnv.setSize(currentSize.width, currentSize.height);
+ };
+ windowShow = () => {
+ AppEnv.center();
+ AppEnv.show();
+ };
+ windowHide = () => {
+ AppEnv.hide();
+ if (this._mounted) {
+ this.setState(this.defaultState());
+ AppEnv.setSize(Constant.MessageWindowSize.width, Constant.MessageWindowSize.height);
+ }
+ };
+ _onCancel = () => {
+ this.windowHide();
+ };
+ _onClick = () => {
+ this.windowHide();
+ };
+
+ render() {
+ return ;
+ }
+}
diff --git a/app/internal_packages/message-window/package.json b/app/internal_packages/message-window/package.json
new file mode 100755
index 0000000000..4778df6aa0
--- /dev/null
+++ b/app/internal_packages/message-window/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "messageWindow",
+ "version": "0.1.0",
+ "main": "./lib/main",
+ "description": "Message Window",
+ "syncInit": false,
+ "license": "GPL-3.0",
+ "private": true,
+ "engines": {
+ "mailspring": "*"
+ },
+ "windowTypes": {
+ "messageWindow": true
+ }
+}
diff --git a/app/internal_packages/preferences/lib/components/preferences-account-details.jsx b/app/internal_packages/preferences/lib/components/preferences-account-details.jsx
index 5dfaa584dd..fdacaecc87 100644
--- a/app/internal_packages/preferences/lib/components/preferences-account-details.jsx
+++ b/app/internal_packages/preferences/lib/components/preferences-account-details.jsx
@@ -220,30 +220,31 @@ class PreferencesAccountDetails extends Component {
if (drafts.length > 0) {
details = `There are ${drafts.length} draft(s) for this account that are currently open.\n Do you want to proceed with deleting account ${account.emailAddress}?\n Deleting account will also close these drafts.`;
}
- const chosen = remote.dialog.showMessageBoxSync({
+ AppEnv.showMessageBox({
type: 'info',
message: 'Are you sure?',
detail: details,
buttons: ['Delete', 'Cancel'],
defaultId: 0,
cancelId: 1,
- });
- if (chosen !== 0) {
- return;
- }
- const openWindowsCount = AppEnv.getOpenWindowsCountByAccountId(account.id);
- if (openWindowsCount > 0) {
- AppEnv.closeWindowsByAccountId(account.id, 'account deleted');
- }
- const index = this.props.accounts.indexOf(account);
- if (account && typeof onRemoveAccount === 'function') {
- // Move the selection 1 up or down after deleting
- const newIndex = index === 0 ? index + 1 : index - 1;
- onRemoveAccount(account);
- if (this.props.accounts[newIndex] && typeof onSelectAccount === 'function') {
- onSelectAccount(this.props.accounts[newIndex]);
+ }).then(({ response } = {}) => {
+ if (response !== 0) {
+ return;
}
- }
+ const openWindowsCount = AppEnv.getOpenWindowsCountByAccountId(account.id);
+ if (openWindowsCount > 0) {
+ AppEnv.closeWindowsByAccountId(account.id, 'account deleted');
+ }
+ const index = this.props.accounts.indexOf(account);
+ if (account && typeof onRemoveAccount === 'function') {
+ // Move the selection 1 up or down after deleting
+ const newIndex = index === 0 ? index + 1 : index - 1;
+ onRemoveAccount(account);
+ if (this.props.accounts[newIndex] && typeof onSelectAccount === 'function') {
+ onSelectAccount(this.props.accounts[newIndex]);
+ }
+ }
+ });
};
// Renderers
diff --git a/app/internal_packages/preferences/lib/components/preferences-general-components.jsx b/app/internal_packages/preferences/lib/components/preferences-general-components.jsx
index eaaea1aefe..6fdd488f1f 100644
--- a/app/internal_packages/preferences/lib/components/preferences-general-components.jsx
+++ b/app/internal_packages/preferences/lib/components/preferences-general-components.jsx
@@ -212,6 +212,13 @@ export class DownloadSelection extends React.Component {
],
fixedOptions: ['Downloads', 'Ask me every time'],
};
+ this._mounted = false;
+ }
+ componentDidMount() {
+ this._mounted = true;
+ }
+ componentWillUnmount() {
+ this._mounted = false;
}
_onChangeValue = ([value]) => {
@@ -234,6 +241,9 @@ export class DownloadSelection extends React.Component {
}
}
AppEnv.showOpenDialog(openDirOption, newPaths => {
+ if (!this._mounted) {
+ return;
+ }
if (newPaths && newPaths.length > 0) {
this.props.config.set(this.props.keyPath, newPaths[0]);
}
diff --git a/app/internal_packages/preferences/lib/components/preferences-keymaps.jsx b/app/internal_packages/preferences/lib/components/preferences-keymaps.jsx
index 4742b79b17..f6a3c5e6da 100644
--- a/app/internal_packages/preferences/lib/components/preferences-keymaps.jsx
+++ b/app/internal_packages/preferences/lib/components/preferences-keymaps.jsx
@@ -38,19 +38,20 @@ export class PreferencesKeymapsHearder extends React.Component {
}
_onDeleteUserKeymap() {
- const chosen = remote.dialog.showMessageBoxSync(AppEnv.getCurrentWindow(), {
+ AppEnv.showMessageBox({
type: 'info',
message: 'Are you sure?',
detail: 'Delete your custom key bindings and reset to the template defaults?',
buttons: ['Cancel', 'Reset'],
defaultId: 1,
cancelId: 0,
+ blockWindowKey: AppEnv.getCurrentWindowKey(),
+ }).then(({ response }) => {
+ if (response === 1) {
+ const keymapsFile = AppEnv.keymaps.getUserKeymapPath();
+ fs.writeFileSync(keymapsFile, '{}');
+ }
});
-
- if (chosen === 1) {
- const keymapsFile = AppEnv.keymaps.getUserKeymapPath();
- fs.writeFileSync(keymapsFile, '{}');
- }
}
render() {
diff --git a/app/src/app-env.es6 b/app/src/app-env.es6
index 1619972ba9..e0a6baf27f 100644
--- a/app/src/app-env.es6
+++ b/app/src/app-env.es6
@@ -13,6 +13,8 @@ import WindowEventHandler from './window-event-handler';
import { createHash } from 'crypto';
import { dirExists, autoGenerateFileName, transfornImgToBase64 } from './fs-utils';
import RegExpUtils from './regexp-utils';
+import { WindowTypes } from './constant';
+
import { WindowLevel } from './constant';
//Hinata gets special treatment for logging and other debugging purposes
@@ -29,6 +31,11 @@ let getDeviceHash = null;
const WebServerApiKey = 'bdH0VGExAEIhPq0z5vwdyVuHVzWx0hcR';
const WebServerRoot = 'https://web-marketing.edison.tech/';
const type = 'mac';
+let actions = null;
+const Actions = () => {
+ actions = actions || require('mailspring-exports').Actions;
+ return actions;
+};
function ensureInteger(f, fallback) {
let int = f;
@@ -623,17 +630,23 @@ export default class AppEnvConstructor {
}
isComposerWindow() {
- return this.getWindowType() === 'composer';
+ return this.getWindowType() === WindowTypes.COMPOSER_WINDOW;
}
isThreadWindow() {
- return this.getWindowType() === 'thread-popout';
+ return this.getWindowType() === WindowTypes.THREAD_WINDOW;
}
isOnboardingWindow() {
- return this.getWindowType() === 'onboarding';
+ return this.getWindowType() === WindowTypes.ONBOARDING_WINDOW;
}
isBugReportingWindow() {
- return this.getWindowType() === 'bugreport';
+ return this.getWindowType() === WindowTypes.BUG_REPORT_WINDOW;
+ }
+ isMessageWindow() {
+ return this.getWindowType() === WindowTypes.MESSAGE_WINDOW;
+ }
+ isDialogWindow() {
+ return this.getWindowType() === WindowTypes.DIALOG_WINDOW;
}
isDisableZoomWindow() {
@@ -767,7 +780,16 @@ export default class AppEnvConstructor {
}
hide() {
- return this.getCurrentWindow().hide();
+ if (this.isMainWindow()) {
+ this.getCurrentWindow().hide();
+ } else {
+ ipcRenderer.send(
+ 'call-window-manager-method',
+ 'hideReserveWindow',
+ this.getCurrentWindowKey(),
+ this.getWindowType()
+ );
+ }
}
quit() {
@@ -789,6 +811,9 @@ export default class AppEnvConstructor {
setSize(width, height) {
return this.getCurrentWindow().setSize(ensureInteger(width, 100), ensureInteger(height, 100));
}
+ setParentWindow(parent) {
+ this.getCurrentWindow().setParentWindow(parent);
+ }
setMinimumWidth(minWidth) {
const win = this.getCurrentWindow();
@@ -826,6 +851,14 @@ export default class AppEnvConstructor {
getCurrentWindow() {
return this.constructor.getCurrentWindow();
}
+ getCurrentWindowKey() {
+ const currentBrowserWindow = this.getCurrentWindow();
+ const current = this.getOpenWindows().find(w => w.browserWindow === currentBrowserWindow);
+ if (current) {
+ return current.windowKey;
+ }
+ return '';
+ }
getOpenWindows(type = 'all') {
try {
return remote.getGlobal('application').windowManager.getOpenWindows(type);
@@ -892,7 +925,20 @@ export default class AppEnvConstructor {
// Extended: Show the current window.
show() {
- return ipcRenderer.send('call-window-method', 'show');
+ const win = this.getCurrentWindow();
+ if (win) {
+ if (this.isMainWindow()) {
+ win.show();
+ } else {
+ this.logDebug(`showReserveWindow ${this.getCurrentWindowKey()}, ${this.getWindowType()}`);
+ ipcRenderer.send(
+ 'call-window-manager-method',
+ 'showReserveWindow',
+ this.getCurrentWindowKey(),
+ this.getWindowType()
+ );
+ }
+ }
}
restore() {
@@ -910,10 +956,10 @@ export default class AppEnvConstructor {
return this.getCurrentWindow().isVisible();
}
- // Extended: Hide the current window.
- hide() {
- return ipcRenderer.send('call-window-method', 'hide');
- }
+ // // Extended: Hide the current window.
+ // hide() {
+ // return ipcRenderer.send('call-window-method', 'hide');
+ // }
// Extended: Reload the current window.
reload() {
@@ -1075,7 +1121,7 @@ export default class AppEnvConstructor {
}
async startWindow() {
- const { windowType } = this.getLoadSettings();
+ const { windowType, windowKey } = this.getLoadSettings();
this.themes.loadStaticStylesheets();
this.initializeBasicSheet();
@@ -1090,6 +1136,7 @@ export default class AppEnvConstructor {
this.menu.update();
ipcRenderer.send('window-command', 'window:loaded');
+ this.logDebug(`This is window ${windowKey}`);
});
});
});
@@ -1167,6 +1214,23 @@ export default class AppEnvConstructor {
newWindow(options = {}) {
return ipcRenderer.send('new-window', options);
}
+ ensureModalMessageWindow = () => {
+ if (this.isMessageWindow()) {
+ this.logDebug(`This is Message window, ignoring`);
+ return;
+ }
+ const options = {};
+ const currentKey = this.getCurrentWindowKey();
+ if (currentKey) {
+ options.windowKey = `messageWindow-${currentKey}`;
+ options.parentWindowKey = currentKey;
+ ipcRenderer.send('ensure-modal-message-window', options);
+ } else {
+ this.logError(
+ new Error(`Unable to create modal message window, cannot get current window key`)
+ );
+ }
+ };
updateWindowKey({ oldKey, newKey, newOptions = {} } = {}) {
const opts = { oldKey, newKey, newOptions };
return ipcRenderer.send('update-window-key', opts);
@@ -1290,8 +1354,12 @@ export default class AppEnvConstructor {
}
showOpenDialog(options, callback) {
+ let win = null;
+ if (options.modal) {
+ win = this.getCurrentWindow();
+ }
return remote.dialog
- .showOpenDialog(this.getCurrentWindow(), {
+ .showOpenDialog(win, {
...options,
securityScopedBookmarks: !!process.mas,
})
@@ -1307,9 +1375,13 @@ export default class AppEnvConstructor {
});
}
- showImageSelectionDialog(cb) {
+ showImageSelectionDialog(cb, modal = false) {
+ let win = null;
+ if (modal) {
+ win = this.getCurrentWindow();
+ }
return remote.dialog
- .showOpenDialog(this.getCurrentWindow(), {
+ .showOpenDialog(win, {
properties: ['openFile', 'multiSelections'],
filters: [
{
@@ -1327,9 +1399,13 @@ export default class AppEnvConstructor {
});
}
- showBase64ImageTransformDialog(cb, maxSize = 0) {
+ showBase64ImageTransformDialog(cb, maxSize = 0, modal = false) {
+ let win = null;
+ if (modal) {
+ win = this.getCurrentWindow();
+ }
return remote.dialog
- .showOpenDialog(this.getCurrentWindow(), {
+ .showOpenDialog(win, {
properties: ['openFile'],
filters: [
{
@@ -1379,7 +1455,7 @@ export default class AppEnvConstructor {
resolve(path.join(downloadPath, fileNewName));
} else {
resolve('');
- remote.dialog.showErrorBox('File Save Error', errorMsg);
+ this.showErrorDialog({ title: 'File Save Error', message: errorMsg });
}
return;
} catch (e) {
@@ -1397,18 +1473,20 @@ export default class AppEnvConstructor {
title: options.title || 'Save File',
securityScopedBookmarks: !!process.mas,
};
- remote.dialog
- .showSaveDialog(this.getCurrentWindow(), optionTmp)
- .then(({ canceled, filePath, bookmark }) => {
- if (canceled) {
- resolve('');
- } else {
- if (bookmark) {
- this.setBookMarkForPath(filePath, bookmark);
- }
- resolve(filePath);
+ let win = null;
+ if (options.modal) {
+ win = this.getCurrentWindow();
+ }
+ remote.dialog.showSaveDialog(win, optionTmp).then(({ canceled, filePath, bookmark }) => {
+ if (canceled) {
+ resolve('');
+ } else {
+ if (bookmark) {
+ this.setBookMarkForPath(filePath, bookmark);
}
- });
+ resolve(filePath);
+ }
+ });
});
}
@@ -1427,7 +1505,7 @@ export default class AppEnvConstructor {
resolve(downloadPath);
} else {
resolve('');
- remote.dialog.showErrorBox('File Save Error', errorMsg);
+ this.showErrorDialog({ title: 'File Save Error', message: errorMsg });
}
return;
} catch (e) {
@@ -1446,9 +1524,12 @@ export default class AppEnvConstructor {
properties: ['openDirectory', 'createDirectory'],
securityScopedBookmarks: !!process.mas,
};
-
+ let win = null;
+ if (options.modal) {
+ win = this.getCurrentWindow();
+ }
return remote.dialog
- .showOpenDialog(this.getCurrentWindow(), optionTmp)
+ .showOpenDialog(win, optionTmp)
.then(({ canceled, filePaths, bookmarks }) => {
if (canceled) {
resolve('');
@@ -1504,7 +1585,7 @@ export default class AppEnvConstructor {
return remote.getGlobal('application').getMainWindow();
}
- showErrorDialog(messageData, { showInMainWindow, detail } = {}) {
+ showErrorDialog(messageData, { showInMainWindow, detail, blockWindowKey = '' } = {}) {
let message;
let title;
if (_.isString(messageData) || _.isNumber(messageData)) {
@@ -1517,53 +1598,48 @@ export default class AppEnvConstructor {
throw new Error('Must pass a valid message to show dialog', message);
}
- let winToShow = null;
if (showInMainWindow) {
- winToShow = remote.getGlobal('application').getMainWindow();
+ blockWindowKey = 'default';
}
if (!detail) {
- return remote.dialog.showMessageBox(winToShow, {
+ return this.showMessageBox({
type: 'warning',
buttons: ['Okay'],
- message: title,
+ title,
detail: message,
+ blockWindowKey,
});
}
- return remote.dialog
- .showMessageBox(winToShow, {
- type: 'warning',
- buttons: ['Okay', 'Show Details'],
- message: title,
- detail: message,
- })
- .then(({ response, ...rest }) => {
- if (response === 1) {
- const { Actions } = require('mailspring-exports');
- const { CodeSnippet } = require('mailspring-component-kit');
- Actions.openModal({
- component: CodeSnippet({ intro: message, code: detail, className: 'error-details' }),
- width: 500,
- height: 300,
- });
- }
- return Promise.resolve({ response, ...rest });
- });
+ return this.showMessageBox({
+ blockWindowKey,
+ type: 'warning',
+ buttons: ['Okay', 'Show Details'],
+ title,
+ detail: message,
+ }).then(({ response, ...rest }) => {
+ if (response === 1) {
+ const { Actions } = require('mailspring-exports');
+ const { CodeSnippet } = require('mailspring-component-kit');
+ Actions.openModal({
+ component: CodeSnippet({ intro: message, code: detail, className: 'error-details' }),
+ width: 500,
+ height: 300,
+ });
+ }
+ return Promise.resolve({ response, ...rest });
+ });
}
showMessageBox({
title = '',
- showInMainWindow,
+ blockWindowKey,
detail = '',
type = 'question',
buttons = ['Okay', 'Cancel'],
defaultId = 0,
cancelId = 1,
} = {}) {
- let winToShow = null;
- if (showInMainWindow) {
- winToShow = remote.getGlobal('application').getMainWindow();
- }
if (!Array.isArray(buttons)) {
buttons = ['Okay', 'Cancel'];
}
@@ -1576,13 +1652,20 @@ export default class AppEnvConstructor {
if (defaultId < 0 || defaultId > buttons.length - 1) {
defaultId = 0;
}
- return remote.dialog.showMessageBox(winToShow, {
- type,
- buttons,
- message: title,
- detail,
- defaultId,
- cancelId,
+ return new Promise((resolve, reject) => {
+ console.log('request Message window');
+ Actions().requestMessageWindow(
+ {
+ type,
+ buttons,
+ title,
+ details: detail,
+ defaultId,
+ cancelId,
+ },
+ blockWindowKey,
+ resolve
+ );
});
}
diff --git a/app/src/browser/application.es6 b/app/src/browser/application.es6
index 05d14ca3b7..1dfbc07165 100644
--- a/app/src/browser/application.es6
+++ b/app/src/browser/application.es6
@@ -825,6 +825,12 @@ export default class Application extends EventEmitter {
title: 'Welcome to EdisonMail',
});
}
+ this.ensureReserveWindowAvailability();
+ }
+ ensureReserveWindowAvailability() {
+ if (this.windowManager) {
+ this.windowManager.ensureMessageWindowExists();
+ }
}
ensureMainWindowVisible() {
@@ -1575,49 +1581,6 @@ export default class Application extends EventEmitter {
}
}
});
- // ipcMain.on('draft-arp', (event, options) => {
- // const mainWindow = this.windowManager.get(WindowManager.MAIN_WINDOW);
- // if (mainWindow && mainWindow.browserWindow.webContents) {
- // mainWindow.browserWindow.webContents.send('draft-arp', options);
- // }
- // if (options.threadId) {
- // const threadWindow = this.windowManager.get(`thread-${options.threadId}`);
- // if (threadWindow && threadWindow.browserWindow.webContents) {
- // threadWindow.browserWindow.webContents.send('draft-arp', options);
- // }
- // }
- // if (options.headerMessageId) {
- // const composerWindow = this.windowManager.get(`composer-${options.headerMessageId}`);
- // if (composerWindow && composerWindow.browserWindow.webContents) {
- // composerWindow.browserWindow.webContents.send('draft-arp', options);
- // }
- // }
- // });
-
- // ipcMain.on('draft-arp-reply', (event, options) => {
- // const mainWindow = this.windowManager.get(WindowManager.MAIN_WINDOW);
- // if (mainWindow && mainWindow.browserWindow.webContents) {
- // mainWindow.browserWindow.webContents.send('draft-arp-reply', options);
- // }
- // if (options.threadId) {
- // const threadWindow = this.windowManager.get(`thread-${options.threadId}`);
- // if (threadWindow && threadWindow.browserWindow.webContents) {
- // threadWindow.browserWindow.webContents.send('draft-arp-reply', options);
- // }
- // }
- // });
- // ipcMain.on('draft-delete', (event, options) => {
- // const mainWindow = this.windowManager.get(WindowManager.MAIN_WINDOW);
- // if (mainWindow && mainWindow.browserWindow.webContents) {
- // mainWindow.browserWindow.webContents.send('draft-delete', options);
- // }
- // if (options.threadId) {
- // const threadWindow = this.windowManager.get(`thread-${options.threadId}`);
- // if (threadWindow && threadWindow.browserWindow.webContents) {
- // threadWindow.browserWindow.webContents.send('draft-delete', options);
- // }
- // }
- // });
ipcMain.on('update-window-key', (event, options) => {
const win = options.oldKey ? this.windowManager.get(options.oldKey) : null;
if (win) {
@@ -1630,6 +1593,35 @@ export default class Application extends EventEmitter {
}
}
});
+ ipcMain.on('reserveWindow-popout', (event, options) => {
+ const win = this.windowManager.getAvailableReserveWindow(options.windowType);
+
+ if (!win || !win.browserWindow.webContents) {
+ this.logDebug(`on rebroadcast to reserveWindow, no window found`);
+ return;
+ }
+ if (BrowserWindow.fromWebContents(event.sender) === win) {
+ this.logDebug(`on rebroadcast to reserveWindow, from same window, ignoring`);
+ return;
+ }
+ this.logDebug(`on rebroadcast to reserveWindow, ${win.windowKey}`);
+ win.browserWindow.webContents.send('reserveWindow-popout', options);
+ });
+
+ ipcMain.on('ensure-modal-message-window', (event, options) => {
+ const win = options.windowKey ? this.windowManager.get(options.windowKey) : null;
+ let parent = options.parentWindowKey ? this.windowManager.get(options.parentWindowKey) : null;
+ let parentWindow = parent ? parent.browserWindow : null;
+ if (!win && parentWindow) {
+ this.windowManager.newMessageWindow({
+ modal: true,
+ parentWindow,
+ windowKey: options.windowKey,
+ });
+ } else {
+ this.logWarning(`windowKey: ${options.windowKey}, parentKey ${options.parentWindowKey}`);
+ }
+ });
ipcMain.on('new-window', (event, options) => {
const win = options.windowKey ? this.windowManager.get(options.windowKey) : null;
@@ -1770,6 +1762,13 @@ export default class Application extends EventEmitter {
}
win[method](...args);
});
+ ipcMain.on('call-window-manager-method', (event, method, ...args) => {
+ const winManager = this.windowManager;
+ if (!winManager[method]) {
+ console.error(`Method ${method} does not exist on BrowserWindow!`);
+ }
+ winManager[method](...args);
+ });
ipcMain.on('call-devtools-webcontents-method', (event, method, ...args) => {
// If devtools aren't open the `webContents::devToolsWebContents` will be null
@@ -1805,6 +1804,21 @@ export default class Application extends EventEmitter {
}
mainWindow.browserWindow.webContents.send('action-bridge-message', ...args);
});
+ ipcMain.on('action-bridge-rebroadcast-to-messageWindow', (event, ...args) => {
+ const messageWindows = this.windowManager.getOpenWindows(WindowManager.MESSAGE_WINDOW);
+ messageWindows.forEach(messageWindow => {
+ if (!messageWindow || !messageWindow.browserWindow.webContents) {
+ this.logDebug(`on rebroadcast to messageWindow, no window found`);
+ return;
+ }
+ if (BrowserWindow.fromWebContents(event.sender) === messageWindow) {
+ this.logDebug(`on rebroadcast to messageWindow, from same window, ignoring`);
+ return;
+ }
+ this.logDebug(`on rebroadcast to messageWindow, ${messageWindow.windowKey}`);
+ messageWindow.browserWindow.webContents.send('action-bridge-message', ...args);
+ });
+ });
ipcMain.on('write-text-to-selection-clipboard', (event, selectedText) => {
clipboard = require('electron').clipboard;
diff --git a/app/src/browser/mailspring-window.es6 b/app/src/browser/mailspring-window.es6
index 7aee052e7e..40d6645de5 100644
--- a/app/src/browser/mailspring-window.es6
+++ b/app/src/browser/mailspring-window.es6
@@ -13,7 +13,7 @@ module.exports = class MailspringWindow extends EventEmitter {
constructor(settings = {}) {
super();
- let frame, height, pathToOpen, resizable, title, width, autoHideMenuBar, titleBarStyle;
+ let frame, height, pathToOpen, resizable, title, width, autoHideMenuBar, titleBarStyle, modal, parentWindow;
this.browserWindow = null;
this.loaded = null;
this.isSpec = null;
@@ -39,6 +39,8 @@ module.exports = class MailspringWindow extends EventEmitter {
configDirPath: this.configDirPath,
autoHideMenuBar,
titleBarStyle,
+ modal,
+ parentWindow,
} = settings);
if (!this.accountId) {
this.accountId = 'all';
@@ -61,6 +63,8 @@ module.exports = class MailspringWindow extends EventEmitter {
width,
height,
resizable,
+ modal,
+ parent: parentWindow,
acceptFirstMouse: true,
webPreferences: {
directWrite: true,
diff --git a/app/src/browser/window-manager.es6 b/app/src/browser/window-manager.es6
index 2fcbbbf315..76d8e739b7 100644
--- a/app/src/browser/window-manager.es6
+++ b/app/src/browser/window-manager.es6
@@ -1,11 +1,14 @@
import _ from 'underscore';
import { app } from 'electron';
import WindowLauncher from './window-launcher';
+import { MessageWindowSize, WindowTypes } from '../constant';
-const MAIN_WINDOW = 'default';
-const SPEC_WINDOW = 'spec';
-const ONBOARDING_WINDOW = 'onboarding';
-const BUG_REPORT_WINDOW = 'bugreport';
+const MAIN_WINDOW = WindowTypes.MAIN_WINDOW;
+const SPEC_WINDOW = WindowTypes.SPEC_WINDOW;
+const ONBOARDING_WINDOW = WindowTypes.ONBOARDING_WINDOW;
+const BUG_REPORT_WINDOW = WindowTypes.BUG_REPORT_WINDOW;
+const MESSAGE_WINDOW = WindowTypes.MESSAGE_WINDOW;
+const DIALOG_WINDOW = WindowTypes.DIALOG_WINDOW;
export default class WindowManager {
constructor({
@@ -19,6 +22,8 @@ export default class WindowManager {
}) {
this.initializeInBackground = initializeInBackground;
this._windows = {};
+ this._reserveWindowTracking = {};
+ this._freeReservedWindowsTimer = null;
const onCreatedHotWindow = win => {
this._registerWindow(win);
@@ -34,6 +39,34 @@ export default class WindowManager {
onCreatedHotWindow,
});
}
+ _freeReservedWindows = () => {
+ const types = Object.keys(this._reserveWindowTracking);
+ types.forEach(type => {
+ console.log(`extra free reserve window found, ${type}`);
+ const keys = Object.keys(this._reserveWindowTracking[type]);
+ let count = 0;
+ keys.forEach(key => {
+ console.log(
+ `extra free reserve window found, ${type}:${key},count ${count}, ${this._reserveWindowTracking[type][key]} `
+ );
+ if (this._reserveWindowTracking[type][key]) {
+ count = count + 1;
+ }
+ if (count > 2) {
+ console.log(`extra free reserve window found, ${type}:${key}, destroying`);
+ this.destroyWindow(key);
+ delete this._reserveWindowTracking[type][key];
+ }
+ });
+ });
+ console.log(`Cleaning up reserve window complete`);
+ this._freeReservedWindowsTimer = null;
+ };
+ _initiateFreeReservedWindows = () => {
+ if (!this._freeReservedWindowsTimer) {
+ this._freeReservedWindowsTimer = setTimeout(this._freeReservedWindows, 3000);
+ }
+ };
get(windowKey) {
return this._windows[windowKey];
@@ -168,6 +201,81 @@ export default class WindowManager {
return win;
}
+ newMessageWindow(options = {}) {
+ options.windowType = MESSAGE_WINDOW;
+ options.windowKey = MESSAGE_WINDOW;
+ options.height = MessageWindowSize.height;
+ options.width = MessageWindowSize.width;
+ this.newHiddenReserveWindow(options);
+ }
+ newDialogWindow(options = {}) {
+ options.windowType = DIALOG_WINDOW;
+ options.windowKey = DIALOG_WINDOW;
+ options.height = MessageWindowSize.height;
+ options.width = MessageWindowSize.width;
+ this.newHiddenReserveWindow(options);
+ }
+
+ newHiddenReserveWindow(options = {}) {
+ options.coldStartOnly = true;
+ const numOfWindows = this.getOpenWindowCount(options.windowType);
+ options.windowKey = `${options.windowKey}-${numOfWindows}`;
+ options.resizable = false;
+ options.hidden = true;
+ const win = this.newWindow(options);
+ if (win && win.browserWindow) {
+ win.browserWindow.hide();
+ }
+ if (!this._reserveWindowTracking[options.windowType]) {
+ this._reserveWindowTracking[options.windowType] = {};
+ }
+ this._reserveWindowTracking[options.windowType][options.windowKey] = true;
+ }
+ getAvailableReserveWindow(type) {
+ if (!this._reserveWindowTracking[type]) {
+ return null;
+ }
+ const keys = Object.keys(this._reserveWindowTracking[type]);
+ for (let i = 0; i < keys.length; i++) {
+ if (keys[i] && this._reserveWindowTracking[type][keys[i]]) {
+ const win = this.get(keys[i]);
+ if (!win) {
+ delete this._reserveWindowTracking[type][keys[i]];
+ } else {
+ return win;
+ }
+ }
+ }
+ return null;
+ }
+ showReserveWindow(windowKey, windowType) {
+ console.log(`windowKey ${windowKey}, ${windowType}`);
+ const win = this.get(windowKey);
+ if (win && win.browserWindow) {
+ win.browserWindow.show();
+ this._reserveWindowTracking[windowType][windowKey] = false;
+ setTimeout(() => {
+ if (win && win.browserWindow) {
+ win.browserWindow.setOpacity(1);
+ }
+ if (windowType === MESSAGE_WINDOW) {
+ this.ensureMessageWindowExists();
+ } else if (windowType === DIALOG_WINDOW) {
+ this.ensureDialogWindowExists();
+ }
+ }, 200);
+ }
+ }
+ hideReserveWindow(key, type) {
+ const win = this.get(key);
+ if (win && win.browserWindow) {
+ console.log(`hide ${key}, ${type}`);
+ win.browserWindow.hide();
+ win.browserWindow.setOpacity(0);
+ this._reserveWindowTracking[type][key] = true;
+ this._initiateFreeReservedWindows();
+ }
+ }
_registerWindow = win => {
if (!win.windowKey) {
@@ -186,6 +294,9 @@ export default class WindowManager {
_didCreateNewWindow = win => {
win.browserWindow.on('closed', () => {
delete this._windows[win.windowKey];
+ if (this._reserveWindowTracking[win.windowType]) {
+ delete this._reserveWindowTracking[win.windowType][win.windowKey];
+ }
this.quitWinLinuxIfNoWindows();
});
@@ -204,6 +315,27 @@ export default class WindowManager {
}
return null;
};
+ ensureMessageWindowExists() {
+ this.ensureReserveWindowAvailable(MESSAGE_WINDOW, {
+ windowKey: MESSAGE_WINDOW,
+ height: MessageWindowSize.height,
+ width: MessageWindowSize.width,
+ });
+ }
+ ensureDialogWindowExists() {
+ this.ensureReserveWindowAvailable(DIALOG_WINDOW, {
+ windowKey: DIALOG_WINDOW,
+ height: MessageWindowSize.height,
+ width: MessageWindowSize.width,
+ });
+ }
+ ensureReserveWindowAvailable(windowType, extraOpts = {}) {
+ const win = this.getAvailableReserveWindow(windowType);
+ if (!win) {
+ extraOpts.windowType = windowType;
+ this.newHiddenReserveWindow(extraOpts);
+ }
+ }
ensureWindow(windowKey, extraOpts) {
const win = this._windows[windowKey];
@@ -249,6 +381,13 @@ export default class WindowManager {
win.browserWindow.webContents.send(msg, ...args);
}
}
+ destroyWindow(windowKey) {
+ const win = this.get(windowKey);
+ if (win && win.browserWindow) {
+ win.browserWindow.destroy();
+ }
+ delete this._windows[windowKey];
+ }
destroyAllWindows() {
this.windowLauncher.cleanupBeforeAppQuit();
@@ -256,6 +395,7 @@ export default class WindowManager {
this._windows[windowKey].browserWindow.destroy();
}
this._windows = {};
+ this._reserveWindowTracking = {};
}
cleanupBeforeAppQuit() {
@@ -336,6 +476,17 @@ export default class WindowManager {
width: 685,
height: 700,
};
+ coreWinOpts[WindowManager.MESSAGE_WINDOW] = {
+ windowKey: WindowManager.MESSAGE_WINDOW,
+ windowType: WindowManager.MESSAGE_WINDOW,
+ title: 'Message Window',
+ // hidden: true, // Displayed by PageRouter::_initializeWindowSize
+ hidden: true,
+ frame: false, // Always false on Mac, explicitly set for Win & Linux
+ toolbar: false,
+ resizable: false,
+ disableZoom: true,
+ };
// The SPEC_WINDOW gets passed its own bootstrapScript
coreWinOpts[WindowManager.SPEC_WINDOW] = {
@@ -359,3 +510,5 @@ WindowManager.MAIN_WINDOW = MAIN_WINDOW;
WindowManager.SPEC_WINDOW = SPEC_WINDOW;
WindowManager.ONBOARDING_WINDOW = ONBOARDING_WINDOW;
WindowManager.BUG_REPORT_WINDOW = BUG_REPORT_WINDOW;
+WindowManager.MESSAGE_WINDOW = MESSAGE_WINDOW;
+WindowManager.DIALOG_WINDOW = DIALOG_WINDOW;
diff --git a/app/src/components/editable-list.jsx b/app/src/components/editable-list.jsx
index 8b5c1ecace..073f3b3c5a 100644
--- a/app/src/components/editable-list.jsx
+++ b/app/src/components/editable-list.jsx
@@ -296,24 +296,28 @@ class EditableList extends Component {
// need display confirm dialog
if (this.props.getConfirmMessage) {
const { message, detail } = this.props.getConfirmMessage(selectedItem);
- const chosen = remote.dialog.showMessageBoxSync({
+ AppEnv.showMessageBox({
type: 'info',
message: message,
detail: detail,
buttons: ['Delete', 'Cancel'],
defaultId: 0,
cancelId: 1,
+ }).then(({ response }) => {
+ if (response === 0) {
+ this.props.onDeleteItem(selectedItem, index);
+ isDeleted = true;
+ }
+ if (isDeleted && this.props.items[newIndex]) {
+ this._selectItem(this.props.items[newIndex], newIndex);
+ }
});
- if (chosen === 0) {
- this.props.onDeleteItem(selectedItem, index);
- isDeleted = true;
- }
} else {
this.props.onDeleteItem(selectedItem, index);
isDeleted = true;
- }
- if (isDeleted && this.props.items[newIndex]) {
- this._selectItem(this.props.items[newIndex], newIndex);
+ if (isDeleted && this.props.items[newIndex]) {
+ this._selectItem(this.props.items[newIndex], newIndex);
+ }
}
}
};
diff --git a/app/src/components/message-window.jsx b/app/src/components/message-window.jsx
new file mode 100644
index 0000000000..1a93feb74a
--- /dev/null
+++ b/app/src/components/message-window.jsx
@@ -0,0 +1,134 @@
+import React from 'react';
+import { RetinaImg } from 'mailspring-component-kit';
+import { Actions } from 'mailspring-exports';
+const minimumDetailHeight = 28;
+export default class MessageWindow extends React.PureComponent {
+ static displayName = 'MessageWindow';
+ static defaultProps = {
+ onCanceled: () => {},
+ onClicked: () => {},
+ style: {},
+ title: '',
+ detail: '',
+ buttons: [
+ { label: 'Ok', order: 0, originalIndex: 0 },
+ { label: 'Cancel', order: 1, originalIndex: 1 },
+ ],
+ checkboxLabel: '',
+ checkboxChecked: false,
+ defaultId: 0,
+ cancelId: 1,
+ sourceWindowKey: '',
+ requestId: '',
+ };
+ static containerRequired = false;
+
+ constructor(props) {
+ super(props);
+ this._buttonClicked = false;
+ this._mounted = false;
+ this._details = null;
+ this._timer = null;
+ }
+ componentDidMount() {
+ this._mounted = true;
+ Actions.closeMessageWindow.listen(this._onCancel);
+ document.body.addEventListener('keydown', this._onKeyPress);
+ }
+
+ componentWillUnmount() {
+ this._mounted = false;
+ document.body.removeListener('keydown', this._onKeyPress);
+ Actions.closeMessageWindow.unlisten(this._onCancel);
+ }
+ _allowButtonClick = () => {
+ if (!this._timer) {
+ this._timer = setTimeout(() => {
+ this._timer = null;
+ }, 300);
+ return true;
+ }
+ return false;
+ };
+ _onCancel = () => {
+ Actions.messageWindowReply({
+ sourceWindowKey: this.props.sourceWindowKey,
+ response: this.props.cancelId,
+ checkboxChecked: this.props.checkboxChecked,
+ requestId: this.props.requestId,
+ });
+ this.props.onCanceled();
+ };
+ _onClick = originalIndex => {
+ if (this._allowButtonClick()) {
+ Actions.messageWindowReply({
+ sourceWindowKey: this.props.sourceWindowKey,
+ response: originalIndex,
+ checkboxChecked: this.props.checkboxChecked,
+ requestId: this.props.requestId,
+ });
+ this.props.onClicked(originalIndex);
+ }
+ };
+ _onKeyPress = e => {
+ //Ignore key press that are part of composition of CJKT character
+ //https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onkeydown
+ if (e.isComposing || e.keyCode === 229) {
+ return;
+ }
+ if (e.key === 'Escape') {
+ this._onCancel();
+ } else if (e.key === 'Enter') {
+ this._onClick(this.props.defaultId);
+ }
+ };
+
+ renderButtons() {
+ return this.props.buttons.map(button => {
+ let className = 'btn';
+ if (button.order === 0) {
+ className += ' default';
+ }
+ return (
+
+ );
+ });
+ }
+
+ render() {
+ return (
+
+
+
+
+
+
+
{this.props.title}
+
(this._details = ref)}
+ style={{ minHeight: minimumDetailHeight }}
+ >
+ {this.props.details}
+
+
+
+
{this.renderButtons()}
+
+ );
+ }
+}
diff --git a/app/src/constant.es6 b/app/src/constant.es6
index 8edd6678a4..35fc3e95c5 100644
--- a/app/src/constant.es6
+++ b/app/src/constant.es6
@@ -1,3 +1,17 @@
+export const MessageWindowSize = {
+ width: 450,
+ height: 150,
+};
+export const WindowTypes = {
+ MAIN_WINDOW: 'default',
+ SPEC_WINDOW: 'spec',
+ ONBOARDING_WINDOW: 'onboarding',
+ BUG_REPORT_WINDOW: 'bugreport',
+ MESSAGE_WINDOW: 'messageWindow',
+ DIALOG_WINDOW: 'dialogWindow',
+ COMPOSER_WINDOW: 'composer',
+ THREAD_WINDOW: 'thread-popout',
+};
export const OAuthList = [
'gmail',
'yahoo',
diff --git a/app/src/flux/action-bridge.es6 b/app/src/flux/action-bridge.es6
index c784628cfc..380d808ac7 100644
--- a/app/src/flux/action-bridge.es6
+++ b/app/src/flux/action-bridge.es6
@@ -4,11 +4,13 @@ import Utils from './models/utils';
const Role = {
MAIN: 'default',
SECONDARY: 'secondary',
+ MESSAGE: 'message',
};
const TargetWindows = {
ALL: 'all',
MAIN: 'default',
+ MESSAGE: 'messageWindow',
};
const printToConsole = false;
@@ -33,7 +35,13 @@ class ActionBridge {
this.ipc = ipc;
this.ipcLastSendTime = null;
this.initiatorId = AppEnv.getWindowType();
- this.role = AppEnv.isMainWindow() ? Role.MAIN : Role.SECONDARY;
+ if (AppEnv.isMainWindow()) {
+ this.role = Role.MAIN;
+ } else if (AppEnv.isMessageWindow()) {
+ this.role = Role.MESSAGE;
+ } else {
+ this.role = Role.SECONDARY;
+ }
AppEnv.onBeforeUnload(this.onBeforeUnload);
@@ -54,6 +62,12 @@ class ActionBridge {
return Actions[name].listen(callback, this);
});
}
+ if (this.role !== Role.MESSAGE) {
+ Actions.messageWindowActions.forEach(name => {
+ const callback = (...args) => this.onRebroadcast(TargetWindows.MESSAGE, name, args);
+ return Actions[name].listen(callback, this);
+ });
+ }
}
onIPCMessage(event, initiatorId, name, json) {
diff --git a/app/src/flux/actions.es6 b/app/src/flux/actions.es6
index 3933f1ae33..6d9cdc4cbe 100644
--- a/app/src/flux/actions.es6
+++ b/app/src/flux/actions.es6
@@ -4,6 +4,7 @@ import { Action } from 'rxjs/internal/scheduler/Action';
const ActionScopeWindow = 'window';
const ActionScopeGlobal = 'global';
const ActionScopeMainWindow = 'main';
+const ActionScopeMessageWindow = 'messageWindow';
/*
Public: In the Flux {Architecture.md}, almost every user action
@@ -624,6 +625,13 @@ class Actions {
// Mute
static changeMuteSucceeded = ActionScopeMainWindow;
+ //App Message Popout Window
+ static requestMessageWindow = ActionScopeWindow;
+ static popoutMessageWindow = ActionScopeMessageWindow;
+ static showMessageWindow = ActionScopeGlobal;
+ static closeMessageWindow = ActionScopeWindow;
+ static messageWindowReply = ActionScopeGlobal;
+
// App Message actions
static pushAppMessage = ActionScopeWindow;
static pushAppMessages = ActionScopeWindow;
@@ -660,11 +668,11 @@ const create = (obj, name, scope) => {
obj[name].sync = true;
};
-const scopes = {
- window: [],
- global: [],
- main: [],
-};
+const scopes = {};
+scopes[ActionScopeMainWindow] = [];
+scopes[ActionScopeGlobal] = [];
+scopes[ActionScopeWindow] = [];
+scopes[ActionScopeMessageWindow] = [];
for (const name of Object.getOwnPropertyNames(Actions)) {
if (
@@ -676,7 +684,12 @@ for (const name of Object.getOwnPropertyNames(Actions)) {
) {
continue;
}
- if (Actions[name] !== 'window' && Actions[name] !== 'global' && Actions[name] !== 'main') {
+ if (
+ Actions[name] !== ActionScopeWindow &&
+ Actions[name] !== ActionScopeGlobal &&
+ Actions[name] !== ActionScopeMainWindow &&
+ Actions[name] !== ActionScopeMessageWindow
+ ) {
continue;
}
const scope = Actions[name];
@@ -687,5 +700,6 @@ for (const name of Object.getOwnPropertyNames(Actions)) {
Actions.windowActions = scopes.window;
Actions.mainWindowActions = scopes.main;
Actions.globalActions = scopes.global;
+Actions.messageWindowActions = scopes.messageWindow;
export default Actions;
diff --git a/app/src/flux/stores/app-message-store.es6 b/app/src/flux/stores/app-message-store.es6
index 5577ca86f5..3151d00d7e 100644
--- a/app/src/flux/stores/app-message-store.es6
+++ b/app/src/flux/stores/app-message-store.es6
@@ -1,6 +1,9 @@
import MailspringStore from 'mailspring-store';
import Actions from '../actions';
import uuid from 'uuid';
+import { ipcRenderer } from 'electron';
+import { WindowTypes } from '../../constant';
+
const silentTTL = 30 * 60 * 1000;
class AppMessageStore extends MailspringStore {
static priority = {
@@ -18,6 +21,7 @@ class AppMessageStore extends MailspringStore {
medium: [],
low: [],
};
+ this._messageWindowPopouts = {};
this._timeouts = {};
this._silentCache = {};
this._mostRecentBlock = null;
@@ -30,7 +34,111 @@ class AppMessageStore extends MailspringStore {
this.listenTo(Actions.removeAppMessages, this._onPopMessage);
this.listenTo(Actions.removeAccount, this._onAccountRemoved);
}
+ if (!AppEnv.isMessageWindow()) {
+ this.listenTo(Actions.requestMessageWindow, this._onRequestMessageWindow);
+ this.listenTo(Actions.messageWindowReply, this._onMessageWindowReply);
+ }
}
+ _onMessageWindowReply = ({ sourceWindowKey, response, checkboxChecked, requestId } = {}) => {
+ const cb = this._messageWindowPopouts[requestId];
+ delete this._messageWindowPopouts[requestId];
+ if (cb) {
+ cb({ response, checkboxChecked });
+ }
+ };
+
+ _onRequestMessageWindow = (messageData, blockWindowKey = '', cb = () => {}) => {
+ messageData.sourceWindowKey = AppEnv.getCurrentWindowKey();
+ // messageData.targetWindowKey = 'messageWindow';
+ if (blockWindowKey) {
+ messageData.targetWindowKey = blockWindowKey;
+ }
+ messageData.requestId = uuid();
+ messageData.windowType = WindowTypes.MESSAGE_WINDOW;
+ messageData = this._validateMessageWindowRequestData(messageData);
+ this._messageWindowPopouts[messageData.requestId] = cb;
+ console.log('show message window');
+ if (!blockWindowKey) {
+ ipcRenderer.send('reserveWindow-popout', messageData);
+ } else {
+ Actions.showMessageWindow(messageData);
+ }
+ };
+ _validateMessageWindowRequestData = ({
+ title = '',
+ details = '',
+ checkLabel = '',
+ buttons,
+ defaultId,
+ cancelId,
+ targetWindowKey = '',
+ sourceWindowKey = '',
+ requestId = '',
+ ...others
+ } = {}) => {
+ if (!sourceWindowKey) {
+ AppEnv.logError(
+ `Id is not available, we won't be able to return result to correct window, default results will be returned to main`
+ );
+ sourceWindowKey = 'default';
+ }
+ let buttons_state = [];
+ if (!Array.isArray(buttons)) {
+ buttons = ['Ok', 'Cancel'];
+ defaultId = 0;
+ cancelId = 1;
+ AppEnv.logWarning(`MessageWindow: buttons is not array from ${sourceWindowKey}`);
+ }
+ if (
+ typeof defaultId !== 'number' ||
+ !Number.isInteger(defaultId) ||
+ defaultId < 0 ||
+ defaultId >= buttons.length
+ ) {
+ defaultId = 0;
+ }
+ if (
+ typeof cancelId !== 'number' ||
+ !Number.isInteger(cancelId) ||
+ cancelId < 0 ||
+ cancelId >= buttons.length
+ ) {
+ cancelId = 0;
+ }
+ let defaultId_state;
+ let cancelId_state;
+ if (defaultId < buttons.length) {
+ buttons_state.push({ label: buttons[defaultId], order: 0, originalIndex: defaultId });
+ defaultId_state = defaultId;
+ }
+ if (cancelId < buttons.length && cancelId !== defaultId) {
+ buttons_state.push({
+ label: buttons[cancelId],
+ order: buttons_state.length,
+ originalIndex: cancelId,
+ });
+ cancelId_state = cancelId;
+ } else {
+ cancelId_state = defaultId_state;
+ }
+ buttons.forEach((button, index) => {
+ if (button && ![defaultId, cancelId].includes(index)) {
+ buttons_state.push({ label: button, order: buttons_state.length, originalIndex: index });
+ }
+ });
+ return {
+ ...others,
+ title,
+ details,
+ checkLabel,
+ defaultId: defaultId_state,
+ cancelId: cancelId_state,
+ buttons: buttons_state,
+ sourceWindowKey,
+ targetWindowKey,
+ requestId,
+ };
+ };
_onPopMessage = blockOrBlocks => {
if (!Array.isArray(blockOrBlocks)) {
diff --git a/app/src/flux/stores/attachment-store.es6 b/app/src/flux/stores/attachment-store.es6
index c0ea04e651..d201114c83 100644
--- a/app/src/flux/stores/attachment-store.es6
+++ b/app/src/flux/stores/attachment-store.es6
@@ -842,7 +842,7 @@ class AccountDrafts {
class AttachmentStore extends MailspringStore {
constructor() {
super();
-
+ if (!AppEnv.isMessageWindow()) {
// viewing messages
this.listenTo(Actions.fetchFile, this._fetch);
this.listenTo(Actions.fetchAndOpenFile, this._fetchAndOpen);
@@ -850,8 +850,7 @@ class AttachmentStore extends MailspringStore {
this.listenTo(Actions.fetchAndSaveAllFiles, this._saveAllFilesToUserDir);
this.listenTo(Actions.abortFetchFile, this._abortFetchFile);
this.listenTo(Actions.fetchAttachments, this._onFetchAttachments);
- this.listenTo(Actions.extractTnefFile, this._extractTnefFile);
-
+ this.listenTo(Actions.extractTnefFile, this._extractTnefFile);
// sending
this.listenTo(Actions.addAttachment, this._onAddAttachment);
this.listenTo(Actions.addAttachments, this._onAddAttachments);
@@ -878,12 +877,13 @@ class AttachmentStore extends MailspringStore {
this._fileSaveSuccess = new Map();
mkdirp(this._filesDirectory);
- DatabaseStore.listen(change => {
- if (change.objectClass === AttachmentProgress.name) {
- this._onPresentChange(change.objects);
- }
- });
- this._triggerDebounced = _.debounce(() => this.trigger(), 20);
+ DatabaseStore.listen(change => {
+ if (change.objectClass === AttachmentProgress.name) {
+ this._onPresentChange(change.objects);
+ }
+ });
+ this._triggerDebounced = _.debounce(() => this.trigger(), 20);
+ }
}
_onDraftAttachmentStateChanged = data => {
console.log(`draft attachment state changed `, data);
@@ -1783,7 +1783,7 @@ class AttachmentStore extends MailspringStore {
const name = file ? file.displayName() : 'one or more files';
const errorString = error ? error.toString() : '';
- return remote.dialog.showMessageBox({
+ return AppEnv.showMessageBox({
type: 'warning',
message: 'Download Failed',
detail: `Unable to download ${name}. Check your network connection and try again. ${errorString}`,
@@ -1802,7 +1802,7 @@ class AttachmentStore extends MailspringStore {
}
if (message) {
- remote.dialog.showMessageBox({
+ AppEnv.showMessageBox({
type: 'warning',
message: 'Download Failed',
detail: `${message}\n\n${error.message}`,
@@ -1815,10 +1815,19 @@ class AttachmentStore extends MailspringStore {
// Section: Adding Files
- _assertIdPresent(messageId) {
- if (!messageId) {
- throw new Error('You need to pass the ID of the message (draft) this Action refers to');
+ _sanityCheck(messageId) {
+ const session = DraftStore.getSessionByMessageId(messageId);
+ if (!session || !session.draft()) {
+ AppEnv.logError(`AttachmentStore: Draft ${messageId} session is not ready`);
+ AppEnv.showMessageBox({
+ title: 'Draft is not ready',
+ detail: 'Cannot attach file(s) to draft',
+ defaultId: 0,
+ buttons: ['Ok'],
+ });
+ return false;
}
+ return true;
}
_getFileStats(filepath) {
@@ -1869,20 +1878,6 @@ class AttachmentStore extends MailspringStore {
});
}
- _copyToInternalPath(originPath, targetPath) {
- return new Promise((resolve, reject) => {
- const readStream = fs.createReadStream(originPath);
- const writeStream = fs.createWriteStream(targetPath);
-
- readStream.on('error', () => reject(new Error(`Could not read file at path: ${originPath}`)));
- writeStream.on('error', () =>
- reject(new Error(`Could not write ${path.basename(targetPath)} to files directory.`))
- );
- readStream.on('end', () => resolve());
- readStream.pipe(writeStream);
- });
- }
-
async _deleteFile(file) {
try {
// Delete the file and it's containing folder. Todo: possibly other empty dirs?
@@ -1933,8 +1928,6 @@ class AttachmentStore extends MailspringStore {
// Handlers
_onSelectAttachment = ({ messageId, accountId, onCreated = () => {}, type = '*' }) => {
- this._assertIdPresent(messageId);
-
// When the dialog closes, it triggers `Actions.addAttachment`
const cb = paths => {
if (paths == null) {
@@ -1977,7 +1970,9 @@ class AttachmentStore extends MailspringStore {
if (!Array.isArray(filePaths) || filePaths.length === 0) {
throw new Error('_onAddAttachments must have an array of filePaths');
}
- this._assertIdPresent(messageId);
+ if (!this._sanityCheck(messageId)) {
+ return;
+ }
try {
const total = filePaths.length;
// const createdFiles = [];
@@ -2081,8 +2076,9 @@ class AttachmentStore extends MailspringStore {
inline = undefined,
onCreated = () => {},
}) => {
- this._assertIdPresent(messageId);
-
+ if (!this._sanityCheck(messageId)) {
+ return;
+ }
try {
const filename = path.basename(filePath);
const stats = await this._getFileStats(filePath);
@@ -2120,9 +2116,6 @@ class AttachmentStore extends MailspringStore {
} else {
Actions.syncAttachmentToMain(tmpData);
}
- // await mkdirpAsync(path.dirname(this.pathForFile(file)));
- // await this._copyToInternalPath(filePath, this.pathForFile(file));
-
await this._applySessionChanges(messageId, files => {
if (files.reduce((c, f) => c + f.size, 0) + file.size >= 25 * 1000000) {
AppEnv.trackingEvent('largeAttachmentSize');
diff --git a/app/src/flux/stores/draft-cache-store.es6 b/app/src/flux/stores/draft-cache-store.es6
index b4509db6c0..df364643d3 100644
--- a/app/src/flux/stores/draft-cache-store.es6
+++ b/app/src/flux/stores/draft-cache-store.es6
@@ -15,15 +15,17 @@ class DraftCacheStore extends MailspringStore {
constructor() {
super();
this.cache = {};
- this.listenTo(Actions.queueTasks, this._taskQueue);
- this.listenTo(Actions.queueTask, task => this._taskQueue([task]));
- this.listenTo(Actions.requestDraftCacheFromMain, this._onRequestForDraftCache);
- this.listenTo(Actions.broadcastDraftCache, this._onBroadcastDraftCacheReceived);
- this.listenTo(Actions.draftDeliverySucceeded, this._onSendDraftSuccess);
- this.listenTo(Actions.draftDeliveryFailed, this._onSendDraftFailed);
- this.listenTo(Actions.cancelOutboxDrafts, this._onRemoveDraft);
- this.listenTo(Actions.destroyDraft, this._onRemoveDraft);
- this.listenTo(DatabaseStore, this._onDBDataChange);
+ if (!AppEnv.isMessageWindow()) {
+ this.listenTo(Actions.queueTasks, this._taskQueue);
+ this.listenTo(Actions.queueTask, task => this._taskQueue([task]));
+ this.listenTo(Actions.requestDraftCacheFromMain, this._onRequestForDraftCache);
+ this.listenTo(Actions.broadcastDraftCache, this._onBroadcastDraftCacheReceived);
+ this.listenTo(Actions.draftDeliverySucceeded, this._onSendDraftSuccess);
+ this.listenTo(Actions.draftDeliveryFailed, this._onSendDraftFailed);
+ this.listenTo(Actions.cancelOutboxDrafts, this._onRemoveDraft);
+ this.listenTo(Actions.destroyDraft, this._onRemoveDraft);
+ this.listenTo(DatabaseStore, this._onDBDataChange);
+ }
}
_onRemoveDraft = ({ messages = [] } = {}) => {
if (!Array.isArray(messages)) {
diff --git a/app/src/flux/stores/draft-store.es6 b/app/src/flux/stores/draft-store.es6
index 602517a6e7..9465818e1d 100644
--- a/app/src/flux/stores/draft-store.es6
+++ b/app/src/flux/stores/draft-store.es6
@@ -47,21 +47,34 @@ Section: Drafts
class DraftStore extends MailspringStore {
constructor() {
super();
- this.listenTo(DatabaseStore, this._onDataChanged);
- this.listenTo(Actions.draftDeliveryFailed, this._onSendDraftFailed);
- this.listenTo(Actions.draftDeliverySucceeded, this._onSendDraftSuccess);
- this.listenTo(Actions.sendingDraft, this._onSendingDraft);
- this.listenTo(Actions.destroyDraftFailed, this._onDestroyDraftFailed);
- this.listenTo(Actions.destroyDraftSucceeded, this._onDestroyDraftSuccess);
- this.listenTo(Actions.destroyDraft, this._onDestroyDrafts);
- this.listenTo(Actions.sendDraft, this._onSendDraftAction);
- this.listenTo(Actions.changeDraftAccount, this._onDraftAccountChangeAction);
- this.listenTo(Actions.draftInlineAttachmentRemoved, this._onInlineItemRemoved);
- this.listenTo(Actions.removeAllNoReferenceInLines, this._onRemoveAllNoReferenceInLines);
- this.listenTo(Actions.broadcastChangeAccount, this._onBroadcastChangeAccount);
- this.listenTo(Actions.broadcastServerDraftSession, this._onSessionForServerDraftReply);
- this.listenTo(Actions.composeReply, this._onReply);
- this.listenTo(Actions.composeForward, this._onForward);
+ if (!AppEnv.isMessageWindow()) {
+ this.listenTo(DatabaseStore, this._onDataChanged);
+ this.listenTo(Actions.draftDeliveryFailed, this._onSendDraftFailed);
+ this.listenTo(Actions.draftDeliverySucceeded, this._onSendDraftSuccess);
+ this.listenTo(Actions.sendingDraft, this._onSendingDraft);
+ this.listenTo(Actions.destroyDraftFailed, this._onDestroyDraftFailed);
+ this.listenTo(Actions.destroyDraftSucceeded, this._onDestroyDraftSuccess);
+ this.listenTo(Actions.destroyDraft, this._onDestroyDrafts);
+ this.listenTo(Actions.sendDraft, this._onSendDraftAction);
+ this.listenTo(Actions.changeDraftAccount, this._onDraftAccountChangeAction);
+ this.listenTo(Actions.draftInlineAttachmentRemoved, this._onInlineItemRemoved);
+ this.listenTo(Actions.removeAllNoReferenceInLines, this._onRemoveAllNoReferenceInLines);
+ this.listenTo(Actions.broadcastChangeAccount, this._onBroadcastChangeAccount);
+ this.listenTo(Actions.broadcastServerDraftSession, this._onSessionForServerDraftReply);
+ this.listenTo(Actions.composeReply, this._onReply);
+ this.listenTo(Actions.composeForward, this._onForward);
+ ipcRenderer.on('action-send-cancelled', (event, messageId, actionKey) => {
+ AppEnv.debugLog(`Undo Send received ${messageId}`);
+ if (AppEnv.isMainWindow()) {
+ AppEnv.debugLog(
+ `Undo Send received ${messageId} main window sending draftDeliveryCancelled`
+ );
+ Actions.draftDeliveryCancelled({ messageId, actionKey });
+ }
+ this._onSendDraftCancelled({ messageId });
+ });
+ AppEnv.onBeforeUnload(this._onBeforeUnload);
+ }
if (AppEnv.isMainWindow()) {
this.listenTo(Actions.requestSessionForServerDraft, this._onServerDraftSessionRequest);
this.listenTo(Actions.toMainSendDraft, this._onSendDraft);
@@ -112,23 +125,6 @@ class DraftStore extends MailspringStore {
Actions.sendDraft(messageId, { actionKey, delay: 0, source: 'Undo timeout' });
});
}
- ipcRenderer.on('action-send-cancelled', (event, messageId, actionKey) => {
- AppEnv.debugLog(`Undo Send received ${messageId}`);
- if (AppEnv.isMainWindow()) {
- AppEnv.debugLog(
- `Undo Send received ${messageId} main window sending draftDeliveryCancelled`
- );
- Actions.draftDeliveryCancelled({ messageId, actionKey });
- }
- this._onSendDraftCancelled({ messageId });
- });
- // popout closed
- // ipcRenderer.on('draft-close-window', this._onPopoutClosed);
- // ipcRenderer.on('draft-got-new-id', this._onDraftGotNewId);
- // ipcRenderer.on('draft-arp', this._onDraftArp);
- // ipcRenderer.on('draft-delete', this._onDraftDeleting);
- AppEnv.onBeforeUnload(this._onBeforeUnload);
-
this._draftSessions = {};
this._draftsSending = {};
this._draftSendingTimeouts = {};
@@ -242,6 +238,9 @@ class DraftStore extends MailspringStore {
}
return this._draftSessions[messageId];
}
+ getSessionByMessageId = messageId => {
+ return this._draftSessions[messageId];
+ };
_onServerDraftSessionRequest = draft => {
if (!AppEnv.isMainWindow()) {
return;
@@ -552,24 +551,24 @@ class DraftStore extends MailspringStore {
}
session.validateDraftForChangeAccount().then(ret => {
const { errors, warnings } = ret;
- const dialog = remote.dialog;
if (warnings.length > 0) {
- dialog
- .showMessageBox(remote.getCurrentWindow(), {
- type: 'warning',
- buttons: ['Yes', 'Cancel'],
- message: 'Draft not ready, change anyways? This will remove all draft attachments.',
- detail: `${warnings.join(' and ')}?`,
- })
- .then(({ response } = {}) => {
- if (response === 0) {
- session.changes.add({ files: [] });
- session.updateAttachments([]);
- session.changingAccount();
- Actions.broadcastChangeAccount(data, AppEnv.getWindowLevel());
- return true;
- }
- });
+ AppEnv.showMessageBox({
+ blockWindowKey: AppEnv.getCurrentWindowKey(),
+ type: 'warning',
+ buttons: ['Yes', 'Cancel'],
+ title: 'Draft not ready',
+ detail: `Draft not ready, change anyways? This will remove all draft attachments.\n${warnings.join(
+ ' and '
+ )}?`,
+ }).then(({ response } = {}) => {
+ if (response === 0) {
+ session.changes.add({ files: [] });
+ session.updateAttachments([]);
+ session.changingAccount();
+ Actions.broadcastChangeAccount(data, AppEnv.getWindowLevel());
+ return true;
+ }
+ });
return false;
}
session.changingAccount();
@@ -948,7 +947,12 @@ class DraftStore extends MailspringStore {
};
_onBeforeUnload = readyToUnload => {
- if (AppEnv.isOnboardingWindow() || AppEnv.isEmptyWindow() || AppEnv.isBugReportingWindow()) {
+ if (
+ AppEnv.isOnboardingWindow() ||
+ AppEnv.isEmptyWindow() ||
+ AppEnv.isBugReportingWindow() ||
+ AppEnv.isMessageWindow()
+ ) {
console.log(`Is not proper window or is empty window ${AppEnv.isEmptyWindow()}`);
return true;
}
@@ -1757,7 +1761,6 @@ class DraftStore extends MailspringStore {
}
};
_onDestroyDraftSuccess = ({ messageIds, accountId }) => {
- AppEnv.logDebug('destroy draft succeeded');
if (Array.isArray(messageIds)) {
const triggerMessageIds = [];
messageIds.forEach(id => {
@@ -1963,14 +1966,14 @@ class DraftStore extends MailspringStore {
const session = this._draftSessions[messageId];
if (session) {
session.validateDraftForSending().then(({ errors, warnings }) => {
- const dialog = remote.dialog;
if (errors.length > 0) {
- dialog.showMessageBox(remote.getCurrentWindow(), {
+ AppEnv.showMessageBox({
+ blockWindowKey: AppEnv.getCurrentWindowKey(),
type: 'warning',
buttons: ['Edit Message', 'Cancel'],
defaultId: 0,
cancelId: 1,
- message: 'Cannot Send',
+ title: 'Cannot Send',
detail: errors[0],
});
Actions.draftDeliveryCancelled({ messageId });
@@ -1978,25 +1981,24 @@ class DraftStore extends MailspringStore {
}
if (warnings.length > 0 && !options.force) {
- dialog
- .showMessageBox(remote.getCurrentWindow(), {
- type: 'warning',
- buttons: ['Send Anyway', 'Cancel'],
- defaultId: 0,
- cancelId: 1,
- message: 'Are you sure?',
- detail: `Send ${warnings.join(' and ')}?`,
- })
- .then(({ response } = {}) => {
- if (response === 0) {
- options.disableDraftCheck = true;
- session.removeMissingAttachments().then(() => {
- this._onSendDraftAction(messageId, options);
- });
- } else {
- Actions.draftDeliveryCancelled({ messageId });
- }
- });
+ AppEnv.showMessageBox({
+ type: 'warning',
+ buttons: ['Send Anyway', 'Cancel'],
+ defaultId: 0,
+ cancelId: 1,
+ title: 'Are you sure?',
+ detail: `Send ${warnings.join(' and ')}?`,
+ blockWindowKey: AppEnv.getCurrentWindowKey(),
+ }).then(({ response } = {}) => {
+ if (response === 0) {
+ options.disableDraftCheck = true;
+ session.removeMissingAttachments().then(() => {
+ this._onSendDraftAction(messageId, options);
+ });
+ } else {
+ Actions.draftDeliveryCancelled({ messageId });
+ }
+ });
return false;
}
this._reRouteSendDraftAction(messageId, options);
diff --git a/app/src/flux/stores/message-store.es6 b/app/src/flux/stores/message-store.es6
index b2ef0b1350..88696c6546 100644
--- a/app/src/flux/stores/message-store.es6
+++ b/app/src/flux/stores/message-store.es6
@@ -21,7 +21,9 @@ class MessageStore extends MailspringStore {
constructor() {
super();
this._setStoreDefaults();
- this._registerListeners();
+ if (!AppEnv.isMessageWindow()) {
+ this._registerListeners();
+ }
}
findAll() {
@@ -211,7 +213,6 @@ class MessageStore extends MailspringStore {
}
_registerListeners() {
- // console.log('register listerners');
AppEnv.onBeforeUnload(this._onWindowClose);
if (AppEnv.isMainWindow()) {
this._currentWindowLevel = 1;
diff --git a/app/src/global/mailspring-component-kit.es6 b/app/src/global/mailspring-component-kit.es6
index f89cb3dc05..57db6a91d6 100644
--- a/app/src/global/mailspring-component-kit.es6
+++ b/app/src/global/mailspring-component-kit.es6
@@ -132,6 +132,7 @@ lazyLoad('MailLabelSet', 'mail-label-set');
lazyLoad('MailImportantIcon', 'mail-important-icon');
lazyLoad('ScenarioEditor', 'scenario-editor');
+lazyLoad('MessageWindow', 'message-window');
// Higher order components
lazyLoad('ListensToObservable', 'decorators/listens-to-observable');
diff --git a/app/src/global/mailspring-exports.es6 b/app/src/global/mailspring-exports.es6
index db2456d3bc..16b7ca5520 100644
--- a/app/src/global/mailspring-exports.es6
+++ b/app/src/global/mailspring-exports.es6
@@ -203,7 +203,6 @@ lazyLoad(`InflatesDraftClientId`, 'decorators/inflates-draft-client-id');
lazyLoad(`ExtensionRegistry`, 'registries/extension-registry');
lazyLoad(`MessageViewExtension`, 'extensions/message-view-extension');
lazyLoad(`ComposerExtension`, 'extensions/composer-extension');
-
// 3rd party libraries
lazyLoadWithGetter(`Rx`, () => require('rx-lite'));
lazyLoadWithGetter(`React`, () => require('react'));
diff --git a/app/src/sheet-container.jsx b/app/src/sheet-container.jsx
index fc41616476..1c34837063 100644
--- a/app/src/sheet-container.jsx
+++ b/app/src/sheet-container.jsx
@@ -9,7 +9,7 @@ import {
DatabaseStore,
Actions,
} from 'mailspring-exports';
-import { RetinaImg } from 'mailspring-component-kit';
+import { RetinaImg, MessageWindow, FullScreenModal } from 'mailspring-component-kit';
import Sheet from './sheet';
import Toolbar from './sheet-toolbar';
import Flexbox from './components/flexbox';
@@ -23,9 +23,12 @@ export default class SheetContainer extends React.Component {
super(props);
this._toolbarComponents = null;
this.state = this._getStateFromStores();
+ this.state.messageWindowData = {};
+ this.state.showMessageWindow = false;
}
componentDidMount() {
+ Actions.showMessageWindow.listen(this._onShowMessageWindow);
ipcRenderer.on('application-activate', this._onAppActive);
this.unsubscribe = WorkspaceStore.listen(this._onStoreChange);
if (AppEnv.isMainWindow()) {
@@ -76,6 +79,7 @@ export default class SheetContainer extends React.Component {
if (this.unsubscribe) {
this.unsubscribe();
}
+ Actions.showMessageWindow.unlisten(this._onShowMessageWindow);
}
_getStateFromStores() {
@@ -153,6 +157,66 @@ export default class SheetContainer extends React.Component {
);
}
+ _onShowMessageWindow = ({
+ title,
+ details,
+ checkLabel,
+ defaultId,
+ cancelId,
+ buttons,
+ sourceWindowKey,
+ targetWindowKey,
+ requestId,
+ } = {}) => {
+ const currentKey = AppEnv.getCurrentWindowKey();
+ if (targetWindowKey !== currentKey && targetWindowKey !== 'all') {
+ AppEnv.logDebug(
+ `not for current window ${currentKey}, targetWindowKey ${targetWindowKey}, ignoring`
+ );
+ return;
+ }
+ this.setState({
+ messageWindowData: {
+ title,
+ details,
+ checkLabel,
+ defaultId,
+ cancelId,
+ buttons,
+ sourceWindowKey,
+ requestId,
+ },
+ showMessageWindow: true,
+ });
+ };
+ _onMessageWindowCanceled = () => {
+ this.setState({ showMessageWindow: false });
+ };
+ _onMessageWindowClicked = () => {
+ this.setState({ showMessageWindow: false });
+ };
+
+ _renderBlockingMessage = () => {
+ if (!this.state.showMessageWindow) {
+ return null;
+ }
+ console.log('block window');
+ return (
+
+
+
+ );
+ };
isValidUser = () => {
// beta invite flow
@@ -227,6 +291,7 @@ export default class SheetContainer extends React.Component {
name="Center"
style={{ height: '100%', order: 2, flex: 1, position: 'relative', zIndex: 1 }}
>
+ {this._renderBlockingMessage()}
{rootSheet}
{popSheet}
diff --git a/app/src/sheet-toolbar.jsx b/app/src/sheet-toolbar.jsx
index 2c001d8db9..bf1eefc51c 100644
--- a/app/src/sheet-toolbar.jsx
+++ b/app/src/sheet-toolbar.jsx
@@ -2,18 +2,13 @@
/* eslint global-require: 0 */
import React from 'react';
import PropTypes from 'prop-types';
-import ReactDOM from 'react-dom';
import { remote } from 'electron';
-import { Actions, ComponentRegistry, WorkspaceStore } from 'mailspring-exports';
-
+import { ComponentRegistry, WorkspaceStore, Actions } from 'mailspring-exports';
import Flexbox from './components/flexbox';
import RetinaImg from './components/retina-img';
import Utils from './flux/models/utils';
import _ from 'underscore';
-let Category = null;
-let FocusedPerspectiveStore = null;
-
class ToolbarSpacer extends React.Component {
static displayName = 'ToolbarSpacer';
static propTypes = {
@@ -93,6 +88,13 @@ class ToolbarWindowControls extends React.Component {
this.setState({ alt: false });
}
};
+ _onClose = () => {
+ if (AppEnv.isMessageWindow()) {
+ Actions.closeMessageWindow();
+ } else {
+ AppEnv.close();
+ }
+ };
render() {
const enabled =
@@ -107,17 +109,19 @@ class ToolbarWindowControls extends React.Component {
if (this.state.alt) {
maxButton = ;
} else if (this.state.isFullScreen) {
- maxButton = ;
+ maxButton = ;
}
return (
-
);
}
@@ -133,9 +137,10 @@ class ToolbarMenuControl extends React.Component {
render() {
const enabled =
- process.platform === 'win32' ||
- (process.platform === 'linux' &&
- AppEnv.config.get('core.workspace.menubarStyle') === 'hamburger');
+ (process.platform === 'win32' ||
+ (process.platform === 'linux' &&
+ AppEnv.config.get('core.workspace.menubarStyle') === 'hamburger')) &&
+ !AppEnv.isMessageWindow();
if (!enabled) {
return ;
@@ -367,6 +372,9 @@ export default class Toolbar extends React.Component {
zIndex: 1,
background: 'transparent',
};
+ if (AppEnv.isMessageWindow()) {
+ delete style.background;
+ }
const toolbars = this.state.columns.map((components, idx) => (
{
+ }).then(() => {
session.changes.add({ subject: newSubject });
});
};
- _getDialog() {
- return require('electron').remote.dialog;
- }
-
render() {
return (