diff --git a/app/components/tool-pallete/toolpallete.element.js b/app/components/tool-pallete/toolpallete.element.js
index 068ac762..e8e262a9 100644
--- a/app/components/tool-pallete/toolpallete.element.js
+++ b/app/components/tool-pallete/toolpallete.element.js
@@ -43,9 +43,9 @@ export default class ToolPallete extends HTMLElement {
this.$shadow = this.attachShadow({mode: 'open'})
this.$shadow.innerHTML = this.render()
- this.selectorEngine = Selectable()
- this.colorPicker = ColorPicker(this.$shadow, this.selectorEngine)
- provideSelectorEngine(this.selectorEngine)
+ this._selectorEngine = Selectable()
+ this.colorPicker = ColorPicker(this.$shadow, this._selectorEngine)
+ provideSelectorEngine(this._selectorEngine)
}
connectedCallback() {
@@ -62,7 +62,7 @@ export default class ToolPallete extends HTMLElement {
disconnectedCallback() {
this.deactivate_feature()
- this.selectorEngine.disconnect()
+ this._selectorEngine.disconnect()
hotkeys.unbind(
Object.keys(this.toolbar_model).reduce((events, key) =>
events += ',' + key, ''))
@@ -135,9 +135,9 @@ export default class ToolPallete extends HTMLElement {
}
text() {
- this.selectorEngine.onSelectedUpdate(EditText)
+ this._selectorEngine.onSelectedUpdate(EditText)
this.deactivate_feature = () =>
- this.selectorEngine.removeSelectedCallback(EditText)
+ this._selectorEngine.removeSelectedCallback(EditText)
}
align() {
@@ -154,15 +154,15 @@ export default class ToolPallete extends HTMLElement {
hueshift() {
let feature = HueShift(this.colorPicker)
- this.selectorEngine.onSelectedUpdate(feature.onNodesSelected)
+ this._selectorEngine.onSelectedUpdate(feature.onNodesSelected)
this.deactivate_feature = () => {
- this.selectorEngine.removeSelectedCallback(feature.onNodesSelected)
+ this._selectorEngine.removeSelectedCallback(feature.onNodesSelected)
feature.disconnect()
}
}
inspector() {
- this.deactivate_feature = MetaTip(this.selectorEngine)
+ this.deactivate_feature = MetaTip(this._selectorEngine)
}
accessibility() {
@@ -179,9 +179,9 @@ export default class ToolPallete extends HTMLElement {
position() {
let feature = Position()
- this.selectorEngine.onSelectedUpdate(feature.onNodesSelected)
+ this._selectorEngine.onSelectedUpdate(feature.onNodesSelected)
this.deactivate_feature = () => {
- this.selectorEngine.removeSelectedCallback(feature.onNodesSelected)
+ this._selectorEngine.removeSelectedCallback(feature.onNodesSelected)
feature.disconnect()
}
}
@@ -189,6 +189,10 @@ export default class ToolPallete extends HTMLElement {
get activeTool() {
return this.active_tool.dataset.tool
}
+
+ get selectorEngine() {
+ return this._selectorEngine
+ }
}
customElements.define('tool-pallete', ToolPallete)
\ No newline at end of file
diff --git a/extension/manifest.json b/extension/manifest.json
index 6afc32a5..5190ac46 100644
--- a/extension/manifest.json
+++ b/extension/manifest.json
@@ -1,11 +1,13 @@
{
"name": "PixelBug",
- "version": "0.1.5",
+ "version": "0.1.51",
"description": "Hacking for Designers",
"manifest_version": 2,
"icons": { "128": "icons/128.png" },
+ "devtools_page": "panel/index.html",
"permissions": [
- "activeTab"
+ "activeTab",
+ "contextMenus"
],
"background": {
"persistent": true,
diff --git a/extension/pane/pixelbug/index.html b/extension/pane/pixelbug/index.html
new file mode 100644
index 00000000..aae0e585
--- /dev/null
+++ b/extension/pane/pixelbug/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Hello from PixelBug
+
+
\ No newline at end of file
diff --git a/extension/pane/sizing/index.html b/extension/pane/sizing/index.html
new file mode 100644
index 00000000..d7de2b7e
--- /dev/null
+++ b/extension/pane/sizing/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Calculated/authored/natural/responsive
+
+
\ No newline at end of file
diff --git a/extension/pane/styleguide/index.html b/extension/pane/styleguide/index.html
new file mode 100644
index 00000000..30ec3486
--- /dev/null
+++ b/extension/pane/styleguide/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ fonts/colors/spacing/etc
+
+
\ No newline at end of file
diff --git a/extension/panel/demo.png b/extension/panel/demo.png
new file mode 100644
index 00000000..0f9f3ae0
Binary files /dev/null and b/extension/panel/demo.png differ
diff --git a/extension/panel/index.html b/extension/panel/index.html
new file mode 100644
index 00000000..e7d75eb4
--- /dev/null
+++ b/extension/panel/index.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extension/panel/index.js b/extension/panel/index.js
new file mode 100644
index 00000000..a2ec9a6c
--- /dev/null
+++ b/extension/panel/index.js
@@ -0,0 +1,20 @@
+// init panels
+chrome.devtools.panels.create('Design', null, 'panel/panel.html', design_panel => {
+ // design_panel.onShown.addListener(e =>
+ // post({action: 'show-toolbar'}))
+})
+
+chrome.devtools.panels.elements.createSidebarPane('PixelBug', sidebar => {
+ sidebar.setPage('../pane/pixelbug/index.html')
+ sidebar.setHeight('8ex')
+})
+
+chrome.devtools.panels.elements.createSidebarPane('Style Guide', sidebar => {
+ sidebar.setPage('../pane/styleguide/index.html')
+ sidebar.setHeight('8ex')
+})
+
+chrome.devtools.panels.elements.createSidebarPane('Sizing', sidebar => {
+ sidebar.setPage('../pane/sizing/index.html')
+ sidebar.setHeight('8ex')
+})
\ No newline at end of file
diff --git a/extension/panel/panel.bundle.css b/extension/panel/panel.bundle.css
new file mode 100644
index 00000000..d3e48bf1
--- /dev/null
+++ b/extension/panel/panel.bundle.css
@@ -0,0 +1,89 @@
+:root {
+ --bg: hsl(0,0%,100%);
+ --bg-hover: hsl(0,0%,95%);
+ --dot: hsl(0,0%,90%);
+ --text: hsl(0,0%,10%);
+}
+
+body {
+ margin: 0;
+ background-color: hsl(0,0%,100%);
+ background-color: var(--bg);
+ color: hsl(0,0%,10%);
+ color: var(--text);
+ background-image: -webkit-repeating-radial-gradient(
+ center center,
+ rgba(0,0,0,.05),
+ rgba(0,0,0,.05) 1px,
+ transparent 1px,
+ transparent 100%
+ );
+ background-size: 0.5rem 0.5rem
+}
+
+body.dark {
+ --bg: hsl(0,0%,15%);
+ --bg-hover: hsl(0,0%,20%);
+ --dot: hsl(0,0%,25%);
+ --text: hsl(0,0%,80%);
+ }
+
+img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+#layers {
+ background-color: hsl(0,0%,100%);
+ background-color: var(--bg);
+ color: hsl(0,0%,10%);
+ color: var(--text);
+ padding: 0.5rem;
+}
+
+ol {
+ padding: 0;
+ margin: 0;
+ list-style-type: none
+}
+
+ol > li,
+ ol li > .layer {
+ margin-left: 0.8rem;
+ }
+
+details > summary:focus {
+ /*outline-color: hotpink;*/
+ outline: none;
+ }
+
+details > summary::-webkit-details-marker {
+ color: hotpink;
+ }
+
+details .layer {
+ padding: 0.5rem 1rem;
+ border-radius: 1rem
+}
+
+details .layer:hover {
+ background: hsl(0,0%,95%);
+ background: var(--bg-hover);
+ cursor: pointer;
+ }
+
+.layer {
+ display: inline-flex;
+ align-items: center;
+}
+
+details [icon] {
+ width: 0.5rem;
+ height: 0.5rem;
+ border-radius: 50%;
+ background-color: hsl(0,0%,90%);
+ background-color: var(--dot);
+ display: inline-block;
+ margin-right: 0.5rem;
+}
\ No newline at end of file
diff --git a/extension/panel/panel.css b/extension/panel/panel.css
new file mode 100644
index 00000000..c57a50ad
--- /dev/null
+++ b/extension/panel/panel.css
@@ -0,0 +1,85 @@
+:root {
+ --bg: hsl(0,0%,100%);
+ --bg-hover: hsl(0,0%,95%);
+ --dot: hsl(0,0%,90%);
+ --text: hsl(0,0%,10%);
+}
+
+body {
+ margin: 0;
+ background-color: var(--bg);
+ color: var(--text);
+ background-image: -webkit-repeating-radial-gradient(
+ center center,
+ rgba(0,0,0,.05),
+ rgba(0,0,0,.05) 1px,
+ transparent 1px,
+ transparent 100%
+ );
+ background-size: 0.5rem 0.5rem;
+
+ &.dark {
+ --bg: hsl(0,0%,15%);
+ --bg-hover: hsl(0,0%,20%);
+ --dot: hsl(0,0%,25%);
+ --text: hsl(0,0%,80%);
+ }
+}
+
+img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+#layers {
+ background-color: var(--bg);
+ color: var(--text);
+ padding: 0.5rem;
+}
+
+ol {
+ padding: 0;
+ margin: 0;
+ list-style-type: none;
+
+ & > li,
+ & li > .layer {
+ margin-left: 0.8rem;
+ }
+}
+
+details > summary {
+ &:focus {
+ /*outline-color: hotpink;*/
+ outline: none;
+ }
+
+ &::-webkit-details-marker {
+ color: hotpink;
+ }
+}
+
+details .layer {
+ padding: 0.5rem 1rem;
+ border-radius: 1rem;
+
+ &:hover {
+ background: var(--bg-hover);
+ cursor: pointer;
+ }
+}
+
+.layer {
+ display: inline-flex;
+ align-items: center;
+}
+
+details [icon] {
+ width: 0.5rem;
+ height: 0.5rem;
+ border-radius: 50%;
+ background-color: var(--dot);
+ display: inline-block;
+ margin-right: 0.5rem;
+}
\ No newline at end of file
diff --git a/extension/panel/panel.html b/extension/panel/panel.html
new file mode 100644
index 00000000..ea89c50b
--- /dev/null
+++ b/extension/panel/panel.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extension/panel/panel.js b/extension/panel/panel.js
new file mode 100644
index 00000000..a92a6ad5
--- /dev/null
+++ b/extension/panel/panel.js
@@ -0,0 +1,54 @@
+import Channel from '../utils/channel.js'
+
+const channel_name = 'design-panel'
+const Pipe = new Channel({
+ name: channel_name,
+ model: {
+ tabId: chrome.devtools.inspectedWindow.tabId,
+ src_channel: channel_name,
+ target_channel: 'design-artboard',
+ }
+})
+
+document.body.classList.add(
+ chrome.devtools.panels.themeName)
+
+Pipe.port.onMessage.addListener((message, sender) => {
+ console.log(`${channel_name} recieved port message`, message, sender)
+
+ if (message.action == 'selected')
+ render_layers(message.payload)
+})
+
+Pipe.message.onMessage.addListener((request, sender, sendResponse) => {
+ console.log(`${channel_name} onMessage`, request)
+})
+
+// todo: render node props then children
+const recurse_nodes = ({nodeName, className, id, children}) => `
+
+
+
+ ${nodeName}${id ? '#' + id : ''} ${className}
+
+
+ ${children.map(node =>
+ node.children.length
+ ? `- ${recurse_nodes(node)}
`
+ : `
+ -
+
+
+ ${node.nodeName}${node.id ? '#' + node.id : ''} ${node.className}
+
+
+ `
+ ).join('')}
+
+
+`
+
+const render_layers = nodes => {
+ console.log('show in dom', nodes)
+ document.getElementById('layers').innerHTML = nodes.map(recurse_nodes).join('')
+}
\ No newline at end of file
diff --git a/extension/pixelbug.js b/extension/pixelbug.js
index 94dcdda1..668d3486 100644
--- a/extension/pixelbug.js
+++ b/extension/pixelbug.js
@@ -1,29 +1,108 @@
-let state = {
+// setup ports
+const connections = {}
+
+chrome.runtime.onConnect.addListener(port => {
+ console.log('pixelbug onConnect', port)
+
+ if (port.name != 'design-panel' && port.name != 'design-artboard') {
+ console.warn(port)
+ return
+ }
+
+ port.onMessage.addListener(message => {
+ console.log('pixelbug port.onMessage', message, port)
+
+ const tabId = port.sender.tab
+ ? port.sender.tab.id
+ : message.tabId
+
+ if (!tabId) return console.log('missing tabId', message, port)
+
+ if (message.data.action == 'register') {
+ if (!connections[tabId])
+ connections[tabId] = {}
+
+ connections[tabId][port.name] = port
+ console.info('pixelbug register success', connections)
+ return
+ }
+
+ if (message.data.action == 'show-toolbar') {
+ toggleIn({id:tabId})
+ }
+
+ if (message.target_channel) {
+ const conn = connections[tabId][message.target_channel]
+ conn && conn.postMessage(message.data)
+ }
+ else
+ console.warn('pixelbug missing message target')
+ })
+
+ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
+ console.log('pixelbug runtime.onMessage', request, sender)
+
+ // Messages from content scripts should have sender.tab set.
+ // The are all relayed to the 'panel' connection.
+ if (request.target == 'design-artboard' && request.tabId)
+ chrome.tabs.sendMessage(request.tabId, request)
+
+ return true
+ })
+})
+
+
+// add/remove pixelbug
+const state = {
loaded: {},
injected: {},
}
-chrome.browserAction.onClicked.addListener(function(tab) {
- if (state.loaded[tab.id] && state.injected[tab.id]) {
- chrome.tabs.executeScript(tab.id, { file: 'toolbar/eject.js' })
- state.injected[tab.id] = false
+const toggleIn = ({id:tab_id}) => {
+
+ // toggle out: it's currently loaded and injected
+ if (state.loaded[tab_id] && state.injected[tab_id]) {
+ chrome.tabs.executeScript(tab_id, { file: 'toolbar/eject.js' })
+ state.injected[tab_id] = false
}
- else if (state.loaded[tab.id] && !state.injected[tab.id]) {
- chrome.tabs.executeScript(tab.id, { file: 'toolbar/inject.js' })
- state.injected[tab.id] = true
+
+ // toggle in: it's loaded and needs injected
+ else if (state.loaded[tab_id] && !state.injected[tab_id]) {
+ chrome.tabs.executeScript(tab_id, { file: 'toolbar/content.js' })
+ state.injected[tab_id] = true
}
+
+ // fresh start in tab
else {
- chrome.tabs.insertCSS(tab.id, { file: 'toolbar/bundle.css' })
- chrome.tabs.executeScript(tab.id, { file: 'web-components.polyfill.js' })
- chrome.tabs.executeScript(tab.id, { file: 'toolbar/bundle.js' })
- chrome.tabs.executeScript(tab.id, { file: 'toolbar/inject.js' })
+ chrome.tabs.insertCSS(tab_id, { file: 'toolbar/bundle.css' })
+ chrome.tabs.executeScript(tab_id, { file: 'web-components.polyfill.js' })
+ chrome.tabs.executeScript(tab_id, { file: 'toolbar/bundle.js' })
+ chrome.tabs.executeScript(tab_id, { file: 'toolbar/content.js' })
- state.loaded[tab.id] = true
- state.injected[tab.id] = true
+ state.loaded[tab_id] = true
+ state.injected[tab_id] = true
}
chrome.tabs.onUpdated.addListener(function(tabId) {
- if (tabId === tab.id)
+ if (tabId === tab_id)
state.loaded[tabId] = false
})
-})
\ No newline at end of file
+}
+
+chrome.browserAction.onClicked.addListener(toggleIn)
+
+chrome.contextMenus.create({
+ title: 'Inspect'
+})
+
+chrome.contextMenus.onClicked.addListener((menuInfo, tab) => {
+ toggleIn(tab)
+
+ chrome.tabs.query({active: true, currentWindow: true}, function([tab]) {
+ tab && chrome.tabs.sendMessage(tab.id, {
+ action: 'toolSelected',
+ params: 'inspector',
+ })
+ })
+})
+
diff --git a/extension/toolbar/content.js b/extension/toolbar/content.js
new file mode 100644
index 00000000..9e65d402
--- /dev/null
+++ b/extension/toolbar/content.js
@@ -0,0 +1,70 @@
+class Channel {
+ constructor({name, model}) {
+ this._port = chrome.runtime.connect({ name });
+ this._message = chrome.runtime;
+ this._model = model;
+
+ this.post({action: 'register'});
+ }
+
+ post(data) {
+ this._port.postMessage(
+ Object.assign(this._model, { data }));
+ }
+
+ message(data) {
+ this._message.postMessage(
+ Object.assign(this._model, { data }));
+ }
+
+ get port() {
+ return this._port
+ }
+
+ get message() {
+ return this._message
+ }
+}
+
+var pallete = document.createElement('tool-pallete');
+const channel_name = 'design-artboard';
+const appendPallete = () => document.body.prepend(pallete);
+
+const Pipe = new Channel({
+ name: channel_name,
+ model: {
+ src_channel: channel_name,
+ target_channel: 'design-panel',
+ }
+});
+
+const layersFromDOM = ({nodeName, className, id, children}) => ({
+ nodeName, className, id,
+ children: [...children].map(layersFromDOM),
+});
+
+// append and watch toolbar selections
+appendPallete();
+pallete.selectorEngine.onSelectedUpdate(nodes =>
+ Pipe.post({
+ action: 'selected',
+ payload: nodes.map(layersFromDOM),
+ }));
+
+// watch pipe messages (they'll be auto filtered for this pipe)
+Pipe.port.onMessage.addListener(message => {
+ console.log(`${channel_name} recieved port message`, message);
+});
+
+Pipe.message.onMessage.addListener((request, sender, sendResponse) => {
+ console.log(`${channel_name} onMessage`, request);
+
+ const { action, params } = request;
+
+ // only respond to toolSelection atm
+ if (action != 'toolSelected') return
+
+ const [pallete] = document.getElementsByTagName('tool-pallete');
+ pallete && pallete[action](params);
+ // todo: send for tool to select element as well
+});
diff --git a/extension/toolbar/inject.js b/extension/toolbar/inject.js
index 01b341f3..3ef5245b 100644
--- a/extension/toolbar/inject.js
+++ b/extension/toolbar/inject.js
@@ -1,2 +1,44 @@
-document.body.prepend(document.createElement('hotkey-map'))
-document.body.prepend(document.createElement('tool-pallete'))
\ No newline at end of file
+import Channel from '../utils/channel.js'
+
+var pallete = document.createElement('tool-pallete')
+const channel_name = 'design-artboard'
+const appendPallete = () => document.body.prepend(pallete)
+
+const Pipe = new Channel({
+ name: channel_name,
+ model: {
+ src_channel: channel_name,
+ target_channel: 'design-panel',
+ }
+})
+
+const layersFromDOM = ({nodeName, className, id, children}) => ({
+ nodeName, className, id,
+ children: [...children].map(layersFromDOM),
+})
+
+// append and watch toolbar selections
+appendPallete()
+pallete.selectorEngine.onSelectedUpdate(nodes =>
+ Pipe.post({
+ action: 'selected',
+ payload: nodes.map(layersFromDOM),
+ }))
+
+// watch pipe messages (they'll be auto filtered for this pipe)
+Pipe.port.onMessage.addListener(message => {
+ console.log(`${channel_name} recieved port message`, message)
+})
+
+Pipe.message.onMessage.addListener((request, sender, sendResponse) => {
+ console.log(`${channel_name} onMessage`, request)
+
+ const { action, params } = request
+
+ // only respond to toolSelection atm
+ if (action != 'toolSelected') return
+
+ const [pallete] = document.getElementsByTagName('tool-pallete')
+ pallete && pallete[action](params)
+ // todo: send for tool to select element as well
+})
diff --git a/extension/toolbar/rollup.config.js b/extension/toolbar/rollup.config.js
new file mode 100644
index 00000000..7f26c2a1
--- /dev/null
+++ b/extension/toolbar/rollup.config.js
@@ -0,0 +1,22 @@
+import resolve from 'rollup-plugin-node-resolve'
+import postcss from 'rollup-plugin-postcss'
+
+const path_base = 'extension/toolbar'
+
+export default {
+ input: `${path_base}/inject.js`,
+ output: {
+ file: `${path_base}/content.js`,
+ format: 'esm',
+ sourcemap: false,
+ },
+ plugins: [
+ resolve({
+ jsnext: true,
+ }),
+ postcss({
+ extract: false,
+ inject: false,
+ }),
+ ]
+}
\ No newline at end of file
diff --git a/extension/utils/channel.js b/extension/utils/channel.js
new file mode 100644
index 00000000..abe71e77
--- /dev/null
+++ b/extension/utils/channel.js
@@ -0,0 +1,27 @@
+export default class Channel {
+ constructor({name, model}) {
+ this._port = chrome.runtime.connect({ name })
+ this._message = chrome.runtime
+ this._model = model
+
+ this.post({action: 'register'})
+ }
+
+ post(data) {
+ this._port.postMessage(
+ Object.assign(this._model, { data }))
+ }
+
+ message(data) {
+ this._message.postMessage(
+ Object.assign(this._model, { data }))
+ }
+
+ get port() {
+ return this._port
+ }
+
+ get message() {
+ return this._message
+ }
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 8cc918f7..236e952d 100644
--- a/package.json
+++ b/package.json
@@ -13,8 +13,11 @@
"dev:css": "postcss app/index.css -o app/bundle.css -w",
"dev:server": "browser-sync start --server 'app' --files 'app/index.html,app/bundle.css,app/bundle.js' --no-open",
"extension": "rollup -c && npm run extension:css && npm run extension:copy && npm run extension:zip",
- "extension:css": "postcss app/extension.css -o extension/toolbar/bundle.css",
+ "extension:js": "rollup -c extension/toolbar/rollup.config.js",
+ "extension:css": "postcss app/extension.css -o extension/toolbar/bundle.css && postcss extension/panel/panel.css -o extension/panel/panel.bundle.css",
+ "extension:dev": "concurrently \"postcss extension/panel/panel.css -o extension/panel/panel.bundle.css -w\" \"rollup -c extension/toolbar/rollup.config.js -w\"",
"extension:copy": "cp app/bundle.js extension/toolbar/",
+ "extension:build": "npm run extension:js && npm run extension:css",
"extension:zip": "mkdir ./pixelbug_v$npm_package_version && cp -R ./extension/* ./pixelbug_v$npm_package_version/ && zip -r ./extension/build/PixelBug_v$npm_package_version.zip ./pixelbug_v$npm_package_version && rm -rf ./pixelbug_v$npm_package_version",
"deploy": "gcloud app deploy --project=google.com:atoms-sandbox"
},