mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-09 02:18:32 +00:00
Save dialog for closing temporary sketch and unsaved files (#893)
* Use normal `OnWillStop` event * Align `CLOSE` command to rest of app * Fixed FS path vs encoded URL comparision when handling stop request. Ref: https://github.com/eclipse-theia/theia/issues/11226 Signed-off-by: Akos Kitta <a.kitta@arduino.cc> * Fixed the translations. Signed-off-by: Akos Kitta <a.kitta@arduino.cc> * Fixed the translations again. Removed `electron` from the `nls-extract`. It does not contain app code. Signed-off-by: Akos Kitta <a.kitta@arduino.cc> * Aligned the stop handler code to Theia. Signed-off-by: Akos Kitta <a.kitta@arduino.cc> Co-authored-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
@@ -17,9 +17,11 @@ import {
|
||||
DisposableCollection,
|
||||
} from '@theia/core';
|
||||
import {
|
||||
Dialog,
|
||||
FrontendApplication,
|
||||
FrontendApplicationContribution,
|
||||
LocalStorageService,
|
||||
OnWillStopAction,
|
||||
SaveableWidget,
|
||||
StatusBar,
|
||||
StatusBarAlignment,
|
||||
@@ -663,4 +665,51 @@ export class ArduinoFrontendContribution
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onWillStop(): OnWillStopAction {
|
||||
return {
|
||||
reason: 'temp-sketch',
|
||||
action: () => {
|
||||
return this.showTempSketchDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async showTempSketchDialog(): Promise<boolean> {
|
||||
const sketch = await this.sketchServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return true;
|
||||
}
|
||||
const isTemp = await this.sketchService.isTemp(sketch);
|
||||
if (!isTemp) {
|
||||
return true;
|
||||
}
|
||||
const messageBoxResult = await remote.dialog.showMessageBox(
|
||||
remote.getCurrentWindow(),
|
||||
{
|
||||
message: nls.localize('arduino/sketch/saveTempSketch', 'Save your sketch to open it again later.'),
|
||||
title: nls.localize('theia/core/quitTitle', 'Are you sure you want to quit?'),
|
||||
type: 'question',
|
||||
buttons: [
|
||||
Dialog.CANCEL,
|
||||
nls.localizeByDefault('Save As...'),
|
||||
nls.localizeByDefault("Don't Save"),
|
||||
],
|
||||
}
|
||||
)
|
||||
const result = messageBoxResult.response;
|
||||
if (result === 2) {
|
||||
return true;
|
||||
} else if (result === 1) {
|
||||
return !!(await this.commandRegistry.executeCommand(
|
||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
{
|
||||
execOnlyIfTemp: false,
|
||||
openAfterMove: false,
|
||||
wipeOriginal: true
|
||||
}
|
||||
));
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { toArray } from '@theia/core/shared/@phosphor/algorithm';
|
||||
import * as remote from '@theia/core/electron-shared/@electron/remote';
|
||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
|
||||
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { SaveAsSketch } from './save-as-sketch';
|
||||
import {
|
||||
SketchContribution,
|
||||
Command,
|
||||
@@ -33,76 +31,7 @@ export class Close extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(Close.Commands.CLOSE, {
|
||||
execute: async () => {
|
||||
// Close current editor if closeable.
|
||||
const { currentEditor } = this.editorManager;
|
||||
if (currentEditor && currentEditor.title.closable) {
|
||||
currentEditor.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Close current widget from the main area if possible.
|
||||
const { currentWidget } = this.shell;
|
||||
if (currentWidget) {
|
||||
const currentWidgetInMain = toArray(
|
||||
this.shell.mainPanel.widgets()
|
||||
).find((widget) => widget === currentWidget);
|
||||
if (currentWidgetInMain && currentWidgetInMain.title.closable) {
|
||||
return currentWidgetInMain.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Close the sketch (window).
|
||||
const sketch = await this.sketchServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
const isTemp = await this.sketchService.isTemp(sketch);
|
||||
const uri = await this.sketchServiceClient.currentSketchFile();
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
if (isTemp && (await this.wasTouched(uri))) {
|
||||
const { response } = await remote.dialog.showMessageBox({
|
||||
type: 'question',
|
||||
buttons: [
|
||||
nls.localize(
|
||||
'vscode/abstractTaskService/saveBeforeRun.dontSave',
|
||||
"Don't Save"
|
||||
),
|
||||
nls.localize('vscode/issueMainService/cancel', 'Cancel'),
|
||||
nls.localize(
|
||||
'vscode/abstractTaskService/saveBeforeRun.save',
|
||||
'Save'
|
||||
),
|
||||
],
|
||||
message: nls.localize(
|
||||
'arduino/common/saveChangesToSketch',
|
||||
'Do you want to save changes to this sketch before closing?'
|
||||
),
|
||||
detail: nls.localize(
|
||||
'arduino/common/loseChanges',
|
||||
"If you don't save, your changes will be lost."
|
||||
),
|
||||
});
|
||||
if (response === 1) {
|
||||
// Cancel
|
||||
return;
|
||||
}
|
||||
if (response === 2) {
|
||||
// Save
|
||||
const saved = await this.commandService.executeCommand(
|
||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
{ openAfterMove: false, execOnlyIfTemp: true }
|
||||
);
|
||||
if (!saved) {
|
||||
// If it was not saved, do bail the close.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
window.close();
|
||||
},
|
||||
execute: () => remote.getCurrentWindow().close()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,29 @@ import { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
import { ElectronWindowService } from '../../electron-window-service';
|
||||
import { ElectronMainMenuFactory } from './electron-main-menu-factory';
|
||||
import { ElectronMenuContribution } from './electron-menu-contribution';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
|
||||
import * as remote from '@theia/core/electron-shared/@electron/remote';
|
||||
import * as dialogs from '@theia/core/lib/browser/dialogs';
|
||||
|
||||
|
||||
Object.assign(dialogs, {
|
||||
confirmExit: async () => {
|
||||
const messageBoxResult = await remote.dialog.showMessageBox(
|
||||
remote.getCurrentWindow(),
|
||||
{
|
||||
message: nls.localize('theia/core/quitMessage', 'Any unsaved changes will not be saved.'),
|
||||
title: nls.localize('theia/core/quitTitle', 'Are you sure you want to quit?'),
|
||||
type: 'question',
|
||||
buttons: [
|
||||
dialogs.Dialog.CANCEL,
|
||||
dialogs.Dialog.YES,
|
||||
],
|
||||
}
|
||||
)
|
||||
return messageBoxResult.response === 1;
|
||||
}
|
||||
});
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(ElectronMenuContribution).toSelf().inSingletonScope();
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
IDEUpdaterPath,
|
||||
} from '../common/protocol/ide-updater';
|
||||
import { IDEUpdaterImpl } from './ide-updater/ide-updater-impl';
|
||||
import { TheiaElectronWindow } from './theia/theia-electron-window';
|
||||
import { TheiaElectronWindow as DefaultTheiaElectronWindow } from '@theia/core/lib/electron-main/theia-electron-window';
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(ElectronMainApplication).toSelf().inSingletonScope();
|
||||
@@ -56,4 +58,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
|
||||
bind(TheiaElectronWindow).toSelf();
|
||||
rebind(DefaultTheiaElectronWindow).toService(TheiaElectronWindow);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { StopReason } from '@theia/core/lib/electron-common/messaging/electron-messages';
|
||||
import { TheiaElectronWindow as DefaultTheiaElectronWindow } from '@theia/core/lib/electron-main/theia-electron-window';
|
||||
import { FileUri } from '@theia/core/lib/node';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
|
||||
@injectable()
|
||||
export class TheiaElectronWindow extends DefaultTheiaElectronWindow {
|
||||
protected async handleStopRequest(
|
||||
onSafeCallback: () => unknown,
|
||||
reason: StopReason
|
||||
): Promise<boolean> {
|
||||
// Only confirm close to windows that have loaded our frontend.
|
||||
// Both the windows's URL and the FS path of the `index.html` should be converted to the "same" format to be able to compare them. (#11226)
|
||||
// Notes:
|
||||
// - Windows: file:///C:/path/to/somewhere vs file:///c%3A/path/to/somewhere
|
||||
// - macOS: file:///Applications/App%20Name.app/Contents vs /Applications/App Name.app/Contents
|
||||
// This URL string comes from electron, we can expect that this is properly encoded URL. For example, a space is `%20`
|
||||
const currentUrl = new URI(this.window.webContents.getURL()).toString();
|
||||
// THEIA_FRONTEND_HTML_PATH is an FS path, we have to covert to an encoded URI string.
|
||||
const frontendUri = FileUri.create(
|
||||
this.globals.THEIA_FRONTEND_HTML_PATH
|
||||
).toString();
|
||||
const safeToClose =
|
||||
!currentUrl.includes(frontendUri) || (await this.checkSafeToStop(reason));
|
||||
if (safeToClose) {
|
||||
try {
|
||||
await onSafeCallback();
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.warn(`Request ${StopReason[reason]} failed.`, e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user