diff --git a/docs/src/dev-guide/contributing-validation.md b/docs/src/dev-guide/contributing-validation.md index 3af915711..12f597409 100644 --- a/docs/src/dev-guide/contributing-validation.md +++ b/docs/src/dev-guide/contributing-validation.md @@ -39,5 +39,6 @@ still be tested manually: * Exporting all logs to a file * Toggling tabbed panels in the sidebar * Using keyboard shortcuts + * Toggling "Prettify" in both the status bar and the address bar [gh-workflow-test]: https://github.com/y-scope/yscope-log-viewer/blob/main/.github/workflows/test.yaml diff --git a/package-lock.json b/package-lock.json index dfc45d51f..75a203cf4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.14.0", "@mui/icons-material": "^6.4.1", "@mui/joy": "^5.0.0-beta.51", + "@types/js-beautify": "^1.14.3", "axios": "^1.8.2", "clp-ffi-js": "^0.5.0", "dayjs": "^1.11.13", @@ -38,6 +39,7 @@ "html-webpack-plugin": "^5.6.3", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "js-beautify": "^1.15.4", "mini-css-extract-plugin": "^2.9.2", "monaco-editor-webpack-plugin": "^7.1.0", "npm-run-all": "^4.1.5", @@ -392,26 +394,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz", - "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.5" + "@babel/types": "^7.27.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -1866,9 +1868,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", - "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -1878,14 +1880,14 @@ } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" @@ -1919,9 +1921,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz", - "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -2553,6 +2555,109 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -3642,6 +3747,24 @@ "node": ">=12.4.0" } }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/core": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", @@ -4066,6 +4189,12 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/js-beautify": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@types/js-beautify/-/js-beautify-1.14.3.tgz", + "integrity": "sha512-FMbQHz+qd9DoGvgLHxeqqVPaNRffpIu5ZjozwV8hf9JAGpIOzuAf4wGbRSo8LNITHqGjmmVjaMggTT5P4v4IHg==", + "license": "MIT" + }, "node_modules/@types/jsdom": { "version": "20.0.1", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", @@ -5075,6 +5204,16 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -6483,6 +6622,17 @@ "dev": true, "license": "MIT" }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "node_modules/connect-history-api-fallback": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", @@ -7269,6 +7419,71 @@ "dev": true, "license": "MIT" }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/editorconfig/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -8672,6 +8887,36 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", @@ -10468,6 +10713,22 @@ "node": ">= 0.4" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", @@ -11198,6 +11459,59 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11850,7 +12164,6 @@ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -11872,6 +12185,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/monaco-editor": { "version": "0.50.0", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.50.0.tgz", @@ -12007,6 +12330,22 @@ "dev": true, "license": "MIT" }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -12582,6 +12921,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -12708,6 +13054,30 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -13172,6 +13542,13 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -14600,6 +14977,22 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -14732,6 +15125,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -16776,6 +17183,25 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index ad64a5b9c..1efa03903 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@emotion/styled": "^11.14.0", "@mui/icons-material": "^6.4.1", "@mui/joy": "^5.0.0-beta.51", + "@types/js-beautify": "^1.14.3", "axios": "^1.8.2", "clp-ffi-js": "^0.5.0", "dayjs": "^1.11.13", @@ -54,6 +55,7 @@ "html-webpack-plugin": "^5.6.3", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "js-beautify": "^1.15.4", "mini-css-extract-plugin": "^2.9.2", "monaco-editor-webpack-plugin": "^7.1.0", "npm-run-all": "^4.1.5", diff --git a/src/components/Editor/index.tsx b/src/components/Editor/index.tsx index 31a9e83c1..f55639b10 100644 --- a/src/components/Editor/index.tsx +++ b/src/components/Editor/index.tsx @@ -1,3 +1,5 @@ +/* eslint max-lines: ["error", 350] */ +/* eslint max-lines-per-function: ["error", 170] */ import { useCallback, useContext, @@ -19,6 +21,7 @@ import { CONFIG_KEY, THEME_NAME, } from "../../typings/config"; +import {HASH_PARAM_NAMES} from "../../typings/url"; import {BeginLineNumToLogEventNumMap} from "../../typings/worker"; import { ACTION_NAME, @@ -135,13 +138,14 @@ const Editor = () => { const {mode, systemMode} = useColorScheme(); const {beginLineNumToLogEventNum, logData, loadPageByAction} = useContext(StateContext); - const {logEventNum} = useContext(UrlContext); + const {isPrettified, logEventNum} = useContext(UrlContext); const [lineNum, setLineNum] = useState(1); const beginLineNumToLogEventNumRef = useRef( beginLineNumToLogEventNum ); const editorRef = useRef>(null); + const isPrettifiedRef = useRef(isPrettified ?? false); const isMouseDownRef = useRef(false); const pageSizeRef = useRef(getConfig(CONFIG_KEY.PAGE_SIZE)); @@ -170,6 +174,11 @@ const Editor = () => { case ACTION_NAME.COPY_LOG_EVENT: handleCopyLogEventAction(editor, beginLineNumToLogEventNumRef.current); break; + case ACTION_NAME.TOGGLE_PRETTIFY: + updateWindowUrlHashParams({ + [HASH_PARAM_NAMES.IS_PRETTIFIED]: !isPrettifiedRef.current, + }); + break; case ACTION_NAME.WORD_WRAP: handleWordWrapAction(editor); break; @@ -251,6 +260,11 @@ const Editor = () => { beginLineNumToLogEventNumRef.current = beginLineNumToLogEventNum; }, [beginLineNumToLogEventNum]); + // Synchronize `isPrettifiedRef` with `isPrettified`. + useEffect(() => { + isPrettifiedRef.current = isPrettified ?? false; + }, [isPrettified]); + // On `logEventNum` update, update line number in the editor. useEffect(() => { if (null === editorRef.current || isMouseDownRef.current) { diff --git a/src/components/MenuBar/ExportLogsButton.tsx b/src/components/MenuBar/ExportLogsButton.tsx index cf88de464..79d5801cf 100644 --- a/src/components/MenuBar/ExportLogsButton.tsx +++ b/src/components/MenuBar/ExportLogsButton.tsx @@ -31,7 +31,7 @@ const ExportLogsButton = () => { return ( ( { - const {uiState, loadPageByAction} = useContext(StateContext); + const { + uiState, + loadPageByAction, + } = useContext(StateContext); - const handleNavButtonClick = (event: React.MouseEvent) => { - const {actionName} = event.currentTarget.dataset; + const handleNavButtonClick = (ev: React.MouseEvent) => { + const {actionName} = ev.currentTarget.dataset; // Ensure `actionName` is a valid navigation action code with no args. if ( @@ -49,14 +52,14 @@ const NavigationBar = () => { > @@ -66,14 +69,14 @@ const NavigationBar = () => { diff --git a/src/components/StatusBar/StatusBarToggleButton.tsx b/src/components/StatusBar/StatusBarToggleButton.tsx new file mode 100644 index 000000000..bba8c6355 --- /dev/null +++ b/src/components/StatusBar/StatusBarToggleButton.tsx @@ -0,0 +1,71 @@ +import React from "react"; + +import { + IconButton, + IconButtonProps, + Tooltip, + TooltipTypeMap, +} from "@mui/joy"; + +import CheckIcon from "@mui/icons-material/Check"; +import CloseIcon from "@mui/icons-material/Close"; + + +interface StatusBarToggleButtonProps extends IconButtonProps { + icons?: { + active?: React.ReactNode; + inactive?: React.ReactNode; + }; + isActive: boolean; + onIcon?: React.ReactNode; + offIcon?: React.ReactNode; + tooltipPlacement?: TooltipTypeMap["props"]["placement"]; + tooltipTitle?: string; +} + +/** + * A toggle button for use in the status bar. + * + * @param props + * @param props.icons Icons to use for the active and inactive states. + * @param props.isActive + * @param props.tooltipPlacement Tooltip position. + * @param props.tooltipTitle Tooltip title, to discern with native HTML tooltip. + * @param props.rest Other IconButton props. + * @return + */ +const StatusBarToggleButton = ({ + icons = { + active: , + inactive: , + }, + isActive = false, + tooltipPlacement, + tooltipTitle, + ...rest +}: StatusBarToggleButtonProps) => { + return ( + + + + {isActive ? + icons.active : + icons.inactive} + + + + ); +}; + + +export default StatusBarToggleButton; diff --git a/src/components/StatusBar/index.tsx b/src/components/StatusBar/index.tsx index af8cd6d2a..7c0bce7df 100644 --- a/src/components/StatusBar/index.tsx +++ b/src/components/StatusBar/index.tsx @@ -1,4 +1,4 @@ -import {useContext} from "react"; +import React, {useContext} from "react"; import { Button, @@ -7,14 +7,21 @@ import { Typography, } from "@mui/joy"; +import AutoFixHighRoundedIcon from "@mui/icons-material/AutoFixHighRounded"; +import AutoFixOffRoundedIcon from "@mui/icons-material/AutoFixOffRounded"; + import {StateContext} from "../../contexts/StateContextProvider"; import { copyPermalinkToClipboard, + updateWindowUrlHashParams, UrlContext, } from "../../contexts/UrlContextProvider"; import {UI_ELEMENT} from "../../typings/states"; +import {HASH_PARAM_NAMES} from "../../typings/url"; +import {ACTION_NAME} from "../../utils/actions"; import {isDisabled} from "../../utils/states"; import LogLevelSelect from "./LogLevelSelect"; +import StatusBarToggleButton from "./StatusBarToggleButton"; import "./index.css"; @@ -33,7 +40,24 @@ const handleCopyLinkButtonClick = () => { */ const StatusBar = () => { const {uiState, numEvents} = useContext(StateContext); - const {logEventNum} = useContext(UrlContext); + const {isPrettified, logEventNum} = useContext(UrlContext); + + const handleStatusButtonClick = (ev: React.MouseEvent) => { + const {actionName} = ev.currentTarget.dataset; + + switch (actionName) { + case ACTION_NAME.TOGGLE_PRETTIFY: + updateWindowUrlHashParams({ + [HASH_PARAM_NAMES.IS_PRETTIFIED]: !isPrettified, + }); + break; + default: + console.error(`Unexpected action: ${actionName}`); + break; + } + }; + + const isPrettifyButtonDisabled = isDisabled(uiState, UI_ELEMENT.PRETTIFY_BUTTON); return ( @@ -59,6 +83,19 @@ const StatusBar = () => { + + , + inactive: , + }} + tooltipTitle={isPrettified ?? false ? + "Turn off Prettify" : + "Turn on Prettify"} + onClick={handleStatusButtonClick}/> ); }; diff --git a/src/contexts/StateContextProvider.tsx b/src/contexts/StateContextProvider.tsx index 414a1ca04..7948b77de 100644 --- a/src/contexts/StateContextProvider.tsx +++ b/src/contexts/StateContextProvider.tsx @@ -1,4 +1,4 @@ -/* eslint max-lines: ["error", 600] */ +/* eslint max-lines: ["error", 650] */ import React, { createContext, useCallback, @@ -68,7 +68,6 @@ interface StateContextType { beginLineNumToLogEventNum: BeginLineNumToLogEventNumMap; exportProgress: Nullable; fileName: string; - uiState: UI_STATE; logData: string; numEvents: number; numPages: number; @@ -76,6 +75,7 @@ interface StateContextType { pageNum: number; queryProgress: number; queryResults: QueryResults; + uiState: UI_STATE; exportLogs: () => void; filterLogs: (filter: LogLevelFilter) => void; @@ -186,13 +186,16 @@ const getPageNumCursor = ( * * @param worker * @param cursor + * @param isPrettified is pretty-printing log messages enabled */ const loadPageByCursor = ( worker: Worker, cursor: CursorType, + isPrettified: boolean, ) => { workerPostReq(worker, WORKER_REQ_CODE.LOAD_PAGE, { cursor: cursor, + isPrettified: isPrettified, }); }; @@ -245,7 +248,7 @@ const updateUrlIfEventOnPage = ( // eslint-disable-next-line max-lines-per-function, max-statements const StateContextProvider = ({children}: StateContextProviderProps) => { const {postPopUp} = useContext(NotificationContext); - const {filePath, logEventNum} = useContext(UrlContext); + const {filePath, isPrettified, logEventNum} = useContext(UrlContext); // States const [activeTabName, setActiveTabName] = useState(STATE_DEFAULT.activeTabName); @@ -266,6 +269,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { const beginLineNumToLogEventNumRef = useRef(STATE_DEFAULT.beginLineNumToLogEventNum); const fileSrcRef = useRef>(null); + const isPrettifiedRef = useRef(isPrettified ?? false); const logEventNumRef = useRef(logEventNum); const logExportManagerRef = useRef(null); const mainWorkerRef = useRef(null); @@ -416,10 +420,11 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { ); mainWorkerRef.current.onmessage = handleMainWorkerResp; workerPostReq(mainWorkerRef.current, WORKER_REQ_CODE.LOAD_FILE, { - fileSrc: fileSrc, - pageSize: getConfig(CONFIG_KEY.PAGE_SIZE), cursor: cursor, decoderOptions: getConfig(CONFIG_KEY.DECODER_OPTIONS), + fileSrc: fileSrc, + isPrettified: isPrettifiedRef.current, + pageSize: getConfig(CONFIG_KEY.PAGE_SIZE), }); }, [ handleMainWorkerResp, @@ -459,7 +464,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { } setUiState(UI_STATE.FAST_LOADING); - loadPageByCursor(mainWorkerRef.current, cursor); + loadPageByCursor(mainWorkerRef.current, cursor, isPrettifiedRef.current); }, [loadFile]); const filterLogs = useCallback((filter: LogLevelFilter) => { @@ -469,10 +474,16 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { setUiState(UI_STATE.FAST_LOADING); workerPostReq(mainWorkerRef.current, WORKER_REQ_CODE.SET_FILTER, { cursor: {code: CURSOR_CODE.EVENT_NUM, args: {eventNum: logEventNumRef.current ?? 1}}, + isPrettified: isPrettifiedRef.current, logLevelFilter: filter, }); }, []); + // Synchronize `isPrettifiedRef` with `isPrettified`. + useEffect(() => { + isPrettifiedRef.current = isPrettified ?? false; + }, [isPrettified]); + // Synchronize `logEventNumRef` with `logEventNum`. useEffect(() => { logEventNumRef.current = logEventNum; @@ -497,6 +508,22 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { } }, [uiState]); + useEffect(() => { + if (null === mainWorkerRef.current) { + return; + } + + const cursor: CursorType = { + code: CURSOR_CODE.EVENT_NUM, + args: {eventNum: logEventNumRef.current ?? 1}, + }; + + setUiState(UI_STATE.FAST_LOADING); + loadPageByCursor(mainWorkerRef.current, cursor, isPrettified ?? false); + }, [ + isPrettified, + ]); + // On `logEventNum` update, clamp it then switch page if necessary or simply update the URL. useEffect(() => { if (null === mainWorkerRef.current) { @@ -523,10 +550,10 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { }; setUiState(UI_STATE.FAST_LOADING); - loadPageByCursor(mainWorkerRef.current, cursor); + loadPageByCursor(mainWorkerRef.current, cursor, isPrettifiedRef.current); }, [ - numEvents, logEventNum, + numEvents, ]); // On `filePath` update, load file. diff --git a/src/contexts/UrlContextProvider.tsx b/src/contexts/UrlContextProvider.tsx index fe02d6d27..70740b997 100644 --- a/src/contexts/UrlContextProvider.tsx +++ b/src/contexts/UrlContextProvider.tsx @@ -30,6 +30,7 @@ const URL_SEARCH_PARAMS_DEFAULT = Object.freeze({ * Default values of the hash parameters. */ const URL_HASH_PARAMS_DEFAULT = Object.freeze({ + [HASH_PARAM_NAMES.IS_PRETTIFIED]: false, [HASH_PARAM_NAMES.LOG_EVENT_NUM]: null, }); @@ -212,6 +213,11 @@ const getWindowUrlHashParams = () => { parsed; } + const isPrettified = hashParams.get(HASH_PARAM_NAMES.IS_PRETTIFIED); + if (null !== isPrettified) { + urlHashParams[HASH_PARAM_NAMES.IS_PRETTIFIED] = "true" === isPrettified; + } + return urlHashParams; }; diff --git a/src/services/LogFileManager/index.ts b/src/services/LogFileManager/index.ts index b134a12a1..dbef950e1 100644 --- a/src/services/LogFileManager/index.ts +++ b/src/services/LogFileManager/index.ts @@ -1,4 +1,6 @@ /* eslint max-lines: ["error", 500] */ +import jsBeautify from "js-beautify"; + import { Decoder, DecodeResult, @@ -243,11 +245,12 @@ class LogFileManager { * Loads a page of log events based on the provided cursor. * * @param cursor The cursor indicating the page to load. See {@link CursorType}. + * @param isPrettified Are the log messages pretty printed. * @return An object containing the logs as a string, a map of line numbers to log event * numbers, and the line number of the first line in the cursor identified event. * @throws {Error} if any error occurs during decode. */ - loadPage (cursor: CursorType): WorkerResp { + loadPage (cursor: CursorType, isPrettified: boolean): WorkerResp { console.debug(`loadPage: cursor=${JSON.stringify(cursor)}`); const filteredLogEventMap = this.#decoder.getFilteredLogEventMap(); const numActiveEvents: number = filteredLogEventMap ? @@ -284,7 +287,11 @@ class LogFileManager { logEventNum, ] = r; - messages.push(msg); + const printedMsg = (isPrettified) ? + `${jsBeautify(msg)}\n` : + msg; + + messages.push(printedMsg); beginLineNumToLogEventNum.set(currentLine, logEventNum); currentLine += msg.split("\n").length - 1; }); diff --git a/src/services/MainWorker.ts b/src/services/MainWorker.ts index 6e2143a50..4587d8899 100644 --- a/src/services/MainWorker.ts +++ b/src/services/MainWorker.ts @@ -96,7 +96,7 @@ onmessage = async (ev: MessageEvent) => { }); postResp( WORKER_RESP_CODE.PAGE_DATA, - LOG_FILE_MANAGER.loadPage(args.cursor) + LOG_FILE_MANAGER.loadPage(args.cursor, args.isPrettified) ); break; } @@ -106,7 +106,7 @@ onmessage = async (ev: MessageEvent) => { } postResp( WORKER_RESP_CODE.PAGE_DATA, - LOG_FILE_MANAGER.loadPage(args.cursor) + LOG_FILE_MANAGER.loadPage(args.cursor, args.isPrettified) ); break; case WORKER_REQ_CODE.SET_FILTER: @@ -117,7 +117,7 @@ onmessage = async (ev: MessageEvent) => { LOG_FILE_MANAGER.setLogLevelFilter(args.logLevelFilter); postResp( WORKER_RESP_CODE.PAGE_DATA, - LOG_FILE_MANAGER.loadPage(args.cursor) + LOG_FILE_MANAGER.loadPage(args.cursor, args.isPrettified) ); break; case WORKER_REQ_CODE.START_QUERY: diff --git a/src/typings/states.ts b/src/typings/states.ts index 7c1330b3c..9f003c109 100644 --- a/src/typings/states.ts +++ b/src/typings/states.ts @@ -36,6 +36,7 @@ enum UI_ELEMENT { LOG_LEVEL_FILTER, NAVIGATION_BAR, OPEN_FILE_BUTTON, + PRETTIFY_BUTTON, PROGRESS_BAR, QUERY_INPUT_BOX, } @@ -61,6 +62,7 @@ const UI_STATE_GRID: UiStateGrid = Object.freeze({ [UI_ELEMENT.LOG_LEVEL_FILTER]: false, [UI_ELEMENT.NAVIGATION_BAR]: false, [UI_ELEMENT.OPEN_FILE_BUTTON]: true, + [UI_ELEMENT.PRETTIFY_BUTTON]: false, [UI_ELEMENT.PROGRESS_BAR]: false, [UI_ELEMENT.QUERY_INPUT_BOX]: false, }, @@ -71,6 +73,7 @@ const UI_STATE_GRID: UiStateGrid = Object.freeze({ [UI_ELEMENT.LOG_LEVEL_FILTER]: false, [UI_ELEMENT.NAVIGATION_BAR]: false, [UI_ELEMENT.OPEN_FILE_BUTTON]: false, + [UI_ELEMENT.PRETTIFY_BUTTON]: false, [UI_ELEMENT.PROGRESS_BAR]: true, [UI_ELEMENT.QUERY_INPUT_BOX]: false, }, @@ -81,6 +84,7 @@ const UI_STATE_GRID: UiStateGrid = Object.freeze({ [UI_ELEMENT.LOG_LEVEL_FILTER]: true, [UI_ELEMENT.NAVIGATION_BAR]: true, [UI_ELEMENT.OPEN_FILE_BUTTON]: true, + [UI_ELEMENT.PRETTIFY_BUTTON]: true, [UI_ELEMENT.PROGRESS_BAR]: true, [UI_ELEMENT.QUERY_INPUT_BOX]: false, }, @@ -91,6 +95,7 @@ const UI_STATE_GRID: UiStateGrid = Object.freeze({ [UI_ELEMENT.LOG_LEVEL_FILTER]: true, [UI_ELEMENT.NAVIGATION_BAR]: true, [UI_ELEMENT.OPEN_FILE_BUTTON]: true, + [UI_ELEMENT.PRETTIFY_BUTTON]: true, [UI_ELEMENT.PROGRESS_BAR]: false, [UI_ELEMENT.QUERY_INPUT_BOX]: true, }, diff --git a/src/typings/url.ts b/src/typings/url.ts index dbbd9d344..b9e331228 100644 --- a/src/typings/url.ts +++ b/src/typings/url.ts @@ -7,6 +7,7 @@ enum SEARCH_PARAM_NAMES { enum HASH_PARAM_NAMES { LOG_EVENT_NUM = "logEventNum", + IS_PRETTIFIED = "isPrettified", } interface UrlSearchParams { @@ -14,7 +15,8 @@ interface UrlSearchParams { } interface UrlHashParams { - logEventNum: number; + [HASH_PARAM_NAMES.IS_PRETTIFIED]: boolean; + [HASH_PARAM_NAMES.LOG_EVENT_NUM]: number; } type UrlSearchParamUpdatesType = { diff --git a/src/typings/worker.ts b/src/typings/worker.ts index ececcc67a..07bdfff39 100644 --- a/src/typings/worker.ts +++ b/src/typings/worker.ts @@ -102,13 +102,16 @@ type WorkerReqMap = { pageSize: number; cursor: CursorType; decoderOptions: DecoderOptions; + isPrettified: boolean; }; [WORKER_REQ_CODE.LOAD_PAGE]: { cursor: CursorType; + isPrettified: boolean; }; [WORKER_REQ_CODE.SET_FILTER]: { cursor: CursorType; logLevelFilter: LogLevelFilter; + isPrettified: boolean; }; [WORKER_REQ_CODE.START_QUERY]: { queryString: string; diff --git a/src/utils/actions.ts b/src/utils/actions.ts index d331334c2..6df478e29 100644 --- a/src/utils/actions.ts +++ b/src/utils/actions.ts @@ -13,6 +13,7 @@ enum ACTION_NAME { PAGE_BOTTOM = "pageBottom", RELOAD = "reload", COPY_LOG_EVENT = "copyLogEvent", + TOGGLE_PRETTIFY = "togglePrettify", WORD_WRAP = "wordWrap", } @@ -71,6 +72,11 @@ const EDITOR_ACTIONS : EditorAction[] = [ keyBindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyC], label: "Copy Log Event", }, + { + actionName: ACTION_NAME.TOGGLE_PRETTIFY, + keyBindings: [monaco.KeyMod.Alt | monaco.KeyCode.Shift | monaco.KeyCode.KeyF], + label: "Toggle Prettify", + }, { actionName: ACTION_NAME.WORD_WRAP, keyBindings: [monaco.KeyMod.Alt | monaco.KeyCode.KeyZ],