Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save dialog for closing temporary sketch and unsaved files #893

Merged
merged 6 commits into from
Jun 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import {
DisposableCollection,
} from '@theia/core';
import {
Dialog,
FrontendApplication,
FrontendApplicationContribution,
LocalStorageService,
OnWillStopAction,
SaveableWidget,
StatusBar,
StatusBarAlignment,
Expand Down Expand Up @@ -659,4 +661,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
}
}
73 changes: 1 addition & 72 deletions arduino-ide-extension/src/browser/contributions/close.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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()
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -56,4 +58,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
)
)
.inSingletonScope();

bind(TheiaElectronWindow).toSelf();
rebind(DefaultTheiaElectronWindow).toService(TheiaElectronWindow);
});
Original file line number Diff line number Diff line change
@@ -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;
}
}
7 changes: 4 additions & 3 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,11 @@
},
"common": {
"later": "Later",
"loseChanges": "If you don't save, your changes will be lost.",
"noBoardSelected": "No board selected",
"notConnected": "[not connected]",
"offlineIndicator": "You appear to be offline. Without an Internet connection, the Arduino CLI might not be able to download the required resources and could cause malfunction. Please connect to the Internet and restart the application.",
"oldFormat": "The '{0}' still uses the old `.pde` format. Do you want to switch to the new `.ino` extension?",
"processing": "Processing",
"saveChangesToSketch": "Do you want to save changes to this sketch before closing?",
"selectBoard": "Select Board",
"selectedOn": "on {0}",
"serialMonitor": "Serial Monitor",
Expand Down Expand Up @@ -297,6 +295,7 @@
"openSketchInNewWindow": "Open Sketch in New Window",
"saveFolderAs": "Save sketch folder as...",
"saveSketchAs": "Save sketch folder as...",
"saveTempSketch": "Save your sketch to open it again later.",
"showFolder": "Show Sketch Folder",
"sketch": "Sketch",
"sketchbook": "Sketchbook",
Expand Down Expand Up @@ -325,7 +324,9 @@
"cannotConnectDaemon": "Cannot connect to the CLI daemon.",
"couldNotSave": "Could not save the sketch. Please copy your unsaved work into your favorite text editor, and restart the IDE.",
"daemonOffline": "CLI Daemon Offline",
"offline": "Offline"
"offline": "Offline",
"quitMessage": "Any unsaved changes will not be saved.",
"quitTitle": "Are you sure you want to quit?"
},
"debug": {
"start": "Start...",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"test": "lerna run test",
"download:plugins": "theia download:plugins",
"update:version": "node ./scripts/update-version.js",
"i18n:generate": "theia nls-extract -e vscode -f \"+(arduino-ide-extension|browser-app|electron|electron-app|plugins)/**/*.ts?(x)\" -o ./i18n/en.json",
"i18n:generate": "theia nls-extract -e vscode -f \"+(arduino-ide-extension|browser-app|electron-app|plugins)/**/*.ts?(x)\" -o ./i18n/en.json",
"i18n:check": "yarn i18n:generate && git add -N ./i18n && git diff --exit-code ./i18n",
"i18n:push": "node ./scripts/i18n/transifex-push.js ./i18n/en.json",
"i18n:pull": "node ./scripts/i18n/transifex-pull.js ./i18n/",
Expand Down