├── .npmrc ├── tests ├── api │ ├── data │ │ ├── response.txt │ │ ├── response.gif │ │ ├── response.jpg │ │ ├── response.png │ │ ├── response.css │ │ ├── response.json │ │ ├── response.svg │ │ ├── response.html │ │ └── response.js │ └── index.js ├── unit │ ├── .eslintrc │ └── HelloWorld.spec.js └── e2e │ ├── .eslintrc │ ├── specs │ └── test.js │ ├── plugins │ └── index.js │ └── support │ ├── index.js │ └── commands.js ├── .npmignore ├── .postcssrc ├── cypress.json ├── src ├── constants │ ├── index.js │ └── PanelType.js ├── components │ ├── VJSONViewer │ │ ├── index.js │ │ ├── VJSONViewer.vue │ │ ├── JSONTextInlineBlock.vue │ │ └── JSONTextBlock.vue │ ├── index.js │ ├── VTabBar.vue │ ├── VTabBarItem.vue │ ├── VFootBar.vue │ ├── VIcon.vue │ └── VHighlightView.vue ├── assets │ └── icons │ │ ├── 2xx.png │ │ ├── 3xx.png │ │ ├── 4xx.png │ │ ├── 5xx.png │ │ ├── add.png │ │ ├── ban.png │ │ ├── edit.png │ │ ├── save.png │ │ ├── close.png │ │ ├── expand.png │ │ ├── collapse.png │ │ ├── refresh.png │ │ ├── setting.png │ │ ├── add-disable.png │ │ ├── ban_disable.png │ │ ├── chrome_logo.png │ │ ├── close-disable.png │ │ ├── edit-disable.png │ │ ├── save-disable.png │ │ └── transparent_bg.png ├── panels │ ├── network │ │ ├── RequestType.js │ │ ├── TabResponse.vue │ │ ├── HttpStatus.js │ │ ├── TabPreview.vue │ │ ├── NetworkRequest.vue │ │ └── TabHeaders.vue │ ├── index.js │ ├── element │ │ ├── StyleColorValue.vue │ │ ├── NodeLink.vue │ │ ├── Tag.vue │ │ ├── StyleProperty.vue │ │ ├── TabStyles.vue │ │ ├── BoxModel.vue │ │ ├── StyleRule.vue │ │ ├── ElementPanel.vue │ │ ├── NodeView.vue │ │ └── TabComputed.vue │ ├── application │ │ ├── ApplicationPanel.vue │ │ └── XStorage.js │ └── console │ │ ├── TextInlineBlock.vue │ │ └── ConsolePanel.vue ├── utils │ ├── filters.js │ ├── index.js │ ├── Logger.js │ ├── EventBus.js │ ├── consoleHooks.js │ ├── TaskScheduler.js │ ├── miscs.js │ └── style.js ├── plugins │ ├── index.js │ ├── pluginEvents.js │ ├── pluginManager.js │ └── Plugin.js ├── styles │ ├── _triangles.scss │ ├── _mixins.scss │ ├── _variables.scss │ └── _global.scss ├── polyfill.js ├── main.js └── App.vue ├── docs ├── snapshot.png ├── snapshot_console.png ├── snapshot_element.png ├── snapshot_network.png ├── snapshot_plugin.png ├── snapshot_element2.png ├── snapshot_element3.png ├── snapshot_application.png ├── snapshot_plugin_settings1.png ├── snapshot_plugin_settings2.png ├── snapshot.md ├── depoly.md └── plugin.md ├── public ├── favicon.ico ├── style_link.css ├── style_import2.css ├── style_import1.css ├── index_cdn.html ├── test_application.js ├── test_network.js ├── test_plugin.js └── test_console.js ├── tsconfig.json ├── .babelrc ├── .eslintrc ├── .gitignore ├── jest.config.js ├── gulpfile.js ├── vue.config.js ├── package.json └── readme.md /.npmrc: -------------------------------------------------------------------------------- 1 | git-tag-version=false -------------------------------------------------------------------------------- /tests/api/data/response.txt: -------------------------------------------------------------------------------- 1 | plain text data -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !dist/*.js 4 | !package*.json -------------------------------------------------------------------------------- /.postcssrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "autoprefixer": {} 4 | } 5 | } -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | export { default as PanelType } from "./PanelType"; 2 | -------------------------------------------------------------------------------- /docs/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/docs/snapshot.png -------------------------------------------------------------------------------- /src/components/VJSONViewer/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./VJSONViewer.vue"; 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /docs/snapshot_console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/docs/snapshot_console.png -------------------------------------------------------------------------------- /docs/snapshot_element.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/docs/snapshot_element.png -------------------------------------------------------------------------------- /docs/snapshot_network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/docs/snapshot_network.png -------------------------------------------------------------------------------- /docs/snapshot_plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/docs/snapshot_plugin.png -------------------------------------------------------------------------------- /src/assets/icons/2xx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/2xx.png -------------------------------------------------------------------------------- /src/assets/icons/3xx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/3xx.png -------------------------------------------------------------------------------- /src/assets/icons/4xx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/4xx.png -------------------------------------------------------------------------------- /src/assets/icons/5xx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/5xx.png -------------------------------------------------------------------------------- /src/assets/icons/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/add.png -------------------------------------------------------------------------------- /src/assets/icons/ban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/ban.png -------------------------------------------------------------------------------- /src/assets/icons/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/edit.png -------------------------------------------------------------------------------- /src/assets/icons/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/save.png -------------------------------------------------------------------------------- /docs/snapshot_element2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/docs/snapshot_element2.png -------------------------------------------------------------------------------- /docs/snapshot_element3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/docs/snapshot_element3.png -------------------------------------------------------------------------------- /src/assets/icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/close.png -------------------------------------------------------------------------------- /src/assets/icons/expand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/expand.png -------------------------------------------------------------------------------- /tests/api/data/response.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/tests/api/data/response.gif -------------------------------------------------------------------------------- /tests/api/data/response.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/tests/api/data/response.jpg -------------------------------------------------------------------------------- /tests/api/data/response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/tests/api/data/response.png -------------------------------------------------------------------------------- /docs/snapshot_application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/docs/snapshot_application.png -------------------------------------------------------------------------------- /src/assets/icons/collapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/collapse.png -------------------------------------------------------------------------------- /src/assets/icons/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/refresh.png -------------------------------------------------------------------------------- /src/assets/icons/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/setting.png -------------------------------------------------------------------------------- /src/assets/icons/add-disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/add-disable.png -------------------------------------------------------------------------------- /src/assets/icons/ban_disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/ban_disable.png -------------------------------------------------------------------------------- /src/assets/icons/chrome_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/chrome_logo.png -------------------------------------------------------------------------------- /docs/snapshot_plugin_settings1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/docs/snapshot_plugin_settings1.png -------------------------------------------------------------------------------- /docs/snapshot_plugin_settings2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/docs/snapshot_plugin_settings2.png -------------------------------------------------------------------------------- /src/assets/icons/close-disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/close-disable.png -------------------------------------------------------------------------------- /src/assets/icons/edit-disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/edit-disable.png -------------------------------------------------------------------------------- /src/assets/icons/save-disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/save-disable.png -------------------------------------------------------------------------------- /src/assets/icons/transparent_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/HEAD/src/assets/icons/transparent_bg.png -------------------------------------------------------------------------------- /tests/api/data/response.css: -------------------------------------------------------------------------------- 1 | @import "aa"; 2 | 3 | .aa { 4 | color: red; 5 | background: rgb(50%, 50%, 50%); 6 | } 7 | -------------------------------------------------------------------------------- /src/panels/network/RequestType.js: -------------------------------------------------------------------------------- 1 | // 请求类型 2 | export default Object.freeze({ 3 | XHR: "xhr", 4 | FETCH: "fetch" 5 | }); 6 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "rules": { 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } -------------------------------------------------------------------------------- /public/style_link.css: -------------------------------------------------------------------------------- 1 | /* 测试元素审查 */ 2 | #element { 3 | background-color: white; 4 | } 5 | .xyz { 6 | border: 1px solid red; 7 | padding: 1px; 8 | } 9 | -------------------------------------------------------------------------------- /public/style_import2.css: -------------------------------------------------------------------------------- 1 | /* 测试元素审查 */ 2 | #element { 3 | border-color: yellow; 4 | } 5 | .xyz { 6 | border: 3px dotted yellow; 7 | padding: 3px; 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@": ["./src"], 6 | "@/*": ["./src/*"] 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/utils/filters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | lowerCase: input => { 3 | if (typeof input !== "string") return input; 4 | else return input.toLowerCase(); 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | export { default as pluginManager } from "./pluginManager"; 2 | export { default as pluginEvents } from "./pluginEvents"; 3 | export { default as Plugin } from "./Plugin"; 4 | -------------------------------------------------------------------------------- /public/style_import1.css: -------------------------------------------------------------------------------- 1 | @import url("style_import2.css"); 2 | 3 | /* 测试元素审查 */ 4 | #element { 5 | border-color: green; 6 | } 7 | .xyz { 8 | border: 2px dashed blue; 9 | padding: 2px; 10 | } 11 | -------------------------------------------------------------------------------- /tests/e2e/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "cypress" 4 | ], 5 | "env": { 6 | "mocha": true, 7 | "cypress/globals": true 8 | }, 9 | "rules": { 10 | "strict": "off" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@vue/app" 4 | ], 5 | "plugins": [ 6 | [ 7 | "component", 8 | { 9 | "libraryName": "mint-ui", 10 | "style": true 11 | } 12 | ] 13 | ] 14 | } -------------------------------------------------------------------------------- /tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('Visits the Kitchen Sink', () => { 5 | cy.visit('/') 6 | cy.contains('h1', 'Welcome to Your Vue.js App') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /tests/api/data/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": 1, 3 | "b": true, 4 | "c": "c", 5 | "d": [1.23, true, "c", { "a": 1, "b": 2 }], 6 | "e": { 7 | "a": 1, 8 | "b": true, 9 | "c": "c", 10 | "d": null, 11 | "e1": 1, 12 | "e2": 1, 13 | "e3": 1 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "plugin:vue/essential", 5 | "eslint:recommended" 6 | ], 7 | "env": { 8 | "browser": true, 9 | "node": true 10 | }, 11 | "globals": { 12 | }, 13 | "rules": { 14 | "no-console": "off", 15 | "no-case-declarations": "off" 16 | } 17 | } -------------------------------------------------------------------------------- /tests/api/data/response.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | Rectangle 6 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as VTabBar } from "./VTabBar"; 2 | export { default as VTabBarItem } from "./VTabBarItem"; 3 | export { default as VFootBar } from "./VFootBar"; 4 | export { default as VJSONViewer } from "./VJSONViewer"; 5 | export { default as VIcon } from "./VIcon"; 6 | export { default as VHighlightView } from "./VHighlightView"; 7 | -------------------------------------------------------------------------------- /tests/api/data/response.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 |

