├── options.html ├── eslint.config.js ├── jsconfig.json ├── src ├── constants │ ├── common.js │ └── options.js ├── vendor │ ├── d3-state-visualizer │ │ ├── lib │ │ │ ├── index.js │ │ │ ├── charts │ │ │ │ ├── index.js │ │ │ │ ├── tree │ │ │ │ │ ├── sortAndSerialize.d.ts │ │ │ │ │ ├── sortAndSerialize.js │ │ │ │ │ ├── utils.d.ts │ │ │ │ │ ├── tree.d.ts │ │ │ │ │ └── utils.js │ │ │ │ └── index.d.ts │ │ │ └── index.d.ts │ │ ├── src │ │ │ ├── index.ts │ │ │ └── charts │ │ │ │ ├── index.ts │ │ │ │ └── tree │ │ │ │ ├── sortAndSerialize.ts │ │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ ├── eslint.config.js │ │ ├── vite.config.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ └── CHANGELOG.md │ ├── d3tooltip │ │ ├── tsconfig.json │ │ ├── eslint.config.js │ │ ├── lib │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── CHANGELOG.md │ │ └── src │ │ │ └── index.ts │ ├── map2tree │ │ ├── tsconfig.json │ │ ├── tsconfig.test.json │ │ ├── jest.config.cjs │ │ ├── lib │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── eslint.config.js │ │ ├── pnpm-lock.yaml │ │ ├── package.json │ │ ├── LICENSE.md │ │ ├── CHANGELOG.md │ │ ├── src │ │ │ └── index.ts │ │ └── test │ │ │ └── map2tree.spec.ts │ ├── tooltip │ │ ├── style.css │ │ └── index.js │ └── switch │ │ ├── switch.css │ │ ├── switch.js │ │ └── example.html ├── utils │ ├── datetime.js │ ├── dowloadFile.js │ ├── json-viewer │ │ ├── json-viewer.css │ │ └── json-viewer.js │ ├── common.js │ ├── chart.ts │ ├── convertContent.js │ └── common.test.js ├── hooks │ └── usePrevious.jsx ├── components │ ├── Logo │ │ ├── index.jsx │ │ └── style.css │ ├── Editor │ │ ├── Toolbar │ │ │ ├── style.css │ │ │ └── index.jsx │ │ ├── style.css │ │ └── index.jsx │ ├── Icons │ │ ├── Tree.jsx │ │ ├── Copy.jsx │ │ ├── Branch.jsx │ │ ├── Brackets.jsx │ │ └── Gear.jsx │ ├── Searchbar │ │ ├── style.scss │ │ └── index.jsx │ ├── Select │ │ └── index.jsx │ └── Dropdown │ │ ├── style.scss │ │ └── index.jsx ├── testserver │ ├── dummy.json │ ├── index.html │ └── index.js ├── css │ ├── color-themes │ │ ├── mdn-light.css │ │ └── dark-pro.css │ └── style.scss ├── scripts │ ├── background.js │ └── contentScript.js ├── main.jsx ├── Menus.jsx ├── App.jsx └── TreeView.jsx ├── chrome-web-store.png ├── postcss.config.js ├── awesome-json-slideshow.gif ├── images └── icons │ ├── extensions.png │ ├── interface.png │ ├── json-file.png │ ├── organizer.png │ ├── seo-and-web.png │ └── bell.svg ├── extension-resources ├── images │ └── icons │ │ ├── icon-128.png │ │ ├── icon-16.png │ │ └── icon-32.png ├── index.html └── manifest.json ├── .prettierrc ├── .editorconfig ├── index.html ├── .gitignore ├── vite.options-page.config.js ├── vite.config.js ├── makefile ├── package.json └── README.md /options.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | {"typeAcquisition": {"include": ["chrome"]}} 2 | -------------------------------------------------------------------------------- /src/constants/common.js: -------------------------------------------------------------------------------- 1 | export const CSP_NONCE = '74554953-7310-42e7-a47a-03995c0de861'; -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/lib/index.js: -------------------------------------------------------------------------------- 1 | export { tree } from './charts/index.js'; 2 | -------------------------------------------------------------------------------- /chrome-web-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbrahul/Awesome-JSON-Viewer/HEAD/chrome-web-store.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | autoprefixer: {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/lib/charts/index.js: -------------------------------------------------------------------------------- 1 | export { default as tree } from './tree/tree.js'; 2 | -------------------------------------------------------------------------------- /awesome-json-slideshow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbrahul/Awesome-JSON-Viewer/HEAD/awesome-json-slideshow.gif -------------------------------------------------------------------------------- /images/icons/extensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbrahul/Awesome-JSON-Viewer/HEAD/images/icons/extensions.png -------------------------------------------------------------------------------- /images/icons/interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbrahul/Awesome-JSON-Viewer/HEAD/images/icons/interface.png -------------------------------------------------------------------------------- /images/icons/json-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbrahul/Awesome-JSON-Viewer/HEAD/images/icons/json-file.png -------------------------------------------------------------------------------- /images/icons/organizer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbrahul/Awesome-JSON-Viewer/HEAD/images/icons/organizer.png -------------------------------------------------------------------------------- /images/icons/seo-and-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbrahul/Awesome-JSON-Viewer/HEAD/images/icons/seo-and-web.png -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/lib/charts/tree/sortAndSerialize.d.ts: -------------------------------------------------------------------------------- 1 | export default function sortAndSerialize(obj: unknown): string; 2 | -------------------------------------------------------------------------------- /extension-resources/images/icons/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbrahul/Awesome-JSON-Viewer/HEAD/extension-resources/images/icons/icon-128.png -------------------------------------------------------------------------------- /extension-resources/images/icons/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbrahul/Awesome-JSON-Viewer/HEAD/extension-resources/images/icons/icon-16.png -------------------------------------------------------------------------------- /extension-resources/images/icons/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbrahul/Awesome-JSON-Viewer/HEAD/extension-resources/images/icons/icon-32.png -------------------------------------------------------------------------------- /src/vendor/d3tooltip/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.esm.base.json", 3 | "compilerOptions": { 4 | "outDir": "lib" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /src/vendor/map2tree/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.esm.base.json", 3 | "compilerOptions": { 4 | "outDir": "lib" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export { tree } from './charts/index.js'; 2 | export type { HierarchyPointNode, Node, Options, StyleValue, } from './charts/index.js'; 3 | -------------------------------------------------------------------------------- /src/vendor/map2tree/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.esm.base.json", 3 | "compilerOptions": { 4 | "types": ["jest"] 5 | }, 6 | "include": ["src", "test"] 7 | } 8 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/src/index.ts: -------------------------------------------------------------------------------- 1 | export { tree } from './charts/index.js'; 2 | export type { 3 | HierarchyPointNode, 4 | Node, 5 | Options, 6 | StyleValue, 7 | } from './charts/index.js'; 8 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.esm.base.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "noEmit": false 6 | }, 7 | "include": ["src"], 8 | } 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "singleQuote": true, 4 | "semi": true, 5 | "trailingComma": "all", 6 | "arrowParens": "always", 7 | "endOfLine": "lf", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/datetime.js: -------------------------------------------------------------------------------- 1 | export const currentDateTime = () => { 2 | const date = new Date(); 3 | return `${date.getFullYear()}${date.getMonth()+1}${date.getDate()}${date.getHours()}${date.getMinutes()}${date.getSeconds()}` 4 | } 5 | 6 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/src/charts/index.ts: -------------------------------------------------------------------------------- 1 | export type { HierarchyPointNode } from 'd3'; 2 | export type { StyleValue } from 'd3tooltip'; 3 | export { default as tree } from './tree/tree.js'; 4 | export type { Node, Options } from './tree/tree.js'; 5 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/lib/charts/index.d.ts: -------------------------------------------------------------------------------- 1 | export type { HierarchyPointNode } from 'd3'; 2 | export type { StyleValue } from '../../../d3tooltip'; 3 | export { default as tree } from './tree/tree.js'; 4 | export type { Node, Options } from './tree/tree.js'; 5 | -------------------------------------------------------------------------------- /src/vendor/d3tooltip/eslint.config.js: -------------------------------------------------------------------------------- 1 | import eslintJs from '../../eslint.js.config.base.mjs'; 2 | import eslintTs from '../../eslint.ts.config.base.mjs'; 3 | 4 | export default [ 5 | ...eslintJs, 6 | ...eslintTs(import.meta.dirname), 7 | { 8 | ignores: ['lib'], 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /src/vendor/map2tree/jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extensionsToTreatAsEsm: ['.ts'], 3 | moduleNameMapper: { 4 | '^(\\.{1,2}/.*)\\.js$': '$1', 5 | }, 6 | transform: { 7 | '^.+\\.ts$': ['ts-jest', { tsconfig: 'tsconfig.test.json', useESM: true }], 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /src/hooks/usePrevious.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | 4 | const usePrevious = (value) => { 5 | const ref = useRef(); 6 | useEffect(() => { 7 | ref.current = value; 8 | },[value]); 9 | return ref.current; 10 | }; 11 | 12 | export default usePrevious; 13 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/eslint.config.js: -------------------------------------------------------------------------------- 1 | import eslintJs from '../../eslint.js.config.base.mjs'; 2 | import eslintTs from '../../eslint.ts.config.base.mjs'; 3 | 4 | export default [ 5 | ...eslintJs, 6 | ...eslintTs(import.meta.dirname), 7 | { 8 | ignores: ['examples', 'lib'], 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /src/vendor/map2tree/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface Node { 2 | name: string; 3 | children?: this[]; 4 | object?: unknown; 5 | value?: unknown; 6 | } 7 | export declare function map2tree(root: unknown, options?: { 8 | key?: string; 9 | pushMethod?: 'push' | 'unshift'; 10 | }, tree?: Node): Node | {}; 11 | -------------------------------------------------------------------------------- /src/components/Logo/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import './style.css'; 3 | 4 | const Logo = () => { 5 | return (
6 | {'{..}'} 7 | JSON Viewer Pro 8 |
9 | ); 10 | } 11 | 12 | export default Logo; 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | charset = utf-8 11 | indent_style = space 12 | indent_size = 4 13 | trim_trailing_whitespace = true 14 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/vite.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import {defineConfig} from 'vite'; 3 | export default defineConfig({ 4 | build: { 5 | lib: { 6 | entry: [resolve(__dirname, './src/index.ts')], 7 | name: 'd3state', 8 | fileName: (format) => `d3state.${format}.js`, 9 | }, 10 | } 11 | }) -------------------------------------------------------------------------------- /src/vendor/map2tree/eslint.config.js: -------------------------------------------------------------------------------- 1 | import eslintJs from '../../eslint.js.config.base.mjs'; 2 | import eslintTs from '../../eslint.ts.config.base.mjs'; 3 | import eslintTsJest from '../../eslint.ts.jest.config.base.mjs'; 4 | 5 | export default [ 6 | ...eslintJs, 7 | ...eslintTs(import.meta.dirname), 8 | ...eslintTsJest(import.meta.dirname), 9 | { 10 | ignores: ['lib'], 11 | }, 12 | ]; 13 | -------------------------------------------------------------------------------- /src/vendor/map2tree/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | lodash-es: 12 | specifier: ^4.17.21 13 | version: 4.17.21 14 | 15 | packages: 16 | 17 | lodash-es@4.17.21: 18 | resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} 19 | 20 | snapshots: 21 | 22 | lodash-es@4.17.21: {} 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JSON Viewer Pro 7 | 8 | 9 |
10 |         {
11 |             "success": {
12 |               "message": "Welcome to JSON Viewer Pro",
13 |               "status_code": 200
14 |             }
15 |         }
16 |     
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # testing 5 | coverage 6 | 7 | # production 8 | build 9 | chrome-extension 10 | 11 | # misc 12 | .idea 13 | .env 14 | npm-debug.log 15 | *.zip 16 | .DS_Store 17 | # Logs 18 | logs 19 | *.log 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | pnpm-debug.log* 24 | lerna-debug.log* 25 | 26 | dist 27 | dist-options-page 28 | dist-ssr 29 | *.local 30 | 31 | # Editor directories and files 32 | .vscode/* 33 | !.vscode/extensions.json 34 | .idea 35 | .DS_Store 36 | *.suo 37 | *.ntvs* 38 | *.njsproj 39 | *.sln 40 | *.sw? 41 | -------------------------------------------------------------------------------- /src/utils/dowloadFile.js: -------------------------------------------------------------------------------- 1 | const downloadFile = (fileContent, contentType, fileName) => { 2 | const downloadBtn = document.createElement('a'); 3 | downloadBtn.id = 'rb-download-json'; 4 | downloadBtn.download = fileName; 5 | downloadBtn.style = 'display:none;'; 6 | downloadBtn.href = 7 | `data:${contentType};charset=utf-8,` + 8 | encodeURIComponent(fileContent); 9 | document.body.appendChild(downloadBtn); 10 | downloadBtn.click(); 11 | setTimeout(() => { 12 | document.body.removeChild(downloadBtn); 13 | }, 500); 14 | }; 15 | 16 | export default downloadFile; 17 | -------------------------------------------------------------------------------- /vite.options-page.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | //@ts-ignore 3 | import path from 'path'; 4 | import terser from '@rollup/plugin-terser'; 5 | 6 | export default defineConfig({ 7 | build: { 8 | outDir: 'dist-options-page', 9 | rollupOptions: { 10 | input: { 11 | options: path.resolve(__dirname, '/src/options/js/options.js'), 12 | }, 13 | output: { 14 | entryFileNames: `[name].js`, 15 | assetFileNames: `[name].[ext]`, 16 | format: 'iife', 17 | plugins: [terser()], 18 | }, 19 | }, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /src/components/Editor/Toolbar/style.css: -------------------------------------------------------------------------------- 1 | .jv-toolbar { 2 | box-sizing: border-box; 3 | display: flex; 4 | flex-direction: row; 5 | justify-content: flex-start; 6 | width: 100%; 7 | padding: 10px; 8 | background-color: var(--toolbar-bg-color); 9 | border:1px solid var(--toolbar-border-color); 10 | } 11 | 12 | .jv-toolbar .jv-btn { 13 | margin-right: 8px; 14 | display: inline-flex; 15 | justify-content: center; 16 | align-items: center; 17 | } 18 | 19 | .jv-content-type-menu{ 20 | width: 115px; 21 | margin-right: 10px; 22 | } 23 | 24 | .jv-content-type-btn{ 25 | padding: 6px 8px; 26 | box-sizing: border-box; 27 | } 28 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/lib/charts/tree/sortAndSerialize.js: -------------------------------------------------------------------------------- 1 | function sortObject(obj, strict) { 2 | if (obj instanceof Array) { 3 | let ary; 4 | if (strict) { 5 | ary = obj.sort(); 6 | } 7 | else { 8 | ary = obj; 9 | } 10 | return ary; 11 | } 12 | if (obj && typeof obj === 'object') { 13 | const tObj = {}; 14 | Object.keys(obj) 15 | .sort() 16 | .forEach((key) => (tObj[key] = sortObject(obj[key]))); 17 | return tObj; 18 | } 19 | return obj; 20 | } 21 | export default function sortAndSerialize(obj) { 22 | return JSON.stringify(sortObject(obj, true), undefined, 2); 23 | } 24 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/lib/charts/tree/utils.d.ts: -------------------------------------------------------------------------------- 1 | import type { InternalNode } from './tree.js'; 2 | export declare function collapseChildren(node: InternalNode): void; 3 | export declare function expandChildren(node: InternalNode): void; 4 | export declare function toggleChildren(node: InternalNode): InternalNode; 5 | export declare function visit(parent: InternalNode, visitFn: (parent: InternalNode) => void, childrenFn: (parent: InternalNode) => InternalNode[] | null | undefined): void; 6 | export declare function getNodeGroupByDepthCount(rootNode: InternalNode): number[]; 7 | export declare function getTooltipString(node: InternalNode, { indentationSize }: { 8 | indentationSize?: number | undefined; 9 | }): string; 10 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/src/charts/tree/sortAndSerialize.ts: -------------------------------------------------------------------------------- 1 | function sortObject(obj: unknown, strict?: boolean) { 2 | if (obj instanceof Array) { 3 | let ary; 4 | if (strict) { 5 | ary = obj.sort(); 6 | } else { 7 | ary = obj; 8 | } 9 | return ary; 10 | } 11 | 12 | if (obj && typeof obj === 'object') { 13 | const tObj: { [key: string]: unknown } = {}; 14 | Object.keys(obj) 15 | .sort() 16 | .forEach((key) => (tObj[key] = sortObject(obj[key as keyof typeof obj]))); 17 | return tObj; 18 | } 19 | 20 | return obj; 21 | } 22 | 23 | export default function sortAndSerialize(obj: unknown) { 24 | return JSON.stringify(sortObject(obj, true), undefined, 2); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Logo/style.css: -------------------------------------------------------------------------------- 1 | .jv-logo { 2 | display: inline-flex; 3 | align-items: center; 4 | justify-content: flex-start; 5 | } 6 | 7 | .jv-logo-icon { 8 | background-color: #7c4bde; 9 | margin: 0 auto; 10 | width: 36px; 11 | height: 36px; 12 | border-radius: 100%; 13 | border: 2px solid white; 14 | display: inline-flex; 15 | justify-content: center; 16 | align-items: center; 17 | font-weight: bold; 18 | box-sizing: border-box; 19 | padding-bottom:3px; 20 | margin-right: 15px; 21 | color: white; 22 | } 23 | 24 | .jv-logo-title { 25 | display: inline-block; 26 | font-size:18px; 27 | color:var(--primary-text-color); 28 | line-height: 100%; 29 | } 30 | -------------------------------------------------------------------------------- /extension-resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | JSON Viewer Pro 12 | 13 | 14 | 15 |
16 |      {
17 |        "success": {
18 |          "message": "Welcome to JSON Viewer Pro",
19 |          "status_code": 200
20 |         }
21 |        }
22 |    
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | //@ts-ignore 4 | import path from 'path'; 5 | import terser from '@rollup/plugin-terser'; 6 | 7 | export default defineConfig({ 8 | plugins: [react()], 9 | css: { 10 | postcss: './postcss.config.js', 11 | }, 12 | build: { 13 | cssCodeSplit: false, 14 | rollupOptions: { 15 | input: { 16 | main: path.resolve(__dirname, '/src/main.jsx'), 17 | }, 18 | output: { 19 | entryFileNames: `assets/[name].js`, 20 | assetFileNames: `assets/[name].[ext]`, 21 | format: 'iife', 22 | plugins: [terser()], 23 | }, 24 | }, 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /images/icons/bell.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/vendor/d3tooltip/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { BaseType, Selection } from 'd3'; 2 | export type StyleValue = string | number | boolean; 3 | interface Options { 4 | left: number | undefined; 5 | top: number | undefined; 6 | offset: { 7 | left: number; 8 | top: number; 9 | }; 10 | root: Selection | undefined; 11 | styles: { 12 | [key: string]: StyleValue; 13 | }; 14 | text: string | ((datum: Datum) => string); 15 | } 16 | export declare function tooltip(className?: string, options?: Partial>): (selection: Selection) => void; 17 | export {}; 18 | -------------------------------------------------------------------------------- /src/vendor/map2tree/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "map2tree", 3 | "version": "4.0.0", 4 | "description": "Utility for mapping maps to trees", 5 | "private": true, 6 | "license": "MIT", 7 | "files": [ 8 | "lib", 9 | "src" 10 | ], 11 | "main": "lib/index.js", 12 | "types": "lib/index.d.ts", 13 | "type": "module", 14 | "sideEffects": false, 15 | "scripts": { 16 | "build": "tsc", 17 | "clean": "rimraf lib", 18 | "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", 19 | "lint": "eslint .", 20 | "type-check": "tsc --noEmit", 21 | "prepack": "pnpm run clean && pnpm run build", 22 | "prepublish": "pnpm run lint && pnpm run test" 23 | }, 24 | "dependencies": { 25 | "lodash-es": "^4.17.21" 26 | }, 27 | "devDependencies": { 28 | "@types/jest": "^29.5.13", 29 | "@types/lodash-es": "^4.17.12", 30 | "immutable": "^4.3.7", 31 | "jest": "^29.7.0", 32 | "rimraf": "^6.0.1", 33 | "ts-jest": "^29.2.5", 34 | "typescript": "~5.5.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/json-viewer/json-viewer.css: -------------------------------------------------------------------------------- 1 | /* Syntax highlighting for JSON objects */ 2 | ul.json-dict, ol.json-array { 3 | list-style-type: none; 4 | margin: 0 0 0 1px; 5 | border-left: 1px dotted #5D6D7E; 6 | padding-left: 2em; 7 | } 8 | .json-string { 9 | color: #0B7500; 10 | } 11 | .json-literal { 12 | color: #b568de; 13 | font-weight: bold; 14 | } 15 | 16 | /* Toggle button */ 17 | a.json-toggle { 18 | position: relative; 19 | color: inherit; 20 | text-decoration: none; 21 | } 22 | a.json-toggle:focus { 23 | outline: none; 24 | } 25 | a.json-toggle:before { 26 | color: #aaa; 27 | content: "\25BC"; /* down arrow */ 28 | position: absolute; 29 | display: inline-block; 30 | width: 1em; 31 | left: -1em; 32 | font-size: 14px; 33 | } 34 | a.json-toggle.collapsed:before { 35 | content: "\25B6"; /* left arrow */ 36 | font-size: 14px; 37 | } 38 | 39 | /* Collapsable placeholder links */ 40 | a.json-placeholder { 41 | color: #aaa; 42 | padding: 0 1em; 43 | text-decoration: none; 44 | } 45 | a.json-placeholder:hover { 46 | text-decoration: underline; 47 | } 48 | -------------------------------------------------------------------------------- /src/constants/options.js: -------------------------------------------------------------------------------- 1 | export const DETECTION_METHOD_CONTENT_TYPE = 'contentType'; 2 | export const DETECTION_METHOD_JSON_CONTENT = 'jsonContent'; 3 | 4 | export const DEFAULT_SELECTED_CONTENT_TYPES = [ 5 | 'application/json', 6 | 'text/json', 7 | 'application/javascript', 8 | ]; 9 | 10 | export const DEFAULT_OPTIONS = { 11 | theme: 'default', 12 | collapsed: 0, 13 | css: ` 14 | /**Write your CSS style **/ 15 | #json-viewer { 16 | font-size: 15px; 17 | } 18 | 19 | .property{ 20 | /*color:#994c9e;*/ 21 | } 22 | 23 | .json-literal-numeric{ 24 | /*color:#F5B041;*/ 25 | } 26 | 27 | .json-literal-url { 28 | /*color: #34a632;*/ 29 | } 30 | 31 | .json-literal-string{ 32 | /*color:#0642b0;*/ 33 | } 34 | 35 | .json-literal{ 36 | /*color:#b568de;*/ 37 | } 38 | 39 | .json-literal-boolean{ 40 | /*color: #f23ebb;*/ 41 | }`, 42 | jsonDetection: { 43 | method: DETECTION_METHOD_CONTENT_TYPE, // contentType | jsonContent 44 | selectedContentTypes: DEFAULT_SELECTED_CONTENT_TYPES, 45 | }, 46 | }; 47 | 48 | export const DARK_THEMES = ['default', 'dark-pro']; 49 | -------------------------------------------------------------------------------- /src/vendor/d3tooltip/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 romseguy 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 | -------------------------------------------------------------------------------- /src/utils/common.js: -------------------------------------------------------------------------------- 1 | export const calculateFileSize = (size) => { 2 | for (let unit of ['bytes', 'KB', 'MB', 'GB', 'TB']) { 3 | if (size < 1000.0) { 4 | if (unit !== 'bytes') { 5 | size = size.toFixed(1); 6 | } 7 | return `${size} ${unit}`; 8 | } 9 | size /= 1000.0; 10 | } 11 | return size; 12 | }; 13 | 14 | export const iconFillColor = (isDarkMode) => { 15 | return { 16 | fillColor: isDarkMode ? '#FFFFFF' : '#000000', 17 | }; 18 | }; 19 | 20 | export const getURL = (assetPath) => { 21 | const optionUrl = window.extensionOptions?.optionPageURL ?? ''; 22 | try { 23 | const url = new URL(optionUrl); 24 | return `${url.origin}/${assetPath}`; 25 | } catch (error) { 26 | return assetPath; 27 | } 28 | }; 29 | 30 | const bigIntToStrTransformer = (key, value, context) => { 31 | if (typeof value === 'number' && value > Number.MAX_SAFE_INTEGER) { 32 | return String(context.source); 33 | } 34 | return value; 35 | }; 36 | 37 | export const parseJson = (jsonStr) => { 38 | return JSON.parse(jsonStr, bigIntToStrTransformer); 39 | }; 40 | -------------------------------------------------------------------------------- /src/vendor/map2tree/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Romain Séguy 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 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Romain Séguy 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 | -------------------------------------------------------------------------------- /src/utils/chart.ts: -------------------------------------------------------------------------------- 1 | export const getAppliedTransformation = ( 2 | element:HTMLElement 3 | ):Record => { 4 | const translateAttr = element.getAttribute('transform'); 5 | const appliedTransforms:Record = {}; 6 | if (translateAttr) { 7 | const translatePattern = 8 | /translate\((-?\d+\.?\d*)+,\s*(-?\d+\.?\d*)+\)/; 9 | const scalePattern = /scale\((-?\d+\.?\d*)+\)/; 10 | const translateExecResult = translatePattern.exec(translateAttr); 11 | 12 | if (translateExecResult) { 13 | appliedTransforms['translate'] = translateExecResult?.[0]; 14 | appliedTransforms['translateX'] = Number(translateExecResult?.[1]); 15 | appliedTransforms['translateY'] = Number(translateExecResult?.[2]); 16 | } 17 | const scalePatternExecResult = scalePattern.exec(translateAttr); 18 | if (scalePatternExecResult) { 19 | appliedTransforms['scale'] = scalePatternExecResult?.[0]; 20 | appliedTransforms['scaleValue'] = Number( 21 | scalePatternExecResult?.[1], 22 | ); 23 | } 24 | } 25 | return appliedTransforms; 26 | }; 27 | -------------------------------------------------------------------------------- /src/vendor/d3tooltip/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d3tooltip", 3 | "version": "4.0.0", 4 | "description": "A highly configurable tooltip for d3", 5 | "keywords": [ 6 | "d3", 7 | "tooltip" 8 | ], 9 | "homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/d3tooltip", 10 | "bugs": { 11 | "url": "https://github.com/reduxjs/redux-devtools/issues" 12 | }, 13 | "license": "MIT", 14 | "author": "romseguy", 15 | "files": [ 16 | "lib", 17 | "src" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "type": "module", 22 | "sideEffects": false, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/reduxjs/redux-devtools.git" 26 | }, 27 | "scripts": { 28 | "build": "tsc", 29 | "clean": "rimraf lib", 30 | "lint": "eslint .", 31 | "type-check": "tsc --noEmit", 32 | "prepack": "pnpm run clean && pnpm run build", 33 | "prepublish": "pnpm run lint" 34 | }, 35 | "devDependencies": { 36 | "@types/d3": "^7.4.3", 37 | "d3": "^7.9.0", 38 | "rimraf": "^6.0.1", 39 | "typescript": "~5.5.4" 40 | }, 41 | "peerDependencies": { 42 | "@types/d3": "^7.4.3", 43 | "d3": "^7.9.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | chrome_build_dir = $(shell pwd)/chrome-extension 2 | 3 | .PHONY: build package clean 4 | 5 | package: 6 | @rm -rf ${chrome_build_dir} 7 | @echo "✅ Deleted Existing Chrome Build Directory" 8 | @echo "🗂 Moving Required Files to the CHROME-EXTENSION" 9 | @mkdir ${chrome_build_dir} ${chrome_build_dir}/css ${chrome_build_dir}/js 10 | @cp ./dist/assets/main.js ${chrome_build_dir}/js 11 | @cp ./dist/assets/style.css ${chrome_build_dir}/css 12 | @cp -r ./extension-resources/ ${chrome_build_dir}/ 13 | @cp -r ./images/ ${chrome_build_dir}/images 14 | @mkdir ${chrome_build_dir}/css/color-themes 15 | @cp -r ./src/css/color-themes/ ${chrome_build_dir}/css/color-themes 16 | @cp -r ./src/css/codemirror.css ${chrome_build_dir}/css/codemirror.css 17 | @cp -r ./dist-options-page/options.js ${chrome_build_dir}/js/ 18 | @cp -r ./src/options/css/ ${chrome_build_dir}/css/ 19 | @cp -r ./src/options/options.html ${chrome_build_dir}/ 20 | @cp -r ./src/scripts/ ${chrome_build_dir}/js/ 21 | @cd $(chrome_build_dir); \ 22 | zip -r chrome-extension.zip . 23 | @echo "✅ Package has been created successfuly. And ready to be shiped 🎁" 24 | 25 | clean: 26 | @rm -rf ${chrome_build_dir} 27 | @rm -rf ./dist 28 | @rm -rf ./dist-options-page 29 | @echo "✅ Deleted BUILD and CHROME_EXTENSION Directories" -------------------------------------------------------------------------------- /src/components/Editor/style.css: -------------------------------------------------------------------------------- 1 | .jv-editor { 2 | position:relative; 3 | margin:0 auto; 4 | margin-top: 80px; 5 | width: 80%; 6 | } 7 | 8 | .jv-code-editor{ 9 | background-color: var(--json-input-bg-color); 10 | height: 600px; 11 | box-sizing: border-box; 12 | overflow:hidden; 13 | } 14 | 15 | .alert-close-btn{ 16 | float: right; 17 | background: none; 18 | border: none; 19 | outline: none; 20 | color: var(--primary-text-color); 21 | font-size: 20px; 22 | padding:0px; 23 | margin:0px; 24 | } 25 | 26 | .editor-footer{ 27 | position: absolute; 28 | bottom:-20px; 29 | right: 0px; 30 | left: 0px; 31 | background-color: var(--editor-footer-bg-color); 32 | color:var(--editor-footer-text-color); 33 | display: flex; 34 | flex-direction: row; 35 | justify-content: flex-start; 36 | align-items: center; 37 | font-size: 12px; 38 | padding: 5px 10px; 39 | border: 1px solid var(--editor-footer-border-color); 40 | } 41 | 42 | .message-area{ 43 | box-sizing: border-box; 44 | padding: 0px 10px; 45 | width: 75%; 46 | } 47 | 48 | .info-area{ 49 | box-sizing: border-box; 50 | width: 25%; 51 | border-left: 1px solid var(--editor-footer-border-color); 52 | padding: 0px 10px; 53 | } 54 | 55 | .cursor-position-info { 56 | margin-right: 20px; 57 | } 58 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d3-state-visualizer", 3 | "version": "3.0.0", 4 | "description": "Visualize your app state with a range of reusable charts", 5 | "keywords": [ 6 | "d3", 7 | "state", 8 | "store", 9 | "tree", 10 | "visualization" 11 | ], 12 | "homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/d3-state-visualizer", 13 | "bugs": { 14 | "url": "https://github.com/reduxjs/redux-devtools/issues" 15 | }, 16 | "license": "MIT", 17 | "author": "romseguy", 18 | "files": [ 19 | "dist", 20 | "lib", 21 | "src" 22 | ], 23 | "main": "lib/index.js", 24 | "types": "lib/index.d.ts", 25 | "type": "module", 26 | "sideEffects": false, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/reduxjs/redux-devtools.git" 30 | }, 31 | "scripts": { 32 | "build": "tsc", 33 | "clean": "rimraf lib", 34 | "lint": "eslint .", 35 | "type-check": "tsc --noEmit", 36 | "prepack": "pnpm run clean && pnpm run build", 37 | "prepublish": "pnpm run lint" 38 | }, 39 | "dependencies": { 40 | "@types/d3": "^7.4.3", 41 | "d3": "^7.9.0", 42 | "deepmerge": "^4.3.1", 43 | "lodash-es": "^4.17.21", 44 | "ramda": "^0.30.1" 45 | }, 46 | "devDependencies": { 47 | "@types/ramda": "^0.30.2", 48 | "rimraf": "^6.0.1", 49 | "typescript": "~5.5.4" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/utils/convertContent.js: -------------------------------------------------------------------------------- 1 | import { dump, load } from 'js-yaml'; 2 | import { XMLParser, XMLBuilder } from 'fast-xml-parser'; 3 | import { parseJson } from './common'; 4 | 5 | const XMLArrayRoot = 'XMLArrayRoot'; 6 | 7 | export const trimXMLArrayRoot = (jsObject) => { 8 | if (!Array.isArray(jsObject) && XMLArrayRoot in jsObject) { 9 | jsObject = jsObject[XMLArrayRoot]; 10 | } 11 | return jsObject; 12 | }; 13 | 14 | export const convertToJsObject = (content, currentFormat) => { 15 | if (currentFormat === 'YAML') { 16 | return load(content); 17 | } 18 | 19 | if (currentFormat === 'XML') { 20 | const parser = new XMLParser(); 21 | return parser.parse(content); 22 | } 23 | 24 | if (currentFormat === 'JSON') { 25 | return parseJson(content); 26 | } 27 | }; 28 | 29 | export const convertContent = (data, from, to) => { 30 | let result; 31 | let jsObject = convertToJsObject(data, from); 32 | 33 | if (to === 'JSON') { 34 | jsObject = trimXMLArrayRoot(jsObject); 35 | result = JSON.stringify(jsObject, null, 4); 36 | } else if (to === 'YAML') { 37 | jsObject = trimXMLArrayRoot(jsObject); 38 | result = dump(jsObject); 39 | } else if (to === 'XML') { 40 | const parserOptions = { 41 | arrayNodeName: XMLArrayRoot, 42 | format: true, 43 | }; 44 | const builder = new XMLBuilder(parserOptions); 45 | result = builder.build(jsObject); 46 | } 47 | return result; 48 | }; 49 | -------------------------------------------------------------------------------- /extension-resources/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JSON Viewer Pro", 3 | "short_name": "JSON Viewer", 4 | "offline_enabled": true, 5 | "version": "1.0.6", 6 | "manifest_version": 3, 7 | "icons": { 8 | "16": "images/icons/icon-16.png", 9 | "38": "images/icons/icon-32.png", 10 | "128": "images/icons/icon-128.png" 11 | }, 12 | "background": { 13 | "service_worker": "/js/background.js" 14 | }, 15 | "options_page": "options.html", 16 | "content_scripts": [ 17 | { 18 | "matches": [ 19 | "http://*/*", 20 | "https://*/*", 21 | "ftp://*/*", 22 | "file:///*", 23 | "*://*/*" 24 | ], 25 | "js": ["/js/contentScript.js"], 26 | "run_at": "document_start", 27 | "all_frames": false 28 | } 29 | ], 30 | "action": { 31 | "default_icon": "images/icons/icon-32.png" 32 | }, 33 | "permissions": ["contextMenus", "storage"], 34 | "host_permissions": ["*://*/*"], 35 | "web_accessible_resources": [ 36 | { 37 | "resources": [ 38 | "/js/main.js", 39 | "/css/style.css", 40 | "/css/color-themes/dark-pro.css", 41 | "/css/color-themes/mdn-light.css", 42 | "/css/codemirror.css", 43 | "/images/icons/bell.svg", 44 | "options.html" 45 | ], 46 | "matches": ["*://*/*", "ftp://*/*", "file:///*"] 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /src/vendor/map2tree/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 4.0.0 4 | 5 | ### Major Changes 6 | 7 | - 191d419: Convert d3 packages to ESM 8 | 9 | ## 3.0.0 10 | 11 | ### Major Changes 12 | 13 | - b323f77d: Remove UMD build. 14 | 15 | ## 2.0.0 16 | 17 | - Adds ESM build (https://github.com/reduxjs/redux-devtools/pull/997) and switches the default export to a named export in order to ensure that the CommonJS output and the ESM output are [interchangeable](https://rollupjs.org/guide/en/#outputexports): 18 | 19 | ```diff 20 | - import map2tree from 'map2tree'; 21 | + import { map2tree } from 'map2tree'; 22 | ``` 23 | 24 | ## [1.5.0](https://github.com/reduxjs/redux-devtools/compare/map2tree@1.4.2...map2tree@1.5.0) (2021-03-06) 25 | 26 | ### Features 27 | 28 | - **d3-state-visualizer:** convert to TypeScript ([#640](https://github.com/reduxjs/redux-devtools/issues/640)) ([0c78a5a](https://github.com/reduxjs/redux-devtools/commit/0c78a5a9a76ee7eff37dcd8e39272d98c03e0869)) 29 | - **d3tooltip:** convert to TypeScript ([#639](https://github.com/reduxjs/redux-devtools/issues/639)) ([3b580da](https://github.com/reduxjs/redux-devtools/commit/3b580dad4cb36abc395f9be139b2c3f94e872d87)) 30 | - **map2tree:** convert to TypeScript ([#638](https://github.com/reduxjs/redux-devtools/issues/638)) ([3b027f4](https://github.com/reduxjs/redux-devtools/commit/3b027f400e0e326596eedc2ee17ab45a8383080d)) 31 | 32 | ## 1.4.2 (2020-08-14) 33 | 34 | ### Bug Fixes 35 | 36 | - **map2tree:** consolidate immutable version ([#538](https://github.com/reduxjs/redux-devtools/issues/538)) ([999ed2a](https://github.com/reduxjs/redux-devtools/commit/999ed2ad8b4a09eddd55c2a944f5488ecce6bc7b)) 37 | -------------------------------------------------------------------------------- /src/vendor/d3tooltip/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 4.0.0 4 | 5 | ### Major Changes 6 | 7 | - 191d419: Convert d3 packages to ESM 8 | 9 | ## 3.0.1 10 | 11 | ### Patch Changes 12 | 13 | - 7f5bddbd: Widen peer dependencies 14 | 15 | ## 3.0.0 16 | 17 | ### Major Changes 18 | 19 | - b323f77d: Upgrade D3 20 | 21 | - Remove UMD build. 22 | - Upgrade d3 peer dependency from v3 to v7. 23 | - Remove `attr` configuration method. 24 | - Rename `style` configuration method to `styles` and move to options. 25 | - Move `text` configuration method to options. 26 | - Remove d3 parameter as first parameter for `tooltip`. 27 | 28 | ## 2.0.0 29 | 30 | - Adds ESM build (https://github.com/reduxjs/redux-devtools/pull/997) and switches the default export to a named export in order to ensure that the CommonJS output and the ESM output are [interchangeable](https://rollupjs.org/guide/en/#outputexports): 31 | 32 | ```diff 33 | - import d3tooltip from 'd3tooltip'; 34 | + import { tooltip } from 'd3tooltip'; 35 | ``` 36 | 37 | ## [1.3.0](https://github.com/reduxjs/redux-devtools/compare/d3tooltip@1.2.3...d3tooltip@1.3.0) (2021-03-06) 38 | 39 | ### Features 40 | 41 | - **d3-state-visualizer:** convert to TypeScript ([#640](https://github.com/reduxjs/redux-devtools/issues/640)) ([0c78a5a](https://github.com/reduxjs/redux-devtools/commit/0c78a5a9a76ee7eff37dcd8e39272d98c03e0869)) 42 | - **d3tooltip:** convert to TypeScript ([#639](https://github.com/reduxjs/redux-devtools/issues/639)) ([3b580da](https://github.com/reduxjs/redux-devtools/commit/3b580dad4cb36abc395f9be139b2c3f94e872d87)) 43 | 44 | ## 1.2.3 (2020-08-14) 45 | 46 | **Note:** Version bump only for package d3tooltip 47 | -------------------------------------------------------------------------------- /src/vendor/tooltip/style.css: -------------------------------------------------------------------------------- 1 | #tooltip-msg { 2 | position:absolute; 3 | padding: 15px; 4 | border-radius: 4px; 5 | color:#fff; 6 | font-size: 14px; 7 | transition: ease-in-ease-out .3s; 8 | background:black; 9 | z-index:100000000000; 10 | max-width:300px; 11 | } 12 | 13 | #tooltip-msg.small { 14 | padding: 8px; 15 | font-size: 12px; 16 | } 17 | 18 | #tooltip-msg.medium { 19 | padding: 10px; 20 | font-size: 13px; 21 | } 22 | 23 | #tooltip-msg:before { 24 | content: ''; 25 | width:0px; 26 | height:0px; 27 | border:8px solid black; 28 | z-index:99999999; 29 | position:absolute; 30 | z-index:-1; 31 | } 32 | .hidden{ 33 | display:none !important; 34 | } 35 | .visible{ 36 | display:block !important; 37 | } 38 | 39 | #tooltip-msg.dir-right { 40 | transform: translateY(-50%); 41 | } 42 | 43 | #tooltip-msg.dir-right::before { 44 | left: -14px; 45 | top: calc(50% - 2px); 46 | transform: rotate(45deg) translateY(-50%); 47 | } 48 | 49 | #tooltip-msg.dir-bottom { 50 | transform: translateX(-50%); 51 | } 52 | 53 | #tooltip-msg.dir-bottom::before { 54 | left: 50%; 55 | top: -2px; 56 | transform: rotate(45deg) translateX(-50%); 57 | } 58 | 59 | #tooltip-msg.dir-left { 60 | transform: translateX(-100%) translateY(-50%); 61 | } 62 | 63 | #tooltip-msg.dir-left::before { 64 | right: -2px; 65 | top: calc(50% - 2px); 66 | transform: rotate(45deg) translateY(-50%); 67 | } 68 | 69 | #tooltip-msg.dir-top { 70 | transform: translateX(-50%) translateY(-100%); 71 | } 72 | 73 | #tooltip-msg.dir-top::before { 74 | left: 50%; 75 | bottom: -12px; 76 | transform: rotate(45deg) translateX(-50%); 77 | } -------------------------------------------------------------------------------- /src/components/Icons/Tree.jsx: -------------------------------------------------------------------------------- 1 | const Tree = ({ className, fillColor = '#ffffff' }) => { 2 | return ( 3 | 11 | 15 | 16 | ); 17 | }; 18 | 19 | export default Tree; 20 | -------------------------------------------------------------------------------- /src/testserver/dummy.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "67fb877de766c949c7a54a37", 4 | "index": 0, 5 | "guid": "f809a75c-4d40-4416-b500-7f1ca560ab8f", 6 | "isActive": false, 7 | "balance": "$2,772.84", 8 | "picture": "http://placehold.it/32x32", 9 | "salary": 21, 10 | "eyeColor": "brown", 11 | "name": "Alford Ashley", 12 | "gender": "male", 13 | "company": "NIPAZ", 14 | "email": "alfordashley@nipaz.com", 15 | "phone": "+1 (828) 503-2560", 16 | "address": "659 Linden Boulevard, Southmont, Alabama, 8543" 17 | }, 18 | 19 | { 20 | "_id": "67fb877d165ded4382e0ecd9", 21 | "index": 2, 22 | "guid": "279df46c-3690-4575-a4cc-62693078b50b", 23 | "isActive": false, 24 | "balance": "$1,483.39", 25 | "picture": "http://placehold.it/32x32", 26 | "salary": 30, 27 | "eyeColor": "green", 28 | "name": "Delia Medina", 29 | "gender": "female", 30 | "company": "GENEKOM", 31 | "email": "deliamedina@genekom.com", 32 | "phone": "+1 (820) 415-2907", 33 | "address": "192 Union Street, Waterford, Oregon, 5854" 34 | }, 35 | { 36 | "_id": "67fb877dc434d258aa0ae48f", 37 | "index": 3, 38 | "guid": "45baeed3-b4d5-4563-9bca-399f7d0445d1", 39 | "isActive": true, 40 | "balance": "$1,633.62", 41 | "picture": "http://placehold.it/32x32", 42 | "salary": 24, 43 | "eyeColor": "blue", 44 | "name": "Stark Farrell", 45 | "gender": "male", 46 | "company": "POLARIUM", 47 | "email": "starkfarrell@polarium.com", 48 | "phone": "+1 (915) 503-3694", 49 | "address": "528 Bassett Avenue, Frizzleburg, Nebraska, 8538" 50 | } 51 | ] 52 | -------------------------------------------------------------------------------- /src/vendor/switch/switch.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --switch-accent-color: #4868f7; 3 | --switch-btn-size: 26px; 4 | } 5 | 6 | .checkbox-ui-wrapper { 7 | position: relative; 8 | display: inline-block; 9 | border-radius: 50px; 10 | width: 60px; 11 | border: 1px solid rgb(196, 196, 196); 12 | box-sizing: border-box; 13 | padding: 3px; 14 | height: 30px; 15 | background-color: #efefef; 16 | background-image: linear-gradient( 17 | to right, 18 | var(--switch-accent-color), 19 | var(--switch-accent-color) 20 | ); 21 | background-position: 0px 0px; 22 | background-size: 0%; 23 | background-repeat: no-repeat; 24 | transition: background-size 0.2s ease-in; 25 | cursor: pointer; 26 | } 27 | 28 | .checkbox-ui-btn { 29 | box-sizing: border-box; 30 | position: absolute; 31 | width: var(--switch-btn-size); 32 | height: var(--switch-btn-size); 33 | content: ''; 34 | display: block; 35 | background: #fff; 36 | border: 1px solid rgb(196, 196, 196); 37 | border-radius: 50%; 38 | left: 0px; 39 | right: initial; 40 | top: 1px; 41 | pointer-events: none; 42 | user-select: none; 43 | transition: left 0.2s ease-in; 44 | } 45 | 46 | .checkbox-ui-checkbox-shadow { 47 | display: none; 48 | } 49 | 50 | .inline-middle { 51 | display: inline-flex; 52 | justify-content: flex-start; 53 | align-items: center; 54 | } 55 | 56 | .checkbox-ui-wrapper > input[type='checkbox']:checked + .checkbox-ui-btn, 57 | .checkbox-ui-wrapper > input[type='radio']:checked + .checkbox-ui-btn { 58 | left: calc(100% - var(--switch-btn-size)); 59 | border-color: var(--switch-accent-color) !important; 60 | } 61 | 62 | .checkbox-ui-wrapper:has(> input[type='checkbox']:checked), 63 | .checkbox-ui-wrapper:has(> input[type='radio']:checked) { 64 | background-size: 100%; 65 | } 66 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/lib/charts/tree/tree.d.ts: -------------------------------------------------------------------------------- 1 | import type { HierarchyPointNode } from 'd3'; 2 | import type { Node } from 'map2tree'; 3 | import type { StyleValue } from 'd3tooltip'; 4 | export interface Options { 5 | state?: {} | null; 6 | tree?: Node | {}; 7 | rootKeyName: string; 8 | pushMethod: 'push' | 'unshift'; 9 | id: string; 10 | chartStyles: { 11 | [key: string]: StyleValue; 12 | }; 13 | nodeStyleOptions: { 14 | colors: { 15 | default: string; 16 | collapsed: string; 17 | parent: string; 18 | }; 19 | radius: number; 20 | }; 21 | textStyleOptions: { 22 | colors: { 23 | default: string; 24 | hover: string; 25 | }; 26 | }; 27 | linkStyles: { 28 | [key: string]: StyleValue; 29 | }; 30 | size: number; 31 | aspectRatio: number; 32 | initialZoom: number; 33 | margin: { 34 | top: number; 35 | right: number; 36 | bottom: number; 37 | left: number; 38 | }; 39 | isSorted: boolean; 40 | heightBetweenNodesCoeff: number; 41 | widthBetweenNodesCoeff: number; 42 | transitionDuration: number; 43 | blinkDuration: number; 44 | onClickText: (datum: HierarchyPointNode) => void; 45 | tooltipOptions: { 46 | disabled?: boolean; 47 | left?: number | undefined; 48 | top?: number | undefined; 49 | offset?: { 50 | left: number; 51 | top: number; 52 | }; 53 | styles?: { 54 | [key: string]: StyleValue; 55 | } | undefined; 56 | indentationSize?: number; 57 | }; 58 | } 59 | export interface InternalNode extends Node { 60 | _children?: this[] | undefined; 61 | id: string | number; 62 | } 63 | export default function (DOMNode: HTMLElement, options?: Partial): (nextState?: {} | null | undefined) => void; 64 | export type { Node }; 65 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 3.0.0 4 | 5 | ### Major Changes 6 | 7 | - 191d419: Convert d3 packages to ESM 8 | 9 | ### Patch Changes 10 | 11 | - Updated dependencies [191d419] 12 | - d3tooltip@4.0.0 13 | - map2tree@4.0.0 14 | 15 | ## 2.0.0 16 | 17 | ### Major Changes 18 | 19 | - b323f77d: Upgrade D3 20 | 21 | - Remove UMD build. 22 | - Split `style` option into `chartStyles`, `nodeStyleOptions`, `textStyleOptions`, and `linkStyles`. 23 | - The shape of the argument passed to the `onClickText` option has been updated. 24 | - Rename `InputOptions` to `Options`, `Primitive` to `StyleValue`, and `NodeWithId` to `HierarchyPointNode`. 25 | 26 | ### Patch Changes 27 | 28 | - Updated dependencies [b323f77d] 29 | - Updated dependencies [b323f77d] 30 | - d3tooltip@3.0.0 31 | - map2tree@3.0.0 32 | 33 | ## [1.4.0](https://github.com/reduxjs/redux-devtools/compare/d3-state-visualizer@1.3.4...d3-state-visualizer@1.4.0) (2021-03-06) 34 | 35 | ### Features 36 | 37 | - **d3-state-visualizer:** convert example to TypeScript ([#641](https://github.com/reduxjs/redux-devtools/issues/641)) ([300b60a](https://github.com/reduxjs/redux-devtools/commit/300b60a8b1f92a6d7c78510a1bea304490aa23be)) 38 | - **d3-state-visualizer:** convert to TypeScript ([#640](https://github.com/reduxjs/redux-devtools/issues/640)) ([0c78a5a](https://github.com/reduxjs/redux-devtools/commit/0c78a5a9a76ee7eff37dcd8e39272d98c03e0869)) 39 | - **redux-devtools-chart-monitor:** convert to TypeScript ([#642](https://github.com/reduxjs/redux-devtools/issues/642)) ([761baba](https://github.com/reduxjs/redux-devtools/commit/761baba0aa0f4dc672f8771f4b12bed3863557f7)) 40 | 41 | ## [1.3.4](https://github.com/reduxjs/redux-devtools/compare/d3-state-visualizer@1.3.3...d3-state-visualizer@1.3.4) (2020-09-07) 42 | 43 | **Note:** Version bump only for package d3-state-visualizer 44 | 45 | ## 1.3.3 (2020-08-14) 46 | 47 | **Note:** Version bump only for package d3-state-visualizer 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-viewer-pro", 3 | "version": "1.0.6", 4 | "private": true, 5 | "type": "module", 6 | "devDependencies": { 7 | "@eslint/js": "^9.16.0", 8 | "@rollup/plugin-terser": "^0.4.4", 9 | "@types/react": "^18.3.12", 10 | "@types/react-dom": "^18.3.1", 11 | "@vitejs/plugin-react": "^4.3.4", 12 | "autoprefixer": "^10.4.21", 13 | "eslint": "^9.16.0", 14 | "eslint-plugin-react": "^7.37.2", 15 | "eslint-plugin-react-hooks": "^5.0.0", 16 | "eslint-plugin-react-refresh": "^0.4.14", 17 | "globals": "^15.13.0", 18 | "rollup-plugin-postcss": "^4.0.2", 19 | "sass-embedded": "^1.82.0", 20 | "vite": "^6.0.2" 21 | }, 22 | "dependencies": { 23 | "@babel/runtime": "^7.26.9", 24 | "@codemirror/lang-css": "^6.3.1", 25 | "@codemirror/lang-json": "^6.0.1", 26 | "@codemirror/lang-xml": "^6.1.0", 27 | "@codemirror/lang-yaml": "^6.1.2", 28 | "@uiw/codemirror-theme-github": "^4.23.10", 29 | "clsx": "^2.1.1", 30 | "codemirror": "^6.0.1", 31 | "d3": "^7.9.0", 32 | "fast-xml-parser": "^5.0.8", 33 | "honey-toast": "^1.0.2", 34 | "jquery": "^3.1.1", 35 | "js-yaml": "^4.1.0", 36 | "jsonpath-plus": "^10.3.0", 37 | "prop-types": "^15.8.1", 38 | "react": "^18.3.1", 39 | "react-dom": "^18.3.1", 40 | "react-icons": "^5.5.0" 41 | }, 42 | "scripts": { 43 | "start": "vite", 44 | "dev:option": "vite ./src/options", 45 | "build": "NODE_ENV=production vite build", 46 | "build:option": "NODE_ENV=production vite build -c ./vite.options-page.config.js", 47 | "package": "npm run build && npm run build:option && make package", 48 | "test:units": "node --test './src/utils/*.test.*'", 49 | "test:ctype": "node './src/testserver/index.js'", 50 | "lint": "eslint .", 51 | "preview": "vite preview" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/vendor/d3tooltip/lib/index.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3'; 2 | const defaultOptions = { 3 | left: undefined, // mouseX 4 | top: undefined, // mouseY 5 | offset: { left: 0, top: 0 }, 6 | root: undefined, 7 | styles: {}, 8 | text: '', 9 | }; 10 | export function tooltip(className = 'tooltip', options = {}) { 11 | const { left, top, offset, root, styles, text } = { 12 | ...defaultOptions, 13 | ...options, 14 | }; 15 | let el; 16 | const anchor = root || d3.select('body'); 17 | const rootNode = anchor.node(); 18 | return function tip(selection) { 19 | selection.on('mouseover.tip', (event, datum) => { 20 | const [pointerX, pointerY] = d3.pointer(event, rootNode); 21 | const [x, y] = [ 22 | left || pointerX + offset.left, 23 | top || pointerY - offset.top, 24 | ]; 25 | anchor.selectAll(`div.${className}`).remove(); 26 | el = anchor 27 | .append('div') 28 | .attr('class', className) 29 | .style('position', 'absolute') 30 | .style('z-index', 1001) 31 | .style('left', `${x}px`) 32 | .style('top', `${y}px`) 33 | .html(typeof text === 'function' ? () => text(datum) : () => text); 34 | for (const [key, value] of Object.entries(styles)) { 35 | el.style(key, value); 36 | } 37 | }); 38 | selection.on('mousemove.tip', (event, datum) => { 39 | const [pointerX, pointerY] = d3.pointer(event, rootNode); 40 | const [x, y] = [ 41 | left || pointerX + offset.left, 42 | top || pointerY - offset.top, 43 | ]; 44 | el.style('left', `${x}px`) 45 | .style('top', `${y}px`) 46 | .html(typeof text === 'function' ? () => text(datum) : () => text); 47 | }); 48 | selection.on('mouseout.tip', () => el.remove()); 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Searchbar/style.scss: -------------------------------------------------------------------------------- 1 | .searchbar { 2 | display: flex; 3 | flex-direction: column; 4 | width: 350px; 5 | position: fixed; 6 | background-color: var(--searchbar-bg-color); 7 | top: 15px; 8 | right: 390px; 9 | border-top: 1px solid var(--searchbar-border-color); 10 | border-radius: 10px; 11 | padding: 6px; 12 | box-shadow: 1px 9px 30px 7px var(--searchbar-box-shadow-color); 13 | } 14 | 15 | .search-input-container { 16 | width: 100%; 17 | display: inline-flex; 18 | justify-content: flex-start; 19 | align-items: center; 20 | position: relative; 21 | } 22 | 23 | .search-info-container { 24 | width: 100%; 25 | display: flex; 26 | justify-content: flex-start; 27 | align-items: center; 28 | padding: 10px; 29 | font-size: 14px; 30 | color: var(--searchbar-info-text-color); 31 | } 32 | 33 | .search-input { 34 | background-color: var(--searchbar-text-input-bg-color); 35 | padding: 8px 10px; 36 | border-radius: 6px; 37 | border: 1px solid var(--searchbar-text-input-border-color); 38 | color: var(--searchbar-text-input-text-color); 39 | font-size: 14px; 40 | font-weight: bold; 41 | width: 350px; 42 | padding-right: 50px; 43 | box-sizing: border-box; 44 | font-family: monospace; 45 | } 46 | 47 | .search-input:focus { 48 | outline: 1px solid var(--searchbar-text-input-focus-outline-color); 49 | } 50 | 51 | .search-clear-btn { 52 | padding: 4px 4px 0px 4px; 53 | color: var(--search-clear-btn-text-color); 54 | margin-left: -34px; 55 | border-radius: 6px; 56 | box-sizing: border-box; 57 | border-left: none; 58 | font-size: 22px; 59 | background-color: var(--search-clear-btn-bg-color); 60 | cursor: pointer; 61 | } 62 | 63 | .path-suggestions { 64 | position: absolute; 65 | width: 280px; 66 | top: 40px; 67 | left: 40px; 68 | 69 | .path-autocompletion { 70 | .dropdown-list-items { 71 | top: 0px; 72 | font-family: monospace; 73 | max-height: 240px; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/components/Select/index.jsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useMemo, useState } from "react" 2 | import PropTypes from 'prop-types'; 3 | 4 | import DropDown from "../Dropdown"; 5 | 6 | 7 | const findSelectedLabel = (label, items) => { 8 | if (label) { 9 | const matchedLabelItem = items.find((item) => item.label === label); 10 | if (matchedLabelItem) { 11 | return matchedLabelItem.label; 12 | } 13 | return label; 14 | } 15 | const selectedItem = items.find(({selected}) => !!selected); 16 | return !!selectedItem ? selectedItem.label: items?.[0]?.label ?? ''; 17 | }; 18 | 19 | const Select = ({ 20 | onChange = () => {}, 21 | size, 22 | label, 23 | items, 24 | ...props 25 | }) => { 26 | const [menuItems, setMenuItems] = useState(items); 27 | 28 | const onClick = useCallback((value) => { 29 | onChange(value); 30 | }, [onChange]); 31 | 32 | let selectedLabel = findSelectedLabel(label, menuItems); 33 | 34 | useEffect(() => { 35 | const modifiedListeItems = items.map(item => { 36 | item.onClick = onClick; 37 | if (item.label === label) { 38 | item.selected = true; 39 | } else { 40 | item.selected = false; 41 | } 42 | return item; 43 | }); 44 | setMenuItems(modifiedListeItems); 45 | selectedLabel = findSelectedLabel(label, menuItems); 46 | }, [label, items]); 47 | 48 | return 49 | } 50 | 51 | 52 | Select.propTypes = { 53 | size: PropTypes.string, 54 | className: PropTypes.string, 55 | labelIcon: PropTypes.node, 56 | hasCaretIcon: PropTypes.string, 57 | open: PropTypes.bool, 58 | label: PropTypes.string, 59 | items: PropTypes.arrayOf(PropTypes.shape({ 60 | label: PropTypes.string, 61 | iconUrl: PropTypes.string, 62 | onClick: PropTypes.func 63 | })), 64 | onChange: PropTypes.func, 65 | onClose: PropTypes.func, 66 | }; 67 | 68 | 69 | export default Select; 70 | -------------------------------------------------------------------------------- /src/components/Icons/Copy.jsx: -------------------------------------------------------------------------------- 1 | const Copy = ({ className, fillColor = '#ffffff' }) => { 2 | return ( 3 | 12 | 16 | 20 | 26 | 27 | ); 28 | }; 29 | 30 | export default Copy; 31 | -------------------------------------------------------------------------------- /src/utils/common.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict'; 2 | import { describe, it } from 'node:test'; 3 | 4 | import { parseJson } from './common.js'; 5 | 6 | const getValidDummyJSON = () => { 7 | return `{ 8 | "_id": "67e4330f7592be6a1d6b017b", 9 | "index": 0, 10 | "guid": "6a216e78-d103-426b-aa14-dc1d775e1513", 11 | "isActive": false, 12 | "balance": "$1,162.00", 13 | "picture": "http://placehold.it/32x32", 14 | "salary": 39, 15 | "eyeColor": "brown", 16 | "name": "Hopkins Stark", 17 | "gender": "male", 18 | "company": "NETUR", 19 | "email": "hopkinsstark@netur.com", 20 | "phone": "+1 (815) 542-3160", 21 | "address": "618 Stockholm Street, Darrtown, Arizona, 8470" 22 | }`; 23 | }; 24 | 25 | const getValidDummyJSONWithLargeNumber = () => { 26 | return `{ 27 | "largeNumberA": 149883901923910003, 28 | "largeNumberWithFloatValue": 149883901923910003.119, 29 | "largeNumberX": "114988390192391005", 30 | "more": { 31 | "largeNumberB": 149883901923910004 32 | } 33 | }`; 34 | }; 35 | 36 | describe('parseJson', () => { 37 | it('should test parseJson parses a Json string', () => { 38 | const dummyJson = getValidDummyJSON(); 39 | assert.doesNotThrow(() => { 40 | parseJson(dummyJson); 41 | }); 42 | const result = parseJson(dummyJson); 43 | assert.deepEqual(result, JSON.parse(dummyJson)); 44 | }); 45 | 46 | it('should parse Json string and convert Big Number to string', () => { 47 | const dummyJson = getValidDummyJSONWithLargeNumber(); 48 | assert.doesNotThrow(() => { 49 | parseJson(dummyJson); 50 | }); 51 | const expectedObj = { 52 | largeNumberA: '149883901923910003', 53 | largeNumberWithFloatValue: '149883901923910003.119', 54 | largeNumberX: '114988390192391005', 55 | more: { 56 | largeNumberB: '149883901923910004', 57 | }, 58 | }; 59 | const result = parseJson(dummyJson); 60 | assert.deepEqual(result, expectedObj); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/css/color-themes/mdn-light.css: -------------------------------------------------------------------------------- 1 | :root{ 2 | --bg-color:#fff; 3 | --primary-text-color: #111; 4 | --breadcumb-border-color: #ccc; 5 | --breadcumb-bg-color: #f6f6f6; 6 | --copy-path-btn-img-filter: invert(1); 7 | --menu-item-img-filter: invert(1); 8 | 9 | --menu-item-border-color: #ccc; 10 | --menu-item-bg-color: #f6f6f6; 11 | --menu-item-active-border-color: #796cd9; 12 | --menu-item-active-bg-color: #E9EDFE; 13 | --breadcumb-item-hover-color: #796cd9; 14 | --breadcumb-arrow-color: #796cd9; 15 | 16 | --copyable-property-bg-color: rgba(231, 232, 233, 0.8); 17 | --copyable-property-border-color: #5d6d7e; 18 | --btn-bg-color: #fff; 19 | --btn-border-color: #ccc; 20 | --btn-default-bg-color: #fff; 21 | --btn-primary-bg-color: #433387; 22 | 23 | /* JSON Tree Color Scheme*/ 24 | --property-color: #994c9e; 25 | --json-literal-numeric: #f5b041; 26 | --json-literal-url: #34a632; 27 | --json-literal-string: #0642b0; 28 | --json-literal-and-boolean: #f23ebb; 29 | 30 | --copier-bg-color: #fff; 31 | --copier-text-color: #2ecc71; 32 | 33 | /* chart view*/ 34 | --json-chart-circle-bg: #01ff70; 35 | 36 | /* editor */ 37 | /* --json-input-bg-color: white; */ 38 | --json-input-border-color: #ccc; 39 | --error-msg-bg-color: #1f0604; 40 | --editor-footer-bg-color: #efefef; 41 | --editor-footer-text-color: #868585; 42 | --editor-footer-border-color: #ccc; 43 | 44 | /* dropdown */ 45 | --dropdown-bg-primary: #fff; 46 | --dropdown-menu-item-bg: #efefef; 47 | --dropdown-menu-item-border-color: #ccc; 48 | --dropdown-menu-border-color: #ccc; 49 | 50 | /* json path search bar */ 51 | --searchbar-bg-color:#ffffff; 52 | --searchbar-border-color:#ccc; 53 | --searchbar-box-shadow-color:rgba(135, 133, 133, 0.4); 54 | --searchbar-text-input-bg-color: #efefef; 55 | --searchbar-text-input-border-color: #ccc; 56 | --searchbar-text-input-text-color: #994c9e; 57 | --searchbar-text-input-focus-outline-color: #ccc; 58 | --searchbar-info-text-color: #565454; 59 | --search-clear-btn-text-color: var(--primary-text-color); 60 | --search-clear-btn-bg-color: transparent; 61 | 62 | /* common */ 63 | --toolbar-bg-color: #efefef; 64 | --toolbar-border-color: #ccc; 65 | } 66 | -------------------------------------------------------------------------------- /src/components/Icons/Branch.jsx: -------------------------------------------------------------------------------- 1 | const Branch = ({ className, fillColor = '#ffffff' }) => { 2 | return ( 3 | 12 | 18 | 19 | ); 20 | }; 21 | 22 | export default Branch; 23 | -------------------------------------------------------------------------------- /src/vendor/map2tree/lib/index.js: -------------------------------------------------------------------------------- 1 | import { isArray, isPlainObject, mapValues } from 'lodash-es'; 2 | function visit(parent, visitFn, childrenFn) { 3 | if (!parent) 4 | return; 5 | visitFn(parent); 6 | const children = childrenFn(parent); 7 | if (children) { 8 | const count = children.length; 9 | for (let i = 0; i < count; i++) { 10 | visit(children[i], visitFn, childrenFn); 11 | } 12 | } 13 | } 14 | function getNode(tree, key) { 15 | let node = null; 16 | visit(tree, (d) => { 17 | if (d.name === key) { 18 | node = d; 19 | } 20 | }, (d) => d.children); 21 | return node; 22 | } 23 | export function map2tree(root, options = {}, tree = { name: options.key || 'state', children: [] }) { 24 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type 25 | if (!isPlainObject(root) && root && !root.toJS) { 26 | return {}; 27 | } 28 | const { key: rootNodeKey = 'state', pushMethod = 'push' } = options; 29 | const currentNode = getNode(tree, rootNodeKey); 30 | if (currentNode === null) { 31 | return {}; 32 | } 33 | mapValues( 34 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type 35 | root && root.toJS 36 | ? // eslint-disable-next-line @typescript-eslint/no-empty-object-type 37 | root.toJS() 38 | : // eslint-disable-next-line @typescript-eslint/no-empty-object-type 39 | root, 40 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type 41 | (maybeImmutable, key) => { 42 | const value = maybeImmutable && maybeImmutable.toJS 43 | ? maybeImmutable.toJS() 44 | : maybeImmutable; 45 | const newNode = { name: key }; 46 | if (isArray(value)) { 47 | newNode.children = []; 48 | for (let i = 0; i < value.length; i++) { 49 | newNode.children[pushMethod]({ 50 | name: `${key}[${i}]`, 51 | [isPlainObject(value[i]) ? 'object' : 'value']: value[i], 52 | }); 53 | } 54 | } 55 | else if (isPlainObject(value)) { 56 | newNode.children = []; 57 | } 58 | else { 59 | newNode.value = value; 60 | } 61 | currentNode.children[pushMethod](newNode); 62 | map2tree(value, { key, pushMethod }, tree); 63 | }); 64 | return tree; 65 | } 66 | -------------------------------------------------------------------------------- /src/components/Dropdown/style.scss: -------------------------------------------------------------------------------- 1 | 2 | .dropdown { 3 | display: inline-block; 4 | background-color: var(--dropdown-bg-primary); 5 | border: 1px solid var(--dropdown-menu-border-color); 6 | position: relative; 7 | width: 100%; 8 | border-radius: 4px; 9 | cursor: pointer; 10 | user-select: none; 11 | 12 | &-btn { 13 | position: relative; 14 | padding: 5px; 15 | font-size: 14px; 16 | 17 | .dropdown-caret-bottom-icon { 18 | padding-left:10px; 19 | display: inline; 20 | align-content: flex-end; 21 | float: right; 22 | } 23 | } 24 | 25 | img.selected-icon { 26 | margin-right: 10px; 27 | width: 20px; 28 | height: 20px; 29 | } 30 | 31 | .selected-label { 32 | width: 100%; 33 | } 34 | 35 | ul.list-items { 36 | width: 100%; 37 | position: absolute; 38 | top: 38px; 39 | right: 0px; 40 | list-style: none; 41 | margin: 0px; 42 | padding: 0px; 43 | background: var(--dropdown-menu-item-bg); 44 | border: 1px solid var(--dropdown-menu-item-border-color); 45 | border-radius: 4px; 46 | display: none; 47 | z-index: 99; 48 | box-shadow: 0px 3px 6px rgba($color: #000000, $alpha: 0.6); 49 | max-height: 40vh; 50 | overflow-y: scroll; 51 | scrollbar-width: none; 52 | 53 | li { 54 | padding: 10px 15px; 55 | border-bottom: 1px solid var(--dropdown-menu-item-border-color); 56 | cursor: pointer; 57 | font-size: 15px; 58 | display: flex; 59 | justify-content: flex-start; 60 | align-items: center; 61 | background-color: var(--dropdown-bg-primary); 62 | 63 | .icon { 64 | width: 18px; 65 | height: 18px; 66 | margin-right: 10px; 67 | } 68 | .selected-icon { 69 | display: inline-block; 70 | width: 16px; 71 | margin-left: 10px; 72 | } 73 | } 74 | 75 | li:hover, li.active { 76 | background: var(--dropdown-menu-item-bg); 77 | } 78 | } 79 | 80 | 81 | &.expanded { 82 | .select-icon { 83 | transform: rotate(180deg); 84 | } 85 | 86 | ul.list-items { 87 | display: block; 88 | } 89 | ul.list-items:focus { 90 | outline: none; 91 | } 92 | } 93 | } 94 | 95 | .dropdown-btn{ 96 | overflow: hidden; 97 | } 98 | 99 | -------------------------------------------------------------------------------- /src/testserver/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test Content Type 7 | 38 | 39 | 40 |
41 | 48 | 49 | 71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /src/css/color-themes/dark-pro.css: -------------------------------------------------------------------------------- 1 | /* 2 | * !IMPORTANT! 3 | * dart-pro.scss contains all the color variables and considered as base color-scheme. 4 | * To create a new color schemes simply create a new YOUR_SCHEME.scss file and overwrite the color variables that dark-pro.scss offers. 5 | */ 6 | 7 | :root{ 8 | --bg-color:#11101E; 9 | --primary-text-color: #fff; 10 | --primary-font-family: sans-serif; 11 | --breadcumb-border-color: rgb(45, 42, 57); 12 | --breadcumb-bg-color: #1A1B28; 13 | --copy-path-btn-img-filter: none; 14 | --base-font-size: 15px; 15 | --breadcumb-item-hover-color: #4fdee5; 16 | --breadcumb-arrow-color: #01ff70; 17 | --menu-item-border-color: #292938; 18 | --menu-item-bg-color: #1A1B28; 19 | --menu-item-active-border-color: rgb(105 75 226); 20 | --menu-item-active-bg-color: #433387; 21 | --menu-item-img-filter: none; 22 | --copyable-property-bg-color: rgba(33, 47, 61, 0.8); 23 | --copyable-property-border-color: #5d6d7e; 24 | --btn-bg-color: #33354a; 25 | --btn-border-color: #111; 26 | --btn-default-bg-color: #1A1B28; 27 | --btn-primary-bg-color: #433387; 28 | --btn-primary-text-color: white; 29 | 30 | /* JSON Tree Color Scheme*/ 31 | --property-color: #4fdee5; 32 | --json-literal-numeric: #f5b041; 33 | --json-literal-string: #01ff70; 34 | --json-literal-url: #0184ff; 35 | --json-literal-and-boolean: #af7ac5; 36 | --copier-bg-color: #2ecc71; 37 | --copier-text-color: #2ecc71; 38 | 39 | /* chart view*/ 40 | --json-chart-circle-bg: #01ff70; 41 | 42 | /* editor */ 43 | --json-input-bg-color: #070707; 44 | --json-input-border-color: #363537; 45 | --error-msg-bg-color: #1f0604; 46 | --editor-footer-bg-color: #111; 47 | --editor-footer-text-color: #868585; 48 | --editor-footer-border-color: #222; 49 | 50 | /* dropdown */ 51 | --dropdown-bg-primary: #1A1B28; 52 | --dropdown-menu-item-bg: #43455d; 53 | --dropdown-menu-item-border-color: #222; 54 | --dropdown-menu-border-color: #111; 55 | 56 | /* json path search bar */ 57 | --searchbar-bg-color:rgba(29, 27, 61, 1); 58 | --searchbar-border-color:#111111; 59 | --searchbar-box-shadow-color:rgba(6, 6, 6, 0.7); 60 | --searchbar-text-input-bg-color: #000000; 61 | --searchbar-text-input-border-color: #222; 62 | --searchbar-text-input-text-color: #01ff70; 63 | --searchbar-text-input-focus-outline-color: #4a4173; 64 | --searchbar-info-text-color: #d8d6d6; 65 | --search-clear-btn-text-color: #4a4173; 66 | --search-clear-btn-bg-color: #000000; 67 | 68 | /* common */ 69 | --toolbar-bg-color: #000; 70 | --toolbar-border-color: #222; 71 | } 72 | -------------------------------------------------------------------------------- /src/vendor/d3-state-visualizer/lib/charts/tree/utils.js: -------------------------------------------------------------------------------- 1 | import { is, join, pipe, replace } from 'ramda'; 2 | import sortAndSerialize from './sortAndSerialize.js'; 3 | export function collapseChildren(node) { 4 | if (node.children) { 5 | node._children = node.children; 6 | node._children.forEach(collapseChildren); 7 | node.children = undefined; 8 | } 9 | } 10 | export function expandChildren(node) { 11 | if (node._children) { 12 | node.children = node._children; 13 | node.children.forEach(expandChildren); 14 | node._children = undefined; 15 | } 16 | } 17 | export function toggleChildren(node) { 18 | if (node.children) { 19 | node._children = node.children; 20 | node.children = undefined; 21 | } 22 | else if (node._children) { 23 | node.children = node._children; 24 | node._children = undefined; 25 | } 26 | return node; 27 | } 28 | export function visit(parent, visitFn, childrenFn) { 29 | if (!parent) { 30 | return; 31 | } 32 | visitFn(parent); 33 | const children = childrenFn(parent); 34 | if (children) { 35 | const count = children.length; 36 | for (let i = 0; i < count; i++) { 37 | visit(children[i], visitFn, childrenFn); 38 | } 39 | } 40 | } 41 | export function getNodeGroupByDepthCount(rootNode) { 42 | const nodeGroupByDepthCount = [1]; 43 | const traverseFrom = function traverseFrom(node, depth = 0) { 44 | if (!node.children || node.children.length === 0) { 45 | return 0; 46 | } 47 | if (nodeGroupByDepthCount.length <= depth + 1) { 48 | nodeGroupByDepthCount.push(0); 49 | } 50 | nodeGroupByDepthCount[depth + 1] += node.children.length; 51 | node.children.forEach((childNode) => { 52 | traverseFrom(childNode, depth + 1); 53 | }); 54 | }; 55 | traverseFrom(rootNode); 56 | return nodeGroupByDepthCount; 57 | } 58 | export function getTooltipString(node, { indentationSize = 4 }) { 59 | if (!is(Object, node)) 60 | return ''; 61 | const spacer = join('  '); 62 | const cr2br = replace(/\n/g, '
'); 63 | const spaces2nbsp = replace(/\s{2}/g, spacer(new Array(indentationSize))); 64 | const json2html = pipe(sortAndSerialize, cr2br, spaces2nbsp); 65 | const children = node.children || node._children; 66 | if (typeof node.value !== 'undefined') 67 | return json2html(node.value); 68 | if (typeof node.object !== 'undefined') 69 | return json2html(node.object); 70 | if (children && children.length) 71 | return `childrenCount: ${children.length}`; 72 | return 'empty'; 73 | } 74 | -------------------------------------------------------------------------------- /src/components/Editor/Toolbar/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Select from '../../Select'; 3 | import { FiUploadCloud, FiFileText, FiPlay, FiSave } from 'react-icons/fi'; 4 | 5 | import './style.css'; 6 | 7 | const Toolbar = ({ 8 | onImportBtnClick, 9 | onParseJson, 10 | onContentTypeChange, 11 | onSaveBtnClick, 12 | contentType, 13 | }) => { 14 | const contentTypMenuItems = { 15 | size: 'small', 16 | label: contentType, 17 | items: [ 18 | { 19 | label: 'JSON', 20 | iconUrl: null, 21 | selected: true, 22 | }, 23 | { 24 | label: 'YAML', 25 | iconUrl: null, 26 | }, 27 | { 28 | label: 'XML', 29 | iconUrl: null, 30 | }, 31 | ], 32 | }; 33 | return ( 34 |
35 |
36 |
39 | 64 | 65 |
66 |
67 | 68 |
69 |
70 | 71 | 72 |
73 |
74 | 75 | 76 |
77 |
78 | 84 | 85 |
86 |
87 | 88 | 89 |
90 |
91 | 92 | 93 |
94 | 95 |
96 | 97 | 98 |
99 |
100 | 101 | 102 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | 4 | import App from './App.jsx'; 5 | import { getURL, parseJson } from './utils/common'; 6 | import { DARK_THEMES, DEFAULT_OPTIONS } from './constants/options.js'; 7 | 8 | const codeMirrorStyleSheetId = 'codemirror-css'; 9 | const COLOR_THEME_LINK_TAG_ID = 'color-theme-css'; 10 | const CUSTOM_CSS_STYLE_TAG_ID = 'custom-css'; 11 | 12 | const isJSONViewerProExtensionPage = () => { 13 | return ( 14 | window.location.href.includes('chrome-extension://') && 15 | window.location.search.includes('options') 16 | ); 17 | }; 18 | 19 | const injectStyleSheet = (stylesheetUrl, idSelector) => { 20 | const linkTag = document.createElement('link'); 21 | linkTag.setAttribute('href', stylesheetUrl); 22 | linkTag.rel = 'stylesheet'; 23 | linkTag.type = 'text/css'; 24 | linkTag.id = idSelector; 25 | document.head.appendChild(linkTag); 26 | return linkTag; 27 | }; 28 | 29 | const applyOptionsIfChromeExtensionPage = (options) => { 30 | if (!isJSONViewerProExtensionPage()) { 31 | return; 32 | } 33 | const themes = { 34 | default: 'dark-pro.css', 35 | mdn: 'mdn-light.css', 36 | }; 37 | let colorThemeStyleSheetLinkElement = document.getElementById( 38 | COLOR_THEME_LINK_TAG_ID, 39 | ); 40 | const colorThemeStylesheetUrl = themes[options.theme] || themes['default']; 41 | const cssURL = getURL('css/color-themes/' + colorThemeStylesheetUrl); 42 | 43 | if (!colorThemeStyleSheetLinkElement) { 44 | injectStyleSheet(cssURL, COLOR_THEME_LINK_TAG_ID); 45 | } else if ( 46 | colorThemeStyleSheetLinkElement && 47 | colorThemeStyleSheetLinkElement.href.indexOf(colorThemeStylesheetUrl) < 48 | 0 49 | ) { 50 | colorThemeStyleSheetLinkElement.setAttribute('href', cssURL); 51 | } 52 | 53 | let customStyleElement = document.getElementById(CUSTOM_CSS_STYLE_TAG_ID); 54 | if (!customStyleElement) { 55 | customStyleElement = document.createElement('style'); 56 | customStyleElement.id = CUSTOM_CSS_STYLE_TAG_ID; 57 | document.head.appendChild(customStyleElement); 58 | } 59 | 60 | customStyleElement.textContent = options.css || ''; 61 | }; 62 | 63 | const getOptions = async () => { 64 | try { 65 | const metaDataElement = document.querySelector( 66 | 'meta[name="extension-options"]', 67 | ); 68 | const options = 69 | metaDataElement && metaDataElement.getAttribute('content'); 70 | 71 | if (options) { 72 | return JSON.parse(options); 73 | } 74 | 75 | if (isJSONViewerProExtensionPage()) { 76 | return JSON.parse( 77 | decodeURIComponent( 78 | new URLSearchParams(window.location.search).get('options'), 79 | ), 80 | ); 81 | } 82 | 83 | const chrome = window.chrome; 84 | if (chrome && chrome.storage && chrome.storage.local) { 85 | return await chrome.storage.local.get([ 86 | 'rb-awesome-json-viewer-options', 87 | ]); 88 | } 89 | } catch (error) { 90 | console.error('Error while fetching options', error); 91 | } 92 | return DEFAULT_OPTIONS; 93 | }; 94 | 95 | const injectCodeMirrorStylesheet = () => { 96 | if (!!document.querySelector('#' + codeMirrorStyleSheetId)) { 97 | document.querySelector('#' + codeMirrorStyleSheetId).remove(); 98 | } 99 | 100 | const cssFilePath = getURL('css/codemirror.css'); 101 | injectStyleSheet(cssFilePath, codeMirrorStyleSheetId); 102 | }; 103 | 104 | /* 105 | ** Some webistes for example: api.github.com rejects Style tag creation if following CSP is set in the Server Response Headers 106 | ** Content-Security-Policy: default-src 'none' 107 | ** CodeMirror 6 Does not provide external CSS file. It only injects styles in the style tag dynamicaly as it was developed 108 | ** based on CSS in JS. 109 | **/ 110 | const detectCSPViolation = () => { 111 | document.addEventListener('securitypolicyviolation', function (e) { 112 | if (e.violatedDirective === 'style-src-elem') { 113 | injectCodeMirrorStylesheet(); 114 | } 115 | }); 116 | }; 117 | 118 | (async () => { 119 | detectCSPViolation(); 120 | try { 121 | let content = document.body?.innerText; 122 | content = content?.trim(); 123 | const jsonData = parseJson(content); 124 | window.json = jsonData; 125 | window.extensionOptions = await getOptions(); 126 | applyOptionsIfChromeExtensionPage(window.extensionOptions); 127 | 128 | const rootElement = document.createElement('div'); 129 | rootElement.setAttribute('id', 'rbrahul-awesome-json'); 130 | document.body.innerHTML = ''; 131 | document.body.appendChild(rootElement); 132 | const root = createRoot(rootElement); 133 | root.render( 134 | , 140 | ); 141 | } catch (e) { 142 | console.error('Something went wrong at Awesome JSON Viewer Pro', e); 143 | } 144 | })(); 145 | -------------------------------------------------------------------------------- /src/vendor/map2tree/test/map2tree.spec.ts: -------------------------------------------------------------------------------- 1 | import { map2tree, Node } from '../src/index.js'; 2 | import * as immutable from 'immutable'; 3 | 4 | test('# rootNodeKey', () => { 5 | const map = {}; 6 | const options = { key: 'foo' }; 7 | 8 | expect((map2tree(map, options) as Node).name).toBe('foo'); 9 | }); 10 | 11 | describe('# shallow map', () => { 12 | test('## null', () => { 13 | const map = { 14 | a: null, 15 | }; 16 | 17 | const expected = { 18 | name: 'state', 19 | children: [{ name: 'a', value: null }], 20 | }; 21 | 22 | expect(map2tree(map)).toEqual(expected); 23 | expect(map2tree(immutable.fromJS(map))).toEqual(expected); 24 | }); 25 | 26 | test('## value', () => { 27 | const map = { 28 | a: 'foo', 29 | b: 'bar', 30 | }; 31 | 32 | const expected = { 33 | name: 'state', 34 | children: [ 35 | { name: 'a', value: 'foo' }, 36 | { name: 'b', value: 'bar' }, 37 | ], 38 | }; 39 | 40 | expect(map2tree(map)).toEqual(expected); 41 | expect(map2tree(immutable.fromJS(map))).toEqual(expected); 42 | }); 43 | 44 | test('## object', () => { 45 | const map = { 46 | a: { aa: 'foo' }, 47 | }; 48 | 49 | const expected = { 50 | name: 'state', 51 | children: [{ name: 'a', children: [{ name: 'aa', value: 'foo' }] }], 52 | }; 53 | 54 | expect(map2tree(map)).toEqual(expected); 55 | expect(map2tree(immutable.fromJS(map))).toEqual(expected); 56 | }); 57 | 58 | test('## immutable Map', () => { 59 | const map = { 60 | a: immutable.fromJS({ aa: 'foo', ab: 'bar' }), 61 | }; 62 | 63 | const expected = { 64 | name: 'state', 65 | children: [ 66 | { 67 | name: 'a', 68 | children: [ 69 | { name: 'aa', value: 'foo' }, 70 | { name: 'ab', value: 'bar' }, 71 | ], 72 | }, 73 | ], 74 | }; 75 | 76 | expect(map2tree(map)).toEqual(expected); 77 | }); 78 | }); 79 | 80 | describe('# deep map', () => { 81 | test('## null', () => { 82 | const map = { 83 | a: { aa: null }, 84 | }; 85 | 86 | const expected = { 87 | name: 'state', 88 | children: [ 89 | { 90 | name: 'a', 91 | children: [ 92 | { 93 | name: 'aa', 94 | value: null, 95 | }, 96 | ], 97 | }, 98 | ], 99 | }; 100 | 101 | expect(map2tree(map)).toEqual(expected); 102 | expect(map2tree(immutable.fromJS(map))).toEqual(expected); 103 | }); 104 | 105 | test('## object', () => { 106 | const map = { 107 | a: { aa: { aaa: 'foo' } }, 108 | }; 109 | 110 | const expected = { 111 | name: 'state', 112 | children: [ 113 | { 114 | name: 'a', 115 | children: [ 116 | { 117 | name: 'aa', 118 | children: [{ name: 'aaa', value: 'foo' }], 119 | }, 120 | ], 121 | }, 122 | ], 123 | }; 124 | 125 | expect(map2tree(map)).toEqual(expected); 126 | expect(map2tree(immutable.fromJS(map))).toEqual(expected); 127 | }); 128 | }); 129 | 130 | describe('# array map', () => { 131 | const map = { 132 | a: [1, 2], 133 | }; 134 | 135 | test('## push', () => { 136 | const expected = { 137 | name: 'state', 138 | children: [ 139 | { 140 | name: 'a', 141 | children: [ 142 | { name: 'a[0]', value: 1 }, 143 | { name: 'a[1]', value: 2 }, 144 | ], 145 | }, 146 | ], 147 | }; 148 | 149 | expect(map2tree(map)).toEqual(expected); 150 | expect(map2tree(immutable.fromJS(map))).toEqual(expected); 151 | }); 152 | 153 | test('## unshift', () => { 154 | const options = { pushMethod: 'unshift' as const }; 155 | const expected = { 156 | name: 'state', 157 | children: [ 158 | { 159 | name: 'a', 160 | children: [ 161 | { name: 'a[1]', value: 2 }, 162 | { name: 'a[0]', value: 1 }, 163 | ], 164 | }, 165 | ], 166 | }; 167 | 168 | expect(map2tree(map, options)).toEqual(expected); 169 | expect(map2tree(immutable.fromJS(map), options)).toEqual(expected); 170 | }); 171 | 172 | test('## null', () => { 173 | const map = { 174 | a: [null], 175 | }; 176 | 177 | const expected = { 178 | name: 'state', 179 | children: [ 180 | { 181 | name: 'a', 182 | children: [{ name: 'a[0]', value: null }], 183 | }, 184 | ], 185 | }; 186 | 187 | expect(map2tree(map)).toEqual(expected); 188 | expect(map2tree(immutable.fromJS(map))).toEqual(expected); 189 | }); 190 | }); 191 | 192 | describe('# collection map', () => { 193 | test('## value', () => { 194 | const map = { 195 | a: [{ aa: 1 }, { aa: 2 }], 196 | }; 197 | 198 | const expected = { 199 | name: 'state', 200 | children: [ 201 | { 202 | name: 'a', 203 | children: [ 204 | { name: 'a[0]', object: { aa: 1 } }, 205 | { name: 'a[1]', object: { aa: 2 } }, 206 | ], 207 | }, 208 | ], 209 | }; 210 | 211 | expect(map2tree(map)).toEqual(expected); 212 | expect(map2tree(immutable.fromJS(map))).toEqual(expected); 213 | }); 214 | 215 | test('## object', () => { 216 | const map = { 217 | a: [{ aa: { aaa: 'foo' } }], 218 | }; 219 | 220 | const expected = { 221 | name: 'state', 222 | children: [ 223 | { 224 | name: 'a', 225 | children: [{ name: 'a[0]', object: { aa: { aaa: 'foo' } } }], 226 | }, 227 | ], 228 | }; 229 | 230 | expect(map2tree(map)).toEqual(expected); 231 | expect(map2tree(immutable.fromJS(map))).toEqual(expected); 232 | }); 233 | }); 234 | -------------------------------------------------------------------------------- /src/Menus.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { FiTerminal, FiX } from 'react-icons/fi'; 3 | import Tree from './components/Icons/Tree'; 4 | import Branch from './components/Icons/Branch'; 5 | import Brackets from './components/Icons/Brackets'; 6 | import Gear from './components/Icons/Gear'; 7 | import { iconFillColor } from './utils/common'; 8 | 9 | class Menus extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | selectedPan: props.selectedTab, 14 | }; 15 | } 16 | 17 | setActive(tab) { 18 | this.setState({ selectedPan: tab }); 19 | this.props.changeTabSelection(tab); 20 | } 21 | 22 | componentWillMount() { 23 | this.prepareComponentState(this.props); 24 | } 25 | 26 | componentWillReceiveProps(nextProps) { 27 | this.prepareComponentState(nextProps); 28 | } 29 | 30 | prepareComponentState(props) { 31 | this.setState({ 32 | selectedPan: props.selectedTab, 33 | }); 34 | } 35 | 36 | showSearchBar() { 37 | this.props.showSearchBar(); 38 | } 39 | 40 | render() { 41 | return ( 42 |
43 | 140 |
141 | ); 142 | } 143 | } 144 | 145 | export default Menus; 146 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Menus from './Menus.jsx'; 3 | import Tooltip from './vendor/tooltip'; 4 | import TreeView from './TreeView.jsx'; 5 | import ChartViews from './ChartView.jsx'; 6 | import Editor from './components/Editor'; 7 | import downloadFile from './utils/dowloadFile'; 8 | import { currentDateTime } from './utils/datetime'; 9 | 10 | import './vendor/tooltip/style.css'; 11 | import './css/style.scss'; 12 | 13 | class App extends Component { 14 | constructor(props) { 15 | super(props); 16 | window.json = props.json; 17 | this.state = { 18 | selectedTab: 'tree', 19 | json: props.json, 20 | selectedJSON: props.json, 21 | isSearchBarVisible: false, 22 | }; 23 | this.showLogInConsole(); 24 | this.locationHashChanged = this.locationHashChanged.bind(this); 25 | this.showSearchBar = this.showSearchBar.bind(this); 26 | this.hideSearchBar = this.hideSearchBar.bind(this); 27 | this.changeJSON = this.changeJSON.bind(this); 28 | this.restoreOriginalJSON = this.restoreOriginalJSON.bind(this); 29 | this.tooltip = React.createRef(); 30 | this.intervalIdRef = React.createRef(); 31 | this.originalJSONRef = React.createRef(props.json); 32 | } 33 | 34 | changeTabSelection(tab) { 35 | this.setState({ selectedTab: tab }); 36 | } 37 | 38 | showSearchBar() { 39 | this.setState({ isSearchBarVisible: true }); 40 | } 41 | 42 | hideSearchBar() { 43 | this.setState({ isSearchBarVisible: false }); 44 | } 45 | 46 | changeJSON(json, openTreeView = true) { 47 | this.setState( 48 | { 49 | json, 50 | selectedJSON: json, 51 | }, 52 | () => { 53 | window.json = json; 54 | if (openTreeView) { 55 | this.changeTabSelection('tree'); 56 | } 57 | }, 58 | ); 59 | } 60 | 61 | mutateOriginalJSONAndRender(json) { 62 | this.originalJSONRef.current = json; 63 | window.json = json; 64 | this.changeJSON(this.originalJSONRef.current); 65 | } 66 | 67 | restoreOriginalJSON() { 68 | this.changeJSON(this.originalJSONRef.current, false); 69 | } 70 | 71 | locationHashChanged() { 72 | if (window.location.href.includes('downloadJSON=true')) { 73 | this.downloadAsJSON(); 74 | setTimeout(() => { 75 | window.location.hash = ''; 76 | }, 2000); 77 | } 78 | } 79 | 80 | downloadAsJSON() { 81 | downloadFile( 82 | JSON.stringify(window.json, null, 2), 83 | 'text/json', 84 | `data-${currentDateTime()}.json`, 85 | ); 86 | } 87 | 88 | componentDidMount() { 89 | if (this.props.json !== this.originalJSONRef.current) { 90 | this.originalJSONRef.current = this.props.json; 91 | } 92 | 93 | if (!this.tooltip.current) { 94 | this.tooltip.current = new Tooltip(); 95 | } else { 96 | this.tooltip.current.initialiseTooltip(); 97 | } 98 | 99 | this.intervalIdRef.current = window.setInterval(() => { 100 | this.tooltip.current?.initialiseTooltip(); 101 | }, 3_000); 102 | 103 | window.addEventListener('hashchange', this.locationHashChanged, false); 104 | } 105 | 106 | componentWillUnmount() { 107 | window.removeEventListener( 108 | 'hashchange', 109 | this.locationHashChanged, 110 | false, 111 | ); 112 | 113 | if (this.tooltip.current) { 114 | this.tooltip.current.destroy(); 115 | } 116 | if (this.intervalIdRef.current) { 117 | clearInterval(this.intervalIdRef.current); 118 | } 119 | } 120 | 121 | showLogInConsole() { 122 | console.log( 123 | '%cTo access the JSON data just write: %cjson', 124 | 'font-size:14px; color: #4fdee5;background:black;padding:8px;padding-right:0px', 125 | 'font-size:14px;color:orange;font-weight:bold;background:black;padding:8px;padding-left:0px', 126 | ); 127 | } 128 | 129 | render() { 130 | return ( 131 |
132 | 140 |
141 | {this.state.selectedTab === 'tree' && ( 142 | 147 | )} 148 | {this.state.selectedTab === 'chart' && ( 149 | 154 | )} 155 | {this.state.selectedTab === 'jsonInput' && ( 156 | 162 | )} 163 |
164 |
165 | ); 166 | } 167 | } 168 | 169 | export default App; 170 | -------------------------------------------------------------------------------- /src/utils/json-viewer/json-viewer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if arg is either an array with at least 1 element, or a dict with at least 1 key 3 | * @return boolean 4 | */ 5 | function isCollapsable(arg) { 6 | return arg instanceof Object && Object.keys(arg).length > 0; 7 | } 8 | 9 | /** 10 | * Check if a string represents a valid url 11 | * @return boolean 12 | */ 13 | function isUrl(string) { 14 | var regexp = 15 | /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; 16 | return regexp.test(string); 17 | } 18 | 19 | const getJsonToggleClassNames = (isCollapsed) => { 20 | let classNames = 'json-toggle'; 21 | return isCollapsed ? classNames + ' collapsed' : classNames; 22 | }; 23 | 24 | const getToggleStyle = (isCollapsed) => { 25 | return isCollapsed ? 'style="display:none;"' : ''; 26 | }; 27 | 28 | const createPlaceHolderNode = (isCollapsed, count) => { 29 | return isCollapsed && count > 0 30 | ? `${count} item${ 31 | count > 1 ? 's' : '' 32 | }` 33 | : ''; 34 | }; 35 | 36 | /** 37 | * Transform a json object into html representation 38 | * @return string 39 | */ 40 | function json2html(json, options, nestedLevel = []) { 41 | var html = ''; 42 | const isCollapsed = options.collapsed && nestedLevel.length > 0; 43 | if (typeof json === 'string') { 44 | // Escape tags 45 | json = json 46 | .replace(/&/g, '&') 47 | .replace(//g, '>'); 49 | if (isUrl(json)) 50 | html += 51 | '"' + 54 | json + 55 | '"'; 56 | else html += '"' + json + '"'; 57 | } else if (typeof json === 'number') { 58 | html += '' + json + ''; 59 | } else if (typeof json === 'boolean') { 60 | html += '' + json + ''; 61 | } else if (json === null) { 62 | html += 'null'; 63 | } else if (json instanceof Array) { 64 | if (json.length > 0) { 65 | html += `[
    `; 66 | for (var i = 0; i < json.length; ++i) { 67 | html += '
  1. '; 68 | // Add toggle button if item is collapsable 69 | if (isCollapsable(json[i])) { 70 | html += ``; 73 | } 74 | nestedLevel.push(1); 75 | html += json2html(json[i], options, nestedLevel); 76 | // Add comma if item is not last 77 | if (i < json.length - 1) { 78 | html += ','; 79 | } 80 | html += '
  2. '; 81 | } 82 | html += `
${createPlaceHolderNode(isCollapsed, json.length)}]`; 83 | } else { 84 | html += '[]'; 85 | } 86 | } else if (typeof json === 'object') { 87 | var key_count = Object.keys(json).length; 88 | if (key_count > 0) { 89 | html += `{
    `; 90 | for (var key in json) { 91 | if (json.hasOwnProperty(key)) { 92 | html += '
  • '; 93 | var keyRepr = options.withQuotes 94 | ? '"' + key + '"' 95 | : key; 96 | // Add toggle button if item is collapsable 97 | if (isCollapsable(json[key])) { 98 | html += 99 | `` + 102 | keyRepr + 103 | ''; 104 | } else { 105 | html += keyRepr; 106 | } 107 | nestedLevel.push(1); 108 | html += ': ' + json2html(json[key], options, nestedLevel); 109 | // Add comma if item is not last 110 | if (--key_count > 0) html += ','; 111 | html += '
  • '; 112 | } 113 | } 114 | html += `
${createPlaceHolderNode( 115 | isCollapsed, 116 | Object.keys(json).length, 117 | )}}`; 118 | } else { 119 | html += '{}'; 120 | } 121 | } 122 | return html; 123 | } 124 | 125 | /** 126 | * jQuery plugin method 127 | * @param json: a javascript object 128 | * @param options: an optional options hash 129 | */ 130 | export const initPlugin = function (node, jQuery, json, option) { 131 | (function ($, node, json, options) { 132 | options = options || { collapsed: false }; 133 | // jQuery chaining 134 | return $(node).each(function () { 135 | // Transform to HTML 136 | var html = json2html(json, options, []); 137 | if (isCollapsable(json)) 138 | html = `` + html; 139 | 140 | // Insert HTML in target DOM element 141 | $(this).html(html); 142 | 143 | // Bind click on toggle buttons 144 | $(this).off('click'); 145 | $(this).on('click', 'span.property', function (e) { 146 | $('li').removeClass('copyable'); 147 | $(this).parents('li').first().addClass('copyable'); 148 | }); 149 | 150 | // Simulate click on toggle button when placeholder is clicked 151 | $(this).on('click', 'a.json-placeholder', function () { 152 | $(this).siblings('a.json-toggle').click(); 153 | return false; 154 | }); 155 | }); 156 | })(jQuery, node, json, option); 157 | }; 158 | -------------------------------------------------------------------------------- /src/vendor/tooltip/index.js: -------------------------------------------------------------------------------- 1 | class Tooltip { 2 | direction = 'bottom'; 3 | distance = 10; 4 | container = document; 5 | tooltipMessageId = 'tooltip-msg'; 6 | styleTagId = 'tooltip-style'; 7 | _allowedDirection = ['top', 'right', 'bottom', 'left']; 8 | size = 'medium'; // small | medium | large 9 | 10 | constructor(options = {}) { 11 | if (options.container) { 12 | this.container = options.container; 13 | } 14 | if (options.direction) { 15 | this.direction = options.direction; 16 | } 17 | 18 | if (options.distance) { 19 | this.distance = options.distance; 20 | } 21 | 22 | if (options.size) { 23 | this.size = options.size; 24 | } 25 | 26 | this.initialiseTooltip(); 27 | } 28 | 29 | getMatchedClasses(classList, expectedClasses = []) { 30 | return expectedClasses.filter((expectedClass) => 31 | classList.contains(expectedClass), 32 | ); 33 | } 34 | 35 | addTooltip() { 36 | const tooltip = document.createElement('div'); 37 | const toolTipId = this.tooltipMessageId; 38 | tooltip.id = toolTipId; 39 | tooltip.classList.add('hidden', this.size || 'medium'); 40 | if (!document.body.querySelector(`#${toolTipId}`)) { 41 | document.body.append(tooltip); 42 | } 43 | } 44 | 45 | getOffset(target, direction = this.direction) { 46 | const rect = target.getBoundingClientRect(); 47 | let offsetTop = rect.top + window.scrollY + rect.height / 2; 48 | let offsetLeft = 49 | rect.left + window.scrollX + rect.width + this.distance; 50 | const halfWidth = target.width / 2; 51 | switch (direction) { 52 | case 'top': 53 | offsetTop = rect.top + window.scrollY - this.distance; 54 | offsetLeft = rect.left + window.scrollX + halfWidth; 55 | break; 56 | case 'right': 57 | offsetTop = rect.top + window.scrollY + rect.height / 2; 58 | offsetLeft = 59 | rect.left + window.scrollX + rect.width + this.distance; 60 | break; 61 | case 'left': 62 | offsetTop = rect.top + window.scrollY + rect.height / 2; 63 | offsetLeft = rect.left + window.scrollX - this.distance; 64 | break; 65 | case 'bottom': 66 | offsetTop = 67 | rect.top + window.scrollY + rect.height + this.distance; 68 | offsetLeft = rect.left + window.scrollX + rect.width / 2; 69 | break; 70 | } 71 | return { 72 | top: offsetTop, 73 | left: offsetLeft, 74 | }; 75 | } 76 | 77 | showToolTip(event) { 78 | this.hideTooltip(); 79 | let direction = this.direction; 80 | const dataset = event.currentTarget.dataset; 81 | 82 | if ('direction' in dataset) { 83 | if (!this._allowedDirection.includes(dataset.direction)) { 84 | // console.warn(`Invalid data-direction attribute was set. Using the default direction:${this.direction}`) 85 | } else { 86 | direction = dataset.direction; 87 | } 88 | } 89 | 90 | const { top: offsetTop, left: offsetLeft } = this.getOffset( 91 | event.target, 92 | direction, 93 | ); 94 | 95 | const tooltipNode = document.querySelector(`#${this.tooltipMessageId}`); 96 | 97 | if ('tooltip' in dataset) { 98 | const tooltipMsg = dataset.tooltip; 99 | if (!tooltipNode) return; 100 | 101 | const matchedClasses = this.getMatchedClasses( 102 | tooltipNode.classList, 103 | this._allowedDirection.map((className) => `dir-${className}`), 104 | ); 105 | 106 | if (!!matchedClasses.length) { 107 | matchedClasses.forEach((className) => 108 | tooltipNode.classList.remove(className), 109 | ); 110 | } 111 | 112 | tooltipNode.classList.add('dir-' + direction); 113 | 114 | tooltipNode.style = `top:${offsetTop}px;left:${offsetLeft}px;`; 115 | tooltipNode.textContent = tooltipMsg; 116 | 117 | if (tooltipNode.classList.contains('hidden')) { 118 | tooltipNode.classList.remove('hidden'); 119 | } 120 | 121 | if (!tooltipNode.classList.contains('visible')) { 122 | tooltipNode.classList.add('visible'); 123 | } 124 | } 125 | if ('tooltipSize' in dataset) { 126 | if ( 127 | tooltipNode?.classList && 128 | !tooltipNode.classList.contains(dataset.tooltipSize) 129 | ) { 130 | tooltipNode.classList.add(dataset.tooltipSize); 131 | } 132 | } else { 133 | if ( 134 | tooltipNode?.classList && 135 | tooltipNode.classList.contains(dataset.tooltipSize) 136 | ) { 137 | tooltipNode.classList.remove(dataset.tooltipSize); 138 | } 139 | } 140 | } 141 | 142 | hideTooltip() { 143 | const tooltipNode = document.querySelector(`#${this.tooltipMessageId}`); 144 | if (!tooltipNode) return; 145 | 146 | if (tooltipNode.classList.contains('visible')) { 147 | tooltipNode.classList.remove('visible'); 148 | } 149 | 150 | if (!tooltipNode.classList.contains('hidden')) { 151 | tooltipNode.classList.add('hidden'); 152 | } 153 | } 154 | 155 | initialiseTooltip() { 156 | this.addTooltip(); 157 | 158 | this.container.querySelectorAll('[data-tooltip]').forEach((element) => { 159 | element.removeEventListener( 160 | 'mouseenter', 161 | this.showToolTip.bind(this), 162 | ); 163 | element.addEventListener('mouseenter', this.showToolTip.bind(this)); 164 | 165 | element.removeEventListener( 166 | 'mouseleave', 167 | this.hideTooltip.bind(this), 168 | ); 169 | element.addEventListener('mouseleave', this.hideTooltip.bind(this)); 170 | }); 171 | } 172 | 173 | cleanUpEvent() { 174 | this.container.querySelectorAll('[data-tooltip]').forEach((element) => { 175 | element.removeEventListener( 176 | 'mouseenter', 177 | this.showToolTip.bind(this), 178 | ); 179 | element.removeEventListener( 180 | 'mouseleave', 181 | this.hideTooltip.bind(this), 182 | ); 183 | }); 184 | } 185 | 186 | destroy() { 187 | const styleTag = document.querySelector(`#${this.styleTagId}`); 188 | if (styleTag) { 189 | styleTag.remove(); 190 | } 191 | 192 | const tooltipMsgElement = document.querySelector( 193 | `#${this.tooltipMessageId}`, 194 | ); 195 | if (tooltipMsgElement) { 196 | tooltipMsgElement.remove(); 197 | } 198 | this.cleanUpEvent(); 199 | } 200 | } 201 | 202 | export default Tooltip; 203 | -------------------------------------------------------------------------------- /src/components/Dropdown/index.jsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useState, useEffect, useRef } from 'react'; 2 | import { FiCheck, FiChevronDown } from 'react-icons/fi'; 3 | import clsx from 'clsx'; 4 | import PropTypes from 'prop-types'; 5 | import './style.scss'; 6 | 7 | const findSelectedByLabel = (items, value) => { 8 | return items.findIndex(({ label }) => label === value); 9 | }; 10 | 11 | const findSelected = (items) => { 12 | return items.findIndex(({ selected }) => !!selected); 13 | }; 14 | 15 | const DropDown = ({ 16 | labelIcon, 17 | label, 18 | items, 19 | className = '', 20 | hasCaretIcon = true, 21 | isButtonVisible = true, 22 | open = false, 23 | onClose, 24 | }) => { 25 | const expandedRef = useRef(open); 26 | const listContainerRef = useRef(); 27 | const [isExpanded, setIsExpanded] = useState(open); 28 | const index = findSelected(items ?? []); 29 | const [selectedIndex, setSelectedIndex] = useState(index < 0 ? 0 : index); 30 | const onChangeHandler = useCallback( 31 | (cb, value) => (_) => { 32 | cb(value); 33 | setIsExpanded(!isExpanded); 34 | const selectedIndex = findSelectedByLabel(items, value); 35 | setSelectedIndex(selectedIndex); 36 | }, 37 | [isExpanded, selectedIndex, items], 38 | ); 39 | 40 | const onClickHandler = useCallback( 41 | (_) => { 42 | setIsExpanded(!isExpanded); 43 | }, 44 | [isExpanded], 45 | ); 46 | 47 | const bodyClickHandler = useCallback( 48 | (e) => { 49 | const target = e.target; 50 | if (!!target?.closest('.dropdown')) { 51 | return; 52 | } 53 | setIsExpanded(false); 54 | if (typeof onClose === 'function') { 55 | onClose(); 56 | } 57 | }, 58 | [isExpanded], 59 | ); 60 | 61 | const scrollThroughList = (element, stepHeight, increase, nextIndex) => { 62 | let totalScrolled = element.scrollTop; 63 | const scrollHeight = element.scrollHeight; 64 | const visibleHeight = stepHeight * 5; 65 | const currentPositionHeight = (nextIndex + 1) * stepHeight; 66 | 67 | if ( 68 | increase && 69 | currentPositionHeight < scrollHeight && 70 | currentPositionHeight > visibleHeight 71 | ) { 72 | totalScrolled += stepHeight; 73 | } else if (!increase && totalScrolled > 0) { 74 | totalScrolled -= stepHeight; 75 | } 76 | 77 | element.scroll(0, totalScrolled); 78 | }; 79 | 80 | const bodyKeyDownHandler = useCallback( 81 | (e) => { 82 | if (!isExpanded || items.length == 0) { 83 | return; 84 | } 85 | 86 | if ([38, 40].includes(e.which)) { 87 | setSelectedIndex((currentIndex) => { 88 | let nextValue = currentIndex; 89 | if (e.which === 38) { 90 | nextValue = currentIndex - 1; 91 | } else if (e.which === 40) { 92 | nextValue = currentIndex + 1; 93 | } 94 | if (nextValue < 0) { 95 | return 0; 96 | } else if (nextValue >= items.length) { 97 | return items.length - 1; 98 | } 99 | 100 | const listItemHeight = 101 | listContainerRef.current?.querySelector('li') 102 | .clientHeight - 1; 103 | scrollThroughList( 104 | listContainerRef.current, 105 | listItemHeight, 106 | currentIndex < nextValue, 107 | nextValue, 108 | ); 109 | return nextValue; 110 | }); 111 | } 112 | 113 | if ([9, 13].includes(e.which)) { 114 | const selectedItem = items[selectedIndex]; 115 | const onClickHandler = selectedItem?.onClick; 116 | setIsExpanded(!isExpanded); 117 | onClickHandler?.(selectedItem?.label); 118 | onClose(); 119 | } 120 | }, 121 | [items, selectedIndex, isExpanded], 122 | ); 123 | 124 | useEffect(() => { 125 | document.body.addEventListener('click', bodyClickHandler, false); 126 | document.body.addEventListener('keydown', bodyKeyDownHandler, false); 127 | return () => { 128 | document.body.removeEventListener('click', bodyClickHandler, false); 129 | document.body.removeEventListener( 130 | 'keydown', 131 | bodyKeyDownHandler, 132 | false, 133 | ); 134 | }; 135 | }, [bodyKeyDownHandler, bodyClickHandler]); 136 | 137 | useEffect(() => { 138 | if (expandedRef.current !== open && isExpanded !== open) { 139 | setIsExpanded(open); 140 | expandedRef.current = open; 141 | } 142 | }, [isExpanded, open]); 143 | 144 | return ( 145 |
149 | {isButtonVisible && ( 150 |
151 | {labelIcon && ( 152 | {labelIcon} 153 | )} 154 | {label && {label}} 155 | {hasCaretIcon && ( 156 | 157 | 158 | 159 | )} 160 |
161 | )} 162 |
    166 | {items.map(({ label, iconUrl, onClick, selected }, index) => ( 167 |
  • 172 | {iconUrl && ( 173 | 174 | )} 175 | {label} 176 | {selected && ( 177 | 178 | 179 | 180 | )} 181 |
  • 182 | ))} 183 |
184 |
185 | ); 186 | }; 187 | 188 | DropDown.propTypes = { 189 | size: PropTypes.string, 190 | className: PropTypes.string, 191 | labelIcon: PropTypes.node, 192 | hasCaretIcon: PropTypes.bool, 193 | open: PropTypes.bool, 194 | label: PropTypes.string, 195 | items: PropTypes.arrayOf( 196 | PropTypes.shape({ 197 | label: PropTypes.string, 198 | iconUrl: PropTypes.string, 199 | onClick: PropTypes.func, 200 | }), 201 | ), 202 | onChange: PropTypes.func, 203 | onClose: PropTypes.func, 204 | }; 205 | 206 | export default DropDown; 207 | -------------------------------------------------------------------------------- /src/scripts/contentScript.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BASE_STYLE_LINK_TAG_ID = 'main-css'; 4 | const COLOR_THEME_LINK_TAG_ID = 'color-theme-css'; 5 | const CUSTOM_CSS_STYLE_TAG_ID = 'custom-css'; 6 | const MAINJS_SCRIPT_TAG_ID = 'main-script'; 7 | const ALLOWED_CONTENT_TYPES = [ 8 | 'application/json', 9 | 'text/json', 10 | 'application/javascript', 11 | 'application/x-javascript', 12 | 'application/vnd.api+json', 13 | 'text/x-json', 14 | 'text/x-javascript', 15 | ]; 16 | 17 | function getCleanTextContent() { 18 | const clonedDom = document.documentElement.cloneNode(true); 19 | clonedDom.querySelectorAll('script, style').forEach((el) => el.remove()); 20 | let bodyInnerText = clonedDom.innerText; 21 | if (typeof bodyInnerText === 'string') { 22 | return bodyInnerText.trim(); 23 | } 24 | return bodyInnerText; 25 | } 26 | 27 | const isDocumentContentTypeJSON = () => 28 | ALLOWED_CONTENT_TYPES.includes(document.contentType); 29 | 30 | const isJsonViewerLoaded = () => 31 | !!document.getElementById('rbrahul-awesome-json') || 32 | window.JSON_VIEWER_PRO_INITIALISED; 33 | 34 | const addBodyTagIfMissing = () => { 35 | if (!document.querySelector('body')) { 36 | const body = document.createElement('body'); 37 | document.querySelector('html').appendChild(body); 38 | } 39 | }; 40 | 41 | const addHeadTagIfMissing = () => { 42 | if (!document.querySelector('head')) { 43 | const headNode = document.createElement('head'); 44 | document 45 | .querySelector('html') 46 | .insertBefore(headNode, document.querySelector('body')); 47 | } 48 | }; 49 | 50 | const injectStyleSheet = (stylesheetUrl, idSelector) => { 51 | const styleTag = document.createElement('link'); 52 | styleTag.setAttribute('href', stylesheetUrl); 53 | styleTag.rel = 'stylesheet'; 54 | styleTag.type = 'text/css'; 55 | styleTag.id = idSelector; 56 | document.head.appendChild(styleTag); 57 | return styleTag; 58 | }; 59 | 60 | const injectCssUrlAndStyleTag = () => { 61 | if (!!document.getElementById(BASE_STYLE_LINK_TAG_ID)) { 62 | return; 63 | } 64 | const baseStyleCssFilePath = chrome.runtime.getURL('/css/style.css'); 65 | injectStyleSheet(baseStyleCssFilePath, BASE_STYLE_LINK_TAG_ID); 66 | 67 | const colorThemeCssFilePath = chrome.runtime.getURL( 68 | '/css/color-themes/dark-pro.css', 69 | ); 70 | injectStyleSheet(colorThemeCssFilePath, COLOR_THEME_LINK_TAG_ID); 71 | 72 | const customStyleTag = document.createElement('style'); 73 | customStyleTag.id = CUSTOM_CSS_STYLE_TAG_ID; 74 | document.head.appendChild(customStyleTag); 75 | }; 76 | 77 | const injectScriptTag = () => { 78 | if (!!document.getElementById(MAINJS_SCRIPT_TAG_ID)) { 79 | return; 80 | } 81 | const scriptTag = document.createElement('script'); 82 | scriptTag.id = MAINJS_SCRIPT_TAG_ID; 83 | const jsFilePath = chrome.runtime.getURL('/js/main.js'); 84 | scriptTag.setAttribute('src', jsFilePath); 85 | document.querySelector('body').appendChild(scriptTag); 86 | }; 87 | 88 | // In Manifest V3 we can't use inline scripts so as a work around we pass options by exposing into meta content 89 | const injectOptionsAsMetaContent = (extensionOptions = {}) => { 90 | const meta = document.createElement('meta'); 91 | meta.name = 'extension-options'; 92 | meta.content = JSON.stringify(extensionOptions); 93 | document.head.appendChild(meta); 94 | }; 95 | 96 | const initApplication = (options = {}) => { 97 | addBodyTagIfMissing(); 98 | addHeadTagIfMissing(); 99 | injectCssUrlAndStyleTag(); 100 | injectScriptTag(); 101 | injectOptionsAsMetaContent(options); 102 | }; 103 | 104 | const applyOptions = (options) => { 105 | const themes = { 106 | default: 'dark-pro.css', 107 | mdn: 'mdn-light.css', 108 | }; 109 | const styleNode = document.getElementById(COLOR_THEME_LINK_TAG_ID); 110 | const colorThemeStylesheetUrl = themes[options.theme] || themes['default']; 111 | const cssURL = chrome.runtime.getURL( 112 | '/css/color-themes/' + colorThemeStylesheetUrl, 113 | ); 114 | 115 | if (styleNode.href.indexOf(colorThemeStylesheetUrl) < 0) { 116 | styleNode.setAttribute('href', cssURL); 117 | } 118 | document.getElementById('custom-css').textContent = options.css || ''; 119 | }; 120 | 121 | const renderApplicationWithURLFiltering = (options) => { 122 | const urls = (options || {}).filteredURL || []; 123 | const isURLBlocked = urls.some((url) => 124 | window.location.href.startsWith(url), 125 | ); 126 | 127 | if ( 128 | !isURLBlocked && 129 | (matchesContentType(options) || 130 | (isJSONContentDetectionEnabled(options) && 131 | doesPageContainsValidJSON())) 132 | ) { 133 | initApplication(options); 134 | applyOptions(options); 135 | window.JSON_VIEWER_PRO_INITIALISED = true; 136 | } 137 | }; 138 | 139 | const matchesContentType = (extensionOptions) => { 140 | return ( 141 | extensionOptions.jsonDetection.method === 'contentType' && 142 | extensionOptions.jsonDetection.selectedContentTypes.includes( 143 | document.contentType, 144 | ) 145 | ); 146 | }; 147 | 148 | const isJSONContentDetectionEnabled = (extensionOptions) => { 149 | return extensionOptions.jsonDetection.method === 'jsonContent'; 150 | }; 151 | 152 | const doesPageContainsValidJSON = () => { 153 | try { 154 | const textContent = getCleanTextContent(); 155 | JSON.parse(textContent); 156 | return true; 157 | } catch (error) { 158 | return false; 159 | } 160 | }; 161 | 162 | const isContentTypeDetectionEnabled = (extensionOptions) => { 163 | return extensionOptions.jsonDetection.method === 'contentType'; 164 | }; 165 | 166 | const messageReceiver = () => { 167 | chrome.runtime.onMessage.addListener((message) => { 168 | switch (message.action) { 169 | case 'options_received': 170 | window.extensionOptions = message.options; 171 | renderApplicationWithURLFiltering(message.options); 172 | break; 173 | 174 | case 'settings_updated': 175 | const previousOptions = window.extensionOptions; 176 | const newOptions = message.options; 177 | 178 | const contentTypeDetectionEnabled = 179 | (isContentTypeDetectionEnabled(previousOptions) || 180 | isContentTypeDetectionEnabled(newOptions)) && 181 | isDocumentContentTypeJSON(); 182 | 183 | const jsonContentDetectionEnabled = 184 | isJSONContentDetectionEnabled(previousOptions) || 185 | isJSONContentDetectionEnabled(newOptions); 186 | 187 | // window.extensionOptions is the previsous state once options are updated 188 | // Previsously rendered page needs to be reloaded to reflect updated options 189 | if ( 190 | isJsonViewerLoaded() || 191 | contentTypeDetectionEnabled || 192 | jsonContentDetectionEnabled 193 | ) { 194 | window.location.reload(); 195 | } 196 | break; 197 | 198 | case 'rb_download_json': 199 | location.hash = 'downloadJSON=true'; 200 | break; 201 | 202 | default: 203 | break; 204 | } 205 | }); 206 | }; 207 | 208 | messageReceiver(); 209 | 210 | // alternative to DOMContentLoaded event 211 | document.onreadystatechange = function () { 212 | if (document.readyState === 'interactive') { 213 | chrome.runtime.sendMessage({ action: 'give_me_options' }); 214 | } 215 | }; 216 | -------------------------------------------------------------------------------- /src/TreeView.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, createRef } from 'react'; 2 | import $ from 'jquery'; 3 | var jQuery = $; 4 | import { initPlugin } from './utils/json-viewer/json-viewer.js'; 5 | import { iconFillColor } from './utils/common'; 6 | import SearchBar from './components/Searchbar'; 7 | import CopyIcon from './components/Icons/Copy'; 8 | 9 | import './utils/json-viewer/json-viewer.css'; 10 | 11 | class TreeView extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | top: 0, 16 | showCopier: false, 17 | actualPath: null, 18 | value: null, 19 | data: props.data, 20 | }; 21 | this.jsonRenderer = createRef(); 22 | this.changeCopyIconLocation = this.changeCopyIconLocation.bind(this); 23 | this.toggleSection = this.toggleSection.bind(this); 24 | } 25 | 26 | copy(event, type) { 27 | event.preventDefault(); 28 | let context; 29 | if (type === 'path') { 30 | context = this.state.actualPath; 31 | } else { 32 | context = this.state.value; 33 | } 34 | let selElement, selRange, selection; 35 | selElement = document.createElement('span'); 36 | selRange = document.createRange(); 37 | selElement.innerText = context; 38 | document.body.appendChild(selElement); 39 | selRange.selectNodeContents(selElement); 40 | selection = window.getSelection(); 41 | selection.removeAllRanges(); 42 | selection.addRange(selRange); 43 | document.execCommand('Copy'); 44 | document.body.removeChild(selElement); 45 | } 46 | 47 | changeCopyIconLocation(e) { 48 | const self = this; 49 | this.findPath(self, e); 50 | self.setState({ 51 | top: $(e.target).offset().top, 52 | showCopier: true, 53 | }); 54 | return false; 55 | } 56 | 57 | getArrayIndex(path) { 58 | const arrayIndexBracketStartAt = path.lastIndexOf('['); 59 | const arrayIndexBracketEndAt = path.lastIndexOf(']'); 60 | if (arrayIndexBracketStartAt > -1) { 61 | return path.substring( 62 | arrayIndexBracketStartAt + 1, 63 | arrayIndexBracketEndAt, 64 | ); 65 | } 66 | return path; 67 | } 68 | 69 | createValidPath(pathArray) { 70 | let path = ''; 71 | pathArray.forEach((item, index) => { 72 | if (index === 0) { 73 | path = path.concat(item); 74 | } else { 75 | if (item.indexOf('-') > -1 || item.indexOf(' ') > -1) { 76 | path = `${path}['${item}']`; 77 | } else if (isNaN(item) === false) { 78 | path = `${path}[${item}]`; 79 | } else { 80 | path = path.concat('.').concat(item); 81 | } 82 | } 83 | }); 84 | return path; 85 | } 86 | 87 | findPath(self, e) { 88 | let keys = []; 89 | let keyValueString = $(e.target).parents('li').first().text(); 90 | let firstIndexOfColone = keyValueString.indexOf(':'); 91 | let value = keyValueString.substring(firstIndexOfColone + 1); 92 | let nodes = $(e.target).parentsUntil('#json-viewer'); 93 | $(nodes).each(function (i, node) { 94 | if ( 95 | $(node).get(0).tagName == 'LI' && 96 | $(node).parent()[0].tagName == 'UL' 97 | ) { 98 | let parentKey = $(node).find('span.property').eq(0).text(); 99 | keys.push(self.getArrayIndex(parentKey.replace(/\"+/g, ''))); 100 | } 101 | 102 | if ( 103 | $(node).get(0).tagName == 'LI' && 104 | $(node).parent()[0].tagName == 'OL' 105 | ) { 106 | var parentKey = 107 | $(node) 108 | .parent('OL') 109 | .parent('li') 110 | .find('span.property') 111 | .eq(0) 112 | .text() + 113 | '[' + 114 | $(node).index() + 115 | ']'; 116 | keys.push(self.getArrayIndex(parentKey.replace(/\"+/g, ''))); 117 | } 118 | }); 119 | 120 | if (value[value.length - 1] === ',') { 121 | value = value.substring(0, value.length - 1); 122 | } 123 | self.setState({ 124 | actualPath: self.createValidPath(keys.reverse()), 125 | value, 126 | }); 127 | } 128 | 129 | toggleSection(e) { 130 | e.preventDefault(); 131 | e.stopPropagation(); 132 | const carretIcon = $(e.currentTarget); 133 | const collapsibleNode = carretIcon.siblings('ul.json-dict, ol.json-array'); 134 | 135 | 136 | // Going to toggle the class and visibility of the carret icon 137 | // Class will be set to 'collapsed' and collapsibleNode will have style display none since it's visible 138 | const isVislbe = collapsibleNode.get(0).checkVisibility(); 139 | if (isVislbe) { 140 | const count = collapsibleNode.children('li').length; 141 | const placeholder = count + (count > 1 ? ' items' : ' item'); 142 | collapsibleNode.after( 143 | '' + placeholder + '', 144 | ); 145 | } else { 146 | collapsibleNode.siblings('.json-placeholder').remove(); 147 | } 148 | 149 | collapsibleNode.toggle(); 150 | carretIcon.toggleClass('collapsed'); 151 | } 152 | 153 | componentDidUpdate(prevProps) { 154 | if (prevProps.data !== this.props.data) { 155 | this.reRenderTree(); 156 | } 157 | } 158 | 159 | reRenderTree(json) { 160 | this.cleanUpEventListeners(); 161 | this.renderJSONTree(json); 162 | window.scrollTo(0, 0); 163 | if (this.state.showCopier) { 164 | this.setState({ 165 | showCopier: false, 166 | }); 167 | } 168 | } 169 | 170 | restoreOriginalJSON() { 171 | this.reRenderTree(this.props.data); 172 | } 173 | 174 | renderJSONTree(json) { 175 | const data = json ?? this.props.data; 176 | this.$node = $(this.jsonRenderer.current); 177 | if ($) { 178 | const pluginOptions = { 179 | collapsed: window.extensionOptions?.collapsed === 1, 180 | withQuotes: true, 181 | }; 182 | initPlugin(this.$node, $, data, pluginOptions); 183 | $(document).on( 184 | 'click', 185 | 'span.property', 186 | this.changeCopyIconLocation, 187 | ); 188 | $(document).off('click', 'a.json-toggle'); 189 | $(document).on('click', 'a.json-toggle', this.toggleSection); 190 | } 191 | } 192 | 193 | componentDidMount() { 194 | this.renderJSONTree(); 195 | } 196 | 197 | cleanUpEventListeners() { 198 | $(document).off('click', 'span.property', this.changeCopyIconLocation); 199 | $(document).off('click', 'a.json-toggle', this.toggleSection); 200 | } 201 | 202 | componentWillUnmount() { 203 | this.cleanUpEventListeners(); 204 | } 205 | 206 | render() { 207 | window.json = this.props.data; 208 | return ( 209 |
210 | 217 | 221 | 233 | 234 |

