├── .eslintrc.js
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── README.zh-CN.md
├── circle.yml
├── examples
├── jsonTree.css
├── jsonTree.html
├── todo.html
└── todo.js
├── package.json
├── scripts
├── devtools.config.js
└── inject.config.js
├── src
├── devtools-ui
│ ├── agent.js
│ ├── components
│ │ ├── Devtools.js
│ │ ├── Element.css
│ │ ├── Element.js
│ │ ├── Elements.css
│ │ ├── Elements.js
│ │ ├── Header.css
│ │ ├── Header.js
│ │ ├── JsonTree.css
│ │ ├── JsonTree.js
│ │ ├── JsonTreeProp.css
│ │ ├── JsonTreeProp.js
│ │ ├── Search.css
│ │ ├── Search.js
│ │ ├── Sidebar.css
│ │ ├── Sidebar.js
│ │ ├── SidebarPane.css
│ │ ├── SidebarPane.js
│ │ ├── SimpleJsonTree.js
│ │ ├── Tabs.css
│ │ └── Tabs.js
│ ├── events
│ │ ├── enter.js
│ │ ├── index.js
│ │ ├── input.js
│ │ ├── mouseenter.js
│ │ └── mouseleave.js
│ ├── fonts
│ │ └── Roboto-Regular.ttf
│ ├── index.css
│ ├── index.js
│ ├── media
│ │ ├── arrow.svg
│ │ ├── at-sign.svg
│ │ ├── github.svg
│ │ ├── inspect.svg
│ │ ├── loading.svg
│ │ ├── next.svg
│ │ ├── prev.svg
│ │ ├── refresh.svg
│ │ ├── regular.png
│ │ ├── search.svg
│ │ ├── target.svg
│ │ └── target_active.svg
│ ├── port.js
│ └── utils
│ │ ├── findElement.js
│ │ ├── getData.js
│ │ ├── getOthersData.js
│ │ ├── highLighter.js
│ │ ├── highlightNode.js
│ │ ├── index.js
│ │ ├── inspectComponent.js
│ │ ├── inspectNode.js
│ │ ├── isPrimitive.js
│ │ ├── makeElementTree.js
│ │ ├── patch.js
│ │ ├── printInConsole.js
│ │ ├── searchPath.js
│ │ ├── showDefinitionByUUID.js
│ │ ├── type.js
│ │ └── updateInstance.js
├── electron
│ └── .gitkeep
├── extension
│ ├── background.js
│ ├── content.js
│ ├── devtools.html
│ ├── devtools.js
│ ├── hook.js
│ ├── inject.js
│ ├── manifest.json
│ └── regular.png
└── shared
│ ├── circular-json.js
│ └── log.js
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": "google",
3 | "installedESLint": true,
4 | "rules": {
5 | "indent": ["error", 4],
6 | "no-negated-condition": 0,
7 | "no-implicit-coercion":0,
8 | "linebreak-style": 0,
9 | "guard-for-in": 0
10 | },
11 | "env": {
12 | "browser": true,
13 | },
14 | "globals": {
15 | "devtoolsModel": true,
16 | "chrome":true,
17 | "Regular":true,
18 | "CircularJSON":true,
19 | "lastSelected":true,
20 | "sidebarView":true,
21 | "componentHandler":true
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | *.log
3 | node_modules
4 | dist
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | #### v0.9.3 2017-09-15
2 |
3 | + Fix currenNode undefined bug
4 |
5 | #### v0.9.2 2017-09-14
6 |
7 | + Fix `lockHightLight` bug
8 |
9 | #### v0.9.1 2017-09-14
10 |
11 | + Improve element view UI
12 | + [Scroll into view when selecting an element in devtools(#26 )](https://github.com/regularjs/regular-devtools/issues/26)
13 |
14 | #### v0.9.0 2017-09-13
15 |
16 | + Improve UI
17 | + Add inspect DOM and focus component mode
18 | + Refactor, using rollup for bundling
19 |
20 | #### v0.1 2016-07-26
21 |
22 | #### v0.2 2016-08-24
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2012-2015 NetEase, Inc. and regularjs contributors.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | 'Software'), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Regular Developer Tools
2 |
3 | [![version][version-image]][version-url]
4 | [![build][build-image]][build-url]
5 | [![download][download-image]][download-url]
6 | [![rating][rating-image]][rating-url]
7 |
8 | > Regular Developer Tools is a Chrome Extension that allows real-time inspection of [Regular](http://regularjs.github.io/) components.
9 |
10 | *Regular Developer Tools is under active development, any feedback is welcome* :clap:
11 |
12 | ### Overview
13 |
14 | Some GIFs to show you how Regular Devtools works. You can:
15 |
16 |
17 | View component tree structure, and select a component to get its data, computed data, filters and directives
18 |
19 |
20 |
21 |
22 | Data changes are synchronized in a bi-direction way
23 |
24 |
25 |
26 |
27 | Select a component, and inspect its instance by evaluating `$r` in the console
28 |
29 |
30 |
31 |
32 | Inspecting mode allow user to select a DOM node and view its corresponding component in Devtools Panel
33 |
34 |
35 |
36 | ### Features
37 |
38 | > **New in v0.9**
39 | > Now you can enter the brand new **inspecting mode** by the hitting the "target" button in navbar.
40 |
41 |
42 |
43 | + Inspecting Regular components hierarchy tree in element view.
44 | + Inspecting data, filters, directives, animations of selected component in sidebar.
45 | + Data changes made with Regular components will be reflected in both element view and sidebar in real-time.
46 | + Sidebar data is editable, changes will be applied to the corresponding component in page.
47 | + Searching component in element view.
48 | + Included contents will be annotated with `#inc`.
49 | + Click `inspect` button in the sidebar to inspect DOM node of selected component in Elements tab.
50 | + **Inspecting mode** allow user to inspect DOM node, and the corresponding component will be focused in Developer Tool.
51 | + **Pro Tip One**: When inspecting DOM node in Elements tab, switch to Regular tab, if the DOM node you are inspecting is rendered from a Regular component, the Regular Devtools will automatically focus on that component. It's like the reverse version of the last feature.
52 | + **Pro Tip Two**: When selecting component in element view, the component instance is available as `$r` in your console.
53 |
54 | ### Prerequisition
55 |
56 | Require [regularjs](https://github.com/regularjs/regular) **v0.5.0+**.
57 |
58 | ### Installation
59 |
60 | Install from [Chrome Web Store](https://chrome.google.com/webstore/detail/regular-developer-tools/ehlcoecgkhfjffhmdhmhbjkjjpaecmam)
61 |
62 | ### Manual Installation
63 |
64 | + **Step 1** Clone this repo.
65 | + **Step 2** run `npm i && npm run build` in command line, you will get `dist` folder in current working directory
66 | + **Step 3** Open Google Chrome and navigate to `chrome://extensions/`.
67 | + **Step 4** Check Developement mode checkbox in right corner.
68 | + **Step 5** Click `Load unpacked extension` and load the `dist` folder.
69 |
70 | ### Development
71 |
72 | ```bash
73 | # Install dependencies
74 | $ npm install
75 | # Build and watch file changes
76 | $ npm run watch
77 | # Build for production
78 | $ npm run build
79 | ```
80 |
81 | ### Changelog
82 |
83 | [CHANGELOG](CHANGELOG.md)
84 |
85 | ### License
86 |
87 | [MIT](https://github.com/regularjs/regular-devtools/blob/master/LICENSE)
88 |
89 | [build-image]: https://img.shields.io/circleci/project/regularjs/regular-devtools/master.svg?style=flat-square
90 | [build-url]: https://circleci.com/gh/regularjs/regular-devtools
91 |
92 | [version-image]: https://img.shields.io/chrome-web-store/v/ehlcoecgkhfjffhmdhmhbjkjjpaecmam.svg?style=flat-square
93 | [version-url]: https://chrome.google.com/webstore/detail/regular-developer-tools/ehlcoecgkhfjffhmdhmhbjkjjpaecmam
94 |
95 | [download-image]: https://img.shields.io/chrome-web-store/d/ehlcoecgkhfjffhmdhmhbjkjjpaecmam.svg?style=flat-square
96 | [download-url]: https://chrome.google.com/webstore/detail/regular-developer-tools/ehlcoecgkhfjffhmdhmhbjkjjpaecmam
97 |
98 | [rating-image]: https://img.shields.io/chrome-web-store/rating/ehlcoecgkhfjffhmdhmhbjkjjpaecmam.svg?style=flat-square
99 | [rating-url]: https://chrome.google.com/webstore/detail/regular-developer-tools/ehlcoecgkhfjffhmdhmhbjkjjpaecmam
100 |
--------------------------------------------------------------------------------
/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | # Regular Developer Tools [![build][build-image]][build-url]
2 |
3 | > Regular Developer Tools是一个chrome扩展,它允许你实时地观察[Regular](http://regularjs.github.io/)组件
4 |
5 | *Regular Developer Tools仍然处于公测阶段, 欢迎反馈任何问题* :clap:
6 |
7 | ### 总览
8 |
9 | 下面一些GIF会告诉你Regular Devtools是如何工作的
10 |
11 | 
12 |
13 | 
14 |
15 | ### 特性
16 |
17 | + 查看Regular组件的树形结构
18 | + 查看选中组件的data、filters、directives、animations
19 | + 页面中的组件变化会实时更新到Regular Devtools中
20 | + 侧边栏的data是可编辑的,编辑后变动将自动应用到页面中
21 | + 在组件视图中搜索组件
22 | + include的内容会使用`#inc`进行标识
23 | + 点击`inspect`查看选中组件对应的DOM节点
24 | + **小贴士一**:当从Elements面板切换到Regular面板时,如果之前选中的DOM节点是由Regular组件渲染出来的,Regular Devtools会自动选中该组件,这相当于最后一条特性的相反版本
25 | + **小贴士二**:当在组件视图中选中一个组件后,你可以在console中通过`$r`取到当前组件实例的引用
26 |
27 | ### 前置条件
28 |
29 | 你的项目需要使用定制的regularjs(或使用官方0.5及以上版本的regularjs),你可以在这里找到定制版本[`/libs/regular.js`](https://github.com/regularjs/regular-devtools/blob/master/lib/regular.js)
30 |
31 | ### 安装
32 |
33 | 从[谷歌应用商店](https://chrome.google.com/webstore/detail/regular-developer-tools/ehlcoecgkhfjffhmdhmhbjkjjpaecmam)安装
34 |
35 | ### 如何开发
36 |
37 | + **步骤1** 克隆本仓库
38 | + **步骤2** 执行命令`npm i && npm run build`,会打包源码到dist目录
39 | + **步骤3** 打开Chrome浏览器,并导航至`chrome://extensions/`
40 | + **步骤4** 勾选右上角的`开发者模式`
41 | + **步骤5** 点击`加载已解压的扩展程序...`,选择刚刚生成的`dist`文件夹
42 |
43 | ### 更新日志
44 |
45 | #### v0.1 2016-07-26
46 |
47 | #### v0.2 2016-08-24
48 |
49 | ### License
50 |
51 | [MIT](https://github.com/regularjs/regular-devtools/blob/master/LICENSE)
52 |
53 | [build-image]: https://img.shields.io/circleci/project/regularjs/regular-devtools/master.svg?style=flat-square
54 | [build-url]: https://circleci.com/gh/regularjs/regular-devtools
55 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | node:
3 | version: 6
--------------------------------------------------------------------------------
/examples/jsonTree.css:
--------------------------------------------------------------------------------
1 | .json-tree {
2 | padding: 10px 20px;
3 | }
4 |
5 | .json-tree-data {
6 | font-size: 13px;
7 | }
8 |
9 | .json-tree-data-key .arrow {
10 | height: 10px;
11 | width: 10px;
12 | margin-left: -13px;
13 | margin-right: 3px;
14 | }
15 |
16 | .json-tree-data-key .arrow-down {
17 | transform: rotate(90deg);
18 | }
19 |
20 | .json-tree-data-key {
21 | line-height: 13px;
22 | padding-bottom:6px;
23 | white-space: nowrap;
24 | font-size: 0;
25 | cursor: default;
26 | }
27 | .json-tree-data-key .item {
28 | display: inline-block;
29 | vertical-align: middle;
30 | font-size: 13px;
31 | }
32 |
33 | .json-tree-data .key{
34 | color:#881391;
35 | padding-right: 5px;
36 | }
37 |
38 | .json-tree-data .edit{
39 | padding: 3px;
40 | margin-top: -3px;
41 | position: absolute;
42 | border: solid 1px #e6e6e6;
43 | box-sizing: border-box;
44 | }
45 |
46 | .json-tree-data .primitive{
47 | color: #03c;
48 | padding-right: 15px;
49 | }
50 |
51 | .json-tree-data .string{
52 | color: #c41a16;
53 | padding-right: 15px;
54 | }
55 |
56 | .json-tree-data .others{
57 | color: #444;
58 | padding-right: 15px;
59 | }
60 |
61 | .json-tree-data-key .hide{
62 | display: none;
63 | }
--------------------------------------------------------------------------------
/examples/jsonTree.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | jsonTree
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
20 |
65 |
177 |
205 |
206 |
207 |
--------------------------------------------------------------------------------
/examples/todo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | regular-todo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
18 |
19 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/examples/todo.js:
--------------------------------------------------------------------------------
1 | var Todo = Regular.extend({
2 | template: "#todo",
3 | foo() {}
4 | });
5 |
6 | Todo.filter("test", function () {});
7 |
8 | var TodoMVC = Regular.extend({
9 | template: '#todomvc', // id | template string | preparsed ast
10 | // get the list;
11 | config() {}
12 | })
13 |
14 | TodoMVC.component("custom-todo", Todo);
15 |
16 | var todos = [
17 | { description: "sleep" },
18 | { description: "work" }
19 | ]
20 | var app = new TodoMVC({
21 | data: {todos: todos}
22 | }).$inject("#todoapp")
23 |
24 | new TodoMVC({
25 | data: {todos: todos}
26 | }).$inject(document.body);
27 | new TodoMVC({
28 | data: {todos: todos}
29 | }).$inject(document.body);
30 | new TodoMVC({
31 | data: {todos: todos}
32 | }).$inject(document.body);
33 | new TodoMVC({
34 | data: {todos: todos}
35 | }).$inject(document.body);
36 | new TodoMVC({
37 | data: {todos: todos}
38 | }).$inject(document.body);
39 | new TodoMVC({
40 | data: {todos: todos}
41 | }).$inject(document.body);
42 | new TodoMVC({
43 | data: {todos: todos}
44 | }).$inject(document.body);
45 | new TodoMVC({
46 | data: {todos: todos}
47 | }).$inject(document.body);
48 | new TodoMVC({
49 | data: {todos: todos}
50 | }).$inject(document.body);
51 | new TodoMVC({
52 | data: {todos: todos}
53 | }).$inject(document.body);
54 | new TodoMVC({
55 | data: {todos: todos}
56 | }).$inject(document.body);
57 | new TodoMVC({
58 | data: {todos: todos}
59 | }).$inject(document.body);
60 | new TodoMVC({
61 | data: {todos: todos}
62 | }).$inject(document.body);
63 | new TodoMVC({
64 | data: {todos: todos}
65 | }).$inject(document.body);
66 | new TodoMVC({
67 | data: {todos: todos}
68 | }).$inject(document.body);
69 | new TodoMVC({
70 | data: {todos: todos}
71 | }).$inject(document.body);
72 | new TodoMVC({
73 | data: {todos: todos}
74 | }).$inject(document.body);
75 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "regular-devtools",
4 | "version": "0.9.3",
5 | "description": "Regular Developer Tools is a Chrome Extension that allows real-time inspection of [Regular](http://regularjs.github.io/) components.",
6 | "scripts": {
7 | "build": "npm run build:devtools && npm run build:inject",
8 | "build:devtools": "poi build --config scripts/devtools.config.js",
9 | "build:inject": "poi build --config scripts/devtools.config.js",
10 | "watch": "concurrently \"npm run watch:devtools\" \"npm run watch:inject\"",
11 | "watch:devtools": "poi watch --config scripts/devtools.config.js",
12 | "watch:inject": "poi watch --config scripts/inject.config.js",
13 | "test": "npm run lint",
14 | "lint": "eslint src --quiet",
15 | "precommit": "lint-staged"
16 | },
17 | "lint-staged": {
18 | "src/**/*.js": "eslint --quiet"
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "git+https://github.com/regularjs/regular-devtools.git"
23 | },
24 | "author": "",
25 | "license": "MIT",
26 | "bugs": {
27 | "url": "https://github.com/regularjs/regular-devtools/issues"
28 | },
29 | "homepage": "https://github.com/regularjs/regular-devtools#readme",
30 | "devDependencies": {
31 | "bili": "^0.14.0",
32 | "concurrently": "^3.3.0",
33 | "eslint": "^3.2.2",
34 | "eslint-config-google": "^0.6.0",
35 | "husky": "^0.14.3",
36 | "lint-staged": "^4.1.3",
37 | "material-design-lite": "^1.3.0",
38 | "mitt": "^1.1.2",
39 | "poi": "^9.3.5",
40 | "regularjs": "0.5.2"
41 | },
42 | "dependencies": {}
43 | }
44 |
--------------------------------------------------------------------------------
/scripts/devtools.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './src/devtools-ui/index.js',
3 | copy: [
4 | {
5 | from: 'src/devtools-ui/media',
6 | to: 'static/media',
7 | },
8 | ],
9 | webpack( config ) {
10 | config.resolve.alias[ 'regularjs' ] = require.resolve( 'regularjs/dist/regular.js' );
11 | return config;
12 | },
13 | minimize: true,
14 | sourceMap: true,
15 | extractCSS: true,
16 | vendor: true
17 | };
18 |
--------------------------------------------------------------------------------
/scripts/inject.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './src/extension/inject.js',
3 | filename: {
4 | js: 'inject.bundle.js'
5 | },
6 | copy: [
7 | {
8 | from: 'src/extension',
9 | to: './',
10 | },
11 | ],
12 | minimize: true,
13 | sourceMap: true,
14 | html: false,
15 | vendor: false
16 | };
17 |
--------------------------------------------------------------------------------
/src/devtools-ui/agent.js:
--------------------------------------------------------------------------------
1 | import mitt from 'mitt';
2 | import port from './port';
3 |
4 | class Agent {
5 | constructor() {
6 | Object.assign(this, mitt());
7 |
8 | port.onMessage.addListener(message => {
9 | switch (message.type) {
10 | case 'dataUpdate':
11 | this.emit('dataUpdate');
12 | break;
13 | case 'reRender':
14 | this.emit('reRender', message.nodes);
15 | break;
16 | case 'initNodes':
17 | this.emit('initNodes', message.nodes);
18 | break;
19 | case 'currNodeChange':
20 | this.emit('currNodeChange', message.uuid);
21 | break;
22 | case 'pageReload':
23 | this.emit('pageReload', message.tabId);
24 | break;
25 | default:
26 | // skip
27 | }
28 | });
29 |
30 | // listen for messge when switch from element tab to regular tab
31 | window.addEventListener("message", event => {
32 | if (event.data.type === "currNodeChange") {
33 | this.emit("currNodeChange", event.data.uuid);
34 | }
35 | }, false);
36 | }
37 |
38 | injectContentScript(tabId) {
39 | port.postMessage({
40 | tabId: tabId || chrome.devtools.inspectedWindow.tabId,
41 | file: "/content.js"
42 | });
43 | }
44 |
45 | openInNewTab(url) {
46 | port.postMessage({url});
47 | }
48 | }
49 |
50 | export default new Agent();
51 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/Devtools.js:
--------------------------------------------------------------------------------
1 | import Regular from 'regularjs';
2 | import Header from './Header';
3 | import Sidebar from './Sidebar';
4 | import Elements from './Elements';
5 | import {searchPath, printInConsole, enter, exit} from '../utils';
6 |
7 | // Regular components for devtools' UI
8 | const Devtools = Regular.extend({
9 | template: `
10 |
16 |
17 |
18 |
19 |
20 |
21 | `,
22 | data: {
23 | inspecting: false
24 | },
25 | onVisitGithub: function() {
26 | this.$emit("openNewTab", "https://github.com/regularjs/regular-devtools");
27 | },
28 | onInspect: function() {
29 | if (this.data.inspecting) {
30 | this.data.inspecting = false;
31 | this.$refs.sidebarView.$emit("lockHighLight", false);
32 | exit();
33 | } else {
34 | this.data.inspecting = true;
35 | this.$refs.sidebarView.$emit("lockHighLight", true);
36 | enter();
37 | }
38 | this.$update();
39 | },
40 | onRefresh: function() {
41 | this.$emit('refresh');
42 | },
43 | focusNode: function(uuid) {
44 | const elementViewDOM = document.querySelector(".elementTree");
45 | const elementView = this.$refs.elementView;
46 | const relativeOffset = elementView._children[0].group.children[0].last().getBoundingClientRect().top;
47 | // unselect last selected
48 | if (this.data.lastSelected) {
49 | this.data.lastSelected.data.selected = false;
50 | }
51 |
52 | // update sidebarView
53 | this.$emit("clickElement", uuid);
54 |
55 | // update elementView
56 | const path = [];
57 | searchPath(elementView._children, uuid, path);
58 | for (let i = 0; i < path.length; i++) {
59 | path[i].data.opened = true;
60 | }
61 | this.data.lastSelected = path[0];
62 | path[0].data.selected = true;
63 | printInConsole(uuid);
64 | elementView.$update();
65 |
66 | // scroll into view
67 | const currTop = path[0].group.children[0].last().getBoundingClientRect().top;
68 | console.log(currTop);
69 | elementViewDOM.scrollTop = currTop - relativeOffset;
70 | }
71 | });
72 |
73 | Devtools.component('devtools-header', Header);
74 | Devtools.component('devtools-sidebar', Sidebar);
75 | Devtools.component('devtools-elements', Elements);
76 |
77 | export default Devtools;
78 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/Element.css:
--------------------------------------------------------------------------------
1 | .element__wrapper {
2 | position: relative;
3 | }
4 |
5 | .element__joint {
6 | position: absolute;
7 | width: 0;
8 | border-right: dashed 1px #ddd;
9 | }
10 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/Element.js:
--------------------------------------------------------------------------------
1 | import Regular from 'regularjs';
2 | import {findElementByUUID} from '../utils';
3 | import './Element.css';
4 |
5 | const Element = Regular.extend({
6 | template: `
7 |
8 | {#if opened}
9 |
10 | {/if}
11 |
17 |
18 |
23 |
<
24 |
25 | {node.name}
26 |
27 |
28 | {#if node.childNodes.length === 0} /{/if}>{#if node.isIncluded }#inc {/if}
29 |
30 |
31 | {#if opened && node.childNodes.length > 0}
32 | {#list node.childNodes as n}
33 |
34 | {/list}
35 | {/if}
36 | {#if node.childNodes.length > 0}
37 |
41 | </
42 | {node.name}
43 | >
44 |
45 | {/if}
46 |
47 | `,
48 | data: {
49 | selected: false,
50 | opened: false
51 | },
52 | onMouseEnter: function(uuid, inspectable) {
53 | // Communication between two components(not directly related), should refactor using eventbus.
54 | this.$root.$refs.sidebarView.highLightNode(uuid, inspectable);
55 | },
56 | onClick: function(node) {
57 | let lastSelected = this.$root.data.lastSelected;
58 | if (lastSelected) {
59 | if (lastSelected === this) {
60 | return;
61 | }
62 |
63 | if (!findElementByUUID(this.$root.data.nodes, lastSelected.data.node.uuid)) {
64 | this.$root.data.lastSelected = null;
65 | } else {
66 | lastSelected.data.selected = false;
67 | }
68 | }
69 | this.data.selected = true;
70 | this.$root.data.lastSelected = this;
71 | this.$root.$emit("clickElement", node.uuid);
72 | }
73 | });
74 |
75 | Element.component('element', Element);
76 |
77 | export default Element;
78 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/Elements.css:
--------------------------------------------------------------------------------
1 | .elementView {
2 | position: relative;
3 | box-sizing: border-box;
4 | display: flex;
5 | flex-direction: column;
6 | width: 50%;
7 | font-size: 20px;
8 | color: #000;
9 | vertical-align: top;
10 | height: 100%;
11 | border-right: 1px solid #e3e3e3;
12 | }
13 |
14 | .elementView .element {
15 | line-height: 18px;
16 | font-size: 0;
17 | padding: 0 20px 3px;
18 | white-space: nowrap;
19 | cursor: default;
20 | }
21 |
22 | .elementView .warnning {
23 | padding: 20px;
24 | font-size: 15px;
25 | }
26 |
27 | .elementView .tag {
28 | color: rgb(168, 148, 166)
29 | }
30 |
31 | .elementView .arrow {
32 | height: 12px;
33 | width: 12px;
34 | }
35 |
36 | .elementView .ele-item {
37 | font-size: 13px;
38 | display: inline-block;
39 | vertical-align: middle;
40 | }
41 |
42 | .elementView .tagname.is-anonymous {
43 | font-style: italic;
44 | }
45 |
46 | .elementView .ele-include {
47 | font-size: 11px;
48 | margin-left: 5px;
49 | color: #999;
50 | }
51 |
52 | .elementView .arrow-down {
53 | transform: rotate(90deg);
54 | }
55 |
56 | .elementView .loading-img {
57 | width: 30px;
58 | height: 30px;
59 | position: absolute;
60 | top: 50%;
61 | left: 50%;
62 | margin-left: -15px;
63 | margin-top: -15px;
64 | }
65 |
66 | @keyframes loading-animation {
67 | from {
68 | transform: rotate(0deg);
69 | }
70 | to {
71 | transform: rotate(360deg);
72 | }
73 | }
74 |
75 | .element-tag:hover {
76 | background-color: #e5e5e5;
77 | }
78 |
79 | .selected {
80 | background-color: #44a1ff;
81 | }
82 |
83 | .flush-animation {}
84 |
85 | .elementView .selected .ele-item {
86 | color: #fff;
87 | }
88 |
89 | .elementView .selected .ele-include {
90 | color: #fff;
91 | }
92 |
93 | .elementView .hide {
94 | display: none;
95 | }
96 |
97 | .elementTree {
98 | flex: 1;
99 | width: 100%;
100 | overflow-y: auto;
101 | padding: 20px 0;
102 | box-sizing: border-box;
103 | }
104 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/Elements.js:
--------------------------------------------------------------------------------
1 | import Regular from 'regularjs';
2 | import Element from './Element';
3 | import Search from './Search';
4 | import './Elements.css';
5 |
6 | const Elements = Regular.extend({
7 | template: `
8 |
9 |
10 | {#if loading }
11 |
14 | {#else}
15 | {#if nodes.length > 0}
16 | {#list nodes as node}
17 | {#if node.hasTemplate && node.hasInjected}
18 |
19 | {/if}
20 | {/list}
21 | {#else}
22 |
There is no Regular instance detected. Please check if you are using the latest version of Regularjs. Or try reloading Regular Devtools
23 | {/if}
24 | {/if}
25 |
26 |
27 |
28 | `,
29 | data: {
30 | nodes: [],
31 | loading: true
32 | },
33 | onMouseLeave: function() {
34 | this.$root.$refs.sidebarView.highLightNode(null, false);
35 | }
36 | });
37 |
38 | Elements.component('element', Element);
39 | Elements.component('search', Search);
40 |
41 | export default Elements;
42 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/Header.css:
--------------------------------------------------------------------------------
1 | .header {
2 | display: flex;
3 | width: 100%;
4 | height: 60px;
5 | border-bottom: 1px solid #e3e3e3;
6 | box-shadow: 0 0 8px rgba(0, 0, 0, .15);
7 | box-sizing: border-box;
8 | position: absolute;
9 | left: 0;
10 | top: 0;
11 | padding: 10px 20px;
12 | }
13 |
14 | .header__logo {
15 | display: flex;
16 | align-items: center;
17 | flex: 1;
18 | padding-left: 50px;
19 | background-image: url(/static/media/regular.png);
20 | background-repeat: no-repeat;
21 | background-size: auto 100%;
22 | white-space: nowrap;
23 | overflow: hidden;
24 | text-overflow: ellipsis;
25 | font-size: 20px;
26 | cursor: default;
27 | }
28 |
29 | .header__toolbar {
30 | height: 70%;
31 | align-self: center;
32 | }
33 |
34 | .header__inspect, .header__refresh, .header__github {
35 | height: 100%;
36 | cursor: pointer;
37 | outline: none;
38 | margin-left: 10px;
39 | }
40 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/Header.js:
--------------------------------------------------------------------------------
1 | import Regular from 'regularjs';
2 | import './Header.css';
3 |
4 | export default Regular.extend({
5 | template: `
6 |
18 | `,
19 | onInspect() {
20 | this.$emit('inspect');
21 | },
22 | onRefresh() {
23 | this.$emit('refresh');
24 | },
25 | onVisitGithub() {
26 | this.$emit('visit-github');
27 | }
28 | });
29 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/JsonTree.css:
--------------------------------------------------------------------------------
1 | .json-tree {
2 | padding: 10px 20px;
3 | }
4 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/JsonTree.js:
--------------------------------------------------------------------------------
1 | import Regular from 'regularjs';
2 | import JsonTreeProp from './JsonTreeProp';
3 | import './JsonTree.css';
4 |
5 | const JsonTree = Regular.extend({
6 | template: `
7 |
8 | {#list Object.keys(source) as k}
9 |
10 | {/list}
11 |
12 | `,
13 | data: {
14 | source: {}
15 | },
16 | config: function() {
17 | var self = this;
18 |
19 | function onClick(e) {
20 | self.$emit('checkClickOutside', e.target);
21 | }
22 | document.addEventListener('click', onClick, false);
23 | this.$on('$destroy', function() {
24 | document.removeEventListener('click', onClick, false);
25 | });
26 | },
27 | isJsonTree() {
28 | return true;
29 | }
30 | });
31 |
32 | JsonTree.component('JsonTreeProp', JsonTreeProp);
33 |
34 | export default JsonTree;
35 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/JsonTreeProp.css:
--------------------------------------------------------------------------------
1 | .json-tree-data {
2 | font-size: 13px;
3 | }
4 |
5 | .json-tree-data-key .arrow {
6 | height: 10px;
7 | width: 10px;
8 | margin-left: -13px;
9 | margin-right: 3px;
10 | }
11 |
12 | .json-tree-data-key .arrow-down {
13 | transform: rotate(90deg);
14 | }
15 |
16 | .json-tree-data-key {
17 | position: relative;
18 | line-height: 13px;
19 | padding-bottom: 6px;
20 | white-space: nowrap;
21 | font-size: 0;
22 | cursor: default;
23 | }
24 |
25 | .json-tree-data-key .item {
26 | display: inline-block;
27 | vertical-align: middle;
28 | font-size: 13px;
29 | }
30 |
31 | .json-tree-data .key {
32 | color: #881391;
33 | padding-right: 5px;
34 | }
35 |
36 | .json-tree-data .edit {
37 | padding: 3px;
38 | margin-top: -3px;
39 | position: absolute;
40 | border: solid 1px #e6e6e6;
41 | box-sizing: border-box;
42 | }
43 |
44 | .json-tree-data .primitive {
45 | color: #03c;
46 | padding-right: 15px;
47 | }
48 |
49 | .json-tree-data .string {
50 | color: #c41a16;
51 | padding-right: 15px;
52 | }
53 |
54 | .json-tree-data .function {
55 | color: rgb(13, 34, 170);
56 | font-style: italic;
57 | }
58 |
59 | .json-tree-data .dom {
60 | font-style: italic;
61 | }
62 |
63 | .json-tree-data .gray {
64 | color: #999;
65 | padding-right: 15px;
66 | }
67 |
68 | .json-tree-data .others {
69 | color: #444;
70 | padding-right: 15px;
71 | }
72 |
73 | .json-tree-data-key .hide {
74 | display: none;
75 | }
76 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/JsonTreeProp.js:
--------------------------------------------------------------------------------
1 | import Regular from 'regularjs';
2 | import {isPrimitive, type} from '../utils';
3 | import './JsonTreeProp.css';
4 |
5 | const JsonTreeProp = Regular.extend({
6 | template: `
7 |
8 |
9 |
15 |
{key + ':'}
16 |
17 |
18 | {#if !editing}
19 | {#if this.isPrimitive(value)}
20 | {#if type === 'String'}
21 | {#if value === 'Function'}
22 | f()
23 | {#elseif value === '[DOM node]'}
24 | DOM Node
25 | {#elseif value === '[Circular]'}
26 | Circular Reference
27 | {#else}
28 | "{value}"
29 | {/if}
30 | {#else}
31 | {value}
32 | {/if}
33 | {#elseif type === 'Array'}
34 | Array[{value.length}]
35 | {#else}
36 | {type}
37 | {/if}
38 | {/if}
39 |
49 |
50 |
51 | {#if opened && hasChildren}
52 |
53 | {#list Object.keys(value) as k}
54 |
55 | {/list}
56 |
57 | {/if}
58 |
59 | `,
60 | data: {
61 | opened: false
62 | },
63 | computed: {
64 | type() {
65 | return type(this.data.value);
66 | },
67 | hasChildren() {
68 | var data = this.data;
69 | return ((type(data.value) === 'Array') || (type(data.value) === 'Object')) &&
70 | ((data.value.length || Object.keys(data.value).length));
71 | }
72 | },
73 | config: function() {
74 | var self = this;
75 | this.$parent.$on('checkClickOutside', function(v) {
76 | if (self.$refs && self.$refs.edit && !self.$refs.edit.contains(v)) {
77 | self.data.editing = false;
78 | self.$update();
79 | }
80 | self.$emit('checkClickOutside', v);
81 | });
82 | },
83 | onEdit: function() {
84 | if (this.data.value === 'function') {
85 | return;
86 | }
87 | if (!isPrimitive(this.data.value)) {
88 | return;
89 | }
90 | this.data.editing = true;
91 | // focus and selectRange after UI updated
92 | setTimeout(() => {
93 | // select all when active
94 | var $edit = this.$refs.edit;
95 | $edit.focus();
96 | if (type(this.data.value) === "String") {
97 | $edit.setSelectionRange(1, $edit.value.length - 1);
98 | } else {
99 | $edit.setSelectionRange(0, $edit.value.length);
100 | }
101 | }, 0);
102 | },
103 | onBlur: function(e) {
104 | this.data.editing = false;
105 | this._sync(e);
106 | },
107 | onEnter: function(e) {
108 | // Enter
109 | if (e.which === 13) {
110 | this.$refs.edit.blur();
111 | }
112 | },
113 | // when editing is finished
114 | _sync: function(e) {
115 | var tmp;
116 | try {
117 | tmp = JSON.parse(e.target.value);
118 | } catch (error) {
119 | const value = this.data.value;
120 | if (type(value)) {
121 | e.target.value = JSON.stringify(value);
122 | } else {
123 | e.target.value = value;
124 | }
125 | }
126 |
127 | // if not primitive or new value equals original one, return
128 | if (!isPrimitive(tmp) || tmp === this.data.value) {
129 | return;
130 | }
131 |
132 | var parent = this.$parent;
133 | while (parent) {
134 | if (isJsonTree(parent)) {
135 | parent.$emit('change', {
136 | path: this.data.path,
137 | value: tmp,
138 | oldValue: this.data.value
139 | });
140 | break;
141 | }
142 | parent = parent.$parent;
143 | }
144 |
145 | function isJsonTree(context) {
146 | return typeof context.isJsonTree === 'function' && context.isJsonTree() === true;
147 | }
148 |
149 | this.data.value = tmp;
150 | this.$update();
151 | },
152 | isPrimitive: isPrimitive
153 | });
154 |
155 | JsonTreeProp.component('JsonTreeProp', JsonTreeProp);
156 |
157 | export default JsonTreeProp;
158 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/Search.css:
--------------------------------------------------------------------------------
1 | .searchView {
2 | display: flex;
3 | width: 100%;
4 | height: 40px;
5 | border-top: 1px solid #e3e3e3;
6 | box-sizing: border-box;
7 | }
8 |
9 | .searchView-md{
10 | width:100%;
11 | }
12 |
13 | .searchView-md-input{
14 | padding: 0 0 0 10px;
15 | }
16 |
17 | .searchView-md-label{
18 | top:12px;
19 | padding-left: 10px;
20 | box-sizing: border-box;
21 | }
22 |
23 | .searchView-md-label:after{
24 | bottom:0;
25 | }
26 |
27 | .searchView-input {
28 | display: block;
29 | width: 100%;
30 | height: 100%;
31 | line-height: 40px;
32 | font-size: 11px;
33 | border: none;
34 | box-sizing: border-box;
35 | outline: none;
36 | }
37 |
38 | .searchView-btns {
39 | display: flex;
40 | justify-content: center;
41 | align-items: center;
42 | position: absolute;
43 | right: 0;
44 | padding-right: 10px;
45 | }
46 |
47 | .searchView-text {
48 | font-size: 12px;
49 | vertical-align: middle;
50 | height: 40px;
51 | line-height: 40px;
52 | white-space: nowrap;
53 | margin-right: 5px;
54 | }
55 |
56 | .searchView-btn {
57 | display: block;
58 | height: 25px;
59 | width: 25px;
60 | cursor: pointer;
61 | }
62 |
63 | .searchView-btn:not(:last-child) {
64 | margin-right: 10px;
65 | }
66 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/Search.js:
--------------------------------------------------------------------------------
1 | import Regular from 'regularjs';
2 | import {findElementByName} from '../utils';
3 | import './Search.css';
4 |
5 | const SearchView = Regular.extend({
6 | template: `
7 |
8 |
9 |
10 | Search By Component Name
11 |
12 |
13 |
14 | {#if hasSearched && !resultList.length}
15 | Not found
16 | {#else}
17 | {#if resultList.length}
18 | {index + 1}/{resultList.length} found
19 | {/if}
20 | {/if}
21 |
22 |
23 |
24 |
25 |
26 |
27 | `,
28 | data: {
29 | value: "",
30 | resultList: [],
31 | index: 0,
32 | hasSearched: false
33 | },
34 | onInput: function() {
35 | this.data.hasSearched = false;
36 | },
37 | onEnter: function() {
38 | if (this.data.hasSearched) {
39 | this.next(1);
40 | } else {
41 | this.search();
42 | }
43 | },
44 | search: function() {
45 | if (!this.data.value) {
46 | return;
47 | }
48 | var reg = new RegExp(this.data.value);
49 | this.data.resultList = [];
50 | this.data.index = 0;
51 | findElementByName(this.$root.data.nodes, reg, this.data.resultList);
52 | this.data.hasSearched = true;
53 | if (this.data.resultList.length) {
54 | this.$root.focusNode(this.data.resultList[0]);
55 | }
56 | },
57 | next: function(dir) {
58 | var data = this.data;
59 | if (data.resultList.length) {
60 | data.index += dir;
61 | if (data.index === data.resultList.length) data.index = 0;
62 | if (data.index === -1) data.index = data.resultList.length - 1;
63 | this.$root.focusNode(data.resultList[data.index]);
64 | }
65 | },
66 | reset: function() {
67 | this.data.value = "";
68 | this.data.resultList = [];
69 | this.data.index = 0;
70 | this.data.hasSearched = false;
71 | this.$update();
72 | }
73 | });
74 |
75 | export default SearchView;
76 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/Sidebar.css:
--------------------------------------------------------------------------------
1 | .sidebar {
2 | display: inline-block;
3 | width: 50%;
4 | height: 100%;
5 | }
6 |
7 | .sidebar__header {
8 | font-size: 15px;
9 | line-height: 30px;
10 | display: flex;
11 | padding: 0 10px;
12 | }
13 |
14 | .sidebar__header-left {
15 | flex-grow: 1;
16 | }
17 |
18 | .sidebar__header-right {
19 | font-size: 13px;
20 | }
21 |
22 | .sidebar__content {
23 | height: -webkit-calc(100% - 61px);
24 | overflow-y: auto;
25 | font-size: 20px;
26 | }
27 |
28 | .sidebar__tools {
29 | padding: 0 10px;
30 | }
31 |
32 | .sidebar__inspect {
33 | display: inline-block;
34 | background-image: url(/static/media/inspect.svg);
35 | background-position: 0 center;
36 | background-size: 16px auto;
37 | background-repeat: no-repeat;
38 | padding-left: 18px;
39 | font-size: 11px;
40 | color: #333;
41 | cursor: pointer;
42 | margin: 10px 0;
43 | }
44 |
45 | .sidebar__definition {
46 | display: inline-block;
47 | background-image: url(/static/media/at-sign.svg);
48 | background-position: 0 center;
49 | background-size: 14px auto;
50 | background-repeat: no-repeat;
51 | padding-left: 16px;
52 | font-size: 11px;
53 | color: #333;
54 | cursor: pointer;
55 | margin: 10px 0;
56 | }
57 |
58 | .sidebar__inspect:hover {
59 | color: #000;
60 | }
61 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/Sidebar.js:
--------------------------------------------------------------------------------
1 | import Regular from 'regularjs';
2 | import SidebarPane from './SidebarPane';
3 | import SimpleJsonTree from './SimpleJsonTree';
4 | import JsonTree from './JsonTree';
5 | import Tabs from './Tabs';
6 | import log from '../../shared/log';
7 | import './Sidebar.css';
8 |
9 | const Sidebar = Regular.extend({
10 | template: `
11 |
65 | `,
66 | config() {
67 | // defaultValue of currentNode
68 | this.data.currentNode = {
69 | name: "",
70 | uuid: "",
71 | inspectable: false,
72 | computed: {},
73 | data: {}
74 | };
75 | // others for currentNode
76 | this.data.others = {};
77 | this.data.tabSource = [
78 | {text: "Data", key: "data"},
79 | {text: 'Others', key: 'others'}
80 | ];
81 | // defaults to `data` pane
82 | this.data.tabSelected = 'data';
83 | this.data.lockHighlight = false;
84 | },
85 | computed: {
86 | currentTabIndex: {
87 | get() {
88 | const source = this.data.tabSource;
89 | for (let i = 0; i < source.length; i++) {
90 | if (this.data.tabSelected === source[i].key) {
91 | return i;
92 | }
93 | }
94 | return 0;
95 | }
96 | }
97 | },
98 | onTabChange(key) {
99 | this.data.tabSelected = key;
100 | this.$update();
101 | log("Tab is Changed to", key);
102 | },
103 | onDataChange({path, value}) {
104 | const uuid = this.data.currentNode.uuid;
105 | this.$emit('dataChange', {uuid, path, value});
106 | },
107 | onInspectNode(uuid) {
108 | this.$emit('inspectNode', uuid);
109 | },
110 | onShowDefinition(uuid) {
111 | this.$emit('showDefinition', uuid);
112 | },
113 | highLightNode(uuid, inspectable) {
114 | this.$emit('highLightNode', {uuid, inspectable});
115 | }
116 | });
117 |
118 | Sidebar.component('SidebarPane', SidebarPane);
119 | Sidebar.component('SimpleJsonTree', SimpleJsonTree);
120 | Sidebar.component('Tabs', Tabs);
121 | Sidebar.component('JsonTree', JsonTree);
122 |
123 | export default Sidebar;
124 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/SidebarPane.css:
--------------------------------------------------------------------------------
1 | .sidebar-pane-header {
2 | display: flex;
3 | align-items: center;
4 | background-color: #eee;
5 | height: 20px;
6 | padding: 3px 10px;
7 | border-top: 1px solid #dadada;
8 | border-bottom: 1px solid #dadada;
9 | white-space: nowrap;
10 | overflow: hidden;
11 | color: #222;
12 | font-size: 12px;
13 | font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;
14 | }
15 |
16 | .sidebar-pane-content {
17 | font-size: 13px;
18 | }
19 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/SidebarPane.js:
--------------------------------------------------------------------------------
1 | import Regular from 'regularjs';
2 | import './SidebarPane.css';
3 |
4 | const SidebarPane = Regular.extend({
5 | template: `
6 |
14 | `
15 | });
16 |
17 | export default SidebarPane;
18 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/SimpleJsonTree.js:
--------------------------------------------------------------------------------
1 | import Regular from 'regularjs';
2 |
3 | const SimpleJsonTree = Regular.extend({
4 | template: `
5 |
6 | {#list Object.keys(source) as k}
7 |
8 |
9 | {k}{source[k] ===''? '': ':'}
10 | {source[k]}
11 |
12 |
13 | {/list}
14 |
15 | `,
16 | config() {
17 | this.data.source = this.data.source || {};
18 | }
19 | });
20 |
21 | export default SimpleJsonTree;
22 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/Tabs.css:
--------------------------------------------------------------------------------
1 | .tabs {
2 | font-family: Consolas, Lucida Console, monospace;
3 | }
4 |
5 | .tabs-header {
6 | position: relative;
7 | overflow: hidden;
8 | background-color: #f3f3f3;
9 | border-bottom: 1px solid #ccc;
10 | font-size: 12px;
11 | user-select: none;
12 | -webkit-user-select: none;
13 | }
14 |
15 | .tab-indicator{
16 | height: 1px;
17 | width: 60px;
18 | background-color: #3E82F7;
19 | position: absolute;
20 | left: 0;
21 | bottom: 0;
22 | transform: translateX(0);
23 | transition: transform 150ms cubic-bezier(0, 0, 0.2, 1);
24 | }
25 |
26 | .tabs-header-item {
27 | box-sizing: border-box;
28 | float: left;
29 | padding: 0 6px;
30 | width: 60px;
31 | height: 25px;
32 | border-bottom: none;
33 | line-height: 15px;
34 | white-space: nowrap;
35 | cursor: default;
36 | display: flex;
37 | align-items: center;
38 | justify-content: center;
39 | color: #5a5a5a;
40 | font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;
41 | outline: none;
42 | }
43 |
44 | .tabs-header-item:hover {
45 | color: #333;
46 | background-color: #e5e5e5;
47 | }
48 |
--------------------------------------------------------------------------------
/src/devtools-ui/components/Tabs.js:
--------------------------------------------------------------------------------
1 | import Regular from 'regularjs';
2 | import './Tabs.css';
3 |
4 | const Tabs = Regular.extend({
5 | template: `
6 |
7 |
26 |
27 | `,
28 | onTabClick: function(key) {
29 | if (this.data.selected === key) {
30 | return;
31 | }
32 | this.$emit('change', key);
33 | }
34 | });
35 |
36 | export default Tabs;
37 |
--------------------------------------------------------------------------------
/src/devtools-ui/events/enter.js:
--------------------------------------------------------------------------------
1 | export default function(Component, Regular) {
2 | const dom = Regular.dom;
3 |
4 | Component.event('enter', function(elem, fire) {
5 | function update(ev) {
6 | if (ev.which === 13) { // ENTER key
7 | ev.preventDefault();
8 | fire(ev); // if key is enter , we fire the event;
9 | }
10 | }
11 | dom.on(elem, "keypress", update);
12 | return function destroy() { // return a destroy function
13 | dom.off(elem, "keypress", update);
14 | };
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/src/devtools-ui/events/index.js:
--------------------------------------------------------------------------------
1 | import enter from './enter';
2 | import input from './input';
3 | import mouseenter from './mouseenter';
4 | import mouseleave from './mouseleave';
5 |
6 | export {
7 | enter,
8 | input,
9 | mouseenter,
10 | mouseleave
11 | };
12 |
--------------------------------------------------------------------------------
/src/devtools-ui/events/input.js:
--------------------------------------------------------------------------------
1 | export default function(Component, Regular) {
2 | const dom = Regular.dom;
3 |
4 | Component.event('input', function(elem, fire) {
5 | function update(ev) {
6 | fire(ev); // if key is enter , we fire the event;
7 | }
8 | dom.on(elem, "input", update);
9 | return function destroy() { // return a destroy function
10 | dom.off(elem, "input", update);
11 | };
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/src/devtools-ui/events/mouseenter.js:
--------------------------------------------------------------------------------
1 | export default function(Component, Regular) {
2 | const dom = Regular.dom;
3 |
4 | Component.event('mouseenter', function(elem, fire) {
5 | function update(ev) {
6 | fire(ev);
7 | }
8 | dom.on(elem, "mouseenter", update);
9 | return function destroy() { // return a destroy function
10 | dom.off(elem, "mouseenter", update);
11 | };
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/src/devtools-ui/events/mouseleave.js:
--------------------------------------------------------------------------------
1 | export default function(Component, Regular) {
2 | const dom = Regular.dom;
3 |
4 | Regular.event('mouseleave', function(elem, fire) {
5 | function update(ev) {
6 | fire(ev);
7 | }
8 | dom.on(elem, "mouseleave", update);
9 | return function destroy() { // return a destroy function
10 | dom.off(elem, "mouseleave", update);
11 | };
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/src/devtools-ui/fonts/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/regularjs/regular-devtools/3782e24f1d2c79de527356f984e0a6b830edc5ea/src/devtools-ui/fonts/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/src/devtools-ui/index.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "Roboto";
3 | src:url("./fonts/Roboto-Regular.ttf");
4 | }
5 |
6 | html {
7 | height: 100%;
8 | }
9 |
10 | body {
11 | margin: 0;
12 | height: 100%;
13 | font-family: Consolas, Lucida Console, Menlo, monospace, '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;
14 | }
15 |
16 | #app {
17 | box-sizing: border-box;
18 | padding-top: 60px;
19 | width: 100%;
20 | height: 100%;
21 | overflow: hidden;
22 | font-size: 0;
23 | }
24 |
25 | .roboto {
26 | font-family: "Roboto";
27 | }
28 |
29 | /* main start */
30 |
31 | .devtoolsMain {
32 | display: flex;
33 | height: 100%;
34 | }
35 |
36 | /* main end */
37 |
38 | /* util start */
39 |
40 | .purplee {
41 | font-size: 18px;
42 | color: rgb(136, 18, 128);
43 | }
44 |
45 | .purple {
46 | color: rgb(136, 18, 128);
47 | }
48 |
49 | .gray {
50 | color: gray;
51 | }
52 |
53 | .blue {
54 | color: blue;
55 | }
56 |
57 | .bold {
58 | font-weight: bold;
59 | }
60 |
61 | /* util end */
62 |
--------------------------------------------------------------------------------
/src/devtools-ui/index.js:
--------------------------------------------------------------------------------
1 | // the real devtools script
2 | // the UI layer of devtools
3 | import Regular from "regularjs";
4 | import 'material-design-lite';
5 | import CircularJSON from "../shared/circular-json";
6 | import log from '../shared/log';
7 | import {enter, input, mouseenter, mouseleave} from './events';
8 | import {
9 | printInConsole,
10 | findElementByUUID,
11 | inspectNodeByUUID,
12 | showDefinitionByUUID,
13 | updateInstanceByUUIDAndPath,
14 | evalHighLightNode,
15 | getData,
16 | getOthersData,
17 | makeElementTree,
18 | patch
19 | } from './utils';
20 | import agent from './agent';
21 | import Devtools from './components/Devtools';
22 |
23 | import 'material-design-lite/dist/material.min.css';
24 | import './index.css';
25 |
26 | // register custom events
27 | Regular.use(enter);
28 | Regular.use(input);
29 | Regular.use(mouseenter);
30 | Regular.use(mouseleave);
31 |
32 | // directive for MDL component registeration
33 | Regular.directive('r-md', function(elem, value) {
34 | componentHandler.upgradeElement(elem);
35 | });
36 |
37 | // variables
38 | let ready = false;
39 |
40 | // devtools
41 | const devtools = new Devtools({
42 | data: {
43 | nodes: [],
44 | lastSelected: null
45 | }
46 | }).$inject("#app");
47 | // left element view
48 | const elementView = devtools.$refs.elementView;
49 | // right sidebar view
50 | const sidebarView = devtools.$refs.sidebarView;
51 | // searchView
52 | const searchView = elementView.$refs.searchView;
53 |
54 | function displayWarning() {
55 | if (elementView.data.loading) {
56 | elementView.data.loading = false;
57 | elementView.$update();
58 | }
59 | }
60 |
61 | // listen for custom events
62 | devtools
63 | .$on("initNodes", function(nodesStr) {
64 | let nodes = CircularJSON.parse(nodesStr);
65 | log("initNodes", nodes);
66 | this.data.nodes = nodes;
67 |
68 | elementView.data.loading = false;
69 | elementView.data.nodes = makeElementTree(nodes, []);
70 | elementView.$update();
71 |
72 | // init sidebar
73 | sidebarView.data.currentNode.name = nodes[0].name;
74 | sidebarView.data.currentNode.uuid = nodes[0].uuid;
75 | sidebarView.data.currentNode.inspectable = nodes[0].inspectable;
76 | sidebarView.$emit('updateData', nodes[0].uuid);
77 | ready = true;
78 | })
79 | .$on("clickElement", function(uuid) {
80 | if (uuid !== sidebarView.data.currentNode.uuid) {
81 | let currentNode = findElementByUUID(this.data.nodes, uuid);
82 | sidebarView.data.currentNode.name = currentNode.name;
83 | sidebarView.data.currentNode.inspectable = currentNode.inspectable;
84 | sidebarView.data.currentNode.uuid = uuid;
85 | sidebarView.$emit('updateData', uuid);
86 | sidebarView.$emit('updateOthersData', uuid);
87 | }
88 | printInConsole(uuid);
89 | })
90 | .$on("stateViewReRender", function(nodesStr) {
91 | log("On stateViewRender.");
92 | sidebarView.$emit('updateData', sidebarView.data.currentNode.uuid);
93 | })
94 | .$on("elementViewReRender", function(nodesStr) {
95 | log("On elementViewRerender.");
96 | let nodes = CircularJSON.parse(nodesStr);
97 | this.data.nodes = nodes;
98 | // need refactor
99 | /* eslint-disable no-unused-vars */
100 | var oldArr = elementView.data.nodes;
101 | var newArr = makeElementTree(nodes, []);
102 | elementView.data.nodes = patch(oldArr, newArr);
103 | /* eslint-enable no-unused-vars */
104 | elementView.$update();
105 | })
106 | .$on("currentNodeChange", function(uuid) {
107 | log("On currentNodeChange.");
108 | if (sidebarView.data.currentNode.uuid !== uuid) {
109 | devtools.focusNode(uuid);
110 | }
111 | })
112 | .$on("reload", function(event) {
113 | ready = false;
114 | log("On reload.");
115 | searchView.reset();
116 | // wait for the page to fully intialize
117 | setTimeout(function() {
118 | agent.injectContentScript(event.tabId);
119 | setTimeout(displayWarning, 4000);
120 | }, 2000);
121 | })
122 | .$on("refresh", () => {
123 | chrome.devtools.inspectedWindow.reload();
124 | })
125 | .$on("openNewTab", function(url) {
126 | agent.openInNewTab(url);
127 | });
128 |
129 | sidebarView
130 | .$on("dataChange", ({uuid, path, value}) => {
131 | updateInstanceByUUIDAndPath({uuid, path, value});
132 | })
133 | .$on("inspectNode", uuid => {
134 | inspectNodeByUUID(uuid);
135 | })
136 | .$on("showDefinition", uuid => {
137 | showDefinitionByUUID(uuid);
138 | })
139 | .$on("highLightNode", ({uuid, inspectable}) => {
140 | if (!sidebarView.data.lockHighlight) {
141 | evalHighLightNode(uuid, inspectable);
142 | }
143 | })
144 | .$on("updateData", uuid => {
145 | getData(uuid).then(data => {
146 | let currentNode = CircularJSON.parse(data);
147 | sidebarView.data.currentNode.data = currentNode.data;
148 | sidebarView.data.currentNode.computed = currentNode.computed;
149 | sidebarView.$update();
150 | });
151 | })
152 | .$on("lockHighLight", flag => {
153 | sidebarView.data.lockHighlight = flag;
154 | })
155 | .$on("updateOthersData", uuid => {
156 | getOthersData(uuid).then(data => {
157 | sidebarView.data.others = data;
158 | sidebarView.$update();
159 | });
160 | });
161 |
162 | agent.on('dataUpdate', () => devtools.$emit("stateViewReRender"));
163 | agent.on('reRender', nodes => devtools.$emit("elementViewReRender", nodes));
164 | agent.on('initNodes', nodes => devtools.$emit("initNodes", nodes));
165 | agent.on('currNodeChange', uuid => {
166 | if (ready) {
167 | devtools.$emit("currentNodeChange", uuid);
168 | }
169 | });
170 | agent.on('pageReload', tabId => {
171 | elementView.data.loading = true;
172 | elementView.$update();
173 | devtools.$emit("reload", {tabId});
174 | });
175 |
176 | agent.injectContentScript();
177 |
178 | // waiting 4000ms, if still loading, remove loading and show warning
179 | setTimeout(displayWarning, 4000);
180 |
--------------------------------------------------------------------------------
/src/devtools-ui/media/arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/devtools-ui/media/at-sign.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/devtools-ui/media/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/devtools-ui/media/inspect.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/devtools-ui/media/loading.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/devtools-ui/media/next.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/devtools-ui/media/prev.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/devtools-ui/media/refresh.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/devtools-ui/media/regular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/regularjs/regular-devtools/3782e24f1d2c79de527356f984e0a6b830edc5ea/src/devtools-ui/media/regular.png
--------------------------------------------------------------------------------
/src/devtools-ui/media/search.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/devtools-ui/media/target.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/devtools-ui/media/target_active.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/devtools-ui/port.js:
--------------------------------------------------------------------------------
1 | // Create a current inspected page unique connection to the background page, by its tabId
2 | export default chrome.runtime.connect({
3 | name: "devToBackCon_" + chrome.devtools.inspectedWindow.tabId
4 | });
5 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/findElement.js:
--------------------------------------------------------------------------------
1 | export function findElementByUUID(nodes, uuid) {
2 | for (var i = 0; i < nodes.length; i++) {
3 | if (nodes[i].uuid === uuid) {
4 | return nodes[i];
5 | }
6 | if (nodes[i].childNodes.length) {
7 | var result = findElementByUUID(nodes[i].childNodes, uuid);
8 | if (result) {
9 | return result;
10 | }
11 | }
12 | }
13 | }
14 |
15 | export function findElementByUUIDNonRecursive(nodes, uuid) {
16 | for (var i = 0; i < nodes.length; i++) {
17 | if (nodes[i].uuid === uuid) {
18 | return nodes[i];
19 | }
20 | }
21 | }
22 |
23 | export function findElementByName(nodes, reg, container) {
24 | for (var i = 0; i < nodes.length; i++) {
25 | if (reg.test(nodes[i].name)) {
26 | container.push(nodes[i].uuid);
27 | }
28 | if (nodes[i].childNodes.length) {
29 | findElementByName(nodes[i].childNodes, reg, container);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/getData.js:
--------------------------------------------------------------------------------
1 | import log from '../../shared/log';
2 |
3 | function getData(uuid) {
4 | var node = window.__REGULAR_DEVTOOLS_GLOBAL_HOOK__.ins.filter(function(n) {
5 | return n.uuid === uuid;
6 | })[0];
7 | return window.devtoolsModel.stringify({
8 | name: node.name || "[anonymous]",
9 | uuid: uuid,
10 | data: node.data,
11 | computed: window.devtoolsModel.fetchComputedProps(node)
12 | });
13 | }
14 |
15 | const getDataStr = getData.toString();
16 |
17 | export default function(uuid) {
18 | return new Promise((resolve, reject) => {
19 | chrome.devtools.inspectedWindow.eval(
20 | `(${getDataStr})(${JSON.stringify(uuid)})`,
21 | function(result, isException) {
22 | if (isException) {
23 | log("Get Data Error: ", isException);
24 | reject(isException);
25 | return;
26 | }
27 | resolve(result);
28 | }
29 | );
30 | });
31 | }
32 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/getOthersData.js:
--------------------------------------------------------------------------------
1 | import log from '../../shared/log';
2 |
3 | function getOthersData(uuid) {
4 | var othersNameArr = ['_directives', '_filters', '_animations'];
5 | var node = window.__REGULAR_DEVTOOLS_GLOBAL_HOOK__.ins.filter(function(n) {
6 | return n.uuid === uuid;
7 | })[0];
8 | if (node) {
9 | var constructor = node.constructor;
10 | var result = {};
11 | for (var prop in constructor) {
12 | if (constructor.hasOwnProperty(prop) && othersNameArr.indexOf(prop) !== -1) {
13 | var tempObj = {};
14 | var curObj = constructor[prop];
15 | var curUI = constructor.prototype;
16 | while (curObj && curUI) {
17 | var tempArr = [];
18 | for (var key in curObj) {
19 | if (curObj.hasOwnProperty(key)) {
20 | tempArr.push(key);
21 | }
22 | }
23 | /* eslint-disable no-proto, no-loop-func */
24 |
25 | tempArr.sort(); // same level sort
26 | tempArr.forEach(function(value) {
27 | if (!tempObj[value]) { // same command big level not show
28 | if (curUI.constructor._addProtoInheritCache) {
29 | tempObj[value] = "[regular]";
30 | } else if (curUI.reset && !curUI.__proto__.reset && curUI.__proto__.constructor._addProtoInheritCache) {
31 | var funStr = curUI.reset.toString();
32 | if (funStr.indexOf("this.data = {}") !== -1 && funStr.indexOf("this.config()") !== -1) {
33 | tempObj[value] = "[regular-ui]"; // very low possible be developer's Component
34 | } else {
35 | tempObj[value] = curUI.name === undefined ? '' : curUI.name;
36 | }
37 | } else {
38 | tempObj[value] = curUI.name === undefined ? '' : curUI.name; // same level same color
39 | }
40 | }
41 | });
42 | curObj = curObj.__proto__;
43 | curUI = curUI.__proto__;
44 |
45 | /* eslint-enable no-proto, no-loop-func*/
46 | }
47 |
48 | result[prop] = tempObj;
49 | }
50 | }
51 | return result;
52 | }
53 | }
54 |
55 | const getOthersDataStr = getOthersData.toString();
56 |
57 | export default function(uuid) {
58 | return new Promise((resolve, reject) => {
59 | chrome.devtools.inspectedWindow.eval(
60 | `(${getOthersDataStr})(${JSON.stringify(uuid)})`,
61 | function(result, isException) {
62 | if (isException) {
63 | log("Get Others Data Error: ", isException);
64 | reject(isException);
65 | return;
66 | }
67 | resolve(result);
68 | }
69 | );
70 | });
71 | }
72 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/highLighter.js:
--------------------------------------------------------------------------------
1 | let maskNode;
2 | let labelNode;
3 |
4 | const setLabelPositon = function(node, rect) {
5 | var w = Math.max(document.documentElement.clientWidth,
6 | window.innerWidth || 0);
7 | var h = Math.max(document.documentElement.clientHeight,
8 | window.innerHeight || 0);
9 |
10 | // detect if rect resides in the viewport
11 | if (rect.top >= 0 && rect.top <= h && rect.left >= 0 && rect.left <= w) {
12 | // set vertical
13 | if (rect.top > 34) {
14 | node.style.top = window.scrollY + rect.top - 29 + "px";
15 | } else if ((h - rect.top - rect.height) > 34) {
16 | node.style.top = window.scrollY + rect.top + rect.height + 5 + "px";
17 | } else {
18 | node.style.top = window.scrollY + rect.top + "px";
19 | }
20 |
21 | // set horizontal
22 | if (rect.left > 120) {
23 | node.style.left = rect.left + "px";
24 | } else if ((h - rect.left - rect.width) > 120) {
25 | node.style.left = rect.left + "px";
26 | } else {
27 | node.style.left = rect.left + "px";
28 | }
29 | } else {
30 | if (rect.top < 0) {
31 | node.style.top = window.scrollY + "px";
32 | } else if (rect.top > h) {
33 | node.style.top = window.scrollY + h - 24 + "px";
34 | }
35 |
36 | if (rect.left < 0) {
37 | node.style.left = window.scrollX + "px";
38 | } else if (rect.left > w) {
39 | node.style.left = window.scrollX + w - 100 + "px";
40 | }
41 |
42 | if (!node.style.left) node.style.left = rect.left + "px";
43 | if (!node.style.top) node.style.top = rect.top + "px";
44 | }
45 | };
46 |
47 | export function clearMask() {
48 | if (maskNode) {
49 | document.querySelector("body").removeChild(maskNode);
50 | document.querySelector("body").removeChild(labelNode);
51 | maskNode = null;
52 | labelNode = null;
53 | }
54 | }
55 |
56 | export function highLightNode(domNode, name) {
57 | var rect = domNode.getBoundingClientRect();
58 | clearMask();
59 |
60 | // draw mask
61 | maskNode = document.createElement("div");
62 | maskNode.style.position = "absolute";
63 | maskNode.style.left = rect.left + "px";
64 | maskNode.style.top = rect.top + window.scrollY + "px";
65 | maskNode.style.width = rect.width + window.scrollX + "px";
66 | maskNode.style.height = rect.height + "px";
67 | maskNode.style.backgroundColor = "rgba(145, 183, 228, 0.6)";
68 | maskNode.style.zIndex = 999999;
69 | maskNode.style.pointerEvents = "none";
70 | document.querySelector("body").appendChild(maskNode);
71 |
72 | // draw label
73 | var demensionStr = "\n" + rect.width.toFixed(0) + "×" + rect.height.toFixed(0);
74 | labelNode = document.createElement("div");
75 | labelNode.textContent = name + demensionStr;
76 | labelNode.style.backgroundColor = "#272931";
77 | labelNode.style.color = "#fff";
78 | labelNode.style.position = "absolute";
79 | labelNode.style.padding = "0 10px";
80 | labelNode.style.height = "24px";
81 | labelNode.style.lineHeight = "24px";
82 | labelNode.style.fontSize = "12px";
83 | labelNode.style.borderRadius = "2px";
84 | labelNode.style.zIndex = 999999;
85 | setLabelPositon(labelNode, rect);
86 | document.querySelector("body").appendChild(labelNode);
87 | }
88 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/highlightNode.js:
--------------------------------------------------------------------------------
1 | import log from '../../shared/log';
2 |
3 | export function evalHighLightNode(uuid, inspectable) {
4 | var evalStr = inspectable ? "devtoolsModel.highLighter('" + uuid + "')" : "devtoolsModel.highLighter()";
5 | chrome.devtools.inspectedWindow.eval(
6 | evalStr,
7 | function(result, isException) {
8 | if (isException) {
9 | log("HightLight Error: ", isException);
10 | }
11 | }
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/index.js:
--------------------------------------------------------------------------------
1 | import isPrimitive from './isPrimitive';
2 | import type from './type.js';
3 | import searchPath from './searchPath.js';
4 | import printInConsole from './printInConsole.js';
5 | import {findElementByUUID, findElementByName, findElementByUUIDNonRecursive} from './findElement.js';
6 | import {inspectNodeByUUID} from './inspectNode';
7 | import showDefinitionByUUID from './showDefinitionByUUID';
8 | import {evalHighLightNode} from './highlightNode';
9 | import {clearMask, highLightNode} from './highLighter';
10 | import {updateInstanceByUUIDAndPath} from './updateInstance';
11 | import {enter, exit} from './inspectComponent';
12 | import getData from './getData';
13 | import getOthersData from './getOthersData';
14 | import makeElementTree from './makeElementTree';
15 | import patch from './patch';
16 |
17 | export {
18 | isPrimitive,
19 | type,
20 | searchPath,
21 | printInConsole,
22 | findElementByUUID,
23 | findElementByName,
24 | findElementByUUIDNonRecursive,
25 | inspectNodeByUUID,
26 | showDefinitionByUUID,
27 | updateInstanceByUUIDAndPath,
28 | evalHighLightNode,
29 | highLightNode,
30 | clearMask,
31 | getData,
32 | getOthersData,
33 | makeElementTree,
34 | patch,
35 | enter,
36 | exit
37 | };
38 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/inspectComponent.js:
--------------------------------------------------------------------------------
1 | import log from '../../shared/log';
2 |
3 | function inspectComponent(type) {
4 | if (type === "enter") {
5 | window.devtoolsModel.enterInspectMode();
6 | } else {
7 | window.devtoolsModel.exitInspectMode();
8 | }
9 | }
10 |
11 | const funcStr = inspectComponent.toString();
12 |
13 | export function enter() {
14 | return new Promise((resolve, reject) => {
15 | chrome.devtools.inspectedWindow.eval(
16 | `(${funcStr})('enter')`,
17 | function(result, isException) {
18 | if (isException) {
19 | log("Get Data Error: ", isException);
20 | reject(isException);
21 | return;
22 | }
23 | resolve(result);
24 | }
25 | );
26 | });
27 | }
28 |
29 | export function exit() {
30 | return new Promise((resolve, reject) => {
31 | chrome.devtools.inspectedWindow.eval(
32 | `(${funcStr})('exit')`,
33 | function(result, isException) {
34 | if (isException) {
35 | log("Get Data Error: ", isException);
36 | reject(isException);
37 | return;
38 | }
39 | resolve(result);
40 | }
41 | );
42 | });
43 | }
44 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/inspectNode.js:
--------------------------------------------------------------------------------
1 | import log from '../../shared/log';
2 |
3 | export function inspectNodeByUUID(uuid) {
4 | chrome.devtools.inspectedWindow.eval(
5 | `
6 | var node = window.__REGULAR_DEVTOOLS_GLOBAL_HOOK__.ins.filter(function(n) {
7 | return n.uuid === '${uuid}'
8 | })[0];
9 | if (node) {
10 | inspect(node.group && node.group.children && node.group.children[0] && node.group.children[0].node && node.group.children[0].node() || node.parentNode);
11 | }
12 | `,
13 | function(result, isException) {
14 | if (isException) {
15 | log("Inspect Error: ", isException);
16 | }
17 | }
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/isPrimitive.js:
--------------------------------------------------------------------------------
1 | export default function isPrimitive(arg) {
2 | var type = typeof arg;
3 | return arg === null || (type !== "object" && type !== "function");
4 | }
5 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/makeElementTree.js:
--------------------------------------------------------------------------------
1 | export default function makeElementTree(nodes, container) {
2 | for (var i = 0; i < nodes.length; i++) {
3 | var node = {
4 | ...nodes[i],
5 | childNodes: []
6 | };
7 | container.push(node);
8 | if (nodes[i].childNodes.length) {
9 | makeElementTree(nodes[i].childNodes, node.childNodes);
10 | }
11 | }
12 | return container;
13 | }
14 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/patch.js:
--------------------------------------------------------------------------------
1 | import {findElementByUUIDNonRecursive} from "./findElement";
2 |
3 | // reuse old nodes
4 | export default function patch(oldArr, newArr) {
5 | const container = [];
6 | for (var i = 0; i < newArr.length; i++) {
7 | var newNode = newArr[i];
8 | var oldNode = findElementByUUIDNonRecursive(oldArr, newArr[i].uuid);
9 | if (oldNode) {
10 | if (JSON.stringify(oldNode) !== JSON.stringify(newNode)) {
11 | oldNode.name = newNode.name;
12 | oldNode.isIncluded = newNode.isIncluded;
13 | oldNode.childNodes = patch(oldNode.childNodes, newNode.childNodes, []);
14 | }
15 | container.push(oldNode);
16 | } else {
17 | container.push(newNode);
18 | }
19 | }
20 | return container;
21 | }
22 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/printInConsole.js:
--------------------------------------------------------------------------------
1 | import log from '../../shared/log';
2 |
3 | export default function(uuid) {
4 | chrome.devtools.inspectedWindow.eval(
5 | `devtoolsModel.print(${JSON.stringify(uuid)})`,
6 | function(result, isException) {
7 | if (isException) {
8 | log("Print Error: ", isException);
9 | }
10 | }
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/searchPath.js:
--------------------------------------------------------------------------------
1 | const searchPath = function(nodes, uuid, path) {
2 | for (var i = 0; i < nodes.length; i++) {
3 | if (nodes[i].data.node.uuid === uuid) {
4 | path.push(nodes[i]);
5 | return true;
6 | } else if (nodes[i]._children.length > 0) {
7 | if (searchPath(nodes[i]._children, uuid, path)) {
8 | path.push(nodes[i]);
9 | return true;
10 | }
11 | }
12 | }
13 | return false;
14 | };
15 |
16 | export default function searchPathWarpper(nodes, uuid, path) {
17 | for (var i = 0; i < nodes.length; i++) {
18 | if (nodes[i].data.node && nodes[i].data.node.uuid === uuid) {
19 | path.push(nodes[i]);
20 | return path;
21 | } else if (searchPath(nodes[i]._children, uuid, path)) {
22 | path.push(nodes[i]);
23 | return path;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/showDefinitionByUUID.js:
--------------------------------------------------------------------------------
1 | import log from '../../shared/log';
2 |
3 | export default function showDefinitionByUUID(uuid) {
4 | chrome.devtools.inspectedWindow.eval(
5 | `
6 | var node = window.__REGULAR_DEVTOOLS_GLOBAL_HOOK__.ins.filter(function(n) {
7 | return n.uuid === '${uuid}'
8 | })[0];
9 | if (node) {
10 | var proto = node.__proto__;
11 | var hasConfig = proto.hasOwnProperty('config') && typeof proto.config === 'function';
12 | var hasInit = proto.hasOwnProperty('init') && typeof proto.init === 'function';
13 |
14 | var found = false
15 | if (hasConfig) {
16 | inspect(proto.config);
17 | } else if (hasInit) {
18 | inspect(proto.init);
19 | } else {
20 | for(var i in proto) {
21 | if (i !== 'constructor' && proto.hasOwnProperty(i) && typeof proto[i] === 'function') {
22 | inspect(proto[i]);
23 | found = true;
24 | break;
25 | }
26 | }
27 |
28 | if(!found){
29 | inspect(proto.constructor);
30 | }
31 | }
32 | }
33 | `,
34 | function(result, isException) {
35 | if (isException) {
36 | log("Show definition Error: ", isException);
37 | }
38 | }
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/type.js:
--------------------------------------------------------------------------------
1 | export default function type(obj) {
2 | return Object.prototype.toString.call(obj).slice(8, -1);
3 | }
4 |
--------------------------------------------------------------------------------
/src/devtools-ui/utils/updateInstance.js:
--------------------------------------------------------------------------------
1 | export function updateInstanceByUUIDAndPath({uuid, path, value}) {
2 | // send message to page, update instance by uuid and path
3 | var fn = function(uuid, path, value) {
4 | window.postMessage({
5 | type: 'FROM_CONTENT_SCRIPT',
6 | action: 'UPDATE_INSTANCE',
7 | payload: {
8 | uuid: uuid,
9 | path: path,
10 | value: value
11 | }
12 | }, '*');
13 | };
14 | chrome.devtools.inspectedWindow.eval(
15 | '(' + fn + ')(' + JSON.stringify(uuid) + ',' + JSON.stringify(path) + ',' + JSON.stringify(value) + ')', {
16 | useContentScriptContext: true
17 | },
18 | function() {}
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/electron/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/regularjs/regular-devtools/3782e24f1d2c79de527356f984e0a6b830edc5ea/src/electron/.gitkeep
--------------------------------------------------------------------------------
/src/extension/background.js:
--------------------------------------------------------------------------------
1 | // background.js, runs presistently
2 | // inject content script for devtools page
3 | // pass message from content script to devtools page
4 | var prefix = "[Regular Devtools] ";
5 | var injectToBackConnection;
6 |
7 | // for every page's different injectToBackConnection to its devToBackConnection according to tab.id
8 | var devToBackConMap = {};
9 |
10 | var injectListener = function(message, sender, sendResponse) {
11 | if (message.file) {
12 | // Inject a content script into the identified tab
13 | chrome.tabs.executeScript(message.tabId, {
14 | file: message.file
15 | });
16 | console.log(prefix + "Content script injected.");
17 | }
18 | };
19 |
20 | var openNewTabListener = function(message, sender, sendResponse) {
21 | if (message.url) {
22 | chrome.tabs.create({url: message.url}, () => {});
23 | console.log(prefix + "Open new tab: " + message.url);
24 | }
25 | };
26 |
27 | // cannot put in onConnect.addListener() for everyConnect add one callbackFun
28 | chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) {
29 | if (changeInfo.status === "complete") {
30 | console.log(prefix + "Backend send pageReload event");
31 |
32 | devToBackConMap[tabId].postMessage({
33 | type: "pageReload",
34 | tabId: tabId
35 | });
36 | }
37 | });
38 |
39 | chrome.runtime.onConnect.addListener(function(connection) {
40 | // add the listener
41 | if (connection.name.indexOf("devToBackCon") !== -1) {
42 | var tabId = connection.name.split('_')[1];
43 | console.log("dev2Back get connect with tabId: " + tabId);
44 | devToBackConMap[tabId] = connection;
45 | devToBackConMap[tabId].onMessage.addListener(injectListener);
46 | devToBackConMap[tabId].onMessage.addListener(openNewTabListener);
47 | return;
48 | }
49 |
50 | // Receive message from content script and relay to its devTools page according sender tab.id
51 | if (connection.name === "injectToBackCon") {
52 | injectToBackConnection = connection;
53 | console.log(prefix + "injectToBack Connection established.", connection);
54 | injectToBackConnection.onMessage.addListener(function(request, sender, sendResponse) {
55 | console.log(prefix + "Backend received message:" + request.type);
56 | console.log("inject2Back sent info with tabId: " + sender.sender.tab.id);
57 | devToBackConMap[sender.sender.tab.id].postMessage(request);
58 | return true;
59 | });
60 | }
61 | });
62 |
--------------------------------------------------------------------------------
/src/extension/content.js:
--------------------------------------------------------------------------------
1 | // this is the content script runs when the panel is activated.
2 | // this script serves as the brigde between app page script(inject and hook) and the backend script
3 |
4 | var port = chrome.runtime.connect({
5 | name: "injectToBackCon"
6 | });
7 |
8 | function injectScript(file, node) {
9 | return new Promise(function(resolve, reject) {
10 | var th = document.getElementsByTagName(node)[0];
11 | var s = document.createElement('script');
12 | s.setAttribute('type', 'text/javascript');
13 | s.setAttribute('src', file);
14 | th.appendChild(s);
15 | s.onload = function() {
16 | resolve();
17 | };
18 | });
19 | }
20 |
21 | window.addEventListener("message", function(event) {
22 | // We only accept messages from ourselves
23 | if (event.source !== window)
24 | return;
25 |
26 | if (event.data.type && (event.data.type === "FROM_PAGE")) {
27 | port.postMessage(event.data.data);
28 | }
29 | }, false);
30 |
31 | injectScript(chrome.extension.getURL('/inject.bundle.js'), 'body');
32 |
--------------------------------------------------------------------------------
/src/extension/devtools.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Regular Developer Tools
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/extension/devtools.js:
--------------------------------------------------------------------------------
1 | // create new panel for devtools
2 | chrome.devtools.panels.create(
3 | "Regular",
4 | "/regular.png",
5 | "/index.html",
6 | function(panel) {
7 | panel.onShown.addListener(function(extPanelWindow) {
8 | chrome.devtools.inspectedWindow.eval(
9 | "window.__REGULAR_DEVTOOLS_GLOBAL_HOOK__.contain($0)",
10 | function(result, isException) {
11 | if (!isException && result) {
12 | extPanelWindow.postMessage({
13 | type: "currNodeChange",
14 | uuid: result
15 | }, "*");
16 | }
17 | }
18 | );
19 | });
20 | }
21 | );
22 |
--------------------------------------------------------------------------------
/src/extension/hook.js:
--------------------------------------------------------------------------------
1 | // https://github.com/vuejs/vue-devtools/blob/master/src/backend/hook.js
2 | // this script is injected into every page.
3 |
4 | /**
5 | * Install the hook on window, which is an event emitter.
6 | * Note because Chrome content scripts cannot directly modify the window object,
7 | * we are evaling this function by inserting a script tag. That's why we have
8 | * to inline the whole event emitter implementation here.
9 | *
10 | * @param {Window} window
11 | */
12 |
13 | function installHook(window) {
14 | var listeners = {};
15 |
16 | var hook = {
17 | ins: [],
18 |
19 | location: {
20 | finalUUID: "",
21 | level: 0
22 | },
23 |
24 | on: function(event, fn) {
25 | event = '$' + event;
26 | (listeners[event] || (listeners[event] = [])).push(fn);
27 | },
28 |
29 | once: function(event, fn) {
30 | event = '$' + event;
31 |
32 | function on() {
33 | this.off(event, on);
34 | fn.apply(this, arguments);
35 | }
36 | (listeners[event] || (listeners[event] = [])).push(on);
37 | },
38 |
39 | off: function(event, fn) {
40 | event = '$' + event;
41 | if (!arguments.length) {
42 | listeners = {};
43 | } else {
44 | const cbs = listeners[event];
45 | if (cbs) {
46 | if (!fn) {
47 | listeners[event] = null;
48 | } else {
49 | for (let i = 0, l = cbs.length; i < l; i++) {
50 | const cb = cbs[i];
51 | if (cb === fn || cb.fn === fn) {
52 | cbs.splice(i, 1);
53 | break;
54 | }
55 | }
56 | }
57 | }
58 | }
59 | },
60 |
61 | emit: function(event) {
62 | event = '$' + event;
63 | let cbs = listeners[event];
64 | if (cbs) {
65 | const args = [].slice.call(arguments, 1);
66 | cbs = cbs.slice();
67 | for (let i = 0, l = cbs.length; i < l; i++) {
68 | cbs[i].apply(this, args);
69 | }
70 | }
71 | },
72 |
73 | contain: function(selectedNode) {
74 | this.location.finalUUID = "";
75 | this.location.level = 0;
76 | // devtoolsModel is in global scope
77 | // TODO: rename devtoolsModel to __DevtoolsModel__
78 | var nodeTree = devtoolsModel.getNodeTree();
79 | for (var i = 0; i < nodeTree.length; i++) {
80 | this.containByNode(selectedNode, nodeTree[i], 0);
81 | }
82 | return this.location.finalUUID;
83 | },
84 | containByNode: function(selectedNode, node, level) {
85 | var currLevel;
86 | if (node.node.length) {
87 | for (var i = 0; i < node.node.length; i++) {
88 | if (node.node[i].contains(selectedNode)) {
89 | if (level >= this.location.level) {
90 | this.location.finalUUID = node.uuid;
91 | this.location.level = level;
92 | }
93 | }
94 | }
95 | }
96 | if (node.childNodes) {
97 | currLevel = level + 1;
98 | for (var j = 0; j < node.childNodes.length; j++) {
99 | this.containByNode(selectedNode,
100 | node.childNodes[j], currLevel);
101 | }
102 | }
103 | }
104 | };
105 |
106 | // debounce helper
107 | var debounce = function(func, wait, immediate) {
108 | var timeout;
109 | return function() {
110 | var context = this;
111 | var args = arguments;
112 | clearTimeout(timeout);
113 | timeout = setTimeout(function() {
114 | timeout = null;
115 | if (!immediate) func.apply(context, args);
116 | }, wait);
117 | if (immediate && !timeout) func.apply(context, args);
118 | };
119 | };
120 |
121 | var emitRerender = function() {
122 | hook.emit("reRender");
123 | };
124 |
125 | var emitStateRender = function() {
126 | hook.emit("flushMessage");
127 | };
128 |
129 | var reRender = debounce(emitRerender, 500);
130 | var reRenderState = debounce(emitStateRender, 500);
131 |
132 | window.__REGULAR_DEVTOOLS_GLOBAL_HOOK__ = hook;
133 |
134 | hook.on('init', function(obj) {
135 | hook.ins.push(obj);
136 | this.emit('addNodeMessage', obj);
137 | reRender();
138 | });
139 |
140 | hook.on('destroy', function(obj) {
141 | hook.ins.splice(hook.ins.indexOf(obj), 1);
142 | reRender();
143 | });
144 |
145 | hook.on('flush', function() {
146 | reRenderState();
147 | });
148 | }
149 |
150 | // inject the hook
151 | var script = document.createElement('script');
152 | script.textContent = ';(' + installHook.toString() + ')(window)';
153 | document.documentElement.appendChild(script);
154 | script.parentNode.removeChild(script);
155 |
--------------------------------------------------------------------------------
/src/extension/inject.js:
--------------------------------------------------------------------------------
1 | import CircularJSON from "../shared/circular-json";
2 | import {findElementByUUID, highLightNode, clearMask} from '../devtools-ui/utils';
3 | import log from '../shared/log';
4 |
5 | // listen for message from content script
6 | // ensure only executing window.addEventListener once
7 | if (!window.devtoolsModel) {
8 | window.addEventListener('message', function(event) {
9 | // We only accept messages from ourselves
10 | if (event.source !== window)
11 | return;
12 |
13 | var data = event.data;
14 |
15 | if (data.type && (data.type === "FROM_CONTENT_SCRIPT")) {
16 | if (data.action === 'UPDATE_INSTANCE' && window.devtoolsModel) {
17 | window.devtoolsModel.updateInstance(data.payload.uuid, data.payload.path, data.payload.value);
18 | }
19 | }
20 | }, false);
21 | }
22 |
23 | // this is injected to the app page when the panel is activated.
24 | // this script serves as the model layer of the devtools
25 | // this lives in origin page context
26 | window.devtoolsModel = (function() {
27 | let Regular;
28 | let hook = window.__REGULAR_DEVTOOLS_GLOBAL_HOOK__;
29 | let ins = window.__REGULAR_DEVTOOLS_GLOBAL_HOOK__.ins || [];
30 | let store = [];
31 | let fetchComputedProps;
32 | let walker;
33 | let treeGen;
34 | let getDomNode;
35 | let searchGroupChildrenForDomNode;
36 | let getNodesByUUID;
37 | let highLighter;
38 | let inspectMode = false;
39 | let inspectResult = "";
40 |
41 | highLighter = function(uuid) {
42 | if (!uuid) {
43 | clearMask();
44 | return;
45 | }
46 |
47 | var node = ins.filter(n => {
48 | return n.uuid === uuid;
49 | })[0];
50 |
51 | var domNode = getNodesByUUID(uuid);
52 | domNode = domNode && domNode[0];
53 |
54 | if (!domNode) {
55 | return;
56 | }
57 | domNode.scrollIntoView();
58 | highLightNode(domNode, node.name || "[anonymous]");
59 | };
60 |
61 | function onMouseOver(e) {
62 | let result = window.__REGULAR_DEVTOOLS_GLOBAL_HOOK__.contain(e.target);
63 | if (result) {
64 | if (inspectResult !== result) {
65 | inspectResult = result;
66 | highLightNode(e.target, e.target.tagName.toLowerCase());
67 | window.postMessage({
68 | type: "FROM_PAGE",
69 | data: {
70 | type: "currNodeChange",
71 | uuid: inspectResult
72 | }
73 | }, "*");
74 | }
75 | }
76 | }
77 |
78 | // reserve for future use
79 | function onClick(e) {
80 | }
81 |
82 | var stringifyStore = function(store) {
83 | return CircularJSON.stringify(store, function(key, item) {
84 | if ((item !== null && typeof item === "object")) {
85 | if (Object.prototype.toString.call(item) === "[object Object]" && !item.constructor.prototype.hasOwnProperty("isPrototypeOf")) {
86 | return "[Circular]";
87 | } else if (isNode(item) || isElement(item)) {
88 | return "[DOM node]";
89 | }
90 | return item;
91 | } else if (isFunction(item)) {
92 | return "Function";
93 | }
94 | return item;
95 | });
96 | };
97 |
98 | fetchComputedProps = function(ins) {
99 | var computed = {};
100 | Object.keys(ins.computed).forEach(function(v) {
101 | try {
102 | computed[v] = ins.$get(v);
103 | } catch (e) {
104 | log("Fetch computed props error:", e);
105 | }
106 | });
107 | return computed;
108 | };
109 |
110 | function isFunction(o) {
111 | return Object.prototype.toString.call(o) === "[object Function]";
112 | }
113 |
114 | // Returns true if it is a DOM node
115 | function isNode(o) {
116 | return (
117 | typeof Node === "object" ? o instanceof Node :
118 | o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string"
119 | );
120 | }
121 |
122 | // Returns true if it is a DOM element
123 | function isElement(o) {
124 | return (
125 | typeof HTMLElement === "object" ? o instanceof HTMLElement :
126 | o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string"
127 | );
128 | }
129 | // setObjectByPath start
130 | // https://github.com/sindresorhus/dot-prop
131 | var isObj = function(arg) {
132 | var type = typeof arg;
133 | return arg !== null && (type === "object" || type === "function");
134 | };
135 |
136 | function getPathSegments(path) {
137 | var pathArr = path.split('.');
138 | var parts = [];
139 |
140 | for (var i = 0; i < pathArr.length; i++) {
141 | var p = pathArr[i];
142 |
143 | while (p[p.length - 1] === '\\' && pathArr[i + 1] !== undefined) {
144 | p = p.slice(0, -1) + '.';
145 | p += pathArr[++i];
146 | }
147 |
148 | parts.push(p);
149 | }
150 |
151 | return parts;
152 | }
153 |
154 | var setObjectByPath = function(obj, path, value) {
155 | if (!isObj(obj) || typeof path !== 'string') {
156 | return;
157 | }
158 |
159 | var pathArr = getPathSegments(path);
160 |
161 | for (var i = 0; i < pathArr.length; i++) {
162 | var p = pathArr[i];
163 |
164 | if (!isObj(obj[p])) {
165 | obj[p] = {};
166 | }
167 |
168 | if (i === pathArr.length - 1) {
169 | obj[p] = value;
170 | }
171 |
172 | obj = obj[p];
173 | }
174 | };
175 | // setObjectByPath end
176 |
177 | getDomNode = function(node) {
178 | var container = [];
179 | if (node.group) {
180 | searchGroupChildrenForDomNode(node.group, container);
181 | }
182 | return container;
183 | };
184 |
185 | searchGroupChildrenForDomNode = function(group, container) {
186 | for (let i = 0; i < group.children.length; i++) {
187 | if (group.get(i).type === "element") {
188 | container.push(group.get(i).last());
189 | } else if (group.get(i).children) {
190 | searchGroupChildrenForDomNode(group.get(i), container);
191 | } else if (group.get(i) instanceof Regular) {
192 | if (group.get(i).group) {
193 | searchGroupChildrenForDomNode(group.get(i).group, container);
194 | }
195 | }
196 | }
197 | };
198 |
199 | getNodesByUUID = function(uuid) {
200 | const element = findElementByUUID(store, uuid);
201 | return element && element.node;
202 | };
203 |
204 | function isASTElement(node) {
205 | return node.type && node.group;
206 | }
207 |
208 | function isASTGroup(node) {
209 | return node.children;
210 | }
211 |
212 | function isRegularInstance(node) {
213 | return node instanceof Regular;
214 | }
215 |
216 | walker = function(node, container, flag) {
217 | var n;
218 | var i;
219 |
220 | if (isRegularInstance(node)) {
221 | n = {
222 | uuid: node.uuid,
223 | name: node.name || findComponentRegisteredName(node) || '[anonymous]',
224 | childNodes: [],
225 | inspectable: false,
226 | hasTemplate: !!node.template,
227 | hasInjected: true
228 | };
229 |
230 | n.node = getDomNode(node);
231 |
232 | if (node.$outer) {
233 | n.isIncluded = true;
234 | }
235 |
236 | // fetch all computed props
237 | // n.computed = fetchComputedProps(node);
238 | node.visited = true;
239 | if (node.group) {
240 | treeGen(node, n.childNodes);
241 | }
242 |
243 | if (n.node.length) {
244 | n.inspectable = true;
245 | }
246 |
247 | container.push(n);
248 | } else if (isASTElement(node)) {
249 | for (i = 0; i < node.group.children.length; i++) {
250 | walker(node.group.children[i], container);
251 | }
252 | } else if (isASTGroup(node)) {
253 | for (i = 0; i < node.children.length; i++) {
254 | walker(node.children[i], container);
255 | }
256 | }
257 | };
258 |
259 | treeGen = function(root, container) {
260 | var tree = container || [];
261 | if (root.group) {
262 | for (var i = 0; i < root.group.children.length; i++) {
263 | walker(root.group.children[i], tree);
264 | }
265 | }
266 | return tree;
267 | };
268 |
269 | var uuid = function() {
270 | function s4() {
271 | return Math.floor((1 + Math.random()) * 0x10000)
272 | .toString(16)
273 | .substring(1);
274 | }
275 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
276 | s4() + '-' + s4() + s4() + s4();
277 | };
278 |
279 | var assignUUID = function(obj) {
280 | if (!obj.uuid) {
281 | obj.uuid = uuid();
282 | }
283 | };
284 |
285 | var assignUUIDMany = function(arr) {
286 | for (var i = 0; i < arr.length; i++) {
287 | if (!arr[i].uuid) {
288 | assignUUID(arr[i]);
289 | }
290 | }
291 | };
292 |
293 | var storeGen = function(flag) {
294 | var node;
295 | store = [];
296 | for (var i = 0; i < ins.length; i++) {
297 | // standalone instances
298 | if (ins[i].$root === ins[i]) {
299 | // fetch all computed props
300 | // var computed = fetchComputedProps(ins[i]);
301 | const nodes = getGroupNode(ins[i]);
302 | node = {
303 | uuid: ins[i].uuid,
304 | name: ins[i].name || "[anonymous]",
305 | childNodes: [],
306 | node: [],
307 | inspectable: !!ins[i].parentNode,
308 | hasTemplate: !!ins[i].template,
309 | hasInjected: !!nodes && !!(Array.isArray(nodes) ? nodes[0].parentNode : nodes.parentNode)
310 | };
311 | var body = document.body;
312 | if (ins[i].parentNode) {
313 | if (ins[i].parentNode === body) {
314 | for (var j = 0; j < ins[i].group.children.length; j++) {
315 | if (ins[i].group.get(j).type) {
316 | node.node.push(ins[i].group.get(j).node());
317 | }
318 | }
319 | } else {
320 | node.node.push(ins[i].parentNode);
321 | }
322 | }
323 | ins[i].visited = true;
324 | treeGen(ins[i], node.childNodes);
325 | store.push(node);
326 | }
327 | }
328 | return stringifyStore(store);
329 | };
330 |
331 | // generate uuid for the first time
332 | assignUUIDMany(ins);
333 | return {
334 | init: function() {
335 | if (ins.length === 0) {
336 | return;
337 | }
338 |
339 | /* eslint-disable no-proto */
340 | let proto = ins[0].__proto__;
341 | while (proto.__proto__ !== Object.prototype) {
342 | proto = proto.__proto__;
343 | }
344 | /* eslint-enable no-proto */
345 | Regular = proto.constructor;
346 |
347 | hook.on("flushMessage", function() {
348 | window.postMessage({
349 | type: "FROM_PAGE",
350 | data: {
351 | type: "dataUpdate"
352 | }
353 | }, "*");
354 | });
355 |
356 | hook.on("addNodeMessage", function(obj) {
357 | assignUUID(obj);
358 | });
359 |
360 | hook.on("reRender", function(obj) {
361 | window.postMessage({
362 | type: "FROM_PAGE",
363 | data: {
364 | type: "reRender",
365 | nodes: storeGen()
366 | }
367 | }, "*");
368 | });
369 |
370 | window.postMessage({
371 | type: "FROM_PAGE",
372 | data: {
373 | type: "initNodes",
374 | nodes: storeGen()
375 | }
376 | }, "*");
377 | },
378 | getNodeTree: function() {
379 | return store;
380 | },
381 | updateInstance: function(uuid, path, value) {
382 | // find instance by uuid
383 | var instance;
384 | var i;
385 | var len;
386 |
387 | for (i = 0, len = ins.length; i < len; i++) {
388 | if (ins[i].uuid === uuid) {
389 | instance = ins[i];
390 | break;
391 | }
392 | }
393 |
394 | if (!instance) return;
395 |
396 | // update instance data by path
397 | setObjectByPath(instance.data, path, value);
398 | instance.$update();
399 |
400 | for (i = 0, len = ins.length; i < len; i++) {
401 | if (ins[i].parentNode) {
402 | ins[i].$update();
403 | }
404 | }
405 | },
406 | print: function(uuid) {
407 | var i;
408 | for (i = 0; i < ins.length; i++) {
409 | if (ins[i].uuid === uuid) {
410 | window.$r = ins[i]; // console output $component as curUI component
411 | break;
412 | }
413 | }
414 | },
415 | enterInspectMode: function() {
416 | if (inspectMode) {
417 | return;
418 | }
419 | inspectMode = true;
420 | window.document.body.addEventListener("mouseover", onMouseOver);
421 | window.document.body.addEventListener("click", onClick);
422 | },
423 | exitInspectMode: function() {
424 | if (!inspectMode) {
425 | return;
426 | }
427 | window.document.body.removeEventListener("mouseover", onMouseOver);
428 | window.document.body.removeEventListener("click", onClick);
429 | clearMask();
430 | inspectMode = false;
431 | },
432 | highLighter: highLighter,
433 | stringify: stringifyStore,
434 | fetchComputedProps: fetchComputedProps
435 | };
436 | })();
437 |
438 | window.devtoolsModel.init();
439 |
440 | function findComponentRegisteredName(node) {
441 | let name;
442 |
443 | if (node.$parent) {
444 | let obj = node.$parent.constructor._components;
445 | let keys = Object.keys(obj);
446 | for (let i = 0; i < keys.length; i++) {
447 | if (obj[keys[i]] === node.constructor) {
448 | name = keys[i];
449 | break;
450 | }
451 | }
452 | }
453 |
454 | return name;
455 | }
456 |
457 | function getGroupNode(item) {
458 | let node;
459 | let nodes;
460 | if (!item) return;
461 | if (typeof item.node === "function") return item.node();
462 | if (typeof item.nodeType === "number") return item;
463 | if (item.group) return getGroupNode(item.group);
464 |
465 | item = item.children || item;
466 | if (Array.isArray(item)) {
467 | const len = item.length;
468 | if (len === 1) {
469 | return getGroupNode(item[0]);
470 | }
471 | nodes = [];
472 | for (let i = 0, len = item.length; i < len; i++) {
473 | node = getGroupNode(item[i]);
474 | if (Array.isArray(node)) {
475 | nodes.push.apply(nodes, node);
476 | } else if (node) {
477 | nodes.push(node);
478 | }
479 | }
480 | return nodes;
481 | }
482 | }
483 |
--------------------------------------------------------------------------------
/src/extension/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Regular Developer Tools",
3 | "description": "A Chrome extension for inspecting regularjs components",
4 | "version": "0.9.3",
5 |
6 | "manifest_version": 2,
7 |
8 | "icons": {
9 | "16": "/regular.png",
10 | "48": "/regular.png",
11 | "128": "/regular.png"
12 | },
13 |
14 | "devtools_page": "/devtools.html",
15 |
16 | "background": {
17 | "persistent": true,
18 | "scripts": ["/background.js"]
19 | },
20 |
21 | "content_scripts": [{
22 | "matches": [""],
23 | "js": ["/hook.js"],
24 | "run_at": "document_start"
25 | }],
26 |
27 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
28 |
29 | "web_accessible_resources": [
30 | "/inject.bundle.js"
31 | ],
32 |
33 | "permissions": ["", "tabs"]
34 | }
35 |
--------------------------------------------------------------------------------
/src/extension/regular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/regularjs/regular-devtools/3782e24f1d2c79de527356f984e0a6b830edc5ea/src/extension/regular.png
--------------------------------------------------------------------------------
/src/shared/circular-json.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /*! (C) WebReflection Mit Style License */
3 | export default (function CircularJSONCtor(e,t){function l(e,t,o){var u=[],f=[e],l=[e],c=[o?n:"[Circular]"],h=e,p=1,d;return function(e,v){return t&&(v=t.call(this,e,v)),e!==""&&(h!==this&&(d=p-a.call(f,this)-1,p-=d,f.splice(p,f.length),u.splice(p-1,u.length),h=this),typeof v=="object"&&v?(a.call(f,v)<0&&f.push(h=v),p=f.length,d=a.call(l,v),d<0?(d=l.push(v)-1,o?(u.push((""+e).replace(s,r)),c[d]=n+u.join(n)):c[d]=c[0]):v=c[d]):typeof v=="string"&&o&&(v=v.replace(r,i).replace(n,r))),v}}function c(e,t){for(var r=0,i=t.length;r