diff --git a/app/features/selectable.js b/app/features/selectable.js
index bd42b801..0f700cc6 100644
--- a/app/features/selectable.js
+++ b/app/features/selectable.js
@@ -710,9 +710,20 @@ export function Selectable(visbug) {
const removeSelectedCallback = cb =>
selectedCallbacks = selectedCallbacks.filter(callback => callback != cb)
- const tellWatchers = () =>
+ const layersFromDOM = ({nodeName, className, id, children}) => ({
+ nodeName, className, id,
+ children: [...children].map(layersFromDOM),
+ })
+
+ const tellWatchers = () => {
selectedCallbacks.forEach(cb => cb(selected))
+ visbug.$shadow.host.dispatchEvent(new CustomEvent('selected', {
+ bubbles: true,
+ detail: JSON.stringify(selected.map(layersFromDOM)),
+ }))
+ }
+
const disconnect = () => {
unselect_all()
unlisten()
diff --git a/extension/manifest.json b/extension/manifest.json
index b8d99c73..d1c3d738 100644
--- a/extension/manifest.json
+++ b/extension/manifest.json
@@ -4,6 +4,7 @@
"description": "Open source browser design tools",
"manifest_version": 2,
"icons": { "128": "icons/visbug.png" },
+ "devtools_page": "panel/index.html",
"permissions": [
"activeTab",
"contextMenus",
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/pane/visbug/index.html b/extension/pane/visbug/index.html
new file mode 100644
index 00000000..e5b1471d
--- /dev/null
+++ b/extension/pane/visbug/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Hello from VisBug
+
+
\ 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..1ba91594
--- /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('VisBug', sidebar => {
+ sidebar.setPage('../pane/visbug/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..162a5d31
--- /dev/null
+++ b/extension/panel/panel.bundle.css
@@ -0,0 +1,83 @@
+: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
+}
+
+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: var(--bg);
+ 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: 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.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..30875e86
--- /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('')
+}
diff --git a/extension/toolbar/content.js b/extension/toolbar/content.js
new file mode 100644
index 00000000..0e54f04c
--- /dev/null
+++ b/extension/toolbar/content.js
@@ -0,0 +1,84 @@
+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
+ }
+}
+
+const channel_name = 'design-artboard';
+
+var platform = typeof browser === 'undefined'
+ ? chrome
+ : browser;
+
+const script = document.createElement('script');
+script.type = 'module';
+script.src = platform.runtime.getURL('toolbar/bundle.min.js');
+document.body.appendChild(script);
+
+const visbug = document.createElement('vis-bug');
+
+const src_path = platform.runtime.getURL(`tuts/guides.gif`);
+visbug.setAttribute('tutsBaseURL', src_path.slice(0, src_path.lastIndexOf('/')));
+
+document.body.prepend(visbug);
+
+const Pipe = new Channel({
+ name: channel_name,
+ model: {
+ src_channel: channel_name,
+ target_channel: 'design-panel',
+ }
+});
+
+visbug.addEventListener('selected', e => {
+ // debugger
+ Pipe.post({
+ action: 'selected',
+ payload: JSON.parse(e.detail),
+ });
+});
+
+// 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 [visbug] = document.getElementsByTagName('vis-bug');
+ visbug && visbug[action](params);
+ // todo: send for tool to select element as well
+});
+
+platform.runtime.onMessage.addListener(request => {
+ if (request.action === 'COLOR_MODE')
+ visbug.setAttribute('color-mode', request.params.mode);
+});
diff --git a/extension/toolbar/inject.js b/extension/toolbar/inject.js
index f0728c3d..1e712234 100644
--- a/extension/toolbar/inject.js
+++ b/extension/toolbar/inject.js
@@ -1,3 +1,6 @@
+import Channel from '../utils/channel.js'
+const channel_name = 'design-artboard'
+
var platform = typeof browser === 'undefined'
? chrome
: browser
@@ -14,7 +17,41 @@ visbug.setAttribute('tutsBaseURL', src_path.slice(0, src_path.lastIndexOf('/')))
document.body.prepend(visbug)
+const Pipe = new Channel({
+ name: channel_name,
+ model: {
+ src_channel: channel_name,
+ target_channel: 'design-panel',
+ }
+})
+
+visbug.addEventListener('selected', e => {
+ // debugger
+ Pipe.post({
+ action: 'selected',
+ payload: JSON.parse(e.detail),
+ })
+})
+
+// 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 [visbug] = document.getElementsByTagName('vis-bug')
+ visbug && visbug[action](params)
+ // todo: send for tool to select element as well
+})
+
platform.runtime.onMessage.addListener(request => {
if (request.action === 'COLOR_MODE')
- visbug.setAttribute('color-mode', request.params.mode)
+ visbug.setAttribute('color-mode', request.params.mode)
})
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/extension/visbug.js b/extension/visbug.js
index 3b83d232..7bba8bef 100644
--- a/extension/visbug.js
+++ b/extension/visbug.js
@@ -1,3 +1,58 @@
+// setup ports
+const connections = {}
+
+chrome.runtime.onConnect.addListener(port => {
+ console.log('visbug onConnect', port)
+
+ if (port.name != 'design-panel' && port.name != 'design-artboard') {
+ console.warn(port)
+ return
+ }
+
+ port.onMessage.addListener(message => {
+ console.log('visbug 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('visbug 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('visbug missing message target')
+ })
+
+ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
+ console.log('visbug 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 visbug
const state = {
loaded: {},
injected: {},
@@ -24,7 +79,7 @@ const toggleIn = ({id:tab_id}) => {
// fresh start in tab
else {
platform.tabs.insertCSS(tab_id, { file: 'toolbar/bundle.css' })
- platform.tabs.executeScript(tab_id, { file: 'toolbar/inject.js' })
+ platform.tabs.executeScript(tab_id, { file: 'toolbar/content.js' })
state.loaded[tab_id] = true
state.injected[tab_id] = true
@@ -36,3 +91,20 @@ const toggleIn = ({id:tab_id}) => {
state.loaded[tabId] = false
})
}
+
+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/package.json b/package.json
index c3a87baf..fa1f5e73 100644
--- a/package.json
+++ b/package.json
@@ -19,14 +19,14 @@
"dev:js": "rollup -c -w --environment build:dev",
"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 --no-notify --no-ui --no-ghost-mode",
- "dev:extension": "npm run extension:build && npm run extension:local:version",
+ "dev:extension": "npm run extension:build && npm run extension:local:version && concurrently \"postcss extension/panel/panel.css -o extension/panel/panel.bundle.css -w\" \"rollup -c extension/toolbar/rollup.config.js -w\"",
"extension": "npm run extension:js && npm run extension:css && npm run extension:copy && npm run extension:local:version",
"extension:local:version": "sed -i '' \"s/{{NPM_VERSION}}/0.0.0/\" ./extension/manifest.json && sed -i '' \"s/VisBug/DevBug/\" ./extension/manifest.json && sed -i '' \"s/visbug.png/visbug-dev.png/\" ./extension/manifest.json",
"extension:ci:version": "sed -i \"s/{{NPM_VERSION}}/$npm_package_version/\" ./extension/manifest.json",
"extension:build": "npm run extension:js && npm run extension:css && npm run extension:copy",
"extension:release": "npm run test:ci && npm run bump && npm run extension:build && npm run extension:zip",
- "extension:js": "npm run bundle:prod",
- "extension:css": "postcss app/extension.css -o extension/toolbar/bundle.css",
+ "extension:js": "npm run bundle:prod && 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:copy": "cp app/bundle.min.js extension/toolbar/ && cp -R app/tuts/ extension/tuts",
"extension:zip": "rm -rf ./extension/build/* && mkdir ./visbug_v$npm_package_version && cp -R ./extension/* ./visbug_v$npm_package_version/ && zip -r ./extension/build/visbug.zip ./visbug_v$npm_package_version/ && rm -rf ./visbug_v$npm_package_version",
"extension:firefox": "npm run dev:extension && cd extension && web-ext run",