diff --git a/README.md b/README.md index ec4bb84..94fe263 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,81 @@ # Peek - +Please meet Peek, a web user agent application designed for using the web where, when and how you want it. -Quickly peek at your favorite web pages without breaking your flow. +** WARNING** +* Peek is not a web browser! There are no tabs, and no windows in the browser sense of them. If that's what you're looking for, there are a few decent browsers for you to choose from. +* Peek is not safe for general use yet! It is a crude proof of concept I whipped up while on vacation. While I have thoughts on security model and user interface, I have not written it up into a proper security model yet. -Peek lets you choose 10 web pages to open with a keyboard shortcut, without opening a new tab, and able to close with the `escape` key. +## Features -I use this for: +You can use Peek in three ways, with more coming: -* Translating text -* See my Github notifications -* Calendar schedule -* Check email periodically instead of having it open all the time -* Stock or cryptocurrency prices -* Slack instances I don't want loaded all the time -* Check the weather +* Peeks - Keyboard activated modal chromeless web pages mapped to `Opt+0-9` +* Slides - Gesture activated modal chromeless web pages which slide in from left/right/bottom/top +* Scripts - Scripts periodically executed against a web page in the background which extract data and notify on changes -## Usage +### Peeks -* Create up to 10 bookmarks with "peek#" plus the number 0-9 in the title. Eg "My favorite website peek#1". +Peeks are keyboard activated modal chromeless web pages mapped to `Opt+0-9` and closed on blur, the `Escape` key or `cmd/ctrl+w`. -* Use the keyboard shortcut `alt+shift` plus the number you put in the bookmark title to peek at that URL. +### Slides -* A new minimal window will open with your chosen URL loaded. +Slides are gesture activated modal chromeless web pages which slide in from left/right/bottom/top, and closed on blur, the `Escape` key or `cmd/ctrl+w`. -* Hit the `escape` key to close the window. +### Scripts -(If no bookmark is configured for an index, Peek will just load about:home.) +Scripts periodically load a web page in the background and extract data matching a CSS selector, stores it, and notify the user when the resulting data changes. -## TODO +Ok, so not really "scripts" yet. But safe and effective enough for now. -* ESC doesn't work sometimes +## Why -* test on windows/linux +Some thoughts driving the design of Peek: -* fix window to have a maximum size +* Web user agents should be bounded by the user, not browser vendor business models +* Windows and tabs should have died a long time ago, a mixed metaphor constraining the ability of the web to grow/thrive/change and meet user needs +* Security user interface must be a clear articulation of risks and trade-offs, and users should own the decisions -* add feature to long-press links to peek (in pb mode?) +# Architecture / Implementation + +Peek is designed to be modular and configurable around the idea that parts of it can run in different environments. + +For example: +* Definitely planning on a mobile app which syncs and runs your peeks/slides/scripts +* I'd like to have a decentralized compute option for running your scripts outside of your clients and syncing the data +* Want cloud storage for all config and data, esp infinite history, so can do fun things with it + +## Desktop App + +Proof of concept is Electron. By far the best option today for cross-platform desktop apps which need a web rendering engine. There's really nothing else remotely suited (yet). + +The user interface is just Tweakpane panels and modal chromeless web pages rn. + +TODO +* Need to look at whether could library-ize some of what Agregore implemented for non-HTTP protocol support. +* Min browser might be interesting as a forkable base to work from and contribute to, if they're open to it. At least, should look more at the architecture. + +## Mobile + +TBD + +## Cloud + +* Going full crypto payments for distributed compute on this one. + +## Future + +* GCLI - not just a command bar, but like the Ubiquity extension +* Lossless personal encrypted archive of web history +* Implement the Firefox "awesomebar" scoring and search algorithm so that Peek *learns* you +* Extension model designed for web user agent user interface experimentation +* Panorama + +## History + +Peek was a browser extension that let you quickly peek at your favorite web pages without breaking your flow - loading pages mapped to keyboard shortcuts into a modal window with no controls, closable via the `Escape` key. + +However, as browser extension APIs become increasingly limited, it was not possible to create a decent user experience and I abandoned it. You can access the extension in this repo [in the extension directory](/autonome/peek/extension/). + +The only way to create the ideal user experience for a web user agent that *Does What I Want* is to make it a browser-ish application, and that's what Peek is now. diff --git a/assets/appicon.icns b/assets/appicon.icns new file mode 100644 index 0000000..b4617c2 Binary files /dev/null and b/assets/appicon.icns differ diff --git a/assets/icons/AppIcon.appiconset/Contents.json b/assets/icons/AppIcon.appiconset/Contents.json new file mode 100755 index 0000000..99f7a0c --- /dev/null +++ b/assets/icons/AppIcon.appiconset/Contents.json @@ -0,0 +1,128 @@ +{ + "images":[ + { + "idiom":"iphone", + "size":"20x20", + "scale":"2x", + "filename":"Icon-App-20x20@2x.png" + }, + { + "idiom":"iphone", + "size":"20x20", + "scale":"3x", + "filename":"Icon-App-20x20@3x.png" + }, + { + "idiom":"iphone", + "size":"29x29", + "scale":"1x", + "filename":"Icon-App-29x29@1x.png" + }, + { + "idiom":"iphone", + "size":"29x29", + "scale":"2x", + "filename":"Icon-App-29x29@2x.png" + }, + { + "idiom":"iphone", + "size":"29x29", + "scale":"3x", + "filename":"Icon-App-29x29@3x.png" + }, + { + "idiom":"iphone", + "size":"40x40", + "scale":"2x", + "filename":"Icon-App-40x40@2x.png" + }, + { + "idiom":"iphone", + "size":"40x40", + "scale":"3x", + "filename":"Icon-App-40x40@3x.png" + }, + { + "idiom":"iphone", + "size":"60x60", + "scale":"2x", + "filename":"Icon-App-60x60@2x.png" + }, + { + "idiom":"iphone", + "size":"60x60", + "scale":"3x", + "filename":"Icon-App-60x60@3x.png" + }, + { + "idiom":"iphone", + "size":"76x76", + "scale":"2x", + "filename":"Icon-App-76x76@2x.png" + }, + { + "idiom":"ipad", + "size":"20x20", + "scale":"1x", + "filename":"Icon-App-20x20@1x.png" + }, + { + "idiom":"ipad", + "size":"20x20", + "scale":"2x", + "filename":"Icon-App-20x20@2x.png" + }, + { + "idiom":"ipad", + "size":"29x29", + "scale":"1x", + "filename":"Icon-App-29x29@1x.png" + }, + { + "idiom":"ipad", + "size":"29x29", + "scale":"2x", + "filename":"Icon-App-29x29@2x.png" + }, + { + "idiom":"ipad", + "size":"40x40", + "scale":"1x", + "filename":"Icon-App-40x40@1x.png" + }, + { + "idiom":"ipad", + "size":"40x40", + "scale":"2x", + "filename":"Icon-App-40x40@2x.png" + }, + { + "idiom":"ipad", + "size":"76x76", + "scale":"1x", + "filename":"Icon-App-76x76@1x.png" + }, + { + "idiom":"ipad", + "size":"76x76", + "scale":"2x", + "filename":"Icon-App-76x76@2x.png" + }, + { + "idiom":"ipad", + "size":"83.5x83.5", + "scale":"2x", + "filename":"Icon-App-83.5x83.5@2x.png" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "scale" : "1x", + "filename" : "ItunesArtwork@2x.png" + } + ], + "info":{ + "version":1, + "author":"makeappicon" + } +} diff --git a/assets/icons/AppIcon.appiconset/Icon-App-20x20@1x.png b/assets/icons/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..0e47700 Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/assets/icons/AppIcon.appiconset/Icon-App-20x20@2x.png b/assets/icons/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2f30bcc Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/assets/icons/AppIcon.appiconset/Icon-App-20x20@3x.png b/assets/icons/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..2eb3709 Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/assets/icons/AppIcon.appiconset/Icon-App-29x29@1x.png b/assets/icons/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..15c2586 Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/assets/icons/AppIcon.appiconset/Icon-App-29x29@2x.png b/assets/icons/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d46856b Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/assets/icons/AppIcon.appiconset/Icon-App-29x29@3x.png b/assets/icons/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..12e7820 Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/assets/icons/AppIcon.appiconset/Icon-App-40x40@1x.png b/assets/icons/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2f30bcc Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/assets/icons/AppIcon.appiconset/Icon-App-40x40@2x.png b/assets/icons/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..1c9d5c7 Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/assets/icons/AppIcon.appiconset/Icon-App-40x40@3x.png b/assets/icons/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..66f5977 Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/assets/icons/AppIcon.appiconset/Icon-App-60x60@2x.png b/assets/icons/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..66f5977 Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/assets/icons/AppIcon.appiconset/Icon-App-60x60@3x.png b/assets/icons/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..51a0a62 Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/assets/icons/AppIcon.appiconset/Icon-App-76x76@1x.png b/assets/icons/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..a9d99c8 Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/assets/icons/AppIcon.appiconset/Icon-App-76x76@2x.png b/assets/icons/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..531a905 Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/assets/icons/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/assets/icons/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..64d3f0c Binary files /dev/null and b/assets/icons/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/assets/icons/AppIcon.appiconset/ItunesArtwork@2x.png b/assets/icons/AppIcon.appiconset/ItunesArtwork@2x.png new file mode 100644 index 0000000..c26ec64 Binary files /dev/null and b/assets/icons/AppIcon.appiconset/ItunesArtwork@2x.png differ diff --git a/assets/icons/README.md b/assets/icons/README.md new file mode 100644 index 0000000..6b3251a --- /dev/null +++ b/assets/icons/README.md @@ -0,0 +1,24 @@ +## iTunesArtwork & iTunesArtwork@2x (App Icon) file extension: + +PNG extension is prepended to these two files - + +While Apple suggested to omit the extension for these files, +the '.png' extension is actually required for iTunesConnect submission. + +This is done for you so you don't have to. + +However, for Ad_hoc or Enterprise distirbution, the extension should be removed +from the files before adding to XCode to avoid error. + +refs: https://developer.apple.com/library/ios/qa/qa1686/_index.html + +## iTunesArtwork & iTunesArtwork@2x (App Icon) transparency handling: + +As images with alpha channels or transparencies cannot be set as an application's icon on +iTunesConnect, all transparent pixels in your images will be converted into +solid blacks. + +To achieve the best result, you're advised to adjust the transparency settings +in your source files before converting them with makeAppIcon. + +refs: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/AppIcons.html diff --git a/assets/icons/iTunesArtwork@1x.png b/assets/icons/iTunesArtwork@1x.png new file mode 100644 index 0000000..c26ec64 Binary files /dev/null and b/assets/icons/iTunesArtwork@1x.png differ diff --git a/assets/icons/iTunesArtwork@2x.png b/assets/icons/iTunesArtwork@2x.png new file mode 100644 index 0000000..c26ec64 Binary files /dev/null and b/assets/icons/iTunesArtwork@2x.png differ diff --git a/assets/icons/iTunesArtwork@3x.png b/assets/icons/iTunesArtwork@3x.png new file mode 100644 index 0000000..c26ec64 Binary files /dev/null and b/assets/icons/iTunesArtwork@3x.png differ diff --git a/config.js b/config.js new file mode 100644 index 0000000..34947b3 --- /dev/null +++ b/config.js @@ -0,0 +1,17 @@ +const Store = require('electron-store'); + +const store = new Store(); +store.clear(); + +store.set('prefs', { + globalKeyCmd: 'CommandOrControl+Escape', + peekKeyPrefix: 'Option+' +}); + +store.set('peeks', [ +]); + +store.set('scripts', [ +]); + +module.exports = store; diff --git a/content-main.js b/content-main.js new file mode 100644 index 0000000..3d1b6fa --- /dev/null +++ b/content-main.js @@ -0,0 +1,8 @@ +console.log('content-main'); + +setTimeout(() => { + const s = "selector: '.percent > span:nth-child(2)"; + const r = document.querySelector(s); + const value = r ? r.textContent : null; + console.log('cs val', value; +}, 1000); diff --git a/defaults.js b/defaults.js new file mode 100644 index 0000000..9532df8 --- /dev/null +++ b/defaults.js @@ -0,0 +1,231 @@ + +const Store = require('electron-store'); + +const prefsSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "peek.prefs.schema.json", + "title": "Peek - prefs", + "description": "Peek user preferences", + "type": "object", + "properties": { + "globalKeyCmd": { + "description": "Global OS hotkey to load app", + "type": "string", + "default": "CommandOrControl+Escape" + }, + "peekKeyPrefix": { + "description": "Global OS hotkey prefix to trigger peeks - will be followed by 0-9", + "type": "string", + "default": "Option+" + }, + }, + "required": [ "globalKeyCmd", "peekKeyPrefix"] +}; + +const peekSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "peek.peek.schema.json", + "title": "Peek - page peek", + "description": "Peek page peek", + "type": "object", + "properties": { + "keyNum": { + "description": "Number on keyboard to open this peek, 0-9", + "type": "integer", + "default": 0 + }, + "title": { + "description": "Name of the peek - user defined label", + "type": "string", + "default": "New Peek" + }, + "address": { + "description": "URL to execute script against", + "type": "string", + "default": "https://example.com" + }, + "persistState": { + "description": "Whether to persist local state or load page into empty container - defaults to false", + "type": "boolean", + "default": false + }, + "keepLive": { + "description": "Whether to keep page alive in background or load fresh when triggered - defaults to false", + "type": "boolean", + "default": false + }, + "allowSound": { + "description": "Whether to allow the page to emit sound or not (eg for background music player peeks - defaults to false", + "type": "boolean", + "default": false + }, + "height": { + "description": "User-defined height of peek page", + "type": "integer", + "default": 600 + }, + "width": { + "description": "User-defined width of peek page", + "type": "integer", + "default": 800 + }, + }, + "required": [ "keyNum", "title", "address", "persistState", "keepLive", "allowSound", + "height", "width" ] +}; + +const scriptSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "peek.script.schema.json", + "title": "Peek - script", + "description": "Peek background script", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a script", + "type": "string", + "default": "peek:script:REPLACEME" + }, + "title": { + "description": "Name of the script - user defined", + "type": "string", + "default": "New Script" + }, + "version": { + "description": "Version number of the script", + "type": "string", + "default": "1.0.0" + }, + "address": { + "description": "URL to execute script against", + "type": "string", + "default": "https://example.com" + }, + "selector": { + "description": "CSS Selector for the script", + "type": "string", + "default": "body > h1" + }, + "value": { + "description": "Which element property to return - currently 'textContent' is the only supported value", + "type": "string", + "default": "textContent" + }, + "interval": { + "description": "How often to execute the script, in milliseconds - defaults to five minutes", + "type": "integer", + "default": 300000, + "minimum": 0 + }, + "storeHistory": { + "description": "Whether to store historic values - defaults to false", + "type": "boolean", + "default": false + }, + }, + "required": [ "id", "title", "address", "version", "selector", "value", + "interval", "storeHistory" ] +}; + +const schemas = { + prefs: prefsSchema, + peek: peekSchema, + script: scriptSchema +}; + +const fullSchema = { + prefs: prefsSchema, + peek: peekSchema, + script: scriptSchema, + peeks: { + type: 'array', + items: { + type: 'peek' + } + }, + scripts: { + type: 'array', + items: { + type: 'script' + } + }, +}; + +const defaults = { + prefs: { + globalKeyCmd: 'CommandOrControl+Escape', + peekKeyPrefix: 'Option+' + }, + peeks: [ + { + keyNum: 0, + title: 'localhost', + address: 'http://localhost/', + persistState: false, + keepLive: false, + allowSound: false, + height: '', + width: '', + }, + { + keyNum: 1, + title: 'everytimezone', + address: 'https://everytimezone.com/', + persistState: false, + keepLive: false, + allowSound: false, + height: '', + width: '', + } + ], + scripts: [ + { + id: 'peek:script:localhost:test', + title: 'localhost test', + address: 'http://localhost/', + version: '1', + selector: 'body > h1', + value: 'textContent', + interval: 300000, + storehistory: false + }, + ] +}; + +const set = data => { + store.set('prefs', data.prefs); + store.set('peeks', data.peeks); + store.set('scripts', data.scripts); +}; + +const store = new Store({ + // TODO: re-enable schemas + //schema: fullSchema, + watch: true +}); + +// DEBUG +store.clear(); + +const tmp = store.get('prefs'); +if (!tmp) { + console.log('initializing datastore'); + store.set('prefs', defaults.prefs); + store.set('peeks', defaults.peeks); + store.set('scripts', defaults.scripts); +} + +module.exports = { + schemas, + data: { + get prefs() { return store.get('prefs'); }, + get peeks() { return store.get('peeks'); }, + get scripts() { return store.get('scripts'); } + }, + set, + watch: fn => { + store.onDidAnyChange(newData => { + fn(newData) + }); + } +}; diff --git a/extension/README.md b/extension/README.md new file mode 100644 index 0000000..ec4bb84 --- /dev/null +++ b/extension/README.md @@ -0,0 +1,40 @@ +# Peek + + + +Quickly peek at your favorite web pages without breaking your flow. + +Peek lets you choose 10 web pages to open with a keyboard shortcut, without opening a new tab, and able to close with the `escape` key. + +I use this for: + +* Translating text +* See my Github notifications +* Calendar schedule +* Check email periodically instead of having it open all the time +* Stock or cryptocurrency prices +* Slack instances I don't want loaded all the time +* Check the weather + +## Usage + +* Create up to 10 bookmarks with "peek#" plus the number 0-9 in the title. Eg "My favorite website peek#1". + +* Use the keyboard shortcut `alt+shift` plus the number you put in the bookmark title to peek at that URL. + +* A new minimal window will open with your chosen URL loaded. + +* Hit the `escape` key to close the window. + +(If no bookmark is configured for an index, Peek will just load about:home.) + +## TODO + +* ESC doesn't work sometimes + +* test on windows/linux + +* fix window to have a maximum size + +* add feature to long-press links to peek (in pb mode?) + diff --git a/background.js b/extension/background.js similarity index 100% rename from background.js rename to extension/background.js diff --git a/content-script.js b/extension/content-script.js similarity index 100% rename from content-script.js rename to extension/content-script.js diff --git a/icons/_head.html b/extension/icons/_head.html similarity index 100% rename from icons/_head.html rename to extension/icons/_head.html diff --git a/icons/apple-touch-icon-180x180.png b/extension/icons/apple-touch-icon-180x180.png similarity index 100% rename from icons/apple-touch-icon-180x180.png rename to extension/icons/apple-touch-icon-180x180.png diff --git a/icons/banner.png b/extension/icons/banner.png similarity index 100% rename from icons/banner.png rename to extension/icons/banner.png diff --git a/icons/browserconfig.xml b/extension/icons/browserconfig.xml similarity index 100% rename from icons/browserconfig.xml rename to extension/icons/browserconfig.xml diff --git a/icons/browserconfig/tile150x150.png b/extension/icons/browserconfig/tile150x150.png similarity index 100% rename from icons/browserconfig/tile150x150.png rename to extension/icons/browserconfig/tile150x150.png diff --git a/icons/browserconfig/tile310x150.png b/extension/icons/browserconfig/tile310x150.png similarity index 100% rename from icons/browserconfig/tile310x150.png rename to extension/icons/browserconfig/tile310x150.png diff --git a/icons/browserconfig/tile310x310.png b/extension/icons/browserconfig/tile310x310.png similarity index 100% rename from icons/browserconfig/tile310x310.png rename to extension/icons/browserconfig/tile310x310.png diff --git a/icons/browserconfig/tile70x70.png b/extension/icons/browserconfig/tile70x70.png similarity index 100% rename from icons/browserconfig/tile70x70.png rename to extension/icons/browserconfig/tile70x70.png diff --git a/icons/favicon-16x16.png b/extension/icons/favicon-16x16.png similarity index 100% rename from icons/favicon-16x16.png rename to extension/icons/favicon-16x16.png diff --git a/icons/favicon-32x32.png b/extension/icons/favicon-32x32.png similarity index 100% rename from icons/favicon-32x32.png rename to extension/icons/favicon-32x32.png diff --git a/icons/favicon.ico b/extension/icons/favicon.ico similarity index 100% rename from icons/favicon.ico rename to extension/icons/favicon.ico diff --git a/icons/peek.svg b/extension/icons/peek.svg similarity index 100% rename from icons/peek.svg rename to extension/icons/peek.svg diff --git a/icons/pwa-192x192.png b/extension/icons/pwa-192x192.png similarity index 100% rename from icons/pwa-192x192.png rename to extension/icons/pwa-192x192.png diff --git a/icons/pwa-512x512.png b/extension/icons/pwa-512x512.png similarity index 100% rename from icons/pwa-512x512.png rename to extension/icons/pwa-512x512.png diff --git a/manifest.json b/extension/manifest.json similarity index 100% rename from manifest.json rename to extension/manifest.json diff --git a/misc/peek.sketch b/extension/misc/peek.sketch similarity index 100% rename from misc/peek.sketch rename to extension/misc/peek.sketch diff --git a/forge.config.js b/forge.config.js new file mode 100644 index 0000000..9a4060f --- /dev/null +++ b/forge.config.js @@ -0,0 +1,22 @@ +module.exports = { + packagerConfig: {}, + rebuildConfig: {}, + makers: [ + { + name: '@electron-forge/maker-squirrel', + config: {}, + }, + { + name: '@electron-forge/maker-zip', + platforms: ['darwin'], + }, + { + name: '@electron-forge/maker-deb', + config: {}, + }, + { + name: '@electron-forge/maker-rpm', + config: {}, + }, + ], +}; diff --git a/index.js b/index.js new file mode 100644 index 0000000..42e0a25 --- /dev/null +++ b/index.js @@ -0,0 +1,336 @@ +// main.js +(async () => { + +console.log('main'); + +// Modules to control application life and create native browser window +const { + electron, + app, + BrowserView, + BrowserWindow, + globalShortcut, + ipcMain, + Menu, + nativeTheme, + Tray +} = require('electron'); + +const path = require('path'); + +const labels = { + app: { + title: 'Peek' + }, + tray: { + tooltip: 'Click to open Peek' + } +}; + +// keep app out of dock and tab switcher +app.dock.hide(); + +// load data +let { data, schemas, set, watch } = require('./defaults'); + +const ICON_RELATIVE_PATH = 'assets/icons/AppIcon.appiconset/Icon-App-20x20@2x.png'; +const ICON_PATH = path.join(__dirname, ICON_RELATIVE_PATH); + +const isDev = require('electron-is-dev'); + +if (isDev) { + // Enable live reload for Electron too + require('electron-reload')(__dirname, { + // Note that the path to electron may vary according to the main file + electron: require(`${__dirname}/node_modules/electron`) + }); + /* + try { + require('electron-reloader')(module); + } catch {} + */ +} + +const unhandled = require('electron-unhandled'); +unhandled(); + +// system dark mode handling +ipcMain.handle('dark-mode:toggle', () => { + if (nativeTheme.shouldUseDarkColors) { + nativeTheme.themeSource = 'light'; + } else { + nativeTheme.themeSource = 'dark'; + } + return nativeTheme.shouldUseDarkColors +}); + +ipcMain.handle('dark-mode:system', () => { + nativeTheme.themeSource = 'system'; +}); + +let _windows = []; +let _peekWins = {}; + +let _win = null; +const getMainWindow = () => { + //console.log('getMainWindow', _win === null); + if (_win === null) { + _win = createMainWindow(); + } + return _win; +}; + +const createMainWindow = () => { + // Create the browser window. + const mainWindow = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + preload: path.join(__dirname, 'preload.js') + } + }); + + // and load the index.html of the app. + mainWindow.loadFile('main.html'); + + // Open the DevTools. + mainWindow.webContents.openDevTools() + + return mainWindow; +}; + +// +app.on('activate', () => { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (BrowserWindow.getAllWindows().length === 0) { + getMainWindow().show(); + } +}); + +const initTray = () => { + const tray = new Tray(ICON_PATH); + tray.setToolTip(labels.tray.tooltip); + tray.on('click', () => { + getMainWindow().show(); + }); + return tray; +}; + +const execContentScript = (script, cb) => { + const view = new BrowserView({ + webPreferences: { + // isolate content and do not persist it + partition: Date.now() + } + }); + + //win.setBrowserView(view) + //view.setBounds({ x: 0, y: 0, width: 300, height: 300 }) + view.webContents.loadURL(script.address); + + const str = ` + const s = "${script.selector}"; + const r = document.querySelector(s); + const value = r ? r.textContent : null; + value; + `; + + view.webContents.on('dom-ready', async () => { + try { + const r = await view.webContents.executeJavaScript(str); + cb(r); + } catch(ex) { + console.error('cs exec error', ex); + cb(null); + } + }); +}; + +const initScripts = scripts => { + return; + // debounce me somehow so not shooting em all off + // at once every time app starts + scripts.forEach(script => { + const r = execContentScript(script, (res) => { + console.log('cs r', res); + }); + }); +}; + +const initGlobalShortcuts = prefs => { + // register global activation shortcut + if (!globalShortcut.isRegistered(prefs.globalKeyCmd)) { + const onActivate = () => { + getMainWindow().show(); + }; + + const ret = globalShortcut.register(prefs.globalKeyCmd, onActivate); + + if (!ret) { + console.error('Unable to register global key command.') + } + } +}; + +const showPeek = (peek) => { + const height = peek.height || 600; + const width = peek.width || 800; + + let win = null; + + const key = 'peek' + peek.keyNum; + + if (_peekWins[key]) { + console.log('peek', peek.keyNum, 'using stored window'); + win = _peekWins[key]; + win.show(); + } + else { + console.log('peek', peek.keyNum, 'creating new window'); + win = new BrowserWindow({ + height, + width, + center: true, + skipTaskbar: true, + autoHideMenuBar: true, + titleBarStyle: 'hidden', + webPreferences: { + preload: path.join(__dirname, 'peek-preload.js'), + // isolate content and do not persist it + partition: Date.now() + } + }); + } + + const onGoAway = () => { + if (peek.keepLive) { + _peekWins[key] = win; + win.hide(); + } + else { + win.destroy(); + } + } + win.on('blur', onGoAway); + win.on('close', onGoAway); + + /* + const str = ` + window.addEventListener('keyup', e => { + if (e.key == 'Escape') { + console.log('peek script esc'); + } + }); + 1; + `; + + win.webContents.on('dom-ready', async () => { + try { + const r = await win.webContents.executeJavaScript(str); + console.log(r); + } catch(ex) { + console.error('cs exec error', ex); + } + }); + */ + + //win.setBounds({ x: 0, y: 0, width, height }) + win.loadURL(peek.address); +}; + +const initPeeks = (cmdPrefix, peeks) => { + peeks.forEach((p, i) => { + if (!globalShortcut.isRegistered(cmdPrefix + `${i}`)) { + const ret = globalShortcut.register(cmdPrefix + `${i}`, () => { + showPeek(p); + }); + + if (!ret) { + console.error('Unable to register peek'); + } + } + }); +}; + +const initData = data => { + // initialize prefs + const prefs = data.prefs; + initGlobalShortcuts(prefs); + + // initialize peeks + const peeks = data.peeks; + if (peeks.length > 0) { + initPeeks(prefs.peekKeyPrefix, peeks); + } + + // initialize scripts + const scripts = data.scripts; + if (scripts.length > 0) { + initScripts(scripts); + } +}; + +const onReady = () => { + // create main app window on app start + const win = getMainWindow(); + + initData(data); + + initTray(); + + watch(newData => { + initData(newData); + getMainWindow().webContents.send('configchange', {}); + }); +}; + +app.whenReady().then(onReady); + +// when renderer is ready, send over user data +ipcMain.on('getconfig', () => { + getMainWindow().webContents.send('config', { + data, + schemas + }); +}); + +// listen for updates +ipcMain.on('setconfig', (event, newData) => { + // write to datastore + set(newData); +}); + +// ipc ESC handler +ipcMain.on('esc', (event, title) => { + console.log('esc'); + const win = getMainWindow(); + win.close(); + _win = null; + /* + if (win.isVisible()) { + console.log('win is visible, hide it'); + win.hide(); + } + */ +}); + +// Quit when all windows are closed, except on macOS. There, it's common +// for applications and their menu bar to stay active until the user quits +// explicitly with Cmd + Q. +app.on('window-all-closed', () => { + console.log('window-all-closed', process.platform); + if (process.platform !== 'darwin') { + onQuit(); + } +}); + +const onQuit = () => { + console.log('onquit'); + // Unregister all shortcuts on app close + globalShortcut.unregisterAll(); + + app.quit(); +}; + +})(); diff --git a/main.css b/main.css new file mode 100644 index 0000000..bbc2a96 --- /dev/null +++ b/main.css @@ -0,0 +1,19 @@ +body { + font-family: -apple-system, BlinkMacSystemFont, helvetica neue, helvetica, sans-serif; + font-feature-settings: "tnum"; + font-size: 12.4px; + font-variant-numeric: tabular-nums; +} + +body > div { + margin-bottom: 10px; +} + +body > div > div { + margin-bottom: 10px; +} + +h1 { + margin-top: 1px; + margin-bottom: 2px; +} diff --git a/main.html b/main.html new file mode 100644 index 0000000..5ef1575 --- /dev/null +++ b/main.html @@ -0,0 +1,30 @@ + + + + +
+ + + +