Skip to content

Commit

Permalink
Version 1.1.0 with improved state handling
Browse files Browse the repository at this point in the history
  • Loading branch information
jwbonner committed Oct 9, 2021
1 parent ef9a0e6 commit 600fdf0
Show file tree
Hide file tree
Showing 17 changed files with 778 additions and 169 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
Adds support for update notifications. GitHub will be checked for updates on startup and a notification will be sent with a download link when a newer version is found.
Improves handling of window state, including open tabs and selected fields. This data is preserved when opening a new log file or restarting the app. Other miscellaneous improvements include...

* Tabs can now be closed in addition to opened!
* Only numbers are allowed as fields for odometry.
* Minor visual updates to the odometry popup button & tab bar.
* Lots of internal cleanup.
10 changes: 10 additions & 0 deletions indexPreload.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ ipcRenderer.on("set-focused", (_, isFocused) => {
}))
})

ipcRenderer.on("restore-state", (_, state) => {
window.dispatchEvent(new CustomEvent("restore-state", {
detail: state
}))
})

window.addEventListener("save-state", event => {
ipcRenderer.send("save-state", event.detail)
})

ipcRenderer.on("open-file", (_, path) => {
fs.open(path, "r", function (err, file) {
if (err) throw err
Expand Down
29 changes: 25 additions & 4 deletions main.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const { app, BrowserWindow, Menu, MenuItem, shell, dialog, ipcMain } = require("electron")
const windowStateKeeper = require("electron-window-state")
const WindowStateKeeper = require("./windowState.js")
const { setUpdateNotification } = require("electron-update-notifier")
const path = require("path")
const os = require("os")

const stateFileName = "state-" + app.getVersion().replaceAll(".", '_') + ".json"

var firstOpenPath = null
app.whenReady().then(() => {
setupMenu()
Expand Down Expand Up @@ -66,10 +68,18 @@ function createWindow() {
}

// Manage window state
var windowState = windowStateKeeper({
var window = null
var windowState = WindowStateKeeper({
file: stateFileName,
defaultWidth: 1100,
defaultHeight: 650,
fullScreen: false
fullScreen: false,
saveDataHandler: saveStateHandler,
restoreDataHandler: state => {
window.once("ready-to-show", () => {
window.send("restore-state", state)
})
}
})
if (BrowserWindow.getFocusedWindow() == null) {
prefs.x = windowState.x
Expand All @@ -91,7 +101,7 @@ function createWindow() {
}

// Create window
const window = new BrowserWindow(prefs)
window = new BrowserWindow(prefs)
windowState.manage(window)

// Finish setup
Expand All @@ -106,6 +116,17 @@ function createWindow() {
return window
}

// Manage state
var states = {}
ipcMain.on("save-state", (event, state) => {
states[event.sender.getOwnerBrowserWindow()] = state
})

function saveStateHandler(window) {
return states[window]
}

// Create app menu
function setupMenu() {
const isMac = process.platform === "darwin"

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "6328-log-viewer",
"productName": "6328 Log Viewer",
"version": "1.0.1",
"version": "1.1.0",
"description": "Logging tool from FRC Team 6328.",
"main": "main.js",
"scripts": {
Expand Down
191 changes: 191 additions & 0 deletions windowState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Modified from https://github.com/mawie81/electron-window-state

const path = require('path');
const electron = require('electron');
const jsonfile = require('jsonfile');
const mkdirp = require('mkdirp');

module.exports = function (options) {
const app = electron.app || electron.remote.app;
const screen = electron.screen || electron.remote.screen;
let state;
let winRef;
let stateChangeTimer;
const eventHandlingDelay = 100;
const config = Object.assign({
file: 'window-state.json',
path: app.getPath('userData'),
maximize: true,
fullScreen: true,
saveDataHandler: () => null,
restoreDataHandler: () => null
}, options);
const fullStoreFileName = path.join(config.path, config.file);

function isNormal(win) {
return !win.isMaximized() && !win.isMinimized() && !win.isFullScreen();
}

function hasBounds() {
return state &&
Number.isInteger(state.x) &&
Number.isInteger(state.y) &&
Number.isInteger(state.width) && state.width > 0 &&
Number.isInteger(state.height) && state.height > 0;
}

function resetStateToDefault() {
const displayBounds = screen.getPrimaryDisplay().bounds;

// Reset state to default values on the primary display
state = {
width: config.defaultWidth || 800,
height: config.defaultHeight || 600,
x: 0,
y: 0,
displayBounds,
data
};
}

function windowWithinBounds(bounds) {
return (
state.x >= bounds.x &&
state.y >= bounds.y &&
state.x + state.width <= bounds.x + bounds.width &&
state.y + state.height <= bounds.y + bounds.height
);
}

function ensureWindowVisibleOnSomeDisplay() {
const visible = screen.getAllDisplays().some(display => {
return windowWithinBounds(display.bounds);
});

if (!visible) {
// Window is partially or fully not visible now.
// Reset it to safe defaults.
return resetStateToDefault();
}
}

function validateState() {
const isValid = state && (hasBounds() || state.isMaximized || state.isFullScreen);
if (!isValid) {
state = null;
return;
}

if (hasBounds() && state.displayBounds) {
ensureWindowVisibleOnSomeDisplay();
}
}

function updateState(win) {
win = win || winRef;
if (!win) {
return;
}
// Don't throw an error when window was closed
try {
const winBounds = win.getBounds();
if (isNormal(win)) {
state.x = winBounds.x;
state.y = winBounds.y;
state.width = winBounds.width;
state.height = winBounds.height;
}
state.isMaximized = win.isMaximized();
state.isFullScreen = win.isFullScreen();
state.displayBounds = screen.getDisplayMatching(winBounds).bounds;
state.data = config.saveDataHandler(win);
} catch (err) { }
}

function saveState(win) {
// Update window state only if it was provided
if (win) {
updateState(win);
}

// Save state
try {
mkdirp.sync(path.dirname(fullStoreFileName));
jsonfile.writeFileSync(fullStoreFileName, state);
} catch (err) {
// Don't care
}
}

function stateChangeHandler() {
// Handles both 'resize' and 'move'
clearTimeout(stateChangeTimer);
stateChangeTimer = setTimeout(updateState, eventHandlingDelay);
}

function closeHandler() {
updateState();
}

function closedHandler() {
// Unregister listeners and save state
unmanage();
saveState();
}

function manage(win) {
if (config.maximize && state.isMaximized) {
win.maximize();
}
if (config.fullScreen && state.isFullScreen) {
win.setFullScreen(true);
}
if (state.data) config.restoreDataHandler(state.data);
win.on('resize', stateChangeHandler);
win.on('move', stateChangeHandler);
win.on('close', closeHandler);
win.on('closed', closedHandler);
winRef = win;
}

function unmanage() {
if (winRef) {
winRef.removeListener('resize', stateChangeHandler);
winRef.removeListener('move', stateChangeHandler);
clearTimeout(stateChangeTimer);
winRef.removeListener('close', closeHandler);
winRef.removeListener('closed', closedHandler);
winRef = null;
}
}

// Load previous state
try {
state = jsonfile.readFileSync(fullStoreFileName);
} catch (err) {
// Don't care
}

// Check state validity
validateState();

// Set state fallback values
state = Object.assign({
width: config.defaultWidth || 800,
height: config.defaultHeight || 600
}, state);

return {
get x() { return state.x; },
get y() { return state.y; },
get width() { return state.width; },
get height() { return state.height; },
get displayBounds() { return state.displayBounds; },
get isMaximized() { return state.isMaximized; },
get isFullScreen() { return state.isFullScreen; },
saveState,
unmanage,
manage,
resetStateToDefault
};
};
5 changes: 0 additions & 5 deletions www/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ body {
}
}

:root {
--side-bar-width: 300px;
--tab-control-inline: 0;
}

/* Buttons */

button {
Expand Down
18 changes: 13 additions & 5 deletions www/index.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
:root {
--tab-control-inline: 0;
}

/* Title bar layout */

div.title-bar {
Expand Down Expand Up @@ -192,7 +196,7 @@ div.field-item-label {
div.tab-bar {
position: absolute;
left: 10px;
right: calc(10px + var(--tab-control-inline) * 144px);
right: calc(10px + var(--tab-control-inline) * 172px);
top: 0px;
height: 50px;

Expand Down Expand Up @@ -221,7 +225,7 @@ div.tab-bar-shadow-left {

div.tab-bar-shadow-right {
opacity: 100%;
right: calc(10px + var(--tab-control-inline) * 144px);
right: calc(10px + var(--tab-control-inline) * 172px);
background-image: linear-gradient(to left, white, rgba(255, 255, 255, 0.75), transparent);
}

Expand All @@ -238,7 +242,7 @@ div.tab-bar-shadow-right {
div.tab-bar-scroll {
position: absolute;
left: 10px;
right: calc(10px + var(--tab-control-inline) * 144px);
right: calc(10px + var(--tab-control-inline) * 172px);
top: 0px;
height: 50px;

Expand Down Expand Up @@ -310,10 +314,14 @@ button.tab-control {
}

button.play, button.pause {
right: 116px;
right: 144px;
}

button.move-left {
right: 106px;
}

button.close {
right: 78px;
}

Expand Down Expand Up @@ -765,7 +773,7 @@ button.odometry-popup-button {
position: absolute;
width: 25px;
height: 25px;
top: 0px;
top: 1px;
right: 5px;
}

Expand Down
6 changes: 6 additions & 0 deletions www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
<line x1="7" y1="14" x2="13" y2="20"></line>
</svg>
</button>
<button class="tab-control close" tabindex="-1">
<svg height="28" width="28">
<line x1="8" y1="8" x2="20" y2="20"></line>
<line x1="20" y1="8" x2="8" y2="20"></line>
</svg>
</button>
<button class="tab-control move-right" tabindex="-1">
<svg height="28" width="28">
<line x1="7" y1="14" x2="21" y2="14"></line>
Expand Down
Loading

0 comments on commit 600fdf0

Please sign in to comment.