├── .editorconfig ├── .eslintrc.cjs ├── .github ├── FUNDING.yml ├── dependabot.yml └── release.yml ├── .gitignore ├── .storybook ├── main.js └── preview.js ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── settings.team.json ├── AUTHORS.MD ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── docs ├── assets │ ├── Color-YHDXOIA2-C8qt7ors.js │ ├── DocsRenderer-CFRXHY34-D0PF2p3t.js │ ├── LazyLog-DaUssjKS.css │ ├── LazyLog.stories-TvxehuJM.js │ ├── chunk-XP5HYGXS-D5tuasO7.js │ ├── entry-preview-DVEKb9W-.js │ ├── entry-preview-docs-DbTroIC7.js │ ├── iframe-hIgijvFR.js │ ├── index-BcrRsUj6.js │ ├── index-COq-NKRs.js │ ├── index-CXQShRbs.js │ ├── index-Dkaqzkgy.js │ ├── index-DqFIyNb4.js │ ├── index-DrFu-skq.js │ ├── index-KAnq9XD5.js │ ├── preview-B8lJiyuQ.js │ ├── preview-BBWR9nbA.js │ ├── preview-BWzBA1C2.js │ ├── preview-BhZ30iok.js │ ├── preview-C8YAHaTL.js │ ├── preview-CEK_eOjp.js │ ├── preview-CvbIS5ZJ.js │ ├── preview-DD_OYowb.js │ ├── preview-DGUiP6tS.js │ ├── preview-DHQbi4pV.js │ └── react-18-Di6jeGmb.js ├── favicon.svg ├── iframe.html ├── index.html ├── index.json ├── nunito-sans-bold-italic.woff2 ├── nunito-sans-bold.woff2 ├── nunito-sans-italic.woff2 ├── nunito-sans-regular.woff2 ├── project.json ├── sb-addons │ ├── essentials-actions-3 │ │ └── manager-bundle.js │ ├── essentials-backgrounds-5 │ │ └── manager-bundle.js │ ├── essentials-controls-2 │ │ └── manager-bundle.js │ ├── essentials-docs-4 │ │ └── manager-bundle.js │ ├── essentials-measure-8 │ │ └── manager-bundle.js │ ├── essentials-outline-9 │ │ └── manager-bundle.js │ ├── essentials-toolbars-7 │ │ └── manager-bundle.js │ ├── essentials-viewport-6 │ │ └── manager-bundle.js │ ├── interactions-10 │ │ └── manager-bundle.js │ ├── links-1 │ │ └── manager-bundle.js │ └── storybook-core-core-server-presets-0 │ │ └── common-manager-bundle.js ├── sb-common-assets │ ├── favicon.svg │ ├── nunito-sans-bold-italic.woff2 │ ├── nunito-sans-bold.woff2 │ ├── nunito-sans-italic.woff2 │ └── nunito-sans-regular.woff2 ├── sb-manager │ ├── globals-module-info.js │ ├── globals-runtime.js │ ├── globals.js │ └── runtime.js └── vite.svg ├── package-lock.json ├── package.json ├── playwright.config.js ├── public └── vite.svg ├── rollup.config.js ├── src ├── components │ ├── LazyLog │ │ ├── README.md │ │ ├── index.module.css │ │ ├── index.module.css.d.ts │ │ └── index.tsx │ ├── Line │ │ ├── README.md │ │ ├── index.module.css │ │ ├── index.module.css.d.ts │ │ └── index.tsx │ ├── LineContent │ │ ├── README.md │ │ ├── index.module.css │ │ ├── index.module.css.d.ts │ │ └── index.tsx │ ├── LineGutter │ │ ├── README.md │ │ ├── index.module.css │ │ ├── index.module.css.d.ts │ │ └── index.tsx │ ├── LineNumber │ │ ├── README.md │ │ ├── index.module.css │ │ ├── index.module.css.d.ts │ │ └── index.tsx │ ├── LinePart │ │ ├── README.md │ │ ├── index.module.css │ │ ├── index.module.css.d.ts │ │ └── index.tsx │ ├── Loading │ │ ├── README.md │ │ ├── index.module.css │ │ ├── index.module.css.d.ts │ │ └── index.tsx │ ├── ScrollFollow │ │ ├── README.md │ │ └── index.tsx │ ├── SearchBar │ │ ├── ArrowIcons │ │ │ ├── DownArrowIcon │ │ │ │ ├── index.module.css │ │ │ │ ├── index.module.css.d.ts │ │ │ │ └── index.tsx │ │ │ └── UpArrowIcon │ │ │ │ ├── index.module.css │ │ │ │ ├── index.module.css.d.ts │ │ │ │ └── index.tsx │ │ ├── FilterLinesIcon │ │ │ ├── README.md │ │ │ ├── index.module.css │ │ │ ├── index.module.css.d.ts │ │ │ └── index.tsx │ │ ├── README.md │ │ ├── index.module.css │ │ ├── index.module.css.d.ts │ │ └── index.tsx │ ├── Utils │ │ ├── ansiparse.ts │ │ ├── encoding.ts │ │ ├── eventsource.ts │ │ ├── request.ts │ │ ├── search.ts │ │ ├── stream.ts │ │ ├── utils.test.ts │ │ ├── utils.ts │ │ └── websocket.ts │ └── index.ts ├── index.ts └── stories │ ├── LazyLog.stories.tsx │ └── assets │ ├── accessibility.png │ ├── accessibility.svg │ ├── addon-library.png │ ├── assets.png │ ├── avif-test-image.avif │ ├── code-brackets.svg │ ├── colors.svg │ ├── comments.svg │ ├── context.png │ ├── direction.svg │ ├── discord.svg │ ├── docs.png │ ├── figma-plugin.png │ ├── flow.svg │ ├── github.svg │ ├── plugin.svg │ ├── repo.svg │ ├── share.png │ ├── stackalt.svg │ ├── styling.png │ ├── testing.png │ ├── theming.png │ ├── tutorials.svg │ └── youtube.svg ├── tests └── e2e │ ├── eventsource.spec.ts │ ├── test-app.html │ └── test-server.js ├── tsconfig.json └── vite.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:react/jsx-runtime', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended'], 5 | ignorePatterns: ['dist', '.eslintrc.cjs'], 6 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 7 | settings: { react: { version: '18.2' } }, 8 | plugins: ['react-refresh'], 9 | rules: { 10 | 'react/jsx-no-target-blank': 'off', 11 | 'react-refresh/only-export-components': [ 12 | 'warn', 13 | { allowConstantExport: true }, 14 | ], 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [melloware] 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | allow: 8 | - dependency-type: "production" 9 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: Breaking Changes 💥 4 | labels: 5 | - breaking-change 6 | - title: New Features & Enhancements 🎉 7 | labels: 8 | - "new feature" 9 | - enhancement 10 | - title: Accessibility ♿ 11 | labels: 12 | - accessibility 13 | - title: Defects 🐞 14 | labels: 15 | - "bug" 16 | - title: Dependencies 👒 17 | labels: 18 | - dependencies 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Test files 2 | /test-results 3 | /.test-server-port 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | .pnpm-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # Snowpack dependency directory (https://snowpack.dev/) 50 | web_modules/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional stylelint cache 62 | .stylelintcache 63 | 64 | # Microbundle cache 65 | .rpt2_cache/ 66 | .rts2_cache_cjs/ 67 | .rts2_cache_es/ 68 | .rts2_cache_umd/ 69 | 70 | # Optional REPL history 71 | .node_repl_history 72 | 73 | # Output of 'npm pack' 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | .yarn-integrity 78 | 79 | # dotenv environment variable files 80 | .env 81 | .env.development.local 82 | .env.test.local 83 | .env.production.local 84 | .env.local 85 | 86 | # parcel-bundler cache (https://parceljs.org/) 87 | .cache 88 | .parcel-cache 89 | 90 | # Next.js build output 91 | .next 92 | out 93 | 94 | # Nuxt.js build / generate output 95 | .nuxt 96 | dist 97 | storybook-static 98 | 99 | # Gatsby files 100 | .cache/ 101 | # Comment in the public line in if your project uses Gatsby and not Next.js 102 | # https://nextjs.org/blog/next-9-1#public-directory-support 103 | # public 104 | 105 | # vuepress build output 106 | .vuepress/dist 107 | 108 | # vuepress v2.x temp and cache directory 109 | .temp 110 | .cache 111 | 112 | # Docusaurus cache and generated files 113 | .docusaurus 114 | 115 | # Serverless directories 116 | .serverless/ 117 | 118 | # FuseBox cache 119 | .fusebox/ 120 | 121 | # DynamoDB Local files 122 | .dynamodb/ 123 | 124 | # TernJS port file 125 | .tern-port 126 | 127 | # Stores VSCode versions used for testing VSCode extensions 128 | .vscode-test 129 | 130 | # yarn v2 131 | .yarn/cache 132 | .yarn/unplugged 133 | .yarn/build-state.yml 134 | .yarn/install-state.gz 135 | .pnp.* 136 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | /** @type { import('@storybook/react-vite').StorybookConfig } */ 2 | const config = { 3 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], 4 | staticDirs: ["../public"], 5 | 6 | addons: [ 7 | "@storybook/addon-links", 8 | "@storybook/addon-essentials", 9 | "@storybook/addon-interactions", 10 | ], 11 | 12 | framework: { 13 | name: "@storybook/react-vite", 14 | options: {}, 15 | }, 16 | 17 | docs: {} 18 | }; 19 | export default config; 20 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | /** @type { import('@storybook/react').Preview } */ 2 | const preview = { 3 | parameters: { 4 | controls: { 5 | matchers: { 6 | color: /(background|color)$/i, 7 | date: /Date$/, 8 | }, 9 | }, 10 | }, 11 | 12 | tags: ["autodocs"] 13 | }; 14 | 15 | export default preview; 16 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "sibiraj-s.vscode-scss-formatter" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}\\components\\lib\\splitter\\Splitter.spec.js", 15 | "outFiles": [ 16 | "${workspaceFolder}/**/*.js" 17 | ] 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": "explicit", 4 | "source.organizeImports": "explicit" 5 | }, 6 | "editor.defaultFormatter": "esbenp.prettier-vscode", 7 | "[javascript]": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode", 9 | "editor.formatOnSave": true 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode", 13 | "editor.formatOnSave": true 14 | }, 15 | "[typescriptreact]": { 16 | "editor.defaultFormatter": "esbenp.prettier-vscode", 17 | "editor.formatOnSave": true 18 | }, 19 | "[css]": { 20 | "editor.defaultFormatter": "sibiraj-s.vscode-scss-formatter", 21 | "editor.formatOnSave": true 22 | }, 23 | "[scss]": { 24 | "editor.defaultFormatter": "sibiraj-s.vscode-scss-formatter", 25 | "editor.formatOnSave": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.vscode/settings.team.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true, 4 | "source.organizeImports": true 5 | }, 6 | "editor.defaultFormatter": "esbenp.prettier-vscode", 7 | "[javascript]": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode", 9 | "editor.formatOnSave": true 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode", 13 | "editor.formatOnSave": true 14 | }, 15 | "[typescriptreact]": { 16 | "editor.defaultFormatter": "esbenp.prettier-vscode", 17 | "editor.formatOnSave": true 18 | }, 19 | "[css]": { 20 | "editor.defaultFormatter": "sibiraj-s.vscode-scss-formatter", 21 | "editor.formatOnSave": true 22 | }, 23 | "[scss]": { 24 | "editor.defaultFormatter": "sibiraj-s.vscode-scss-formatter", 25 | "editor.formatOnSave": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AUTHORS.MD: -------------------------------------------------------------------------------- 1 | Project Authors 2 | =============== 3 | 4 | This project was originally written by [mozilla-frontend-infra/react-lazylog](https://github.com/mozilla-frontend-infra/react-lazylog) 5 | but has since been forked by Melloware to continue its legacy... 6 | 7 | ## Project technical leads: 8 | 9 | * Melloware [melloware](https://github.com/melloware) - Current Development Lead 10 | 11 | 12 | ## Original Developers: 13 | 14 | * Eli Perelman [eliperelman](https://github.com/eliperelman) - Original Author 15 | * Hassan Ali [helfi92](https://github.com/helfi92) - Original Author 16 | 17 | ## All other contributors and their affiliations: 18 | 19 | * Oliver Coleman 20 | * alexjalkanen-okta-zz 21 | * liorbd 22 | * Brendan Ryan 23 | * Charles Verge 24 | * Shubham Chinda 25 | * Arshad Kazmi 26 | * Arun Kumar Mohan 27 | 28 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | mellowaredev@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://img.shields.io/npm/v/%40melloware%2Freact-logviewer?style=for-the-badge&color=green)](https://www.npmjs.com/package/%40melloware/react-logviewer) 2 | [![NPM Downloads](https://img.shields.io/npm/dm/%40melloware%2Freact-logviewer?style=for-the-badge&color=purple)](https://www.npmjs.com/package/%40melloware/react-logviewer) 3 | [![License: MPL 2.0](https://img.shields.io/badge/License-MPL_2.0-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MPL-2.0) 4 | ![React.js](https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB) 5 | ![Typescript](https://img.shields.io/badge/typescript-%23323330.svg?style=for-the-badge&logo=typescript&logoColor=%23F7DF1E) 6 | 7 | # React LogViewer 8 | 9 | React component that loads and views remote text in the browser lazily and efficiently. Logs can be loaded from static text, a URL, a WebSocket, or an EventSource and including ANSI highlighting. 10 | Originally forked from [mozilla-frontend-infra/react-lazylog](https://github.com/mozilla-frontend-infra/react-lazylog). 11 | 12 | ## React LogViewer - Improved Fork 13 | 14 | This is a fork of [@melloware/react-logviewer](https://github.com/melloware/react-logviewer) that fixes critical issues with EventSource handling. 15 | 16 | ### Why This Fork? 17 | 18 | The original library has a fundamental flaw in how it handles EventSource connections: 19 | 20 | 1. **Fighting Browser Behavior**: EventSource has built-in automatic reconnection after ~3 seconds on network errors. The original library tries to manually control reconnection by closing and recreating EventSource instances, which breaks the browser's native behavior. 21 | 22 | 2. **Lost Event IDs**: When manually recreating EventSource instances, the `Last-Event-ID` header is lost, preventing proper resumption of log streams. 23 | 24 | 3. **Zombie Connections**: The original library was missing an abort handler for EventSource, causing connections to persist even after components unmounted. 25 | 26 | **If you like this project, please consider supporting me ❤️** 27 | 28 | [![GitHub Sponsor](https://img.shields.io/badge/GitHub-FFDD00?style=for-the-badge&logo=github&logoColor=black)](https://github.com/sponsors/melloware) 29 | [![PayPal](https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white)](https://www.paypal.me/mellowareinc) 30 | 31 | ## Features 32 | 33 | - Efficient scrolling performance thanks to [Virtua](https://github.com/inokawa/virtua) 34 | - Infinite scrolling thanks to [Virtua](https://github.com/inokawa/virtua) 35 | - Able to load large files upwards of 100MB without crashing the browser 36 | - Parses, colorizes, and styles ANSI escapes within content 37 | - Supports remote text files as well as chunked/streamed responses 38 | - Line highlighting 39 | - Customizable styling 40 | - Searching through log 41 | - Works in latest browser versions, including iOS Safari and Android Chrome 42 | - TypeScript support 43 | 44 | ## Demo 45 | 46 | You can see a running demonstration at https://melloware.github.io/react-logviewer/ which is built from StoryBook. 47 | 48 | ## Installation 49 | 50 | Install the plugin with npm or yarn: 51 | 52 | ```shell 53 | $ npm i --save @melloware/react-logviewer 54 | ``` 55 | 56 | ## Getting started 57 | 58 | The core component from react-logviewer is `LazyLog`. There is also a higher-order component (HOC) for 59 | following logs until scroll. This module can be required via ES imports, CommonJS require, or UMD. 60 | 61 | ```js 62 | import { LazyLog } from "@melloware/react-logviewer"; 63 | 64 | // using require 65 | const { LazyLog } = require("@melloware/react-logviewer"); 66 | ``` 67 | 68 | ## `` 69 | 70 | ### Usage 71 | 72 | After importing a component, it can be rendered with the required `url` prop: 73 | 74 | ```jsx 75 | import React from "react"; 76 | import { render } from "react-dom"; 77 | import { LazyLog } from "@melloware/react-logviewer"; 78 | 79 | render(, document.getElementById("root")); 80 | ``` 81 | 82 | By default the `LazyLog` will expand to fill its container, so ensure this container has valid dimensions and layout. 83 | If you wish to have fixed dimensions, change the `height` and `width` props. 84 | 85 | If you are going to be rendering a complete file, or an endpoint which can be downloaded all at once, use the 86 | `` component as-is for better overall performance at the expense of slightly longer upfront load time. 87 | 88 | If you are going to be requesting a streaming or chunked response, use the `` component with the 89 | `stream` prop of `true` for quicker upfront rendering as content can be decoded as it arrives. 90 | 91 | ## `` 92 | 93 | `ScrollFollow` is a higher-order component (HOC) that aims to simplify toggling a `LazyLog`'s 94 | "follow" functionality based on user scrolling. 95 | 96 | ### Usage 97 | 98 | The `ScrollFollow` component accepts a render prop function which should return a component to render based on the 99 | function's arguments. 100 | 101 | > [!NOTE] 102 | > ScrollFollow must be wrapped in an element that contains a fixed height such as a `
` 103 | 104 | ```jsx 105 | import React from "react"; 106 | import { render } from "react-dom"; 107 | import { LazyLog, ScrollFollow } from "@melloware/react-logviewer"; 108 | 109 | render( 110 |
111 | ( 114 | 115 | )} 116 | /> 117 |
, 118 | document.getElementById("root"), 119 | ); 120 | ``` 121 | 122 | ## Styling 123 | 124 | All of the components exposed from react-lazylog use CSS modules to contain its styles to each individual component. If 125 | you wish to override these styles, there are a few techniques you can use. 126 | 127 | ### `style` and `containerStyle` 128 | 129 | For the core container of ``, you can pass a `style` object prop to affect many styles. 130 | For affecting the look or behavior of the scrollable region of these components, use the `containerStyle` prop with a 131 | styling object. 132 | 133 | ### `defaultProps.style` 134 | 135 | For many react-logviewer components, continually passing varied styling objects is tedious. To make this simpler, you can 136 | override the `defaultProps.style` of any desired component to override styles of that component. For example: 137 | 138 | ```jsx 139 | import Line from "@melloware/react-logviewer/build/Line"; 140 | 141 | // Use defaultProps.style to set the style for an internal component 142 | Line.defaultProps.style = { 143 | color: "green", 144 | }; 145 | ``` 146 | 147 | **Note: overriding the ANSI colors and styles is currently undocumented, and will probably need some source-diving to 148 | figure out. I would gladly accept a pull request that could improve the styling API.** 149 | 150 | ### CSS stylesheets 151 | 152 | If you are using CSS stylesheets, you can target the main virtual `LazyList` component with the `react-lazylog` 153 | class name. From there you can target the individual `div` lines, `a` line numbers, or `span` line content. 154 | 155 | ## Sub-components 156 | 157 | react-lazylog uses a number of sub-components internally to render individual parts of the log-viewing component: 158 | 159 | ### `` 160 | 161 | A single row of content, containing both the line number and any text content within the line. 162 | 163 | ### `` 164 | 165 | The line number of a single line. The anchor contained within is interactive, and will highlight the entire line upon 166 | selection. 167 | 168 | ### `` 169 | 170 | The container of all the individual pieces of content that is on a single line. May contain one or more `LinePart`s 171 | depending on ANSI parsing. 172 | 173 | ### `` 174 | 175 | An individual segment of text within a line. When the text content is ANSI-parsed, each boundary is placed within its 176 | own `LinePart` and styled separately (colors, text formatting, etc.) from the rest of the line's content. 177 | 178 | ## Technology 179 | 180 | - [Virtua](https://github.com/inokawa/virtua) for efficiently rendering large lines of data 181 | - `fetch` API for efficiently requesting data with array buffers and binary streams 182 | - [ansiparse](https://www.npmjs.com/package/ansiparse) for nice log styling, like Travis 183 | - [mitt](https://www.npmjs.com/package/mitt) for dead-simple events to manage streaming lifecycle 184 | - [Immutable](https://www.npmjs.com/package/immutable) for efficiently storing and managing very large collections of lines and highlight ranges 185 | - `Uint8Array` for dealing with text content as binary, allows for conditionally rendering partial data and decoding everything without crashing your browser 186 | 187 | ## Development and Contributing 188 | 189 | - Fork and clone this repo. 190 | - Install the dependencies with `npm`. 191 | - Start the development server with `npm run storybook`. This will launch a StoryBook instance. 192 | Open a browser to http://localhost:6006 to preview the React components. 193 | - Use CTRL-C to exit the StoryBook. 194 | - Use `npm run build` to generate the compiled component for publishing to npm. 195 | 196 | ## Publishing 197 | 198 | Adjust the version in the `package.json` if necessary, then 199 | 200 | ```bash 201 | npm login 202 | # This will run npm run build automatically 203 | npm publish --access public 204 | ``` 205 | 206 | Then upload code to github, create tag & release. 207 | 208 | ## License 209 | 210 | Licensed under the [Mozilla Public License 2.0](https://opensource.org/license/mpl-2-0/) license. 211 | 212 | `SPDX-License-Identifier: Mozilla Public License 2.0` 213 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only the latest version will be supported for security updates 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please just open a GitHub ticket it will be addressed quickly and new release made 10 | -------------------------------------------------------------------------------- /docs/assets/LazyLog-DaUssjKS.css: -------------------------------------------------------------------------------- 1 | ._bold_127iv_1{font-weight:700}._underline_127iv_3{text-decoration:underline}._italic_127iv_5{font-style:italic}._wrapLine_127iv_9{white-space:normal;text-indent:0;display:inline-block}._noWrapLine_127iv_21{display:ruby}._black_127iv_29{color:#4e4e4e}._red_127iv_31{color:#ff6c60}._green_127iv_33{color:#0a0}._yellow_127iv_35{color:#ffffb6}._blue_127iv_37{color:#96cbfe}._magenta_127iv_39{color:#ff73fd}._cyan_127iv_41{color:#0aa}._white_127iv_43{color:#eee}._grey_127iv_45{color:#969696}._blackBold_127iv_49{color:#7c7c7c}._redBold_127iv_51{color:#ff9b93}._greenBold_127iv_53{color:#ceffab}._yellowBold_127iv_55{color:#ffffcb}._blueBold_127iv_57{color:#b5dcfe}._magentaBold_127iv_59{color:#ff9cfe}._cyanBold_127iv_61{color:#5ff}._whiteBold_127iv_63{color:#fff}._greyBold_127iv_65{color:#969696}._blackBg_127iv_69{background-color:#4e4e4e}._redBg_127iv_71{background-color:#ff6c60}._greenBg_127iv_73{background-color:#0a0}._yellowBg_127iv_75{background-color:#ffffb6}._blueBg_127iv_77{background-color:#96cbfe}._magentaBg_127iv_79{background-color:#ff73fd}._cyanBg_127iv_81{background-color:#0aa}._whiteBg_127iv_83{background-color:#eee}._greyBg_127iv_85{background-color:#969696}._lineContent_17xbk_1{-webkit-user-select:initial;user-select:initial}._lineGutter_i4frd_1{display:inline-flex;overflow:hidden;min-width:20px;padding-right:.5em;color:#a7a7a7;text-decoration:none;-webkit-user-select:none;user-select:none}._lineNumber_jm5lr_1{display:inline-block;width:55px;margin-left:15px;margin-right:15px;color:#7e7e7e;-webkit-user-select:none;user-select:none;text-align:right;min-width:40px;cursor:pointer;text-decoration:none;padding-right:1em;vertical-align:top}._wrapLine_jm5lr_31{min-width:55px}._lineNumber_jm5lr_1:before{content:attr(id)}._lineNumberHighlight_jm5lr_47{color:#fff}._line_1mn7u_1{margin:0;-webkit-user-select:none;user-select:none}._wrapLine_1mn7u_11{display:flex}._line_1mn7u_1:hover{background-color:#444}._lineHighlight_1mn7u_27{background-color:#666}._lineSelectable_1mn7u_35{-webkit-user-select:text;user-select:text}._loading_4hm73_1{position:absolute;top:50%;left:50%;transform:translate(-50%) translateY(-50%)}._downArrowIcon_149cp_1,._upArrowIcon_rmkju_1,._filterLinesIcon_1y5x6_1{height:15px;cursor:pointer}._searchBar_1f73d_1{display:flex;align-items:center;justify-content:flex-end;font-family:Monaco,monospace;font-size:12px;background-color:#222;color:#d6d6d6;padding:10px}._searchInput_1f73d_12{background-color:#464646;color:#d6d6d6;height:20px;min-width:200px;font-size:12px;padding:2px 5px;border:1px solid #4e4e4e;margin-right:10px}._active_1f73d_23{color:#d6d6d6;fill:#d6d6d6}._clickable_1f73d_28:hover{border-radius:5px;background:#666}._inactive_1f73d_33{color:#464646;fill:#464646;padding:3px 3px 1px}._button_1f73d_40{background:none;border:none;margin-right:10px}._lazyLog_1hlpt_1{overflow:auto!important;font-family:Monaco,monospace;font-size:12px;margin:0;white-space:pre;background-color:#222;color:#fff;font-weight:400;will-change:initial;outline:none}._lazyLog_1hlpt_1 span a{color:#d6d6d6}._wrapLine_1hlpt_35{overflow-x:hidden!important}._searchMatch_1hlpt_43{background-color:#ff0;color:#222}._searchMatchHighlighted_1hlpt_53{background-color:#ff10f0;color:#222} 2 | -------------------------------------------------------------------------------- /docs/assets/chunk-XP5HYGXS-D5tuasO7.js: -------------------------------------------------------------------------------- 1 | var u=Object.create,a=Object.defineProperty,s=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,c=Object.getPrototypeOf,O=Object.prototype.hasOwnProperty,l=(e,r)=>function(){return e&&(r=(0,e[o(e)[0]])(e=0)),r},v=(e,r)=>function(){return r||(0,e[o(e)[0]])((r={exports:{}}).exports,r),r.exports},b=(e,r)=>{for(var t in r)a(e,t,{get:r[t],enumerable:!0})},n=(e,r,t,p)=>{if(r&&typeof r=="object"||typeof r=="function")for(let _ of o(r))!O.call(e,_)&&_!==t&&a(e,_,{get:()=>r[_],enumerable:!(p=s(r,_))||p.enumerable});return e},P=(e,r,t)=>(t=e!=null?u(c(e)):{},n(!e||!e.__esModule?a(t,"default",{value:e,enumerable:!0}):t,e)),y=e=>n(a({},"__esModule",{value:!0}),e);export{P as _,v as a,l as b,y as c,b as d}; 2 | -------------------------------------------------------------------------------- /docs/assets/index-Dkaqzkgy.js: -------------------------------------------------------------------------------- 1 | function X(a){return a&&a.__esModule&&Object.prototype.hasOwnProperty.call(a,"default")?a.default:a}var k={exports:{}},r={};/** 2 | * @license React 3 | * react.production.min.js 4 | * 5 | * Copyright (c) Facebook, Inc. and its affiliates. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE file in the root directory of this source tree. 9 | */var D;function Y(){if(D)return r;D=1;var a=Symbol.for("react.element"),V=Symbol.for("react.portal"),A=Symbol.for("react.fragment"),F=Symbol.for("react.strict_mode"),U=Symbol.for("react.profiler"),L=Symbol.for("react.provider"),M=Symbol.for("react.context"),N=Symbol.for("react.forward_ref"),z=Symbol.for("react.suspense"),B=Symbol.for("react.memo"),H=Symbol.for("react.lazy"),w=Symbol.iterator;function W(e){return e===null||typeof e!="object"?null:(e=w&&e[w]||e["@@iterator"],typeof e=="function"?e:null)}var $={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},b=Object.assign,C={};function y(e,t,n){this.props=e,this.context=t,this.refs=C,this.updater=n||$}y.prototype.isReactComponent={},y.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},y.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function j(){}j.prototype=y.prototype;function v(e,t,n){this.props=e,this.context=t,this.refs=C,this.updater=n||$}var m=v.prototype=new j;m.constructor=v,b(m,y.prototype),m.isPureReactComponent=!0;var O=Array.isArray,g=Object.prototype.hasOwnProperty,S={current:null},x={key:!0,ref:!0,__self:!0,__source:!0};function P(e,t,n){var o,u={},i=null,s=null;if(t!=null)for(o in t.ref!==void 0&&(s=t.ref),t.key!==void 0&&(i=""+t.key),t)g.call(t,o)&&!x.hasOwnProperty(o)&&(u[o]=t[o]);var f=arguments.length-2;if(f===1)u.children=n;else if(1{var r;return!!((r=O==null?void 0:O.matchMedia("(prefers-reduced-motion: reduce)"))!=null&&r.matches)},A=r=>{(Array.isArray(r)?r:[r]).forEach(N)},N=r=>{var t;let e=b.getElementById(r);e&&((t=e.parentElement)==null||t.removeChild(e))},F=(r,e)=>{let t=b.getElementById(r);if(t)t.innerHTML!==e&&(t.innerHTML=e);else{let o=b.createElement("style");o.setAttribute("id",r),o.innerHTML=e,b.head.appendChild(o)}},Y=(r,e,t)=>{var a;let o=b.getElementById(r);if(o)o.innerHTML!==e&&(o.innerHTML=e);else{let d=b.createElement("style");d.setAttribute("id",r),d.innerHTML=e;let i=`addon-backgrounds-grid${t?`-docs-${t}`:""}`,n=b.getElementById(i);n?(a=n.parentElement)==null||a.insertBefore(d,n):b.head.appendChild(d)}},W={cellSize:100,cellAmount:10,opacity:.8},w="addon-backgrounds",R="addon-backgrounds-grid",q=D()?"":"transition: background-color 0.3s;",J=(r,e)=>{let{globals:t,parameters:o,viewMode:a,id:d}=e,{options:i=U,disable:n,grid:s=W}=o[p]||{},c=t[p]||{},u=c.value,l=u?i[u]:void 0,$=(l==null?void 0:l.value)||"transparent",f=c.grid||!1,y=!!l&&!n,m=a==="docs"?`#anchor--${d} .docs-story`:".sb-show-main",E=a==="docs"?`#anchor--${d} .docs-story`:".sb-show-main",H=o.layout===void 0||o.layout==="padded",L=a==="docs"?20:H?16:0,{cellAmount:k,cellSize:g,opacity:x,offsetX:v=L,offsetY:S=L}=s,B=a==="docs"?`${w}-docs-${d}`:`${w}-color`,G=a==="docs"?d:null;_(()=>{let M=` 2 | ${m} { 3 | background: ${$} !important; 4 | ${q} 5 | }`;if(!y){A(B);return}Y(B,M,G)},[m,B,G,y,$]);let T=a==="docs"?`${R}-docs-${d}`:`${R}`;return _(()=>{if(!f){A(T);return}let M=[`${g*k}px ${g*k}px`,`${g*k}px ${g*k}px`,`${g}px ${g}px`,`${g}px ${g}px`].join(", "),K=` 6 | ${E} { 7 | background-size: ${M} !important; 8 | background-position: ${v}px ${S}px, ${v}px ${S}px, ${v}px ${S}px, ${v}px ${S}px !important; 9 | background-blend-mode: difference !important; 10 | background-image: linear-gradient(rgba(130, 130, 130, ${x}) 1px, transparent 1px), 11 | linear-gradient(90deg, rgba(130, 130, 130, ${x}) 1px, transparent 1px), 12 | linear-gradient(rgba(130, 130, 130, ${x/2}) 1px, transparent 1px), 13 | linear-gradient(90deg, rgba(130, 130, 130, ${x/2}) 1px, transparent 1px) !important; 14 | } 15 | `;F(T,K)},[k,g,E,T,f,v,S,x]),r()},Q=(r,e=[],t)=>{if(r==="transparent")return"transparent";if(e.find(a=>a.value===r)||r)return r;let o=e.find(a=>a.name===t);if(o)return o.value;if(t){let a=e.map(d=>d.name).join(", ");X.warn(P` 16 | Backgrounds Addon: could not find the default color "${t}". 17 | These are the available colors for your story based on your configuration: 18 | ${a}. 19 | `)}return"transparent"},Z=(r,e)=>{var u;let{globals:t,parameters:o}=e,a=(u=t[p])==null?void 0:u.value,d=o[p],i=h(()=>d.disable?"transparent":Q(a,d.values,d.default),[d,a]),n=h(()=>i&&i!=="transparent",[i]),s=e.viewMode==="docs"?`#anchor--${e.id} .docs-story`:".sb-show-main",c=h(()=>` 20 | ${s} { 21 | background: ${i} !important; 22 | ${D()?"":"transition: background-color 0.3s;"} 23 | } 24 | `,[i,s]);return _(()=>{let l=e.viewMode==="docs"?`addon-backgrounds-docs-${e.id}`:"addon-backgrounds-color";if(!n){A(l);return}Y(l,c,e.viewMode==="docs"?e.id:null)},[n,c,e]),r()},V=(r,e)=>{var y;let{globals:t,parameters:o}=e,a=o[p].grid,d=((y=t[p])==null?void 0:y.grid)===!0&&a.disable!==!0,{cellAmount:i,cellSize:n,opacity:s}=a,c=e.viewMode==="docs",u=o.layout===void 0||o.layout==="padded"?16:0,l=a.offsetX??(c?20:u),$=a.offsetY??(c?20:u),f=h(()=>{let m=e.viewMode==="docs"?`#anchor--${e.id} .docs-story`:".sb-show-main",E=[`${n*i}px ${n*i}px`,`${n*i}px ${n*i}px`,`${n}px ${n}px`,`${n}px ${n}px`].join(", ");return` 25 | ${m} { 26 | background-size: ${E} !important; 27 | background-position: ${l}px ${$}px, ${l}px ${$}px, ${l}px ${$}px, ${l}px ${$}px !important; 28 | background-blend-mode: difference !important; 29 | background-image: linear-gradient(rgba(130, 130, 130, ${s}) 1px, transparent 1px), 30 | linear-gradient(90deg, rgba(130, 130, 130, ${s}) 1px, transparent 1px), 31 | linear-gradient(rgba(130, 130, 130, ${s/2}) 1px, transparent 1px), 32 | linear-gradient(90deg, rgba(130, 130, 130, ${s/2}) 1px, transparent 1px) !important; 33 | } 34 | `},[n]);return _(()=>{let m=e.viewMode==="docs"?`addon-backgrounds-grid-docs-${e.id}`:"addon-backgrounds-grid";if(!d){A(m);return}F(m,f)},[d,f,e]),r()},C,ae=(C=globalThis.FEATURES)!=null&&C.backgroundsStoryGlobals?[J]:[V,Z],I,oe={[p]:{grid:{cellSize:20,opacity:.5,cellAmount:5},disable:!1,...!((I=globalThis.FEATURES)!=null&&I.backgroundsStoryGlobals)&&{values:Object.values(U)}}},ee={[p]:{value:void 0,grid:!1}},z,de=(z=globalThis.FEATURES)!=null&&z.backgroundsStoryGlobals?ee:{[p]:null};export{ae as decorators,de as initialGlobals,oe as parameters}; 35 | -------------------------------------------------------------------------------- /docs/assets/preview-BBWR9nbA.js: -------------------------------------------------------------------------------- 1 | var j="Invariant failed";function S(e,t){if(!e)throw new Error(j)}const{useEffect:T}=__STORYBOOK_MODULE_PREVIEW_API__,{global:d}=__STORYBOOK_MODULE_GLOBAL__;var K="measureEnabled";function Y(){let e=d.document.documentElement,t=Math.max(e.scrollHeight,e.offsetHeight);return{width:Math.max(e.scrollWidth,e.offsetWidth),height:t}}function G(){let e=d.document.createElement("canvas");e.id="storybook-addon-measure";let t=e.getContext("2d");S(t!=null);let{width:o,height:l}=Y();return A(e,t,{width:o,height:l}),e.style.position="absolute",e.style.left="0",e.style.top="0",e.style.zIndex="2147483647",e.style.pointerEvents="none",d.document.body.appendChild(e),{canvas:e,context:t,width:o,height:l}}function A(e,t,{width:o,height:l}){e.style.width=`${o}px`,e.style.height=`${l}px`;let i=d.window.devicePixelRatio;e.width=Math.floor(o*i),e.height=Math.floor(l*i),t.scale(i,i)}var h={};function U(){h.canvas||(h=G())}function H(){h.context&&h.context.clearRect(0,0,h.width??0,h.height??0)}function V(e){H(),e(h.context)}function Z(){S(h.canvas),S(h.context),A(h.canvas,h.context,{width:0,height:0});let{width:e,height:t}=Y();A(h.canvas,h.context,{width:e,height:t}),h.width=e,h.height=t}function J(){var e;h.canvas&&(H(),(e=h.canvas.parentNode)==null||e.removeChild(h.canvas),h={})}var w={margin:"#f6b26b",border:"#ffe599",padding:"#93c47d",content:"#6fa8dc",text:"#232020"},c=6;function W(e,{x:t,y:o,w:l,h:i,r:n}){t=t-l/2,o=o-i/2,l<2*n&&(n=l/2),i<2*n&&(n=i/2),e.beginPath(),e.moveTo(t+n,o),e.arcTo(t+l,o,t+l,o+i,n),e.arcTo(t+l,o+i,t,o+i,n),e.arcTo(t,o+i,t,o,n),e.arcTo(t,o,t+l,o,n),e.closePath()}function Q(e,{padding:t,border:o,width:l,height:i,top:n,left:r}){let f=l-o.left-o.right-t.left-t.right,a=i-t.top-t.bottom-o.top-o.bottom,s=r+o.left+t.left,u=n+o.top+t.top;return e==="top"?s+=f/2:e==="right"?(s+=f,u+=a/2):e==="bottom"?(s+=f/2,u+=a):e==="left"?u+=a/2:e==="center"&&(s+=f/2,u+=a/2),{x:s,y:u}}function x(e,t,{margin:o,border:l,padding:i},n,r){let f=m=>0,a=0,s=0,u=r?1:.5,g=r?n*2:0;return e==="padding"?f=m=>i[m]*u+g:e==="border"?f=m=>i[m]+l[m]*u+g:e==="margin"&&(f=m=>i[m]+l[m]+o[m]*u+g),t==="top"?s=-f("top"):t==="right"?a=f("right"):t==="bottom"?s=f("bottom"):t==="left"&&(a=-f("left")),{offsetX:a,offsetY:s}}function tt(e,t){return Math.abs(e.x-t.x){let f=l&&n.position==="center"?lt(e,t,n):ot(e,t,n,i[r-1],l);i[r]=f})}function nt(e,t,o,l){let i=o.reduce((n,r)=>{var f;return Object.prototype.hasOwnProperty.call(n,r.position)||(n[r.position]=[]),(f=n[r.position])==null||f.push(r),n},{});i.top&&E(e,t,i.top,l),i.right&&E(e,t,i.right,l),i.bottom&&E(e,t,i.bottom,l),i.left&&E(e,t,i.left,l),i.center&&E(e,t,i.center,l)}var L={margin:"#f6b26ba8",border:"#ffe599a8",padding:"#93c47d8c",content:"#6fa8dca8"},B=30;function p(e){return parseInt(e.replace("px",""),10)}function b(e){return Number.isInteger(e)?e:e.toFixed(2)}function P(e){return e.filter(t=>t.text!==0&&t.text!=="0")}function ft(e){let t={top:d.window.scrollY,bottom:d.window.scrollY+d.window.innerHeight,left:d.window.scrollX,right:d.window.scrollX+d.window.innerWidth},o={top:Math.abs(t.top-e.top),bottom:Math.abs(t.bottom-e.bottom),left:Math.abs(t.left-e.left),right:Math.abs(t.right-e.right)};return{x:o.left>o.right?"left":"right",y:o.top>o.bottom?"top":"bottom"}}function rt(e){let t=d.getComputedStyle(e),{top:o,left:l,right:i,bottom:n,width:r,height:f}=e.getBoundingClientRect(),{marginTop:a,marginBottom:s,marginLeft:u,marginRight:g,paddingTop:m,paddingBottom:v,paddingLeft:k,paddingRight:F,borderBottomWidth:I,borderTopWidth:D,borderLeftWidth:$,borderRightWidth:N}=t;o=o+d.window.scrollY,l=l+d.window.scrollX,n=n+d.window.scrollY,i=i+d.window.scrollX;let y={top:p(a),bottom:p(s),left:p(u),right:p(g)},q={top:p(m),bottom:p(v),left:p(k),right:p(F)},z={top:p(D),bottom:p(I),left:p($),right:p(N)},_={top:o-y.top,bottom:n+y.bottom,left:l-y.left,right:i+y.right};return{margin:y,padding:q,border:z,top:o,left:l,bottom:n,right:i,width:r,height:f,extremities:_,floatingAlignment:ft(_)}}function at(e,{margin:t,width:o,height:l,top:i,left:n,bottom:r,right:f}){let a=l+t.bottom+t.top;e.fillStyle=L.margin,e.fillRect(n,i-t.top,o,t.top),e.fillRect(f,i-t.top,t.right,a),e.fillRect(n,r,o,t.bottom),e.fillRect(n-t.left,i-t.top,t.left,a);let s=[{type:"margin",text:b(t.top),position:"top"},{type:"margin",text:b(t.right),position:"right"},{type:"margin",text:b(t.bottom),position:"bottom"},{type:"margin",text:b(t.left),position:"left"}];return P(s)}function st(e,{padding:t,border:o,width:l,height:i,top:n,left:r,bottom:f,right:a}){let s=l-o.left-o.right,u=i-t.top-t.bottom-o.top-o.bottom;e.fillStyle=L.padding,e.fillRect(r+o.left,n+o.top,s,t.top),e.fillRect(a-t.right-o.right,n+t.top+o.top,t.right,u),e.fillRect(r+o.left,f-t.bottom-o.bottom,s,t.bottom),e.fillRect(r+o.left,n+t.top+o.top,t.left,u);let g=[{type:"padding",text:t.top,position:"top"},{type:"padding",text:t.right,position:"right"},{type:"padding",text:t.bottom,position:"bottom"},{type:"padding",text:t.left,position:"left"}];return P(g)}function ht(e,{border:t,width:o,height:l,top:i,left:n,bottom:r,right:f}){let a=l-t.top-t.bottom;e.fillStyle=L.border,e.fillRect(n,i,o,t.top),e.fillRect(n,r-t.bottom,o,t.bottom),e.fillRect(n,i+t.top,t.left,a),e.fillRect(f-t.right,i+t.top,t.right,a);let s=[{type:"border",text:t.top,position:"top"},{type:"border",text:t.right,position:"right"},{type:"border",text:t.bottom,position:"bottom"},{type:"border",text:t.left,position:"left"}];return P(s)}function ut(e,{padding:t,border:o,width:l,height:i,top:n,left:r}){let f=l-o.left-o.right-t.left-t.right,a=i-t.top-t.bottom-o.top-o.bottom;return e.fillStyle=L.content,e.fillRect(r+o.left+t.left,n+o.top+t.top,f,a),[{type:"content",position:"center",text:`${b(f)} x ${b(a)}`}]}function dt(e){return t=>{if(e&&t){let o=rt(e),l=at(t,o),i=st(t,o),n=ht(t,o),r=ut(t,o),f=o.width<=B*3||o.height<=B;nt(t,o,[...r,...i,...n,...l],f)}}}function mt(e){V(dt(e))}var gt=(e,t)=>{let o=d.document.elementFromPoint(e,t),l=i=>{if(i&&i.shadowRoot){let n=i.shadowRoot.elementFromPoint(e,t);return i.isEqualNode(n)?i:n.shadowRoot?l(n):n}return i};return l(o)||o},O,M={x:0,y:0};function R(e,t){O=gt(e,t),mt(O)}var pt=(e,t)=>{let{measureEnabled:o}=t.globals;return T(()=>{let l=i=>{window.requestAnimationFrame(()=>{i.stopPropagation(),M.x=i.clientX,M.y=i.clientY})};return document.addEventListener("pointermove",l),()=>{document.removeEventListener("pointermove",l)}},[]),T(()=>{let l=n=>{window.requestAnimationFrame(()=>{n.stopPropagation(),R(n.clientX,n.clientY)})},i=()=>{window.requestAnimationFrame(()=>{Z()})};return t.viewMode==="story"&&o&&(document.addEventListener("pointerover",l),U(),window.addEventListener("resize",i),R(M.x,M.y)),()=>{window.removeEventListener("resize",i),J()}},[o,t.viewMode]),e()},ct=[pt],wt={[K]:!1};export{ct as decorators,wt as initialGlobals}; 2 | -------------------------------------------------------------------------------- /docs/assets/preview-BWzBA1C2.js: -------------------------------------------------------------------------------- 1 | import{d as $}from"./index-DrFu-skq.js";const{useMemo:x,useEffect:f}=__STORYBOOK_MODULE_PREVIEW_API__,{global:p}=__STORYBOOK_MODULE_GLOBAL__;var m="outline",u=i=>{(Array.isArray(i)?i:[i]).forEach(r)},r=i=>{let t=typeof i=="string"?i:i.join(""),o=p.document.getElementById(t);o&&o.parentElement&&o.parentElement.removeChild(o)},b=(i,t)=>{let o=p.document.getElementById(i);if(o)o.innerHTML!==t&&(o.innerHTML=t);else{let n=p.document.createElement("style");n.setAttribute("id",i),n.innerHTML=t,p.document.head.appendChild(n)}};function s(i){return $` 2 | ${i} body { 3 | outline: 1px solid #2980b9 !important; 4 | } 5 | 6 | ${i} article { 7 | outline: 1px solid #3498db !important; 8 | } 9 | 10 | ${i} nav { 11 | outline: 1px solid #0088c3 !important; 12 | } 13 | 14 | ${i} aside { 15 | outline: 1px solid #33a0ce !important; 16 | } 17 | 18 | ${i} section { 19 | outline: 1px solid #66b8da !important; 20 | } 21 | 22 | ${i} header { 23 | outline: 1px solid #99cfe7 !important; 24 | } 25 | 26 | ${i} footer { 27 | outline: 1px solid #cce7f3 !important; 28 | } 29 | 30 | ${i} h1 { 31 | outline: 1px solid #162544 !important; 32 | } 33 | 34 | ${i} h2 { 35 | outline: 1px solid #314e6e !important; 36 | } 37 | 38 | ${i} h3 { 39 | outline: 1px solid #3e5e85 !important; 40 | } 41 | 42 | ${i} h4 { 43 | outline: 1px solid #449baf !important; 44 | } 45 | 46 | ${i} h5 { 47 | outline: 1px solid #c7d1cb !important; 48 | } 49 | 50 | ${i} h6 { 51 | outline: 1px solid #4371d0 !important; 52 | } 53 | 54 | ${i} main { 55 | outline: 1px solid #2f4f90 !important; 56 | } 57 | 58 | ${i} address { 59 | outline: 1px solid #1a2c51 !important; 60 | } 61 | 62 | ${i} div { 63 | outline: 1px solid #036cdb !important; 64 | } 65 | 66 | ${i} p { 67 | outline: 1px solid #ac050b !important; 68 | } 69 | 70 | ${i} hr { 71 | outline: 1px solid #ff063f !important; 72 | } 73 | 74 | ${i} pre { 75 | outline: 1px solid #850440 !important; 76 | } 77 | 78 | ${i} blockquote { 79 | outline: 1px solid #f1b8e7 !important; 80 | } 81 | 82 | ${i} ol { 83 | outline: 1px solid #ff050c !important; 84 | } 85 | 86 | ${i} ul { 87 | outline: 1px solid #d90416 !important; 88 | } 89 | 90 | ${i} li { 91 | outline: 1px solid #d90416 !important; 92 | } 93 | 94 | ${i} dl { 95 | outline: 1px solid #fd3427 !important; 96 | } 97 | 98 | ${i} dt { 99 | outline: 1px solid #ff0043 !important; 100 | } 101 | 102 | ${i} dd { 103 | outline: 1px solid #e80174 !important; 104 | } 105 | 106 | ${i} figure { 107 | outline: 1px solid #ff00bb !important; 108 | } 109 | 110 | ${i} figcaption { 111 | outline: 1px solid #bf0032 !important; 112 | } 113 | 114 | ${i} table { 115 | outline: 1px solid #00cc99 !important; 116 | } 117 | 118 | ${i} caption { 119 | outline: 1px solid #37ffc4 !important; 120 | } 121 | 122 | ${i} thead { 123 | outline: 1px solid #98daca !important; 124 | } 125 | 126 | ${i} tbody { 127 | outline: 1px solid #64a7a0 !important; 128 | } 129 | 130 | ${i} tfoot { 131 | outline: 1px solid #22746b !important; 132 | } 133 | 134 | ${i} tr { 135 | outline: 1px solid #86c0b2 !important; 136 | } 137 | 138 | ${i} th { 139 | outline: 1px solid #a1e7d6 !important; 140 | } 141 | 142 | ${i} td { 143 | outline: 1px solid #3f5a54 !important; 144 | } 145 | 146 | ${i} col { 147 | outline: 1px solid #6c9a8f !important; 148 | } 149 | 150 | ${i} colgroup { 151 | outline: 1px solid #6c9a9d !important; 152 | } 153 | 154 | ${i} button { 155 | outline: 1px solid #da8301 !important; 156 | } 157 | 158 | ${i} datalist { 159 | outline: 1px solid #c06000 !important; 160 | } 161 | 162 | ${i} fieldset { 163 | outline: 1px solid #d95100 !important; 164 | } 165 | 166 | ${i} form { 167 | outline: 1px solid #d23600 !important; 168 | } 169 | 170 | ${i} input { 171 | outline: 1px solid #fca600 !important; 172 | } 173 | 174 | ${i} keygen { 175 | outline: 1px solid #b31e00 !important; 176 | } 177 | 178 | ${i} label { 179 | outline: 1px solid #ee8900 !important; 180 | } 181 | 182 | ${i} legend { 183 | outline: 1px solid #de6d00 !important; 184 | } 185 | 186 | ${i} meter { 187 | outline: 1px solid #e8630c !important; 188 | } 189 | 190 | ${i} optgroup { 191 | outline: 1px solid #b33600 !important; 192 | } 193 | 194 | ${i} option { 195 | outline: 1px solid #ff8a00 !important; 196 | } 197 | 198 | ${i} output { 199 | outline: 1px solid #ff9619 !important; 200 | } 201 | 202 | ${i} progress { 203 | outline: 1px solid #e57c00 !important; 204 | } 205 | 206 | ${i} select { 207 | outline: 1px solid #e26e0f !important; 208 | } 209 | 210 | ${i} textarea { 211 | outline: 1px solid #cc5400 !important; 212 | } 213 | 214 | ${i} details { 215 | outline: 1px solid #33848f !important; 216 | } 217 | 218 | ${i} summary { 219 | outline: 1px solid #60a1a6 !important; 220 | } 221 | 222 | ${i} command { 223 | outline: 1px solid #438da1 !important; 224 | } 225 | 226 | ${i} menu { 227 | outline: 1px solid #449da6 !important; 228 | } 229 | 230 | ${i} del { 231 | outline: 1px solid #bf0000 !important; 232 | } 233 | 234 | ${i} ins { 235 | outline: 1px solid #400000 !important; 236 | } 237 | 238 | ${i} img { 239 | outline: 1px solid #22746b !important; 240 | } 241 | 242 | ${i} iframe { 243 | outline: 1px solid #64a7a0 !important; 244 | } 245 | 246 | ${i} embed { 247 | outline: 1px solid #98daca !important; 248 | } 249 | 250 | ${i} object { 251 | outline: 1px solid #00cc99 !important; 252 | } 253 | 254 | ${i} param { 255 | outline: 1px solid #37ffc4 !important; 256 | } 257 | 258 | ${i} video { 259 | outline: 1px solid #6ee866 !important; 260 | } 261 | 262 | ${i} audio { 263 | outline: 1px solid #027353 !important; 264 | } 265 | 266 | ${i} source { 267 | outline: 1px solid #012426 !important; 268 | } 269 | 270 | ${i} canvas { 271 | outline: 1px solid #a2f570 !important; 272 | } 273 | 274 | ${i} track { 275 | outline: 1px solid #59a600 !important; 276 | } 277 | 278 | ${i} map { 279 | outline: 1px solid #7be500 !important; 280 | } 281 | 282 | ${i} area { 283 | outline: 1px solid #305900 !important; 284 | } 285 | 286 | ${i} a { 287 | outline: 1px solid #ff62ab !important; 288 | } 289 | 290 | ${i} em { 291 | outline: 1px solid #800b41 !important; 292 | } 293 | 294 | ${i} strong { 295 | outline: 1px solid #ff1583 !important; 296 | } 297 | 298 | ${i} i { 299 | outline: 1px solid #803156 !important; 300 | } 301 | 302 | ${i} b { 303 | outline: 1px solid #cc1169 !important; 304 | } 305 | 306 | ${i} u { 307 | outline: 1px solid #ff0430 !important; 308 | } 309 | 310 | ${i} s { 311 | outline: 1px solid #f805e3 !important; 312 | } 313 | 314 | ${i} small { 315 | outline: 1px solid #d107b2 !important; 316 | } 317 | 318 | ${i} abbr { 319 | outline: 1px solid #4a0263 !important; 320 | } 321 | 322 | ${i} q { 323 | outline: 1px solid #240018 !important; 324 | } 325 | 326 | ${i} cite { 327 | outline: 1px solid #64003c !important; 328 | } 329 | 330 | ${i} dfn { 331 | outline: 1px solid #b4005a !important; 332 | } 333 | 334 | ${i} sub { 335 | outline: 1px solid #dba0c8 !important; 336 | } 337 | 338 | ${i} sup { 339 | outline: 1px solid #cc0256 !important; 340 | } 341 | 342 | ${i} time { 343 | outline: 1px solid #d6606d !important; 344 | } 345 | 346 | ${i} code { 347 | outline: 1px solid #e04251 !important; 348 | } 349 | 350 | ${i} kbd { 351 | outline: 1px solid #5e001f !important; 352 | } 353 | 354 | ${i} samp { 355 | outline: 1px solid #9c0033 !important; 356 | } 357 | 358 | ${i} var { 359 | outline: 1px solid #d90047 !important; 360 | } 361 | 362 | ${i} mark { 363 | outline: 1px solid #ff0053 !important; 364 | } 365 | 366 | ${i} bdi { 367 | outline: 1px solid #bf3668 !important; 368 | } 369 | 370 | ${i} bdo { 371 | outline: 1px solid #6f1400 !important; 372 | } 373 | 374 | ${i} ruby { 375 | outline: 1px solid #ff7b93 !important; 376 | } 377 | 378 | ${i} rt { 379 | outline: 1px solid #ff2f54 !important; 380 | } 381 | 382 | ${i} rp { 383 | outline: 1px solid #803e49 !important; 384 | } 385 | 386 | ${i} span { 387 | outline: 1px solid #cc2643 !important; 388 | } 389 | 390 | ${i} br { 391 | outline: 1px solid #db687d !important; 392 | } 393 | 394 | ${i} wbr { 395 | outline: 1px solid #db175b !important; 396 | }`}var e=(i,t)=>{let{globals:o}=t,n=[!0,"true"].includes(o[m]),d=t.viewMode==="docs",l=x(()=>s(d?'[data-story-block="true"]':".sb-show-main"),[t]);return f(()=>{let a=d?`addon-outline-docs-${t.id}`:"addon-outline";return n?b(a,l):u(a),()=>{u(a)}},[n,l,t]),i()},h=[e],g={[m]:!1};export{h as decorators,g as initialGlobals}; 397 | -------------------------------------------------------------------------------- /docs/assets/preview-BhZ30iok.js: -------------------------------------------------------------------------------- 1 | const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./DocsRenderer-CFRXHY34-D0PF2p3t.js","./iframe-hIgijvFR.js","./index-Dkaqzkgy.js","./index-DqFIyNb4.js","./index-COq-NKRs.js","./index-CXQShRbs.js","./index-DrFu-skq.js","./react-18-Di6jeGmb.js"])))=>i.map(i=>d[i]); 2 | import{_ as a}from"./iframe-hIgijvFR.js";var i=Object.defineProperty,s=(e,r)=>{for(var t in r)i(e,t,{get:r[t],enumerable:!0})},_={};s(_,{parameters:()=>d});var p=Object.entries(globalThis.TAGS_OPTIONS??{}).reduce((e,r)=>{let[t,o]=r;return o.excludeFromDocsStories&&(e[t]=!0),e},{}),d={docs:{renderer:async()=>{let{DocsRenderer:e}=await a(()=>import("./DocsRenderer-CFRXHY34-D0PF2p3t.js").then(r=>r.D),__vite__mapDeps([0,1,2,3,4,5,6,7]),import.meta.url);return new e},stories:{filter:e=>{var r;return(e.tags||[]).filter(t=>p[t]).length===0&&!((r=e.parameters.docs)!=null&&r.disable)}}}};export{d as parameters}; 3 | -------------------------------------------------------------------------------- /docs/assets/preview-C8YAHaTL.js: -------------------------------------------------------------------------------- 1 | const o={parameters:{controls:{matchers:{color:/(background|color)$/i,date:/Date$/}}},tags:["autodocs"]};export{o as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/preview-CEK_eOjp.js: -------------------------------------------------------------------------------- 1 | import{i as r}from"./index-BcrRsUj6.js";var n=r({step:(p,t,e)=>t(e)},{intercept:!0}).step,s={throwPlayFunctionExceptions:!1};export{s as parameters,n as runStep}; 2 | -------------------------------------------------------------------------------- /docs/assets/preview-CvbIS5ZJ.js: -------------------------------------------------------------------------------- 1 | var o="viewport",a={[o]:{value:void 0,isRotated:!1}},t={viewport:"reset",viewportRotated:!1},e,l=(e=globalThis.FEATURES)!=null&&e.viewportStoryGlobals?a:t;export{l as initialGlobals}; 2 | -------------------------------------------------------------------------------- /docs/assets/preview-DD_OYowb.js: -------------------------------------------------------------------------------- 1 | let p;const S=new Uint8Array(16);function A(){if(!p&&(p=typeof crypto<"u"&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!p))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return p(S)}const o=[];for(let e=0;e<256;++e)o.push((e+256).toString(16).slice(1));function D(e,t=0){return o[e[t+0]]+o[e[t+1]]+o[e[t+2]]+o[e[t+3]]+"-"+o[e[t+4]]+o[e[t+5]]+"-"+o[e[t+6]]+o[e[t+7]]+"-"+o[e[t+8]]+o[e[t+9]]+"-"+o[e[t+10]]+o[e[t+11]]+o[e[t+12]]+o[e[t+13]]+o[e[t+14]]+o[e[t+15]]}const I=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),h={randomUUID:I};function f(e,t,r){if(h.randomUUID&&!e)return h.randomUUID();e=e||{};const n=e.random||(e.rng||A)();return n[6]=n[6]&15|64,n[8]=n[8]&63|128,D(n)}const{addons:v}=__STORYBOOK_MODULE_PREVIEW_API__,{ImplicitActionsDuringRendering:U}=__STORYBOOK_MODULE_CORE_EVENTS_PREVIEW_ERRORS__,{global:d}=__STORYBOOK_MODULE_GLOBAL__;var w="storybook/actions",j=`${w}/action-event`,V={depth:10,clearOnStoryChange:!0,limit:50},E=(e,t)=>{let r=Object.getPrototypeOf(e);return!r||t(r)?r:E(r,t)},C=e=>!!(typeof e=="object"&&e&&E(e,t=>/^Synthetic(?:Base)?Event$/.test(t.constructor.name))&&typeof e.persist=="function"),K=e=>{if(C(e)){let t=Object.create(e.constructor.prototype,Object.getOwnPropertyDescriptors(e));t.persist();let r=Object.getOwnPropertyDescriptor(t,"view"),n=r==null?void 0:r.value;return typeof n=="object"&&(n==null?void 0:n.constructor.name)==="Window"&&Object.defineProperty(t,"view",{...r,value:Object.create(n.constructor.prototype)}),t}return e},L=()=>typeof crypto=="object"&&typeof crypto.getRandomValues=="function"?f():Date.now().toString(36)+Math.random().toString(36).substring(2);function _(e,t={}){let r={...V,...t},n=function(...s){var g,O;if(t.implicit){let y=(g="__STORYBOOK_PREVIEW__"in d?d.__STORYBOOK_PREVIEW__:void 0)==null?void 0:g.storyRenders.find(l=>l.phase==="playing"||l.phase==="rendering");if(y){let l=!((O=globalThis==null?void 0:globalThis.FEATURES)!=null&&O.disallowImplicitActionsInRenderV8),R=new U({phase:y.phase,name:e,deprecated:l});if(l)console.warn(R);else throw R}}let i=v.getChannel(),c=L(),a=5,u=s.map(K),b=s.length>1?u:u[0],x={id:c,count:0,data:{name:e,args:b},options:{...r,maxDepth:a+(r.depth||3),allowFunction:r.allowFunction||!1}};i.emit(j,x)};return n.isAction=!0,n.implicit=t.implicit,n}var T=(e,t)=>typeof t[e]>"u"&&!(e in t),B=e=>{let{initialArgs:t,argTypes:r,id:n,parameters:{actions:s}}=e;if(!s||s.disable||!s.argTypesRegex||!r)return{};let i=new RegExp(s.argTypesRegex);return Object.entries(r).filter(([c])=>!!i.test(c)).reduce((c,[a,u])=>(T(a,t)&&(c[a]=_(a,{implicit:!0,id:n})),c),{})},M=e=>{let{initialArgs:t,argTypes:r,parameters:{actions:n}}=e;return n!=null&&n.disable||!r?{}:Object.entries(r).filter(([s,i])=>!!i.action).reduce((s,[i,c])=>(T(i,t)&&(s[i]=_(typeof c.action=="string"?c.action:i)),s),{})},Y=[M,B],m=!1,P=e=>{let{parameters:{actions:t}}=e;if(!(t!=null&&t.disable)&&!m&&"__STORYBOOK_TEST_ON_MOCK_CALL__"in d&&typeof d.__STORYBOOK_TEST_ON_MOCK_CALL__=="function"){let r=d.__STORYBOOK_TEST_ON_MOCK_CALL__;r((n,s)=>{let i=n.getMockName();i!=="spy"&&(!/^next\/.*::/.test(i)||["next/router::useRouter()","next/navigation::useRouter()","next/navigation::redirect","next/cache::","next/headers::cookies().set","next/headers::cookies().delete","next/headers::headers().set","next/headers::headers().delete"].some(c=>i.startsWith(c)))&&_(i)(s)}),m=!0}},N=[P];export{Y as argsEnhancers,N as loaders}; 2 | -------------------------------------------------------------------------------- /docs/assets/preview-DGUiP6tS.js: -------------------------------------------------------------------------------- 1 | const{STORY_CHANGED:r}=__STORYBOOK_MODULE_CORE_EVENTS__,{addons:s}=__STORYBOOK_MODULE_PREVIEW_API__,{global:O}=__STORYBOOK_MODULE_GLOBAL__;var d="storybook/highlight",i="storybookHighlight",g=`${d}/add`,E=`${d}/reset`,{document:l}=O,H=(e="#FF4785",t="dashed")=>` 2 | outline: 2px ${t} ${e}; 3 | outline-offset: 2px; 4 | box-shadow: 0 0 0 6px rgba(255,255,255,0.6); 5 | `,h=s.getChannel(),T=e=>{let t=i;n();let o=Array.from(new Set(e.elements)),_=l.createElement("style");_.setAttribute("id",t),_.innerHTML=o.map(a=>`${a}{ 6 | ${H(e.color,e.style)} 7 | }`).join(" "),l.head.appendChild(_)},n=()=>{var o;let e=i,t=l.getElementById(e);t&&((o=t.parentNode)==null||o.removeChild(t))};h.on(r,n);h.on(E,n);h.on(g,T); 8 | -------------------------------------------------------------------------------- /docs/assets/preview-DHQbi4pV.js: -------------------------------------------------------------------------------- 1 | const{makeDecorator:O,addons:_}=__STORYBOOK_MODULE_PREVIEW_API__,{STORY_CHANGED:l,SELECT_STORY:E}=__STORYBOOK_MODULE_CORE_EVENTS__,{global:L}=__STORYBOOK_MODULE_GLOBAL__;var c="links",{document:s,HTMLElement:v}=L,d=e=>_.getChannel().emit(E,e),i=e=>{let{target:t}=e;if(!(t instanceof v))return;let o=t,{sbKind:a,sbStory:r}=o.dataset;(a||r)&&(e.preventDefault(),d({kind:a,story:r}))},n=!1,m=()=>{n||(n=!0,s.addEventListener("click",i))},k=()=>{n&&(n=!1,s.removeEventListener("click",i))},R=O({name:"withLinks",parameterName:c,wrapper:(e,t)=>(m(),_.getChannel().once(l,k),e(t))}),S=[R];export{S as decorators}; 2 | -------------------------------------------------------------------------------- /docs/assets/react-18-Di6jeGmb.js: -------------------------------------------------------------------------------- 1 | import{r as i}from"./index-Dkaqzkgy.js";import{r as m}from"./index-COq-NKRs.js";var n={},u;function c(){if(u)return n;u=1;var e=m();return n.createRoot=e.createRoot,n.hydrateRoot=e.hydrateRoot,n}var R=c(),s=new Map;function v(){return globalThis.IS_REACT_ACT_ENVIRONMENT}var f=({callback:e,children:t})=>{let r=i.useRef();return i.useLayoutEffect(()=>{r.current!==e&&(r.current=e,e())},[e]),t};typeof Promise.withResolvers>"u"&&(Promise.withResolvers=()=>{let e=null,t=null;return{promise:new Promise((r,o)=>{e=r,t=o}),resolve:e,reject:t}});var d=async(e,t,r)=>{let o=await p(t,r);if(v()){o.render(e);return}let{promise:a,resolve:l}=Promise.withResolvers();return o.render(i.createElement(f,{callback:l},e)),a},w=(e,t)=>{let r=s.get(e);r&&(r.unmount(),s.delete(e))},p=async(e,t)=>{let r=s.get(e);return r||(r=R.createRoot(e,t),s.set(e,r)),r};export{d as renderElement,w as unmountElement}; 2 | -------------------------------------------------------------------------------- /docs/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @storybook/core - Storybook 7 | 8 | 9 | 10 | 11 | 12 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 77 | 78 | 79 | 80 | 81 |
82 | 83 | 84 | 145 | 146 | 147 | 176 | 177 | -------------------------------------------------------------------------------- /docs/index.json: -------------------------------------------------------------------------------- 1 | {"v":5,"entries":{"stories-lazylog--docs":{"id":"stories-lazylog--docs","title":"stories/LazyLog","name":"Docs","importPath":"./src/stories/LazyLog.stories.tsx","type":"docs","tags":["dev","test","autodocs"],"storiesImports":[]},"stories-lazylog--text-log":{"type":"story","id":"stories-lazylog--text-log","name":"Text Based","title":"stories/LazyLog","importPath":"./src/stories/LazyLog.stories.tsx","componentPath":"./src/components","tags":["dev","test","autodocs"]},"stories-lazylog--url-log":{"type":"story","id":"stories-lazylog--url-log","name":"URL Based","title":"stories/LazyLog","importPath":"./src/stories/LazyLog.stories.tsx","componentPath":"./src/components","tags":["dev","test","autodocs"]},"stories-lazylog--external-mode":{"type":"story","id":"stories-lazylog--external-mode","name":"External Mode","title":"stories/LazyLog","importPath":"./src/stories/LazyLog.stories.tsx","componentPath":"./src/components","tags":["dev","test","autodocs"]},"stories-lazylog--clear-method":{"type":"story","id":"stories-lazylog--clear-method","name":"Clear Method","title":"stories/LazyLog","importPath":"./src/stories/LazyLog.stories.tsx","componentPath":"./src/components","tags":["dev","test","autodocs"]},"stories-lazylog--click-events":{"type":"story","id":"stories-lazylog--click-events","name":"Click Events","title":"stories/LazyLog","importPath":"./src/stories/LazyLog.stories.tsx","componentPath":"./src/components","tags":["dev","test","autodocs"]},"stories-lazylog--html-links":{"type":"story","id":"stories-lazylog--html-links","name":"Html Links","title":"stories/LazyLog","importPath":"./src/stories/LazyLog.stories.tsx","componentPath":"./src/components","tags":["dev","test","autodocs"]},"stories-lazylog--append-lines":{"type":"story","id":"stories-lazylog--append-lines","name":"Append Lines","title":"stories/LazyLog","importPath":"./src/stories/LazyLog.stories.tsx","componentPath":"./src/components","tags":["dev","test","autodocs"]},"stories-lazylog--highlight-and-scrolling":{"type":"story","id":"stories-lazylog--highlight-and-scrolling","name":"Highlight And Scrolling","title":"stories/LazyLog","importPath":"./src/stories/LazyLog.stories.tsx","componentPath":"./src/components","tags":["dev","test","autodocs"]},"stories-lazylog--line-wrapping":{"type":"story","id":"stories-lazylog--line-wrapping","name":"Line Wrapping","title":"stories/LazyLog","importPath":"./src/stories/LazyLog.stories.tsx","componentPath":"./src/components","tags":["dev","test","autodocs"]},"stories-lazylog--infinite-scrolling":{"type":"story","id":"stories-lazylog--infinite-scrolling","name":"Infinite Scrolling","title":"stories/LazyLog","importPath":"./src/stories/LazyLog.stories.tsx","componentPath":"./src/components","tags":["dev","test","autodocs"]}}} -------------------------------------------------------------------------------- /docs/nunito-sans-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/docs/nunito-sans-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/nunito-sans-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/docs/nunito-sans-bold.woff2 -------------------------------------------------------------------------------- /docs/nunito-sans-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/docs/nunito-sans-italic.woff2 -------------------------------------------------------------------------------- /docs/nunito-sans-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/docs/nunito-sans-regular.woff2 -------------------------------------------------------------------------------- /docs/project.json: -------------------------------------------------------------------------------- 1 | {"generatedAt":1741866381324,"hasCustomBabel":false,"hasCustomWebpack":false,"hasStaticDirs":true,"hasStorybookEslint":true,"refCount":0,"testPackages":{"msw":"2.7.3","msw-storybook-addon":"2.0.4"},"hasRouterPackage":false,"packageManager":{"type":"npm","version":"10.9.0"},"preview":{"usesGlobals":false},"framework":{"name":"@storybook/react-vite","options":{}},"builder":"@storybook/builder-vite","renderer":"@storybook/react","portableStoriesFileCount":3,"applicationFileCount":0,"storybookVersion":"8.6.4","storybookVersionSpecifier":"^8.6.4","language":"javascript","storybookPackages":{"@storybook/blocks":{"version":"8.6.4"},"@storybook/react":{"version":"8.6.4"},"@storybook/react-vite":{"version":"8.6.4"},"@storybook/test":{"version":"8.6.4"},"eslint-plugin-storybook":{"version":"0.11.4"},"msw-storybook-addon":{"version":"2.0.4"},"storybook":{"version":"8.6.4"}},"addons":{"@storybook/addon-links":{"version":"8.6.4"},"@storybook/addon-essentials":{"version":"8.6.4"},"@storybook/addon-interactions":{"version":"8.6.4"}}} -------------------------------------------------------------------------------- /docs/sb-addons/essentials-measure-8/manager-bundle.js: -------------------------------------------------------------------------------- 1 | try{ 2 | (()=>{var t=__REACT__,{Children:O,Component:f,Fragment:R,Profiler:P,PureComponent:w,StrictMode:L,Suspense:E,__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:D,cloneElement:M,createContext:v,createElement:x,createFactory:H,createRef:U,forwardRef:F,isValidElement:N,lazy:G,memo:W,startTransition:K,unstable_act:Y,useCallback:u,useContext:q,useDebugValue:V,useDeferredValue:Z,useEffect:d,useId:z,useImperativeHandle:J,useInsertionEffect:Q,useLayoutEffect:X,useMemo:$,useReducer:j,useRef:oo,useState:no,useSyncExternalStore:eo,useTransition:co,version:to}=__REACT__;var io=__STORYBOOK_API__,{ActiveTabs:so,Consumer:uo,ManagerContext:mo,Provider:po,RequestResponseError:So,addons:l,combineParameters:Co,controlOrMetaKey:ho,controlOrMetaSymbol:Ao,eventMatchesShortcut:_o,eventToShortcut:bo,experimental_MockUniversalStore:To,experimental_UniversalStore:go,experimental_requestResponse:yo,experimental_useUniversalStore:Bo,isMacLike:ko,isShortcutTaken:Oo,keyToSymbol:fo,merge:Ro,mockChannel:Po,optionOrAltSymbol:wo,shortcutMatchesShortcut:Lo,shortcutToHumanString:Eo,types:m,useAddonState:Do,useArgTypes:Mo,useArgs:vo,useChannel:xo,useGlobalTypes:Ho,useGlobals:p,useParameter:Uo,useSharedState:Fo,useStoryPrepared:No,useStorybookApi:S,useStorybookState:Go}=__STORYBOOK_API__;var Vo=__STORYBOOK_COMPONENTS__,{A:Zo,ActionBar:zo,AddonPanel:Jo,Badge:Qo,Bar:Xo,Blockquote:$o,Button:jo,ClipboardCode:on,Code:nn,DL:en,Div:cn,DocumentWrapper:tn,EmptyTabContent:rn,ErrorFormatter:In,FlexBar:an,Form:ln,H1:sn,H2:un,H3:dn,H4:mn,H5:pn,H6:Sn,HR:Cn,IconButton:C,IconButtonSkeleton:hn,Icons:An,Img:_n,LI:bn,Link:Tn,ListItem:gn,Loader:yn,Modal:Bn,OL:kn,P:On,Placeholder:fn,Pre:Rn,ProgressSpinner:Pn,ResetWrapper:wn,ScrollArea:Ln,Separator:En,Spaced:Dn,Span:Mn,StorybookIcon:vn,StorybookLogo:xn,Symbols:Hn,SyntaxHighlighter:Un,TT:Fn,TabBar:Nn,TabButton:Gn,TabWrapper:Wn,Table:Kn,Tabs:Yn,TabsState:qn,TooltipLinkList:Vn,TooltipMessage:Zn,TooltipNote:zn,UL:Jn,WithTooltip:Qn,WithTooltipPure:Xn,Zoom:$n,codeCommon:jn,components:oe,createCopyToClipboardFunction:ne,getStoryHref:ee,icons:ce,interleaveSeparators:te,nameSpaceClassNames:re,resetComponents:Ie,withReset:ae}=__STORYBOOK_COMPONENTS__;var de=__STORYBOOK_ICONS__,{AccessibilityAltIcon:me,AccessibilityIcon:pe,AddIcon:Se,AdminIcon:Ce,AlertAltIcon:he,AlertIcon:Ae,AlignLeftIcon:_e,AlignRightIcon:be,AppleIcon:Te,ArrowBottomLeftIcon:ge,ArrowBottomRightIcon:ye,ArrowDownIcon:Be,ArrowLeftIcon:ke,ArrowRightIcon:Oe,ArrowSolidDownIcon:fe,ArrowSolidLeftIcon:Re,ArrowSolidRightIcon:Pe,ArrowSolidUpIcon:we,ArrowTopLeftIcon:Le,ArrowTopRightIcon:Ee,ArrowUpIcon:De,AzureDevOpsIcon:Me,BackIcon:ve,BasketIcon:xe,BatchAcceptIcon:He,BatchDenyIcon:Ue,BeakerIcon:Fe,BellIcon:Ne,BitbucketIcon:Ge,BoldIcon:We,BookIcon:Ke,BookmarkHollowIcon:Ye,BookmarkIcon:qe,BottomBarIcon:Ve,BottomBarToggleIcon:Ze,BoxIcon:ze,BranchIcon:Je,BrowserIcon:Qe,ButtonIcon:Xe,CPUIcon:$e,CalendarIcon:je,CameraIcon:oc,CategoryIcon:nc,CertificateIcon:ec,ChangedIcon:cc,ChatIcon:tc,CheckIcon:rc,ChevronDownIcon:Ic,ChevronLeftIcon:ac,ChevronRightIcon:lc,ChevronSmallDownIcon:ic,ChevronSmallLeftIcon:sc,ChevronSmallRightIcon:uc,ChevronSmallUpIcon:dc,ChevronUpIcon:mc,ChromaticIcon:pc,ChromeIcon:Sc,CircleHollowIcon:Cc,CircleIcon:hc,ClearIcon:Ac,CloseAltIcon:_c,CloseIcon:bc,CloudHollowIcon:Tc,CloudIcon:gc,CogIcon:yc,CollapseIcon:Bc,CommandIcon:kc,CommentAddIcon:Oc,CommentIcon:fc,CommentsIcon:Rc,CommitIcon:Pc,CompassIcon:wc,ComponentDrivenIcon:Lc,ComponentIcon:Ec,ContrastIcon:Dc,ControlsIcon:Mc,CopyIcon:vc,CreditIcon:xc,CrossIcon:Hc,DashboardIcon:Uc,DatabaseIcon:Fc,DeleteIcon:Nc,DiamondIcon:Gc,DirectionIcon:Wc,DiscordIcon:Kc,DocChartIcon:Yc,DocListIcon:qc,DocumentIcon:Vc,DownloadIcon:Zc,DragIcon:zc,EditIcon:Jc,EllipsisIcon:Qc,EmailIcon:Xc,ExpandAltIcon:$c,ExpandIcon:jc,EyeCloseIcon:ot,EyeIcon:nt,FaceHappyIcon:et,FaceNeutralIcon:ct,FaceSadIcon:tt,FacebookIcon:rt,FailedIcon:It,FastForwardIcon:at,FigmaIcon:lt,FilterIcon:it,FlagIcon:st,FolderIcon:ut,FormIcon:dt,GDriveIcon:mt,GithubIcon:pt,GitlabIcon:St,GlobeIcon:Ct,GoogleIcon:ht,GraphBarIcon:At,GraphLineIcon:_t,GraphqlIcon:bt,GridAltIcon:Tt,GridIcon:gt,GrowIcon:yt,HeartHollowIcon:Bt,HeartIcon:kt,HomeIcon:Ot,HourglassIcon:ft,InfoIcon:Rt,ItalicIcon:Pt,JumpToIcon:wt,KeyIcon:Lt,LightningIcon:Et,LightningOffIcon:Dt,LinkBrokenIcon:Mt,LinkIcon:vt,LinkedinIcon:xt,LinuxIcon:Ht,ListOrderedIcon:Ut,ListUnorderedIcon:Ft,LocationIcon:Nt,LockIcon:Gt,MarkdownIcon:Wt,MarkupIcon:Kt,MediumIcon:Yt,MemoryIcon:qt,MenuIcon:Vt,MergeIcon:Zt,MirrorIcon:zt,MobileIcon:Jt,MoonIcon:Qt,NutIcon:Xt,OutboxIcon:$t,OutlineIcon:jt,PaintBrushIcon:or,PaperClipIcon:nr,ParagraphIcon:er,PassedIcon:cr,PhoneIcon:tr,PhotoDragIcon:rr,PhotoIcon:Ir,PinAltIcon:ar,PinIcon:lr,PlayAllHollowIcon:ir,PlayBackIcon:sr,PlayHollowIcon:ur,PlayIcon:dr,PlayNextIcon:mr,PlusIcon:pr,PointerDefaultIcon:Sr,PointerHandIcon:Cr,PowerIcon:hr,PrintIcon:Ar,ProceedIcon:_r,ProfileIcon:br,PullRequestIcon:Tr,QuestionIcon:gr,RSSIcon:yr,RedirectIcon:Br,ReduxIcon:kr,RefreshIcon:Or,ReplyIcon:fr,RepoIcon:Rr,RequestChangeIcon:Pr,RewindIcon:wr,RulerIcon:h,SaveIcon:Lr,SearchIcon:Er,ShareAltIcon:Dr,ShareIcon:Mr,ShieldIcon:vr,SideBySideIcon:xr,SidebarAltIcon:Hr,SidebarAltToggleIcon:Ur,SidebarIcon:Fr,SidebarToggleIcon:Nr,SpeakerIcon:Gr,StackedIcon:Wr,StarHollowIcon:Kr,StarIcon:Yr,StatusFailIcon:qr,StatusPassIcon:Vr,StatusWarnIcon:Zr,StickerIcon:zr,StopAltHollowIcon:Jr,StopAltIcon:Qr,StopIcon:Xr,StorybookIcon:$r,StructureIcon:jr,SubtractIcon:oI,SunIcon:nI,SupportIcon:eI,SwitchAltIcon:cI,SyncIcon:tI,TabletIcon:rI,ThumbsUpIcon:II,TimeIcon:aI,TimerIcon:lI,TransferIcon:iI,TrashIcon:sI,TwitterIcon:uI,TypeIcon:dI,UbuntuIcon:mI,UndoIcon:pI,UnfoldIcon:SI,UnlockIcon:CI,UnpinIcon:hI,UploadIcon:AI,UserAddIcon:_I,UserAltIcon:bI,UserIcon:TI,UsersIcon:gI,VSCodeIcon:yI,VerifiedIcon:BI,VideoIcon:kI,WandIcon:OI,WatchIcon:fI,WindowsIcon:RI,WrenchIcon:PI,XIcon:wI,YoutubeIcon:LI,ZoomIcon:EI,ZoomOutIcon:DI,ZoomResetIcon:MI,iconList:vI}=__STORYBOOK_ICONS__;var i="storybook/measure-addon",A=`${i}/tool`,_=()=>{let[r,c]=p(),{measureEnabled:I}=r,s=S(),a=u(()=>c({measureEnabled:!I}),[c,I]);return d(()=>{s.setAddonShortcut(i,{label:"Toggle Measure [M]",defaultShortcut:["M"],actionName:"measure",showInMenu:!1,action:a})},[a,s]),t.createElement(C,{key:A,active:I,title:"Enable measure",onClick:a},t.createElement(h,null))};l.register(i,()=>{l.add(A,{type:m.TOOL,title:"Measure",match:({viewMode:r,tabId:c})=>r==="story"&&!c,render:()=>t.createElement(_,null)})});})(); 3 | }catch(e){ console.error("[Storybook] One of your manager-entries failed: " + import.meta.url, e); } 4 | -------------------------------------------------------------------------------- /docs/sb-addons/essentials-outline-9/manager-bundle.js: -------------------------------------------------------------------------------- 1 | try{ 2 | (()=>{var t=__REACT__,{Children:k,Component:R,Fragment:P,Profiler:w,PureComponent:L,StrictMode:E,Suspense:D,__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:v,cloneElement:x,createContext:H,createElement:M,createFactory:U,createRef:F,forwardRef:N,isValidElement:G,lazy:W,memo:u,startTransition:K,unstable_act:Y,useCallback:d,useContext:q,useDebugValue:V,useDeferredValue:Z,useEffect:p,useId:z,useImperativeHandle:J,useInsertionEffect:Q,useLayoutEffect:X,useMemo:$,useReducer:j,useRef:oo,useState:no,useSyncExternalStore:eo,useTransition:co,version:to}=__REACT__;var io=__STORYBOOK_API__,{ActiveTabs:so,Consumer:uo,ManagerContext:po,Provider:mo,RequestResponseError:So,addons:l,combineParameters:Co,controlOrMetaKey:ho,controlOrMetaSymbol:Ao,eventMatchesShortcut:_o,eventToShortcut:To,experimental_MockUniversalStore:bo,experimental_UniversalStore:go,experimental_requestResponse:yo,experimental_useUniversalStore:Oo,isMacLike:Bo,isShortcutTaken:fo,keyToSymbol:ko,merge:Ro,mockChannel:Po,optionOrAltSymbol:wo,shortcutMatchesShortcut:Lo,shortcutToHumanString:Eo,types:m,useAddonState:Do,useArgTypes:vo,useArgs:xo,useChannel:Ho,useGlobalTypes:Mo,useGlobals:S,useParameter:Uo,useSharedState:Fo,useStoryPrepared:No,useStorybookApi:C,useStorybookState:Go}=__STORYBOOK_API__;var Vo=__STORYBOOK_COMPONENTS__,{A:Zo,ActionBar:zo,AddonPanel:Jo,Badge:Qo,Bar:Xo,Blockquote:$o,Button:jo,ClipboardCode:on,Code:nn,DL:en,Div:cn,DocumentWrapper:tn,EmptyTabContent:rn,ErrorFormatter:In,FlexBar:an,Form:ln,H1:sn,H2:un,H3:dn,H4:pn,H5:mn,H6:Sn,HR:Cn,IconButton:h,IconButtonSkeleton:hn,Icons:An,Img:_n,LI:Tn,Link:bn,ListItem:gn,Loader:yn,Modal:On,OL:Bn,P:fn,Placeholder:kn,Pre:Rn,ProgressSpinner:Pn,ResetWrapper:wn,ScrollArea:Ln,Separator:En,Spaced:Dn,Span:vn,StorybookIcon:xn,StorybookLogo:Hn,Symbols:Mn,SyntaxHighlighter:Un,TT:Fn,TabBar:Nn,TabButton:Gn,TabWrapper:Wn,Table:Kn,Tabs:Yn,TabsState:qn,TooltipLinkList:Vn,TooltipMessage:Zn,TooltipNote:zn,UL:Jn,WithTooltip:Qn,WithTooltipPure:Xn,Zoom:$n,codeCommon:jn,components:oe,createCopyToClipboardFunction:ne,getStoryHref:ee,icons:ce,interleaveSeparators:te,nameSpaceClassNames:re,resetComponents:Ie,withReset:ae}=__STORYBOOK_COMPONENTS__;var de=__STORYBOOK_ICONS__,{AccessibilityAltIcon:pe,AccessibilityIcon:me,AddIcon:Se,AdminIcon:Ce,AlertAltIcon:he,AlertIcon:Ae,AlignLeftIcon:_e,AlignRightIcon:Te,AppleIcon:be,ArrowBottomLeftIcon:ge,ArrowBottomRightIcon:ye,ArrowDownIcon:Oe,ArrowLeftIcon:Be,ArrowRightIcon:fe,ArrowSolidDownIcon:ke,ArrowSolidLeftIcon:Re,ArrowSolidRightIcon:Pe,ArrowSolidUpIcon:we,ArrowTopLeftIcon:Le,ArrowTopRightIcon:Ee,ArrowUpIcon:De,AzureDevOpsIcon:ve,BackIcon:xe,BasketIcon:He,BatchAcceptIcon:Me,BatchDenyIcon:Ue,BeakerIcon:Fe,BellIcon:Ne,BitbucketIcon:Ge,BoldIcon:We,BookIcon:Ke,BookmarkHollowIcon:Ye,BookmarkIcon:qe,BottomBarIcon:Ve,BottomBarToggleIcon:Ze,BoxIcon:ze,BranchIcon:Je,BrowserIcon:Qe,ButtonIcon:Xe,CPUIcon:$e,CalendarIcon:je,CameraIcon:oc,CategoryIcon:nc,CertificateIcon:ec,ChangedIcon:cc,ChatIcon:tc,CheckIcon:rc,ChevronDownIcon:Ic,ChevronLeftIcon:ac,ChevronRightIcon:lc,ChevronSmallDownIcon:ic,ChevronSmallLeftIcon:sc,ChevronSmallRightIcon:uc,ChevronSmallUpIcon:dc,ChevronUpIcon:pc,ChromaticIcon:mc,ChromeIcon:Sc,CircleHollowIcon:Cc,CircleIcon:hc,ClearIcon:Ac,CloseAltIcon:_c,CloseIcon:Tc,CloudHollowIcon:bc,CloudIcon:gc,CogIcon:yc,CollapseIcon:Oc,CommandIcon:Bc,CommentAddIcon:fc,CommentIcon:kc,CommentsIcon:Rc,CommitIcon:Pc,CompassIcon:wc,ComponentDrivenIcon:Lc,ComponentIcon:Ec,ContrastIcon:Dc,ControlsIcon:vc,CopyIcon:xc,CreditIcon:Hc,CrossIcon:Mc,DashboardIcon:Uc,DatabaseIcon:Fc,DeleteIcon:Nc,DiamondIcon:Gc,DirectionIcon:Wc,DiscordIcon:Kc,DocChartIcon:Yc,DocListIcon:qc,DocumentIcon:Vc,DownloadIcon:Zc,DragIcon:zc,EditIcon:Jc,EllipsisIcon:Qc,EmailIcon:Xc,ExpandAltIcon:$c,ExpandIcon:jc,EyeCloseIcon:ot,EyeIcon:nt,FaceHappyIcon:et,FaceNeutralIcon:ct,FaceSadIcon:tt,FacebookIcon:rt,FailedIcon:It,FastForwardIcon:at,FigmaIcon:lt,FilterIcon:it,FlagIcon:st,FolderIcon:ut,FormIcon:dt,GDriveIcon:pt,GithubIcon:mt,GitlabIcon:St,GlobeIcon:Ct,GoogleIcon:ht,GraphBarIcon:At,GraphLineIcon:_t,GraphqlIcon:Tt,GridAltIcon:bt,GridIcon:gt,GrowIcon:yt,HeartHollowIcon:Ot,HeartIcon:Bt,HomeIcon:ft,HourglassIcon:kt,InfoIcon:Rt,ItalicIcon:Pt,JumpToIcon:wt,KeyIcon:Lt,LightningIcon:Et,LightningOffIcon:Dt,LinkBrokenIcon:vt,LinkIcon:xt,LinkedinIcon:Ht,LinuxIcon:Mt,ListOrderedIcon:Ut,ListUnorderedIcon:Ft,LocationIcon:Nt,LockIcon:Gt,MarkdownIcon:Wt,MarkupIcon:Kt,MediumIcon:Yt,MemoryIcon:qt,MenuIcon:Vt,MergeIcon:Zt,MirrorIcon:zt,MobileIcon:Jt,MoonIcon:Qt,NutIcon:Xt,OutboxIcon:$t,OutlineIcon:A,PaintBrushIcon:jt,PaperClipIcon:or,ParagraphIcon:nr,PassedIcon:er,PhoneIcon:cr,PhotoDragIcon:tr,PhotoIcon:rr,PinAltIcon:Ir,PinIcon:ar,PlayAllHollowIcon:lr,PlayBackIcon:ir,PlayHollowIcon:sr,PlayIcon:ur,PlayNextIcon:dr,PlusIcon:pr,PointerDefaultIcon:mr,PointerHandIcon:Sr,PowerIcon:Cr,PrintIcon:hr,ProceedIcon:Ar,ProfileIcon:_r,PullRequestIcon:Tr,QuestionIcon:br,RSSIcon:gr,RedirectIcon:yr,ReduxIcon:Or,RefreshIcon:Br,ReplyIcon:fr,RepoIcon:kr,RequestChangeIcon:Rr,RewindIcon:Pr,RulerIcon:wr,SaveIcon:Lr,SearchIcon:Er,ShareAltIcon:Dr,ShareIcon:vr,ShieldIcon:xr,SideBySideIcon:Hr,SidebarAltIcon:Mr,SidebarAltToggleIcon:Ur,SidebarIcon:Fr,SidebarToggleIcon:Nr,SpeakerIcon:Gr,StackedIcon:Wr,StarHollowIcon:Kr,StarIcon:Yr,StatusFailIcon:qr,StatusPassIcon:Vr,StatusWarnIcon:Zr,StickerIcon:zr,StopAltHollowIcon:Jr,StopAltIcon:Qr,StopIcon:Xr,StorybookIcon:$r,StructureIcon:jr,SubtractIcon:oI,SunIcon:nI,SupportIcon:eI,SwitchAltIcon:cI,SyncIcon:tI,TabletIcon:rI,ThumbsUpIcon:II,TimeIcon:aI,TimerIcon:lI,TransferIcon:iI,TrashIcon:sI,TwitterIcon:uI,TypeIcon:dI,UbuntuIcon:pI,UndoIcon:mI,UnfoldIcon:SI,UnlockIcon:CI,UnpinIcon:hI,UploadIcon:AI,UserAddIcon:_I,UserAltIcon:TI,UserIcon:bI,UsersIcon:gI,VSCodeIcon:yI,VerifiedIcon:OI,VideoIcon:BI,WandIcon:fI,WatchIcon:kI,WindowsIcon:RI,WrenchIcon:PI,XIcon:wI,YoutubeIcon:LI,ZoomIcon:EI,ZoomOutIcon:DI,ZoomResetIcon:vI,iconList:xI}=__STORYBOOK_ICONS__;var i="storybook/outline",_="outline",T=u(function(){let[c,r]=S(),s=C(),I=[!0,"true"].includes(c[_]),a=d(()=>r({[_]:!I}),[I]);return p(()=>{s.setAddonShortcut(i,{label:"Toggle Outline",defaultShortcut:["alt","O"],actionName:"outline",showInMenu:!1,action:a})},[a,s]),t.createElement(h,{key:"outline",active:I,title:"Apply outlines to the preview",onClick:a},t.createElement(A,null))});l.register(i,()=>{l.add(i,{title:"Outline",type:m.TOOL,match:({viewMode:c,tabId:r})=>!!(c&&c.match(/^(story|docs)$/))&&!r,render:()=>t.createElement(T,null)})});})(); 3 | }catch(e){ console.error("[Storybook] One of your manager-entries failed: " + import.meta.url, e); } 4 | -------------------------------------------------------------------------------- /docs/sb-addons/essentials-toolbars-7/manager-bundle.js: -------------------------------------------------------------------------------- 1 | try{ 2 | (()=>{var n=__REACT__,{Children:se,Component:ie,Fragment:ue,Profiler:ce,PureComponent:pe,StrictMode:me,Suspense:de,__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:be,cloneElement:Se,createContext:_e,createElement:Te,createFactory:ye,createRef:ve,forwardRef:fe,isValidElement:Ce,lazy:Ie,memo:Oe,startTransition:xe,unstable_act:Ee,useCallback:f,useContext:ge,useDebugValue:ke,useDeferredValue:he,useEffect:g,useId:Ae,useImperativeHandle:Re,useInsertionEffect:Le,useLayoutEffect:Be,useMemo:Me,useReducer:Pe,useRef:L,useState:B,useSyncExternalStore:Ne,useTransition:we,version:De}=__REACT__;var Ge=__STORYBOOK_API__,{ActiveTabs:Ke,Consumer:Ue,ManagerContext:Ye,Provider:$e,RequestResponseError:qe,addons:k,combineParameters:ze,controlOrMetaKey:je,controlOrMetaSymbol:Ze,eventMatchesShortcut:Je,eventToShortcut:Qe,experimental_MockUniversalStore:Xe,experimental_UniversalStore:et,experimental_requestResponse:tt,experimental_useUniversalStore:rt,isMacLike:ot,isShortcutTaken:at,keyToSymbol:nt,merge:lt,mockChannel:st,optionOrAltSymbol:it,shortcutMatchesShortcut:ut,shortcutToHumanString:ct,types:M,useAddonState:pt,useArgTypes:mt,useArgs:dt,useChannel:bt,useGlobalTypes:P,useGlobals:h,useParameter:St,useSharedState:_t,useStoryPrepared:Tt,useStorybookApi:N,useStorybookState:yt}=__STORYBOOK_API__;var Ot=__STORYBOOK_COMPONENTS__,{A:xt,ActionBar:Et,AddonPanel:gt,Badge:kt,Bar:ht,Blockquote:At,Button:Rt,ClipboardCode:Lt,Code:Bt,DL:Mt,Div:Pt,DocumentWrapper:Nt,EmptyTabContent:wt,ErrorFormatter:Dt,FlexBar:Vt,Form:Ht,H1:Wt,H2:Ft,H3:Gt,H4:Kt,H5:Ut,H6:Yt,HR:$t,IconButton:w,IconButtonSkeleton:qt,Icons:A,Img:zt,LI:jt,Link:Zt,ListItem:Jt,Loader:Qt,Modal:Xt,OL:er,P:tr,Placeholder:rr,Pre:or,ProgressSpinner:ar,ResetWrapper:nr,ScrollArea:lr,Separator:D,Spaced:sr,Span:ir,StorybookIcon:ur,StorybookLogo:cr,Symbols:pr,SyntaxHighlighter:mr,TT:dr,TabBar:br,TabButton:Sr,TabWrapper:_r,Table:Tr,Tabs:yr,TabsState:vr,TooltipLinkList:V,TooltipMessage:fr,TooltipNote:Cr,UL:Ir,WithTooltip:H,WithTooltipPure:Or,Zoom:xr,codeCommon:Er,components:gr,createCopyToClipboardFunction:kr,getStoryHref:hr,icons:Ar,interleaveSeparators:Rr,nameSpaceClassNames:Lr,resetComponents:Br,withReset:Mr}=__STORYBOOK_COMPONENTS__;var K={type:"item",value:""},U=(r,t)=>({...t,name:t.name||r,description:t.description||r,toolbar:{...t.toolbar,items:t.toolbar.items.map(e=>{let o=typeof e=="string"?{value:e,title:e}:e;return o.type==="reset"&&t.toolbar.icon&&(o.icon=t.toolbar.icon,o.hideIcon=!0),{...K,...o}})}}),Y=["reset"],$=r=>r.filter(t=>!Y.includes(t.type)).map(t=>t.value),S="addon-toolbars",q=async(r,t,e)=>{e&&e.next&&await r.setAddonShortcut(S,{label:e.next.label,defaultShortcut:e.next.keys,actionName:`${t}:next`,action:e.next.action}),e&&e.previous&&await r.setAddonShortcut(S,{label:e.previous.label,defaultShortcut:e.previous.keys,actionName:`${t}:previous`,action:e.previous.action}),e&&e.reset&&await r.setAddonShortcut(S,{label:e.reset.label,defaultShortcut:e.reset.keys,actionName:`${t}:reset`,action:e.reset.action})},z=r=>t=>{let{id:e,toolbar:{items:o,shortcuts:a}}=t,c=N(),[_,i]=h(),l=L([]),u=_[e],C=f(()=>{i({[e]:""})},[i]),I=f(()=>{let s=l.current,m=s.indexOf(u),d=m===s.length-1?0:m+1,p=l.current[d];i({[e]:p})},[l,u,i]),O=f(()=>{let s=l.current,m=s.indexOf(u),d=m>-1?m:0,p=d===0?s.length-1:d-1,b=l.current[p];i({[e]:b})},[l,u,i]);return g(()=>{a&&q(c,e,{next:{...a.next,action:I},previous:{...a.previous,action:O},reset:{...a.reset,action:C}})},[c,e,a,I,O,C]),g(()=>{l.current=$(o)},[]),n.createElement(r,{cycleValues:l.current,...t})},W=({currentValue:r,items:t})=>r!=null&&t.find(e=>e.value===r&&e.type!=="reset"),j=({currentValue:r,items:t})=>{let e=W({currentValue:r,items:t});if(e)return e.icon},Z=({currentValue:r,items:t})=>{let e=W({currentValue:r,items:t});if(e)return e.title},J=({active:r,disabled:t,title:e,icon:o,description:a,onClick:c})=>n.createElement(w,{active:r,title:a,disabled:t,onClick:t?()=>{}:c},o&&n.createElement(A,{icon:o,__suppressDeprecationWarning:!0}),e?`\xA0${e}`:null),Q=({right:r,title:t,value:e,icon:o,hideIcon:a,onClick:c,disabled:_,currentValue:i})=>{let l=o&&n.createElement(A,{style:{opacity:1},icon:o,__suppressDeprecationWarning:!0}),u={id:e??"_reset",active:i===e,right:r,title:t,disabled:_,onClick:c};return o&&!a&&(u.icon=l),u},X=z(({id:r,name:t,description:e,toolbar:{icon:o,items:a,title:c,preventDynamicIcon:_,dynamicTitle:i}})=>{let[l,u,C]=h(),[I,O]=B(!1),s=l[r],m=!!s,d=r in C,p=o,b=c;_||(p=j({currentValue:s,items:a})||p),i&&(b=Z({currentValue:s,items:a})||b),!b&&!p&&console.warn(`Toolbar '${t}' has no title or icon`);let F=f(E=>{u({[r]:E})},[r,u]);return n.createElement(H,{placement:"top",tooltip:({onHide:E})=>{let G=a.filter(({type:x})=>{let R=!0;return x==="reset"&&!s&&(R=!1),R}).map(x=>Q({...x,currentValue:s,disabled:d,onClick:()=>{F(x.value),E()}}));return n.createElement(V,{links:G})},closeOnOutsideClick:!0,onVisibleChange:O},n.createElement(J,{active:I||m,disabled:d,description:e||"",icon:p,title:b||""}))}),ee=()=>{let r=P(),t=Object.keys(r).filter(e=>!!r[e].toolbar);return t.length?n.createElement(n.Fragment,null,n.createElement(D,null),t.map(e=>{let o=U(e,r[e]);return n.createElement(X,{key:e,id:e,...o})})):null};k.register(S,()=>k.add(S,{title:S,type:M.TOOL,match:({tabId:r})=>!r,render:()=>n.createElement(ee,null)}));})(); 3 | }catch(e){ console.error("[Storybook] One of your manager-entries failed: " + import.meta.url, e); } 4 | -------------------------------------------------------------------------------- /docs/sb-addons/links-1/manager-bundle.js: -------------------------------------------------------------------------------- 1 | try{ 2 | (()=>{var y=__STORYBOOK_API__,{ActiveTabs:E,Consumer:T,ManagerContext:h,Provider:v,RequestResponseError:A,addons:a,combineParameters:b,controlOrMetaKey:O,controlOrMetaSymbol:k,eventMatchesShortcut:R,eventToShortcut:g,experimental_MockUniversalStore:x,experimental_UniversalStore:I,experimental_requestResponse:M,experimental_useUniversalStore:C,isMacLike:P,isShortcutTaken:U,keyToSymbol:f,merge:q,mockChannel:D,optionOrAltSymbol:G,shortcutMatchesShortcut:K,shortcutToHumanString:V,types:$,useAddonState:B,useArgTypes:N,useArgs:Q,useChannel:Y,useGlobalTypes:H,useGlobals:L,useParameter:j,useSharedState:w,useStoryPrepared:z,useStorybookApi:F,useStorybookState:J}=__STORYBOOK_API__;var e="storybook/links",n={NAVIGATE:`${e}/navigate`,REQUEST:`${e}/request`,RECEIVE:`${e}/receive`};a.register(e,t=>{t.on(n.REQUEST,({kind:u,name:l})=>{let i=t.storyId(u,l);t.emit(n.RECEIVE,i)})});})(); 3 | }catch(e){ console.error("[Storybook] One of your manager-entries failed: " + import.meta.url, e); } 4 | -------------------------------------------------------------------------------- /docs/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js: -------------------------------------------------------------------------------- 1 | try{ 2 | (()=>{var g=__STORYBOOK_API__,{ActiveTabs:T,Consumer:O,ManagerContext:f,Provider:v,RequestResponseError:x,addons:n,combineParameters:A,controlOrMetaKey:k,controlOrMetaSymbol:M,eventMatchesShortcut:P,eventToShortcut:R,experimental_MockUniversalStore:w,experimental_UniversalStore:C,experimental_requestResponse:G,experimental_useUniversalStore:I,isMacLike:K,isShortcutTaken:U,keyToSymbol:q,merge:B,mockChannel:F,optionOrAltSymbol:Y,shortcutMatchesShortcut:j,shortcutToHumanString:E,types:H,useAddonState:L,useArgTypes:N,useArgs:z,useChannel:D,useGlobalTypes:J,useGlobals:Q,useParameter:V,useSharedState:W,useStoryPrepared:X,useStorybookApi:Z,useStorybookState:$}=__STORYBOOK_API__;var S=(()=>{let e;return typeof window<"u"?e=window:typeof globalThis<"u"?e=globalThis:typeof window<"u"?e=window:typeof self<"u"?e=self:e={},e})(),c="tag-filters",p="static-filter";n.register(c,e=>{let i=Object.entries(S.TAGS_OPTIONS??{}).reduce((t,r)=>{let[o,u]=r;return u.excludeFromSidebar&&(t[o]=!0),t},{});e.experimental_setFilter(p,t=>{let r=t.tags??[];return(r.includes("dev")||t.type==="docs")&&r.filter(o=>i[o]).length===0})});})(); 3 | }catch(e){ console.error("[Storybook] One of your manager-entries failed: " + import.meta.url, e); } 4 | -------------------------------------------------------------------------------- /docs/sb-common-assets/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/sb-common-assets/nunito-sans-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/docs/sb-common-assets/nunito-sans-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/sb-common-assets/nunito-sans-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/docs/sb-common-assets/nunito-sans-bold.woff2 -------------------------------------------------------------------------------- /docs/sb-common-assets/nunito-sans-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/docs/sb-common-assets/nunito-sans-italic.woff2 -------------------------------------------------------------------------------- /docs/sb-common-assets/nunito-sans-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/docs/sb-common-assets/nunito-sans-regular.woff2 -------------------------------------------------------------------------------- /docs/sb-manager/globals.js: -------------------------------------------------------------------------------- 1 | import ESM_COMPAT_Module from "node:module"; 2 | import { fileURLToPath as ESM_COMPAT_fileURLToPath } from 'node:url'; 3 | import { dirname as ESM_COMPAT_dirname } from 'node:path'; 4 | const __filename = ESM_COMPAT_fileURLToPath(import.meta.url); 5 | const __dirname = ESM_COMPAT_dirname(__filename); 6 | const require = ESM_COMPAT_Module.createRequire(import.meta.url); 7 | 8 | // src/manager/globals/globals.ts 9 | var _ = { 10 | react: "__REACT__", 11 | "react-dom": "__REACT_DOM__", 12 | "react-dom/client": "__REACT_DOM_CLIENT__", 13 | "@storybook/icons": "__STORYBOOK_ICONS__", 14 | "storybook/internal/manager-api": "__STORYBOOK_API__", 15 | "@storybook/manager-api": "__STORYBOOK_API__", 16 | "@storybook/core/manager-api": "__STORYBOOK_API__", 17 | "storybook/internal/components": "__STORYBOOK_COMPONENTS__", 18 | "@storybook/components": "__STORYBOOK_COMPONENTS__", 19 | "@storybook/core/components": "__STORYBOOK_COMPONENTS__", 20 | "storybook/internal/channels": "__STORYBOOK_CHANNELS__", 21 | "@storybook/channels": "__STORYBOOK_CHANNELS__", 22 | "@storybook/core/channels": "__STORYBOOK_CHANNELS__", 23 | "storybook/internal/core-errors": "__STORYBOOK_CORE_EVENTS__", 24 | "@storybook/core-events": "__STORYBOOK_CORE_EVENTS__", 25 | "@storybook/core/core-events": "__STORYBOOK_CORE_EVENTS__", 26 | "storybook/internal/manager-errors": "__STORYBOOK_CORE_EVENTS_MANAGER_ERRORS__", 27 | "@storybook/core-events/manager-errors": "__STORYBOOK_CORE_EVENTS_MANAGER_ERRORS__", 28 | "@storybook/core/manager-errors": "__STORYBOOK_CORE_EVENTS_MANAGER_ERRORS__", 29 | "storybook/internal/router": "__STORYBOOK_ROUTER__", 30 | "@storybook/router": "__STORYBOOK_ROUTER__", 31 | "@storybook/core/router": "__STORYBOOK_ROUTER__", 32 | "storybook/internal/theming": "__STORYBOOK_THEMING__", 33 | "@storybook/theming": "__STORYBOOK_THEMING__", 34 | "@storybook/core/theming": "__STORYBOOK_THEMING__", 35 | "storybook/internal/theming/create": "__STORYBOOK_THEMING_CREATE__", 36 | "@storybook/theming/create": "__STORYBOOK_THEMING_CREATE__", 37 | "@storybook/core/theming/create": "__STORYBOOK_THEMING_CREATE__", 38 | "storybook/internal/client-logger": "__STORYBOOK_CLIENT_LOGGER__", 39 | "@storybook/client-logger": "__STORYBOOK_CLIENT_LOGGER__", 40 | "@storybook/core/client-logger": "__STORYBOOK_CLIENT_LOGGER__", 41 | "storybook/internal/types": "__STORYBOOK_TYPES__", 42 | "@storybook/types": "__STORYBOOK_TYPES__", 43 | "@storybook/core/types": "__STORYBOOK_TYPES__" 44 | }, o = Object.keys(_); 45 | export { 46 | o as globalPackages, 47 | _ as globalsNameReferenceMap 48 | }; 49 | -------------------------------------------------------------------------------- /docs/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@melloware/react-logviewer", 3 | "version": "6.3.0", 4 | "description": "React Lazy LogViewer", 5 | "keywords": [ 6 | "react", 7 | "log", 8 | "viewer", 9 | "logviewer", 10 | "lazy", 11 | "lazylog" 12 | ], 13 | "author": "Melloware (https://github.com/melloware)", 14 | "contributors": [ 15 | "Eli Perelman (https://github.com/eliperelman)", 16 | "Hassan Ali (http://hassanali.me)", 17 | "Melloware (https://github.com/melloware)" 18 | ], 19 | "main": "dist/cjs/index.js", 20 | "module": "dist/esm/index.js", 21 | "types": "dist/index.d.ts", 22 | "files": [ 23 | "dist" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/melloware/react-logviewer.git" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/melloware/react-logviewer/issues" 31 | }, 32 | "homepage": "https://melloware.github.io/react-logviewer/", 33 | "license": "MPL-2.0", 34 | "scripts": { 35 | "dev": "vite", 36 | "build": "rimraf dist && rollup -c", 37 | "preview": "vite preview", 38 | "storybook": "storybook dev -p 6006", 39 | "build-storybook": "storybook build -o docs/", 40 | "init-msw": "msw init public/", 41 | "prepare": "npm run build", 42 | "prepack": "npm run build", 43 | "test": "npm run build && playwright test", 44 | "test:server": "node tests/e2e/test-server.js", 45 | "test:ui": "npm run build && playwright test --ui", 46 | "test:eventsource": "npm run build && playwright test eventsource.spec.ts" 47 | }, 48 | "dependencies": { 49 | "hotkeys-js": "3.13.10", 50 | "mitt": "3.0.1", 51 | "react-string-replace": "1.1.1", 52 | "virtua": "0.41.3" 53 | }, 54 | "devDependencies": { 55 | "@playwright/test": "^1.52.0", 56 | "@rollup/plugin-commonjs": "^25.0.7", 57 | "@rollup/plugin-node-resolve": "^15.3.0", 58 | "@rollup/plugin-typescript": "^11.1.6", 59 | "@storybook/addon-essentials": "^8.6.4", 60 | "@storybook/addon-interactions": "^8.6.4", 61 | "@storybook/addon-links": "^8.6.4", 62 | "@storybook/blocks": "^8.6.4", 63 | "@storybook/react": "^8.6.4", 64 | "@storybook/react-vite": "^8.6.4", 65 | "@storybook/test": "^8.6.4", 66 | "@types/node": "^22.15.29", 67 | "@types/react": "^19.0.10", 68 | "@types/react-dom": "^19.0.4", 69 | "@vitejs/plugin-react": "^4.3.4", 70 | "cors": "^2.8.5", 71 | "eslint": "^9.22.0", 72 | "eslint-plugin-react": "^7.37.4", 73 | "eslint-plugin-react-hooks": "^5.2.0", 74 | "eslint-plugin-react-refresh": "^0.4.19", 75 | "eslint-plugin-storybook": "^0.11.4", 76 | "express": "^5.1.0", 77 | "msw": "^2.7.3", 78 | "msw-storybook-addon": "^2.0.4", 79 | "prop-types": "^15.8.1", 80 | "rimraf": "^6.0.1", 81 | "rollup": "^2.79.2", 82 | "rollup-plugin-dts": "4.2.3", 83 | "rollup-plugin-postcss": "^4.0.2", 84 | "storybook": "^8.6.4", 85 | "vite": "^6.2.1" 86 | }, 87 | "peerDependencies": { 88 | "react": ">=17.0.0", 89 | "react-dom": ">=17.0.0" 90 | }, 91 | "packageManager": "pnpm@8.1.1+sha1.f3d2cd2bddf0fa447a13b80e166d1b85a7823486" 92 | } 93 | -------------------------------------------------------------------------------- /playwright.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('@playwright/test'); 2 | 3 | module.exports = defineConfig({ 4 | testDir: './tests/e2e', 5 | timeout: 30000, 6 | fullyParallel: false, // Run tests sequentially to avoid port conflicts 7 | workers: 1, 8 | reporter: 'list', 9 | use: { 10 | trace: 'on-first-retry', 11 | }, 12 | projects: [ 13 | { 14 | name: 'chromium', 15 | use: { 16 | browserName: 'chromium', 17 | viewport: { width: 1280, height: 720 } 18 | }, 19 | }, 20 | ], 21 | webServer: { 22 | command: 'node tests/e2e/test-server.js', 23 | port: 54321, 24 | reuseExistingServer: false, 25 | stdout: 'pipe', 26 | stderr: 'pipe', 27 | }, 28 | }); -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "@rollup/plugin-commonjs"; 2 | import resolve from "@rollup/plugin-node-resolve"; 3 | import typescript from "@rollup/plugin-typescript"; 4 | import dts from "rollup-plugin-dts"; 5 | import postcss from "rollup-plugin-postcss"; 6 | 7 | const packageJson = require("./package.json"); 8 | 9 | export default [ 10 | { 11 | input: "src/index.ts", 12 | output: [ 13 | { 14 | file: packageJson.main, 15 | format: "cjs", 16 | sourcemap: true, 17 | exports: "auto", 18 | }, 19 | { 20 | file: packageJson.module, 21 | format: "esm", 22 | sourcemap: true, 23 | }, 24 | { 25 | file: "dist/umd/index.js", 26 | format: "umd", 27 | name: "ReactLogViewer", 28 | sourcemap: true, 29 | globals: { 30 | react: "React", 31 | "react-dom": "ReactDOM", 32 | "react/jsx-runtime": "jsxRuntime", 33 | }, 34 | }, 35 | ], 36 | plugins: [ 37 | resolve(), 38 | commonjs(), 39 | typescript({ tsconfig: "./tsconfig.json" }), 40 | typescript({ 41 | tsconfig: "./tsconfig.json", 42 | declaration: true, 43 | declarationDir: "dist", 44 | outputToFilesystem: true, // Required for proper type output in Rollup 4 45 | }), 46 | postcss(), 47 | ], 48 | external: ['react', 'react/jsx-runtime', 'react-dom', 'react-dom/client'], 49 | }, 50 | { 51 | input: "dist/esm/types/index.d.ts", 52 | output: [{ file: "dist/index.d.ts", format: "esm" }], 53 | plugins: [dts()], 54 | external: [/\.(css|less|scss)$/], 55 | }, 56 | ]; 57 | -------------------------------------------------------------------------------- /src/components/LazyLog/README.md: -------------------------------------------------------------------------------- 1 | Normal log viewing using a `url`: 2 | 3 | ```js 4 | const url = 'https://gist.githubusercontent.com/helfi92/96d4444aa0ed46c5f9060a789d316100/raw/ba0d30a9877ea5cc23c7afcd44505dbc2bab1538/typical-live_backing.log'; 5 | 6 |
7 | 8 |
9 | ``` 10 | 11 | See [`ScrollFollow`](#scrollfollow) for an example of a streaming endpoint. 12 | 13 | Log viewing using `text` from a string : 14 | 15 | ```js 16 | const text = ` 17 | Sed ut perspiciatis unde omnis iste natus error sit voluptatem 18 | accusantium doloremque laudantium, totam rem aperiam, 19 | eaque ipsa quae ab illo inventore veritatis et quasi architecto 20 | beatae vitae dicta sunt explicabo. Nemo enim ipsam 21 | voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed 22 | quia consequuntur magni dolores eos qui ratione 23 | voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem 24 | ipsum quia dolor sit amet, consectetur, adipisci 25 | velit, sed quia non numquam eius modi tempora incidunt ut labore 26 | et dolore magnam aliquam quaerat voluptatem. 27 | 28 | Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit 29 | laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure 30 | reprehenderit qui in ea voluptate velit esse quam nihil molestiae 31 | consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? 32 | `; 33 | 34 |
35 | 36 |
37 | ``` 38 | 39 | Log viewing using a websocket 40 | 41 | ```jsx harmony 42 | const url = 'wss://echo.websocket.org'; 43 | let socket = null; 44 | 45 |
46 | 51 |
52 | { 58 | socket = sock; sock.send(JSON.stringify({message: "Socket has been opened!"})) 59 | }, 60 | formatMessage: e => JSON.parse(e).message, 61 | }} 62 | /> 63 |
64 |
65 | ``` 66 | 67 | Log viewing using an eventsource 68 | 69 | ```jsx harmony 70 | const url = 'https://my.eventsource.tld'; 71 | 72 |
73 |
74 | JSON.parse(e).message, 80 | }} 81 | /> 82 |
83 |
84 | ``` 85 | -------------------------------------------------------------------------------- /src/components/LazyLog/index.module.css: -------------------------------------------------------------------------------- 1 | .lazyLog { 2 | overflow: auto !important; 3 | font-family: "Monaco", monospace; 4 | font-size: 12px; 5 | margin: 0; 6 | white-space: pre; 7 | background-color: #222222; 8 | color: #ffffff; 9 | font-weight: 400; 10 | will-change: initial; 11 | outline: none; 12 | } 13 | 14 | .lazyLog span a { 15 | color: #d6d6d6; 16 | } 17 | 18 | .wrapLine { 19 | overflow-x: hidden !important; 20 | } 21 | 22 | .searchMatch { 23 | background-color: #ffff00; 24 | color: #222222; 25 | } 26 | 27 | .searchMatchHighlighted { 28 | background-color: #ff10f0; 29 | color: #222222; 30 | } 31 | -------------------------------------------------------------------------------- /src/components/LazyLog/index.module.css.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IndexModuleCssNamespace { 2 | export interface IIndexModuleCss { 3 | lazyLog: string; 4 | searchMatch: string; 5 | searchMatchHighlighted: string; 6 | wrapLine: string; 7 | } 8 | } 9 | 10 | declare const IndexModuleCssModule: IndexModuleCssNamespace.IIndexModuleCss & { 11 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 12 | locals: IndexModuleCssNamespace.IIndexModuleCss; 13 | }; 14 | 15 | export = IndexModuleCssModule; 16 | -------------------------------------------------------------------------------- /src/components/Line/README.md: -------------------------------------------------------------------------------- 1 | ```js 2 | 7 | ``` -------------------------------------------------------------------------------- /src/components/Line/index.module.css: -------------------------------------------------------------------------------- 1 | .line { 2 | margin: 0; 3 | user-select: none; 4 | } 5 | 6 | .wrapLine { 7 | display: flex; 8 | } 9 | 10 | .line:hover { 11 | background-color: #444444; 12 | } 13 | 14 | .lineHighlight { 15 | background-color: #666666; 16 | } 17 | 18 | .lineSelectable { 19 | user-select: text; 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Line/index.module.css.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IndexModuleCssNamespace { 2 | export interface IIndexModuleCss { 3 | line: string; 4 | lineHighlight: string; 5 | lineSelectable: string; 6 | wrapLine: string; 7 | } 8 | } 9 | 10 | declare const IndexModuleCssModule: IndexModuleCssNamespace.IIndexModuleCss & { 11 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 12 | locals: IndexModuleCssNamespace.IIndexModuleCss; 13 | }; 14 | 15 | export = IndexModuleCssModule; 16 | -------------------------------------------------------------------------------- /src/components/Line/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | CSSProperties, 3 | Component, 4 | MouseEventHandler, 5 | ReactNode, 6 | } from "react"; 7 | 8 | import LineContent from "../LineContent"; 9 | import LineGutter from "../LineGutter"; 10 | import LineNumber from "../LineNumber"; 11 | import styles from "./index.module.css"; 12 | 13 | export interface LineProps { 14 | data?: any[]; 15 | number?: number | string; 16 | rowHeight?: number; 17 | highlight?: boolean | undefined; 18 | selectable?: boolean | undefined; 19 | style?: CSSProperties | undefined; 20 | className?: string; 21 | gutter?: React.ReactNode; 22 | highlightClassName?: string; 23 | /** 24 | * Enable the line numbers to be displayed. Default is true. 25 | */ 26 | enableLineNumbers?: boolean | undefined; 27 | /** 28 | * Enable the line gutters to be displayed. Default is false 29 | */ 30 | enableGutters?: boolean | undefined; 31 | /** 32 | * Wrap overflowing lines. Default is false 33 | */ 34 | wrapLines?: boolean | undefined; 35 | /** 36 | * Enable hyperlinks to be discovered in log text and made clickable links. Default is false. 37 | */ 38 | enableLinks?: boolean; 39 | formatPart?: ((text: string) => ReactNode) | undefined; 40 | onLineNumberClick?: MouseEventHandler | undefined; 41 | /** 42 | * Callback to invoke on click of line contents. 43 | * @param {React.MouseEvent} event - Browser event. 44 | */ 45 | onLineContentClick?(event: React.MouseEvent): void; 46 | } 47 | 48 | /** 49 | * A single row of content, containing both the line number 50 | * and any text content within the line. 51 | */ 52 | export default class Line extends Component { 53 | static defaultProps: LineProps = { 54 | highlight: false, 55 | selectable: false, 56 | style: {}, 57 | formatPart: undefined, 58 | onLineNumberClick: undefined, 59 | onLineContentClick: undefined, 60 | className: "", 61 | highlightClassName: "", 62 | enableLineNumbers: true, 63 | enableLinks: false, 64 | wrapLines: false, 65 | }; 66 | 67 | render() { 68 | const { 69 | data, 70 | formatPart, 71 | highlight, 72 | selectable, 73 | onLineNumberClick, 74 | onLineContentClick, 75 | number, 76 | rowHeight, 77 | style, 78 | className, 79 | highlightClassName, 80 | gutter, 81 | wrapLines, 82 | } = this.props; 83 | const selectableClass = selectable ? ` ${styles.lineSelectable}` : ""; 84 | const highlightClass = highlight 85 | ? ` ${styles.lineHighlight} ${highlightClassName}` 86 | : ""; 87 | const classes = `${styles.line}${selectableClass}${highlightClass} ${ 88 | wrapLines ? styles.wrapLine : "" 89 | } ${className}`; 90 | const lineStyle = { 91 | ...style, 92 | lineHeight: `${style ? style.height || rowHeight : rowHeight}px`, 93 | minWidth: style ? style.width || "100%" : "100%", 94 | width: undefined, 95 | } as CSSProperties; 96 | 97 | return ( 98 |
99 | {this.props.enableLineNumbers ? ( 100 | 106 | ) : null} 107 | {this.props.enableGutters ? ( 108 | 109 | ) : null} 110 | 118 |
119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/components/LineContent/README.md: -------------------------------------------------------------------------------- 1 | Simple usage: 2 | 3 | ```js 4 | 8 | ``` 9 | 10 | Format each line part 11 | 12 | ```js 13 | text.replace('world', 'galaxy')} 17 | /> 18 | ``` -------------------------------------------------------------------------------- /src/components/LineContent/index.module.css: -------------------------------------------------------------------------------- 1 | .lineContent { 2 | user-select: initial; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/LineContent/index.module.css.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IndexModuleCssNamespace { 2 | export interface IIndexModuleCss { 3 | lineContent: string; 4 | } 5 | } 6 | 7 | declare const IndexModuleCssModule: IndexModuleCssNamespace.IIndexModuleCss & { 8 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 9 | locals: IndexModuleCssNamespace.IIndexModuleCss; 10 | }; 11 | 12 | export = IndexModuleCssModule; 13 | -------------------------------------------------------------------------------- /src/components/LineContent/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, Component, ReactNode } from "react"; 2 | 3 | import LinePart from "../LinePart"; 4 | import styles from "./index.module.css"; 5 | 6 | /* eslint-disable react/no-array-index-key */ 7 | 8 | export interface LineContentProps { 9 | /** 10 | * The pieces of data to render in a line. Will typically 11 | * be multiple items in the array if ANSI parsed prior. 12 | */ 13 | data?: any[]; 14 | /** 15 | * The line number being rendered. 16 | */ 17 | number: string | number | undefined; 18 | /** 19 | * Execute a function against each line part's 20 | * `text` property in `data` to process and 21 | * return a new value to render for the part. 22 | */ 23 | formatPart?: ((text: string) => ReactNode) | undefined; 24 | /** 25 | * Execute a function when the line is clicked. 26 | */ 27 | onClick?(event: React.MouseEvent): void; 28 | /** 29 | * CSS Style of the LineContent. 30 | */ 31 | style?: CSSProperties | undefined; 32 | /** 33 | * Enable hyperlinks to be discovered in log text and made clickable links. Default is false. 34 | */ 35 | enableLinks?: boolean; 36 | /** 37 | * Wrap overflowing lines. Default is false 38 | */ 39 | wrapLines?: boolean | undefined; 40 | } 41 | 42 | /** 43 | * The container of all the individual pieces of content that 44 | * is on a single line. May contain one or more `LinePart`s 45 | * depending on ANSI parsing. 46 | */ 47 | export default class LineContent extends Component { 48 | static defaultProps = { 49 | formatPart: null, 50 | style: null, 51 | }; 52 | 53 | render() { 54 | const { data, formatPart, onClick, number, style } = this.props; 55 | 56 | if (data) { 57 | const last = data[data.length - 1]; 58 | 59 | if ( 60 | last && 61 | typeof last.text === "string" && 62 | !last.text.endsWith("\n") 63 | ) { 64 | last.text += "\n"; 65 | } 66 | } 67 | 68 | return ( 69 | 74 | {data && 75 | data.map((part: any, n: number) => ( 76 | 83 | ))} 84 | 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/components/LineGutter/README.md: -------------------------------------------------------------------------------- 1 | Render something in the gutter 2 | 3 | ```js 4 |
} /> 5 | ``` 6 | -------------------------------------------------------------------------------- /src/components/LineGutter/index.module.css: -------------------------------------------------------------------------------- 1 | .lineGutter { 2 | display: inline-flex; 3 | overflow: hidden; 4 | min-width: 20px; 5 | padding-right: 0.5em; 6 | 7 | color: #a7a7a7; 8 | text-decoration: none; 9 | user-select: none; 10 | } 11 | -------------------------------------------------------------------------------- /src/components/LineGutter/index.module.css.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IndexModuleCssNamespace { 2 | export interface IIndexModuleCss { 3 | lineGutter: string; 4 | } 5 | } 6 | 7 | declare const IndexModuleCssModule: IndexModuleCssNamespace.IIndexModuleCss & { 8 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 9 | locals: IndexModuleCssNamespace.IIndexModuleCss; 10 | }; 11 | 12 | export = IndexModuleCssModule; 13 | -------------------------------------------------------------------------------- /src/components/LineGutter/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import styles from "./index.module.css"; 4 | 5 | export interface LineGutterProps { 6 | /** 7 | * The gutter object 8 | */ 9 | gutter: React.ReactNode; 10 | } 11 | 12 | /** 13 | * The gutter is an element between the line number and content. 14 | */ 15 | export default class LineGutter extends Component { 16 | static defaultProps = { 17 | gutter: null, 18 | }; 19 | 20 | render() { 21 | const { gutter } = this.props; 22 | 23 | return ( 24 | {gutter} 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/LineNumber/README.md: -------------------------------------------------------------------------------- 1 | Render a line number. 2 | 3 | ```js 4 | 7 | ``` 8 | 9 | Execute a function on click. 10 | 11 | ```js 12 | alert(`Line ${number} clicked!`)} 15 | /> 16 | ``` 17 | -------------------------------------------------------------------------------- /src/components/LineNumber/index.module.css: -------------------------------------------------------------------------------- 1 | .lineNumber { 2 | display: inline-block; 3 | width: 55px; 4 | margin-left: 15px; 5 | margin-right: 15px; 6 | color: #7e7e7e; 7 | user-select: none; 8 | text-align: right; 9 | min-width: 40px; 10 | cursor: pointer; 11 | text-decoration: none; 12 | padding-right: 1em; 13 | vertical-align: top; 14 | } 15 | 16 | .wrapLine { 17 | min-width: 55px; 18 | } 19 | 20 | .lineNumber::before { 21 | content: attr(id); 22 | } 23 | 24 | .lineNumberHighlight { 25 | composes: lineNumber; 26 | color: #ffffff; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/LineNumber/index.module.css.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IndexModuleCssNamespace { 2 | export interface IIndexModuleCss { 3 | lineNumber: string; 4 | lineNumberHighlight: string; 5 | wrapLine: string; 6 | } 7 | } 8 | 9 | declare const IndexModuleCssModule: IndexModuleCssNamespace.IIndexModuleCss & { 10 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 11 | locals: IndexModuleCssNamespace.IIndexModuleCss; 12 | }; 13 | 14 | export = IndexModuleCssModule; 15 | -------------------------------------------------------------------------------- /src/components/LineNumber/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, Component, MouseEventHandler } from "react"; 2 | 3 | import styles from "./index.module.css"; 4 | 5 | export interface LineNumberProps { 6 | /** 7 | * The line number to display in the anchor. 8 | */ 9 | number: string | number | undefined; 10 | /** 11 | * Specify whether this line is highlighted. 12 | */ 13 | highlight?: boolean | undefined; 14 | /** 15 | * Execute a function when the line number is clicked. 16 | */ 17 | onClick?: MouseEventHandler | undefined; 18 | /** 19 | * CSS style for the Line Number. 20 | */ 21 | style?: CSSProperties | undefined; 22 | /** 23 | * Wrap overflowing lines. Default is false 24 | */ 25 | wrapLines?: boolean | undefined; 26 | } 27 | 28 | /** 29 | * The line number of a single line. 30 | * The anchor contained within is interactive, and will highlight the 31 | * entire line upon selection. 32 | */ 33 | export default class LineNumber extends Component { 34 | static defaultProps = { 35 | style: null, 36 | highlight: false, 37 | onClick: null, 38 | wrapLines: false, 39 | }; 40 | 41 | render() { 42 | const { highlight, onClick, number, style } = this.props; 43 | const className = `log-number ${ 44 | highlight ? styles.lineNumberHighlight : styles.lineNumber 45 | } ${this.props.wrapLines ? styles.wrapLine : ""}`; 46 | 47 | return ( 48 | 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/components/LinePart/README.md: -------------------------------------------------------------------------------- 1 | Simple usage: 2 | 3 | ```js 4 | 7 | ``` 8 | 9 | Format each line part 10 | 11 | ```js 12 | text.replace('world', 'galaxy')} 15 | /> 16 | ``` 17 | -------------------------------------------------------------------------------- /src/components/LinePart/index.module.css: -------------------------------------------------------------------------------- 1 | .bold { font-weight: bold; } 2 | .underline { text-decoration: underline; } 3 | .italic { font-style: italic; } 4 | 5 | .wrapLine { 6 | white-space: normal; 7 | text-indent: 0; 8 | display: inline-block; 9 | } 10 | 11 | .noWrapLine { 12 | display: ruby; 13 | } 14 | 15 | .black { color: #4e4e4e; } 16 | .red { color: #ff6c60; } 17 | .green { color: #00aa00; } 18 | .yellow { color: #ffffb6; } 19 | .blue { color: #96cbfe; } 20 | .magenta { color: #ff73fd; } 21 | .cyan { color: #00aaaa; } 22 | .white { color: #eeeeee; } 23 | .grey { color: #969696; } 24 | 25 | .blackBold { color: #7c7c7c; } 26 | .redBold { color: #ff9b93; } 27 | .greenBold { color: #ceffab } 28 | .yellowBold { color: #ffffcb; } 29 | .blueBold { color: #b5dcfe; } 30 | .magentaBold { color: #ff9cfe; } 31 | .cyanBold { color: #55ffff; } 32 | .whiteBold { color: #ffffff; } 33 | .greyBold { color: #969696; } 34 | 35 | .blackBg { background-color: #4e4e4e; } 36 | .redBg { background-color: #ff6c60; } 37 | .greenBg { background-color: #00aa00; } 38 | .yellowBg { background-color: #ffffb6; } 39 | .blueBg { background-color: #96cbfe; } 40 | .magentaBg { background-color: #ff73fd; } 41 | .cyanBg { background-color: #00aaaa; } 42 | .whiteBg { background-color: #eeeeee; } 43 | .greyBg { background-color: #969696; } 44 | -------------------------------------------------------------------------------- /src/components/LinePart/index.module.css.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IndexModuleCssNamespace { 2 | export interface IIndexModuleCss { 3 | black: string; 4 | blackBg: string; 5 | blackBold: string; 6 | blue: string; 7 | blueBg: string; 8 | blueBold: string; 9 | bold: string; 10 | cyan: string; 11 | cyanBg: string; 12 | cyanBold: string; 13 | green: string; 14 | greenBg: string; 15 | greenBold: string; 16 | grey: string; 17 | greyBg: string; 18 | greyBold: string; 19 | italic: string; 20 | magenta: string; 21 | magentaBg: string; 22 | magentaBold: string; 23 | red: string; 24 | redBg: string; 25 | redBold: string; 26 | underline: string; 27 | white: string; 28 | whiteBg: string; 29 | whiteBold: string; 30 | yellow: string; 31 | yellowBg: string; 32 | yellowBold: string; 33 | wrapLine: string; 34 | noWrapLine: string; 35 | } 36 | } 37 | 38 | declare const IndexModuleCssModule: IndexModuleCssNamespace.IIndexModuleCss & { 39 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 40 | locals: IndexModuleCssNamespace.IIndexModuleCss; 41 | [key: string]: any; 42 | }; 43 | 44 | export = IndexModuleCssModule; 45 | -------------------------------------------------------------------------------- /src/components/LinePart/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, Component, ReactNode } from "react"; 2 | 3 | import styles from "./index.module.css"; 4 | 5 | export interface LinePartCss { 6 | foreground?: string | number; 7 | bold?: boolean; 8 | background?: string; 9 | italic?: boolean; 10 | underline?: boolean; 11 | email?: boolean; 12 | link?: boolean; 13 | text?: string; 14 | wrapLine?: boolean; 15 | [key: string]: any; 16 | } 17 | 18 | const getClassName = (part: LinePartCss, wrapLines: boolean) => { 19 | const className = ["log-part"]; 20 | 21 | if (part.foreground && part.bold) { 22 | className.push(styles[`${part.foreground}Bold`], styles.bold); 23 | } else if (part.foreground) { 24 | className.push(styles[part.foreground]); 25 | } else if (part.bold) { 26 | className.push(styles.bold); 27 | } 28 | 29 | if (wrapLines) { 30 | className.push(styles.wrapLine); 31 | } else { 32 | className.push(styles.noWrapLine); 33 | } 34 | 35 | if (part.background) { 36 | className.push(styles[`${part.background}Bg`]); 37 | } 38 | 39 | if (part.italic) { 40 | className.push(styles.italic); 41 | } 42 | 43 | if (part.underline) { 44 | className.push(styles.underline); 45 | } 46 | 47 | return className.join(" "); 48 | }; 49 | 50 | export interface LinePartProps { 51 | /** 52 | * The pieces of data to render in a line. Will typically 53 | * be multiple items in the array if ANSI parsed prior. 54 | */ 55 | part: LinePartCss; 56 | /** 57 | * Style for the line Part 58 | */ 59 | style?: CSSProperties | undefined; 60 | /** 61 | * Enable hyperlinks to be discovered in log text and made clickable links. Default is false. 62 | */ 63 | enableLinks?: boolean; 64 | /** 65 | * Execute a function against each line part's 66 | * `text` property in `data` to process and 67 | * return a new value to render for the part. 68 | */ 69 | format?: ((text: string) => ReactNode) | undefined; 70 | /** 71 | * Wrap overflowing lines. Default is false 72 | */ 73 | wrapLines?: boolean | undefined; 74 | } 75 | 76 | /** 77 | * An individual segment of text within a line. When the text content 78 | * is ANSI-parsed, each boundary is placed within its own `LinePart` 79 | * and styled separately (colors, text formatting, etc.) from the 80 | * rest of the line's content. 81 | */ 82 | export default class LinePart extends Component { 83 | static defaultProps = { 84 | format: null, 85 | style: null, 86 | enableLinks: false, 87 | wrapLines: false, 88 | }; 89 | 90 | render() { 91 | const { format, part, style } = this.props; 92 | const partText = part.text; 93 | const partClassName = getClassName(part, !!this.props.wrapLines); 94 | const renderedText = format ? format(partText!) : partText!; 95 | 96 | if (this.props.enableLinks) { 97 | if (part.link) { 98 | return ( 99 | 100 | 106 | {renderedText} 107 | {" "} 108 | 109 | ); 110 | } 111 | 112 | if (part.email) { 113 | return ( 114 | 115 | 119 | {renderedText} 120 | {" "} 121 | 122 | ); 123 | } 124 | } 125 | 126 | return ( 127 | 128 | {renderedText} 129 | {this.props.enableLinks ? " " : null} 130 | 131 | ); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/components/Loading/README.md: -------------------------------------------------------------------------------- 1 | ```js 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /src/components/Loading/index.module.css: -------------------------------------------------------------------------------- 1 | .loading { 2 | position: absolute; 3 | top: 50%; 4 | left: 50%; 5 | transform: translateX(-50%) translateY(-50%); 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Loading/index.module.css.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IndexModuleCssNamespace { 2 | export interface IIndexModuleCss { 3 | loading: string; 4 | } 5 | } 6 | 7 | declare const IndexModuleCssModule: IndexModuleCssNamespace.IIndexModuleCss & { 8 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 9 | locals: IndexModuleCssNamespace.IIndexModuleCss; 10 | }; 11 | 12 | export = IndexModuleCssModule; 13 | -------------------------------------------------------------------------------- /src/components/Loading/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import styles from "./index.module.css"; 4 | 5 | /** 6 | * Just a loading spinner. 7 | */ 8 | export const Loading = React.memo((inProps) => { 9 | return ( 10 | 18 | 19 | 20 | 30 | 40 | 41 | 42 | 52 | 62 | 63 | 64 | 65 | ); 66 | }); 67 | 68 | Loading.displayName = "Loading"; 69 | -------------------------------------------------------------------------------- /src/components/ScrollFollow/README.md: -------------------------------------------------------------------------------- 1 | `ScrollFollow` is a higher-order component (HOC) that aims to simplify 2 | toggling a `LazyLog`'s "follow" functionality based on user scrolling. 3 | The `ScrollFollow` component accepts a render prop function which should return a 4 | component to render based on the function's arguments. 5 | 6 | ```js 7 | import { LazyLog } from ".."; 8 | const url = "https://runkit.io/eliperelman/streaming-endpoint/branches/master"; 9 | 10 |
11 | ( 14 | 15 | )} 16 | /> 17 |
18 | ``` 19 | -------------------------------------------------------------------------------- /src/components/ScrollFollow/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment, ReactNode } from "react"; 2 | 3 | export interface ScrollFollowRenderProps { 4 | onScroll: (args: { 5 | scrollTop: number; 6 | scrollHeight: number; 7 | clientHeight: number; 8 | }) => void; 9 | follow: boolean; 10 | startFollowing: () => void; 11 | stopFollowing: () => void; 12 | } 13 | 14 | export interface ScrollFollowProps { 15 | /** 16 | * Render a component based on the function's arguments 17 | * 18 | * - `follow: bool` This value is `true` or `false` 19 | * based on whether the component should be auto-following. 20 | * This value can be passed directly to the Lazy component's 21 | * `follow` prop. 22 | * 23 | * - `onScroll: func`: This function is used to listen for scrolling 24 | * events and turn off auto-following (`follow`). 25 | * This value can be passed directly to the Lazy component's 26 | * `onScroll` prop. 27 | * 28 | * - `startFollowing: func`: A helper function for manually re-starting 29 | * `follow`ing. Is not used by a Lazy component; 30 | * rather this can be invoked whenever you need to turn back on 31 | * auto-following, but cannot reliably do this from the `startFollowing` 32 | * prop. e.g `startFollowing();` 33 | * 34 | * - `stopFollowing: func`: A helper function for manually stopping 35 | * `follow`ing. Is not used by a Lazy component; 36 | * rather this can be invoked whenever you need to turn off 37 | * auto-following, but cannot reliably do this from the `startFollowing` 38 | * prop. e.g `stopFollowing();` 39 | */ 40 | render: (props: ScrollFollowRenderProps) => ReactNode; 41 | /** 42 | * The initial follow action; defaults to `false`. 43 | * The value provided here will inform the initial `follow` 44 | * property passed to the child function. 45 | */ 46 | startFollowing?: boolean | undefined; 47 | } 48 | type ScrollFollowState = { 49 | follow: boolean; 50 | }; 51 | 52 | export default class ScrollFollow extends Component< 53 | ScrollFollowProps, 54 | ScrollFollowState 55 | > { 56 | static defaultProps = { 57 | startFollowing: false, 58 | }; 59 | 60 | // Initial state is the startFollowing prop. 61 | state: ScrollFollowState = { 62 | follow: this.props.startFollowing ?? false, 63 | }; 64 | 65 | handleScroll = ({ scrollTop, scrollHeight, clientHeight }: any) => { 66 | // Only update the state if the content exceeds the available space 67 | // otherwise the follow will be disabled before the screen is filled. 68 | if (scrollHeight > clientHeight) { 69 | if ( 70 | this.state.follow && 71 | scrollHeight - scrollTop !== clientHeight 72 | ) { 73 | // Disable follow, if we're currently following and have manually scrolled away from the bottom. 74 | this.setState({ follow: false }); 75 | } else if ( 76 | !this.state.follow && 77 | scrollHeight - scrollTop === clientHeight 78 | ) { 79 | // Enable follow if we are not currently following and have scrolled to the bottom. 80 | this.setState({ follow: true }); 81 | } 82 | } 83 | }; 84 | 85 | startFollowing = () => { 86 | this.setState({ follow: true }); 87 | }; 88 | 89 | stopFollowing = () => { 90 | this.setState({ follow: false }); 91 | }; 92 | 93 | render() { 94 | const { render } = this.props; 95 | const { follow } = this.state; 96 | 97 | return ( 98 | 99 | {render({ 100 | follow, 101 | onScroll: this.handleScroll, 102 | startFollowing: this.startFollowing, 103 | stopFollowing: this.stopFollowing, 104 | })} 105 | 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/components/SearchBar/ArrowIcons/DownArrowIcon/index.module.css: -------------------------------------------------------------------------------- 1 | .downArrowIcon { 2 | height: 15px; 3 | cursor: pointer; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/SearchBar/ArrowIcons/DownArrowIcon/index.module.css.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IndexModuleCssNamespace { 2 | export interface IIndexModuleCss { 3 | downArrowIcon: string; 4 | } 5 | } 6 | 7 | declare const IndexModuleCssModule: IndexModuleCssNamespace.IIndexModuleCss & { 8 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 9 | locals: IndexModuleCssNamespace.IIndexModuleCss; 10 | }; 11 | 12 | export = IndexModuleCssModule; 13 | -------------------------------------------------------------------------------- /src/components/SearchBar/ArrowIcons/DownArrowIcon/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import styles from "./index.module.css"; 4 | 5 | export const DownArrowIcon = React.memo( 6 | (props: React.SVGProps) => { 7 | return ( 8 | 14 | 15 | 16 | ); 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /src/components/SearchBar/ArrowIcons/UpArrowIcon/index.module.css: -------------------------------------------------------------------------------- 1 | .upArrowIcon { 2 | height: 15px; 3 | cursor: pointer; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/SearchBar/ArrowIcons/UpArrowIcon/index.module.css.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IndexModuleCssNamespace { 2 | export interface IIndexModuleCss { 3 | upArrowIcon: string; 4 | } 5 | } 6 | 7 | declare const IndexModuleCssModule: IndexModuleCssNamespace.IIndexModuleCss & { 8 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 9 | locals: IndexModuleCssNamespace.IIndexModuleCss; 10 | }; 11 | 12 | export = IndexModuleCssModule; 13 | -------------------------------------------------------------------------------- /src/components/SearchBar/ArrowIcons/UpArrowIcon/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import styles from "./index.module.css"; 4 | 5 | export const UpArrowIcon = React.memo( 6 | (props: React.SVGProps) => { 7 | return ( 8 | 14 | 15 | 16 | ); 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /src/components/SearchBar/FilterLinesIcon/README.md: -------------------------------------------------------------------------------- 1 | ```js 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /src/components/SearchBar/FilterLinesIcon/index.module.css: -------------------------------------------------------------------------------- 1 | .filterLinesIcon { 2 | height: 15px; 3 | cursor: pointer; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/SearchBar/FilterLinesIcon/index.module.css.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IndexModuleCssNamespace { 2 | export interface IIndexModuleCss { 3 | filterLinesIcon: string; 4 | } 5 | } 6 | 7 | declare const IndexModuleCssModule: IndexModuleCssNamespace.IIndexModuleCss & { 8 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 9 | locals: IndexModuleCssNamespace.IIndexModuleCss; 10 | }; 11 | 12 | export = IndexModuleCssModule; 13 | -------------------------------------------------------------------------------- /src/components/SearchBar/FilterLinesIcon/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import styles from "./index.module.css"; 4 | 5 | export const FilterLinesIcon = React.memo( 6 | (props: React.SVGProps) => { 7 | return ( 8 | 14 | 15 | 16 | ); 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /src/components/SearchBar/README.md: -------------------------------------------------------------------------------- 1 | ```js 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /src/components/SearchBar/index.module.css: -------------------------------------------------------------------------------- 1 | .searchBar { 2 | display: flex; 3 | align-items: center; 4 | justify-content: flex-end; 5 | font-family: "Monaco", monospace; 6 | font-size: 12px; 7 | background-color: #222222; 8 | color: #d6d6d6; 9 | padding: 10px; 10 | } 11 | 12 | .searchInput { 13 | background-color: #464646; 14 | color: #d6d6d6; 15 | height: 20px; 16 | min-width: 200px; 17 | font-size: 12px; 18 | padding: 2px 5px; 19 | border: 1px solid #4e4e4e; 20 | margin-right: 10px; 21 | } 22 | 23 | .active { 24 | color: #d6d6d6; 25 | fill: #d6d6d6; 26 | } 27 | 28 | .clickable:hover { 29 | border-radius: 5px; 30 | background: #666666; 31 | } 32 | 33 | .inactive { 34 | color: #464646; 35 | fill: #464646; 36 | padding: 3px; 37 | padding-bottom: 1px; 38 | } 39 | 40 | .button { 41 | background: none; 42 | border: none; 43 | margin-right: 10px; 44 | } 45 | -------------------------------------------------------------------------------- /src/components/SearchBar/index.module.css.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IndexModuleCssNamespace { 2 | export interface IIndexModuleCss { 3 | active: string; 4 | button: string; 5 | clickable: string; 6 | inactive: string; 7 | searchBar: string; 8 | searchInput: string; 9 | } 10 | } 11 | 12 | declare const IndexModuleCssModule: IndexModuleCssNamespace.IIndexModuleCss & { 13 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 14 | locals: IndexModuleCssNamespace.IIndexModuleCss; 15 | }; 16 | 17 | export = IndexModuleCssModule; 18 | -------------------------------------------------------------------------------- /src/components/Utils/ansiparse.ts: -------------------------------------------------------------------------------- 1 | import { LinePartCss } from "../LinePart"; 2 | 3 | /* eslint-disable no-plusplus, no-continue */ 4 | type AnisMap = { 5 | [key: string]: string; 6 | }; 7 | const foregroundColors: AnisMap = { 8 | "30": "black", 9 | "31": "red", 10 | "32": "green", 11 | "33": "yellow", 12 | "34": "blue", 13 | "35": "magenta", 14 | "36": "cyan", 15 | "37": "white", 16 | "90": "grey", 17 | }; 18 | const backgroundColors: AnisMap = { 19 | "40": "black", 20 | "41": "red", 21 | "42": "green", 22 | "43": "yellow", 23 | "44": "blue", 24 | "45": "magenta", 25 | "46": "cyan", 26 | "47": "white", 27 | }; 28 | const styles: AnisMap = { 29 | "1": "bold", 30 | "3": "italic", 31 | "4": "underline", 32 | }; 33 | const eraseChar = (matchingText: string, result: any[]): any[] => { 34 | if (matchingText.length) { 35 | return [matchingText.substr(0, matchingText.length - 1), result]; 36 | } else if (result.length) { 37 | const index = result.length - 1; 38 | const { text } = result[index]; 39 | const newResult = 40 | text.length === 1 41 | ? result.slice(0, result.length - 1) 42 | : result.map((item, i) => 43 | index === i 44 | ? { ...item, text: text.substr(0, text.length - 1) } 45 | : item 46 | ); 47 | 48 | return [matchingText, newResult]; 49 | } 50 | 51 | return [matchingText, result]; 52 | }; 53 | 54 | const ansiparse = (str: string): LinePartCss[] => { 55 | let matchingControl = null; 56 | let matchingData = null; 57 | let matchingText = ""; 58 | let ansiState = []; 59 | let result = []; 60 | let state: LinePartCss = {}; 61 | 62 | for (let i = 0; i < str.length; i++) { 63 | if (matchingControl !== null) { 64 | if (matchingControl === "\x1b" && str[i] === "[") { 65 | if (matchingText) { 66 | state.text = matchingText; 67 | result.push(state); 68 | state = { text: "" }; 69 | matchingText = ""; 70 | } 71 | 72 | matchingControl = null; 73 | matchingData = ""; 74 | } else { 75 | matchingText += matchingControl + str[i]; 76 | matchingControl = null; 77 | } 78 | 79 | continue; 80 | } else if (matchingData !== null) { 81 | if (str[i] === ";") { 82 | ansiState.push(matchingData); 83 | matchingData = ""; 84 | } else if (str[i] === "m") { 85 | ansiState.push(matchingData); 86 | matchingData = null; 87 | matchingText = ""; 88 | 89 | for (let a = 0; a < ansiState.length; a++) { 90 | const ansiCode = ansiState[a]; 91 | 92 | if (foregroundColors[ansiCode]) { 93 | state.foreground = foregroundColors[ansiCode]; 94 | } else if (backgroundColors[ansiCode]) { 95 | state.background = backgroundColors[ansiCode]; 96 | } else if (ansiCode === "39") { 97 | delete state.foreground; 98 | } else if (ansiCode === "49") { 99 | delete state.background; 100 | } else if (styles[ansiCode]) { 101 | state[styles[ansiCode]] = true; 102 | } else if (ansiCode === "22") { 103 | state.bold = false; 104 | } else if (ansiCode === "23") { 105 | state.italic = false; 106 | } else if (ansiCode === "24") { 107 | state.underline = false; 108 | } 109 | } 110 | 111 | ansiState = []; 112 | } else { 113 | matchingData += str[i]; 114 | } 115 | 116 | continue; 117 | } 118 | 119 | if (str[i] === "\x1b") { 120 | matchingControl = str[i]; 121 | } else if (str[i] === "\u0008") { 122 | [matchingText, result] = eraseChar(matchingText, result); 123 | } else { 124 | matchingText += str[i]; 125 | } 126 | } 127 | 128 | if (matchingText) { 129 | state.text = matchingText + (matchingControl || ""); 130 | result.push(state); 131 | } 132 | 133 | return result; 134 | }; 135 | 136 | export default ansiparse; 137 | -------------------------------------------------------------------------------- /src/components/Utils/encoding.ts: -------------------------------------------------------------------------------- 1 | export const encode = (value: string | undefined) => 2 | new TextEncoder().encode(value); 3 | export const decode = (value: BufferSource | any) => { 4 | if (!ArrayBuffer.isView(value)) { 5 | value = new Uint8Array([value]); 6 | } 7 | return new TextDecoder("utf-8").decode(value); 8 | }; 9 | -------------------------------------------------------------------------------- /src/components/Utils/eventsource.ts: -------------------------------------------------------------------------------- 1 | import { List } from "immutable"; 2 | import mitt from "mitt"; 3 | 4 | import { EventSourceOptions } from "../LazyLog"; 5 | import { encode } from "./encoding"; 6 | import { bufferConcat, convertBufferToLines } from "./utils"; 7 | 8 | export default (url: string | URL, options: EventSourceOptions) => { 9 | const { withCredentials, onOpen, onClose, onError, formatMessage } = 10 | options; 11 | const emitter = mitt(); 12 | let encodedLog = new Uint8Array(); 13 | let overage: any = null; 14 | 15 | emitter.on("data", (data) => { 16 | encodedLog = bufferConcat( 17 | encodedLog, 18 | encode(data as unknown as string) 19 | ); 20 | 21 | const { lines, remaining } = convertBufferToLines( 22 | encode(data as unknown as string), 23 | overage 24 | ); 25 | 26 | overage = remaining; 27 | 28 | emitter.emit("update", { lines, encodedLog }); 29 | }); 30 | 31 | emitter.on("done", () => { 32 | if (overage) { 33 | emitter.emit("update", { lines: List.of(overage), encodedLog }); 34 | } 35 | 36 | emitter.emit("end", encodedLog); 37 | }); 38 | 39 | emitter.on("start", () => { 40 | try { 41 | // Create EventSource - it will handle reconnection automatically 42 | const eventSource = new EventSource(url, { withCredentials }); 43 | 44 | eventSource.addEventListener("open", (e) => { 45 | // relay on open events if a handler is registered 46 | onOpen && onOpen(e, eventSource); 47 | }); 48 | 49 | // Note: EventSource API doesn't have a 'close' event in the spec 50 | // This listener will never fire, but we keep it for backwards compatibility 51 | // in case any code depends on the onClose callback 52 | eventSource.addEventListener("close", (e) => { 53 | onClose && onClose(e); 54 | }); 55 | 56 | eventSource.addEventListener("error", (err) => { 57 | onError && onError(err); 58 | // EventSource will automatically reconnect after ~3 seconds 59 | // unless options.reconnect is false. 60 | // 61 | // EventSource will also not reconnect if the server sends a 62 | // 204 No Content response. 63 | if (options.reconnect === false) { 64 | eventSource.close(); 65 | } 66 | }); 67 | 68 | eventSource.addEventListener("message", (e) => { 69 | let msg = formatMessage ? formatMessage(e.data) : e.data; 70 | if (typeof msg !== "string") { 71 | return; 72 | } 73 | // add a new line character between each message if one doesn't exist. 74 | // this allows our search index to properly distinguish new lines. 75 | msg = msg.endsWith("\n") ? msg : `${msg}\n`; 76 | 77 | emitter.emit("data", msg); 78 | }); 79 | 80 | emitter.on("abort", () => { 81 | // Close the EventSource when component unmounts 82 | eventSource.close(); 83 | }); 84 | } catch (err) { 85 | emitter.emit("error", err); 86 | } 87 | }); 88 | 89 | return emitter; 90 | }; 91 | -------------------------------------------------------------------------------- /src/components/Utils/request.ts: -------------------------------------------------------------------------------- 1 | import mitt from "mitt"; 2 | 3 | import { convertBufferToLines } from "./utils"; 4 | 5 | const fetcher = Promise.resolve().then(() => globalThis.fetch); 6 | 7 | export default (url: RequestInfo | URL, options: RequestInit) => { 8 | const emitter = mitt(); 9 | 10 | emitter.on("start", async () => { 11 | try { 12 | const fetch = await fetcher; 13 | const response = await fetch( 14 | url, 15 | Object.assign({ credentials: "omit" }, options) 16 | ); 17 | 18 | if (!response.ok) { 19 | const error = new Error(response.statusText); 20 | 21 | // @ts-ignore 22 | error["status"] = response.status; 23 | emitter.emit("error", error); 24 | 25 | return; 26 | } 27 | 28 | const arrayBuffer = await response.arrayBuffer(); 29 | const encodedLog = new Uint8Array(arrayBuffer); 30 | const { lines } = convertBufferToLines(encodedLog); 31 | 32 | emitter.emit("update", { 33 | lines: lines, 34 | }); 35 | emitter.emit("end", encodedLog); 36 | } catch (err) { 37 | emitter.emit("error", err); 38 | } 39 | }); 40 | 41 | return emitter; 42 | }; 43 | -------------------------------------------------------------------------------- /src/components/Utils/search.ts: -------------------------------------------------------------------------------- 1 | import { decode, encode } from "./encoding"; 2 | import { getLinesLengthRanges } from "./utils"; 3 | 4 | /** 5 | * Implements the Knuth-Morris-Pratt (KMP) string searching algorithm. 6 | * This function searches for occurrences of a keyword within a given log. 7 | * 8 | * @param {string | undefined} rawKeywords - The search term to look for. 9 | * @param {Uint8Array} rawLog - The log data to search within. 10 | * @returns {number[]} An array of indices where the keyword is found in the log. 11 | */ 12 | export const searchIndexes = ( 13 | rawKeywords: string | undefined, 14 | rawLog: Uint8Array 15 | ) => { 16 | // Encode the keywords for byte-level comparison 17 | const keywords = Array.from(encode(rawKeywords)); 18 | // Initialize the KMP failure function table 19 | const table = [-1, 0]; 20 | const keywordsLength = keywords.length; 21 | const fileLength = rawLog.length; 22 | const maxKeywordsIndex = keywordsLength - 1; 23 | let keywordsIndex = 0; 24 | let fileIndex = 0; 25 | let index = 0; 26 | let position = 2; 27 | 28 | // Build the KMP failure function table 29 | // This preprocessing step takes O(keywordsLength) time 30 | while (position < keywordsLength) { 31 | if (keywords[position - 1] === keywords[keywordsIndex]) { 32 | keywordsIndex += 1; 33 | table[position] = keywordsIndex; 34 | position += 1; 35 | } else if (keywordsIndex > 0) { 36 | keywordsIndex = table[keywordsIndex]; 37 | } else { 38 | table[position] = 0; 39 | position += 1; 40 | } 41 | } 42 | 43 | const results = []; 44 | 45 | // Perform the KMP search 46 | // This main search step takes O(fileLength) time 47 | while (fileIndex + index < fileLength) { 48 | if (keywords[index] === rawLog[fileIndex + index]) { 49 | if (index === maxKeywordsIndex) { 50 | // Found a match, store the starting index 51 | results.push(fileIndex); 52 | } 53 | index += 1; 54 | } else if (table[index] > -1) { 55 | // Partial match, use the failure function to skip comparisons 56 | fileIndex = fileIndex + index - table[index]; 57 | index = table[index]; 58 | } else { 59 | // Mismatch, move to the next character in the file 60 | index = 0; 61 | fileIndex += 1; 62 | } 63 | } 64 | 65 | return results; 66 | }; 67 | 68 | /** 69 | * Searches for keywords within log lines, handling case sensitivity. 70 | * 71 | * @param {string | undefined} rawKeywords - The search term to look for. 72 | * @param {Uint8Array} rawLog - The log data to search within. 73 | * @param {boolean} isCaseInsensitive - Whether the search should be case-insensitive. 74 | * @returns {number[]} An array of line numbers where the keyword is found. 75 | */ 76 | export const searchLines = ( 77 | rawKeywords: string | undefined, 78 | rawLog: Uint8Array, 79 | isCaseInsensitive: boolean 80 | ) => { 81 | let keywords = rawKeywords; 82 | let log = rawLog; 83 | let decodedLog = decode(log); 84 | 85 | // Handle case sensitivity 86 | if (isCaseInsensitive) { 87 | keywords = keywords?.toLowerCase(); 88 | decodedLog = decodedLog.toLowerCase(); 89 | } 90 | // Ensure the log ends with a newline for consistent line handling 91 | decodedLog = decodedLog.endsWith("\n") ? decodedLog : decodedLog + "\n"; 92 | log = encode(decodedLog); 93 | 94 | // Perform the search 95 | const results = searchIndexes(keywords, log); 96 | const linesRanges = getLinesLengthRanges(log); 97 | const maxLineRangeIndex = linesRanges.length; 98 | const maxResultIndex = results.length; 99 | const resultLines = []; 100 | let lineRangeIndex = 0; 101 | let resultIndex = 0; 102 | let lineRange; 103 | let result; 104 | 105 | // Map search results to line numbers 106 | while (lineRangeIndex < maxLineRangeIndex) { 107 | lineRange = linesRanges[lineRangeIndex]; 108 | 109 | while (resultIndex < maxResultIndex) { 110 | result = results[resultIndex]; 111 | 112 | if (result <= lineRange) { 113 | // The search result is within the current line 114 | resultLines.push(lineRangeIndex + 1); 115 | resultIndex += 1; 116 | } else { 117 | // Move to the next line 118 | break; 119 | } 120 | } 121 | 122 | lineRangeIndex += 1; 123 | } 124 | 125 | return resultLines; 126 | }; 127 | -------------------------------------------------------------------------------- /src/components/Utils/stream.ts: -------------------------------------------------------------------------------- 1 | import { List } from "immutable"; 2 | import mitt, { Emitter, EventType } from "mitt"; 3 | 4 | import { bufferConcat, convertBufferToLines } from "./utils"; 5 | 6 | const fetcher = Promise.resolve().then(() => globalThis.fetch); 7 | 8 | export const recurseReaderAsEvent: any = async ( 9 | reader: ReadableStreamDefaultReader, 10 | emitter: Emitter> 11 | ) => { 12 | const result = await reader.read(); 13 | 14 | if (result.value) { 15 | emitter.emit("data", result.value); 16 | } 17 | 18 | if (!result.done) { 19 | return recurseReaderAsEvent(reader, emitter); 20 | } 21 | 22 | emitter.emit("done"); 23 | }; 24 | 25 | export default (url: RequestInfo | URL, options: any) => { 26 | const emitter = mitt(); 27 | let overage: any = null; 28 | let encodedLog = new Uint8Array(); 29 | 30 | emitter.on("data", (data: any) => { 31 | encodedLog = bufferConcat(encodedLog, new Uint8Array(data)); 32 | 33 | const { lines, remaining } = convertBufferToLines(data, overage); 34 | 35 | overage = remaining; 36 | emitter.emit("update", { lines, encodedLog }); 37 | }); 38 | 39 | emitter.on("done", () => { 40 | if (overage) { 41 | emitter.emit("update", { lines: List.of(overage), encodedLog }); 42 | } 43 | 44 | emitter.emit("end", encodedLog); 45 | }); 46 | 47 | emitter.on("start", async () => { 48 | try { 49 | const fetch = await fetcher; 50 | const response = await fetch( 51 | url, 52 | Object.assign({ credentials: "omit" }, options) 53 | ); 54 | 55 | if (!response.ok) { 56 | const error = new Error(response.statusText); 57 | 58 | // @ts-ignore 59 | error["status"] = response.status; 60 | emitter.emit("error", error); 61 | 62 | return; 63 | } 64 | 65 | const reader = response.body?.getReader(); 66 | 67 | emitter.on("abort", () => reader?.cancel("ABORTED")); 68 | 69 | return recurseReaderAsEvent(reader!, emitter); 70 | } catch (err) { 71 | emitter.emit("error", err); 72 | } 73 | }); 74 | 75 | return emitter; 76 | }; 77 | -------------------------------------------------------------------------------- /src/components/Utils/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { parseLinks } from "./utils"; 2 | 3 | describe("Parsing lines with links", () => { 4 | it("should be able to parse links wrapped in parentheses.", () => { 5 | const lines = [ 6 | { 7 | text: "(https://example.com)", 8 | }, 9 | ]; 10 | const links = parseLinks(lines); 11 | expect(links.length).toBe(4); 12 | expect(links[0]?.text).toBeFalsy(); 13 | expect(links[1]?.text).toBe("("); 14 | expect(links[2]?.text?.endsWith(")")).toBeFalsy(); 15 | expect(links[3]?.text).toBe(")"); 16 | }); 17 | 18 | it("should be able to parse links starting with a parenthesis.", () => { 19 | const lines = [ 20 | { 21 | text: "(https://example.com", 22 | }, 23 | ]; 24 | const links = parseLinks(lines); 25 | expect(links.length).toBe(3); 26 | expect(links[0]?.text).toBeFalsy(); 27 | expect(links[1]?.text).toBe("("); 28 | expect(links[2]?.text?.endsWith(")")).toBeFalsy(); 29 | }); 30 | 31 | it("should be able to parse links with a trailing parenthesis.", () => { 32 | const lines = [ 33 | { 34 | text: "https://example.com)", 35 | }, 36 | ]; 37 | const links = parseLinks(lines); 38 | expect(links.length).toBe(3); 39 | expect(links[0]?.text).toBeFalsy(); // 40 | expect(links[1]?.text?.endsWith(")")).toBeFalsy(); // 41 | expect(links[2]?.text).toBe(")"); 42 | }); 43 | }); 44 | 45 | describe("", () => { 46 | it("", () => { 47 | const lines = [ 48 | { 49 | text: "aaa bbb ccc", 50 | }, 51 | { 52 | text: "wrap1 'http://bla.alb/add' wrap2 wrap3", 53 | }, 54 | { 55 | text: "�[31m�[1m>�[22m�[2m�[39m�[90m �[36mthis�[39m�[33m.�[39mextend(�[36mthis", 56 | }, 57 | ]; 58 | const links = parseLinks(lines); 59 | expect(links[0]?.text).toBe("aaa bbb ccc"); 60 | // 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/components/Utils/websocket.ts: -------------------------------------------------------------------------------- 1 | import { List } from "immutable"; 2 | import mitt from "mitt"; 3 | 4 | import { WebsocketOptions } from "../LazyLog"; 5 | import { encode } from "./encoding"; 6 | import { bufferConcat, convertBufferToLines } from "./utils"; 7 | 8 | export default (url: string | URL, options: WebsocketOptions) => { 9 | const { onOpen, onClose, onError, formatMessage } = options; 10 | const emitter = mitt(); 11 | let encodedLog = new Uint8Array(); 12 | let overage: any = null; 13 | let aborted: boolean = false; 14 | 15 | emitter.on("data", (data) => { 16 | encodedLog = bufferConcat( 17 | encodedLog, 18 | encode(data as unknown as string) 19 | ); 20 | 21 | const { lines, remaining } = convertBufferToLines( 22 | encode(data as unknown as string), 23 | overage 24 | ); 25 | 26 | overage = remaining; 27 | 28 | emitter.emit("update", { lines, encodedLog }); 29 | }); 30 | 31 | emitter.on("done", () => { 32 | if (overage) { 33 | emitter.emit("update", { lines: List.of(overage), encodedLog }); 34 | } 35 | 36 | emitter.emit("end", encodedLog); 37 | }); 38 | 39 | emitter.on("start", () => { 40 | try { 41 | // try to connect to websocket 42 | const socket = new WebSocket(url); 43 | 44 | socket.addEventListener("open", (e) => { 45 | // relay on open events if a handler is registered 46 | onOpen && onOpen(e, socket); 47 | }); 48 | socket.addEventListener("close", (e) => { 49 | onClose && onClose(e); 50 | if(!aborted && options.reconnect) { 51 | const timeout = options.reconnectWait ?? 1; 52 | setTimeout(() => emitter.emit("start"), timeout*1000); 53 | } 54 | }); 55 | 56 | socket.addEventListener("error", (err) => { 57 | onError && onError(err); 58 | }); 59 | 60 | socket.addEventListener("message", (e) => { 61 | let msg = formatMessage ? formatMessage(e.data) : e.data; 62 | 63 | if (typeof msg !== "string") { 64 | return; 65 | } 66 | // add a new line character between each message if one doesn't exist. 67 | // this allows our search index to properly distinguish new lines. 68 | msg = msg.endsWith("\n") ? msg : `${msg}\n`; 69 | 70 | emitter.emit("data", msg); 71 | }); 72 | 73 | emitter.on("abort", () => { 74 | aborted = true; 75 | socket.close() 76 | }); 77 | } catch (err) { 78 | emitter.emit("error", err); 79 | } 80 | }); 81 | 82 | return emitter; 83 | }; 84 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as LazyLog } from "./LazyLog"; 2 | export { default as Line } from "./Line"; 3 | export { default as LineContent } from "./LineContent"; 4 | export { default as LineGutter } from "./LineGutter"; 5 | export { default as LineNumber } from "./LineNumber"; 6 | export { default as LinePart } from "./LinePart"; 7 | export { Loading } from "./Loading"; 8 | export { default as ScrollFollow } from "./ScrollFollow"; 9 | export { default as SearchBar } from "./SearchBar"; 10 | export { DownArrowIcon } from "./SearchBar/ArrowIcons/DownArrowIcon"; 11 | export { UpArrowIcon } from "./SearchBar/ArrowIcons/UpArrowIcon"; 12 | export { FilterLinesIcon } from "./SearchBar/FilterLinesIcon"; 13 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; -------------------------------------------------------------------------------- /src/stories/assets/accessibility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/src/stories/assets/accessibility.png -------------------------------------------------------------------------------- /src/stories/assets/accessibility.svg: -------------------------------------------------------------------------------- 1 | 2 | Accessibility 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/stories/assets/addon-library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/src/stories/assets/addon-library.png -------------------------------------------------------------------------------- /src/stories/assets/assets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/src/stories/assets/assets.png -------------------------------------------------------------------------------- /src/stories/assets/avif-test-image.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/src/stories/assets/avif-test-image.avif -------------------------------------------------------------------------------- /src/stories/assets/code-brackets.svg: -------------------------------------------------------------------------------- 1 | illustration/code-brackets -------------------------------------------------------------------------------- /src/stories/assets/colors.svg: -------------------------------------------------------------------------------- 1 | illustration/colors -------------------------------------------------------------------------------- /src/stories/assets/comments.svg: -------------------------------------------------------------------------------- 1 | illustration/comments -------------------------------------------------------------------------------- /src/stories/assets/context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/src/stories/assets/context.png -------------------------------------------------------------------------------- /src/stories/assets/direction.svg: -------------------------------------------------------------------------------- 1 | illustration/direction -------------------------------------------------------------------------------- /src/stories/assets/discord.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/stories/assets/docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/src/stories/assets/docs.png -------------------------------------------------------------------------------- /src/stories/assets/figma-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/src/stories/assets/figma-plugin.png -------------------------------------------------------------------------------- /src/stories/assets/flow.svg: -------------------------------------------------------------------------------- 1 | illustration/flow -------------------------------------------------------------------------------- /src/stories/assets/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/stories/assets/plugin.svg: -------------------------------------------------------------------------------- 1 | illustration/plugin -------------------------------------------------------------------------------- /src/stories/assets/repo.svg: -------------------------------------------------------------------------------- 1 | illustration/repo -------------------------------------------------------------------------------- /src/stories/assets/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/src/stories/assets/share.png -------------------------------------------------------------------------------- /src/stories/assets/stackalt.svg: -------------------------------------------------------------------------------- 1 | illustration/stackalt -------------------------------------------------------------------------------- /src/stories/assets/styling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/src/stories/assets/styling.png -------------------------------------------------------------------------------- /src/stories/assets/testing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/src/stories/assets/testing.png -------------------------------------------------------------------------------- /src/stories/assets/theming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melloware/react-logviewer/6ec87860f0e0c1feb48b7dfbd96a701c324c5f95/src/stories/assets/theming.png -------------------------------------------------------------------------------- /src/stories/assets/tutorials.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/stories/assets/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/e2e/test-app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LazyLog EventSource Test 7 | 8 | 9 | 17 | 44 | 45 | 46 |

LazyLog EventSource Test

47 | 48 |
Ready
49 |
50 | 51 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /tests/e2e/test-server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | 4 | const app = express(); 5 | app.use(cors()); 6 | 7 | // Track active connections for cleanup 8 | const activeConnections = new Set(); 9 | 10 | // Store sent event IDs per client for testing Last-Event-ID 11 | const clientEventIds = new Map(); 12 | 13 | // Basic EventSource endpoint 14 | app.get('/sse/basic', (req, res) => { 15 | res.writeHead(200, { 16 | 'Content-Type': 'text/event-stream', 17 | 'Cache-Control': 'no-cache', 18 | 'Connection': 'keep-alive', 19 | }); 20 | 21 | activeConnections.add(res); 22 | 23 | let counter = 0; 24 | const interval = setInterval(() => { 25 | counter++; 26 | res.write(`data: Log line ${counter}\n\n`); 27 | }, 100); 28 | 29 | req.on('close', () => { 30 | clearInterval(interval); 31 | activeConnections.delete(res); 32 | }); 33 | }); 34 | 35 | // EventSource endpoint that tracks Last-Event-ID 36 | app.get('/sse/with-id', (req, res) => { 37 | const clientId = req.query.clientId || 'default'; 38 | const lastEventId = req.headers['last-event-id']; 39 | 40 | res.writeHead(200, { 41 | 'Content-Type': 'text/event-stream', 42 | 'Cache-Control': 'no-cache', 43 | 'Connection': 'keep-alive', 44 | }); 45 | 46 | activeConnections.add(res); 47 | 48 | // Start from last ID or 0 49 | let counter = lastEventId ? parseInt(lastEventId) : 0; 50 | 51 | // Send a reconnect message if we're resuming 52 | if (lastEventId) { 53 | res.write(`data: Resumed from event ${lastEventId}\n\n`); 54 | } 55 | 56 | const interval = setInterval(() => { 57 | counter++; 58 | res.write(`id: ${counter}\n`); 59 | res.write(`data: Event ${counter}\n\n`); 60 | clientEventIds.set(clientId, counter); 61 | }, 100); 62 | 63 | req.on('close', () => { 64 | clearInterval(interval); 65 | activeConnections.delete(res); 66 | }); 67 | }); 68 | 69 | // Endpoint that simulates connection errors 70 | app.get('/sse/flaky', (req, res) => { 71 | const errorAfter = parseInt(req.query.errorAfter || '5'); 72 | 73 | res.writeHead(200, { 74 | 'Content-Type': 'text/event-stream', 75 | 'Cache-Control': 'no-cache', 76 | 'Connection': 'keep-alive', 77 | }); 78 | 79 | activeConnections.add(res); 80 | 81 | let counter = 0; 82 | const interval = setInterval(() => { 83 | counter++; 84 | if (counter === errorAfter) { 85 | // Abruptly close connection to simulate network error 86 | res.destroy(); 87 | clearInterval(interval); 88 | activeConnections.delete(res); 89 | } else { 90 | res.write(`data: Message ${counter}\n\n`); 91 | } 92 | }, 100); 93 | 94 | req.on('close', () => { 95 | clearInterval(interval); 96 | activeConnections.delete(res); 97 | }); 98 | }); 99 | 100 | // Endpoint that returns 204 No Content immediately 101 | app.get('/sse/204', (req, res) => { 102 | // Send 204 No Content - this should close EventSource without reconnection 103 | res.status(204).end(); 104 | }); 105 | 106 | // Endpoint that closes connection cleanly after a few messages 107 | app.get('/sse/with-close', (req, res) => { 108 | const messageCount = parseInt(req.query.messages || '3'); 109 | 110 | res.writeHead(200, { 111 | 'Content-Type': 'text/event-stream', 112 | 'Cache-Control': 'no-cache', 113 | 'Connection': 'keep-alive', 114 | }); 115 | 116 | activeConnections.add(res); 117 | 118 | let counter = 0; 119 | const interval = setInterval(() => { 120 | counter++; 121 | if (counter <= messageCount) { 122 | res.write(`data: Message ${counter} before close\n\n`); 123 | } else { 124 | // Close the connection cleanly 125 | // This simulates server-side close which should trigger 'close' event 126 | clearInterval(interval); 127 | activeConnections.delete(res); 128 | res.end(); 129 | } 130 | }, 100); 131 | 132 | req.on('close', () => { 133 | clearInterval(interval); 134 | activeConnections.delete(res); 135 | }); 136 | }); 137 | 138 | // Endpoint to check active connections (for testing cleanup) 139 | app.get('/test/connections', (req, res) => { 140 | res.json({ activeConnections: activeConnections.size }); 141 | }); 142 | 143 | // Endpoint to get last event ID for a client 144 | app.get('/test/last-event-id/:clientId', (req, res) => { 145 | const lastId = clientEventIds.get(req.params.clientId) || 0; 146 | res.json({ lastEventId: lastId }); 147 | }); 148 | 149 | // Static file serving for test app 150 | app.use(express.static('public')); 151 | 152 | // Cleanup on server shutdown 153 | const PORT = process.env.PORT || 54321; 154 | const server = app.listen(PORT, () => { 155 | console.log(`Test server running on port ${PORT}`); 156 | }); 157 | 158 | process.on('SIGTERM', () => { 159 | // Close all active connections 160 | activeConnections.forEach(res => res.end()); 161 | server.close(); 162 | }); 163 | 164 | module.exports = { app, server }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Default 4 | "target": "es2020", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | 10 | // Added 11 | "jsx": "react", 12 | "module": "ESNext", 13 | "declaration": true, 14 | "declarationDir": "types", 15 | "sourceMap": true, 16 | "outDir": "dist", 17 | "moduleResolution": "node", 18 | "allowSyntheticDefaultImports": true, 19 | "emitDeclarationOnly": true 20 | }, 21 | "exclude": [ 22 | "**/*.test.ts", 23 | "**/*.test.tsx", 24 | "**/*.spec.ts", 25 | "**/*.spec.tsx", 26 | "tests/**/*" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | --------------------------------------------------------------------------------