mirror of
https://github.com/cupcakearmy/unpixel.git
synced 2025-12-14 01:35:03 +00:00
initial commit
This commit is contained in:
84
src/back/banner.ts
Normal file
84
src/back/banner.ts
Normal 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
33
src/back/index.ts
Normal 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
84
src/back/settings.ts
Normal 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
64
src/back/tray.ts
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user