Hello World

11 | 12 | -------------------------------------------------------------------------------- /docs/snapshot.md: -------------------------------------------------------------------------------- 1 | # Element 面板 2 | 3 | ![element](snapshot_element.png) 4 | 5 | ![element](snapshot_element2.png) 6 | 7 | ![element](snapshot_element3.png) 8 | 9 | # Console 面板 10 | 11 | ![console](snapshot_console.png) 12 | 13 | # Network 面板 14 | 15 | ![network](snapshot_network.png) 16 | 17 | # Application 面板 18 | 19 | ![application](snapshot_application.png) 20 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export { default as Style } from "./style"; 2 | export { default as filters } from "./filters"; 3 | export { default as Logger } from "./Logger"; 4 | export { default as consoleHooks } from "./consoleHooks"; 5 | export { default as TaskScheduler } from "./TaskScheduler"; 6 | export { default as eventBus, EventBus } from "./eventBus"; 7 | export * from "./miscs"; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /demo 5 | 6 | /tests/e2e/videos/ 7 | /tests/e2e/screenshots/ 8 | 9 | # local env files 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | -------------------------------------------------------------------------------- /src/panels/index.js: -------------------------------------------------------------------------------- 1 | export { default as SettingsPanel } from "./settings/SettingsPanel.vue"; 2 | export { default as NetworkPanel } from "./network/NetworkPanel.vue"; 3 | export { default as ConsolePanel } from "./console/ConsolePanel"; 4 | export { default as ApplicationPanel } from "./application/ApplicationPanel"; 5 | export { default as ElementPanel } from "./element/ElementPanel.vue"; 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue' 7 | ], 8 | transform: { 9 | '^.+\\.vue$': 'vue-jest', 10 | '^.+\\.jsx?$': 'babel-jest' 11 | }, 12 | moduleNameMapper: { 13 | '^@/(.*)$': '/src/$1' 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-serializer-vue' 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tests/unit/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import { shallow } from '@vue/test-utils' 2 | import HelloWorld from '@/components/HelloWorld.vue' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message' 7 | const wrapper = shallow(HelloWorld, { 8 | propsData: { msg } 9 | }) 10 | expect(wrapper.text()).toMatch(msg) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/guides/guides/plugins-guide.html 2 | 3 | module.exports = (on, config) => { 4 | return Object.assign({}, config, { 5 | fixturesFolder: 'tests/e2e/fixtures', 6 | integrationFolder: 'tests/e2e/specs', 7 | screenshotsFolder: 'tests/e2e/screenshots', 8 | videosFolder: 'tests/e2e/videos', 9 | supportFile: 'tests/e2e/support/index.js' 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /docs/depoly.md: -------------------------------------------------------------------------------- 1 | ## 部署 2 | 3 | 发布到 npm 仓库: 4 | 5 | ```bash 6 | npm run semantic-release 7 | ``` 8 | 9 | 更新 github.io 在线示例: 10 | 11 | ```bash 12 | npm run depoly 13 | ``` 14 | 15 | 本地调试 16 | 17 | ```js 18 | const script = document.createElement("script"); 19 | script.src = "http://localhost:8081/app.js"; 20 | script.onload = function() { 21 | new window.WebConsole(); 22 | }; 23 | document.body.appendChild(script); 24 | ``` 25 | -------------------------------------------------------------------------------- /tests/api/data/response.js: -------------------------------------------------------------------------------- 1 | function sayHello(params) { 2 | console.log(params); 3 | Array.prototype.every.call(this, 1, 2); 4 | var a = { 5 | 1: 100, 6 | 2: true, 7 | 3: null, 8 | 4: undefined, 9 | 5: [100, 200], 10 | 6: "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstring", 11 | 7: { 12 | a: 1 13 | }, 14 | 8: Symbol("a") 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /public/index_cdn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 测试CDN下载 7 | 8 | 9 | 10 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /src/plugins/pluginEvents.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 插件事件 3 | * 4 | * 约定:事件名称与插件的生命周期方法同名 5 | */ 6 | export default Object.freeze({ 7 | // 插件 DOM 已渲染 8 | WEB_CONSOLE_READY: "onWebConsoleReady", 9 | WEB_CONSOLE_SHOW: "onWebConsoleShow", 10 | WEB_CONSOLE_HIDE: "onWebConsoleHide", 11 | WEB_CONSOLE_TAB_CHANGED: "onWebConsoleTabChanged", 12 | WEB_CONSOLE_SETTINGS_LOADED: "onWebConsoleSettingsLoaded", 13 | WEB_CONSOLE_SETTINGS_CHANGED: "onWebConsoleSettingsChanged" 14 | }); 15 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const pkgInfo = require("./package.json"); 2 | const child_process = require("child_process"); 3 | const process = require("process"); 4 | var gulp = require("gulp"); 5 | 6 | gulp.task("default", function(cb) { 7 | // 将你的默认的任务代码放在这 8 | }); 9 | 10 | let version = "prerelease"; 11 | 12 | gulp.task("version", cb => { 13 | const oldVersion = pkgInfo.version; 14 | console.log(oldVersion); 15 | console.log(process.argv, ";", process.argv0); 16 | child_process.exec("npm version " + version, (err, stdout, stderr) => { 17 | if (err) cb(err); 18 | console.log(stdout); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/constants/PanelType.js: -------------------------------------------------------------------------------- 1 | export default Object.freeze( 2 | Object.setPrototypeOf( 3 | { 4 | ELEMENT: "element", 5 | CONSOLE: "console", 6 | APPLICATION: "application", 7 | NETWORK: "network" 8 | }, 9 | { 10 | text(type) { 11 | switch (type) { 12 | case this.ELEMENT: 13 | return "Element"; 14 | case this.CONSOLE: 15 | return "Console"; 16 | case this.APPLICATION: 17 | return "Application"; 18 | case this.NETWORK: 19 | return "Network"; 20 | default: 21 | return type; 22 | } 23 | } 24 | } 25 | ) 26 | ); 27 | -------------------------------------------------------------------------------- /src/panels/element/StyleColorValue.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | 20 | 32 | -------------------------------------------------------------------------------- /src/styles/_triangles.scss: -------------------------------------------------------------------------------- 1 | @mixin triangles-collapse($border-width: 4px, $color: black) { 2 | display: inline-block; 3 | content: ""; 4 | width: 0; 5 | height: 0; 6 | /* 等边三角形,tan(30) 约为 0.5773502691896257 */ 7 | border-left: $border-width solid $color; 8 | border-top: $border-width * 0.8 solid transparent; 9 | border-bottom: $border-width * 0.8 solid transparent; 10 | } 11 | 12 | @mixin triangles-expand($border-width: 4px, $color: black) { 13 | display: inline-block; 14 | content: ""; 15 | width: 0; 16 | height: 0; 17 | /* 等边三角形,tan(30) 约为 0.5773502691896257 */ 18 | border-top: $border-width solid $color; 19 | border-left: $border-width * 0.8 solid transparent; 20 | border-right: $border-width * 0.8 solid transparent; 21 | } 22 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | /** 以 VUE_APP_* 开头的环境变量会替换代码中相应的变量 */ 4 | const npmPkg = require("./package.json"); 5 | process.env.VUE_APP_NAME = npmPkg.name; 6 | process.env.VUE_APP_VERSION = npmPkg.version; 7 | process.env.VUE_APP_DATE = new Date(Date.now() - new Date().getTimezoneOffset() * 60 * 1000).toISOString(); 8 | 9 | module.exports = { 10 | css: { 11 | extract: false 12 | }, 13 | // 部署和测试时用,打包成 lib 时不需要 14 | publicPath: process.env.NODE_ENV === "production" ? "./" : "/", 15 | configureWebpack: { 16 | // 将入口的导出作为默认导出 17 | output: { 18 | libraryExport: "default" 19 | }, 20 | resolve: { 21 | alias: { 22 | "@": path.resolve(__dirname, "src") 23 | } 24 | } 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /public/test_application.js: -------------------------------------------------------------------------------- 1 | window.$application = (function() { 2 | function testWriteCookie(n = 1) { 3 | for (var i = 0; i < n; ++i) { 4 | const obj = { timestamp: Date.now() }; 5 | document.cookie = i + "=" + JSON.stringify(obj); 6 | } 7 | } 8 | 9 | function testWriteLocalStorage(n = 1) { 10 | for (var i = 0; i < n; ++i) { 11 | const obj = { timestamp: Date.now() }; 12 | window.localStorage.setItem(i, JSON.stringify(obj)); 13 | } 14 | } 15 | 16 | function testWriteSessionStorage(n = 1) { 17 | for (var i = 0; i < n; ++i) { 18 | const obj = { timestamp: Date.now() }; 19 | window.sessionStorage.setItem(i, JSON.stringify(obj)); 20 | } 21 | } 22 | 23 | return { 24 | testWriteCookie, 25 | testWriteSessionStorage, 26 | testWriteLocalStorage 27 | }; 28 | })(); 29 | -------------------------------------------------------------------------------- /src/components/VJSONViewer/VJSONViewer.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /src/polyfill.js: -------------------------------------------------------------------------------- 1 | if (Element.prototype.matches === undefined) { 2 | Element.prototype.matches = 3 | Element.prototype.matchesSelector || 4 | Element.prototype.mozMatchesSelector || 5 | Element.prototype.msMatchesSelector || 6 | Element.prototype.oMatchesSelector || 7 | Element.prototype.webkitMatchesSelector || 8 | function(s) { 9 | var matches = (this.document || this.ownerDocument).querySelectorAll(s), 10 | i = matches.length; 11 | while (--i >= 0 && matches.item(i) !== this); 12 | return i > -1; 13 | }; 14 | } 15 | 16 | if (Element.prototype.getAttributeNames == undefined) { 17 | Element.prototype.getAttributeNames = function() { 18 | var attributes = this.attributes; 19 | var length = attributes.length; 20 | var result = new Array(length); 21 | for (var i = 0; i < length; i++) { 22 | result[i] = attributes[i].name; 23 | } 24 | return result; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /src/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | // 隐藏滚动条(刷新后生效) 2 | @mixin hide-scrollbar { 3 | &::-webkit-scrollbar { 4 | display: none; 5 | overflow: -moz-scrollbars-none; 6 | } 7 | } 8 | 9 | // 给当前元素以及后代元素设置属性 10 | @mixin descendant-attr($name, $value) { 11 | #{$name}: $value; 12 | /deep/ * { 13 | #{$name}: $value; 14 | } 15 | } 16 | 17 | // 文本超出父容器时显示省略号 18 | @mixin text-ellipsis { 19 | white-space: nowrap; 20 | overflow: hidden; 21 | text-overflow: ellipsis; 22 | } 23 | 24 | // 文本超出父容器时可滚动 25 | @mixin text-scroll { 26 | white-space: nowrap; 27 | overflow: auto; 28 | } 29 | 30 | // input 输入框样式 31 | @mixin input($height: 1.8em) { 32 | height: $height; 33 | outline: none; 34 | color: #5a5a5a; 35 | background-color: white; 36 | border: 1px solid transparent; 37 | &::placeholder { 38 | color: rgb(128, 128, 128); 39 | } 40 | &:focus { 41 | border: 1px solid #2196f3; 42 | } 43 | } 44 | 45 | // 初始化元素属性,避免宿主页面与选择器同名时受影响 46 | @mixin initial() { 47 | width: initial; 48 | height: initial; 49 | padding: initial; 50 | margin: initial; 51 | border: initial; 52 | position: initial; 53 | } 54 | -------------------------------------------------------------------------------- /src/utils/Logger.js: -------------------------------------------------------------------------------- 1 | import { isDev, isString } from "./miscs"; 2 | 3 | /* 原始的 console 方法 */ 4 | const _error = window.console.error; 5 | const _log = window.console.log; 6 | const _warn = window.console.warn; 7 | 8 | /** 9 | * 日志类 10 | * 调用原始的 console 方法打印日志 11 | */ 12 | export default class Logger { 13 | constructor(prefix) { 14 | this._prefix = prefix ? prefix + " " : ""; 15 | } 16 | 17 | error(...args) { 18 | if (!isDev) return; 19 | 20 | if (isString(args[0])) { 21 | args[0] = this._prefix + args[0]; 22 | } else { 23 | args.unshift(this._prefix); 24 | } 25 | _error.apply(this, args); 26 | } 27 | warn(...args) { 28 | if (!isDev) return; 29 | 30 | if (isString(args[0])) { 31 | args[0] = this._prefix + args[0]; 32 | } else { 33 | args.unshift(this._prefix); 34 | } 35 | _warn.apply(this, args); 36 | } 37 | log(...args) { 38 | if (!isDev) return; 39 | 40 | if (isString(args[0])) { 41 | args[0] = this._prefix + args[0]; 42 | } else { 43 | args.unshift(this._prefix); 44 | } 45 | _log.apply(this, args); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | /* web-console UI structure */ 2 | $panel-height: 75vh; 3 | 4 | $footbar-height: 40px; 5 | 6 | $tabbar-height: 40px; 7 | 8 | $list-row-height: 38px; 9 | 10 | /* color */ 11 | $default-color: rgb(48, 57, 66); 12 | 13 | $accent-color: #03a9f4; 14 | $accent-color-b: #2196f3; 15 | $accent-color-c: #3e82f7; 16 | 17 | $toolbar-bg-color: rgb(243, 243, 243); 18 | $toolbar-hover-bg-color: #eaeaea; 19 | $toolbar-border-color: rgb(205, 205, 205); 20 | 21 | $selection-fg-color: white; 22 | $selection-bg-color: $accent-color-b; 23 | $selection-inactive-bg-color: #dadada; 24 | 25 | $tab-selected-fg-color: #333; 26 | $tab-selected-bg-color: $toolbar-bg-color; 27 | 28 | $drop-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0, 0, 0, 0.1); 29 | 30 | $divider-color: #d0d0d0; 31 | 32 | $tabbar-bg-color: $toolbar-bg-color; 33 | $tabbar-border-color: $toolbar-border-color; 34 | $tab-fg-color: rgb(90, 90, 90); 35 | $tab-selected-fg-color: rgb(51, 51, 51); 36 | 37 | $dom-tag-name-color: rgb(136, 18, 128); 38 | $dom-attribute-name-color: rgb(153, 69, 0); 39 | $dom-attribute-value-color: rgb(26, 26, 166); 40 | $dom-link-color: rgb(17, 85, 204); 41 | 42 | /* font */ 43 | $primary-font-size: 15px; 44 | $secondary-font-size: 13px; 45 | $source-code-font-size: 14px; 46 | $font-family: Helvetica Neue, Helvetica, Arial, sans-serif; 47 | -------------------------------------------------------------------------------- /src/utils/EventBus.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Logger from "./Logger"; 3 | 4 | const logger = new Logger("[EventBus]"); 5 | class EventBus { 6 | constructor(events = {}) { 7 | this._vue = new Vue(); 8 | 9 | Object.assign(this, events); 10 | // this._eventNameList = Object.keys(events).map(name => events[name]); 11 | } 12 | emit(eventName, ...args) { 13 | // if (!this._eventNameList.some(v => v === eventName)) { 14 | // logger.warn("unregistered event name:", eventName); 15 | // return; 16 | // } 17 | 18 | logger.log('emit: "%s"', eventName, ...args); 19 | // freeze 一下避免 Vue 添加一些额外字段,同时也确保事件数据传递时不被修改 20 | this._vue.$emit(eventName, ...args.map(v => Object.freeze(v))); 21 | } 22 | on(eventName, callback) { 23 | // if (!this._eventNameList.some(v => v === eventName)) { 24 | // logger.warn("unregistered event name:", eventName); 25 | // return; 26 | // } 27 | 28 | // logger.log('on: "%s"', eventName); 29 | this._vue.$on(eventName, callback); 30 | } 31 | } 32 | 33 | export { EventBus }; 34 | 35 | /* 36 | * 预定义事件类型 37 | * 目标触发了事件:: 38 | * 请求目标触发事件::: 39 | */ 40 | export default new EventBus({ 41 | POPUP_VISIBILITY_CHANGE: "popup_visibility_change", 42 | SETTINGS_CHANGE: "settings_change", 43 | SETTINGS_LOADED: "settings_loaded", 44 | WEB_CONSOLE_SHOW: "web-console:show", 45 | WEB_CONSOLE_HIDE: "web-console:hide", 46 | REQUEST_WEB_CONSOLE_HIDE: "request:web-console:hide" 47 | }); 48 | -------------------------------------------------------------------------------- /src/utils/consoleHooks.js: -------------------------------------------------------------------------------- 1 | import { uuid, createStack } from "./miscs"; 2 | 3 | // hook console 4 | // install 前的 console 接口 5 | const originConsole = {}; 6 | // install 后的 console 接口(之后第三方可能会再次重写改接口) 7 | const currentConsole = {}; 8 | const names = ["log", "info", "error", "warn", "debug"]; 9 | const msgList = []; 10 | let active = false; 11 | 12 | const install = () => { 13 | active = true; 14 | names.forEach(name => { 15 | originConsole[name] = window.console[name]; 16 | 17 | currentConsole[name] = function(...args) { 18 | if (active) { 19 | const logArgs = args.map(v => { 20 | if (v instanceof Error) { 21 | createStack(v, currentConsole[name]); 22 | } 23 | return v; 24 | }); 25 | const msg = { 26 | id: uuid(), 27 | type: name, 28 | timestamps: Date.now(), 29 | logArgs 30 | }; 31 | // 冻结计算结果,避免 Vue 添加额外属性 32 | msgList.push(Object.freeze(msg)); 33 | } 34 | originConsole[name].apply(this, args); 35 | }; 36 | 37 | window.console[name] = currentConsole[name]; 38 | }); 39 | }; 40 | 41 | // 卸载 42 | const uninstall = () => { 43 | active = false; 44 | // 如果第三方没有重写 console 接口,则还原 console 接口为原生的 console 接口 45 | names.forEach(name => { 46 | if (currentConsole[name] === window.console[name]) { 47 | window.console[name] = originConsole[name]; 48 | } 49 | }); 50 | }; 51 | 52 | const getMsgList = () => msgList; 53 | 54 | export default { install, uninstall, getMsgList }; 55 | -------------------------------------------------------------------------------- /src/components/VTabBar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 45 | 46 | 73 | -------------------------------------------------------------------------------- /src/panels/network/TabResponse.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 53 | 54 | 55 | 76 | -------------------------------------------------------------------------------- /src/components/VTabBarItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 29 | 30 | 72 | -------------------------------------------------------------------------------- /src/styles/_global.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // 全局样式 4 | .web-console { 5 | font-size: $primary-font-size; 6 | font-family: $font-family; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | color: $default-color; 10 | .g-source-code, 11 | .source-code { 12 | font-size: $source-code-font-size !important; 13 | font-family: Menlo, Consolas, monospace; 14 | } 15 | 16 | .platform-linux { 17 | color: rgb(48, 57, 66); 18 | font-family: Roboto, Ubuntu, Arial, sans-serif; 19 | } 20 | .platform-mac { 21 | color: rgb(48, 57, 66); 22 | font-family: ".SFNSDisplay-Regular", "Helvetica Neue", "Lucida Grande", sans-serif; 23 | } 24 | .platform-windows { 25 | font-family: "Segoe UI", Tahoma, sans-serif; 26 | } 27 | 28 | // 隐藏滚动条 29 | .g-hide-scrollbar::-webkit-scrollbar { 30 | display: none; 31 | overflow: -moz-scrollbars-none; 32 | } 33 | 34 | // 重置默认样式 35 | * { 36 | box-sizing: border-box; 37 | user-select: auto; 38 | // IOS:移除默认的触摸高亮样式 39 | -webkit-tap-highlight-color: transparent; 40 | &:focus { 41 | outline: none; 42 | } 43 | } 44 | 45 | input, 46 | select, 47 | textarea, 48 | button { 49 | font-size: $primary-font-size; 50 | font-family: Helvetica Neue, Helvetica, Arial, sans-serif; 51 | -webkit-font-smoothing: antialiased; 52 | -moz-osx-font-smoothing: grayscale; 53 | border: none; 54 | outline: none; 55 | } 56 | 57 | pre { 58 | margin: 0; 59 | } 60 | 61 | // Fix:
标签不换行导致的样式问题(如 Element 面板的盒模型坍塌) 62 | br:before { 63 | content: "\A"; 64 | white-space: pre-line; 65 | } 66 | br:after { 67 | white-space: pre-line; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/VFootBar.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 45 | 46 | 47 | 79 | -------------------------------------------------------------------------------- /src/plugins/pluginManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 插件管理 3 | */ 4 | import { eventBus, EventBus } from "@/utils"; 5 | import Plugin from "./Plugin"; 6 | 7 | class PluginManager extends EventBus { 8 | constructor(...args) { 9 | super(...args); 10 | /** 11 | * 插件集合 12 | * @type {Array} 13 | */ 14 | this._plugins = []; 15 | // 当前最新的设置 16 | this._settings = {}; 17 | // 宿主代理 18 | this._hostProxy = null; 19 | } 20 | 21 | // 宿主代理,提供一些操作 web-console 的方法 22 | get hostProxy() { 23 | if (!this._hostProxy) { 24 | this._hostProxy = { 25 | hidePanel: () => eventBus.emit(eventBus.REQUEST_WEB_CONSOLE_HIDE), 26 | getSettings: () => this._settings 27 | }; 28 | } 29 | return this._hostProxy; 30 | } 31 | 32 | /** 33 | * 注册插件 34 | * @param {Plugin} plugin 35 | */ 36 | addPlugin(plugin) { 37 | if (!(plugin instanceof Plugin)) { 38 | console.warn("Invalid plugin: plugin should inherit WebConsole.Plugin"); 39 | return; 40 | } 41 | 42 | if (!plugin.id) { 43 | console.warn(`Empty plugin id: plugin id must not be empty and must be unique among all plugins`); 44 | return; 45 | } 46 | 47 | if (this._plugins.find(v => v.id === plugin.id)) { 48 | console.warn(`Plugin conflict: plugin id "${plugin.id}" has existed`); 49 | return; 50 | } 51 | 52 | plugin.__init__(this); 53 | this._plugins.push(plugin); 54 | } 55 | 56 | getPlugins() { 57 | return this._plugins; 58 | } 59 | 60 | updateSettings(settings) { 61 | this._settings = settings; 62 | } 63 | 64 | getSettings() { 65 | return this._settings; 66 | } 67 | 68 | hasPlugin(pluginId) { 69 | return this._plugins.findIndex(plugin => plugin.id === pluginId) !== -1; 70 | } 71 | 72 | toString() { 73 | return JSON.stringify(this._plugins, null, 4); 74 | } 75 | } 76 | 77 | export default new PluginManager(); 78 | -------------------------------------------------------------------------------- /src/panels/network/HttpStatus.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "100": "Continue", 3 | "101": "Switching Protocols", 4 | "102": "Processing", 5 | "200": "OK", 6 | "201": "Created", 7 | "202": "Accepted", 8 | "203": "Non-Authoritative Information", 9 | "204": "No Content", 10 | "205": "Reset Content", 11 | "206": "Partial Content", 12 | "207": "Multi-Status", 13 | "208": "Already Reported", 14 | "226": "IM Used", 15 | "300": "Multiple Choices", 16 | "301": "Moved Permanently", 17 | "302": "Found", 18 | "303": "See Other", 19 | "304": "Not Modified", 20 | "305": "Use Proxy", 21 | "307": "Temporary Redirect", 22 | "308": "Permanent Redirect", 23 | "400": "Bad Request", 24 | "401": "Unauthorized", 25 | "402": "Payment Required", 26 | "403": "Forbidden", 27 | "404": "Not Found", 28 | "405": "Method Not Allowed", 29 | "406": "Not Acceptable", 30 | "407": "Proxy Authentication Required", 31 | "408": "Request Timeout", 32 | "409": "Conflict", 33 | "410": "Gone", 34 | "411": "Length Required", 35 | "412": "Precondition Failed", 36 | "413": "Payload Too Large", 37 | "414": "URI Too Long", 38 | "415": "Unsupported Media Type", 39 | "416": "Range Not Satisfiable", 40 | "417": "Expectation Failed", 41 | "418": "I'm a teapot", 42 | "421": "Misdirected Request", 43 | "422": "Unprocessable Entity", 44 | "423": "Locked", 45 | "424": "Failed Dependency", 46 | "425": "Unordered Collection", 47 | "426": "Upgrade Required", 48 | "428": "Precondition Required", 49 | "429": "Too Many Requests", 50 | "431": "Request Header Fields Too Large", 51 | "451": "Unavailable For Legal Reasons", 52 | "500": "Internal Server Error", 53 | "501": "Not Implemented", 54 | "502": "Bad Gateway", 55 | "503": "Service Unavailable", 56 | "504": "Gateway Timeout", 57 | "505": "HTTP Version Not Supported", 58 | "506": "Variant Also Negotiates", 59 | "507": "Insufficient Storage", 60 | "508": "Loop Detected", 61 | "509": "Bandwidth Limit Exceeded", 62 | "510": "Not Extended", 63 | "511": "Network Authentication Required" 64 | }; 65 | -------------------------------------------------------------------------------- /src/components/VIcon.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 55 | 56 | 66 | -------------------------------------------------------------------------------- /src/plugins/Plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 插件基类 3 | */ 4 | import { isFunction } from "@/utils"; 5 | import { VFootBar, VIcon, VTabBar, VTabBarItem, VHighlightView, VJSONViewer } from "@/components"; 6 | import pluginEvents from "./pluginEvents"; 7 | 8 | export default class Plugin { 9 | constructor({ id, name, settings, component }) { 10 | this.id = id; 11 | this.name = name || this.id; 12 | this.settings = isFunction(settings) ? settings.call(this) : settings; 13 | this.component = isFunction(component) ? component.call(this) : component; 14 | } 15 | 16 | __init__(pluginMgr) { 17 | // const plugin = this; 18 | const component = this.component; 19 | 20 | this.component = { 21 | ...component, 22 | name: component.name || this.id, 23 | mixins: [ 24 | ...(component.mixins || []), 25 | { 26 | components: { 27 | VFootBar, 28 | VHighlightView, 29 | VIcon, 30 | VTabBar, 31 | VTabBarItem, 32 | VJSONViewer 33 | }, 34 | created() { 35 | // created 周期函数触发时,Vue 已完成事件部署 36 | const vm = this; 37 | // 注册插件生命周期方法 38 | // 当接收到特定事件时,自动触发相应的插件生命周期方法 39 | Object.keys(pluginEvents) 40 | .map(key => pluginEvents[key]) 41 | .forEach(event => { 42 | pluginMgr.on(event, (...args) => { 43 | if (isFunction(vm[event])) { 44 | vm[event].call(vm, pluginMgr.hostProxy, ...args); 45 | } 46 | }); 47 | }); 48 | } // created 49 | } 50 | ] // mixins 51 | }; 52 | 53 | // 暂不支持 54 | // Object.keys(pluginEvents) 55 | // .map(key => pluginEvents[key]) 56 | // .forEach(event => { 57 | // pluginMgr.on(event, (...args) => { 58 | // if (isFunction(plugin[event])) { 59 | // plugin[event].call(plugin, pluginMgr.hostProxy, ...args); 60 | // } 61 | // }); 62 | // }); 63 | } 64 | 65 | // [pluginEvents.WEB_CONSOLE_READY]() {} 66 | // [pluginEvents.WEB_CONSOLE_SHOW]() {} 67 | // [pluginEvents.WEB_CONSOLE_HIDE]() {} 68 | // [pluginEvents.WEB_CONSOLE_TAB_CHANGED]() {} 69 | // [pluginEvents.WEB_CONSOLE_SETTINGS_LOADED]() {} 70 | // [pluginEvents.WEB_CONSOLE_SETTINGS_CHANGED]() {} 71 | 72 | toString() { 73 | return this.id + "(" + this.name + ")"; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/panels/element/NodeLink.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 64 | 65 | 91 | -------------------------------------------------------------------------------- /src/utils/TaskScheduler.js: -------------------------------------------------------------------------------- 1 | import { isFunction } from "./miscs"; 2 | // import Logger from "./Logger"; 3 | 4 | // const logger = new Logger("[TaskScheduler]"); 5 | /** 6 | * 任务调度器 7 | * 用途:按任意速率添加任务,之后这些任务按指定速率按序执行,避免阻塞交互 8 | * 9 | * 第一版实现方式:新增任务放入队列,调度器间隔一定时间执行 1 个任务,这种方式问题在于每次执行的任务数量 10 | * 只有一个,导致任务执行的总时间变长,总时间 = 任务数量 * 间隔时间 11 | * 12 | * T T T T T T T <-- push task 13 | * T -> wait -> T -> wait -> T 14 | * 15 | * 第二版实现方式:新增任务放入队列,调度器间隔一定时间执行 N 个任务,总时间 = 任务数量 / N * 间隔时间, 16 | * 大大缩短了任务执行的总时间 17 | * 18 | * T T T T T T T <-- push task 19 | * T...T -> wait -> T...T -> wait -> T...T 20 | * 21 | * 示例: 22 | * const scheduler = new TaskScheduler(interval) 23 | * scheduler.addAndStart(task1) 24 | * scheduler.addAndStart(task2) 25 | */ 26 | export default class TaskScheduler { 27 | constructor({ interval = 200, taskBundleSize = 100 } = {}) { 28 | this._interval = interval; 29 | this._isRunning = false; 30 | this._tasks = []; 31 | this._timerId = null; 32 | this._taskBundleSize = taskBundleSize; 33 | } 34 | 35 | get length() { 36 | return this._tasks.length; 37 | } 38 | 39 | add(task) { 40 | if (!isFunction(task)) return; 41 | 42 | this._tasks.push(task); 43 | } 44 | 45 | addAndStart(task) { 46 | this.add(task); 47 | if (this.length > 0) { 48 | this.start({ immediate: true }); 49 | } 50 | } 51 | 52 | /** 53 | * 启动调度 54 | * @param {Object} params 55 | * @param {Boolean} params.immediate 是否立即执行队列任务 56 | */ 57 | start({ immediate = true } = {}) { 58 | this._runTask(immediate); 59 | } 60 | 61 | // 停止调度,并清空任务队列 62 | stop() { 63 | if (this._timerId !== null) { 64 | clearTimeout(this._timerId); 65 | this._timerId = null; 66 | } 67 | this._tasks = []; 68 | this._isRunning = false; 69 | } 70 | 71 | _shiftTaskBunlde() { 72 | const count = Math.min(this._tasks.length, this._taskBundleSize); 73 | const taskBundle = this._tasks.slice(0, count); 74 | this._tasks = this._tasks.slice(count); 75 | 76 | // logger.log("_shiftTaskBunlde remove:", count, "rest:", this.length); 77 | return taskBundle; 78 | } 79 | 80 | _runTask(immediate = false) { 81 | if (this.length <= 0 || this._isRunning) return; 82 | 83 | this._isRunning = true; 84 | 85 | if (immediate) { 86 | const task = this._tasks.shift(); 87 | task.call(null); 88 | // logger.log("_runTask immediate size:", 1); 89 | } 90 | this._timerId = setTimeout(() => { 91 | const tasks = this._shiftTaskBunlde(); 92 | tasks.forEach(task => task.call(null)); 93 | 94 | this._isRunning = false; 95 | this._runTask(); 96 | }, this._interval); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/utils/miscs.js: -------------------------------------------------------------------------------- 1 | export { default as cloneDeep } from "lodash.clonedeep"; 2 | 3 | export const isNull = v => v === null; 4 | export const isUndefined = v => v === undefined; 5 | export const isString = v => typeof v === "string"; 6 | export const isFunction = v => typeof v === "function"; 7 | export const isArray = v => Array.isArray(v); 8 | export const isNumber = v => typeof v === "number"; 9 | export const isSymbol = v => typeof v === "symbol"; 10 | export const isBoolean = v => typeof v === "boolean"; 11 | export const isObject = v => typeof v === "object" && v !== null; 12 | 13 | export const isDev = process.env.NODE_ENV !== "production"; 14 | 15 | export const noop = function() {}; 16 | 17 | export const nextTick = cb => { 18 | if (typeof window.Promise === "function") { 19 | Promise.resolve().then(cb); 20 | } else { 21 | setTimeout(cb, 0); 22 | } 23 | }; 24 | 25 | // 将 HTTP content-type 类型转换成 highlight.js 所支持的语法高亮类型 26 | export const mimeType2Language = mimeType => { 27 | switch (true) { 28 | case /javascript/.test(mimeType): 29 | return "javascript"; 30 | case /json/.test(mimeType): 31 | return "json"; 32 | case /html/.test(mimeType): 33 | return "html"; 34 | case /css/.test(mimeType): 35 | return "css"; 36 | default: 37 | return ""; 38 | } 39 | }; 40 | 41 | /** 42 | * generate an uuid 43 | * @returns string 44 | */ 45 | export const uuid = () => { 46 | let id = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { 47 | let r = (Math.random() * 16) | 0; 48 | let v = c === "x" ? r : (r & 0x3) | 0x8; 49 | return v.toString(16); 50 | }); 51 | return id; 52 | }; 53 | 54 | export const flatMap = (arr, callback) => { 55 | let r = []; 56 | arr.forEach((v, i) => { 57 | const r2 = callback(v, i); 58 | r2.forEach(v2 => r.push(v2)); 59 | }); 60 | return r; 61 | }; 62 | 63 | export const formatFileName = (s = "") => s.replace(/https?:\/\/.*\/(.*)/g, "$1"); 64 | 65 | /** 66 | * 在目标对象上创建一个 stack 属性表示调用堆栈 67 | * @param {*} targetObject 68 | * @param {*} constructorOpt 该函数(含自身)以上的堆栈都会被忽略 69 | */ 70 | export const createStack = (targetObject, constructorOpt) => { 71 | // Chrome 提供 captureStackTrace 方法记录堆栈信息到目标对象的 stack 属性,参考 72 | if (typeof Error.captureStackTrace === "function") { 73 | Error.captureStackTrace(targetObject, constructorOpt); 74 | } 75 | // safari 创建的 Error 对象包含 stack 属性记录堆栈信息 76 | if (typeof targetObject.stack === "string") { 77 | targetObject.stack = formatFileName(targetObject.stack); 78 | } 79 | }; 80 | 81 | // 获取 URL 中的文件名(含扩展名) 82 | export const getURLFileName = url => { 83 | if (typeof url !== "string") return ""; 84 | 85 | const index = url.lastIndexOf("/"); 86 | return url.slice(index + 1); 87 | }; 88 | -------------------------------------------------------------------------------- /src/panels/network/TabPreview.vue: -------------------------------------------------------------------------------- 1 |