initial commit

This commit is contained in:
2021-04-06 15:03:44 +02:00
commit 606832a141
21 changed files with 9238 additions and 0 deletions

84
src/back/banner.ts Normal file
View File

@@ -0,0 +1,84 @@
import dayjs from 'dayjs'
import { BrowserWindow, BrowserWindowConstructorOptions, ipcMain } from 'electron'
import { join } from 'path'
import logger from 'electron-log'
import { DEV } from '.'
import Settings from './settings'
import TrayUtility from './tray'
export default class Banner {
static interval: ReturnType<typeof setInterval>
static window: BrowserWindow | null = null
static init() {
if (this.interval) return
this.interval = setInterval(this.check, 1000)
ipcMain.on('close', () => {
this.close()
})
}
static check() {
const paused: boolean = Settings.load('paused')
if (paused) {
TrayUtility.setStatus('Paused')
return
}
const every = Settings.load('every')
const now = dayjs()
const lastRun = Settings.load('lastRun')
const diff = every - now.diff(dayjs(lastRun), 'minutes')
TrayUtility.setStatus(`Next break: ${diff}m`)
if (diff < 1) {
Banner.open()
}
}
static open() {
if (this.window) return
logger.debug('Showing banner')
const options: BrowserWindowConstructorOptions = {
frame: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
width: 1200,
height: 600,
}
if (!DEV) {
Object.assign(options, {
resizable: false,
movable: false,
simpleFullscreen: true,
fullscreen: true,
transparent: true,
})
}
this.window = new BrowserWindow(options)
const entry = join(__dirname, '../front/banner/index.html')
this.window.loadFile(entry)
if (!DEV) {
this.window.maximize()
this.window.setAlwaysOnTop(true, 'floating', 99)
this.window.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true })
this.window.setFullScreenable(false)
} else {
this.window.webContents.toggleDevTools()
}
this.window.focus()
}
static close() {
if (this.window) {
Settings.save('lastRun', Date.now())
this.window.close()
this.window = null
}
}
}

33
src/back/index.ts Normal file
View File

@@ -0,0 +1,33 @@
import { app } from 'electron'
import logger from 'electron-log'
import TrayUtility from './tray'
import Settings from './settings'
import Banner from './banner'
export const DEV = !app.isPackaged
// Disable gpu
app.disableHardwareAcceleration()
app.commandLine.appendSwitch('disable-software-rasterizer')
logger.catchErrors({ showDialog: true })
logger.log('Starting')
app
.whenReady()
.then(() => {
logger.log('Initializing')
if (!DEV) app.dock.hide()
TrayUtility.init()
Settings.init()
Banner.init()
logger.log('Done')
})
.catch((e) => {
logger.error(e)
process.exit(1)
})
app.on('window-all-closed', () => {
// Prevent closing of the app
})

84
src/back/settings.ts Normal file
View File

@@ -0,0 +1,84 @@
import { BrowserWindow, ipcMain } from 'electron'
import Store from 'electron-store'
import { join } from 'path'
import AutoLaunch from 'auto-launch'
import { productName } from '../../package.json'
const autoLaunch = new AutoLaunch({ name: productName, mac: { useLaunchAgent: true } })
import { DEV } from '.'
const store = new Store()
const defaults = {
every: 20,
duration: 20,
boot: true,
paused: false,
lastRun: 0,
autoClose: false,
}
export type SettingKeys = keyof typeof defaults
const IntNormalizer = (x: any) => parseInt(x)
const BoolNormalizer = (x: any) => !!x
const normalizers: Record<SettingKeys, (x: any) => any> = {
every: IntNormalizer,
duration: IntNormalizer,
boot: BoolNormalizer,
autoClose: BoolNormalizer,
paused: BoolNormalizer,
lastRun: IntNormalizer,
}
export default class Settings {
static win: BrowserWindow | null = null
static init() {
ipcMain.on('save', (e, { key, value }) => {
this.save(key, value)
})
ipcMain.on('load', (e, { key }) => {
e.returnValue = this.load(key)
})
if (Settings.load('boot')) {
autoLaunch.enable()
}
Settings.save('lastRun', Date.now())
}
static save<T extends SettingKeys>(key: T, value: typeof defaults[T]) {
const normalized = normalizers[key](value)
store.set(key, normalized)
if (key === 'boot') {
normalized ? autoLaunch.enable() : autoLaunch.disable()
}
}
static load<T extends SettingKeys>(key: T) {
const saved = store.get(key) as typeof defaults[T] | undefined
return saved ?? defaults[key]
}
static open() {
if (this.win) return
this.win = new BrowserWindow({
width: 400,
height: 485,
center: true,
resizable: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
})
const entry = join(__dirname, '../front/settings/index.html')
Settings.win.loadFile(entry)
if (DEV) {
Settings.win.setSize(800, 485)
Settings.win.setResizable(true)
Settings.win.webContents.openDevTools()
}
}
}

64
src/back/tray.ts Normal file
View File

@@ -0,0 +1,64 @@
import { Tray, Menu, nativeImage } from 'electron'
import path from 'path'
import Banner from './banner'
import Settings from './settings'
enum Items {
Status = 'status',
Pause = 'pause',
Run = 'run',
}
export default class TrayUtility {
static menu: Parameters<typeof Menu['buildFromTemplate']>[0] = [
{ label: 'Status', type: 'normal', enabled: false, id: Items.Status },
{ type: 'separator' },
{
label: 'Take a break now',
type: 'normal',
id: Items.Run,
click: () => Banner.open(),
},
{ label: 'Pause', type: 'checkbox', id: Items.Pause },
{ label: 'Settings', type: 'normal', click: () => Settings.open() },
{ type: 'separator' },
{ label: 'Quit', type: 'normal', role: 'quit' },
]
static tray: Tray | null = null
static setStatus(status: string) {
this.menu[0].label = status
this.tray.setContextMenu(this.build())
}
private static build() {
const menu = Menu.buildFromTemplate(this.menu)
for (const item of menu.items) {
if (item.id === Items.Pause) {
let initial = Settings.load('paused')
item.checked = initial
item.click = () => {
initial = !initial
item.checked = initial
Settings.save('paused', initial)
}
break
}
}
return menu
}
static init() {
if (!this.tray) {
const file = path.join(__dirname, '../../assets/tray.png')
const icon = nativeImage.createFromPath(file).resize({
width: 24,
height: 24,
})
this.tray = new Tray(icon)
this.tray.setContextMenu(this.build())
}
}
}