├── .browserslistrc ├── .gitignore ├── .prettierrc.js ├── .storybook ├── main.js └── webpack.config.js ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── babel.config.js ├── example ├── App.vue ├── main.js └── public │ └── index.html ├── jest.config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── src ├── components │ ├── connected-tree-node.vue │ ├── object-label.vue │ ├── object-preview.vue │ ├── object-root-label.vue │ ├── object-value.vue │ ├── tree-node.vue │ └── tree-view.vue ├── index.js ├── libs │ ├── data.js │ ├── pathUtils.js │ ├── pathUtils.spec.js │ └── propertyUtils.js ├── object-inspector.vue └── styles │ ├── chromedark │ ├── index.less │ ├── object.less │ └── tree.less │ ├── chromelight │ ├── index.less │ ├── object.less │ └── tree.less │ └── index.less ├── stories ├── stories-array.js ├── stories-basic-type.js ├── stories-maps-sets-functions.js ├── stories-object.js └── stories-themes.js └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // default 3 | printWidth: 80, 4 | useTabs: false, 5 | tabWidth: 2, 6 | bracketSpacing: true, 7 | 8 | // modify 9 | semi: false, 10 | singleQuote: true, 11 | trailingComma: 'es5', 12 | endOfLint: 'lf', 13 | } 14 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: [ 3 | '../stories/stories-object.js', 4 | '../stories/stories-array.js', 5 | '../stories/stories-maps-sets-functions.js', 6 | '../stories/stories-basic-type.js', 7 | '../stories/stories-themes.js', 8 | ], 9 | addons: [ 10 | // "@storybook/addon-links", 11 | // "@storybook/addon-essentials" 12 | ], 13 | } 14 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | // Export a function. Accept the base config as the only param. 4 | module.exports = async ({ config, mode }) => { 5 | // `mode` has a value of 'DEVELOPMENT' or 'PRODUCTION' 6 | // You can change the configuration based on that. 7 | // 'PRODUCTION' is used when building the static version of storybook. 8 | 9 | // Make whatever fine-grained changes you need 10 | config.module.rules.push({ 11 | test: /\.less/, 12 | use: ['style-loader', 'css-loader', 'less-loader'], 13 | include: path.resolve(__dirname, '../'), 14 | }) 15 | Object.assign(config.resolve.alias, { 16 | '@': path.resolve(__dirname, '../src'), 17 | '@assets': path.resolve(__dirname, '../src/assets/'), 18 | }) 19 | // Return the altered config 20 | return config 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "[javascript]": { 4 | "editor.defaultFormatter": "esbenp.prettier-vscode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 VikydZhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-object-inspector 2 | 3 | Vue component used as an js/json object inspector/viewer, inspired by [react-inspector](https://github.com/storybookjs/react-inspector) . 4 | 5 | > Nodes will not be rendered if parent is collapsed. 6 | 7 | ## Online Playground 8 | 9 | Online Playground: 10 | 11 | https://codesandbox.io/s/vue-object-inspector-demo-gjs9h?file=/src/components/InspectorDemo.vue 12 | 13 | https://codesandbox.io/s/vue-object-inspector-demo-dark-bouxd?file=/src/components/InspectorDemo.vue 14 | 15 | ## Examples 16 | 17 | ![](https://raw.githubusercontent.com/vikyd/repos-bigfile/main/vue-object-inspector/object.png) 18 | 19 | ![](https://raw.githubusercontent.com/vikyd/repos-bigfile/main/vue-object-inspector/chromedark.png) 20 | 21 | ![](https://raw.githubusercontent.com/vikyd/repos-bigfile/main/vue-object-inspector/array.png) 22 | 23 | ![](https://raw.githubusercontent.com/vikyd/repos-bigfile/main/vue-object-inspector/json.png) 24 | 25 | ![](https://raw.githubusercontent.com/vikyd/repos-bigfile/main/vue-object-inspector/proto.png) 26 | 27 | ## Usage 28 | 29 | ```sh 30 | npm install vue-object-inspector 31 | ``` 32 | 33 | In Vue component: 34 | 35 | ```vue 36 | 41 | 42 | 62 | ``` 63 | 64 | ## Vue `props` 65 | 66 | This component supports some Vue props, similar to [react-inspector](https://github.com/storybookjs/react-inspector/blob/v5.1.0/README.md#api) , just a bit different. 67 | 68 | ### `data` 69 | 70 | - what: JavaScript Object or any var you would like to inspect 71 | - type: any 72 | - mandatory: true 73 | 74 | ### `name` 75 | 76 | - what: root node prefix name 77 | - type: String 78 | - mandatory: false 79 | 80 | ### `expandLevel` 81 | 82 | - what: an integer specifying to which level the tree should be initially expanded 83 | - type: Integer 84 | - mandatory: false 85 | - default: `0` 86 | 87 | ### `expandPaths` 88 | 89 | - what: an array containing all the paths that should be expanded when the component is initialized, or a string of just one path 90 | - type: Array of String 91 | - mandatory: false 92 | - details: syntax like [JSONPath](https://goessner.net/articles/JsonPath/) 93 | - default: `[]` 94 | - examples: 95 | - `['$']`: expand root node, `$` always refers to the root node 96 | - `['$.myKey']`: expand to `myKey` node (will also expand all parent nodes) 97 | - this is different from [react-inspector](https://github.com/storybookjs/react-inspector) 98 | - `['$.myKey.myArr']`: expand to `myArr` node (will also expand all parent nodes) 99 | - `['$.a', '$.b']`: expand first level nodes `a` and `b` 100 | - `['$.1']`: expand by array index 101 | - `['$.*']`: wildcard, expand all level 2 nodes, equivalent to `:expandLevel="2"` 102 | - `['$.*.*']`: wildcard, expand all level 3 nodes, equivalent to `:expandLevel="3"` 103 | 104 | ### `expandPaths` vs `expandPaths` 105 | 106 | Both are reactive. 107 | 108 | In most case, don't use both at the same time. 109 | 110 | One of them changes, only the changed one will take effect and ignore the other one. 111 | 112 | If want to expand all level, change `expandLevel` to a very big number. 113 | 114 | If want to collapse all level, change `expandLevel` to 0. 115 | 116 | If already change expand by hand, change the `expandLevel` to a nagative number, then change it back in `$nextTick`. 117 | 118 | ### `showNonenumerable` 119 | 120 | - what: show non-enumerable properties, like `__proto__`, `length`, `constructor` ... 121 | - type: Boolean 122 | - mandatory: false 123 | - default: `false` 124 | 125 | ### `sortObjectKeys` 126 | 127 | - what: sort object keys like [Array.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 128 | - type: Boolean or Function 129 | - mandatory: false 130 | - default: no sort 131 | - examples: 132 | - `true`: keys of objects are sorted in alphabetical order except for arrays 133 | - function in Vue component methods: 134 | ```js 135 | reverseSortFunc(a, b) { 136 | return b > a ? 1 : -1 137 | } 138 | ``` 139 | 140 | ### `nodeRenderer` 141 | 142 | - what: use a custom `nodeRenderer` to render the object properties 143 | - type: Function, should return [JSX](https://cn.vuejs.org/v2/guide/render-function.html#ad) elements 144 | - function parameters: `depth`, `name`, `data`, `isNonenumerable` 145 | - `isNonenumerable` refers to default hidden properties like `__proto__`, `length` ... 146 | - mandatory: false 147 | - default: a default `nodeRenderer` 148 | - examples: 149 | - function in Vue component methods: 150 | ```js 151 | myNodeRenderer(depth, name, data, isNonenumerable) { 152 | return ( 153 | 156 | ) 157 | } 158 | ``` 159 | 160 | ### `objectMaxProperties` 161 | 162 | - what: max count object properties should be list in preview label 163 | - type: Interger 164 | - mandatory: false 165 | - default: `5` 166 | 167 | ### `arrayMaxProperties` 168 | 169 | - what: max count array properties should be list in preview label 170 | - type: Interger 171 | - mandatory: false 172 | - default: `10` 173 | 174 | ### `theme` 175 | 176 | - what: use light or dark theme 177 | - type: String 178 | - mandatory: false 179 | - default: light theme, keep this prop empty 180 | - dark theme value: `chromeDark` 181 | 182 | ## Development 183 | 184 | Local preview page is [example/App.vue](example/App.vue) . 185 | 186 | ```sh 187 | # Install dependencies 188 | npm install 189 | 190 | # Compiles and hot-reloads for development 191 | npm run serve 192 | 193 | # Compiles and minifies for production 194 | npm run build 195 | ``` 196 | 197 | ## Storybook Preview 198 | 199 | After `npm install`, you can also run this command to see lots of live examples. 200 | 201 | ```sh 202 | npm run storybook 203 | ``` 204 | 205 | See `stories` folder for source code of examples. 206 | 207 | ## Thanks 208 | 209 | - [react-inspector](https://github.com/storybookjs/react-inspector) 210 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/cli-plugin-babel/preset'], 3 | } 4 | -------------------------------------------------------------------------------- /example/App.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 111 | 112 | 127 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | render: (h) => h(App), 8 | }).$mount('#app') 9 | -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest', 3 | } 4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-object-inspector", 3 | "version": "1.0.12", 4 | "description": "Vue component used as an js/json object inspector/viewer", 5 | "keywords": [ 6 | "vue", 7 | "object", 8 | "chrome", 9 | "devtools", 10 | "component", 11 | "inspector", 12 | "object-inspector", 13 | "object-viewer", 14 | "array", 15 | "array-viewer", 16 | "tree", 17 | "tree-view", 18 | "treeview", 19 | "ui", 20 | "view" 21 | ], 22 | "license": "MIT", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/vikyd/vue-object-inspector.git" 26 | }, 27 | "scripts": { 28 | "serve": "vue-cli-service serve", 29 | "build": "vue-cli-service build --target lib src/index.js", 30 | "test:unit": "vue-cli-service test:unit", 31 | "storybook": "start-storybook -p 6006", 32 | "build-storybook": "build-storybook" 33 | }, 34 | "main": "dist/vue-object-inspector.umd.js", 35 | "dependencies": { 36 | "core-js": "^3.6.5", 37 | "vue": "^2.6.11" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "^7.12.10", 41 | "@storybook/addon-actions": "^6.1.11", 42 | "@storybook/addon-essentials": "^6.1.11", 43 | "@storybook/addon-links": "^6.1.11", 44 | "@storybook/vue": "^6.1.11", 45 | "@vue/cli-plugin-babel": "~4.5.0", 46 | "@vue/cli-plugin-unit-jest": "~4.5.0", 47 | "@vue/cli-service": "~4.5.0", 48 | "@vue/test-utils": "^1.0.3", 49 | "babel-loader": "^8.2.2", 50 | "less": "^3.0.4", 51 | "less-loader": "^5.0.0", 52 | "vue-template-compiler": "^2.6.11" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/components/connected-tree-node.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/components/object-label.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/components/object-preview.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 99 | -------------------------------------------------------------------------------- /src/components/object-root-label.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/object-value.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/components/tree-node.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/components/tree-view.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import ObjectInspector from './object-inspector' 2 | 3 | export default ObjectInspector 4 | -------------------------------------------------------------------------------- /src/libs/data.js: -------------------------------------------------------------------------------- 1 | import { getPropertyValue } from '@/libs/propertyUtils' 2 | 3 | export const createIterator = (showNonenumerable, sortObjectKeys) => { 4 | const objectIterator = function* (data) { 5 | const shouldIterate = 6 | (typeof data === 'object' && data !== null) || typeof data === 'function' 7 | if (!shouldIterate) return 8 | 9 | const dataIsArray = Array.isArray(data) 10 | 11 | // iterable objects (except arrays) 12 | if (!dataIsArray && data[Symbol.iterator]) { 13 | let i = 0 14 | for (let entry of data) { 15 | if (Array.isArray(entry) && entry.length === 2) { 16 | const [k, v] = entry 17 | yield { 18 | name: k, 19 | data: v, 20 | } 21 | } else { 22 | yield { 23 | name: i.toString(), 24 | data: entry, 25 | } 26 | } 27 | i++ 28 | } 29 | } else { 30 | const keys = Object.getOwnPropertyNames(data) 31 | if (sortObjectKeys === true && !dataIsArray) { 32 | // Array keys should not be sorted in alphabetical order 33 | keys.sort() 34 | } else if (typeof sortObjectKeys === 'function') { 35 | keys.sort(sortObjectKeys) 36 | } 37 | 38 | for (const propertyName of keys) { 39 | if (propertyIsEnumerable.call(data, propertyName)) { 40 | const propertyValue = getPropertyValue(data, propertyName) 41 | yield { 42 | name: propertyName || `""`, 43 | data: propertyValue, 44 | } 45 | } else if (showNonenumerable) { 46 | // To work around the error (happens some time when propertyName === 'caller' || propertyName === 'arguments') 47 | // 'caller' and 'arguments' are restricted function properties and cannot be accessed in this context 48 | // http://stackoverflow.com/questions/31921189/caller-and-arguments-are-restricted-function-properties-and-cannot-be-access 49 | let propertyValue 50 | try { 51 | propertyValue = getPropertyValue(data, propertyName) 52 | } catch (e) { 53 | // console.warn(e) 54 | } 55 | 56 | if (propertyValue !== undefined) { 57 | yield { 58 | name: propertyName, 59 | data: propertyValue, 60 | isNonenumerable: true, 61 | } 62 | } 63 | } 64 | } 65 | 66 | // [[Prototype]] of the object: `Object.getPrototypeOf(data)` 67 | // the property name is shown as "__proto__" 68 | if (showNonenumerable && data !== Object.prototype /* already added */) { 69 | yield { 70 | name: '__proto__', 71 | data: Object.getPrototypeOf(data), 72 | isNonenumerable: true, 73 | } 74 | } 75 | } 76 | } 77 | 78 | return objectIterator 79 | } 80 | -------------------------------------------------------------------------------- /src/libs/pathUtils.js: -------------------------------------------------------------------------------- 1 | export const DEFAULT_ROOT_PATH = '$' 2 | 3 | const WILDCARD = '*' 4 | 5 | export function hasChildNodes(data, dataIterator) { 6 | return !dataIterator(data).next().done 7 | } 8 | 9 | export const wildcardPathsFromLevel = (level) => { 10 | // i is depth 11 | return Array.from({ length: level }, (_, i) => 12 | [DEFAULT_ROOT_PATH].concat(Array.from({ length: i }, () => '*')).join('.') 13 | ) 14 | } 15 | 16 | export const getExpandedPaths = ( 17 | data, 18 | dataIterator, 19 | expandPaths, 20 | expandLevel, 21 | prevExpandedPaths 22 | ) => { 23 | let wildcardPaths = [] 24 | .concat(wildcardPathsFromLevel(expandLevel)) 25 | .concat(expandPaths) 26 | .filter((path) => typeof path === 'string') // could be undefined 27 | 28 | const expandedPaths = [] 29 | wildcardPaths.forEach((wildcardPath) => { 30 | const keyPaths = wildcardPath.split('.') 31 | const populatePaths = (curData, curPath, depth) => { 32 | if (depth === keyPaths.length) { 33 | expandedPaths.push(curPath) 34 | return 35 | } 36 | const key = keyPaths[depth] 37 | if (depth === 0) { 38 | if ( 39 | hasChildNodes(curData, dataIterator) && 40 | (key === DEFAULT_ROOT_PATH || key === WILDCARD) 41 | ) { 42 | populatePaths(curData, DEFAULT_ROOT_PATH, depth + 1) 43 | } 44 | } else { 45 | if (key === WILDCARD) { 46 | for (let { name, data } of dataIterator(curData)) { 47 | if (hasChildNodes(data, dataIterator)) { 48 | populatePaths(data, `${curPath}.${name}`, depth + 1) 49 | } 50 | } 51 | } else { 52 | const value = curData[key] 53 | if (hasChildNodes(value, dataIterator)) { 54 | populatePaths(value, `${curPath}.${key}`, depth + 1) 55 | } 56 | } 57 | } 58 | } 59 | 60 | populatePaths(data, '', 0) 61 | }) 62 | 63 | return expandedPaths.reduce( 64 | (obj, path) => { 65 | obj[path] = true 66 | return obj 67 | }, 68 | { ...prevExpandedPaths } 69 | ) 70 | } 71 | -------------------------------------------------------------------------------- /src/libs/pathUtils.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | 3 | import { DEFAULT_ROOT_PATH, wildcardPathsFromLevel } from './pathUtils' 4 | 5 | const root = DEFAULT_ROOT_PATH 6 | 7 | describe('PathUtils', () => { 8 | beforeEach(() => {}) 9 | 10 | it('wildcardPathsFromLevel works', () => { 11 | expect(wildcardPathsFromLevel(-1)).toEqual([]) 12 | 13 | expect(wildcardPathsFromLevel(0)).toEqual([]) 14 | 15 | expect(wildcardPathsFromLevel(1)).toEqual([root]) 16 | 17 | expect(wildcardPathsFromLevel(2)).toEqual([root, `${root}.*`]) 18 | 19 | expect(wildcardPathsFromLevel(3)).toEqual([ 20 | root, 21 | `${root}.*`, 22 | `${root}.*.*`, 23 | ]) 24 | 25 | expect(wildcardPathsFromLevel(4)).toEqual([ 26 | root, 27 | `${root}.*`, 28 | `${root}.*.*`, 29 | `${root}.*.*.*`, 30 | ]) 31 | }) 32 | 33 | // it('getExpandedPaths works', () => { 34 | // }) 35 | }) 36 | -------------------------------------------------------------------------------- /src/libs/propertyUtils.js: -------------------------------------------------------------------------------- 1 | export function getPropertyValue(object, propertyName) { 2 | const propertyDescriptor = Object.getOwnPropertyDescriptor( 3 | object, 4 | propertyName 5 | ) 6 | if (propertyDescriptor.get) { 7 | try { 8 | return propertyDescriptor.get() 9 | } catch { 10 | return propertyDescriptor.get 11 | } 12 | } 13 | 14 | return object[propertyName] 15 | } 16 | -------------------------------------------------------------------------------- /src/object-inspector.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /src/styles/chromedark/index.less: -------------------------------------------------------------------------------- 1 | .vue-object-inspector-chromedark { 2 | @import './tree.less'; 3 | @import './object.less'; 4 | } 5 | -------------------------------------------------------------------------------- /src/styles/chromedark/object.less: -------------------------------------------------------------------------------- 1 | .object-name { 2 | color: rgb(227, 110, 236); 3 | } 4 | 5 | .object-name-dimmed { 6 | opacity: 0.6; 7 | } 8 | 9 | .object-preview-desc { 10 | font-style: italic; 11 | } 12 | 13 | .object-preview { 14 | font-style: italic; 15 | } 16 | 17 | .object-value-null { 18 | color: rgb(127, 127, 127); 19 | } 20 | .object-value-undefined { 21 | color: rgb(127, 127, 127); 22 | } 23 | .object-value-regexp { 24 | color: rgb(233, 63, 59); 25 | } 26 | .object-value-string { 27 | color: rgb(233, 63, 59); 28 | } 29 | .object-value-symbol { 30 | color: rgb(233, 63, 59); 31 | } 32 | .object-value-number { 33 | color: hsl(252, 100%, 75%); 34 | } 35 | .object-value-boolean { 36 | color: hsl(252, 100%, 75%); 37 | } 38 | .object-value-function-prefix { 39 | // color: rgb(13, 34, 170); 40 | color: rgb(85, 106, 242); 41 | font-style: italic; 42 | } 43 | .object-value-function-name { 44 | font-style: italic; 45 | } 46 | -------------------------------------------------------------------------------- /src/styles/chromedark/tree.less: -------------------------------------------------------------------------------- 1 | .tree-view-outline { 2 | margin: 0; 3 | padding: 0; 4 | list-style-type: none; 5 | } 6 | 7 | .tree-node { 8 | color: rgb(213, 213, 213); 9 | background-color: rgb(36, 36, 36); 10 | 11 | line-height: 1.2; 12 | cursor: default; 13 | 14 | box-sizing: border-box; 15 | list-style: none; 16 | 17 | font-family: Menlo, monospace; 18 | font-size: 11px; 19 | } 20 | 21 | // .tree-node-preview-container { 22 | // } 23 | 24 | .tree-node-placeholder { 25 | white-space: pre; 26 | 27 | font-size: 12px; 28 | margin-right: 3px; 29 | } 30 | 31 | .tree-node-arrow { 32 | color: rgb(145, 145, 145); 33 | display: inline-block; 34 | font-size: 12px; 35 | margin-right: 3px; 36 | } 37 | .tree-node-arrow-expanded { 38 | transform: rotate(90deg); 39 | } 40 | .tree-node-arrow-collapsed { 41 | transform: rotate(0deg); 42 | } 43 | 44 | .tree-node-child-nodes-container { 45 | margin: 0; 46 | padding-left: 12px; 47 | } 48 | -------------------------------------------------------------------------------- /src/styles/chromelight/index.less: -------------------------------------------------------------------------------- 1 | .vue-object-inspector { 2 | @import './tree.less'; 3 | @import './object.less'; 4 | } 5 | -------------------------------------------------------------------------------- /src/styles/chromelight/object.less: -------------------------------------------------------------------------------- 1 | .object-name { 2 | color: rgb(136, 19, 145); 3 | } 4 | 5 | .object-name-dimmed { 6 | opacity: 0.6; 7 | } 8 | 9 | .object-preview-desc { 10 | font-style: italic; 11 | } 12 | 13 | .object-preview { 14 | font-style: italic; 15 | } 16 | 17 | .object-value-null { 18 | color: rgb(128, 128, 128); 19 | } 20 | .object-value-undefined { 21 | color: rgb(128, 128, 128); 22 | } 23 | .object-value-regexp { 24 | color: rgb(196, 26, 22); 25 | } 26 | .object-value-string { 27 | color: rgb(196, 26, 22); 28 | } 29 | .object-value-symbol { 30 | color: rgb(196, 26, 22); 31 | } 32 | .object-value-number { 33 | color: rgb(28, 0, 207); 34 | } 35 | .object-value-boolean { 36 | color: rgb(28, 0, 207); 37 | } 38 | .object-value-function-prefix { 39 | // color: rgb(13, 34, 170); 40 | color: rgb(170, 13, 145); 41 | font-style: italic; 42 | } 43 | .object-value-function-name { 44 | font-style: italic; 45 | } 46 | -------------------------------------------------------------------------------- /src/styles/chromelight/tree.less: -------------------------------------------------------------------------------- 1 | .tree-view-outline { 2 | margin: 0; 3 | padding: 0; 4 | list-style-type: none; 5 | } 6 | 7 | .tree-node { 8 | color: #000; 9 | background-color: #fff; 10 | 11 | line-height: 1.2; 12 | cursor: default; 13 | 14 | box-sizing: border-box; 15 | list-style: none; 16 | 17 | font-family: Menlo, monospace; 18 | font-size: 11px; 19 | } 20 | 21 | // .tree-node-preview-container { 22 | // } 23 | 24 | .tree-node-placeholder { 25 | white-space: pre; 26 | 27 | font-size: 12px; 28 | margin-right: 3px; 29 | } 30 | 31 | .tree-node-arrow { 32 | color: #6e6e6e; 33 | display: inline-block; 34 | font-size: 12px; 35 | margin-right: 3px; 36 | } 37 | .tree-node-arrow-expanded { 38 | transform: rotate(90deg); 39 | } 40 | .tree-node-arrow-collapsed { 41 | transform: rotate(0deg); 42 | } 43 | 44 | .tree-node-child-nodes-container { 45 | margin: 0; 46 | padding-left: 12px; 47 | } 48 | -------------------------------------------------------------------------------- /src/styles/index.less: -------------------------------------------------------------------------------- 1 | @import './chromelight/index.less'; 2 | @import './chromedark/index.less'; 3 | -------------------------------------------------------------------------------- /stories/stories-array.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { storiesOf } from '@storybook/vue' 3 | 4 | import ObjectInspector from '../src/index.js' 5 | 6 | storiesOf('Arrays', module) 7 | .add('Empty Array', () => ({ 8 | components: { ObjectInspector }, 9 | template: '', 10 | })) 11 | .add('Empty Array (show non-enumerable properties)', () => ({ 12 | components: { ObjectInspector }, 13 | template: 14 | '', 15 | })) 16 | .add('Basic Array', () => ({ 17 | components: { ObjectInspector }, 18 | template: '', 19 | })) 20 | .add('Array with different types of elements', () => ({ 21 | components: { ObjectInspector }, 22 | template: '', 23 | })) 24 | .add('Long Array', () => ({ 25 | components: { ObjectInspector }, 26 | template: '', 27 | data() { 28 | return { data: new Array(1000).fill(0).map((x, i) => i + '') } 29 | }, 30 | })) 31 | .add('Array with big objects', () => ({ 32 | components: { ObjectInspector }, 33 | template: '', 34 | data() { 35 | return { 36 | data: new Array(100).fill(0).map((x, i) => ({ 37 | key: i, 38 | name: `John #${i}`, 39 | dateOfBirth: new Date(i * 10e8), 40 | address: `${i} Main Street`, 41 | zip: 90210 + i, 42 | })), 43 | } 44 | }, 45 | })) 46 | .add('Uint32Array', () => ({ 47 | components: { ObjectInspector }, 48 | template: '', 49 | data() { 50 | return { data: new Uint32Array(1000) } 51 | }, 52 | })) 53 | -------------------------------------------------------------------------------- /stories/stories-basic-type.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { storiesOf } from '@storybook/vue' 3 | 4 | import ObjectInspector from '../src/index.js' 5 | 6 | storiesOf('Numbers', module) 7 | .add('positive', () => ({ 8 | components: { ObjectInspector }, 9 | template: '', 10 | })) 11 | .add('zero', () => ({ 12 | components: { ObjectInspector }, 13 | template: '', 14 | })) 15 | .add('negative', () => ({ 16 | components: { ObjectInspector }, 17 | template: '', 18 | })) 19 | .add('float', () => ({ 20 | components: { ObjectInspector }, 21 | template: '', 22 | })) 23 | .add('exponential', () => ({ 24 | components: { ObjectInspector }, 25 | template: '', 26 | })) 27 | .add('NaN', () => ({ 28 | components: { ObjectInspector }, 29 | template: '', 30 | })) 31 | .add('Infinity', () => ({ 32 | components: { ObjectInspector }, 33 | template: '', 34 | })) 35 | 36 | storiesOf('BigInts', module) 37 | .add('positive', () => ({ 38 | components: { ObjectInspector }, 39 | template: '', 40 | })) 41 | .add('zero', () => ({ 42 | components: { ObjectInspector }, 43 | template: '', 44 | })) 45 | .add('negative', () => ({ 46 | components: { ObjectInspector }, 47 | template: '', 48 | })) 49 | 50 | storiesOf('Strings', module) 51 | .add('empty string', () => ({ 52 | components: { ObjectInspector }, 53 | template: '', 54 | })) 55 | .add('simple', () => ({ 56 | components: { ObjectInspector }, 57 | template: '', 58 | })) 59 | 60 | storiesOf('Booleans', module) 61 | .add('true', () => ({ 62 | components: { ObjectInspector }, 63 | template: '', 64 | })) 65 | .add('false', () => ({ 66 | components: { ObjectInspector }, 67 | template: '', 68 | })) 69 | 70 | storiesOf('Undefined', module).add('undefined', () => ({ 71 | components: { ObjectInspector }, 72 | template: '', 73 | })) 74 | 75 | storiesOf('Null', module).add('null', () => ({ 76 | components: { ObjectInspector }, 77 | template: '', 78 | })) 79 | 80 | storiesOf('Symbols', module).add('test', () => ({ 81 | components: { ObjectInspector }, 82 | template: '', 83 | data() { 84 | return { data: Symbol.for('test') } 85 | }, 86 | })) 87 | -------------------------------------------------------------------------------- /stories/stories-maps-sets-functions.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { storiesOf } from '@storybook/vue' 3 | 4 | import ObjectInspector from '../src/index.js' 5 | 6 | storiesOf('Maps', module) 7 | .add('Map: Empty Map', () => ({ 8 | components: { ObjectInspector }, 9 | template: '', 10 | })) 11 | .add('Map: Boolean keys', () => ({ 12 | components: { ObjectInspector }, 13 | template: 14 | '', 15 | })) 16 | .add('Map: Regex keys', () => ({ 17 | components: { ObjectInspector }, 18 | template: 19 | '', 20 | })) 21 | .add('Map: String keys', () => ({ 22 | components: { ObjectInspector }, 23 | template: 24 | '', 25 | })) 26 | .add('Map: Object keys', () => ({ 27 | components: { ObjectInspector }, 28 | template: 29 | '', 30 | })) 31 | .add('Map: Array keys', () => ({ 32 | components: { ObjectInspector }, 33 | template: 34 | '', 35 | })) 36 | .add('Map: Map keys', () => ({ 37 | components: { ObjectInspector }, 38 | template: 39 | '', 40 | })) 41 | 42 | storiesOf('Sets', module) 43 | .add('Set: Empty Set', () => ({ 44 | components: { ObjectInspector }, 45 | template: '', 46 | })) 47 | .add('Set: Simple Set', () => ({ 48 | components: { ObjectInspector }, 49 | template: 50 | '', 51 | })) 52 | .add('Set: Nested Set', () => ({ 53 | components: { ObjectInspector }, 54 | template: 55 | '', 56 | })) 57 | 58 | storiesOf('Functions', module) 59 | .add('Functions: anonymous function', () => ({ 60 | components: { ObjectInspector }, 61 | template: '', 62 | })) 63 | .add('Functions: anonymous arrow function', () => ({ 64 | components: { ObjectInspector }, 65 | template: '', 66 | })) 67 | .add('Functions: named function', () => ({ 68 | components: { ObjectInspector }, 69 | template: '', 70 | data() { 71 | return { 72 | data: function namedFunction() {}, 73 | } 74 | }, 75 | })) 76 | .add('Functions: named function (show non-enumerable properties)', () => ({ 77 | components: { ObjectInspector }, 78 | template: 79 | '', 80 | data() { 81 | return { 82 | data: function namedFunction() {}, 83 | } 84 | }, 85 | })) 86 | -------------------------------------------------------------------------------- /stories/stories-object.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { storiesOf } from '@storybook/vue' 3 | 4 | import ObjectInspector from '../src/index.js' 5 | 6 | storiesOf('Nested object examples', module) 7 | .add('Ice sculpture', () => ({ 8 | components: { ObjectInspector }, 9 | template: '', 10 | data() { 11 | return { 12 | data: { 13 | id: 2, 14 | name: 'An ice sculpture', 15 | // "price": 12.50, 16 | tags: ['cold', 'ice'], 17 | dimensions: { 18 | length: 7.0, 19 | width: 12.0, 20 | height: 9.5, 21 | }, 22 | warehouseLocation: { 23 | latitude: -78.75, 24 | longitude: 20.4, 25 | }, 26 | }, 27 | } 28 | }, 29 | })) 30 | .add('Github', () => ({ 31 | components: { ObjectInspector }, 32 | template: '', 33 | data() { 34 | return { 35 | data: { 36 | login: 'vikyd', 37 | id: 2208880, 38 | avatar_url: 'https://avatars0.githubusercontent.com/u/2208880?v=4', 39 | gravatar_id: '', 40 | url: 'https://api.github.com/users/vikyd', 41 | html_url: 'https://github.com/vikyd', 42 | followers_url: 'https://api.github.com/users/vikyd/followers', 43 | following_url: 44 | 'https://api.github.com/users/vikyd/following{/other_user}', 45 | gists_url: 'https://api.github.com/users/vikyd/gists{/gist_id}', 46 | starred_url: 47 | 'https://api.github.com/users/vikyd/starred{/owner}{/repo}', 48 | subscriptions_url: 'https://api.github.com/users/vikyd/subscriptions', 49 | organizations_url: 'https://api.github.com/users/vikyd/orgs', 50 | repos_url: 'https://api.github.com/users/vikyd/repos', 51 | events_url: 'https://api.github.com/users/vikyd/events{/privacy}', 52 | received_events_url: 53 | 'https://api.github.com/users/vikyd/received_events', 54 | type: 'User', 55 | site_admin: false, 56 | name: 'VikydZhang', 57 | company: null, 58 | blog: 'https://github.com/vikyd/note', 59 | location: null, 60 | email: null, 61 | hireable: null, 62 | bio: 'Why am I here ?', 63 | twitter_username: null, 64 | public_repos: 76, 65 | public_gists: 4, 66 | followers: 15, 67 | following: 119, 68 | created_at: '2012-08-24T03:24:37Z', 69 | updated_at: '2021-01-05T07:52:04Z', 70 | }, 71 | } 72 | }, 73 | })) 74 | .add('Glossary', () => ({ 75 | components: { ObjectInspector }, 76 | template: '', 77 | data() { 78 | return { 79 | data: { 80 | glossary: { 81 | title: 'example glossary', 82 | GlossDiv: { 83 | title: 'S', 84 | GlossList: { 85 | GlossEntry: { 86 | ID: 'SGML', 87 | SortAs: 'SGML', 88 | GlossTerm: 'Standard Generalized Markup Language', 89 | Acronym: 'SGML', 90 | Abbrev: 'ISO 8879:1986', 91 | GlossDef: { 92 | para: 93 | 'A meta-markup language, used to create markup languages such as DocBook.', 94 | GlossSeeAlso: ['GML', 'XML'], 95 | }, 96 | GlossSee: 'markup', 97 | }, 98 | }, 99 | }, 100 | }, 101 | }, 102 | } 103 | }, 104 | })) 105 | .add('Contrived example', () => ({ 106 | components: { ObjectInspector }, 107 | template: '', 108 | data() { 109 | return { 110 | data: { 111 | a1: 1, 112 | a2: 'A2', 113 | a3: true, 114 | a4: undefined, 115 | a5: { 116 | 'a5-1': null, 117 | 'a5-2': ['a5-2-1', 'a5-2-2'], 118 | 'a5-3': {}, 119 | }, 120 | a6: function () { 121 | // eslint-disable-next-line 122 | console.log('hello world') 123 | }, 124 | a7: new Date('2005-04-03'), 125 | }, 126 | } 127 | }, 128 | })) 129 | 130 | storiesOf('Objects', module) 131 | .add('Object: Date', () => ({ 132 | components: { ObjectInspector }, 133 | template: '', 134 | })) 135 | .add('Object: Regular Expressin', () => ({ 136 | components: { ObjectInspector }, 137 | template: '', 138 | })) 139 | .add('Object: Empty Object', () => ({ 140 | components: { ObjectInspector }, 141 | template: 142 | '', 143 | })) 144 | .add('Object: Empty String key', () => ({ 145 | components: { ObjectInspector }, 146 | template: ``, 147 | })) 148 | .add('Object: Object with getter property', () => ({ 149 | components: { ObjectInspector }, 150 | template: 151 | '', 152 | })) 153 | .add('Object: Object with getter property that throws', () => ({ 154 | components: { ObjectInspector }, 155 | template: '', 156 | data() { 157 | // TODO: this example will not working now 158 | return { 159 | data: { 160 | get prop() { 161 | throw new Error() 162 | }, 163 | }, 164 | } 165 | }, 166 | })) 167 | .add('Object: Simple Object', () => ({ 168 | components: { ObjectInspector }, 169 | template: 170 | '', 171 | })) 172 | .add('Object: inherited object', () => ({ 173 | components: { ObjectInspector }, 174 | template: 175 | '', 176 | })) 177 | .add('Object: `Object`', () => ({ 178 | components: { ObjectInspector }, 179 | template: 180 | '', 181 | })) 182 | .add('Object: `Object.prototype`', () => ({ 183 | components: { ObjectInspector }, 184 | template: 185 | '', 186 | })) 187 | .add('Object: Simple Object with name', () => ({ 188 | components: { ObjectInspector }, 189 | template: 190 | '', 191 | })) 192 | .add( 193 | 'Object: `Object.create(null) (Empty object with null prototype)`', 194 | () => ({ 195 | components: { ObjectInspector }, 196 | template: 197 | '', 198 | }) 199 | ) 200 | .add('Object: Object with null prototype', () => ({ 201 | components: { ObjectInspector }, 202 | template: 203 | '', 204 | })) 205 | -------------------------------------------------------------------------------- /stories/stories-themes.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { storiesOf } from '@storybook/vue' 3 | 4 | import ObjectInspector from '../src/index.js' 5 | 6 | const data = { a: 1, b: 'abc', c: [1, 2, 3] } 7 | 8 | storiesOf('Themes', module) 9 | .add('chromeLight', () => ({ 10 | components: { ObjectInspector }, 11 | template: '', 12 | data() { 13 | return { 14 | data: data, 15 | } 16 | }, 17 | })) 18 | .add('chromeDark', () => ({ 19 | components: { ObjectInspector }, 20 | template: 21 | '', 22 | data() { 23 | return { 24 | data: data, 25 | } 26 | }, 27 | })) 28 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // modify dev page 3 | pages: { 4 | index: { 5 | // dev entry 6 | entry: 'example/main.js', 7 | template: 'example/public/index.html', 8 | filename: 'index.html', 9 | }, 10 | }, 11 | css: { extract: false }, 12 | } 13 | --------------------------------------------------------------------------------