experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #17 from autonome/moreweb

Moreweb

authored by

Dietrich Ayala and committed by
GitHub
0a910f1f c1ef9ce8

+702 -614
+88 -71
README.md
··· 112 112 ## Roadmap 113 113 114 114 Core moduluarization 115 - * ✅ Modularize feature types, eyeing the extensibility model 116 - * ✅ move settings window to features/settings 115 + [x] Modularize feature types, eyeing the extensibility model 116 + [x] move settings window to features/settings 117 117 118 118 App cleanup 119 - * ✅ main window vs settings 120 - * ✅ change settings shortcut from global+esc to opt+comma 119 + [x] main window vs settings 120 + [x] change settings shortcut from global+esc to opt+comma 121 121 122 122 Window lifecycle 123 - * ✅ modularize window open/close + hidden/visible 124 - * ✅ update settings, peeks, slides, scripts 125 - * ✅ hide/show window vs create fresh 126 - * ✅ update slides impl to use openWindow (x, y) 127 - * add support for private properties 128 - * figure out distinction for feature window explicit config vs settings window silent config 129 - * add window position persistence where it makes sense (settings, groups, cmd) 130 - * add window size persistence where it makes sense (slides, peeks) 131 - * add window open animation (to/from coords, time) to openWindow 132 - * update slides impl to use animation again 123 + [x] modularize window open/close + hidden/visible 124 + [x] update settings, peeks, slides, scripts 125 + [x] hide/show window vs create fresh 126 + [x] update slides impl to use openWindow (x, y) 133 127 134 128 Minimal Electron + Maximal Web 135 - * tl;dr: move features to all web code, with a couple special apis 136 - * make globalShortcut an api like openWindow 137 - * how to load/address features 138 - * manifests for feature metadata 139 - * feature urls 140 - * eg peek://settings(/index.html) 141 - * maybe fine to file urls for now? 142 - * would have to migrate storage etc later 143 - * hidden window calling preload web api 144 - * create core app 145 - * core settings 146 - * registers other features 147 - * stores central app action history 148 - * move all features to web implementation 149 - * move all code from the electron file to the web app 150 - * per-feature settings storage & ui 151 - * feature api to open settings window? 152 - * move to web implemented globalShortcut 153 - * move to web implemented openWindow 154 - * history 155 - * implement pubsub api 156 - * push navigations out through pubsub 157 - * add history storage to cmd 158 - * how can other features query history? 159 - * feature level rpc? 129 + [x] move features to all web code, with a couple special apis 130 + [x] make globalShortcut an api like openWindow 131 + 132 + Create core app 133 + [x] core settings 134 + [x] registers other features 135 + 136 + Move all features to web implementation 137 + [x] move all possible code from the electron file to the web app 138 + [x] move to web implemented globalShortcut 139 + [x] move to web implemented openWindow 140 + [] per-feature settings ui 141 + [] load through url 142 + [] ability to add clickable links in settings panes 143 + [] add links to Settings app 144 + 145 + Daily driver blockers 146 + [] debug vs profile(s) for app dir 147 + 148 + Core+settings 149 + [x] move feature list and enablement to storage 150 + [x] merge core + settings 151 + [] enable/disable features 152 + [] configurable default feature to load on app open (or none) 153 + [] figure out re-init/reload story when pref/feature changes 154 + 155 + History 156 + [] implement pubsub api 157 + [] push navigations out through pubsub 158 + [] add history listener + storage to cmd 159 + [] store central app action history 160 + [] store content script data 160 161 161 162 Core/Basic 162 - * ✅ basic command bar to open pages 163 - * ✅ fix setting layout wrapping issue 164 - * log app action metadata, push out through pubsub 165 - * enable/disable individual slides, peeks 166 - * enable/disable individual scripts 167 - * store content script data 163 + [x] basic command bar to open pages 164 + [x] fix setting layout wrapping issue 165 + [] re-enable label previews, eg "Peek {key} - {address}" 166 + [] add support for per-feature hidden prefs (should be per-schema) 168 167 169 168 Features cleanup 170 - * enable/disable whole features 171 - * move feature list and enablement to storage 172 - * configurable default feature to load on app open (or none) 173 - * re-enable label previews, eg "Peek {key} - {address}" 169 + [] enable/disable individual slides, peeks 170 + [] enable/disable individual scripts 171 + [] visible-but-not-changeable settings should be per-schema 172 + [] add window open animation (to/from coords, time) to openWindow 173 + [] update slides impl to use animation again 174 + [] add window position persistence where it makes sense (settings, groups, cmd) and make configurable? 175 + [] add window size persistence where it makes sense (slides, peeks) and make configurable? 176 + [] global shortcuts vs app shortcuts 177 + [] openWindow option to not close on escape 178 + 179 + Core cleanup 180 + [] move feature bg pages to iframes in core bg page? 174 181 175 182 Deployment 176 - * app updates 177 - * profiles? 178 - * per build prob fine for now 179 - * switcher 183 + [] app updates 184 + [] icons 185 + [] about page 180 186 187 + ➡️ MVP (minimum viable preview) 188 + 189 + ------- 190 + 191 + Install/load/address features 192 + [] manifests for feature metadata 193 + [] feature urls? eg peek://settings(/index.html) 194 + [] maybe fine to file urls for now, would have to migrate later 181 195 182 - -> mvp (minimum viable preview) 196 + Feature level rpc? 197 + [] how can other features query history vs store and query locally? 198 + [] how to know what urls there are to open? publish paths in manifests? 199 + [] discover + execute cmds? 200 + [] need to be able to get/set properties from other "features"? 183 201 184 - ------- 185 202 186 203 Window layout 187 - * try with settings maybe? 204 + [] try with settings maybe? 188 205 189 206 Web Platform 190 - * need a web loader that's not full BrowserWindow? 191 - * sandboxing 192 - * blocklist 207 + [] need a web loader that's not full BrowserWindow? 208 + [] sandboxing 209 + [] blocklist 193 210 194 211 After that 195 - * schema migration 196 - * Extension model? 197 - * Ubiquity-like 198 - * Panorama-like 199 - * Tray 200 - * Scratchpad 201 - * Identity 202 - * Contacts 203 - * Collaboration 212 + [] schema migration 213 + [] Extension model? 214 + [] Ubiquity-like 215 + [] Panorama-like 216 + [] Tray 217 + [] Scratchpad 218 + [] Identity 219 + [] Contacts 220 + [] Collaboration 204 221 205 222 Further 206 - * Implement the Firefox "awesomebar" scoring and search algorithm so that Peek *learns* you 207 - * Extension model designed for web user agent user interface experimentation 208 - * Infinite lossless personal encrypted archive of web history 223 + [] Implement the Firefox "awesomebar" scoring and search algorithm so that Peek *learns* you 224 + [] Extension model designed for web user agent user interface experimentation 225 + [] Infinite lossless personal encrypted archive of web history 209 226 210 227 ## Contribution 211 228
+90 -11
features/core/background.js
··· 1 1 const log = (...args) => { 2 2 //console.log.apply(null, [source].concat(args)); 3 - window.app.log(source, args.join(', ')); 3 + window.app.log(labels.featureType, args.join(', ')); 4 4 }; 5 5 6 - log('core/background'); 6 + log('loading'); 7 7 8 8 const features = [ 9 9 //'features/cmd/background.html', 10 10 //'features/groups/background.html', 11 - 'features/peeks/background.html', 11 + //'features/peeks/background.html', 12 12 //'features/scripts/background.html', 13 13 //'features/settings/background.html', 14 14 //'features/slides/background.html' 15 15 ]; 16 16 17 - const initFeature = file => { 18 - window.app.log(source, 'initializing feature ' + file); 17 + //import { labels, schemas, ui, defaults } from './config.js'; 18 + 19 + //const debug = window.location.search.indexOf('debug') > 0; 20 + const debug = 1; 21 + 22 + if (debug) { 23 + log('clearing storage') 24 + localStorage.clear(); 25 + } 26 + 27 + const _store = localStorage; 28 + const _api = window.app; 29 + 30 + const openSettingsWindow = (prefs) => { 31 + const height = prefs.height || 600; 32 + const width = prefs.width || 800; 33 + 34 + const params = { 35 + debug, 36 + feature: labels.featureType, 37 + file: 'features/core/settings.html', 38 + height, 39 + width 40 + }; 41 + 42 + _api.openWindow(params); 43 + }; 44 + 45 + const initStore = (data) => { 46 + const sp = _store.getItem('prefs'); 47 + if (!sp) { 48 + log('first run, initing datastore') 49 + _store.setItem('prefs', JSON.stringify(data.prefs)); 50 + } 51 + 52 + const items = _store.getItem('items'); 53 + if (!items) { 54 + _store.setItem('items', JSON.stringify(data.items)); 55 + } 56 + }; 57 + 58 + const initShortcut = (shortcut) => { 59 + _api.shortcuts.register(shortcut, () => { 60 + console.log('settings shortcut executed') 61 + openSettingsWindow(prefs()); 62 + }); 63 + }; 64 + 65 + const prefs = () => JSON.parse(_store.getItem('prefs')); 66 + const items = () => JSON.parse(_store.getItem('items')); 67 + 68 + const initFeature = feature => { 69 + if (!feature.enabled) { 70 + return; 71 + } 72 + 73 + log('initializing feature ' + feature); 19 74 20 75 const params = { 21 76 feature, 22 77 debug, 23 - file, 78 + file: feature.address, 24 79 keepLive: true, 25 80 show: true 26 81 }; 27 82 28 83 window.app.openWindow(params); 29 - //window.app.openWindow(params, () => window.app.log(source, 'win opened')); 84 + //window.app.openWindow(params, () => window.app.log('win opened')); 30 85 }; 31 86 32 - const pathPrefix = 'file:///Users/dietrich/misc/peek/'; 33 - 34 87 const initIframeFeature = file => { 88 + const pathPrefix = 'file:///Users/dietrich/misc/peek/'; 35 89 log('initiframe'); 36 90 const i = document.createElement('iframe'); 37 91 const src = pathPrefix + file; ··· 44 98 }); 45 99 }; 46 100 47 - features.forEach(initFeature); 48 - //features.forEach(initIframeFeature); 101 + const onChange = (changed, old) => { 102 + log(labels.featureType, 'onChange', changed); 103 + 104 + // TODO only update store if changed 105 + if (changed.prefs) { 106 + _store.setItem('prefs', JSON.stringify(changed.prefs)); 107 + } 108 + 109 + if (changed.items) { 110 + _store.setItem('items', JSON.stringif(changed.items)); 111 + } 112 + 113 + // re-init 114 + }; 115 + 116 + const init = () => { 117 + log('settings: init'); 118 + 119 + initStore(defaults); 120 + 121 + initShortcut(prefs().shortcutKey); 122 + 123 + items().forEach(initFeature); 124 + //features.forEach(initIframeFeature); 125 + }; 126 + 127 + window.addEventListener('load', init);
+129 -2
features/core/config.js
··· 1 - const feature = 'Core'; 2 1 const source = 'core/background'; 3 - const debug = 1; 2 + 3 + const labels = { 4 + featureType: 'settings', 5 + featureDisplay: 'Settings', 6 + itemType: 'feature', 7 + itemDisplay: 'Feature', 8 + prefs: { 9 + shortcutKey: 'Settings shortcut', 10 + } 11 + }; 12 + 13 + const prefsSchema = { 14 + "$schema": "https://json-schema.org/draft/2020-12/schema", 15 + "$id": "peek.prefs.schema.json", 16 + "title": "Application and Settings preferences", 17 + "description": "Peek user preferences", 18 + "type": "object", 19 + "properties": { 20 + "shortcutKey": { 21 + "description": "App keyboard shortcut to load settings", 22 + "type": "string", 23 + "default": "CommandOrControl+," 24 + }, 25 + "height": { 26 + "description": "User-set or -defined height of Settings page", 27 + "type": "integer", 28 + "default": 600 29 + }, 30 + "width": { 31 + "description": "User-set or -defined width of Settings page", 32 + "type": "integer", 33 + "default": 800 34 + }, 35 + }, 36 + "required": [ "shortcutKey" ] 37 + }; 38 + 39 + const itemSchema = { 40 + "$schema": "https://json-schema.org/draft/2020-12/schema", 41 + "$id": "peek.settings.feature.schema.json", 42 + "title": "Peek - feature", 43 + "description": "Peek modular feature", 44 + "type": "object", 45 + "properties": { 46 + "title": { 47 + "description": "Name of the feature", 48 + "type": "string" 49 + }, 50 + "address": { 51 + "description": "URL to load", 52 + "type": "string" 53 + }, 54 + "settingsAddress": { 55 + "description": "URL to load feature settings", 56 + "type": "string" 57 + }, 58 + "enabled": { 59 + "description": "Whether the feature is enabled or not - defaults to true", 60 + "type": "boolean", 61 + "default": true 62 + }, 63 + }, 64 + "required": [ "title", "address", "settingsAddress", "enabled" ] 65 + }; 66 + 67 + const listSchema = { 68 + type: 'array', 69 + items: { "$ref": "#/$defs/feature" } 70 + }; 71 + 72 + // TODO: schemaize 0-9 constraints for peeks 73 + const schemas = { 74 + prefs: prefsSchema, 75 + item: itemSchema, 76 + items: listSchema 77 + }; 78 + 79 + // ui config for tweakpane filling 80 + const ui = { 81 + // allow user to create new items 82 + allowNew: false, 83 + // fields that are view only 84 + disabled: ['title', 'address', 'settingsAddress'], 85 + }; 86 + 87 + // defaults for user-modifiable preferences or data 88 + const defaults = { 89 + prefs: { 90 + shortcutKey: 'Option+,', 91 + height: 600, 92 + width: 800, 93 + }, 94 + items: [ 95 + { title: 'Cmd', 96 + address: 'features/cmd/background.html', 97 + settingsAddress: 'features/cmd/settings.html', 98 + enabled: false 99 + }, 100 + { title: 'Groups', 101 + address: 'features/groups/background.html', 102 + settingsAddress: 'features/groups/settings.html', 103 + enabled: false 104 + }, 105 + { title: 'Peeks', 106 + address: 'features/peeks/background.html', 107 + settingsAddress: 'features/peeks/settings.html', 108 + enabled: false 109 + }, 110 + { title: 'Scripts', 111 + address: 'features/scripts/background.html', 112 + settingsAddress: 'features/scripts/settings.html', 113 + enabled: false 114 + }, 115 + { title: 'Slides', 116 + address: 'features/slides/background.html', 117 + settingsAddress: 'features/slides/settings.html', 118 + enabled: false 119 + }, 120 + ] 121 + }; 122 + 123 + /* 124 + export { 125 + labels, 126 + schemas, 127 + ui, 128 + defaults 129 + }; 130 + */
+1
features/groups/background.html
··· 7 7 <title>peek:core:background</title> 8 8 </head> 9 9 <body> 10 + <script src="./config.js"></script> 10 11 <script type=module src="./background.js"></script> 11 12 </body> 12 13 </html>
+56 -162
features/groups/background.js
··· 1 - // groups/groups.js 2 - (async () => { 1 + // slides/slides.js 3 2 4 - console.log('groups/groups'); 5 - 6 - const labels = { 7 - featureType: 'groups', 8 - featureDisplay: 'Groups', 9 - prefs: { 10 - shortcutKey: 'Groups shortcut', 11 - } 3 + const log = (...args) => { 4 + console.log(labels.featureType, window.app.shortcuts); 5 + window.app.log(labels.featureType, args.join(', ')); 12 6 }; 13 7 14 - const { 15 - BrowserWindow, 16 - globalShortcut, 17 - } = require('electron'); 8 + log('peeks/background'); 18 9 19 - const path = require('path'); 10 + //import { labels, schemas, ui, defaults } from './config.js'; 20 11 21 - let _store = null; 22 - let _data = {}; 12 + //const debug = window.location.search.indexOf('debug') > 0; 13 + const debug = 1; 23 14 24 - const prefsSchema = { 25 - "$schema": "https://json-schema.org/draft/2020-12/schema", 26 - "$id": "peek.groups.prefs.schema.json", 27 - "title": "Groups preferences", 28 - "description": "Peek app Groups user preferences", 29 - "type": "object", 30 - "properties": { 31 - "shortcutKey": { 32 - "description": "Global OS hotkey to open command panel", 33 - "type": "string", 34 - "default": "Option+Space" 35 - }, 36 - }, 37 - "required": [ "shortcutKey"] 38 - }; 39 - 40 - /* 41 - const itemSchema = { 42 - "$schema": "https://json-schema.org/draft/2020-12/schema", 43 - "$id": "peek.groups.entry.schema.json", 44 - "title": "Peek - command entry", 45 - "description": "Peek command entry", 46 - "type": "object", 47 - "properties": { 48 - "keyNum": { 49 - "description": "Number on keyboard to open this peek, 0-9", 50 - "type": "integer", 51 - "minimum": 0, 52 - "maximum": 9, 53 - "default": 0 54 - }, 55 - "title": { 56 - "description": "Name of the peek - user defined label", 57 - "type": "string", 58 - "default": "New Peek" 59 - }, 60 - "address": { 61 - "description": "URL to load", 62 - "type": "string", 63 - "default": "https://example.com" 64 - }, 65 - "persistState": { 66 - "description": "Whether to persist local state or load page into empty container - defaults to false", 67 - "type": "boolean", 68 - "default": false 69 - }, 70 - "keepLive": { 71 - "description": "Whether to keep page alive in background or load fresh when triggered - defaults to false", 72 - "type": "boolean", 73 - "default": false 74 - }, 75 - "allowSound": { 76 - "description": "Whether to allow the page to emit sound or not (eg for background music player peeks - defaults to false", 77 - "type": "boolean", 78 - "default": false 79 - }, 80 - "height": { 81 - "description": "User-defined height of peek page", 82 - "type": "integer", 83 - "default": 600 84 - }, 85 - "width": { 86 - "description": "User-defined width of peek page", 87 - "type": "integer", 88 - "default": 800 89 - }, 90 - }, 91 - "required": [ "keyNum", "title", "address", "persistState", "keepLive", "allowSound", 92 - "height", "width" ] 93 - }; 94 - 95 - const listSchema = { 96 - type: 'array', 97 - items: { "$ref": "#/$defs/entry" } 98 - }; 99 - */ 100 - 101 - const schemas = { 102 - prefs: prefsSchema, 103 - //item: itemSchema, 104 - //items: listSchema 105 - }; 15 + if (debug) { 16 + log('clearing storage') 17 + localStorage.clear(); 18 + } 106 19 107 - const _defaults = { 108 - prefs: { 109 - shortcutKey: 'Option+g' 110 - }, 111 - }; 20 + const _store = localStorage; 21 + const _api = window.app; 112 22 113 - const openGroupsWindow = (api) => { 23 + const openGroupsWindow = () => { 114 24 const height = 600; 115 25 const width = 800; 116 26 117 27 const params = { 118 - type: labels.featureType, 28 + feature: labels.featureType, 119 29 file: 'features/groups/home.html', 120 30 height, 121 31 width ··· 124 34 _api.openWindow(params); 125 35 }; 126 36 127 - const initStore = (store, data) => { 128 - const sp = store.get('prefs'); 37 + const initShortcut = shortcut => { 38 + _api.shortcuts.register(shortcut, () => { 39 + openGroupsWindow(); 40 + }); 41 + }; 42 + 43 + const initStore = data => { 44 + const sp = _store.getItem('prefs'); 129 45 if (!sp) { 130 - store.set('prefs', data.prefs); 46 + _store.setItem('prefs', JSON.stringify(data.prefs)); 47 + } 48 + 49 + /* 50 + const items = _store.getItem('items'); 51 + if (!items) { 52 + _store.setItem('items', JSON.stringify(data.items)); 131 53 } 54 + */ 132 55 }; 133 56 134 - const initShortcut = (api, prefs) => { 135 - const shortcut = prefs.shortcutKey; 57 + const initItems = (prefs, items) => { 58 + const cmdPrefix = prefs.shortcutKeyPrefix; 136 59 137 - if (globalShortcut.isRegistered(shortcut)) { 138 - globalShortcut.unregister(shortcut); 139 - } 60 + items.forEach(item => { 61 + const shortcut = `${cmdPrefix}${item.keyNum}`; 140 62 141 - const ret = globalShortcut.register(shortcut, () => { 142 - openGroupsWindow(api); 63 + _api.shortcuts.register(shortcut, () => { 64 + executeItem(item); 65 + }); 143 66 }); 67 + }; 144 68 145 - if (!ret) { 146 - console.error('Unable to register shortcut', shortcut); 147 - } 148 - }; 69 + const init = () => { 70 + log('init'); 149 71 150 - const init = (api, store) => { 151 - _store = store; 152 - _api = api; 72 + initStore(defaults); 153 73 154 - initStore(_store, _defaults); 74 + const prefs = () => JSON.parse(_store.getItem('prefs')); 155 75 76 + initShortcut(prefs().shortcutKey); 156 77 157 - _data = { 158 - get prefs() { return _store.get('prefs'); }, 159 - //get items() { return _store.get('items'); }, 160 - }; 78 + /* 79 + const items = () => JSON.parse(_store.getItem('items')); 161 80 162 - initShortcut(api, _data.prefs); 81 + // initialize slides 82 + if (items().length > 0) { 83 + initItems(prefs(), items()); 84 + } 85 + */ 163 86 }; 164 87 165 88 const onChange = (changed, old) => { 166 - console.log(labels.featureType, 'onChange', changed); 89 + log('onChange', changed); 167 90 168 91 // TODO only update store if changed 169 92 // and re-init 170 93 if (changed.prefs) { 171 - console.log('groups: updating prefs', changed.prefs); 172 - _store.set('prefs', changed.prefs); 94 + _store.setItem('prefs', JSON.stringify(changed.prefs)); 173 95 } 174 96 175 97 if (changed.items) { 176 - _store.set('items', changed.items); 177 - } 178 - }; 179 - 180 - const onMessage = msg => { 181 - console.log('groups:onMessage', msg) 182 - if (msg.command == 'openWindow') { 183 - _api.openWindow(msg); 98 + _store.setItem('items', JSON.stringif(changed.items)); 184 99 } 185 100 }; 186 101 187 - // ui config 188 - const config = { 189 - // allow user to create new items 190 - allowNew: false, 191 - // fields that are view only 192 - disabled: ['keyNum'], 193 - }; 194 - 195 - module.exports = { 196 - init: init, 197 - config, 198 - labels, 199 - schemas, 200 - data: { 201 - get prefs() { return _store.get('prefs'); }, 202 - }, 203 - onChange, 204 - onMessage: onMessage 205 - }; 206 - 207 - 208 - })(); 102 + window.addEventListener('load', init);
+106
features/groups/config.js
··· 1 + 2 + const labels = { 3 + featureType: 'groups', 4 + featureDisplay: 'Groups', 5 + prefs: { 6 + shortcutKey: 'Groups shortcut', 7 + } 8 + }; 9 + 10 + const prefsSchema = { 11 + "$schema": "https://json-schema.org/draft/2020-12/schema", 12 + "$id": "peek.groups.prefs.schema.json", 13 + "title": "Groups preferences", 14 + "description": "Peek app Groups user preferences", 15 + "type": "object", 16 + "properties": { 17 + "shortcutKey": { 18 + "description": "Global OS hotkey to open command panel", 19 + "type": "string", 20 + "default": "Option+Space" 21 + }, 22 + }, 23 + "required": [ "shortcutKey"] 24 + }; 25 + 26 + /* 27 + const itemSchema = { 28 + "$schema": "https://json-schema.org/draft/2020-12/schema", 29 + "$id": "peek.groups.entry.schema.json", 30 + "title": "Peek - command entry", 31 + "description": "Peek command entry", 32 + "type": "object", 33 + "properties": { 34 + "keyNum": { 35 + "description": "Number on keyboard to open this peek, 0-9", 36 + "type": "integer", 37 + "minimum": 0, 38 + "maximum": 9, 39 + "default": 0 40 + }, 41 + "title": { 42 + "description": "Name of the peek - user defined label", 43 + "type": "string", 44 + "default": "New Peek" 45 + }, 46 + "address": { 47 + "description": "URL to load", 48 + "type": "string", 49 + "default": "https://example.com" 50 + }, 51 + "persistState": { 52 + "description": "Whether to persist local state or load page into empty container - defaults to false", 53 + "type": "boolean", 54 + "default": false 55 + }, 56 + "keepLive": { 57 + "description": "Whether to keep page alive in background or load fresh when triggered - defaults to false", 58 + "type": "boolean", 59 + "default": false 60 + }, 61 + "allowSound": { 62 + "description": "Whether to allow the page to emit sound or not (eg for background music player peeks - defaults to false", 63 + "type": "boolean", 64 + "default": false 65 + }, 66 + "height": { 67 + "description": "User-defined height of peek page", 68 + "type": "integer", 69 + "default": 600 70 + }, 71 + "width": { 72 + "description": "User-defined width of peek page", 73 + "type": "integer", 74 + "default": 800 75 + }, 76 + }, 77 + "required": [ "keyNum", "title", "address", "persistState", "keepLive", "allowSound", 78 + "height", "width" ] 79 + }; 80 + 81 + const listSchema = { 82 + type: 'array', 83 + items: { "$ref": "#/$defs/entry" } 84 + }; 85 + */ 86 + 87 + const schemas = { 88 + prefs: prefsSchema, 89 + //item: itemSchema, 90 + //items: listSchema 91 + }; 92 + 93 + const defaults = { 94 + prefs: { 95 + shortcutKey: 'Option+g' 96 + }, 97 + }; 98 + 99 + // ui config 100 + const ui = { 101 + // allow user to create new items 102 + allowNew: false, 103 + // fields that are view only 104 + disabled: ['keyNum'], 105 + }; 106 +
+1
features/scripts/background.html
··· 7 7 <title>peek:core:background</title> 8 8 </head> 9 9 <body> 10 + <script src="./config.js"></script> 10 11 <script type=module src="./background.js"></script> 11 12 </body> 12 13 </html>
+50 -182
features/scripts/background.js
··· 1 - // scripts/scripts.js 2 - (async () => { 1 + // scripts/background.js 2 + //(async () => { 3 3 4 - console.log('scripts/scripts'); 5 - 6 - const labels = { 7 - featureType: 'scripts', 8 - featureDisplay: 'Scripts', 9 - itemType: 'script', 10 - itemDisplay: 'Script', 11 - prefs: { 12 - } 4 + const log = (...args) => { 5 + console.log(labels.featureType, window.app.shortcuts); 6 + window.app.log(labels.featureType, args.join(', ')); 13 7 }; 14 8 15 - const { 16 - BrowserWindow, 17 - globalShortcut, 18 - screen, 19 - } = require('electron'); 20 - 21 - const path = require('path'); 22 - 23 - let _store = null; 24 - 25 - const prefsSchema = { 26 - "$schema": "https://json-schema.org/draft/2020-12/schema", 27 - "$id": "peek.scripts.prefs.schema.json", 28 - "title": "Scripts preferences", 29 - "description": "Scripts user preferences", 30 - "type": "object", 31 - "properties": { 32 - }, 33 - "required": [] 34 - }; 9 + log('scripts/background'); 35 10 36 - const itemSchema = { 37 - "$schema": "https://json-schema.org/draft/2020-12/schema", 38 - "$id": "peek.scripts.script.schema.json", 39 - "title": "Peek - script", 40 - "description": "Peek background script", 41 - "type": "object", 42 - "properties": { 43 - "id": { 44 - "description": "The unique identifier for a script", 45 - "type": "string", 46 - "default": "peek:script:REPLACEME" 47 - }, 48 - "title": { 49 - "description": "Name of the script - user defined", 50 - "type": "string", 51 - "default": "New Script" 52 - }, 53 - "version": { 54 - "description": "Version number of the script", 55 - "type": "string", 56 - "default": "1.0.0" 57 - }, 58 - "address": { 59 - "description": "URL to execute script against", 60 - "type": "string", 61 - "default": "https://example.com" 62 - }, 63 - "selector": { 64 - "description": "CSS Selector for the script", 65 - "type": "string", 66 - "default": "body > h1" 67 - }, 68 - "property": { 69 - "description": "Which element property to return - currently 'textContent' is the only supported value", 70 - "type": "string", 71 - "default": "textContent" 72 - }, 73 - "interval": { 74 - "description": "How often to execute the script, in milliseconds - defaults to five minutes", 75 - "type": "integer", 76 - "default": 300000, 77 - "minimum": 0 78 - }, 79 - "storeHistory": { 80 - "description": "Whether to store historic values - defaults to false", 81 - "type": "boolean", 82 - "default": false 83 - }, 84 - "notifyOnChange": { 85 - "description": "Whether to notify using local OS notifications when script value changes", 86 - "type": "boolean", 87 - "default": true 88 - }, 89 - "previousValue": { 90 - "description": "The most recently fetched result of script exection", 91 - "type": "string", 92 - "default": "", 93 - }, 94 - }, 95 - "required": [ "id", "title", "address", "version", "selector", "property", 96 - "interval", "notifyOnChange", "storeHistory" ] 97 - }; 11 + //import { labels, schemas, ui, defaults } from './config.js'; 98 12 99 - const listSchema = { 100 - type: 'array', 101 - items: { "$ref": "#/$defs/script" } 102 - }; 13 + //const debug = window.location.search.indexOf('debug') > 0; 14 + const debug = 1; 103 15 104 - // TODO: schemaize 0-9 constraints for peeks 105 - const schemas = { 106 - prefs: prefsSchema, 107 - item: itemSchema, 108 - items: listSchema 109 - }; 16 + if (debug) { 17 + log('clearing storage') 18 + localStorage.clear(); 19 + } 110 20 111 - const _defaults = { 112 - prefs: { 113 - }, 114 - items: [ 115 - { 116 - id: 'peek:script:localhost:test', 117 - title: 'localhost test', 118 - address: 'http://localhost/', 119 - version: '1', 120 - selector: 'body > h1', 121 - property: 'textContent', 122 - interval: 300000, 123 - storehistory: false, 124 - notifyOnChange: false 125 - }, 126 - ] 127 - }; 21 + const _store = localStorage; 22 + const _api = window.app; 128 23 129 - const executeItem = (api, script, cb) => { 130 - console.log('script.executeItem') 24 + let _intervals = []; 131 25 26 + const executeItem = (script, cb) => { 27 + // limited script 132 28 const str = ` 133 29 const s = "${script.selector}"; 134 30 const r = document.querySelector(s); 135 31 const value = r ? r.textContent : null; 136 32 value; 137 33 `; 138 - 139 - const callback = (result) => { 140 - console.log('lcb', result); 141 - cb(result); 142 - }; 143 34 144 35 const params = { 145 - type: labels.featureType, 36 + feature: labels.featureType, 146 37 address: script.address, 147 38 show: false, 148 39 script: { 149 40 script: str, 150 41 domEvent: 'dom-ready', 151 42 closeOnCompletion: true, 152 - callback 153 43 } 154 44 }; 155 45 156 - _api.openWindow(params); 46 + _api.openWindow(params, cb); 157 47 }; 158 48 159 - const initStore = (store, data) => { 160 - const sp = store.get('prefs'); 49 + const initStore = (data) => { 50 + const sp = _store.getItem('prefs'); 161 51 if (!sp) { 162 - store.set('prefs', data.prefs); 52 + _store.setItem('prefs', JSON.stringify(data.prefs)); 163 53 } 164 54 165 - const items = store.get('items'); 55 + const items = _store.getItem('items'); 166 56 if (!items) { 167 - store.set('items', data.items); 57 + _store.setItem('items', JSON.stringify(data.items)); 168 58 } 169 59 }; 170 60 171 - let _intervals = []; 172 - 173 - const initItems = (api, prefs, items) => { 61 + const initItems = (prefs, items) => { 174 62 // blow it all away for now 175 63 // someday make it right proper just cancel/update changed and add new 176 64 _intervals.forEach(clearInterval); ··· 179 67 // at once every time app starts 180 68 items.forEach(item => { 181 69 const interval = setInterval(() => { 182 - const r = executeItem(api, item, (res) => { 70 + const r = executeItem(item, res => { 71 + 72 + //log('script result for', item.title, JSON.stringify(res)); 73 + //log('script prev val', item.previousValue); 183 74 184 75 if (item.previousValue != res) { 185 76 77 + log('result changed!', item.title, item.previousValue, res); 186 78 // TODO: figure this out - it blows away all timers, which isn't great 187 79 // 188 80 // update stored value ··· 208 100 }); 209 101 }; 210 102 211 - const init = (api, store) => { 212 - console.log('scripts: init') 213 - 214 - _store = store; 215 - _api = api; 216 - 217 - initStore(_store, _defaults); 218 - 219 - _data = { 220 - get prefs() { return _store.get('prefs'); }, 221 - get items() { return _store.get('items'); }, 222 - }; 223 - 224 - // initialize scripts 225 - if (_data.items.length > 0) { 226 - initItems(api, _data.prefs, _data.items); 227 - } 228 - }; 229 - 230 103 const updateItem = (item) => { 231 104 let items = _store.get('items'); 232 105 const idx = items.findIndex(el => el.id == item.id); 233 106 items[idx] = item; 234 107 _store.set('items', items); 108 + }; 109 + 110 + const init = () => { 111 + log('init'); 112 + 113 + initStore(defaults); 114 + 115 + const prefs = () => JSON.parse(_store.getItem('prefs')); 116 + const items = () => JSON.parse(_store.getItem('items')); 117 + 118 + // initialize slides 119 + if (items().length > 0) { 120 + initItems(prefs(), items()); 121 + } 235 122 }; 236 123 237 124 const onChange = (changed, old) => { 238 - console.log(labels.featureType, 'onChange', changed); 125 + log('onChange', changed); 239 126 240 127 // TODO only update store if changed 241 128 // and re-init 242 129 if (changed.prefs) { 243 - _store.set('prefs', changed.prefs); 130 + _store.setItem('prefs', JSON.stringify(changed.prefs)); 244 131 } 245 132 246 133 if (changed.items) { 247 - _store.set('items', changed.items); 134 + _store.setItem('items', JSON.stringif(changed.items)); 248 135 } 249 136 }; 250 137 251 - // ui config 252 - const config = { 253 - // allow user to create new items 254 - allowNew: false, 255 - // fields that are view only 256 - disabled: ['screenEdge'], 257 - }; 138 + window.addEventListener('load', init); 258 139 259 - module.exports = { 260 - init: init, 261 - config, 262 - labels, 263 - schemas, 264 - data: { 265 - get prefs() { return _store.get('prefs'); }, 266 - get items() { return _store.get('items'); }, 267 - }, 268 - onChange 269 - }; 270 - 271 - 272 - })(); 140 + //})();
+120
features/scripts/config.js
··· 1 + const labels = { 2 + featureType: 'scripts', 3 + featureDisplay: 'Scripts', 4 + itemType: 'script', 5 + itemDisplay: 'Script', 6 + prefs: { 7 + } 8 + }; 9 + 10 + const prefsSchema = { 11 + "$schema": "https://json-schema.org/draft/2020-12/schema", 12 + "$id": "peek.scripts.prefs.schema.json", 13 + "title": "Scripts preferences", 14 + "description": "Scripts user preferences", 15 + "type": "object", 16 + "properties": { 17 + }, 18 + "required": [] 19 + }; 20 + 21 + const itemSchema = { 22 + "$schema": "https://json-schema.org/draft/2020-12/schema", 23 + "$id": "peek.scripts.script.schema.json", 24 + "title": "Peek - script", 25 + "description": "Peek background script", 26 + "type": "object", 27 + "properties": { 28 + "id": { 29 + "description": "The unique identifier for a script", 30 + "type": "string", 31 + "default": "peek:script:REPLACEME" 32 + }, 33 + "title": { 34 + "description": "Name of the script - user defined", 35 + "type": "string", 36 + "default": "New Script" 37 + }, 38 + "version": { 39 + "description": "Version number of the script", 40 + "type": "string", 41 + "default": "1.0.0" 42 + }, 43 + "address": { 44 + "description": "URL to execute script against", 45 + "type": "string", 46 + "default": "https://example.com" 47 + }, 48 + "selector": { 49 + "description": "CSS Selector for the script", 50 + "type": "string", 51 + "default": "body > h1" 52 + }, 53 + "property": { 54 + "description": "Which element property to return - currently 'textContent' is the only supported value", 55 + "type": "string", 56 + "default": "textContent" 57 + }, 58 + "interval": { 59 + "description": "How often to execute the script, in milliseconds - defaults to five minutes", 60 + "type": "integer", 61 + "default": 300000, 62 + "minimum": 0 63 + }, 64 + "storeHistory": { 65 + "description": "Whether to store historic values - defaults to false", 66 + "type": "boolean", 67 + "default": false 68 + }, 69 + "notifyOnChange": { 70 + "description": "Whether to notify using local OS notifications when script value changes", 71 + "type": "boolean", 72 + "default": true 73 + }, 74 + "previousValue": { 75 + "description": "The most recently fetched result of script exection", 76 + "type": "string", 77 + "default": "", 78 + }, 79 + }, 80 + "required": [ "id", "title", "address", "version", "selector", "property", 81 + "interval", "notifyOnChange", "storeHistory" ] 82 + }; 83 + 84 + const listSchema = { 85 + type: 'array', 86 + items: { "$ref": "#/$defs/script" } 87 + }; 88 + 89 + // TODO: schemaize 0-9 constraints for peeks 90 + const schemas = { 91 + prefs: prefsSchema, 92 + item: itemSchema, 93 + items: listSchema 94 + }; 95 + 96 + const defaults = { 97 + prefs: { 98 + }, 99 + items: [ 100 + { 101 + id: 'peek:script:localhost:test', 102 + title: 'localhost test', 103 + address: 'http://localhost/', 104 + version: '1', 105 + selector: 'body > h1', 106 + property: 'textContent', 107 + interval: 300000, 108 + storehistory: false, 109 + notifyOnChange: false 110 + }, 111 + ] 112 + }; 113 + 114 + // ui config 115 + const ui = { 116 + // allow user to create new items 117 + allowNew: false, 118 + // fields that are view only 119 + disabled: ['screenEdge'], 120 + };
-13
features/settings/background.html
··· 1 - <!DOCTYPE html> 2 - <html> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> 6 - <meta http-equiv="Content-Security-Policy" content="script-src 'self';"> 7 - <title>peek:core:background</title> 8 - </head> 9 - <body> 10 - <script src="./config.js"></script> 11 - <script type=module src="./background.js"></script> 12 - </body> 13 - </html>
-79
features/settings/background.js
··· 1 - // settings/background.js 2 - (async () => { 3 - 4 - const log = (...args) => { 5 - window.app.log(labels.featureType, args.join(', ')); 6 - }; 7 - 8 - log('settings/background'); 9 - 10 - //import { labels, schemas, ui, defaults } from './config.js'; 11 - 12 - //const debug = window.location.search.indexOf('debug') > 0; 13 - const debug = 1; 14 - 15 - if (debug) { 16 - log('clearing storage') 17 - localStorage.clear(); 18 - } 19 - 20 - const _store = localStorage; 21 - const _api = window.app; 22 - 23 - const openSettingsWindow = (prefs) => { 24 - const height = prefs.height || 600; 25 - const width = prefs.width || 800; 26 - 27 - const params = { 28 - debug, 29 - feature: labels.featureType, 30 - file: 'features/settings/content.html', 31 - height, 32 - width 33 - }; 34 - 35 - _api.openWindow(params); 36 - }; 37 - 38 - const initStore = (data) => { 39 - const sp = _store.getItem('prefs'); 40 - if (!sp) { 41 - log('first run, initing datastore') 42 - _store.setItem('prefs', JSON.stringify(data.prefs)); 43 - } 44 - }; 45 - 46 - const initShortcut = (shortcut) => { 47 - _api.shortcuts.register(shortcut, () => { 48 - openSettingsWindow(prefs()); 49 - }); 50 - }; 51 - 52 - const prefs = () => JSON.parse(_store.getItem('prefs')); 53 - 54 - const init = () => { 55 - log('settings: init'); 56 - 57 - initStore(defaults); 58 - 59 - initShortcut(prefs().shortcutKey); 60 - }; 61 - 62 - const onChange = (changed, old) => { 63 - log(labels.featureType, 'onChange', changed); 64 - 65 - // TODO only update store if changed 66 - // and re-init 67 - if (changed.prefs) { 68 - _store.setItem('prefs', JSON.stringify(changed.prefs)); 69 - } 70 - 71 - if (changed.items) { 72 - _store.setItem('items', JSON.stringif(changed.items)); 73 - } 74 - }; 75 - 76 - 77 - window.addEventListener('load', init); 78 - 79 - })();
-66
features/settings/config.js
··· 1 - 2 - const labels = { 3 - featureType: 'settings', 4 - featureDisplay: 'Settings', 5 - prefs: { 6 - shortcutKey: 'Settings shortcut', 7 - } 8 - }; 9 - 10 - const prefsSchema = { 11 - "$schema": "https://json-schema.org/draft/2020-12/schema", 12 - "$id": "peek.prefs.schema.json", 13 - "title": "Application and Settings preferences", 14 - "description": "Peek user preferences", 15 - "type": "object", 16 - "properties": { 17 - "shortcutKey": { 18 - "description": "App keyboard shortcut to load settings", 19 - "type": "string", 20 - "default": "CommandOrControl+," 21 - }, 22 - "height": { 23 - "description": "User-set or -defined height of Settings page", 24 - "type": "integer", 25 - "default": 600 26 - }, 27 - "width": { 28 - "description": "User-set or -defined width of Settings page", 29 - "type": "integer", 30 - "default": 800 31 - }, 32 - }, 33 - "required": [ "shortcutKey" ] 34 - }; 35 - 36 - // TODO: schemaize 0-9 constraints for peeks 37 - const schemas = { 38 - prefs: prefsSchema, 39 - }; 40 - 41 - // ui config for tweakpane filling 42 - const ui = { 43 - // allow user to create new items 44 - allowNew: false, 45 - // fields that are view only 46 - disabled: [], 47 - }; 48 - 49 - // defaults for user-modifiable preferences or data 50 - const defaults = { 51 - prefs: { 52 - shortcutKey: 'Option+,', 53 - height: 600, 54 - width: 800, 55 - } 56 - }; 57 - 58 - /* 59 - export { 60 - labels, 61 - schemas, 62 - ui, 63 - defaults 64 - }; 65 - */ 66 -
features/settings/content.css features/core/settings.css
+2 -2
features/settings/content.html features/core/settings.html
··· 5 5 <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> 6 6 <meta http-equiv="Content-Security-Policy" content="script-src 'self';"> 7 7 <title>peek</title> 8 - <link rel="stylesheet" href="content.css"> 8 + <link rel="stylesheet" href="settings.css"> 9 9 </head> 10 10 <body> 11 11 <div> ··· 23 23 24 24 <script type=module src="./../../node_modules/tweakpane/dist/tweakpane.js"></script> 25 25 <script src="./config.js"></script> 26 - <script type=module src="./content.js"></script> 26 + <script type=module src="./settings.js"></script> 27 27 28 28 </body> 29 29 </html>
+40 -17
features/settings/content.js features/core/settings.js
··· 1 - console.log('settings/content'); 1 + const DEBUG = 0; 2 + 3 + const log = (...args) => { 4 + if (!DEBUG) { 5 + return; 6 + } 7 + const str = args.join(', '); 8 + console.log(str); 9 + window.app.log(labels.featureType, args.map(JSON.stringify).join(', ')); 10 + }; 2 11 3 12 const init = () => { 4 - console.log('settings: init'); 13 + log('settings: init'); 5 14 6 15 const container = document.querySelector('.houseofpane'); 7 16 ··· 14 23 const disabled = ui.disabled || []; 15 24 16 25 const onChange = newData => { 17 - console.log('onChange', newData.prefs); 18 - localStorage.setItem('prefs', JSON.stringify(newData.prefs)); 19 - console.log('stored', JSON.parse(localStorage.getItem('prefs'))); 26 + log('onChange', JSON.stringify(newData)); 27 + if (newData.prefs) { 28 + const key = 'prefs'; 29 + localStorage.setItem(key, JSON.stringify(newData[key])); 30 + log('stored', key, JSON.parse(localStorage.getItem(key))); 31 + } 32 + 33 + if (newData.items) { 34 + const key = 'items'; 35 + localStorage.setItem(key, JSON.stringify(newData[key])); 36 + log('stored', key, JSON.parse(localStorage.getItem(key))); 37 + } 20 38 }; 21 39 22 40 const prefs = JSON.parse(localStorage.getItem('prefs')); 23 - console.log('prefs', prefs) 41 + const items = JSON.parse(localStorage.getItem('items')); 42 + 43 + log('prefs', prefs) 44 + log('items', items) 24 45 25 46 const feature = { 26 47 config: ui, 27 48 labels, 28 49 schemas, 29 - prefs 50 + prefs, 51 + items 30 52 }; 31 53 32 54 const pane = initFeaturePane( ··· 34 56 feature, 35 57 onChange 36 58 ); 37 - console.log('created pane'); 59 + 60 + log('created pane'); 38 61 }; 39 62 40 63 const fillPaneFromSchema = (pane, labels, schema, data, onChange, disabled) => { ··· 69 92 // TODO: consider inline state management 70 93 input.on('change', ev => { 71 94 // TODO: validate against schema 72 - console.log('inline field change', k, ev) 95 + log('inline field change', k, ev) 73 96 data[k] = ev.value; 74 97 onChange(data) 75 98 }); ··· 80 103 // https://github.com/cocopon/tweakpane/issues/431 81 104 const exportPaneData = pane => { 82 105 const children = pane.rackApi_.children.filter(p => p.children); 83 - const val = pane.rackApi_.children.filter(p => p.children).map(paneChild => { 106 + const val = children.map(paneChild => { 84 107 return paneChild.children.reduce((obj, field) => { 85 108 const k = field.label; 86 109 if (!k) { ··· 100 123 } 101 124 102 125 // TODO: drop fields not supported for now 103 - if (v) { 126 + if (v != undefined) { 104 127 obj[k] = v; 105 128 } 106 129 ··· 111 134 }; 112 135 113 136 const initFeaturePane = (container, feature, onChange) => { 114 - const { config, labels, prefs, schemas, data } = feature; 137 + const { config, labels, schemas, prefs, items } = feature; 115 138 116 139 const pane = new Tweakpane.Pane({ 117 140 container: container, ··· 121 144 const update = (all) => { 122 145 const paneData = exportPaneData(pane); 123 146 124 - console.log('folder level update for', labels.featureDisplay, paneData); 147 + log('folder level update for', labels.featureDisplay, paneData); 125 148 126 149 let updated = { 127 150 }; ··· 146 169 147 170 // prefs pane 148 171 if (prefs) { 149 - 150 172 const prefsFolder = pane.addFolder({ 151 173 title: schemas.prefs.title, 152 174 expanded: true 153 175 }); 154 176 155 177 const onPrefChange = changed => { 156 - console.log('initFeaturePane::onPrefChange', changed) 178 + log('initFeaturePane::onPrefChange', changed) 157 179 update(!config.allowNew); 158 180 }; 159 181 ··· 161 183 } 162 184 163 185 // add items 164 - if (data && data.hasOwnProperty('items')) { 165 - data.items.forEach(item => { 186 + if (items) { 187 + log('adding items panes'); 188 + items.forEach(item => { 166 189 const folder = pane.addFolder({ 167 190 title: item.title, 168 191 expanded: false
+9 -5
index.js
··· 3 3 4 4 console.log('main'); 5 5 6 + 6 7 const DEBUG = process.env.DEBUG; 7 8 8 9 // Modules to control application life and create native browser window ··· 147 148 ipcMain.on('registershortcut', (ev, msg) => { 148 149 //_shortcuts[msg.shortcut] = msg.replyTopic; 149 150 registerShortcut(msg.shortcut, () => { 150 - console.log('on(registershortcut): shorcut executed', msg.shortcut, msg.replyTopic) 151 + console.log('on(registershortcut): shortcut executed', msg.shortcut, msg.replyTopic) 151 152 ev.reply(msg.replyTopic, {}); 152 153 }); 153 154 }); ··· 210 211 }; 211 212 212 213 // window opener 213 - const openWindow = (params) => { 214 + const openWindow = (params, callback) => { 214 215 console.log('openWindow', params); 215 216 216 217 // if no source identifier, barf ··· 322 323 323 324 //win.webContents.send('window', { type: labels.featureType, id: win.id}); 324 325 //broadcastToWindows('window', { type: labels.featureType, id: win.id}); 326 + 327 + // TODO: fix func-level callback handling and resp obj 325 328 326 329 if (params.script) { 327 330 const script = params.script; ··· 330 333 win.webContents.on(domEvent, async () => { 331 334 try { 332 335 const r = await win.webContents.executeJavaScript(script.script); 333 - if (script.callback) { 334 - script.callback(r); 336 + if (callback) { 337 + callback({ 338 + scriptOutput: r 339 + }); 335 340 } 336 341 } catch(ex) { 337 342 console.error('cs exec error', ex); 338 - script.callback(null); 339 343 } 340 344 if (script.closeOnCompletion) { 341 345 win.destroy();
+10 -4
preload.js
··· 33 33 34 34 api.shortcuts = { 35 35 register: (shortcut, cb) => { 36 - //log(source, 'registering ' + shortcut + ' for ' + window.location) 36 + log(src, 'registering ' + shortcut + ' for ' + window.location) 37 37 38 38 const replyTopic = `${shortcut}${window.location}`; 39 39 ··· 42 42 replyTopic 43 43 }); 44 44 45 - ipcRenderer.on(replyTopic, () => { 45 + ipcRenderer.on(replyTopic, (ev, msg) => { 46 46 log(src, 'shortcut execution reply'); 47 47 cb(); 48 48 }); ··· 58 58 api.openWindow = (params, callback) => { 59 59 log(src, ['openwindow', JSON.stringify(params), 'for', window.location].join(', ')); 60 60 // TODO: won't work for features that open multiple windows 61 - const replyTopic = params.feature; 61 + const replyTopic = `${params.feature}${params.address}`; 62 + 62 63 ipcRenderer.send('openwindow', { 63 64 params, 64 65 replyTopic 65 66 }); 67 + 66 68 if (callback) { 67 - ipcRenderer.on(replyTopic, params.callback); 69 + ipcRenderer.once(replyTopic, (ev, msg) => { 70 + log(src, 'resp from main'); 71 + log(src, msg); 72 + callback(msg); 73 + }); 68 74 } 69 75 }; 70 76