production-taskbar-client / src / main / mainWindow.js
mainWindow.js
Raw
import path from "path";
import { app, BrowserWindow, screen, ipcMain } from "electron";

import installDevExtensions from "./utils/installDevExtensions";
import { registerAppBar } from "./utils/registerWinAppBar";
import settings from "./settings";
import openDevToolsWindow from "./utils/openDevToolsWindow";
import setWindowBouns from "./utils/setWindowBounds";

const kMainWindowChannel = "mainWindow";
const isDev = process.env.NODE_ENV === "development";
const defaultFloatingWidth = 350;
const defaultFloatingHeight = 100;

const setBounds = (window) => {
  const { isAutoHide } = window;
  // eslint-disable-next-line prefer-const
  const { x, y, width, height } = setWindowBouns(window);
  settings.set("mainWindowSettings", {
    isFloating: true,
    isHide: false,
    isAutoHide,
    x,
    y,
    w: width,
    h: height,
  });
};

const getBounds = () => {
  const mainWindowSettings = settings.get("mainWindowSettings");
  if (mainWindowSettings) {
    // eslint-disable-next-line prefer-const
    let { isFloating, isHide, isAutoHide, x, y, w, h } = mainWindowSettings;
    const { width: screenWidth, height: screenHeight } =
      screen.getPrimaryDisplay().size;

    x = parseInt(x, 10);
    y = parseInt(y, 10);
    w = parseInt(w, 10);
    h = parseInt(h, 10);
    const isBoundsValid = x && y && w && h;
    if (!isBoundsValid) {
      x = screenWidth - defaultFloatingWidth - 1;
      y = screenHeight - defaultFloatingHeight - 1;
      w = defaultFloatingWidth;
      h = defaultFloatingHeight;
    }
    if (typeof isFloating === "boolean") {
      isHide = typeof isHide === "boolean" ? isHide : false;
      isAutoHide = typeof isAutoHide === "boolean" ? isAutoHide : false;
      if (isFloating) {
        return { x, y, w, h, isHide, isAutoHide };
      }
    } else {
      settings.delete("mainWindowSettings");
    }
  }
  return null;
};

