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 + ? `
  1. ${recurse_nodes(node)}
  2. ` + : ` +
  3. + + + ${node.nodeName}${node.id ? '#' + node.id : ''} ${node.className} + +
  4. + ` + ).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",