Skip to content

Commit

Permalink
FIX-2361 Support multiple windows in desktop app (equinor#2388)
Browse files Browse the repository at this point in the history
  • Loading branch information
eliasbruvik authored Apr 28, 2024
1 parent 6ac6a63 commit f100d0f
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 94 deletions.
3 changes: 2 additions & 1 deletion Src/WitsmlExplorer.Api/HttpHandlers/JobHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ private static bool IsAdminOrDeveloper(string token)
public static IResult GetAllJobInfos(IJobCache jobCache, IConfiguration configuration)
{
bool useOAuth2 = StringHelpers.ToBoolean(configuration[ConfigConstants.OAuth2Enabled]);
if (!useOAuth2)
bool IsDesktopApp = StringHelpers.ToBoolean(configuration[ConfigConstants.IsDesktopApp]);
if (!useOAuth2 && !IsDesktopApp)
{
return TypedResults.Unauthorized();
}
Expand Down
108 changes: 66 additions & 42 deletions Src/WitsmlExplorer.Desktop/src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import {
dialog,
Event,
ipcMain,
IpcMainEvent,
MessageBoxOptions
} from "electron";
import * as fs from "fs";
import * as path from "path";

let mainWindow: BrowserWindow;
let apiProcess: any;

const isDevelopment = process.env.NODE_ENV === "development";
Expand Down Expand Up @@ -76,7 +76,7 @@ function showErrorAndQuit(message: string) {
app.quit();
}

function showUnfinishedJobsWarningOnClose() {
function showUnfinishedJobsWarningOnClose(browserWindow: BrowserWindow) {
const options: MessageBoxOptions = {
type: "warning",
buttons: ["Cancel", "Quit"],
Expand All @@ -85,7 +85,7 @@ function showUnfinishedJobsWarningOnClose() {
detail:
"You have unfinished jobs that may cause issues if not completed before exiting.\n\nAre you sure you want to exit the application?"
};
return dialog.showMessageBoxSync(mainWindow, options);
return dialog.showMessageBoxSync(browserWindow, options);
}

interface Deferred<T> {
Expand Down Expand Up @@ -185,60 +185,84 @@ async function startApi(appConfig: AppConfig) {
});
}

function createWindow() {
mainWindow = new BrowserWindow({
function createWindow(route: string = "") {
const newBrowserWindow = new BrowserWindow({
width: 1920,
height: 1080,
webPreferences: {
preload: path.join(__dirname, "../preload/preload.js")
},
icon: path.join(__dirname, "../../resources/logo.png")
});
mainWindow.setMenuBarVisibility(false);
newBrowserWindow.setMenuBarVisibility(false);

if (isDevelopment) {
mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
} else {
mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
}
const loadWindow = isDevelopment
? () => newBrowserWindow.loadURL(process.env["ELECTRON_RENDERER_URL"])
: () =>
newBrowserWindow.loadFile(
path.join(__dirname, "../renderer/index.html")
);

mainWindow.on("close", async (e: Event) => {
e.preventDefault();
mainWindow.webContents.send("closeWindow");
loadWindow().then(() => {
if (route) {
newBrowserWindow.webContents.send("navigate", route);
}
});

mainWindow.on("closed", (): void => (mainWindow = null));
}

app.whenReady().then(async () => {
const appConfig = readOrCreateAppConfig();
await startApi(appConfig);
ipcMain.handle("getConfig", () => appConfig);

ipcMain.on("closeWindowResponse", async (_event, isUnfinishedJobs) => {
if (isUnfinishedJobs) {
const dialogResponse = showUnfinishedJobsWarningOnClose();
if (dialogResponse === 1) mainWindow.destroy();
} else {
mainWindow.destroy();
newBrowserWindow.on("close", async (e: Event) => {
if (BrowserWindow.getAllWindows().length === 1) {
e.preventDefault();
newBrowserWindow.webContents.send("closeWindow");
}
});
}

const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
// Only allow one instance of the application. app.on('second-instance') will be called in the first instance and create a new window instead.
app.quit();
} else {
app.whenReady().then(async () => {
const appConfig = readOrCreateAppConfig();
await startApi(appConfig);
ipcMain.handle("getConfig", () => appConfig);

ipcMain.on("closeWindowResponse", async (_event, isUnfinishedJobs) => {
const browserWindow = BrowserWindow.fromWebContents(_event.sender);
if (isUnfinishedJobs) {
const dialogResponse = showUnfinishedJobsWarningOnClose(browserWindow);
if (dialogResponse === 1) browserWindow.destroy();
} else {
browserWindow.destroy();
}
});

ipcMain.on("newWindow", (_event: IpcMainEvent, route: string) => {
createWindow(route);
});

createWindow();
createWindow();

// From Electron docs: macOS apps generally continue running even without any windows open, and activating the app when no windows are available should open a new one.
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
// From Electron docs: macOS apps generally continue running even without any windows open, and activating the app when no windows are available should open a new one.
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
});

app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("second-instance", () => {
// Create a new window in this instance if trying to open the application again.
createWindow();
});

app.on("before-quit", () => {
apiProcess?.kill();
apiProcess = null;
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});

app.on("before-quit", () => {
apiProcess?.kill();
apiProcess = null;
});
}
9 changes: 9 additions & 0 deletions Src/WitsmlExplorer.Desktop/src/preload/preload.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { contextBridge, ipcRenderer } from "electron";

contextBridge.exposeInMainWorld("electronAPI", {
// Navigation events
newWindow: (route: string) => ipcRenderer.send("newWindow", route),
onNavigate: (callback: any) =>
ipcRenderer.on("navigate", (_, route: string) => callback(route)),
removeNavigateListener: () => ipcRenderer.removeAllListeners("navigate"),

// Config events
getConfig: () => ipcRenderer.invoke("getConfig"),

// Close window events
onCloseWindow: (callback: any) =>
ipcRenderer.on("closeWindow", () => callback()),
removeCloseWindowListener: () =>
Expand Down
38 changes: 0 additions & 38 deletions Src/WitsmlExplorer.Frontend/components/CloseDesktopAppHandler.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import AuthorizationService from "services/authorizationService";
import JobService, { JobType } from "services/jobService";
import styled from "styled-components";
import Icon from "styles/Icons";
import { openRouteInNewWindow } from "tools/windowHelpers";
import { ModalContentLayout } from "../StyledComponents/ModalContentLayout";

export const StyledIcon = styled(Icon)`
Expand Down Expand Up @@ -69,7 +70,6 @@ export const onClickShowObjectOnServer = async (
indexCurve: IndexCurve = null
) => {
dispatchOperation({ type: OperationType.HideContextMenu });
const host = `${window.location.protocol}//${window.location.host}`;
let url = "";
if (objectType === ObjectType.Log) {
const logTypePath =
Expand Down Expand Up @@ -100,7 +100,7 @@ export const onClickShowObjectOnServer = async (
objectType
);
}
window.open(`${host}${url}`);
openRouteInNewWindow(url);
};

export const onClickShowGroupOnServer = async (
Expand All @@ -111,7 +111,6 @@ export const onClickShowGroupOnServer = async (
indexCurve: IndexCurve = null
) => {
dispatchOperation({ type: OperationType.HideContextMenu });
const host = `${window.location.protocol}//${window.location.host}`;
let url = "";
if (objectType === ObjectType.Log && indexCurve) {
const logTypePath =
Expand Down Expand Up @@ -140,7 +139,7 @@ export const onClickShowGroupOnServer = async (
objectType
);
}
window.open(`${host}${url}`);
openRouteInNewWindow(url);
};

export const onClickDeleteObjects = async (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import React from "react";
import { getWellboresViewPath } from "routes/utils/pathBuilder";
import JobService, { JobType } from "services/jobService";
import { colors } from "styles/Colors";
import { openRouteInNewWindow } from "tools/windowHelpers";
import { v4 as uuid } from "uuid";

export interface WellContextMenuProps {
Expand Down Expand Up @@ -196,9 +197,8 @@ const WellContextMenu = (props: WellContextMenuProps): React.ReactElement => {

const onClickShowOnServer = async (server: Server) => {
dispatchOperation({ type: OperationType.HideContextMenu });
const host = `${window.location.protocol}//${window.location.host}`;
const wellboresViewPath = getWellboresViewPath(server.url, well.uid);
window.open(`${host}${wellboresViewPath}`);
openRouteInNewWindow(wellboresViewPath);
};

const onClickBatchUpdate = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import React, { useContext } from "react";
import { getObjectGroupsViewPath } from "routes/utils/pathBuilder";
import JobService, { JobType } from "services/jobService";
import { colors } from "styles/Colors";
import { openRouteInNewWindow } from "tools/windowHelpers";
import { v4 as uuid } from "uuid";

export interface WellboreContextMenuProps {
Expand Down Expand Up @@ -210,13 +211,12 @@ const WellboreContextMenu = (

const onClickShowOnServer = async (server: Server) => {
dispatchOperation({ type: OperationType.HideContextMenu });
const host = `${window.location.protocol}//${window.location.host}`;
const objectGroupsViewPath = getObjectGroupsViewPath(
server.url,
wellbore.wellUid,
wellbore.uid
);
window.open(`${host}${objectGroupsViewPath}`);
openRouteInNewWindow(objectGroupsViewPath);
};

return (
Expand Down
50 changes: 50 additions & 0 deletions Src/WitsmlExplorer.Frontend/components/DesktopAppEventHandler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import JobStatus from "models/jobStatus";
import { ReactElement, useCallback, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import JobService from "services/jobService";

export function DesktopAppEventHandler(): ReactElement {
const navigate = useNavigate();

const onCloseWindowListener = useCallback(async () => {
const jobInfos = await JobService.getAllJobInfos();

const unfinishedJobs = jobInfos.filter(
(jobInfo) => jobInfo.status === JobStatus.Started
);

if (unfinishedJobs.length > 0) {
// @ts-ignore
window.electronAPI.closeWindowResponse(true);
} else {
// @ts-ignore
window.electronAPI.closeWindowResponse(false);
}
}, []);

useEffect(() => {
// @ts-ignore
window.electronAPI.onCloseWindow(onCloseWindowListener);

return () => {
// @ts-ignore
window.electronAPI.removeCloseWindowListener(onCloseWindowListener);
};
}, [onCloseWindowListener]);

const onNavigateListener = useCallback((route: string) => {
navigate(route);
}, []);

useEffect(() => {
// @ts-ignore
window.electronAPI.onNavigate(onNavigateListener);

return () => {
// @ts-ignore
window.electronAPI.removeNavigateListener(onNavigateListener);
};
}, [onNavigateListener]);

return null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { getLogCurveValuesViewPath } from "routes/utils/pathBuilder";
import styled from "styled-components";
import { Colors } from "styles/Colors";
import Icon from "styles/Icons";
import { openRouteInNewWindow } from "tools/windowHelpers";

enum IndexRangeOptions {
Full = "Full",
Expand Down Expand Up @@ -99,7 +100,6 @@ export function ShowLogDataOnServerModal() {

const handleOnSubmit = () => {
dispatchOperation({ type: OperationType.HideModal });
const host = `${window.location.protocol}//${window.location.host}`;
const targetServerLogCurveValuesViewPath = getLogCurveValuesViewPath(
selectedServer.url,
wellUid,
Expand All @@ -121,8 +121,8 @@ export function ShowLogDataOnServerModal() {
targetServerEndIndex,
JSON.parse(targetServerMnemonics)
).toString();
window.open(
`${host}${targetServerLogCurveValuesViewPath}${
openRouteInNewWindow(
`${targetServerLogCurveValuesViewPath}${
targetServerSearchParams.length !== 0
? `?${targetServerSearchParams}`
: ""
Expand Down
Loading

0 comments on commit f100d0f

Please sign in to comment.