mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Desktop: Wait for note to be saved before closing the app
This commit is contained in:
parent
93dccf62df
commit
75b28c46af
@ -4,6 +4,7 @@ const url = require('url');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { dirname } = require('lib/path-utils');
|
const { dirname } = require('lib/path-utils');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
const { ipcMain } = require('electron');
|
||||||
|
|
||||||
class ElectronAppWrapper {
|
class ElectronAppWrapper {
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ class ElectronAppWrapper {
|
|||||||
this.willQuitApp_ = false;
|
this.willQuitApp_ = false;
|
||||||
this.tray_ = null;
|
this.tray_ = null;
|
||||||
this.buildDir_ = null;
|
this.buildDir_ = null;
|
||||||
|
this.rendererProcessQuitReply_ = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
electronApp() {
|
electronApp() {
|
||||||
@ -112,9 +114,11 @@ class ElectronAppWrapper {
|
|||||||
// On Windows and Linux, the app is closed when the window is closed *except* if the tray icon is used. In which
|
// On Windows and Linux, the app is closed when the window is closed *except* if the tray icon is used. In which
|
||||||
// case the app must be explicitly closed with Ctrl+Q or by right-clicking on the tray icon and selecting "Exit".
|
// case the app must be explicitly closed with Ctrl+Q or by right-clicking on the tray icon and selecting "Exit".
|
||||||
|
|
||||||
|
let isGoingToExit = false;
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
if (this.willQuitApp_) {
|
if (this.willQuitApp_) {
|
||||||
this.win_ = null;
|
isGoingToExit = true;
|
||||||
} else {
|
} else {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.hide();
|
this.hide();
|
||||||
@ -124,9 +128,39 @@ class ElectronAppWrapper {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.win_.hide();
|
this.win_.hide();
|
||||||
} else {
|
} else {
|
||||||
this.win_ = null;
|
isGoingToExit = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isGoingToExit) {
|
||||||
|
if (!this.rendererProcessQuitReply_) {
|
||||||
|
// If we haven't notified the renderer process yet, do it now
|
||||||
|
// so that it can tell us if we can really close the app or not.
|
||||||
|
// Search for "appClose" event for closing logic on renderer side.
|
||||||
|
event.preventDefault();
|
||||||
|
this.win_.webContents.send('appClose');
|
||||||
|
} else {
|
||||||
|
// If the renderer process has responded, check if we can close or not
|
||||||
|
if (this.rendererProcessQuitReply_.canClose) {
|
||||||
|
// Really quit the app
|
||||||
|
this.rendererProcessQuitReply_ = null;
|
||||||
|
this.win_ = null;
|
||||||
|
} else {
|
||||||
|
// Wait for renderer to finish task
|
||||||
|
event.preventDefault();
|
||||||
|
this.rendererProcessQuitReply_ = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('asynchronous-message', (event, message, args) => {
|
||||||
|
if (message === 'appCloseReply') {
|
||||||
|
// We got the response from the renderer process:
|
||||||
|
// save the response and try quit again.
|
||||||
|
this.rendererProcessQuitReply_ = args;
|
||||||
|
this.electronApp_.quit();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Let us register listeners on the window, so we can update the state
|
// Let us register listeners on the window, so we can update the state
|
||||||
|
@ -5,6 +5,7 @@ const { SideBar } = require('./SideBar.min.js');
|
|||||||
const { NoteList } = require('./NoteList.min.js');
|
const { NoteList } = require('./NoteList.min.js');
|
||||||
const { NoteText } = require('./NoteText.min.js');
|
const { NoteText } = require('./NoteText.min.js');
|
||||||
const NoteText2 = require('./NoteText2.js').default;
|
const NoteText2 = require('./NoteText2.js').default;
|
||||||
|
const { stateUtils } = require('lib/reducer.js');
|
||||||
const { PromptDialog } = require('./PromptDialog.min.js');
|
const { PromptDialog } = require('./PromptDialog.min.js');
|
||||||
const NoteContentPropertiesDialog = require('./NoteContentPropertiesDialog.js').default;
|
const NoteContentPropertiesDialog = require('./NoteContentPropertiesDialog.js').default;
|
||||||
const NotePropertiesDialog = require('./NotePropertiesDialog.min.js');
|
const NotePropertiesDialog = require('./NotePropertiesDialog.min.js');
|
||||||
@ -23,11 +24,25 @@ const VerticalResizer = require('./VerticalResizer.min');
|
|||||||
const PluginManager = require('lib/services/PluginManager');
|
const PluginManager = require('lib/services/PluginManager');
|
||||||
const TemplateUtils = require('lib/TemplateUtils');
|
const TemplateUtils = require('lib/TemplateUtils');
|
||||||
const EncryptionService = require('lib/services/EncryptionService');
|
const EncryptionService = require('lib/services/EncryptionService');
|
||||||
|
const ipcRenderer = require('electron').ipcRenderer;
|
||||||
|
|
||||||
class MainScreenComponent extends React.Component {
|
class MainScreenComponent extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
promptOptions: null,
|
||||||
|
modalLayer: {
|
||||||
|
visible: false,
|
||||||
|
message: '',
|
||||||
|
},
|
||||||
|
notePropertiesDialogOptions: {},
|
||||||
|
noteContentPropertiesDialogOptions: {},
|
||||||
|
shareNoteDialogOptions: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setupAppCloseHandling();
|
||||||
|
|
||||||
this.notePropertiesDialog_close = this.notePropertiesDialog_close.bind(this);
|
this.notePropertiesDialog_close = this.notePropertiesDialog_close.bind(this);
|
||||||
this.noteContentPropertiesDialog_close = this.noteContentPropertiesDialog_close.bind(this);
|
this.noteContentPropertiesDialog_close = this.noteContentPropertiesDialog_close.bind(this);
|
||||||
this.shareNoteDialog_close = this.shareNoteDialog_close.bind(this);
|
this.shareNoteDialog_close = this.shareNoteDialog_close.bind(this);
|
||||||
@ -35,6 +50,37 @@ class MainScreenComponent extends React.Component {
|
|||||||
this.noteList_onDrag = this.noteList_onDrag.bind(this);
|
this.noteList_onDrag = this.noteList_onDrag.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupAppCloseHandling() {
|
||||||
|
this.waitForNotesSavedIID_ = null;
|
||||||
|
|
||||||
|
// This event is dispached from the main process when the app is about
|
||||||
|
// to close. The renderer process must respond with the "appCloseReply"
|
||||||
|
// and tell the main process whether the app can really be closed or not.
|
||||||
|
// For example, it cannot be closed right away if a note is being saved.
|
||||||
|
// If a note is being saved, we wait till it is saved and then call
|
||||||
|
// "appCloseReply" again.
|
||||||
|
ipcRenderer.on('appClose', () => {
|
||||||
|
if (this.waitForNotesSavedIID_) clearInterval(this.waitForNotesSavedIID_);
|
||||||
|
this.waitForNotesSavedIID_ = null;
|
||||||
|
|
||||||
|
ipcRenderer.send('asynchronous-message', 'appCloseReply', {
|
||||||
|
canClose: !this.props.hasNotesBeingSaved,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.props.hasNotesBeingSaved) {
|
||||||
|
this.waitForNotesSavedIID_ = setInterval(() => {
|
||||||
|
if (!this.props.hasNotesBeingSaved) {
|
||||||
|
clearInterval(this.waitForNotesSavedIID_);
|
||||||
|
this.waitForNotesSavedIID_ = null;
|
||||||
|
ipcRenderer.send('asynchronous-message', 'appCloseReply', {
|
||||||
|
canClose: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
sidebar_onDrag(event) {
|
sidebar_onDrag(event) {
|
||||||
Setting.setValue('style.sidebar.width', this.props.sidebarWidth + event.deltaX);
|
Setting.setValue('style.sidebar.width', this.props.sidebarWidth + event.deltaX);
|
||||||
}
|
}
|
||||||
@ -55,19 +101,6 @@ class MainScreenComponent extends React.Component {
|
|||||||
this.setState({ shareNoteDialogOptions: {} });
|
this.setState({ shareNoteDialogOptions: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
|
||||||
this.setState({
|
|
||||||
promptOptions: null,
|
|
||||||
modalLayer: {
|
|
||||||
visible: false,
|
|
||||||
message: '',
|
|
||||||
},
|
|
||||||
notePropertiesDialogOptions: {},
|
|
||||||
noteContentPropertiesDialogOptions: {},
|
|
||||||
shareNoteDialogOptions: {},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(newProps) {
|
UNSAFE_componentWillReceiveProps(newProps) {
|
||||||
// Execute a command if any, and if we haven't already executed it
|
// Execute a command if any, and if we haven't already executed it
|
||||||
if (newProps.windowCommand && newProps.windowCommand !== this.props.windowCommand) {
|
if (newProps.windowCommand && newProps.windowCommand !== this.props.windowCommand) {
|
||||||
@ -735,6 +768,7 @@ const mapStateToProps = state => {
|
|||||||
selectedNoteId: state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null,
|
selectedNoteId: state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null,
|
||||||
plugins: state.plugins,
|
plugins: state.plugins,
|
||||||
templates: state.templates,
|
templates: state.templates,
|
||||||
|
hasNotesBeingSaved: stateUtils.hasNotesBeingSaved(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,19 +22,17 @@ const { bridge } = require('electron').remote.require('./bridge');
|
|||||||
async function initialize() {
|
async function initialize() {
|
||||||
this.wcsTimeoutId_ = null;
|
this.wcsTimeoutId_ = null;
|
||||||
|
|
||||||
bridge()
|
bridge().window().on('resize', function() {
|
||||||
.window()
|
if (this.wcsTimeoutId_) clearTimeout(this.wcsTimeoutId_);
|
||||||
.on('resize', function() {
|
|
||||||
if (this.wcsTimeoutId_) clearTimeout(this.wcsTimeoutId_);
|
|
||||||
|
|
||||||
this.wcsTimeoutId_ = setTimeout(() => {
|
this.wcsTimeoutId_ = setTimeout(() => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'WINDOW_CONTENT_SIZE_SET',
|
type: 'WINDOW_CONTENT_SIZE_SET',
|
||||||
size: bridge().windowContentSize(),
|
size: bridge().windowContentSize(),
|
||||||
});
|
});
|
||||||
this.wcsTimeoutId_ = null;
|
this.wcsTimeoutId_ = null;
|
||||||
}, 10);
|
}, 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Need to dispatch this to make sure the components are
|
// Need to dispatch this to make sure the components are
|
||||||
// displayed at the right size. The windowContentSize is
|
// displayed at the right size. The windowContentSize is
|
||||||
|
@ -627,7 +627,7 @@ class BaseApplication {
|
|||||||
initArgs = Object.assign(initArgs, extraFlags);
|
initArgs = Object.assign(initArgs, extraFlags);
|
||||||
|
|
||||||
this.logger_.addTarget('file', { path: `${profileDir}/log.txt` });
|
this.logger_.addTarget('file', { path: `${profileDir}/log.txt` });
|
||||||
// if (Setting.value('env') === 'dev' && Setting.value('appType') === 'desktop') this.logger_.addTarget('console', { level: Logger.LEVEL_DEBUG });
|
if (Setting.value('env') === 'dev' && Setting.value('appType') === 'desktop') this.logger_.addTarget('console', { level: Logger.LEVEL_DEBUG });
|
||||||
this.logger_.setLevel(initArgs.logLevel);
|
this.logger_.setLevel(initArgs.logLevel);
|
||||||
|
|
||||||
reg.setLogger(this.logger_);
|
reg.setLogger(this.logger_);
|
||||||
|
@ -89,6 +89,13 @@ stateUtils.foldersOrder = function(stateSettings) {
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
stateUtils.hasNotesBeingSaved = function(state) {
|
||||||
|
for (const id in state.editorNoteStatuses) {
|
||||||
|
if (state.editorNoteStatuses[id] === 'saving') return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
stateUtils.parentItem = function(state) {
|
stateUtils.parentItem = function(state) {
|
||||||
const t = state.notesParentType;
|
const t = state.notesParentType;
|
||||||
let id = null;
|
let id = null;
|
||||||
|
Loading…
Reference in New Issue
Block a user