Confirm when the browser window is closed and there is unsaved work (#397)
* - browser confirmation on page exit - prompt to save a document when closed will only trigger when unsaved * add back select document for close prompt * - name -> displayname - add preventPropigation to middle click
This commit is contained in:
parent
5f248cd176
commit
9904021744
|
|
@ -4,7 +4,7 @@
|
||||||
<MenuBarInput v-if="platform !== ApplicationPlatform.Mac" />
|
<MenuBarInput v-if="platform !== ApplicationPlatform.Mac" />
|
||||||
</div>
|
</div>
|
||||||
<div class="header-third">
|
<div class="header-third">
|
||||||
<WindowTitle :title="`${documents.activeDocument} - Graphite`" />
|
<WindowTitle :title="`${documents.documents[documents.activeDocumentIndex].displayName} - Graphite`" />
|
||||||
</div>
|
</div>
|
||||||
<div class="header-third">
|
<div class="header-third">
|
||||||
<WindowButtonsWindows :maximized="maximized" v-if="platform === ApplicationPlatform.Windows || platform === ApplicationPlatform.Linux" />
|
<WindowButtonsWindows :maximized="maximized" v-if="platform === ApplicationPlatform.Windows || platform === ApplicationPlatform.Linux" />
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,26 @@
|
||||||
:class="{ active: tabIndex === tabActiveIndex }"
|
:class="{ active: tabIndex === tabActiveIndex }"
|
||||||
v-for="(tabLabel, tabIndex) in tabLabels"
|
v-for="(tabLabel, tabIndex) in tabLabels"
|
||||||
:key="tabIndex"
|
:key="tabIndex"
|
||||||
@click.middle="closeDocumentWithConfirmation(tabIndex)"
|
@click.middle="
|
||||||
|
(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
closeDocumentWithConfirmation(tabIndex);
|
||||||
|
}
|
||||||
|
"
|
||||||
@click="panelType === 'Document' && selectDocument(tabIndex)"
|
@click="panelType === 'Document' && selectDocument(tabIndex)"
|
||||||
>
|
>
|
||||||
<span>{{ tabLabel }}</span>
|
<span>{{ tabLabel }}</span>
|
||||||
<IconButton :action="() => closeDocumentWithConfirmation(tabIndex)" :icon="'CloseX'" :size="16" v-if="tabCloseButtons" />
|
<IconButton
|
||||||
|
:action="
|
||||||
|
(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
closeDocumentWithConfirmation(tabIndex);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
:icon="'CloseX'"
|
||||||
|
:size="16"
|
||||||
|
v-if="tabCloseButtons"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PopoverButton :icon="PopoverButtonIcon.VerticalEllipsis">
|
<PopoverButton :icon="PopoverButtonIcon.VerticalEllipsis">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<LayoutRow class="workspace-grid-subdivision">
|
<LayoutRow class="workspace-grid-subdivision">
|
||||||
<LayoutCol class="workspace-grid-subdivision" style="flex-grow: 1597">
|
<LayoutCol class="workspace-grid-subdivision" style="flex-grow: 1597">
|
||||||
<Panel :panelType="'Document'" :tabCloseButtons="true" :tabMinWidths="true" :tabLabels="documents.documents" :tabActiveIndex="documents.activeDocumentIndex" />
|
<Panel
|
||||||
|
:panelType="'Document'"
|
||||||
|
:tabCloseButtons="true"
|
||||||
|
:tabMinWidths="true"
|
||||||
|
:tabLabels="documents.documents.map((doc) => doc.displayName)"
|
||||||
|
:tabActiveIndex="documents.activeDocumentIndex"
|
||||||
|
/>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol class="workspace-grid-resize-gutter"></LayoutCol>
|
<LayoutCol class="workspace-grid-resize-gutter"></LayoutCol>
|
||||||
<LayoutCol class="workspace-grid-subdivision" style="flex-grow: 319">
|
<LayoutCol class="workspace-grid-subdivision" style="flex-grow: 319">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
|
|
||||||
import { fullscreenModeChanged } from "@/utilities/fullscreen";
|
import { fullscreenModeChanged } from "@/utilities/fullscreen";
|
||||||
import { onKeyUp, onKeyDown, onMouseMove, onMouseDown, onMouseUp, onMouseScroll, onWindowResize } from "@/utilities/input";
|
import { onKeyUp, onKeyDown, onMouseMove, onMouseDown, onMouseUp, onMouseScroll, onWindowResize, onBeforeUnload } from "@/utilities/input";
|
||||||
import "@/utilities/errors";
|
import "@/utilities/errors";
|
||||||
import App from "@/App.vue";
|
import App from "@/App.vue";
|
||||||
import { panicProxy } from "@/utilities/panic-proxy";
|
import { panicProxy } from "@/utilities/panic-proxy";
|
||||||
|
|
@ -21,6 +21,8 @@ const wasm = import("@/../wasm/pkg").then(panicProxy);
|
||||||
window.addEventListener("resize", onWindowResize);
|
window.addEventListener("resize", onWindowResize);
|
||||||
onWindowResize();
|
onWindowResize();
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", onBeforeUnload);
|
||||||
|
|
||||||
document.addEventListener("contextmenu", (e) => e.preventDefault());
|
document.addEventListener("contextmenu", (e) => e.preventDefault());
|
||||||
document.addEventListener("fullscreenchange", () => fullscreenModeChanged());
|
document.addEventListener("fullscreenchange", () => fullscreenModeChanged());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,17 @@ import { panicProxy } from "@/utilities/panic-proxy";
|
||||||
|
|
||||||
const wasm = import("@/../wasm/pkg").then(panicProxy);
|
const wasm = import("@/../wasm/pkg").then(panicProxy);
|
||||||
|
|
||||||
|
class DocumentState {
|
||||||
|
readonly displayName: string;
|
||||||
|
|
||||||
|
constructor(readonly name: string, readonly isSaved: boolean) {
|
||||||
|
this.displayName = `${name}${isSaved ? "" : "*"}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
documents: [] as string[],
|
documents: [] as DocumentState[],
|
||||||
activeDocumentIndex: 0,
|
activeDocumentIndex: 0,
|
||||||
get activeDocument() {
|
|
||||||
return this.documents[this.activeDocumentIndex];
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function selectDocument(tabIndex: number) {
|
export async function selectDocument(tabIndex: number) {
|
||||||
|
|
@ -29,9 +34,16 @@ export async function selectDocument(tabIndex: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function closeDocumentWithConfirmation(tabIndex: number) {
|
export async function closeDocumentWithConfirmation(tabIndex: number) {
|
||||||
selectDocument(tabIndex);
|
const targetDocument = state.documents[tabIndex];
|
||||||
|
if (targetDocument.isSaved) {
|
||||||
|
(await wasm).close_document(tabIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const tabLabel = state.documents[tabIndex];
|
// Show the document is being prompted to close
|
||||||
|
await selectDocument(tabIndex);
|
||||||
|
|
||||||
|
const tabLabel = targetDocument.displayName;
|
||||||
|
|
||||||
createDialog("File", "Save changes before closing?", tabLabel, [
|
createDialog("File", "Save changes before closing?", tabLabel, [
|
||||||
{
|
{
|
||||||
|
|
@ -84,7 +96,7 @@ export default readonly(state);
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.UpdateOpenDocumentsList, (responseData: Response) => {
|
registerResponseHandler(ResponseType.UpdateOpenDocumentsList, (responseData: Response) => {
|
||||||
const documentListData = responseData as UpdateOpenDocumentsList;
|
const documentListData = responseData as UpdateOpenDocumentsList;
|
||||||
state.documents = documentListData.open_documents.map(({ name, isSaved }) => `${name}${isSaved ? "" : "*"}`);
|
state.documents = documentListData.open_documents.map(({ name, isSaved }) => new DocumentState(name, isSaved));
|
||||||
});
|
});
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.SetActiveDocument, (responseData: Response) => {
|
registerResponseHandler(ResponseType.SetActiveDocument, (responseData: Response) => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { toggleFullscreen } from "@/utilities/fullscreen";
|
import { toggleFullscreen } from "@/utilities/fullscreen";
|
||||||
import { dialogIsVisible, dismissDialog, submitDialog } from "@/utilities/dialog";
|
import { dialogIsVisible, dismissDialog, submitDialog } from "@/utilities/dialog";
|
||||||
import { panicProxy } from "@/utilities/panic-proxy";
|
import { panicProxy } from "@/utilities/panic-proxy";
|
||||||
|
import documents from "./documents";
|
||||||
|
|
||||||
const wasm = import("@/../wasm/pkg").then(panicProxy);
|
const wasm = import("@/../wasm/pkg").then(panicProxy);
|
||||||
|
|
||||||
|
|
@ -125,6 +126,14 @@ export async function onWindowResize() {
|
||||||
if (boundsOfViewports.length > 0) (await wasm).bounds_of_viewports(data);
|
if (boundsOfViewports.length > 0) (await wasm).bounds_of_viewports(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function onBeforeUnload(event: BeforeUnloadEvent) {
|
||||||
|
const allDocumentsSaved = documents.documents.reduce((acc, doc) => doc.isSaved && acc, true);
|
||||||
|
if (!allDocumentsSaved) {
|
||||||
|
event.returnValue = "Unsaved work will be lost if the web browser tab is closed. Close anyway?";
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function makeModifiersBitfield(e: MouseEvent | KeyboardEvent): number {
|
export function makeModifiersBitfield(e: MouseEvent | KeyboardEvent): number {
|
||||||
return Number(e.ctrlKey) | (Number(e.shiftKey) << 1) | (Number(e.altKey) << 2);
|
return Number(e.ctrlKey) | (Number(e.shiftKey) << 1) | (Number(e.altKey) << 2);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue