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 #20 from autonome/moreweb

Moreweb

authored by

Dietrich Ayala and committed by
GitHub
f44123f8 ff51d0a0

+1103 -747
+40 -8
README.md
··· 35 35 * Commands 36 36 * Groups 37 37 38 + Thinking about: 39 + - "native" web apps 40 + 38 41 ### Peeks 39 42 40 43 Peeks are keyboard activated modal chromeless web pages mapped to `Opt+0-9` and closed on blur, the `Escape` key or `cmd/ctrl+w`. ··· 57 60 * 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 58 61 * Security user interface must be a clear articulation of risks and trade-offs, and users should own the decisions 59 62 60 - ## Design 63 + ## User values 64 + 65 + - users can move, resize, change to their requirements 66 + - eg, browsers restrict minheight of a window, but i should be able make as short as i like 67 + 68 + ## Design patterns 61 69 62 70 * Escape IZUI 63 71 * IZUI: inverse zooming user interface ··· 109 117 110 118 * Going full crypto payments for distributed compute on this one. 111 119 120 + ## Daily Papercut Log 121 + 122 + - open bandcamp in a window, move over to 2nd display, accidently close it while moving around between other windows 123 + 112 124 ## Roadmap 113 125 114 126 Core moduluarization ··· 137 149 [x] move all possible code from the electron file to the web app 138 150 [x] move to web implemented globalShortcut 139 151 [x] move to web implemented openWindow 140 - [] per-feature settings ui 141 - [] load through url 152 + [] move settings re-use code to utils lib 142 153 [] ability to add clickable links in settings panes 143 154 [] add links to Settings app 155 + [] per-feature settings ui 156 + [] feature metadata in manifest 144 157 145 158 Daily driver blockers 146 - [] debug vs profile(s) for app dir 159 + [x] debug vs profile(s) for app dir 147 160 148 161 Core+settings 149 162 [x] move feature list and enablement to storage 150 163 [x] merge core + settings 151 164 [x] enable/disable features 152 - [] configurable default feature to load on app open (or none) 165 + [x] configurable default feature to load on app open (default to settings) 166 + [x] wire up tray icon to pref 167 + [x] tray click opens default app 168 + 169 + Commands/messaging 170 + [x] implement pubsub api 171 + [x] way to tell feature to open default ui (if there is one) 172 + [] way to tell feature to open settings ui (if there is one) 153 173 [] figure out re-init/reload story when pref/feature changes 154 174 [] figure out feature unload/reload (unreg shortcuts, close windows, etc) 155 175 176 + Misc 177 + [] fix ESC not working in web content 178 + 156 179 History 157 - [] implement pubsub api 158 180 [] push navigations out through pubsub 159 181 [] add history listener + storage to cmd 160 182 [] store central app action history 161 183 [] store content script data 162 184 185 + [] esc stack: from feature settings back to core settings 186 + [] add to izui stack (and ix w/ history?) 187 + 163 188 Core/Basic 164 189 [x] basic command bar to open pages 165 190 [x] fix setting layout wrapping issue ··· 200 225 [] discover + execute cmds? 201 226 [] need to be able to get/set properties from other "features"? 202 227 203 - 204 228 Window layout 205 229 [] try with settings maybe? 230 + [] tile/untile 206 231 207 232 Web Platform 208 233 [] need a web loader that's not full BrowserWindow? ··· 234 259 235 260 ``` 236 261 yarn install 237 - yarn start 262 + yarn debug 238 263 ``` 264 + 265 + ## Mobile 266 + 267 + - some of the features don't make sense as-is on mobile 268 + - but maybe quick access on mobile to slides/peeks would be nice 269 + - and seeing output of content scripts, or ability to re-run locally on demand 270 + - needs some sync facility (inevitable anyway) 239 271 240 272 ## Resources 241 273
+2 -1
features/cmd/background.html
··· 7 7 <title>peek:core:background</title> 8 8 </head> 9 9 <body> 10 - <script src="./config.js"></script> 10 + <script type=module src="../utils.js"></script> 11 + <script type=module src="./config.js"></script> 11 12 <script type=module src="./background.js"></script> 12 13 </body> 13 14 </html>
+36 -42
features/cmd/background.js
··· 1 1 // cmd/background.js 2 - (async () => { 3 2 4 - const log = (...args) => { 5 - window.app.log(labels.featureType, args.join(', ')); 6 - }; 3 + import { id, labels, schemas, ui, defaults } from './config.js'; 4 + import { log as l, openStore } from "../utils.js"; 7 5 8 - log('cmd/background'); 6 + const log = function(...args) { l(id, args); }; 9 7 10 - //import { labels, schemas, ui, defaults } from './config.js'; 8 + log('background'); 11 9 12 - //const debug = window.location.search.indexOf('debug') > 0; 13 - const debug = 1; 10 + const debug = window.app.debug; 11 + const store = openStore(id, defaults); 12 + const api = window.app; 14 13 15 - if (debug) { 16 - log('clearing storage') 17 - localStorage.clear(); 18 - } 19 - 20 - const _store = localStorage; 21 - const _api = window.app; 14 + const storageKeys = { 15 + PREFS: 'prefs', 16 + ITEMS: 'items', 17 + }; 22 18 23 19 const openInputWindow = prefs => { 24 20 const height = prefs.height || 50; ··· 32 28 width 33 29 }; 34 30 35 - _api.openWindow(params); 31 + api.openWindow(params); 36 32 }; 37 33 38 - const initStore = (data) => { 39 - const sp = _store.getItem('prefs'); 40 - if (!sp) { 41 - _store.setItem('prefs', JSON.stringify(data.prefs)); 42 - } 34 + const openSettingsWindow = (prefs) => { 35 + const height = prefs.height || 600; 36 + const width = prefs.width || 800; 37 + 38 + const params = { 39 + debug, 40 + feature: labels.featureType, 41 + file: 'features/core/settings.html', 42 + height, 43 + width 44 + }; 45 + 46 + _api.openWindow(params); 43 47 }; 44 48 45 - const initShortcut = (shortcut) => { 46 - _api.shortcuts.register(shortcut, () => { 47 - openInputWindow(prefs()); 49 + const initShortcut = (prefs) => { 50 + api.shortcuts.register(prefs.shortcutKey, () => { 51 + openInputWindow(prefs); 48 52 }); 49 53 }; 50 - 51 - const prefs = () => JSON.parse(_store.getItem('prefs')); 52 54 53 55 const init = () => { 54 - initStore(defaults); 56 + log('init'); 55 57 56 - initShortcut(prefs().shortcutKey); 57 - }; 58 + const prefs = () => store.get(storageKeys.PREFS); 58 59 59 - const onChange = (changed, old) => { 60 - log('onChange', changed); 60 + initShortcut(prefs()); 61 61 62 - // TODO only update store if changed 63 - // and re-init 64 - if (changed.prefs) { 65 - _store.setItem('prefs', JSON.stringify(changed.prefs)); 66 - } 62 + window.app.subscribe('open', msg => { 63 + if (msg.feature && msg.feature == `${id}/settings`) { 64 + openSettingsWindow(prefs()); 65 + } 66 + }); 67 67 68 - if (changed.items) { 69 - _store.setItem('items', JSON.stringif(changed.items)); 70 - } 71 68 }; 72 - 73 69 74 70 window.addEventListener('load', init); 75 - 76 - })();
+13 -22
features/cmd/commands.js
··· 1 - (async () => { 1 + import { id, labels, schemas, ui, defaults } from './config.js'; 2 + import { log as l, openStore } from "../utils.js"; 2 3 3 - const DEBUG = 1; 4 - const TEST = 0; 4 + const log = function(...args) { l(id, args); }; 5 5 6 - dbg('POPUP INIT'); 6 + log('background'); 7 7 8 - if (TEST === 1) { 9 - let title = '+pin'; 10 - let url = "javascript:q=location.href;if(document.getSelection){d=document.getSelection();}else{d='';};p=document.title;void(open('https://pinboard.in/add?showtags=yes&url='+encodeURIComponent(q)+'&description='+encodeURIComponent(d)+'&title='+encodeURIComponent(p),'Pinboard','toolbar=no,scrollbars=yes,width=750,height=700'));" 11 - let r = await browser.bookmarks.search({ title }) 12 - if (r.length == 0) { 13 - let bm = await browser.bookmarks.create({ title, url }) 14 - } 15 - } 8 + const debug = window.app.debug; 9 + const store = openStore(id); 10 + const api = window.app; 16 11 12 + const storageKeys = { 13 + PREFS: 'prefs', 14 + ITEMS: 'items', 15 + }; 17 16 18 17 let commands = {}; 19 18 20 19 function onCommandsUpdated () { 21 20 window.dispatchEvent(new CustomEvent('cmd-update-commands', { detail: commands })); 22 - dbg('main sending updated commands out', Object.keys(commands)) 21 + log('main sending updated commands out', Object.keys(commands)) 23 22 } 24 23 25 24 window.addEventListener('DOMContentLoaded', initializeCommandSources); ··· 41 40 } 42 41 43 42 function initializeCommandSources() { 44 - dbg('initializeCommandSources'); 43 + log('initializeCommandSources'); 45 44 46 45 sourceOpenURL(); 47 46 //sourceBookmarklets(); ··· 282 281 await sourceNote() 283 282 */ 284 283 285 - function dbg(...args) { 286 - if (DEBUG == 1) { 287 - console.log(...args) 288 - } 289 - } 290 - 291 284 function notify(title, content) { 292 285 browser.notifications.create({ 293 286 "type": "basic", ··· 296 289 "message": content 297 290 }); 298 291 } 299 - 300 - })();
+3 -2
features/cmd/config.js
··· 1 + const id = 'features/cmd'; 2 + 1 3 const labels = { 2 4 featureType: 'cmd', 3 5 featureDisplay: 'Cmd', ··· 103 105 disabled: [], 104 106 }; 105 107 106 - /* 107 108 export { 109 + id, 108 110 labels, 109 111 schemas, 110 112 ui, 111 113 defaults 112 114 }; 113 - */ 114 115
+5
features/cmd/panel.css
··· 1 + 2 + body { 3 + margin: 0; 4 + padding: 0; 5 + }
+18 -19
features/cmd/panel.html
··· 1 1 <!DOCTYPE html> 2 2 <html> 3 - <head> 4 - <title>cmd</title> 5 - <meta charset="utf-8"> 6 - <meta name="description" content=""> 7 - <meta name="viewport" content="width=device-width, initial-scale=1"> 8 - <style> 9 - body { 10 - margin: 0; 11 - padding: 0; 12 - } 13 - </style> 14 - </head> 15 - <body> 16 - <script src="./config.js"></script> 17 - <script src="./panel.js"></script> 18 - <script src="./commands.js"></script> 19 - </body> 20 - </html> 21 - 3 + <head> 4 + <title>peek:cmd:panel</title> 5 + <meta charset="utf-8"> 6 + <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> 7 + <meta http-equiv="Content-Security-Policy" content="script-src 'self';"> 8 + <meta name="description" content=""> 9 + <meta name="viewport" content="width=device-width, initial-scale=1"> 10 + <style> 11 + </style> 12 + <link rel="stylesheet" href="panel.css"> 13 + </head> 14 + <body> 15 + <script type=module src="../utils.js"></script> 16 + <script type=module src="./config.js"></script> 17 + <script type=module src="./panel.js"></script> 18 + <script type=module src="./commands.js"></script> 19 + </body> 20 + </html>
+13 -7
features/cmd/panel.js
··· 1 + // cmd/panel.js 1 2 /* 2 3 3 4 TODO: NOW ··· 37 38 38 39 */ 39 40 40 - (async () => { 41 + import { id, labels, schemas, ui, defaults } from './config.js'; 42 + import { log as l, openStore } from "../utils.js"; 43 + 44 + const log = function(...args) { l(id, args); }; 41 45 42 - const log = (...args) => { 43 - window.app.log(labels.featureType, args.join(', ')); 44 - }; 46 + log('background'); 45 47 46 - log('cmd/panel'); 48 + const debug = window.app.debug; 49 + const store = openStore(id); 50 + const api = window.app; 47 51 52 + const storageKeys = { 53 + PREFS: 'prefs', 54 + ITEMS: 'items', 55 + }; 48 56 49 57 let state = { 50 58 commands: [], // array of command names ··· 413 421 } 414 422 return str; 415 423 } 416 - 417 - })();
+2 -1
features/core/background.html
··· 7 7 <title>peek:core:background</title> 8 8 </head> 9 9 <body> 10 - <script src="./config.js"></script> 10 + <script type=module src="./config.js"></script> 11 + <script type=module src="../utils.js"></script> 11 12 <script type=module src="./background.js"></script> 12 13 </body> 13 14 </html>
+63 -64
features/core/background.js
··· 1 - const log = (...args) => { 2 - //console.log.apply(null, [source].concat(args)); 3 - window.app.log(labels.featureType, args.join(', ')); 4 - }; 5 - 6 - log('loading'); 7 - 8 - const features = [ 9 - //'features/cmd/background.html', 10 - //'features/groups/background.html', 11 - //'features/peeks/background.html', 12 - //'features/scripts/background.html', 13 - //'features/settings/background.html', 14 - //'features/slides/background.html' 15 - ]; 1 + import { id, labels, schemas, ui, defaults } from './config.js'; 2 + import { log as l, openStore } from "../utils.js"; 16 3 17 - //import { labels, schemas, ui, defaults } from './config.js'; 4 + const log = function(...args) { l(id, args); }; 18 5 19 - //const debug = window.location.search.indexOf('debug') > 0; 20 - const debug = 1; 6 + log('background'); 21 7 22 - if (debug) { 23 - log('clearing storage') 24 - localStorage.clear(); 25 - } 8 + const debug = window.app.debug; 26 9 27 - const _store = localStorage; 10 + const _store = openStore(id, defaults); 28 11 const _api = window.app; 29 12 13 + const storageKeys = { 14 + PREFS: 'prefs', 15 + FEATURES: 'items', 16 + }; 17 + 30 18 const openSettingsWindow = (prefs) => { 31 19 const height = prefs.height || 600; 32 20 const width = prefs.width || 800; ··· 42 30 _api.openWindow(params); 43 31 }; 44 32 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 - openSettingsWindow(prefs()); 33 + const initShortcut = (prefs) => { 34 + _api.shortcuts.register(prefs.shortcutKey, () => { 35 + openSettingsWindow(prefs); 61 36 }); 62 37 }; 63 - 64 - const prefs = () => JSON.parse(_store.getItem('prefs')); 65 - const items = () => JSON.parse(_store.getItem('items')); 66 38 67 39 const initFeature = f => { 68 40 if (!f.enabled) { ··· 76 48 debug, 77 49 file: f.address, 78 50 keepLive: true, 79 - show: false 51 + show: debug 80 52 }; 81 53 82 54 window.app.openWindow(params); ··· 97 69 }); 98 70 }; 99 71 100 - const odiff = (a, b) => Object.entries(b).reduce((c, [k, v]) => Object.assign(c, a[k] ? {} : { [k]: v }), {}); 101 - 102 - const onStorageChange = (e) => { 103 - console.log(e); 104 - //log('oSC', e.key, JSON.stringify(odiff(e.oldValue, e.newValue))); 105 - /* 106 - e.key; 107 - e.oldValue; 108 - e.newValue; 109 - e.url; 110 - JSON.stringify( 111 - e.storageArea 112 - ); 113 - */ 114 - items().forEach(initFeature); 115 - }; 116 - 117 - window.addEventListener('storage', onStorageChange); 72 + const prefs = () => _store.get(storageKeys.PREFS); 73 + const items = () => _store.get(storageKeys.FEATURES); 118 74 119 75 const init = () => { 120 - log('settings: init'); 76 + log('init'); 121 77 122 - initStore(defaults); 78 + const p = prefs(); 123 79 124 - initShortcut(prefs().shortcutKey); 80 + initShortcut(p); 125 81 126 82 items().forEach(initFeature); 127 83 //features.forEach(initIframeFeature); 84 + 85 + const startupFeatureTitle = p.startupFeature; 86 + 87 + const startupFeature = items().find(f => f.title = startupFeatureTitle); 88 + 89 + window.app.subscribe('open', msg => { 90 + if (msg.feature && msg.feature == 'feature/core/settings') { 91 + openSettingsWindow(p); 92 + } 93 + }); 94 + 95 + window.app.publish('prefs', { 96 + feature: id, 97 + prefs: p 98 + }); 128 99 }; 129 100 130 101 window.addEventListener('load', init); 102 + 103 + const odiff = (a, b) => Object.entries(b).reduce((c, [k, v]) => Object.assign(c, a[k] ? {} : { [k]: v }), {}); 104 + 105 + const onStorageChange = (e) => { 106 + const old = JSON.parse(e.oldValue); 107 + const now = JSON.parse(e.newValue); 108 + 109 + const featureKey = `${id}+${storageKeys.FEATURES}`; 110 + //log('onStorageChane', e.key, featureKey) 111 + if (e.key == featureKey) { 112 + //log('STORAGE CHANGE', e.key, old[0].enabled, now[0].enabled); 113 + items().forEach((feat, i) => { 114 + log(feat.title, i, feat.enabled, old[i].enabled, now[i].enabled); 115 + // disabled, so unload 116 + if (old[i].enabled == true && now[i].enabled == false) { 117 + // TODO 118 + log('TODO: add unloading of features', feat) 119 + } 120 + // enabled, so load 121 + else if (old[i].enabled == false && now[i].enabled == true) { 122 + initFeature(feat); 123 + } 124 + }); 125 + } 126 + //JSON.stringify(e.storageArea); 127 + }; 128 + 129 + window.addEventListener('storage', onStorageChange);
+95 -30
features/core/config.js
··· 1 - const source = 'core/background'; 1 + const id = 'features/core'; 2 2 3 3 const labels = { 4 4 featureType: 'settings', ··· 13 13 const prefsSchema = { 14 14 "$schema": "https://json-schema.org/draft/2020-12/schema", 15 15 "$id": "peek.prefs.schema.json", 16 - "title": "Application and Settings preferences", 16 + "title": "Application Settings", 17 17 "description": "Peek user preferences", 18 18 "type": "object", 19 19 "properties": { ··· 32 32 "type": "integer", 33 33 "default": 800 34 34 }, 35 + "startupFeature": { 36 + "description": "Feature to load at app startup", 37 + "type": "string", 38 + "default": "Settings" 39 + }, 40 + "showTrayIcon": { 41 + "description": "Whether to show app icon in system tray", 42 + "type": "boolean", 43 + "default": true 44 + }, 45 + "showInDockAndSwitcher": { 46 + "description": "Whether to hide or show app in OS dock and app switcher", 47 + "type": "boolean", 48 + "default": false 49 + }, 35 50 }, 36 - "required": [ "shortcutKey" ] 51 + "required": [ "shortcutKey", "startupFeature", "enableTrayIcon", "showInDockAndSwitcher" ] 37 52 }; 38 53 39 54 const itemSchema = { 40 55 "$schema": "https://json-schema.org/draft/2020-12/schema", 41 56 "$id": "peek.settings.feature.schema.json", 42 - "title": "Peek - feature", 43 - "description": "Peek modular feature", 57 + "title": "Feature", 58 + "description": "Application feature", 44 59 "type": "object", 45 60 "properties": { 46 61 "title": { ··· 49 64 }, 50 65 "address": { 51 66 "description": "URL to load", 52 - "type": "string" 53 - }, 54 - "settingsAddress": { 55 - "description": "URL to load feature settings", 56 - "type": "string" 67 + "type": "string", 68 + "format": "uri" 57 69 }, 58 70 "enabled": { 59 71 "description": "Whether the feature is enabled or not - defaults to true", ··· 61 73 "default": true 62 74 }, 63 75 }, 64 - "required": [ "title", "address", "settingsAddress", "enabled" ] 76 + "required": [ "title", "address", "enabled" ] 65 77 }; 66 78 67 79 const listSchema = { 68 - type: 'array', 69 - items: { "$ref": "#/$defs/feature" } 80 + "title": "Features", 81 + "type": 'array', 82 + "features": { "$ref": "#/$defs/feature" } 70 83 }; 71 84 72 - // TODO: schemaize 0-9 constraints for peeks 73 85 const schemas = { 74 86 prefs: prefsSchema, 75 87 item: itemSchema, 76 88 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 89 }; 86 90 87 91 // defaults for user-modifiable preferences or data ··· 90 94 shortcutKey: 'Option+,', 91 95 height: 600, 92 96 width: 800, 93 - openDefaultFeature: 'Settings', 97 + startupFeature: 'feature/core/settings', 94 98 showTrayIcon: true, 99 + showInTrayAndSwitcher: true 95 100 }, 96 101 items: [ 97 102 { title: 'Cmd', 98 103 address: 'features/cmd/background.html', 104 + enabled: false, 99 105 settingsAddress: 'features/cmd/settings.html', 100 - enabled: false 101 106 }, 102 107 { title: 'Groups', 103 108 address: 'features/groups/background.html', 109 + enabled: false, 104 110 settingsAddress: 'features/groups/settings.html', 105 - enabled: false 106 111 }, 107 112 { title: 'Peeks', 108 113 address: 'features/peeks/background.html', 114 + enabled: false, 109 115 settingsAddress: 'features/peeks/settings.html', 110 - enabled: true 111 116 }, 112 117 { title: 'Scripts', 113 118 address: 'features/scripts/background.html', 119 + enabled: false, 114 120 settingsAddress: 'features/scripts/settings.html', 115 - enabled: false 116 121 }, 117 122 { title: 'Slides', 118 123 address: 'features/slides/background.html', 124 + enabled: false, 119 125 settingsAddress: 'features/slides/settings.html', 120 - enabled: true 121 - }, 126 + } 122 127 ] 123 128 }; 124 129 130 + // ui config for tweakpane filling 131 + // TODO: this needs to be per section 132 + // or integrated some other way entirely, kind of a mess 133 + // 134 + // gotta think about much more complex objects 135 + // and also multiple types of items/lists 136 + const ui = { 137 + // allow user to create new items 138 + allowNew: false, 139 + 140 + // fields that are view only 141 + disabled: ['title', 'address' ], 142 + 143 + // fields to make links 144 + linkify: [ 145 + { field: 'settingsAddress', 146 + title: 'Settings' 147 + } 148 + ], 149 + }; 150 + 151 + 125 152 /* 153 + const paneData = { 154 + label: labels.featureDisplay, 155 + children: [], 156 + }; 157 + 158 + settings.sections.push({ 159 + }); 160 + 161 + { 162 + "disabled": false, 163 + "expanded": true, 164 + "hidden": false, 165 + "children": [ 166 + { 167 + "disabled": false, 168 + "expanded": true, 169 + "hidden": false, 170 + "label": "param1", 171 + "binding": { 172 + "key": "param1", 173 + "value": 1 174 + }, 175 + "tag": "foo" 176 + }, 177 + { 178 + "disabled": false, 179 + "hidden": false, 180 + "label": "param2", 181 + "binding": { 182 + "key": "param2", 183 + "value": 2 184 + }, 185 + "tag": "bar" 186 + } 187 + ], 188 + } 189 + */ 190 + 126 191 export { 192 + id, 127 193 labels, 128 194 schemas, 129 195 ui, 130 196 defaults 131 197 }; 132 - */
+5 -3
features/core/settings.html
··· 1 1 <!DOCTYPE html> 2 2 <html> 3 3 <head> 4 + <title>peek:core:settings</title> 4 5 <meta charset="UTF-8"> 5 6 <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> 6 7 <meta http-equiv="Content-Security-Policy" content="script-src 'self';"> 7 - <title>peek</title> 8 + <meta name="description" content=""> 9 + <meta name="viewport" content="width=device-width, initial-scale=1"> 8 10 <link rel="stylesheet" href="settings.css"> 9 11 </head> 10 12 <body> ··· 21 23 Electron <span id="electron-version"></span><br> 22 24 </div> 23 25 24 - <script type=module src="./../../node_modules/tweakpane/dist/tweakpane.js"></script> 25 - <script src="./config.js"></script> 26 + <script type=module src="../utils.js"></script> 27 + <script type=module src="./config.js"></script> 26 28 <script type=module src="./settings.js"></script> 27 29 28 30 </body>
+71 -207
features/core/settings.js
··· 1 - const DEBUG = 0; 2 - 3 - const log = (...args) => { 4 - if (!DEBUG) { 5 - return; 6 - } 7 - //const str = args.map(JSON.stringify).join(', '); 8 - const str = args.join(', '); 9 - console.log(str); 10 - window.app.log(labels.featureType, str); 11 - }; 12 - 13 - const init = () => { 14 - log('settings: init'); 15 - 16 - const container = document.querySelector('.houseofpane'); 17 - 18 - const type = labels.featureType; 19 - 20 - const paneContainer = document.createElement('div'); 21 - container.appendChild(paneContainer); 22 - 23 - const allowNew = ui.allowNew || false; 24 - const disabled = ui.disabled || []; 25 - 26 - const onChange = newData => { 27 - log('onChange', JSON.stringify(newData)); 28 - 29 - if (newData.prefs) { 30 - const key = 'prefs'; 31 - localStorage.setItem(key, JSON.stringify(newData[key])); 32 - log('stored', key, JSON.parse(localStorage.getItem(key))); 33 - } 34 - 35 - if (newData.items) { 36 - const key = 'items'; 37 - localStorage.setItem(key, JSON.stringify(newData[key])); 38 - log('stored', key, JSON.parse(localStorage.getItem(key))); 39 - } 40 - }; 1 + import { id, labels, schemas, ui, defaults } from './config.js'; 2 + import { log as l, openStore, settingsPane } from "../utils.js"; 41 3 42 - const prefs = JSON.parse(localStorage.getItem('prefs')); 43 - const items = JSON.parse(localStorage.getItem('items')); 4 + const log = function(...args) { l(id, args); }; 5 + const DEBUG = window.app.debug; 44 6 45 - log('prefs', prefs) 46 - log('items', items) 7 + log('loading', id); 47 8 48 - const feature = { 49 - config: ui, 50 - labels, 51 - schemas, 52 - prefs, 53 - items 54 - }; 55 - 56 - const pane = initFeaturePane( 57 - paneContainer, 58 - feature, 59 - onChange 60 - ); 61 - 62 - log('created pane'); 9 + const storageKeys = { 10 + PREFS: 'prefs', 11 + FEATURES: 'items', 63 12 }; 64 13 65 - const fillPaneFromSchema = (pane, labels, schema, data, onChange, disabled) => { 66 - const props = schema.properties; 67 - 68 - Object.keys(props).forEach(k => { 69 - // schema for property 70 - const s = props[k]; 71 - 72 - // value (or default) 73 - const v = 74 - (data && data.hasOwnProperty(k)) 75 - ? data[k] 76 - : props[k].default; 14 + const store = openStore(id); 15 + const container = document.querySelector('.houseofpane'); 16 + const prefs = store.get(storageKeys.PREFS); 17 + const items = store.get(storageKeys.FEATURES); 77 18 78 - const params = {}; 79 - const opts = {}; 80 - 81 - // dedecimalize 82 - if (s.type == 'integer') { 83 - opts.step = 1; 84 - } 85 - 86 - // disabled fields 87 - if (disabled.includes(k)) { 88 - opts.disabled = true; 89 - } 90 - 91 - params[k] = v; 92 - 93 - const input = pane.addInput(params, k, opts); 94 - 95 - // TODO: consider inline state management 96 - input.on('change', ev => { 97 - // TODO: validate against schema 98 - log('inline field change', k, ev) 99 - data[k] = ev.value; 100 - onChange(data) 101 - }); 102 - }); 103 - }; 104 - 105 - // TODO: fuckfuckfuck 106 - // https://github.com/cocopon/tweakpane/issues/431 107 - const exportPaneData = pane => { 108 - const children = pane.rackApi_.children.filter(p => p.children); 109 - const val = children.map(paneChild => { 110 - return paneChild.children.reduce((obj, field) => { 111 - const k = field.label; 112 - if (!k) { 113 - return obj; 114 - } 115 - 116 - let v = null; 117 - 118 - const input = field.element.querySelector('.tp-txtv_i') 119 - if (input) { 120 - v = input.value; 121 - } 122 - 123 - const checkbox = field.element.querySelector('.tp-ckbv_i'); 124 - if (checkbox) { 125 - v = checkbox.checked; 126 - } 127 - 128 - // TODO: drop fields not supported for now 129 - if (v != undefined) { 130 - obj[k] = v; 131 - } 19 + const onChange = newData => { 20 + log('onChange', JSON.stringify(newData)); 132 21 133 - return obj; 134 - }, {}); 135 - }); 136 - return val; 22 + if (newData.prefs) { 23 + const key = 'prefs'; 24 + store.set(storageKeys.PREFS, newData[key]); 25 + log('stored', key, store.get(storageKeys.PREFS)); 26 + } 27 + 28 + if (newData.items) { 29 + const key = 'items'; 30 + store.set(storageKeys.FEATURES, newData[key]); 31 + log('stored', key, store.get(storageKeys.FEATURES)); 32 + } 137 33 }; 138 34 139 - const initFeaturePane = (container, feature, onChange) => { 140 - const { config, labels, schemas, prefs, items } = feature; 35 + const paneConfig = { 36 + 37 + label: labels.featureDisplay, 141 38 142 - const pane = new Tweakpane.Pane({ 143 - container: container, 144 - title: labels.featureDisplay 145 - }); 39 + children: [ 40 + { 41 + label: schemas.prefs.title, 146 42 147 - const update = (all) => { 148 - const paneData = exportPaneData(pane); 43 + // json schema to validate against 44 + schema: schemas.prefs, 149 45 150 - log('folder level update for', labels.featureDisplay, paneData); 46 + // data to populate 47 + data: defaults.prefs, 48 + }, 151 49 152 - let updated = {}; 50 + { 51 + label: schemas.items.title, 153 52 154 - // TODO: make this right, ugh 155 - if (prefs) { 156 - updated.prefs = paneData.shift(); 157 - } 53 + // json schema to validate against 54 + schema: schemas.items, 158 55 159 - // remove "new item" entry if not editable feature 160 - // TODO: make this right 161 - if (!all) { 162 - newData.pop(); 163 - } 56 + // data to populate 57 + data: defaults.items, 164 58 165 - if (paneData.length > 0) { 166 - updated.items = paneData; 167 - } 59 + // allow user to create new entries 60 + // adds an "add" button 61 + canAdd: true, 168 62 169 - onChange(updated); 170 - }; 63 + // allow user to delete entries 64 + canDelete: true, 171 65 172 - // prefs pane 173 - if (prefs) { 174 - const prefsFolder = pane.addFolder({ 175 - title: schemas.prefs.title, 176 - expanded: true 177 - }); 178 - 179 - const onPrefChange = changed => { 180 - log('initFeaturePane::onPrefChange', changed) 181 - update(!config.allowNew); 182 - }; 66 + // fields that are view only 67 + disabled: [ 68 + 'title', 'address' 69 + ], 183 70 184 - fillPaneFromSchema(prefsFolder, labels, schemas.prefs, prefs, onPrefChange, []); 185 - } 71 + // field callbacks: function(pane, data) 72 + callbacks: [ 73 + 'settingsAddress', 74 + ], 186 75 187 - // add items 188 - if (items) { 189 - log('adding items panes'); 190 - items.forEach(item => { 191 - const folder = pane.addFolder({ 192 - title: item.title, 193 - expanded: false 194 - }); 76 + // non-schema fields to add 77 + addlFields: [ 78 + ], 79 + }, 80 + ] 81 + }; 195 82 196 - fillPaneFromSchema(folder, labels, schemas.item, item, update, config.disabled); 197 - 198 - // TODO: implement 199 - //folder.addButton({title: labels.testBtn}); 83 + const init = () => { 200 84 201 - if (config.allowNew) { 202 - const delBtn = folder.addButton({title: labels.delBtn}); 203 - delBtn.on('click', () => { 204 - pane.remove(folder); 205 - // TODO: https://github.com/cocopon/tweakpane/issues/533 206 - update(); 207 - }); 208 - } 209 - 210 - //folder.on('change', () => update(!config.allowNew)); 85 + /* 86 + pubsub.publish('open', { 87 + feature: msg.prefs.startupFeature 211 88 }); 212 - } 89 + */ 213 90 214 91 /* 215 - if (config.allowNew) { 216 - // add new item entry 217 - const folder = pane.addFolder({ 218 - title: labels.newFolder, 219 - expanded: false 220 - }); 92 + items.forEach(item => { 93 + item.settings = 'button'; 94 + }); 95 + */ 221 96 222 - //fillPaneFromSchema(folder, labels, schema); 223 - fillPaneFromSchema(folder, labels, schema, {}, onChange, disabled); 224 - 225 - const btn = pane.addButton({title: labels.addBtn}); 97 + settingsPane(container, ui, labels, schemas, prefs, items, onChange); 226 98 227 - // handle adds of new entries 228 - btn.on('click', () => { 229 - update(true); 230 - }); 231 - } 232 - */ 233 - 234 - return pane; 235 99 }; 236 100 237 101 window.addEventListener('load', init);
+2 -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 + <script type=module src="../utils.js"></script> 11 + <script type=module src="./config.js"></script> 11 12 <script type=module src="./background.js"></script> 12 13 </body> 13 14 </html>
+17 -51
features/groups/background.js
··· 1 - // slides/slides.js 1 + // groups/background.js 2 2 3 - const log = (...args) => { 4 - console.log(labels.featureType, window.app.shortcuts); 5 - window.app.log(labels.featureType, args.join(', ')); 6 - }; 3 + import { id, labels, schemas, ui, defaults } from './config.js'; 4 + import { log as l, openStore } from "../utils.js"; 7 5 8 - log('peeks/background'); 6 + const log = function(...args) { l(id, args); }; 9 7 10 - //import { labels, schemas, ui, defaults } from './config.js'; 8 + const debug = window.app.debug; 11 9 12 - //const debug = window.location.search.indexOf('debug') > 0; 13 - const debug = 1; 10 + log('background'); 14 11 15 - if (debug) { 16 - log('clearing storage') 17 - localStorage.clear(); 18 - } 12 + const store = openStore(id, defaults); 13 + const api = window.app; 19 14 20 - const _store = localStorage; 21 - const _api = window.app; 15 + const storageKeys = { 16 + PREFS: 'prefs', 17 + ITEMS: 'items', 18 + }; 22 19 23 20 const openGroupsWindow = () => { 24 21 const height = 600; ··· 31 28 width 32 29 }; 33 30 34 - _api.openWindow(params); 31 + api.openWindow(params); 35 32 }; 36 33 37 34 const initShortcut = shortcut => { 38 - _api.shortcuts.register(shortcut, () => { 35 + api.shortcuts.register(shortcut, () => { 39 36 openGroupsWindow(); 40 37 }); 41 38 }; 42 39 43 - const initStore = data => { 44 - const sp = _store.getItem('prefs'); 45 - if (!sp) { 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)); 53 - } 54 - */ 55 - }; 56 - 57 40 const initItems = (prefs, items) => { 58 41 const cmdPrefix = prefs.shortcutKeyPrefix; 59 42 60 43 items.forEach(item => { 61 44 const shortcut = `${cmdPrefix}${item.keyNum}`; 62 45 63 - _api.shortcuts.register(shortcut, () => { 46 + api.shortcuts.register(shortcut, () => { 64 47 executeItem(item); 65 48 }); 66 49 }); ··· 69 52 const init = () => { 70 53 log('init'); 71 54 72 - initStore(defaults); 73 - 74 - const prefs = () => JSON.parse(_store.getItem('prefs')); 55 + const prefs = () => store.get(storageKeys.PREFS); 75 56 76 57 initShortcut(prefs().shortcutKey); 77 58 78 59 /* 79 - const items = () => JSON.parse(_store.getItem('items')); 60 + const items = () => store.get(storageKeys.ITEMS); 80 61 81 - // initialize slides 82 62 if (items().length > 0) { 83 63 initItems(prefs(), items()); 84 64 } 85 65 */ 86 - }; 87 - 88 - const onChange = (changed, old) => { 89 - log('onChange', changed); 90 - 91 - // TODO only update store if changed 92 - // and re-init 93 - if (changed.prefs) { 94 - _store.setItem('prefs', JSON.stringify(changed.prefs)); 95 - } 96 - 97 - if (changed.items) { 98 - _store.setItem('items', JSON.stringif(changed.items)); 99 - } 100 66 }; 101 67 102 68 window.addEventListener('load', init);
+8
features/groups/config.js
··· 1 + const id = 'features/groups'; 1 2 2 3 const labels = { 3 4 featureType: 'groups', ··· 104 105 disabled: ['keyNum'], 105 106 }; 106 107 108 + export { 109 + id, 110 + labels, 111 + schemas, 112 + ui, 113 + defaults 114 + };
+3 -1
features/groups/home.js
··· 56 56 initStorage(); 57 57 58 58 // Data loaded, start building UI 59 - populateUI(); 59 + //populateUI(); 60 60 61 61 // New group click handler 62 62 document.querySelector('.newgroup').addEventListener('click', function() { ··· 171 171 } 172 172 173 173 function initializeGroupData() { 174 + /* 174 175 return new Promise(function(resolve, reject) { 175 176 // Clear out old tab ids 176 177 for (let id in config.groups) { ··· 194 195 resolve(); 195 196 }); 196 197 }); 198 + */ 197 199 } 198 200 199 201 function newGroup(title) {
+2 -1
features/peeks/background.html
··· 7 7 <title>peek:core:background</title> 8 8 </head> 9 9 <body> 10 - <script src="./config.js"></script> 10 + <script type=module src="../utils.js"></script> 11 + <script type=module src="./config.js"></script> 11 12 <script type=module src="./background.js"></script> 12 13 </body> 13 14 </html>
+21 -53
features/peeks/background.js
··· 1 - // slides/slides.js 2 - //(async () => { 1 + // peeks/background.js 3 2 4 - const log = (...args) => { 5 - console.log(labels.featureType, window.app.shortcuts); 6 - window.app.log(labels.featureType, args.join(', ')); 7 - }; 3 + import { id, labels, schemas, ui, defaults } from './config.js'; 4 + import { log as l, openStore } from "../utils.js"; 8 5 9 - log('peeks/background'); 6 + const log = function(...args) { l(id, args); }; 10 7 11 - //import { labels, schemas, ui, defaults } from './config.js'; 8 + log('background'); 12 9 13 - //const debug = window.location.search.indexOf('debug') > 0; 14 - const debug = 1; 10 + const debug = window.app.debug; 15 11 16 - if (debug) { 17 - log('clearing storage') 18 - localStorage.clear(); 19 - } 12 + const store = openStore(id, defaults); 13 + const api = window.app; 20 14 21 - const _store = localStorage; 22 - const _api = window.app; 15 + const storageKeys = { 16 + PREFS: 'prefs', 17 + ITEMS: 'items', 18 + }; 23 19 24 20 const executeItem = (item) => { 25 21 const height = item.height || 600; ··· 38 34 persistData: item.persistData || false 39 35 }; 40 36 41 - _api.openWindow(params); 42 - }; 43 - 44 - const initStore = (data) => { 45 - const sp = _store.getItem('prefs'); 46 - if (!sp) { 47 - _store.setItem('prefs', JSON.stringify(data.prefs)); 48 - } 49 - 50 - const items = _store.getItem('items'); 51 - if (!items) { 52 - _store.setItem('items', JSON.stringify(data.items)); 53 - } 37 + api.openWindow(params); 54 38 }; 55 39 56 40 const initItems = (prefs, items) => { 57 41 const cmdPrefix = prefs.shortcutKeyPrefix; 58 42 59 43 items.forEach(item => { 60 - const shortcut = `${cmdPrefix}${item.keyNum}`; 44 + if (item.enabled == true) { 45 + const shortcut = `${cmdPrefix}${item.keyNum}`; 61 46 62 - _api.shortcuts.register(shortcut, () => { 63 - executeItem(item); 64 - }); 47 + api.shortcuts.register(shortcut, () => { 48 + executeItem(item); 49 + }); 50 + } 65 51 }); 66 52 }; 67 53 68 54 const init = () => { 69 55 log('init'); 70 56 71 - initStore(defaults); 72 - 73 - const prefs = () => JSON.parse(_store.getItem('prefs')); 74 - const items = () => JSON.parse(_store.getItem('items')); 57 + const prefs = () => store.get(storageKeys.PREFS); 58 + const items = () => store.get(storageKeys.ITEMS); 75 59 76 60 // initialize slides 77 61 if (items().length > 0) { ··· 79 63 } 80 64 }; 81 65 82 - const onChange = (changed, old) => { 83 - log('onChange', changed); 84 - 85 - // TODO only update store if changed 86 - // and re-init 87 - if (changed.prefs) { 88 - _store.setItem('prefs', JSON.stringify(changed.prefs)); 89 - } 90 - 91 - if (changed.items) { 92 - _store.setItem('items', JSON.stringif(changed.items)); 93 - } 94 - }; 95 - 96 66 window.addEventListener('load', init); 97 - 98 - //})();
+15 -1
features/peeks/config.js
··· 1 + const id = 'features/peeks'; 1 2 2 3 const labels = { 3 4 featureType: 'peeks', ··· 74 75 "type": "integer", 75 76 "default": 800 76 77 }, 78 + "enabled": { 79 + "description": "Whether this peek is enabled or not.", 80 + "type": "boolean", 81 + "default": false 82 + }, 77 83 }, 78 84 "required": [ "keyNum", "title", "address", "persistState", "keepLive", "allowSound", 79 - "height", "width" ] 85 + "height", "width", "enabled" ] 80 86 }; 81 87 82 88 const listSchema = { ··· 108 114 allowSound: false, 109 115 height: 600, 110 116 width: 800, 117 + enabled: false, 111 118 }; 112 119 } 113 120 ··· 119 126 disabled: ['keyNum'], 120 127 }; 121 128 129 + export { 130 + id, 131 + labels, 132 + schemas, 133 + ui, 134 + defaults 135 + };
+2 -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 + <script type=module src="../utils.js"></script> 11 + <script type=module src="./config.js"></script> 11 12 <script type=module src="./background.js"></script> 12 13 </body> 13 14 </html>
+44 -76
features/scripts/background.js
··· 1 1 // scripts/background.js 2 - //(async () => { 3 2 4 - const log = (...args) => { 5 - console.log(labels.featureType, window.app.shortcuts); 6 - window.app.log(labels.featureType, args.join(', ')); 7 - }; 3 + import { id, labels, schemas, ui, defaults } from './config.js'; 4 + import { log as l, openStore } from "../utils.js"; 8 5 9 - log('scripts/background'); 6 + const log = function(...args) { l(id, args); }; 10 7 11 - //import { labels, schemas, ui, defaults } from './config.js'; 8 + log('background'); 12 9 13 - //const debug = window.location.search.indexOf('debug') > 0; 14 - const debug = 1; 10 + const debug = window.app.debug; 15 11 16 - if (debug) { 17 - log('clearing storage') 18 - localStorage.clear(); 19 - } 12 + const store = openStore(id, defaults); 13 + const api = window.app; 20 14 21 - const _store = localStorage; 22 - const _api = window.app; 15 + const storageKeys = { 16 + PREFS: 'prefs', 17 + ITEMS: 'items', 18 + }; 23 19 24 20 let _intervals = []; 25 21 ··· 43 39 } 44 40 }; 45 41 46 - _api.openWindow(params, cb); 47 - }; 48 - 49 - const initStore = (data) => { 50 - const sp = _store.getItem('prefs'); 51 - if (!sp) { 52 - _store.setItem('prefs', JSON.stringify(data.prefs)); 53 - } 54 - 55 - const items = _store.getItem('items'); 56 - if (!items) { 57 - _store.setItem('items', JSON.stringify(data.items)); 58 - } 42 + api.openWindow(params, cb); 59 43 }; 60 44 61 45 const initItems = (prefs, items) => { ··· 66 50 // debounce me somehow so not shooting em all off 67 51 // at once every time app starts 68 52 items.forEach(item => { 69 - const interval = setInterval(() => { 70 - const r = executeItem(item, res => { 53 + if (item.enabled == true) { 54 + const interval = setInterval(() => { 55 + const r = executeItem(item, res => { 71 56 72 - //log('script result for', item.title, JSON.stringify(res)); 73 - //log('script prev val', item.previousValue); 57 + //log('script result for', item.title, JSON.stringify(res)); 58 + //log('script prev val', item.previousValue); 74 59 75 - if (item.previousValue != res) { 60 + if (item.previousValue != res) { 76 61 77 - log('result changed!', item.title, item.previousValue, res); 78 - // TODO: figure this out - it blows away all timers, which isn't great 79 - // 80 - // update stored value 81 - //item.previousValue = res; 82 - //updateItem(item); 62 + log('result changed!', item.title, item.previousValue, res); 63 + // TODO: figure this out - it blows away all timers, which isn't great 64 + // 65 + // update stored value 66 + //item.previousValue = res; 67 + //updateItem(item); 83 68 84 - // notification 85 - // add to schema and support per script 86 - /* 87 - const title = `Peek :: Script :: ${item.title}`; 88 - const body = [ 89 - `Script result changed for ${item.title}:`, 90 - `- Old: ${previousValue}`, 91 - `- New: ${res}` 92 - ].join('\n'); 69 + // notification 70 + // add to schema and support per script 71 + /* 72 + const title = `Peek :: Script :: ${item.title}`; 73 + const body = [ 74 + `Script result changed for ${item.title}:`, 75 + `- Old: ${previousValue}`, 76 + `- New: ${res}` 77 + ].join('\n'); 93 78 94 - new Notification({ title, body }).show(); 95 - */ 96 - } 97 - }); 98 - }, item.interval); 99 - _intervals.push(interval); 79 + new Notification({ title, body }).show(); 80 + */ 81 + } 82 + }); 83 + }, item.interval); 84 + _intervals.push(interval); 85 + } 100 86 }); 101 87 }; 102 88 103 89 const updateItem = (item) => { 104 - let items = _store.get('items'); 90 + let items = store.get('items'); 105 91 const idx = items.findIndex(el => el.id == item.id); 106 92 items[idx] = item; 107 - _store.set('items', items); 93 + store.set('items', items); 108 94 }; 109 95 110 96 const init = () => { 111 97 log('init'); 112 98 113 - initStore(defaults); 114 - 115 - const prefs = () => JSON.parse(_store.getItem('prefs')); 116 - const items = () => JSON.parse(_store.getItem('items')); 99 + const prefs = () => store.get(storageKeys.PREFS); 100 + const items = () => store.get(storageKeys.ITEMS); 117 101 118 102 // initialize slides 119 103 if (items().length > 0) { ··· 121 105 } 122 106 }; 123 107 124 - const onChange = (changed, old) => { 125 - log('onChange', changed); 126 - 127 - // TODO only update store if changed 128 - // and re-init 129 - if (changed.prefs) { 130 - _store.setItem('prefs', JSON.stringify(changed.prefs)); 131 - } 132 - 133 - if (changed.items) { 134 - _store.setItem('items', JSON.stringif(changed.items)); 135 - } 136 - }; 137 - 138 108 window.addEventListener('load', init); 139 - 140 - //})();
+18 -2
features/scripts/config.js
··· 1 + const id = 'features/scripts'; 2 + 1 3 const labels = { 2 4 featureType: 'scripts', 3 5 featureDisplay: 'Scripts', ··· 76 78 "type": "string", 77 79 "default": "", 78 80 }, 81 + "enabled": { 82 + "description": "Whether this script is enabled or not.", 83 + "type": "boolean", 84 + "default": false 85 + }, 79 86 }, 80 87 "required": [ "id", "title", "address", "version", "selector", "property", 81 - "interval", "notifyOnChange", "storeHistory" ] 88 + "interval", "notifyOnChange", "storeHistory", "enabled" ] 82 89 }; 83 90 84 91 const listSchema = { ··· 106 113 property: 'textContent', 107 114 interval: 300000, 108 115 storehistory: false, 109 - notifyOnChange: false 116 + notifyOnChange: false, 117 + enabled: false, 110 118 }, 111 119 ] 112 120 }; ··· 118 126 // fields that are view only 119 127 disabled: ['screenEdge'], 120 128 }; 129 + 130 + export { 131 + id, 132 + labels, 133 + schemas, 134 + ui, 135 + defaults 136 + };
+2 -1
features/slides/background.html
··· 7 7 <title>peek:core:background</title> 8 8 </head> 9 9 <body> 10 - <script src="./config.js"></script> 10 + <script type=module src="../utils.js"></script> 11 + <script type=module src="./config.js"></script> 11 12 <script type=module src="./background.js"></script> 12 13 </body> 13 14 </html>
+23 -52
features/slides/background.js
··· 1 1 // slides/slides.js 2 - //(async () => { 3 2 4 - const log = (...args) => { 5 - console.log(labels.featureType, window.app.shortcuts); 6 - window.app.log(labels.featureType, args.join(', ')); 7 - }; 3 + import { id, labels, schemas, ui, defaults } from './config.js'; 4 + import { log as l, openStore } from "../utils.js"; 8 5 9 - //log('slides/background'); 6 + const log = function(...args) { l(id, args); }; 10 7 11 - //import { labels, schemas, ui, defaults } from './config.js'; 8 + log('background'); 12 9 13 - //const debug = window.location.search.indexOf('debug') > 0; 14 - const debug = 1; 10 + const debug = window.app.debug; 15 11 16 - if (debug) { 17 - log('clearing storage') 18 - localStorage.clear(); 19 - } 12 + const store = openStore(id, defaults); 13 + const api = window.app; 20 14 21 - const _store = localStorage; 22 - const _api = window.app; 15 + const storageKeys = { 16 + PREFS: 'prefs', 17 + ITEMS: 'items', 18 + }; 23 19 24 20 const executeItem = (item) => { 25 21 let height = item.height || 600; ··· 89 85 90 86 log(item.screenEdge, x, y); 91 87 88 + const key = `${item.screenEdge}:${item.address}`; 89 + 92 90 //animateSlide(win, item).then(); 93 91 94 92 const params = { ··· 106 104 // slide 107 105 x, 108 106 y, 107 + key, 109 108 }; 110 109 111 - _api.openWindow(params); 112 - }; 113 - 114 - const initStore = (data) => { 115 - const sp = _store.getItem('prefs'); 116 - if (!sp) { 117 - _store.setItem('prefs', JSON.stringify(data.prefs)); 118 - } 119 - 120 - const items = _store.getItem('items'); 121 - if (!items) { 122 - _store.setItem('items', JSON.stringify(data.items)); 123 - } 110 + api.openWindow(params); 124 111 }; 125 112 126 113 const initItems = (prefs, items) => { ··· 128 115 const cmdPrefix = prefs.shortcutKeyPrefix; 129 116 130 117 items.forEach(item => { 131 - const shortcut = `${cmdPrefix}${item.screenEdge}`; 118 + //if (item.enabled == true) { 119 + const shortcut = `${cmdPrefix}${item.screenEdge}`; 132 120 133 - _api.shortcuts.register(shortcut, () => { 134 - executeItem(item); 135 - }); 121 + api.shortcuts.register(shortcut, () => { 122 + executeItem(item); 123 + }); 124 + //} 136 125 }); 137 126 }; 138 127 139 128 const init = () => { 140 129 log('init'); 141 130 142 - initStore(defaults); 143 - 144 - const prefs = () => JSON.parse(_store.getItem('prefs')); 145 - const items = () => JSON.parse(_store.getItem('items')); 131 + const prefs = () => store.get(storageKeys.PREFS); 132 + const items = () => store.get(storageKeys.ITEMS); 146 133 147 134 // initialize slides 148 135 if (items().length > 0) { 149 136 initItems(prefs(), items()); 150 - } 151 - }; 152 - 153 - const onChange = (changed, old) => { 154 - log('onChange', changed); 155 - 156 - // TODO only update store if changed 157 - // and re-init 158 - if (changed.prefs) { 159 - _store.setItem('prefs', JSON.stringify(changed.prefs)); 160 - } 161 - 162 - if (changed.items) { 163 - _store.setItem('items', JSON.stringif(changed.items)); 164 137 } 165 138 }; 166 139 ··· 222 195 }); 223 196 }; 224 197 */ 225 - 226 - //})();
+19 -1
features/slides/config.js
··· 1 + const id = 'features/slides'; 1 2 2 3 const labels = { 3 4 featureType: 'slides', ··· 78 79 "type": "integer", 79 80 "default": 800 80 81 }, 82 + "enabled": { 83 + "description": "Whether this slide is enabled or not.", 84 + "type": "boolean", 85 + "default": false 86 + }, 81 87 }, 82 88 "required": [ "screenEdge", "title", "address", "persistState", "keepLive", "allowSound", 83 - "height", "width" ] 89 + "height", "width", "enabled" ] 84 90 }; 85 91 86 92 const listSchema = { ··· 116 122 allowSound: false, 117 123 height: 600, 118 124 width: 800, 125 + enabled: true, 119 126 }, 120 127 { 121 128 screenEdge: 'Down', ··· 126 133 allowSound: false, 127 134 height: 600, 128 135 width: 800, 136 + enabled: true, 129 137 }, 130 138 { 131 139 screenEdge: 'Left', ··· 136 144 allowSound: false, 137 145 height: 600, 138 146 width: 800, 147 + enabled: true, 139 148 }, 140 149 { 141 150 screenEdge: 'Right', ··· 146 155 allowSound: false, 147 156 height: 600, 148 157 width: 800, 158 + enabled: true, 149 159 }, 150 160 ] 151 161 }; 162 + 163 + export { 164 + id, 165 + labels, 166 + schemas, 167 + ui, 168 + defaults 169 + };
+353
features/utils.js
··· 1 + import {Pane} from './../node_modules/tweakpane/dist/tweakpane.js'; 2 + 3 + const id = 'features/utils'; 4 + 5 + const log = (...args) => { 6 + if (!window.app.debug) { 7 + return; 8 + } 9 + 10 + const aargs = [...args]; 11 + const source = aargs.shift(); 12 + const str = aargs.map(JSON.stringify).join(', '); 13 + //const str = aargs.join(', '); 14 + console.log(str); 15 + window.app.log(source, str); 16 + }; 17 + 18 + const openStore = (prefix, defaults) => { 19 + 20 + //log(id, 'openStore', prefix, (defaults ? Object.keys(defaults) : '')); 21 + 22 + // Simple localStorage abstraction/wrapper 23 + const store = { 24 + set: (k, v) => { 25 + const key = keyify(k); 26 + const value = JSON.stringify(v); 27 + //log(id, 'store.set', key) 28 + localStorage.setItem(key, value); 29 + }, 30 + get: (k) => { 31 + const key = keyify(k); 32 + //log(id, 'store.get', key) 33 + const r = localStorage.getItem(key); 34 + return r ? JSON.parse(r) : null; 35 + }, 36 + clear: () => localStorage.clear() 37 + }; 38 + 39 + if (window.app.debug 40 + && window.app.debugLevel == window.app.debugLevels.FIRST_RUN) { 41 + log(id, 'openStore(): clearing storage') 42 + store.clear(); 43 + } 44 + 45 + //store.clear(); 46 + 47 + // multiple contexts 48 + const keyify = k => `${prefix}+${k}`; 49 + 50 + const initStore = (store, data) => { 51 + Object.keys(data).forEach(k => { 52 + const v = store.get(k); 53 + if (!v) { 54 + //log(id, 'openStore(): init is setting', k, data[k]); 55 + store.set(k, data[k]); 56 + } 57 + }); 58 + }; 59 + 60 + if (defaults) { 61 + //log('UTILS/openStore()', 'initing'); 62 + initStore(store, defaults); 63 + } 64 + 65 + return store; 66 + }; 67 + 68 + const settingsPane = (container, config, labels, schemas, prefs, items, onChange) => { 69 + log('settingsPane()'); 70 + 71 + const paneContainer = document.createElement('div'); 72 + container.appendChild(paneContainer); 73 + 74 + const allowNew = config.allowNew || false; 75 + const disabled = config.disabled || []; 76 + 77 + const pane = new Pane({ 78 + container: paneContainer, 79 + title: labels.featureDisplay 80 + }); 81 + 82 + const update = (all) => { 83 + const paneData = exportPaneData(pane); 84 + 85 + log('folder level update for', labels.featureDisplay); 86 + log('pane data', paneData); 87 + 88 + let updated = {}; 89 + 90 + // TODO: make this right, ugh 91 + if (prefs) { 92 + updated.prefs = paneData.shift(); 93 + } 94 + 95 + // remove "new item" entry if not editable feature 96 + // TODO: make this right 97 + if (!all) { 98 + newData.pop(); 99 + } 100 + 101 + if (paneData.length > 0) { 102 + updated.items = paneData; 103 + } 104 + 105 + onChange(updated); 106 + }; 107 + 108 + // prefs pane 109 + if (prefs) { 110 + const prefsFolder = pane.addFolder({ 111 + title: schemas.prefs.title, 112 + expanded: true 113 + }); 114 + 115 + /* 116 + const btn = pane.addButton({ 117 + title: 'clickme' 118 + }); 119 + btn.on('click', () => { 120 + alert('clickd'); 121 + }); 122 + */ 123 + 124 + const onPrefChange = changed => { 125 + log('initFeaturePane::onPrefChange', changed) 126 + update(!config.allowNew); 127 + }; 128 + 129 + fillPaneFromSchema(prefsFolder, labels, schemas.prefs, prefs, onPrefChange, []); 130 + } 131 + 132 + // add items 133 + if (items) { 134 + //log('adding items panes'); 135 + items.forEach(item => { 136 + const folder = pane.addFolder({ 137 + title: item.title, 138 + expanded: false 139 + }); 140 + 141 + fillPaneFromSchema(folder, labels, schemas.item, item, update, config.disabled); 142 + 143 + // TODO: implement 144 + //folder.addButton({title: labels.testBtn}); 145 + 146 + if (config.allowNew) { 147 + const delBtn = folder.addButton({title: labels.delBtn}); 148 + delBtn.on('click', () => { 149 + pane.remove(folder); 150 + // TODO: https://github.com/cocopon/tweakpane/issues/533 151 + update(); 152 + }); 153 + } 154 + 155 + //folder.on('change', () => update(!config.allowNew)); 156 + }); 157 + } 158 + 159 + /* 160 + if (config.allowNew) { 161 + // add new item entry 162 + const folder = pane.addFolder({ 163 + title: labels.newFolder, 164 + expanded: false 165 + }); 166 + 167 + //fillPaneFromSchema(folder, labels, schema); 168 + fillPaneFromSchema(folder, labels, schema, {}, onChange, disabled); 169 + 170 + const btn = pane.addButton({title: labels.addBtn}); 171 + 172 + // handle adds of new entries 173 + btn.on('click', () => { 174 + update(true); 175 + }); 176 + } 177 + */ 178 + }; 179 + 180 + const fillPaneFromSchema = (pane, labels, schema, data, onChange, disabled) => { 181 + const props = schema.properties; 182 + 183 + Object.keys(props).forEach(k => { 184 + // TODO: unhack 185 + if (k == 'settingsAddress') { 186 + log('sa', data[k], data); 187 + //log('settingsAddress', k, 'v', data[k]); 188 + const btn = pane.addButton({title: k}); 189 + 190 + btn.on('click', () => { 191 + console.log('settings click!') 192 + const address = data[k]; 193 + 194 + const params = { 195 + debug: window.app.debug, 196 + feature: labels.featureType, 197 + file: address, 198 + }; 199 + 200 + window.app.publish('open', { 201 + feature: 'feature/cmd/settings' 202 + }); 203 + }); 204 + } 205 + else { 206 + // schema for property 207 + const s = props[k]; 208 + 209 + // value (or default) 210 + const v = 211 + (data && data.hasOwnProperty(k)) 212 + ? data[k] 213 + : props[k].default; 214 + 215 + const params = {}; 216 + const opts = {}; 217 + 218 + // dedecimalize 219 + if (s.type == 'integer') { 220 + opts.step = 1; 221 + } 222 + 223 + // disabled fields 224 + if (disabled.includes(k)) { 225 + opts.disabled = true; 226 + } 227 + 228 + params[k] = v; 229 + 230 + const input = pane.addBinding(params, k, opts); 231 + 232 + // TODO: consider inline state management 233 + input.on('change', ev => { 234 + // TODO: validate against schema 235 + log('inline field change', k, ev.value) 236 + data[k] = ev.value; 237 + onChange(data) 238 + }); 239 + } 240 + }); 241 + }; 242 + 243 + /* 244 + const paneGenerator = (pane, labels, schema, data, onChange, disabled) => { 245 + const schemaKeys = Object.keys(schema.properties); 246 + const dataKeys = data ? Object.keys(data): []; 247 + const keys = shemaKeys.append(dataKeys); 248 + 249 + const inSchema = (data && data.hasOwnProperty(k)) 250 + 251 + Object.keys(data).forEach(k => { 252 + // TODO: unhack 253 + if (k == 'settingsAddress') { 254 + log('sa', data[k], data); 255 + //log('settingsAddress', k, 'v', data[k]); 256 + const btn = pane.addButton({title: k}); 257 + 258 + btn.on('click', () => { 259 + console.log('settings click!') 260 + const address = data[k]; 261 + 262 + const params = { 263 + debug: window.app.debug, 264 + feature: labels.featureType, 265 + file: address, 266 + }; 267 + 268 + window.app.publish('open', { 269 + feature: 'feature/cmd/settings' 270 + }); 271 + }); 272 + } 273 + else { 274 + // schema for property 275 + const s = props[k]; 276 + 277 + // value (or default) 278 + const v = 279 + (data && data.hasOwnProperty(k)) 280 + ? data[k] 281 + : props[k].default; 282 + 283 + const params = {}; 284 + const opts = {}; 285 + 286 + // dedecimalize 287 + if (s.type == 'integer') { 288 + opts.step = 1; 289 + } 290 + 291 + // disabled fields 292 + if (disabled.includes(k)) { 293 + opts.disabled = true; 294 + } 295 + 296 + params[k] = v; 297 + 298 + const input = pane.addBinding(params, k, opts); 299 + 300 + // TODO: consider inline state management 301 + input.on('change', ev => { 302 + // TODO: validate against schema 303 + log('inline field change', k, ev.value) 304 + data[k] = ev.value; 305 + onChange(data) 306 + }); 307 + } 308 + }); 309 + }; 310 + */ 311 + 312 + // TODO: fuckfuckfuck 313 + // https://github.com/cocopon/tweakpane/issues/431 314 + const exportPaneData = pane => { 315 + const val = pane.exportState(); 316 + /* 317 + const children = pane.rackApi_.children.filter(p => p.children); 318 + const val = children.map(paneChild => { 319 + return paneChild.children.reduce((obj, field) => { 320 + const k = field.label; 321 + if (!k) { 322 + return obj; 323 + } 324 + 325 + let v = null; 326 + 327 + const input = field.element.querySelector('.tp-txtv_i') 328 + if (input) { 329 + v = input.value; 330 + } 331 + 332 + const checkbox = field.element.querySelector('.tp-ckbv_i'); 333 + if (checkbox) { 334 + v = checkbox.checked; 335 + } 336 + 337 + // TODO: drop fields not supported for now 338 + if (v != undefined) { 339 + obj[k] = v; 340 + } 341 + 342 + return obj; 343 + }, {}); 344 + }); 345 + */ 346 + return val; 347 + }; 348 + 349 + export { 350 + log, 351 + openStore, 352 + settingsPane 353 + };
+149 -48
index.js
··· 4 4 console.log('main'); 5 5 6 6 const DEBUG = process.env.DEBUG; 7 + const DEBUG_LEVELS = { 8 + BASIC: 1, 9 + FIRST_RUN: 2 10 + }; 11 + const DEBUG_LEVEL = DEBUG_LEVELS.BASIC; 12 + //const DEBUG_LEVEL = DEBUG_LEVELS.FIRST_RUN; 7 13 8 14 // Modules to control application life and create native browser window 9 15 const { ··· 28 34 const webCoreAddress = 'features/core/background.html'; 29 35 30 36 const p = process.env.PROFILE; 31 - console.log('env prof?', p, p != undefined, typeof p, p.length) 37 + console.log('env prof?', p, p != undefined, typeof p) 32 38 const profileIsLegit = p => p != undefined && typeof p == 'string' && p.length > 0; 33 39 34 40 const PROFILE = ··· 124 130 } 125 131 }); 126 132 133 + // ***** Caches ***** 134 + 135 + const windowCache = { 136 + cache: [], 137 + add: entry => windowCache.cache.push(entry), 138 + byId: id => windowCache.cache.find(w => w.id == id), 139 + byKey: key => windowCache.cache.find(w => w.key == key), 140 + hasKey: key => windowCache.byKey(key) != undefined, 141 + indexOfKey: key => windowCache.cache.findIndex(w => w.key == key), 142 + removeByKey: key => windowCache.cache.splice(windowCache.indexOfKey(key), 1) 143 + }; 144 + 145 + const _shortcuts = {}; 146 + 147 + const _prefs = {}; 148 + 149 + // ***** pubsub ***** 150 + 151 + const pubsub = (() => { 152 + 153 + const topics = new Map(); 154 + 155 + return { 156 + publish: (topic, msg) => { 157 + if (topics.has(topic)) { 158 + topics.get(topic).forEach(subscriber => { 159 + subscriber(msg); 160 + }); 161 + } 162 + }, 163 + subscribe: (topic, cb) => { 164 + if (!topics.has(topic)) { 165 + topics.set(topic, [cb]); 166 + } 167 + else { 168 + const subscribers = topics.get(topic); 169 + subscribers.push(cb); 170 + topics.set(topic, subscribers); 171 + } 172 + }, 173 + }; 174 + 175 + })(); 176 + 127 177 // ***** Tray ***** 128 178 129 179 const ICON_RELATIVE_PATH = 'assets/icons/AppIcon.appiconset/Icon-App-20x20@2x.png'; ··· 136 186 _tray = new Tray(ICON_PATH); 137 187 _tray.setToolTip(labels.tray.tooltip); 138 188 _tray.on('click', () => { 139 - //features.settings.open(); 189 + pubsub.publish('open', { 190 + feature: _prefs['features/core'].startupFeature 191 + }); 140 192 }); 141 193 } 142 194 return _tray; 143 195 }; 144 196 145 - // ***** Caches ***** 146 - 147 - const _windows = new Set(); 148 - 149 - const windowCache = { 150 - cache: [], 151 - add: entry => windowCache.cache.push(entry), 152 - byId: id => windowCache.cache.find(w => w.id == id), 153 - byKey: key => windowCache.cache.find(w => w.key == key) 154 - }; 155 - 156 - const _shortcuts = {}; 197 + // ***** init ***** 157 198 158 199 // Electron app load 159 200 const onReady = () => { 160 201 console.log('onReady'); 161 202 162 - // keep app out of dock and tab switcher 163 - if (app.dock) { 164 - app.dock.hide(); 165 - } 166 - 167 - // initialize system tray 168 - // mostly just useful to know if the app is running or not 169 - initTray(); 170 - 171 203 // init web core 172 204 openWindow({ 173 205 feature: 'Core', 174 206 file: webCoreAddress, 175 207 show: true, 176 208 keepLive: true, 209 + keepVisible: true, 177 210 debug: DEBUG 178 211 }) 179 212 180 - /* 181 - // open settings on startup for now 182 - if (BrowserWindow.getAllWindows().length === 0) { 183 - features.settings.open(); 184 - } 185 - */ 213 + pubsub.subscribe('prefs', msg => { 214 + // cache all prefs 215 + _prefs[msg.feature] = msg.prefs; 216 + 217 + // show/hide in dock and tab switcher 218 + if (app.dock && (msg.prefs.showInDockAndSwitcher == false || !DEBUG)) { 219 + app.dock.hide(); 220 + } 221 + 222 + // initialize system tray 223 + if (msg.prefs.showTrayIcon == true) { 224 + initTray(); 225 + } 226 + 227 + // open default app 228 + pubsub.publish('open', { 229 + feature: msg.prefs.startupFeature 230 + }); 231 + }); 186 232 233 + // eh, for helpers really 187 234 registerShortcut('Option+q', onQuit); 188 235 }; 189 236 ··· 207 254 208 255 ipcMain.on('openwindow', (ev, msg) => { 209 256 openWindow(msg.params, output => { 210 - if (msg.replyTopic) { 211 - ev.reply(msg.replyTopic, { output }); 212 - } 257 + ev.reply(msg.replyTopic, output); 213 258 }); 214 259 }); 215 260 216 261 // generic dispatch - messages only from trusted code (💀) 217 - ipcMain.on('sendmessage', (ev, msg) => { 218 - console.log('sendmsg', msg); 262 + ipcMain.on('publish', (ev, msg) => { 263 + //console.log('publish', msg); 264 + 265 + pubsub.publish(msg.topic, msg.data); 266 + }); 267 + 268 + ipcMain.on('subscribe', (ev, msg) => { 269 + //console.log('subscribe', msg); 270 + 271 + pubsub.subscribe(msg.topic, data => { 272 + ev.reply(msg.replyTopic, data); 273 + }); 219 274 }); 220 275 221 276 // ipc ESC handler 277 + // close focused window on Escape 222 278 ipcMain.on('esc', (ev, title) => { 223 - console.log('esc'); 279 + console.log('index.js: ESC'); 280 + // XXX remove 281 + return; 224 282 225 283 const fwin = BrowserWindow.getFocusedWindow(); 226 284 const entry = windowCache.byId(fwin.id); 285 + // focused window is managed by me 286 + // so hide it instead of actually closing it 227 287 if (entry) { 228 288 BrowserWindow.fromId(entry.id).hide(); 289 + console.log('index.js: ESC: hiding focused content window'); 229 290 } 291 + // focused window is me 230 292 else if (!fwin.isDestroyed()) { 231 293 fwin.close(); 294 + console.log('index.js: ESC: closing focused window, is not in cache and not destroyed'); 232 295 } 233 296 }); 234 297 ··· 268 331 // TODO: need to figure out a better approach 269 332 const show = params.hasOwnProperty('show') ? params.show : true; 270 333 334 + // keep visible 335 + const keepVisible = params.hasOwnProperty('keepVisible') ? params.keepVisible : false; 336 + 337 + if (!params.address && !params.file) { 338 + console.error('openWindow: neither address nor file!'); 339 + return; 340 + } 341 + 271 342 // cache key 343 + // window keys can be provided by features. 344 + // eg for different slides that have same url, don't want to re-use window. 345 + // 346 + // otherwise use a simple concat 347 + // 272 348 // TODO: need to figure out a better approach 273 - const key = params.feature + (params.address || params.file); 349 + const key = params.key ? params.key : (params.feature + (params.address || params.file)); 274 350 275 - if (params.keepLive == true) { 351 + if (windowCache.hasKey(key)) { 352 + console.log('REUSING WINDOW for ', key) 276 353 const entry = windowCache.byKey(key); 277 354 if (entry != undefined) { 278 355 const win = BrowserWindow.fromId(entry.id); ··· 324 401 winPreferences.center = true; 325 402 } 326 403 327 - console.log('final params', winPreferences.x, winPreferences.y); 404 + console.log('final dimension params (x, y, center)', winPreferences.x, winPreferences.y, winPreferences.center); 328 405 329 406 let win = new BrowserWindow(winPreferences); 330 407 ··· 332 409 if (params.keepLive == true) { 333 410 windowCache.add({ 334 411 id: win.id, 335 - key: key 412 + key, 413 + params 336 414 }); 337 415 } 338 416 339 417 // TODO: make configurable 340 418 const onGoAway = () => { 341 - if (params.keepLive) { 342 - //console.log('win.onGoAway(): hiding ', params.address); 343 - win.hide(); 419 + if (params.keepLive == true) { 420 + if (params.keepVisible == false) { 421 + console.log('win.onGoAway(): hiding ', params.address); 422 + win.hide(); 423 + } 344 424 } 345 425 else { 346 - //console.log('win.onGoAway(): destroying ', params.address); 426 + console.log('win.onGoAway(): destroying ', params.address); 347 427 win.destroy(); 348 428 } 349 429 } 350 - win.on('blur', onGoAway); 430 + 431 + // don't do this in debug mode, devtools steals focus 432 + // and closes everything 😐 433 + // TODO: fix 434 + // TODO: should be configurable behavior 435 + if (!DEBUG) { 436 + win.on('blur', onGoAway); 437 + } 438 + 351 439 win.on('close', onGoAway); 352 440 353 441 win.on('closed', () => { 354 - //console.log('win.on(closed): deleting ', key, ' for ', params.address); 355 - _windows.delete(win); 442 + console.log('win.on(closed): deleting ', key, ' for ', params.address); 443 + windowCache.removeByKey(key); 356 444 win = null; 357 445 }); 358 446 359 - if (params.debug) { 447 + //if (params.debug) { 448 + // TODO: why not working for core background page? 449 + //win.webContents.openDevTools({ mode: 'detach' }); 360 450 win.webContents.openDevTools(); 361 - } 451 + //} 362 452 363 453 if (params.address) { 364 454 win.loadURL(params.address); ··· 366 456 else { 367 457 console.error('openWindow: neither address nor file!'); 368 458 } 459 + 460 + /* 461 + win.webContents.on('keyup', async () => { 462 + console.log('main: keyup') 463 + }); 464 + */ 465 + 466 + //const escScript = "window.addEventListener('keyup', e => window.close())"; 467 + //win.webContents.executeJavaScript(escScript); 369 468 370 469 //win.webContents.send('window', { type: labels.featureType, id: win.id}); 371 470 //broadcastToWindows('window', { type: labels.featureType, id: win.id}); ··· 394 493 } 395 494 }; 396 495 496 + /* 397 497 // send message to all windows 398 498 const broadcastToWindows = (topic, msg) => { 399 499 _windows.forEach(win => { 400 500 win.webContents.send(topic, msg); 401 501 }); 402 502 }; 503 + */ 403 504 404 505 // Quit when all windows are closed, except on macOS. There, it's common 405 506 // for applications and their menu bar to stay active until the user quits
+15 -14
package.json
··· 6 6 "author": "dietrich ayala", 7 7 "license": "MIT", 8 8 "devDependencies": { 9 - "@electron-forge/cli": "^6.1.1", 10 - "@electron-forge/maker-deb": "^6.0.5", 11 - "@electron-forge/maker-rpm": "^6.0.5", 12 - "@electron-forge/maker-squirrel": "^6.0.5", 13 - "@electron-forge/maker-zip": "^6.0.5", 14 - "electron": "^23.1.2", 15 - "electron-is-dev": "^2.0.0", 16 - "electron-rebuild": "^3.2.9", 17 - "electron-reload": "^2.0.0-alpha.1", 18 - "electron-reloader": "^1.2.3" 9 + "@electron-forge/cli": "7.2.0", 10 + "@electron-forge/maker-deb": "7.2.0", 11 + "@electron-forge/maker-rpm": "7.2.0", 12 + "@electron-forge/maker-squirrel": "7.2.0", 13 + "@electron-forge/maker-zip": "7.2.0", 14 + "electron": "27.1.3", 15 + "electron-is-dev": "2.0.0", 16 + "electron-rebuild": "3.2.9", 17 + "electron-reload": "2.0.0-alpha.1", 18 + "electron-reloader": "1.2.3" 19 19 }, 20 20 "dependencies": { 21 - "electron-squirrel-startup": "^1.0.0", 22 - "electron-store": "^8.1.0", 23 - "electron-unhandled": "^4.0.1", 24 - "tweakpane": "^3.1.7" 21 + "electron-squirrel-startup": "1.0.0", 22 + "electron-store": "8.1.0", 23 + "electron-unhandled": "4.0.1", 24 + "tweakpane": "4.0.1" 25 25 }, 26 26 "scripts": { 27 27 "start": "electron-forge start", 28 + "debug": "DEBUG=1 electron-forge start", 28 29 "package": "electron-forge package", 29 30 "make": "electron-forge make" 30 31 }
+44 -38
preload.js
··· 5 5 6 6 const src = 'preload'; 7 7 8 + const DEBUG = process.env.DEBUG; 9 + const DEBUG_LEVELS = { 10 + BASIC: 1, 11 + FIRST_RUN: 2 12 + }; 13 + 14 + const DEBUG_LEVEL = DEBUG_LEVELS.BASIC; 15 + //const DEBUG_LEVEL = DEBUG_LEVELS.FIRST_RUN; 16 + 8 17 const log = (source, text) => { 9 18 ipcRenderer.send('console', { 10 19 source, ··· 12 21 }); 13 22 }; 14 23 15 - /* 16 - ipcRenderer.on('window', (ev, msg) => { 17 - console.log('preload: onwindow', msg); 18 - const { type, id, data } = msg; 19 - if (type == 'main') { 20 - handleMainWindow(); 21 - } 22 - }); 23 - */ 24 - 25 24 // all visible window types close on escape 26 25 window.addEventListener('keyup', e => { 26 + log('preload', 'keyup', e.key, window.location) 27 27 if (e.key == 'Escape') { 28 28 ipcRenderer.send('esc', ''); 29 29 } 30 30 }); 31 + 32 + const addEscListener = () => { 33 + window.addEventListener('keyup', e => { 34 + log('preload', 'keyup', e.key, window.location) 35 + if (e.key == 'Escape') { 36 + ipcRenderer.send('esc', ''); 37 + } 38 + }); 39 + }; 31 40 32 41 let api = {}; 33 42 ··· 75 84 }; 76 85 77 86 api.log = log; 87 + api.debug = DEBUG; 88 + api.debugLevels = DEBUG_LEVELS; 89 + api.debugLevel = DEBUG_LEVEL; 78 90 79 - /* 80 - api.onConfigChange = callback => { 91 + api.publish = (topic, msg) => { 92 + //log(src, 'publish', topic) 93 + 81 94 // noop if not an internal app file 95 + // TODO: hmmm 82 96 const isMain = window.location.protocol == 'file:'; 83 97 if (!isMain) { 84 98 return; 85 99 } 86 100 87 - ipcRenderer.on('configchange', (ev, msg) => { 88 - callback(msg); 101 + ipcRenderer.send('publish', { 102 + topic, 103 + data: msg, 89 104 }); 90 105 }; 91 106 92 - api.getConfig = new Promise((resolve, reject) => { 107 + api.subscribe = (topic, callback) => { 108 + //log(src, 'subscribe', topic) 109 + 93 110 // noop if not an internal app file 111 + // TODO: hmmm 94 112 const isMain = window.location.protocol == 'file:'; 95 - if (!isMain) { 96 - return; 97 - } 98 113 99 - // TODO: race potential 100 - ipcRenderer.once('config', (ev, msg) => { 101 - resolve(msg); 102 - }); 103 - ipcRenderer.send('getconfig', {isMain}); 104 - }); 105 - 106 - api.setConfig = cfg => { 107 - // noop if not an internal app file 108 - const isMain = window.location.protocol == 'file:'; 109 114 if (!isMain) { 115 + // TODO: error 110 116 return; 111 117 } 112 118 113 - ipcRenderer.send('setconfig', cfg); 114 - }; 119 + const replyTopic = `${Date.now()}`; 115 120 116 - api.sendMessage = msg => { 117 - // noop if not an internal app file 118 - const isMain = window.location.protocol == 'file:'; 119 - if (!isMain) { 120 - return; 121 - } 121 + ipcRenderer.send('subscribe', { 122 + topic, 123 + replyTopic 124 + }); 122 125 123 - ipcRenderer.send('sendmessage', msg); 126 + ipcRenderer.on(replyTopic, (ev, msg) => { 127 + if (callback) { 128 + callback(msg); 129 + } 130 + }); 124 131 }; 125 - */ 126 132 127 133 contextBridge.exposeInMainWorld('app', api); 128 134