diff --git a/package.json b/package.json index 043776d4..1ad48dc5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "crossover", "productName": "CrossOver", - "version": "1.3.2", + "version": "2.0.0-alpha", "description": "A Crosshair Overlay for any screen", "license": "MIT", "repository": "lacymorrow/crossover", @@ -42,11 +42,12 @@ "bindings": "^1.5.0", "electron-context-menu": "^2.0.1", "electron-debug": "^3.0.1", - "electron-preferences": "^2.2.0", + "electron-preferences": "../electron-preferences", "electron-store": "^7.0.0", "electron-unhandled": "^3.0.2", "electron-updater": "^4.3.1", - "electron-util": "^0.15.0" + "electron-util": "^0.15.0", + "iohook": "^0.9.3" }, "devDependencies": { "ava": "^3.8.2", @@ -61,6 +62,21 @@ "spectron": "^13.0.0", "xo": "^0.37.1" }, + "iohook": { + "targets": [ + "node-83", + "electron-85" + ], + "platforms": [ + "win32", + "darwin", + "linux" + ], + "arches": [ + "x64", + "ia32" + ] + }, "xo": { "envs": [ "node", diff --git a/readme.md b/readme.md index cc36256b..f79b2c3e 100644 --- a/readme.md +++ b/readme.md @@ -10,8 +10,8 @@ CrossOver allows you to place a customizable crosshair overlay above any application window. Improve your aim and gain a competitive advantage with a permanant colored crosshair to mark center screen. -

- +

+

# Quick Start @@ -54,8 +54,8 @@ Improve your aim and gain a competitive advantage with a permanant colored cross ##### [View all dowloads for any OS](https://github.com/lacymorrow/crossover/releases/latest) -

- +

+

Other crosshair programs offer a single style or color option, and often don't allow you to reposition them. CrossOver is a small, unintrusive crosshair overlay which has plenty of configuration options to assist with aiming and vision of your crosshair. CrossOver offers a huge improvement to many games' default crosshairs for those with colorblindness or contrast issues. @@ -102,6 +102,11 @@ Other crosshair programs offer a single style or color option, and often don't a ###### _Further instructions for [Windows](https://www.techjunkie.com/windows-protected-your-pc-disable-smartscreen/) and [Mac](https://support.apple.com/en-us/HT202491)_ + + +

+ +

--- ## Usage @@ -112,28 +117,66 @@ Other crosshair programs offer a single style or color option, and often don't a **Choose Crosshair:** Click the bullseye â—Ž to select from tons of preloaded crosshairs. Drag an image to the window to use your own custom crosshair. -**Customize** settings using the "gear" icon. +**Customize** settings using the "gear" icon. _See [Settings](#settings) below._ -**Unlock the app to quit**. In Windows: right-click > `Close`. +**Unlock the app to quit** using Control-Alt-Shift-X, then click the close icon in the top-left corner. In Windows you can right-click on the crosshair window > `Close`. -#### Keyboard Shortcuts +#### Default Keyboard Shortcuts Description | Keys -----------------------| ----------------------- -Move the crosshair a single pixel | Control-Alt-Shift-Arrows Toggle the settings window and lock the crosshair in place | Control-Alt-Shift-X +Move the crosshair a single pixel | Control-Alt-Shift-Arrows Quickly hide/show the application | Control-Alt-Shift-H Center the crosshair window | Control-Alt-Shift-C +Move the crosshair to the next connected display | Control-Alt-Shift-M Duplicate your crosshair in a shadow window | Control-Alt-Shift-D -Reset all settings and center the window | Control-Alt-Shift-R Display the "About" window details | Control-Alt-Shift-A +Reset all settings and center the window | Control-Alt-Shift-R ###### Mac: the Option key is used instead of Alt. ###### Linux: Some distributions do not center on double-click. +

+ +

+--- + + +## Settings + +##### Crosshair +Choose from over 100 real and fictional crosshairs, or use your own by dragging an image to the window. You can change the size and opacity. + +##### Reticle +The reticle is the small dot or cross at the center of the sight. You can select the shape and the color, or this can be turned off. + +##### Hide on ADS +Choose a mouse button to use as your ADS button. The crosshair will be hidden while this button is held down. + +##### Harware acceleration +A handful of users have reported FPS issues with certain games ([#70](https://github.com/lacymorrow/crossover/issues/70)), CrossOver should not add any FPS or latency so if you experience it, toggle this setting on or off and disable [`Hide on ADS`](#hide-on-ads) + +##### Start on boot +You can automatically set CrossOver to start when your system starts on your gaming PC, so you're always ready to pop heads. + +##### Automatic Updates +By default CrossOver will automatically keep itself updated with bug fixes and improvements. You can disable this in the settings to prevent all network requests. +CrossOver will _only_ connect to GitHub to download published releases and does not send or store _any_ personal data. I'm a solo developer and you aren't important to me 🙂 I just want to game. + +#### Keybinds +> See [default keyboard shortcuts](#default-keyboard-shortcuts). +All of the keybinds can be changed or disabled except for the bind to reset all settings. Click into the input and press your bind, or press Backspace or Delete to disable a bind. + +Keep in mind that keybinds may conflict with other programs so be careful about what you choose. There's no real mechanism for preventing you from setting two conflicting keyinds within the app, so don't do that. + ##### Duplicate crosshairs -Duplicate crosshair windows use the same settings as the main window and do not support all of the features of the main window. Settings for duplicate crosshairs will not be saved and all duplicate windows will be closed if the main window is closed. +Duplicate crosshair windows use the same settings as the main window and do not support all of the features of the main window. Settings for duplicate crosshairs will not be saved and all duplicate windows will be closed if the main window is closed. Duplicate crosshairs cannot follow the mouse. + +##### Reset +To reset all settings and binds to default, press Control-Alt-Shift-R +


diff --git a/src/config.js b/src/config.js index 5084e166..e0e0b72d 100644 --- a/src/config.js +++ b/src/config.js @@ -1,46 +1,17 @@ 'use strict' -const Store = require( 'electron-store' ) - -// Default app settings -const defaults = { - crosshair: 'static/crosshairs/Actual/leupold-dot.png', - color: '#FFF83B', - appOpacity: 80, - opacity: 80, - positionX: null, - positionY: null, - sight: 'dot', - size: 60, - windowLocked: false, - - app: { - DISABLE_GPU: false, - OS_STARTUP: false, - WINDOW_FRAME: false - } -} - -// Initialize app state -const config = new Store( { - defaults -} ) // Constants const APP_HEIGHT = 124 const MAX_SHADOW_WINDOWS = 20 -const SETTINGS_WINDOW_HEIGHT = 250 +const SETTINGS_WINDOW_DEVTOOLS = false const SHADOW_WINDOW_OFFSET = 40 const SUPPORTED_IMAGE_FILE_TYPES = [ '.bmp', '.jpg', '.jpeg', '.png', '.gif', '.webp' ] module.exports = { - config, - - defaults, - APP_HEIGHT, MAX_SHADOW_WINDOWS, - SETTINGS_WINDOW_HEIGHT, + SETTINGS_WINDOW_DEVTOOLS, SHADOW_WINDOW_OFFSET, SUPPORTED_IMAGE_FILE_TYPES diff --git a/src/main.js b/src/main.js index 1a9072c9..2dc9d1b2 100644 --- a/src/main.js +++ b/src/main.js @@ -1,8 +1,32 @@ 'use strict' -// Conflicting accelerator on Fedora -// Improve escapeAction to be window-aware -// dont setPosition if monitor has been unplugged +/* + Changed: + #20 Custom keybinds + #85 turn off updates + #84 Mouse hooks + #70 Performance settings - gpu + #86 start on boot + #88 allow disable keybinds + hide settings on blur + + High: + Follow Mouse + prevent double keybind + test window placement on windows/mac + fix unhandled #81 + + Medium: + polish menu + Custom crosshair should be a setting + shadow window bug on move to next display + + Low: + Define preferences browserwindowoverrdes in main.j, make main a parent window + Conflicting accelerator on Fedora + Improve escapeAction to be window-aware + dont setPosition if monitor has been unplugged +*/ // const NativeExtension = require('bindings')('NativeExtension'); const fs = require( 'fs' ) @@ -13,11 +37,13 @@ const { autoUpdater } = require( 'electron-updater' ) const { activeWindow, centerWindow, debugInfo, getWindowBoundsCentered, is, showAboutWindow } = require( 'electron-util' ) const unhandled = require( 'electron-unhandled' ) const debug = require( 'electron-debug' ) -const { debounce } = require( './util.js' ) -const { config, defaults, APP_HEIGHT, MAX_SHADOW_WINDOWS, SHADOW_WINDOW_OFFSET, SUPPORTED_IMAGE_FILE_TYPES } = require( './config.js' ) +let ioHook // Dynamic Import +// Const ioHook = require('iohook'); +const { checkboxTrue, debounce } = require( './util.js' ) +const { APP_HEIGHT, MAX_SHADOW_WINDOWS, SETTINGS_WINDOW_DEVTOOLS, SHADOW_WINDOW_OFFSET, SUPPORTED_IMAGE_FILE_TYPES } = require( './config.js' ) const menu = require( './menu.js' ) +const prefs = require( './preferences.js' ) -// Maybe add settings here? // Const contextMenu = require('electron-context-menu') // contextMenu() @@ -27,11 +53,31 @@ debug( { devToolsMode: 'undocked' } ) +// Electron reloader is janky sometimes +// try { +// require( 'electron-reloader' )( module ) +// } catch {} + +/* App setup */ + +// Note: Must match `build.appId` in package.json +app.setAppUserModelId( 'com.lacymorrow.crossover' ) + +// Prevent multiple instances of the app +if ( !app.requestSingleInstanceLock() ) { + + app.quit() + +} + +// Auto-Updater // Uncomment this before publishing your first version. // It's commented out as it throws an error if there are no published versions. try { - if ( !is.development && !is.linux ) { + if ( !is.development && !is.linux && checkboxTrue( prefs.value( 'app.updates' ), 'updates' ) ) { + + console.log( 'Setting: Automatic Updates' ) const FOUR_HOURS = 1000 * 60 * 60 * 4 setInterval( () => { @@ -46,27 +92,26 @@ try { } catch {} -// Electron reloader is janky sometimes -// try { -// require( 'electron-reloader' )( module ) -// } catch {} - -/* App setup */ +// Start app on boot +if ( !is.development && checkboxTrue( prefs.value( 'app.boot' ), 'boot' ) ) { -// Note: Must match `build.appId` in package.json -app.setAppUserModelId( 'com.lacymorrow.crossover' ) + app.setLoginItemSettings( { + openAtLogin: true + } ) -// Prevent multiple instances of the app -if ( !app.requestSingleInstanceLock() ) { +} else { - app.quit() + app.setLoginItemSettings( { + openAtLogin: false + } ) } // Fix for Linux transparency issues -if ( is.linux || config.get( 'app' ).DISABLE_GPU ) { +if ( is.linux || !checkboxTrue( prefs.value( 'app.gpu' ), 'gpu' ) ) { // Disable hardware acceleration + console.log( 'Setting: Disable GPU' ) app.commandLine.appendSwitch( 'enable-transparent-visuals' ) app.commandLine.appendSwitch( 'disable-gpu' ) app.disableHardwareAcceleration() @@ -76,7 +121,7 @@ if ( is.linux || config.get( 'app' ).DISABLE_GPU ) { // Prevent window from being garbage collected let mainWindow let chooserWindow -let settingsWindow +let prefsWindow const shadowWindows = new Set() let windowHidden = false // Maintain hidden state @@ -110,7 +155,7 @@ const createMainWindow = async isShadowWindow => { webPreferences: { contextIsolation: !is.linux, enableRemoteModule: true, - nodeIntegration: false, // We don't absolutely need this, but renderer require's some things + nodeIntegration: false, preload: path.join( __dirname, 'preload.js' ) } @@ -126,10 +171,12 @@ const createMainWindow = async isShadowWindow => { setDockVisible( false ) win.setFullScreenable( false ) + // VisibleOnFullscreen removed in https://github.com/electron/electron/pull/21706 win.setVisibleOnAllWorkspaces( true, { visibleOnFullScreen: true } ) + // Enables staying on fullscreen apps - mac - // setDockVisible( true ) + setDockVisible( true ) if ( isShadowWindow ) { @@ -170,14 +217,14 @@ const createMainWindow = async isShadowWindow => { const createChildWindow = async ( parent, windowName ) => { - const VALID_WINDOWS = [ 'chooser', 'settings' ] + const VALID_WINDOWS = [ 'chooser' ] - const preferences = { + const options = { parent, modal: true, show: false, type: 'toolbar', - frame: config.get( 'app' ).WINDOW_FRAME, + frame: prefs.value( 'hidden.frame' ), hasShadow: true, titleBarStyle: 'customButtonsOnHover', fullscreenable: false, @@ -200,14 +247,7 @@ const createChildWindow = async ( parent, windowName ) => { } - if ( windowName === 'settings' ) { - - preferences.width = 200 - preferences.height = 250 - - } - - const win = new BrowserWindow( preferences ) + const win = new BrowserWindow( options ) await win.loadFile( path.join( __dirname, `${windowName}.html` ) ) @@ -258,7 +298,7 @@ const getActiveWindow = () => { } -// Save position to settings +// Save position const saveBounds = debounce( win => { if ( !win ) { @@ -269,8 +309,8 @@ const saveBounds = debounce( win => { const bounds = win.getBounds() console.log( `Save bounds: ${bounds.x}, ${bounds.y}` ) - config.set( 'positionX', bounds.x ) - config.set( 'positionY', bounds.y ) + prefs.value( 'hidden.positionX', bounds.x ) + prefs.value( 'hidden.positionY', bounds.y ) }, 1000 ) @@ -278,7 +318,6 @@ const getCrosshairImages = async () => { // How many levels deep to recurse const crosshairsObject = await getImages( crosshairsPath, 2 ) - config.set( 'crosshairsObject', crosshairsObject ) return crosshairsObject @@ -327,20 +366,18 @@ const getImages = ( directory, level ) => { const setColor = ( color, targetWindow = mainWindow ) => { targetWindow.webContents.send( 'set_color', color ) - settingsWindow.webContents.send( 'set_color', color ) } const setOpacity = ( opacity, targetWindow = mainWindow ) => { targetWindow.webContents.send( 'set_opacity', opacity ) - settingsWindow.webContents.send( 'set_opacity', opacity ) } const setPosition = ( posX, posY, targetWindow = mainWindow ) => { - if ( posX === null || posY === null ) { + if ( posX === null || posY === null || typeof posX === 'undefined' || typeof posY === 'undefined' ) { return @@ -351,8 +388,8 @@ const setPosition = ( posX, posY, targetWindow = mainWindow ) => { if ( targetWindow === mainWindow ) { console.log( 'Save XY:', posX, posY ) - config.set( 'positionX', posX ) - config.set( 'positionY', posY ) + prefs.value( 'hidden.positionX', posX ) + prefs.value( 'hidden.positionY', posY ) } @@ -361,14 +398,12 @@ const setPosition = ( posX, posY, targetWindow = mainWindow ) => { const setSight = ( sight, targetWindow = mainWindow ) => { targetWindow.webContents.send( 'set_sight', sight ) - settingsWindow.webContents.send( 'set_sight', sight ) } const setSize = ( size, targetWindow = mainWindow ) => { targetWindow.webContents.send( 'set_size', size ) - settingsWindow.webContents.send( 'set_size', size ) } @@ -449,7 +484,7 @@ const hideWindow = () => { } -const toggleWindowLock = ( lock = !config.get( 'windowLocked' ) ) => { +const toggleWindowLock = ( lock = !prefs.value( 'hidden.locked' ) ) => { lockWindow( lock ) shadowWindows.forEach( currentWindow => { @@ -486,7 +521,7 @@ const lockWindow = ( lock, targetWindow = mainWindow ) => { } - config.set( 'windowLocked', lock ) + prefs.value( 'hidden.locked', lock ) } @@ -504,9 +539,11 @@ const hideChooserWindow = () => { // Switch window type when hiding chooser const hideSettingsWindow = () => { - if ( settingsWindow ) { + if ( prefsWindow && prefsWindow.isVisible() ) { - settingsWindow.hide() + prefs.value( 'hidden.showSettings', false ) + prefsWindow.close() + prefsWindow = null } @@ -517,7 +554,7 @@ const openChooserWindow = async () => { hideSettingsWindow() // Don't do anything if locked - if ( config.get( 'windowLocked' ) ) { + if ( prefs.value( 'hidden.locked' ) ) { return @@ -541,7 +578,7 @@ const openChooserWindow = async () => { // Modal placement is different per OS if ( is.macos ) { - const bounds = chooserWindow.getBounds() + const bounds = mainWindow.getBounds() chooserWindow.setBounds( { y: bounds.y + APP_HEIGHT } ) } else { @@ -557,21 +594,21 @@ const openChooserWindow = async () => { const openSettingsWindow = async () => { - hideChooserWindow() - // Don't do anything if locked - if ( config.get( 'windowLocked' ) ) { + if ( prefs.value( 'hidden.locked' ) ) { // || prefs.value( 'hidden.showSettings' ) return } - if ( !settingsWindow ) { + if ( prefs.value( 'hidden.showSettings' ) ) { - settingsWindow = await createSettings() + return escapeAction() } + hideChooserWindow() + // Create shortcut to close window if ( !globalShortcut.isRegistered( 'Escape' ) ) { @@ -579,20 +616,44 @@ const openSettingsWindow = async () => { } - settingsWindow.show() + prefsWindow = prefs.show() + if ( prefsWindow ) { - // Modal placement is different per OS - if ( is.macos ) { + prefsWindow.on( 'blur', () => { - const bounds = settingsWindow.getBounds() - settingsWindow.setBounds( { y: bounds.y + APP_HEIGHT } ) + if ( !SETTINGS_WINDOW_DEVTOOLS ) { - } else { + hideSettingsWindow() - // Windows - const bounds = getWindowBoundsCentered( { window: settingsWindow, useFullBounds: true } ) - const mainBounds = mainWindow.getBounds() - settingsWindow.setBounds( { x: bounds.x, y: mainBounds.y + mainBounds.height + 1 } ) + } + + } ) + + prefsWindow.on( 'closed', () => { + + prefs.value( 'hidden.showSettings', false ) + prefsWindow = null + + } ) + + prefsWindow.setAlwaysOnTop( true, 'pop-up-menu' ) + + // Modal placement is different per OS + if ( is.macos ) { + + const bounds = mainWindow.getBounds() + prefsWindow.setBounds( { y: bounds.y + APP_HEIGHT } ) + + } else { + + // Windows + const bounds = getWindowBoundsCentered( { window: prefsWindow, useFullBounds: true } ) + const mainBounds = mainWindow.getBounds() + prefsWindow.setBounds( { x: bounds.x, y: mainBounds.y + mainBounds.height + 1 } ) + + } + + prefs.value( 'hidden.showSettings', true ) } @@ -607,7 +668,7 @@ const moveWindow = options => { } const saveSettings = options.targetWindow === mainWindow - const locked = config.get( 'windowLocked' ) + const locked = prefs.value( 'hidden.locked' ) if ( !locked ) { @@ -621,7 +682,7 @@ const moveWindow = options => { options.targetWindow.setBounds( { y: newBound } ) if ( saveSettings ) { - config.set( 'positionY', newBound ) + prefs.value( 'hidden.positionY', newBound ) } @@ -631,7 +692,7 @@ const moveWindow = options => { options.targetWindow.setBounds( { y: newBound } ) if ( saveSettings ) { - config.set( 'positionY', newBound ) + prefs.value( 'hidden.positionY', newBound ) } @@ -641,7 +702,7 @@ const moveWindow = options => { options.targetWindow.setBounds( { x: newBound } ) if ( saveSettings ) { - config.set( 'positionX', newBound ) + prefs.value( 'hidden.positionX', newBound ) } @@ -651,7 +712,7 @@ const moveWindow = options => { options.targetWindow.setBounds( { x: newBound } ) if ( saveSettings ) { - config.set( 'positionX', newBound ) + prefs.value( 'hidden.positionX', newBound ) } @@ -709,36 +770,96 @@ const aboutWindow = () => { const escapeAction = () => { + console.log( 'Escape event' ) + hideChooserWindow() hideSettingsWindow() globalShortcut.unregister( 'Escape' ) } -const resetSettings = skipSetup => { +const mouseAction = ( event, hideOnMouse ) => { - // Close extra crosshairs - closeShadowWindows() + // Don't do anything if not locked + if ( hideOnMouse === -1 || !prefs.value( 'hidden.locked' ) ) { - const keys = Object.keys( defaults ) - for ( const element of keys ) { + return + + } - config.set( element, defaults[element] ) + // If right click + if ( event.button === hideOnMouse ) { + + hideWindow() } - centerAppWindow() +} + +const syncSettings = preferences => { - if ( !skipSetup ) { + setColor( preferences?.crosshair?.color ) + setOpacity( preferences?.crosshair?.opacity ) + setSight( preferences?.crosshair?.reticle ) + setSize( preferences?.crosshair?.size ) - setupApp() + // Reset all custom shortcuts + const escapeActive = globalShortcut.isRegistered( 'Escape' ) + globalShortcut.unregisterAll() + if ( escapeActive ) { + + globalShortcut.register( 'Escape', escapeAction ) } + registerShortcuts() + + registerMouseEvents() + +} + +const registerMouseEvents = async () => { + + const hideOnMouse = Number.parseInt( prefs.value( 'crosshair.hideOnMouse' ), 10 ) + if ( hideOnMouse === -1 ) { + + // Mouse events off + return + + } + + console.log( 'Setting: Mouse Events' ) + + // Dynamically require ioHook + if ( !ioHook ) { + + ioHook = await require( 'iohook' ) + + } + + // Unregister + ioHook.removeAllListeners( 'mousedown' ) + ioHook.removeAllListeners( 'mouseup' ) + + // Register + ioHook.on( 'mousedown', event => mouseAction( event, hideOnMouse ) ) + ioHook.on( 'mouseup', event => mouseAction( event, hideOnMouse ) ) + + // Register and start hook + ioHook.start() + } const registerEvents = () => { + registerMouseEvents() + + prefs.on( 'save', preferences => { + + syncSettings( preferences ) + + } ) + mainWindow.on( 'move', () => { saveBounds( mainWindow ) @@ -755,15 +876,6 @@ const registerEvents = () => { } ) - settingsWindow.on( 'close', async () => { - - hideWindow() - await createSettings() - registerEvents() - mainWindow.show() - - } ) - // Close windows if clicked away (mac only) if ( !is.development ) { @@ -773,37 +885,10 @@ const registerEvents = () => { } ) - settingsWindow.on( 'blur', () => { - - hideSettingsWindow() - - } ) - } } -const dColorInput = debounce( arg => { - - console.log( `Save color: ${arg}` ) - config.set( 'color', arg ) - -}, 400 ) - -const dOpacityInput = debounce( arg => { - - console.log( `Save opacity: ${arg}` ) - config.set( 'opacity', arg ) - -}, 400 ) - -const dSizeInput = debounce( arg => { - - console.log( `Save size: ${arg}` ) - config.set( 'size', arg ) - -}, 400 ) - const registerIpc = () => { /* IP Communication */ @@ -831,12 +916,6 @@ const registerIpc = () => { } ) - ipcMain.on( 'close_settings', _ => { - - hideSettingsWindow() - - } ) - ipcMain.on( 'close_window', event => { // Close a shadow window @@ -844,19 +923,6 @@ const registerIpc = () => { } ) - ipcMain.on( 'save_color', ( event, arg ) => { - - mainWindow.webContents.send( 'set_color', arg ) // Pass to renderer - // pass to shadows - shadowWindows.forEach( currentWindow => { - - currentWindow.webContents.send( 'set_color', arg ) - - } ) - dColorInput( arg ) - - } ) - ipcMain.on( 'save_custom_image', ( event, arg ) => { // Is it a file and does it have a supported extension? @@ -870,7 +936,7 @@ const registerIpc = () => { } ) - config.set( 'crosshair', arg ) + prefs.value( 'crosshair.crosshair', arg ) hideChooserWindow() } @@ -884,7 +950,7 @@ const registerIpc = () => { chooserWindow.webContents.send( 'load_crosshairs', { crosshairs: await getCrosshairImages(), - current: config.get( 'crosshair' ) + current: prefs.value( 'crosshair.crosshair' ) } ) } @@ -904,7 +970,7 @@ const registerIpc = () => { } ) - config.set( 'crosshair', arg ) + prefs.value( 'crosshair.crosshair', arg ) } else { @@ -914,43 +980,6 @@ const registerIpc = () => { } ) - ipcMain.on( 'save_opacity', ( event, arg ) => { - - mainWindow.webContents.send( 'set_opacity', arg ) // Pass to renderer - shadowWindows.forEach( currentWindow => { - - currentWindow.webContents.send( 'set_opacity', arg ) - - } ) - dOpacityInput( arg ) - - } ) - - ipcMain.on( 'save_size', ( event, arg ) => { - - mainWindow.webContents.send( 'set_size', arg ) // Pass to renderer - shadowWindows.forEach( currentWindow => { - - currentWindow.webContents.send( 'set_size', arg ) - - } ) - dSizeInput( arg ) - - } ) - - ipcMain.on( 'save_sight', ( event, arg ) => { - - mainWindow.webContents.send( 'set_sight', arg ) // Pass to renderer - shadowWindows.forEach( currentWindow => { - - currentWindow.webContents.send( 'set_sight', arg ) - - } ) - console.log( `Save sight: ${arg}` ) - config.set( 'sight', arg ) - - } ) - ipcMain.on( 'center_window', () => { console.log( 'Center window' ) @@ -966,7 +995,7 @@ const registerIpc = () => { } -const defaultShortcuts = () => { +const keyboardShortcuts = () => { /* Default accelerator */ const accelerator = 'Control+Shift+Alt' @@ -1035,7 +1064,7 @@ const defaultShortcuts = () => { keybind: `${accelerator}+R`, fn: () => { - resetSettings() + resetApp() } }, @@ -1095,19 +1124,31 @@ const defaultShortcuts = () => { const registerShortcuts = () => { // Register all shortcuts - const customShortcuts = [] - defaultShortcuts().forEach( shortcut => { + const { keybinds } = prefs.defaults + const custom = prefs.value( 'keybinds' ) // Defaults + keyboardShortcuts().forEach( shortcut => { + + // Custom shortcuts + if ( custom[shortcut.action] === '' ) { + + console.log( `Clearing keybind for ${shortcut.action}` ) - // If action exists in customShortcuts - const index = customShortcuts.map( element => element.action ).indexOf( shortcut.action ) - if ( index > -1 ) { + } else if ( custom[shortcut.action] && keybinds[shortcut.action] && custom[shortcut.action] !== keybinds[shortcut.action] ) { // If a custom shortcut exists for this action console.log( `Custom keybind for ${shortcut.action}` ) - globalShortcut.register( customShortcuts[index].keybind, shortcut.fn ) + globalShortcut.register( custom[shortcut.action], shortcut.fn ) + + } else if ( keybinds[shortcut.action] ) { + + // Set default keybind + globalShortcut.register( keybinds[shortcut.action], shortcut.fn ) } else { + // Fallback to internal bind - THIS SHOULDNT HAPPEN + // if it does you forgot to add a default keybind for this shortcut + console.log( 'ERROR', shortcut ) globalShortcut.register( shortcut.keybind, shortcut.fn ) } @@ -1120,7 +1161,7 @@ const createChooser = async currentCrosshair => { if ( !currentCrosshair ) { - currentCrosshair = config.get( 'crosshair' ) + currentCrosshair = prefs.value( 'crosshair.crosshair' ) } @@ -1136,16 +1177,40 @@ const createChooser = async currentCrosshair => { } -const createSettings = async () => { +// Temp until implemented in prefs +const resetPreferences = () => { + + const { defaults } = prefs + for ( const [ key, value ] of Object.entries( defaults ) ) { + + prefs.value( key, value ) + + } + +} + +const resetApp = async skipSetup => { + + // Close extra crosshairs + closeShadowWindows() + escapeAction() + resetPreferences() + centerAppWindow( { targetWindow: mainWindow } ) + + if ( !skipSetup ) { - settingsWindow = await createChildWindow( mainWindow, 'settings' ) + globalShortcut.unregisterAll() + setupApp() - return settingsWindow + } } const setupApp = async () => { + // Preferences + prefs.value( 'hidden.showSettings', false ) + // IPC registerIpc() @@ -1153,10 +1218,7 @@ const setupApp = async () => { registerShortcuts() // Set to previously selected crosshair - const currentCrosshair = config.get( 'crosshair' ) - - // Create child windows - await createSettings() + const currentCrosshair = prefs.value( 'crosshair.crosshair' ) if ( currentCrosshair ) { @@ -1165,26 +1227,27 @@ const setupApp = async () => { } - setColor( config.get( 'color' ) ) - setOpacity( config.get( 'opacity' ) ) - setSight( config.get( 'sight' ) ) - setSize( config.get( 'size' ) ) + setColor( prefs.value( 'crosshair.color' ) ) + setOpacity( prefs.value( 'crosshair.opacity' ) ) + setSight( prefs.value( 'crosshair.reticle' ) ) + setSize( prefs.value( 'crosshair.size' ) ) - // Center app by default - set position if config exists - if ( config.get( 'positionX' ) !== null ) { + // Center app by default - set position if exists + if ( prefs.value( 'hidden.positionX' ) !== null && typeof prefs.value( 'hidden.positionX' ) !== 'undefined' ) { - setPosition( config.get( 'positionX' ), config.get( 'positionY' ) ) + setPosition( prefs.value( 'hidden.positionX' ), prefs.value( 'hidden.positionY' ) ) } // Set lock state, timeout makes it pretty setTimeout( () => { - const locked = config.get( 'windowLocked' ) + const locked = prefs.value( 'hidden.locked' ) lockWindow( locked ) // Show on first load if unlocked (unlocking shows already) + // if locked we have to call show() if another window has focus if ( locked ) { mainWindow.show() @@ -1193,7 +1256,11 @@ const setupApp = async () => { }, 500 ) - await createChooser( currentCrosshair ) + if ( !chooserWindow ) { + + await createChooser( currentCrosshair ) + + } // Window Events after windows are created registerEvents() @@ -1202,7 +1269,7 @@ const setupApp = async () => { if ( process.env.CROSSOVER_RESET ) { console.log( 'Reset Triggered' ) - resetSettings( true ) + resetApp( true ) } @@ -1211,19 +1278,19 @@ const setupApp = async () => { const setupShadowWindow = async shadow => { shadow.webContents.send( 'add_class', 'shadow' ) - shadow.webContents.send( 'set_crosshair', config.get( 'crosshair' ) ) - setColor( config.get( 'color' ), shadow ) - setOpacity( config.get( 'opacity' ), shadow ) - setSight( config.get( 'sight' ), shadow ) - setSize( config.get( 'size' ), shadow ) - if ( config.get( 'positionX' ) > -1 ) { + shadow.webContents.send( 'set_crosshair', prefs.value( 'crosshair.crosshair' ) ) + setColor( prefs.value( 'crosshair.color' ), shadow ) + setOpacity( prefs.value( 'crosshair.opacity' ), shadow ) + setSight( prefs.value( 'crosshair.reticle' ), shadow ) + setSize( prefs.value( 'crosshair.size' ), shadow ) + if ( prefs.value( 'hidden.positionX' ) > -1 ) { // Offset position slightly - setPosition( config.get( 'positionX' ) + ( shadowWindows.size * SHADOW_WINDOW_OFFSET ), config.get( 'positionY' ) + ( shadowWindows.size * SHADOW_WINDOW_OFFSET ), shadow ) + setPosition( prefs.value( 'hidden.positionX' ) + ( shadowWindows.size * SHADOW_WINDOW_OFFSET ), prefs.value( 'hidden.positionY' ) + ( shadowWindows.size * SHADOW_WINDOW_OFFSET ), shadow ) } - lockWindow( config.get( 'windowLocked' ), shadow ) + lockWindow( prefs.value( 'hidden.locked' ), shadow ) } diff --git a/src/menu.js b/src/menu.js index beef7599..bc7d65ac 100644 --- a/src/menu.js +++ b/src/menu.js @@ -11,25 +11,25 @@ const { } = require( 'electron-util' ) const { config } = require( './config.js' ) -const showBootLaunch = () => { +// Const showBootLaunch = () => { - if ( app.getLoginItemSettings().openAtLogin ) { +// if ( app.getLoginItemSettings().openAtLogin ) { - console.log( 'Set open at login: true' ) - app.setLoginItemSettings( { - openAtLogin: false - } ) +// console.log( 'Set open at login: true' ) +// app.setLoginItemSettings( { +// openAtLogin: false +// } ) - } else { +// } else { - console.log( 'Set open at login: false' ) - app.setLoginItemSettings( { - openAtLogin: true - } ) +// console.log( 'Set open at login: false' ) +// app.setLoginItemSettings( { +// openAtLogin: true +// } ) - } +// } -} +// } const showPreferences = () => { @@ -208,16 +208,16 @@ const editSubmenu = [ const macosTemplate = [ appMenu( [ - { - label: 'Open at startup', - type: 'checkbox', - checked: false, - click() { + // { + // label: 'Open at startup', + // type: 'checkbox', + // checked: false, + // click() { - showBootLaunch() + // showBootLaunch() - } - }, + // } + // }, { label: 'Preferences…', accelerator: 'Command+,', diff --git a/src/preferences.js b/src/preferences.js new file mode 100644 index 00000000..191b5f5c --- /dev/null +++ b/src/preferences.js @@ -0,0 +1,281 @@ +// Via https://github.com/tkambler/electron-preferences +'use strict' +const electron = require( 'electron' ) +const { app } = electron +const path = require( 'path' ) +const ElectronPreferences = require( 'electron-preferences' ) +const { SETTINGS_WINDOW_DEVTOOLS } = require( './config.js' ) + +const preferences = new ElectronPreferences( { + /** + * Where should preferences be saved? + */ + dataStore: path.resolve( app.getPath( 'userData' ), 'preferences.json' ), + /** + * Default values. + */ + defaults: { + crosshair: { + crosshair: 'static/crosshairs/Actual/leupold-dot.png', + color: '#FFF83B', + size: 80, + opacity: 80, + reticle: 'dot', + reticleSize: 80, + hideOnMouse: '-1' + }, + app: { + updates: [ 'updates' ], + boot: [], + gpu: [ 'gpu' ] + }, + keybinds: { + reset: 'Control+Shift+Alt+R', + lock: 'Control+Shift+Alt+X', + center: 'Control+Shift+Alt+C', + hide: 'Control+Shift+Alt+H', + // HideHold: 'Control+Shift+Alt+Y', + duplicate: 'Control+Shift+Alt+D', + changeDisplay: 'Control+Shift+Alt+M', + moveUp: 'Control+Shift+Alt+Up', + moveDown: 'Control+Shift+Alt+Down', + moveLeft: 'Control+Shift+Alt+Left', + moveRight: 'Control+Shift+Alt+Right', + about: 'Control+Shift+Alt+A' + }, + hidden: { + frame: false, + locked: false, + showSettings: false, + positionX: null, + positionY: null, + test: true + + } + }, + + browserWindowOverrides: { + title: 'CrossOver Preferences', + webPreferences: { + devTools: SETTINGS_WINDOW_DEVTOOLS + } + + }, + /** + * The preferences window is divided into sections. Each section has a label, an icon, and one or + * more fields associated with it. Each section should also be given a unique ID. + */ + sections: [ + { + id: 'crosshair', + label: 'Crosshair Settings', + icon: 'vector', + form: { + groups: [ + { + /** + * Group heading is optional. + */ + label: 'Crosshair Settings', + fields: [ + { + label: 'Color', + key: 'color', + type: 'color', + format: 'hex', // Can be hex, hsl or rgb + help: 'Center sight color' + }, + // { + // label: 'Custom Crosshair', + // key: 'crosshair', + // type: 'text', + // help: 'What is your last name?' + // }, + { + label: 'Reticle', + key: 'reticle', + type: 'radio', + options: [ + { label: 'Dot', value: 'dot' }, + { label: 'Cross', value: 'cross' }, + { label: 'No sight', value: 'off' } + ] + }, + // { + // label: 'Reticle size', + // key: 'reticleSize', + // type: 'slider', + // min: 1, + // max: 50 + // }, + { + label: 'Crosshair Size', + key: 'size', + type: 'slider', + min: 1, + max: 100 + }, + { + label: 'Opacity', + key: 'opacity', + type: 'slider', + min: 1, + max: 100 + }, + { + label: 'Hide crosshair when Aiming Down Sights (ADS):', + key: 'hideOnMouse', + type: 'radio', + options: [ + { label: 'Never', value: '-1' }, + { label: 'Right mouse-button', value: '2' }, + { label: 'Middle mouse-button', value: '3' }, + { label: 'Left mouse-button', value: '1' }, + { label: 'Backward mouse-button', value: '4' }, + { label: 'Forward mouse-button', value: '5' } + ], + help: 'CrossOver can be hidden while aiming down sights. This is a beta feature, use at your own risk.' + } + ] + } + ] + } + }, + { + id: 'app', + label: 'System Settings', + icon: 'preferences', + form: { + groups: [ + { + label: 'System Settings', + fields: [ + { + label: 'Automatic Updates', + key: 'updates', + type: 'checkbox', + options: [ + { label: 'Allow CrossOver to automatically update', value: 'updates' } + ], + help: 'CrossOver will make a network connection to GitHub.com. No personal data is sent.' + }, + { + label: 'Run at startup', + key: 'system', + type: 'checkbox', + options: [ + { label: 'Start on system boot', value: 'boot' } + ], + help: 'CrossOver will start when your computer starts.' + }, + { + label: 'Hardware acceleration', + key: 'gpu', + type: 'checkbox', + options: [ + { label: 'Enable hardware acceleration', value: 'gpu' } + ], + help: 'If you are having issues with FPS, try disabling hardware acceleration.' + } + ] + } + ] + } + }, + { + id: 'keybinds', + label: 'Keybinds', + icon: 'handout', + form: { + groups: [ + { + label: 'Custom Keybinds (Beta)', + fields: [ + + { + heading: 'Important Message', + content: '

You can clear or disable a keybind completely by using Backspace/Delete. Use CTRL+ALT+SHIFT+R to reset all settings.

', + type: 'message' + }, + + { + label: 'Lock Crosshair in Place', + key: 'lock', + type: 'accelerator', + help: 'Unlock CrossOver to change settings, then lock the app in place to game.' + }, + { + label: 'Center Crosshair', + key: 'center', + type: 'accelerator', + help: 'Center the crosshair window on the current screen.' + }, + { + label: 'Toggle Hide Crosshair', + key: 'hide', + type: 'accelerator', + help: 'Hide CrossOver from the screen.' + }, + // { + // label: 'Hold Hide Crosshair', + // key: 'hideHold', + // type: 'accelerator', + // help: 'Hide CrossOver from the screen while holding down the shortcut.' + // }, + { + label: 'Duplicate Crosshair', + key: 'duplicate', + type: 'accelerator', + help: 'Create a duplicate "shadow" crosshair. Settings are not saved for shadow crosshairs.' + }, + { + label: 'Change Display', + key: 'changeDisplay', + type: 'accelerator', + help: 'Center CrossOver on the next connected display.' + }, + // { + // label: 'Reset All Settings', + // key: 'reset', + // type: 'accelerator', + // help: 'Reset all settings to default and center the crosshair.' + // }, + { + label: 'Move Up', + key: 'moveUp', + type: 'accelerator', + help: 'Move the crosshair up 1 pixel.' + }, + { + label: 'Move Down', + key: 'moveDown', + type: 'accelerator', + help: 'Move the crosshair down 1 pixel.' + }, + { + label: 'Move Left', + key: 'moveLeft', + type: 'accelerator', + help: 'Move the crosshair left 1 pixel.' + }, + { + label: 'Move Right', + key: 'moveRight', + type: 'accelerator', + help: 'Move the crosshair right 1 pixel.' + }, + { + label: 'About CrossOver', + key: 'about', + type: 'accelerator', + help: 'Open the "About CrossOver" window for more information.' + } + ] + } + ] + } + } + ] +} ) + +module.exports = preferences diff --git a/src/preload-settings.js b/src/preload-settings.js deleted file mode 100644 index b09a99d4..00000000 --- a/src/preload-settings.js +++ /dev/null @@ -1,38 +0,0 @@ -// Via https://github.com/reZach/secure-electron-template - -const { - contextBridge, - ipcRenderer -} = require( 'electron' ) -const { is } = require( 'electron-util' ) -const { debounce } = require( './util.js' ) - -contextBridge.exposeInMainWorld( 'crossover', { - debounce, - isMacOs: is.macos, - send: ( channel, data ) => { - - // Whitelist channels - const validChannels = new Set( [ 'save_color', 'save_opacity', 'save_size', 'save_sight', 'center_window', 'open_chooser', 'save_custom_image', 'close_settings' ] ) - - if ( validChannels.has( channel ) ) { - - ipcRenderer.send( channel, data ) - - } - - }, - - receive: ( channel, func ) => { - - const validChannels = new Set( [ 'set_color', 'set_crosshair', 'set_custom_image', 'set_opacity', 'set_size', 'set_sight' ] ) - - if ( validChannels.has( channel ) ) { - - // Deliberately strip event as it includes `sender` - ipcRenderer.on( channel, ( event, ...args ) => func( ...args ) ) - - } - - } -} ) diff --git a/src/renderer.js b/src/renderer.js index f749dd45..2c186c4e 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -3,7 +3,6 @@ ( () => { // DOM elements - // const wrapper = document.querySelector( '.crosshair-wrapper' ) const background = document.querySelector( '.background' ) const closeBtn = document.querySelector( '.close-button' ) const centerBtn = document.querySelector( '.center-button' ) diff --git a/src/settings.html b/src/settings.html deleted file mode 100644 index b1f94d28..00000000 --- a/src/settings.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - CrossOver - - - - - - - - -
- - - -
- -
- - -
-
- - - -
- - - -
- - - -
- - -
- - 100% -
- - -
- - 50% -
-

CTRL+ALT+SHIFT+X

- -
- -
- - - - - diff --git a/src/settings.js b/src/settings.js deleted file mode 100644 index 6e052e2d..00000000 --- a/src/settings.js +++ /dev/null @@ -1,298 +0,0 @@ -/* global feather, Pickr */ - -( () => { - - // DOM elements - const wrapperElement = document.querySelector( '#settings-container' ) - const opacityInput = document.querySelector( '#setting-opacity' ) - const opacityOutput = document.querySelector( '#output-opacity' ) - const selectCrosshairBtn = document.querySelector( '#select-crosshair-button' ) - const sizeInput = document.querySelector( '#setting-size' ) - const sizeOutput = document.querySelector( '#output-size' ) - const systemModifier = document.querySelector( '#system-modifier' ) - const closeBtn = document.querySelector( '.close-button' ) - - // OS Specific - if ( window.crossover.isMacOs ) { - - // Set class - document.body.classList.add( 'mac' ) - - // Set System Modifier on first load - systemModifier.textContent = 'OPTION' - - } else { - - systemModifier.textContent = 'ALT' - - } - - // Render icons - if ( feather ) { - - feather.replace() - - } - - // Create color picker - const pickr = Pickr.create( { - el: '.color-picker', - theme: 'nano', // Or 'monolith', or 'nano' - closeOnScroll: true, - position: 'right-start', - - swatches: [ - 'rgba(244, 67, 54, 1)', - 'rgba(233, 30, 99, 0.95)', - 'rgba(156, 39, 176, 0.9)', - 'rgba(103, 58, 183, 0.85)', - 'rgba(63, 81, 181, 0.8)', - 'rgba(33, 150, 243, 0.75)', - 'rgba(3, 169, 244, 0.7)', - 'rgba(0, 188, 212, 0.7)', - 'rgba(0, 150, 136, 0.75)', - 'rgba(76, 175, 80, 0.8)', - 'rgba(139, 195, 74, 0.85)', - 'rgba(205, 220, 57, 0.9)', - 'rgba(255, 235, 59, 0.95)', - 'rgba(255, 193, 7, 1)' - ], - - components: { - // Main components - preview: true, - opacity: true, - hue: true, - - // Input / output Options - interaction: { - hex: false, - rgba: false, - hsla: false, - hsva: false, - cmyk: false, - input: true, - clear: false, - save: true - } - } - } ) - window.pickr = pickr - - // Color - const stripHex = color => { - - const hex = color.toHEXA().toString() - if ( hex.length > 7 ) { - - return hex.slice( 0, 7 ) - - } - - return hex - - } - - const loadColor = color => { - - window.pickr.setColor( color ) - - } - - const setColor = color => { - - window.crossover.send( 'save_color', color ) - - } - - window.pickr - .on( 'change', color => { - - setColor( stripHex( color ) ) - - } ) - .on( 'save', color => { - - window.pickr.hide() - - setColor( stripHex( color ) ) - - } ) - .on( 'show', () => { - - document.body.classList.add( 'pickr-open' ) - - } ) - .on( 'hide', () => { - - document.body.classList.remove( 'pickr-open' ) - - } ) - - window.crossover.receive( 'set_color', arg => { - - loadColor( arg ) - - } ) - - // Opacity - const setOpacity = opacity => { - - opacityInput.value = opacity - opacityOutput.textContent = opacity - window.crossover.send( 'save_opacity', opacity ) - - } - - opacityInput.addEventListener( 'input', event => { - - setOpacity( event.target.value ) - - } ) - - window.crossover.receive( 'set_opacity', arg => { - - setOpacity( arg ) - - } ) - - // Size - const setSize = size => { - - sizeInput.value = size - sizeOutput.textContent = size - window.crossover.send( 'save_size', size ) - - } - - sizeInput.addEventListener( 'input', event => { - - setSize( event.target.value ) - - } ) - - window.crossover.receive( 'set_size', arg => { - - setSize( arg ) - - } ) - - // Sight - const setSight = sight => { - - document.querySelector( `.radio.${sight} input` ).checked = true - window.crossover.send( 'save_sight', sight ) - - } - - const sightInputs = document.querySelectorAll( '.radio' ) - for ( const element of sightInputs ) { - - element.addEventListener( 'change', event => { - - setSight( event.target.value ) - - } ) - - } - - window.crossover.receive( 'set_sight', arg => { - - setSight( arg ) - - } ) - - // Lock - window.crossover.receive( 'lock_window', arg => { - - window.pickr.hide() - if ( arg ) { - - document.body.classList.remove( 'draggable' ) - - } else { - - document.body.classList.add( 'draggable' ) - - } - - } ) - - // // Custom keybinds - // const recordShortcut = () => { - - // Mousetrap.record( sequence => { - - // // Sequence is an array like ['ctrl+k', 'c'] - // alert( 'You pressed: ' + sequence.join( ' ' ) ) - - // } ) - - // } - - // Close - closeBtn.addEventListener( 'click', () => { - - window.crossover.send( 'close_settings' ) - - } ) - - // Button to open crosshair chooser - selectCrosshairBtn.addEventListener( 'click', () => { - - // Send open request with current crosshair - window.crossover.send( 'open_chooser' ) - - } ) - - // Drag and drop Custom Image - let eventCounter = 0 // I kind of hate this but it works - document.addEventListener( 'dragenter', event => { - - event.preventDefault() - - // Highlight potential drop target when the draggable element enters it - eventCounter++ - wrapperElement.classList.add( 'dropping' ) - - }, false ) - - document.addEventListener( 'dragover', event => { - - event.preventDefault() - wrapperElement.classList.add( 'dropping' ) - - }, false ) - - document.addEventListener( 'dragleave', event => { - - event.preventDefault() - eventCounter-- - if ( eventCounter === 0 || window.crossover.isMacOs ) { - - wrapperElement.classList.remove( 'dropping' ) - - } - - } ) - - document.addEventListener( 'dragend', event => { - - event.preventDefault() - eventCounter = 0 - wrapperElement.classList.remove( 'dropping' ) - - }, false ) - - document.addEventListener( 'drop', event => { - - event.preventDefault() - eventCounter = 0 - wrapperElement.classList.remove( 'dropping' ) - - // Send file path to main - window.crossover.send( 'save_custom_image', event.dataTransfer.files[0].path ) - - }, false ) - -} )() diff --git a/src/static/meta/demo-chooser.png b/src/static/meta/demo-chooser.png new file mode 100644 index 00000000..b82d7cf7 Binary files /dev/null and b/src/static/meta/demo-chooser.png differ diff --git a/src/static/meta/demo-duplicate.png b/src/static/meta/demo-duplicate.png new file mode 100644 index 00000000..f6cbe209 Binary files /dev/null and b/src/static/meta/demo-duplicate.png differ diff --git a/src/static/meta/demo-main.png b/src/static/meta/demo-main.png new file mode 100644 index 00000000..6224a1d4 Binary files /dev/null and b/src/static/meta/demo-main.png differ diff --git a/src/static/meta/demo-settings.png b/src/static/meta/demo-settings.png new file mode 100644 index 00000000..8b153a21 Binary files /dev/null and b/src/static/meta/demo-settings.png differ diff --git a/src/static/meta/demo.png b/src/static/meta/demo.png index 65467a25..f6cbe209 100644 Binary files a/src/static/meta/demo.png and b/src/static/meta/demo.png differ diff --git a/src/static/meta/demo2.png b/src/static/meta/demo2.png deleted file mode 100644 index 093a2674..00000000 Binary files a/src/static/meta/demo2.png and /dev/null differ diff --git a/src/util.js b/src/util.js index c2fc6ed5..e2e0078c 100644 --- a/src/util.js +++ b/src/util.js @@ -22,6 +22,12 @@ const debounce = ( func, delay ) => { } +const checkboxTrue = ( value, key ) => { + + return ( typeof value === 'object' && value.includes( key ) ) + +} + /* eslint-disable no-prototype-builtins */ /** * Recursively Object.freeze() on objects and functions @@ -51,5 +57,6 @@ function deepFreeze( o ) { /* eslint-enable */ +exports.checkboxTrue = checkboxTrue exports.debounce = debounce exports.deepFreeze = deepFreeze