235 |                 {this.props.isSearchBarVisible && (
236 |                     
243 |                 )}
244 |             
245 | ); 246 | } 247 | } 248 | 249 | export default TreeView; 250 | -------------------------------------------------------------------------------- /src/css/style.scss: -------------------------------------------------------------------------------- 1 | @use './color-themes/dark-pro.css'; 2 | 3 | body { 4 | background-color: var(--bg-color); 5 | color: var(--primary-text-color); 6 | font-size: var(--base-font-size); 7 | margin: 0; 8 | padding: 0; 9 | font-family: var(--primary-font-family); 10 | } 11 | 12 | .tab-container { 13 | margin: 20px; 14 | margin-left: 40px; 15 | } 16 | 17 | .breadcumb { 18 | position: fixed; 19 | top: 20px; 20 | left: 20px; 21 | display: inline-flex; 22 | border: 1px solid var(--breadcumb-border-color); 23 | background-color: var(--breadcumb-bg-color); 24 | border-radius: 3px; 25 | padding: 0px; 26 | user-select: none; 27 | 28 | > ul { 29 | margin: 0; 30 | padding: 0; 31 | max-width: 600px; 32 | 33 | & > li { 34 | list-style: none; 35 | display: inline-block; 36 | } 37 | } 38 | } 39 | 40 | .breadcumb > ul > li > a { 41 | display: block; 42 | padding: 10px; 43 | padding-right: 0px; 44 | text-decoration: none; 45 | color: var(--primary-text-color); 46 | width: auto; 47 | border-right: none; 48 | font-size: 14px; 49 | border-right: none; 50 | border-left: none; 51 | cursor: pointer; 52 | 53 | &:hover { 54 | color: var(--breadcumb-item-hover-color); 55 | } 56 | } 57 | 58 | .breadcumb > ul > li:last-child > a { 59 | padding-right: 10px; 60 | } 61 | 62 | .breadcumb > ul > li:first-child > a { 63 | padding-left: 10px; 64 | } 65 | 66 | .breadcumb > ul > li > a::after { 67 | content: '\276F'; /* left arrow */ 68 | color: var(--breadcumb-arrow-color); 69 | cursor: auto; 70 | margin-left: 7px; 71 | } 72 | 73 | .breadcumb > ul > li:last-child > a::after { 74 | content: ''; /* no arrow after last child*/ 75 | } 76 | 77 | .copy-breadcumb-btn { 78 | text-align: center; 79 | align-items: center; 80 | display: flex; 81 | padding: 0 7px; 82 | border-right: 1px solid var(--breadcumb-border-color); 83 | cursor: pointer; 84 | 85 | img.path-copy-icon { 86 | filter: var(--copy-path-btn-img-filter); 87 | } 88 | } 89 | 90 | .action-area { 91 | width: auto; 92 | position: fixed; 93 | top: 20px; 94 | right: 10px; 95 | user-select: none; 96 | } 97 | 98 | .action-area > ul.menus { 99 | margin: 0; 100 | padding: 0; 101 | 102 | li { 103 | list-style: none; 104 | display: inline-block; 105 | overflow: hidden; 106 | margin-right: 6px; 107 | 108 | > a { 109 | display: inline-flex; 110 | padding: 8px 10px; 111 | text-decoration: none; 112 | color: var(--primary-text-color); 113 | width: auto; 114 | border-right: none; 115 | font-size: 12px; 116 | font-weight: bold; 117 | align-items: center; 118 | border: 1px solid var(--menu-item-border-color); 119 | background-color: var(--menu-item-bg-color); 120 | border-radius: 6px; 121 | img { 122 | filter: var(--menu-item-img-filter); 123 | } 124 | } 125 | } 126 | } 127 | 128 | .rotate-90 { 129 | transform: rotate(90deg); 130 | } 131 | 132 | .rotate-270 { 133 | transform: rotate(270deg); 134 | } 135 | 136 | .sm-icon { 137 | width: 20px; 138 | height: 20px; 139 | } 140 | 141 | .sm-icon:has(+ span.menu-label) { 142 | margin-right: 5px; 143 | } 144 | 145 | .sm-icon.option-icon { 146 | width: 24px; 147 | height: 24px; 148 | } 149 | 150 | .action-area > ul.menus > li.active > a { 151 | border: 1px solid var(--menu-item-active-border-color); 152 | background-color: var(--menu-item-active-bg-color); 153 | } 154 | 155 | .action-area > ul.menus > li > a.option-menu { 156 | padding: 6px !important; 157 | } 158 | 159 | ul.json-dict li { 160 | width: auto; 161 | display: block; 162 | line-height: 180%; 163 | } 164 | 165 | .copyable { 166 | background: var(--copyable-property-bg-color); 167 | width: auto; 168 | border: 1px dotted var(--copyable-property-border-color); 169 | } 170 | 171 | .copyable > ul.json-dict, 172 | .copyable > ol.json-array { 173 | border-left: none; 174 | } 175 | 176 | .property { 177 | color: var(--property-color); /*#3DAAE0;*/ 178 | line-height: 160%; 179 | font-weight: bold; 180 | } 181 | 182 | .json-literal-numeric { 183 | color: var(--json-literal-numeric); 184 | } 185 | 186 | .json-literal-string { 187 | color: var(--json-literal-string); 188 | white-space: normal; 189 | } 190 | 191 | .json-literal-url { 192 | color: var(--json-literal-url); 193 | white-space: normal; 194 | } 195 | 196 | .json-literal-boolean, 197 | .json-literal { 198 | color: var(--json-literal-and-boolean); 199 | } 200 | 201 | .nodeCircle { 202 | fill: var(--json-chart-circle-bg); 203 | } 204 | 205 | .chart-holder { 206 | font-size: 14px; 207 | } 208 | 209 | .json-input-section { 210 | width: 100%; 211 | } 212 | 213 | .json-input-section h1 { 214 | color: var(--primary-text-color); 215 | font-size: 30px; 216 | font-weight: 100; 217 | line-height: 110%; 218 | text-align: center; 219 | } 220 | 221 | .save-btn-area { 222 | text-align: center; 223 | } 224 | 225 | .save-btn-area button:not(:first-child) { 226 | margin-left: 1em; 227 | } 228 | 229 | .json-input-error-msg { 230 | color: var(--primary-text-color); 231 | padding: 12px 15px; 232 | background-color: var(--error-msg-bg-color); 233 | margin: 0 auto; 234 | box-shadow: 0 7px 7px 0 #111; 235 | border: 1px solid rgb(201, 9, 9); 236 | } 237 | 238 | .copier { 239 | background: transparent; 240 | padding: 3px 3px 0px 3px; 241 | display: none; 242 | top: 10px; 243 | left: 10px; 244 | position: absolute; 245 | z-index: 1001; 246 | box-sizing: border-box; 247 | } 248 | 249 | .copy-btn { 250 | width: 26px; 251 | height: auto; 252 | margin-top: -5px; 253 | } 254 | 255 | ul.copyMenu { 256 | left: 30px; 257 | top: -20px; 258 | padding: 0; 259 | list-style: none; 260 | position: absolute; 261 | z-index: 1001; 262 | display: none; 263 | border: 1px solid var(--dropdown-menu-item-border-color); 264 | } 265 | 266 | .copier:hover > ul.copyMenu { 267 | display: block; 268 | } 269 | 270 | ul.copyMenu::before { 271 | width: 20px; 272 | height: 20px; 273 | content: ''; 274 | transform: rotate(45deg); 275 | background: var(--dropdown-bg-primary); 276 | position: absolute; 277 | top: 4px; 278 | left: -10px; 279 | z-index: -100; 280 | border: 1px solid var(--dropdown-menu-item-border-color); 281 | } 282 | 283 | ul.copyMenu { 284 | box-shadow: 0 5px 10px 0 rgba(6, 6, 6, 0.8); 285 | 286 | li > a { 287 | background: var(--dropdown-bg-primary); 288 | color: var(--primary-text-color); 289 | font-size: 12px; 290 | font-weight: bold; 291 | width: 100px; 292 | padding: 6px 8px; 293 | border-bottom: 1px solid var(--dropdown-menu-item-border-color); 294 | display: block; 295 | cursor: pointer; 296 | 297 | &:hover { 298 | background: var(--dropdown-menu-item-bg); 299 | color: var(--primary-text-color); 300 | } 301 | } 302 | 303 | li:first-child { 304 | border-radius: 4px 4px 0 0; 305 | } 306 | 307 | li:last-child { 308 | border-radius: 0 0 4px 4px; 309 | & > a { 310 | border-bottom: none; 311 | } 312 | } 313 | } 314 | 315 | .d-none { 316 | display: none !important; 317 | } 318 | 319 | text.nodeText { 320 | color: var(--primary-text-color); 321 | } 322 | 323 | .jv-btn { 324 | border: 1px solid var(--btn-border-color); 325 | font-size: 15px; 326 | border-radius: 4px; 327 | background-color: var(--btn-bg-color); 328 | } 329 | 330 | .jv-btn-md { 331 | padding: 10px 16px; 332 | font-size: 16px; 333 | border-radius: 4px; 334 | } 335 | 336 | .jv-btn-sm { 337 | padding: 6px 12px; 338 | font-size: 14px; 339 | border-radius: 4px; 340 | } 341 | 342 | .jv-btn-default { 343 | background-color: var(--btn-default-bg-color); 344 | color: var(--primary-text-color); 345 | } 346 | 347 | .jv-btn-primary { 348 | background-color: var(--btn-primary-bg-color); 349 | color: var(--btn-primary-text-color); 350 | } 351 | 352 | .jv-align-start { 353 | display: flex; 354 | flex-direction: row; 355 | justify-content: flex-start; 356 | } 357 | 358 | .jv-align-right { 359 | display: flex; 360 | flex-direction: row; 361 | justify-content: flex-end; 362 | } 363 | 364 | .jv-width-half { 365 | width: 50%; 366 | } 367 | 368 | .debug-cmd { 369 | font-size: 16px; 370 | margin: 5px; 371 | margin-left: 15px; 372 | } 373 | 374 | .sm-btn-icon { 375 | margin-right: 10px; 376 | display: inline-flex; 377 | align-items: center; 378 | } 379 | 380 | .inline-flex { 381 | display: inline-flex; 382 | align-items: center; 383 | } 384 | 385 | .search-icon-btn { 386 | font-size: 20px !important; 387 | padding: 8px !important; 388 | text-align: center; 389 | box-sizing: border-box; 390 | } 391 | 392 | .path-copy-icon { 393 | user-select: none; 394 | } 395 | 396 | .disabled-btn { 397 | user-select: none; 398 | -moz-user-select: none; 399 | pointer-events: none; 400 | } 401 | 402 | #json-viewer{ 403 | margin-top:50px; 404 | } 405 | -------------------------------------------------------------------------------- /src/components/Searchbar/index.jsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useRef, useState } from 'react'; 2 | import { JSONPath } from 'jsonpath-plus'; 3 | 4 | import './style.scss'; 5 | import Select from '../Select'; 6 | import { FiDelete } from 'react-icons/fi'; 7 | 8 | const JSON_PATH_SEARCH_PATTERN_FOR_AUTO_SUGGESTION = /(\.|\[|\"|\'|\[")/; 9 | 10 | const makeListItem = (items, searchFlag) => { 11 | let filteredList = items; 12 | if (searchFlag.length > 0) { 13 | filteredList = items.filter((item) => item.startsWith(searchFlag)); 14 | } 15 | return filteredList.map((listItem) => ({ 16 | label: listItem, 17 | selected: false, 18 | })); 19 | }; 20 | 21 | const SearchBar = ({ json, renderJSON, restoreOriginalJSON }) => { 22 | const searchInputRef = useRef(); 23 | const [searchText, setSearchText] = useState(''); 24 | const [searchInfo, setSearchInfo] = useState(''); 25 | const [suggestions, setSuggestions] = useState([]); 26 | const [filteredSuggestions, setFilteredSuggestions] = useState([]); 27 | const [showSuggestion, setShowSuggestion] = useState(false); 28 | const [isJsonModified, setIsJsonModified] = useState(false); 29 | 30 | const parseViaJSONPath = (path) => { 31 | let jsonPath = path; 32 | if (jsonPath.startsWith('[')) { 33 | jsonPath = `$.${jsonPath}`; 34 | } else if (jsonPath.startsWith('.')) { 35 | jsonPath = `\$${jsonPath}`; 36 | } 37 | const result = JSONPath({ 38 | path: jsonPath, 39 | json, 40 | eval: false, 41 | }); 42 | if (result && Array.isArray(result) && result.length > 0) { 43 | return result[0]; 44 | } 45 | }; 46 | 47 | const onInputChange = (e) => { 48 | let path = e.target.value; 49 | if (path.length === 0) { 50 | onSearchTextClear(); 51 | } 52 | setSearchText(path); 53 | setShowSuggestion(true); 54 | setSearchInfo(''); 55 | try { 56 | const matchedDelemeterParts = 57 | path.endsWith('.') || 58 | path.endsWith("'") || 59 | path.endsWith('"') || 60 | path.endsWith('[') || 61 | path.endsWith('["'); 62 | 63 | if (!matchedDelemeterParts) { 64 | return; 65 | } 66 | const resolvedPathValue = parseViaJSONPath(path); 67 | if (resolvedPathValue) { 68 | let suggestions = []; 69 | if (!Array.isArray(resolvedPathValue)) { 70 | suggestions = Object.keys(resolvedPathValue); 71 | } else { 72 | suggestions = new Array(resolvedPathValue.length) 73 | .fill(0) 74 | .map((_, i) => String(i)); 75 | } 76 | setSuggestions(suggestions); 77 | } else { 78 | setSuggestions([]); 79 | } 80 | setSearchInfo(''); 81 | } catch (e) { 82 | console.error('failed to parse json path:', e); 83 | setSearchInfo( 84 | 'Failed to retrieve value from the Path you provided', 85 | ); 86 | } 87 | }; 88 | 89 | const onkeyDown = useCallback( 90 | (e) => { 91 | // tab key press 92 | if (e.which === 9 && showSuggestion) { 93 | e.preventDefault(); 94 | return; 95 | } 96 | if (e.which === 13) { 97 | const isSuggestionDropDownMenuActive = 98 | showSuggestion && filteredSuggestions.length > 0; 99 | if ( 100 | searchText.length === '' || 101 | isSuggestionDropDownMenuActive 102 | ) { 103 | return; 104 | } 105 | 106 | try { 107 | const resolvedPathValue = parseViaJSONPath(searchText); 108 | if (typeof resolvedPathValue === 'undefined') { 109 | setSearchInfo( 110 | 'Failed to retrieve value from the Path you provided', 111 | ); 112 | return; 113 | } 114 | const newJsonToRender = { 115 | [searchText]: resolvedPathValue, 116 | }; 117 | if (typeof renderJSON === 'function') { 118 | renderJSON(newJsonToRender); 119 | setIsJsonModified(true); 120 | } 121 | } catch (error) { 122 | setSearchInfo( 123 | 'Failed to retrieve value from the Path you provided', 124 | ); 125 | } 126 | } 127 | if (e.which === 40 || e.which === 38) { 128 | e.preventDefault(); 129 | } 130 | }, 131 | [showSuggestion, filteredSuggestions, searchText, renderJSON], 132 | ); 133 | 134 | useEffect(() => { 135 | const searchParts = searchText.split( 136 | JSON_PATH_SEARCH_PATTERN_FOR_AUTO_SUGGESTION, 137 | ); 138 | const searchFlag = 139 | searchParts && searchParts.length > 0 140 | ? searchParts[searchParts.length - 1].trim() 141 | : ''; 142 | const filteredSuggestedItems = makeListItem(suggestions, searchFlag); 143 | setFilteredSuggestions(filteredSuggestedItems); 144 | }, [suggestions, searchText]); 145 | 146 | const onSuggestionSelected = (value) => { 147 | const delemeterCompletionPairs = { 148 | "'": "'", 149 | '"': '"', 150 | '[': ']', 151 | '.': '', 152 | }; 153 | const searchParts = searchText.split( 154 | JSON_PATH_SEARCH_PATTERN_FOR_AUTO_SUGGESTION, 155 | ); 156 | if (searchParts && searchParts.length > 1) { 157 | let pathTillPathDelimeter = searchText.substring( 158 | 0, 159 | searchText.length - searchParts[searchParts.length - 1].length, 160 | ); 161 | const startingDelemeter = searchParts[searchParts.length - 2]; 162 | const closingDelemeter = 163 | startingDelemeter && 164 | startingDelemeter in delemeterCompletionPairs 165 | ? delemeterCompletionPairs[startingDelemeter] 166 | : ''; 167 | const completePathWithAutoSuggestionApplied = `${pathTillPathDelimeter}${value}${closingDelemeter}`; 168 | setSearchText(completePathWithAutoSuggestionApplied); 169 | searchInputRef.current.focus(); 170 | searchInputRef.current.scroll( 171 | searchInputRef.current.scrollWidth, 172 | 0, 173 | ); 174 | searchInputRef.current.setSelectionRange( 175 | completePathWithAutoSuggestionApplied.length, 176 | completePathWithAutoSuggestionApplied.length, 177 | ); 178 | } 179 | setShowSuggestion(false); 180 | }; 181 | 182 | const onSuggestionDropdownClosed = () => { 183 | setShowSuggestion(false); 184 | }; 185 | 186 | const resetSuggestions = () => { 187 | setSearchText(''); 188 | setShowSuggestion(false); 189 | setSuggestions([]); 190 | }; 191 | 192 | const onSearchTextClear = () => { 193 | resetSuggestions(); 194 | if (searchInfo) { 195 | setSearchInfo(''); 196 | } 197 | 198 | if (isJsonModified) { 199 | restoreOriginalJSON(); 200 | setIsJsonModified(false); 201 | } 202 | }; 203 | 204 | useEffect(() => restoreOriginalJSON, []); 205 | 206 | return ( 207 |
208 |
209 | setShowSuggestion(true)} 220 | autoComplete="off" 221 | autoCorrect="off" 222 | /> 223 |
224 |
230 | 231 |
232 |
233 | 234 | {filteredSuggestions && 235 | filteredSuggestions.length > 0 && 236 | showSuggestion && ( 237 |
238 | 249 |
250 |
251 | 252 | {this.state.footerMessage} 253 |
254 |
255 | 256 | Line {this.state.cursorPosition.line}, Col{' '} 257 | {this.state.cursorPosition.col} 258 | {' '} 259 | 260 | Total Bytes:{' '} 261 | {calculateFileSize(totalNumberOfBytes)} 262 | 263 |
264 |
265 |
266 |
267 | 268 | ); 269 | } 270 | } 271 | 272 | export default Editor; 273 | --------------------------------------------------------------------------------