async function createTaskbarWindow(params = undefined) {
  let x;
  let y;
  let width;
  let height = 80;
  const isShow = false;
  let isHide = false;
  let isAutoHide = false;
  let isToggling = false;
  let isMovable = isDev;
  let isResizable = isDev;
  let maxHeight = 80;
  let minHeight = 50;
  const toggleButtonWidth = 30; // must be same as [ToggleButton.less => @width]
  let maxWidth = 600 + toggleButtonWidth;
  const minWidth = 300 + toggleButtonWidth;

  if (params) {
    ({ x, y, w: width, h: height, isHide, isAutoHide } = params);
    isMovable = true;
    isResizable = true;
    maxHeight = 150;
    minHeight = 100;
  } else {
    const { width: screenWidth, height: screenHeight } = isDev
      ? screen.getPrimaryDisplay().workAreaSize
      : screen.getPrimaryDisplay().size;

    width = screenWidth;
    maxWidth = screenWidth;
    x = 0;
    y = screenHeight - height;
  }

  //* Real position applies in [registerAppBar] when windows send a resize after a new appbar is added.
  //* If set size right now, the windows resize comes last and overrides us.
  let win = new BrowserWindow({
    x,
    y,
    width,
    height,
    show: isShow,
    minHeight,
    maxHeight,
    minWidth,
    maxWidth,
    type: "toolbar",
    closable: false,
    skipTaskbar: true,
    hasShadow: false,
    fullscreenable: false,
    maximizable: false,
    minimizable: false,
    movable: isMovable,
    thickFrame: false,
    titleBarStyle: "hidden",
    transparent: true,
    alwaysOnTop: true,
    frame: false,
    resizable: isResizable,
    webPreferences: {
      preload: path.join(app.getAppPath(), "./dist/preload.js"),
      nodeIntegration: true,
      contextIsolation: false,
      backgroundThrottling: false,
      devTools: isDev,
      additionalArguments: ["mainWindow"],
    },
  });

  const toggleWin = (hide) => {
    if (!isToggling) {
      isToggling = true;
      const intervalDelay = 10;
      const offsetX = 80;
      const { width: screenWidth } = screen.getPrimaryDisplay().size;
      let { x: currentX, y: currentY } = win.getBounds();
      const { x: savedX, y: savedY, w: savedW, h: savedH } = getBounds();

      currentX = Math.round(currentX);
      currentY = Math.round(currentY);
      const leftPos = screenWidth - toggleButtonWidth;

      if (hide) {
        // Hiding animation interval
        isHide = true;
        const interval = setInterval(() => {
          currentX += offsetX;
          if (currentX >= screenWidth) {
            clearInterval(interval);
            win.setBounds({
              x: leftPos,
              y: currentY,
              width: savedW,
              height: savedH,
            });
            settings.set("mainWindowSettings", {
              isFloating: true,
              isHide: true,
              isAutoHide,
              x: savedX,
              y: savedY,
              w: savedW,
              h: savedH,
            });
          } else {
            win.setBounds({
              x: currentX,
              y: currentY,
              width: savedW,
              height: savedH,
            });
          }
          isToggling = false;
          win.blur();
        }, intervalDelay);
      } else {
        // Showing animation interval
        isHide = false;
        const interval = setInterval(() => {
          currentX -= offsetX;
          if (currentX <= savedX) {
            clearInterval(interval);
            win.setBounds({
              x: savedX,
              y: currentY,
              width: savedW,
              height: savedH,
            });
            settings.set("mainWindowSettings", {
              isFloating: true,
              isHide: false,
              isAutoHide,
              x: savedX,
              y: savedY,
              w: savedW,
              h: savedH,
            });
          } else {
            win.setBounds({
              x: currentX,
              y: currentY,
              width: savedW,
              height: savedH,
            });
          }
          isToggling = false;
        }, intervalDelay);
      }
    }
  };

  win.on("closed", () => {
    win = null;
  });

  win.on("blur", () => {
    if (isAutoHide) {
      toggleWin(true);
      win.webContents.send(kMainWindowChannel, {
        event: "set-ishide",
        data: isHide,
      });
    }
  });

  win.once("ready-to-show", () => {
    if (!params && process.platform === "win32") {
      registerAppBar(win); // and show window after
    }

    win.setAlwaysOnTop(true, "screen-saver");
    win.showInactive();

    if (isHide) {
      toggleWin(isHide);
    }
  });

  win.on("moved", () => {
    setBounds(win);
  });
  win.on("resized", () => setBounds(win));

  ipcMain.handle(kMainWindowChannel, async (_e, { event, data }) => {
    switch (event) {
      case "toggle-win":
        toggleWin(data);
        return true;
      case "get-toggle-settings":
        return { isHide, isAutoHide };
      case "set-autohide": {
        isAutoHide = data;
        win.isAutoHide = data; // use win to access isAutoHide on saving settings on move
        const mainWindowSettings = settings.get("mainWindowSettings");
        mainWindowSettings.isAutoHide = data;
        settings.set("mainWindowSettings", mainWindowSettings);
        return true;
      }
      case "get-autohide":
        return isAutoHide;
      default:
        return false;
    }
  });

  // loadURL after init event listeners
  if (isDev) {
    process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "false";
    await installDevExtensions();
    let url = "http://localhost:4000";
    if (params) url = `http://localhost:4000#/compact`;

    await win.loadURL(url);
    openDevToolsWindow({ window: win, title: kMainWindowChannel });

    // Reopen fix redux extenstion [No Store Found] after hot reload
    win.webContents.on("devtools-closed", () =>
      setTimeout(
        () => openDevToolsWindow({ window: win, title: kMainWindowChannel }),
        500
      )
    );
  } else {
    let url = `file://${app.getAppPath()}/dist/renderer/index.html`;
    if (params)
      url = `file://${app.getAppPath()}/dist/renderer/index.html#/compact`;

    win.loadURL(url);
  }

  return win;
}

export default async function getTaskbarWindow() {
  const bounds = getBounds();
  if (bounds) return createTaskbarWindow(bounds);
  return createTaskbarWindow();
}