diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..f47ef9586
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,13 @@
+# EditorConfig http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# All files
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.eslintrc b/.eslintrc
index 570701c76..7cc3223f3 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,11 +1,22 @@
{
- "extends": ["airbnb", "plugin:jest/recommended"],
+ "extends": [
+ "plugin:jest/recommended",
+ "plugin:@typescript-eslint/eslint-recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:testing-library/react", // added in for RTL tests
+ "plugin:jest-dom/recommended" // added in for RTL tests
+ ],
"root": true,
- "plugins": ["jest", "react"],
+ "plugins": ["jest", "react", "react-hooks", "@typescript-eslint", "testing-library", "jest-dom"],
"rules": {
"arrow-parens": [2, "as-needed"],
"import/no-unresolved": "off",
- "import/extensions": "off"
+ "import/extensions": "off",
+ "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
+ "react-hooks/exhaustive-deps": "warn", // Checks effect dependencies
+ "react/jsx-filename-extension": [0],
+ "linebreak-style": "off",
+ "max-len": [{ "ignoreComments": true }]
},
"env": {
"es6": true,
@@ -25,4 +36,4 @@
"ecmaVersion": 2018,
"sourceType": "module"
}
-}
\ No newline at end of file
+}
diff --git a/.gitignore b/.gitignore
index 9ae5b953c..aa3509cac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,19 @@
node_modules
+node_modules/
+/node_modules
+./node_modules
.DS_Store
src/extension/build/bundles
package/reactime-*.tgz
-tictactoe
parents
coverage
src/extension/build.zip
src/extension/build.crx
-src/extension/build/key.pem
+src/extension/build.pem
bower_components
+sandboxes/manual-tests/NextJS/.next
+.vscode
+package-lock.json
+yarn.lock
+docs/**/*
+docs/*
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..2fc98bd91
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "reactime-website"]
+ path = reactime-website
+ url = https://github.com/reactimetravel/reactime-website
diff --git a/.npmcheckrc b/.npmcheckrc
new file mode 100644
index 000000000..297d19125
--- /dev/null
+++ b/.npmcheckrc
@@ -0,0 +1,10 @@
+{"depcheck":
+ {
+ "ignoreMatches": [
+ "css-loader",
+ "sass-loader",
+ "style-loader",
+ "typedoc-webpack-plugin"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 000000000..9bdd4961d
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,9 @@
+{
+ "printWidth": 100,
+ "semi": true,
+ "singleQuote": true,
+ "jsxSingleQuote": true,
+ "tabWidth": 2,
+ "bracketSpacing": true,
+ "trailingComma": "all"
+}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 0655bdd57..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-services:
- - docker
-script:
- - docker-compose up --abort-on-container-exit
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..86a390f5d
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "eslint.enable": true,
+ "git.ignoreLimitWarning": true
+}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..d63679ccb
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,107 @@
+# Contributing to Reactime :sparkles:
+
+Thank you for your interest in making Reactime even better! :heart_eyes: Your help is invaluable, and we appreciate every contribution, big or small. This guide will walk you through the process of opening issues, creating pull requests, and navigating our workflow.
+
+## New Contributor Guide :hatching_chick:
+
+Whether you’re brand new to open source or a seasoned pro, we encourage you to:
+
+- **Check out our [README](README.md).**
+ It’ll give you a birds-eye view of what Reactime does and how you can get involved.
+- **Explore these helpful resources:**
+ - [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github)
+ - [Set up Git](https://docs.github.com/en/get-started/quickstart/set-up-git)
+ - [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow)
+ - [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests)
+
+## Getting Started :rocket:
+
+If you want to understand the codebase in more detail, take a look at our [Developer Guidelines](src/DEVELOPER_README.md). :confetti_ball:
+
+### Issues :eyes:
+
+#### Creating a New Issue :new:
+
+1. **Check existing issues.**
+ Before opening a new issue, please [search if it already exists](https://github.com/open-source-labs/reactime/issues).
+
+2. **Open your own issue.**
+ If you can’t find an existing issue, feel free to [open a new one](https://github.com/open-source-labs/reactime/issues/new) to report bugs, request features, or suggest improvements.
+
+#### Solving an Issue :wrench:
+
+1. **Pick an issue.**
+ Browse through our [open issues](https://github.com/open-source-labs/reactime/issues). We don’t officially assign them, so if something sparks your interest, go for it!
+
+2. **Open a pull request.**
+ Once you’re ready to propose a fix or feature, you can open a PR referencing the issue you’re solving.
+
+### Make Changes :rainbow:
+
+#### Small Edits in the UI :pencil2:
+
+- Click **Make a contribution** at the bottom of any documentation page to quickly fix typos, broken links, or small wording changes.
+- This will take you directly to the `.md` file, where you can make edits and open a pull request.
+
+#### Larger Changes Locally :computer:
+
+1. **Install Git LFS.**
+ Follow the instructions [here](https://docs.github.com/en/github/managing-large-files/versioning-large-files/installing-git-large-file-storage).
+
+2. **Fork the Repository.**
+
+ - **GitHub Desktop:**
+ [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) walks you through setup. Then, you can [fork the repo](https://github.com/open-source-labs/reactime.git) right from GitHub Desktop!
+ - **Command Line:**
+ [Fork the repo](https://github.com/open-source-labs/reactime.git) and clone your fork locally so you can work on your own copy.
+
+3. **Create a working branch.**
+ Name it something descriptive (e.g., `feature/new-feature` or `fix/typo-in-docs`).
+
+### Commit Your Changes :white_check_mark:
+
+When you’re happy with your updates:
+
+1. **Commit them locally.**
+ Write clear commit messages describing _what_ you changed and _why_.
+
+2. **Push to your branch.**
+ This makes your changes visible on GitHub.
+
+### Open a Pull Request :arrows_counterclockwise:
+
+Once you’ve finished working and pushed your code:
+
+1. **Create the PR.**
+ Click on **Compare & pull request** on your branch to open a new PR.
+
+2. **Fill the “Ready for review” template.**
+ This helps reviewers quickly understand the context and purpose of your changes.
+
+3. **Link Issues.**
+ If your PR fixes or relates to an existing issue, [link it](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) in the PR description.
+
+4. **Allow Maintainer Edits.**
+ Check the box so our team can help update your branch if needed.
+
+5. **Address Feedback.**
+ If reviewers suggest changes, you can:
+
+ - Apply **suggested changes** directly on the GitHub UI.
+ - Make edits in your local branch and push them.
+
+6. **Resolve Conversations.**
+ Mark each PR comment as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations) once you’ve addressed it.
+
+7. **Handle Merge Conflicts.**
+ Check out [this tutorial](https://github.com/skills/resolve-merge-conflicts) if you get stuck.
+
+## Your PR is Merged! :tada:
+
+**Congratulations and thank you!** :dancer: :dancer: Once we merge your PR, your contributions become part of Reactime. We appreciate every contribution, and we hope you’ll stick around for more.
+
+> If you have any further questions or ideas, don’t hesitate to open another issue or join the conversation in the repo!
+
+---
+
+Happy coding and welcome to the Reactime community! :sparkles:
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 7eae5ac62..000000000
--- a/Dockerfile
+++ /dev/null
@@ -1,4 +0,0 @@
-FROM node:10.16.2
-WORKDIR /usr/src/app
-COPY package*.json ./
-RUN npm i
diff --git a/LICENSE b/LICENSE
index d19ddaf80..498003dd9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019 reactime
+Copyright (c) 2025 reactime
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.fr.md b/README.fr.md
new file mode 100644
index 000000000..86ac9d8e2
--- /dev/null
+++ b/README.fr.md
@@ -0,0 +1,379 @@
+
+
+
+Une puissante extension Chrome qui améliore le développement React grâce au débogage avec retour dans le temps et à la surveillance avancée des performances
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## ✨ Fonctionnalités Clés
+
+### 🔍 Visualisation de l'État
+
+- **Vues Multiples** : Visualisez l’état de votre application via des Graphiques de Composants, des Arborescences JSON, des Graphiques de Performances et des Arbres d’Accessibilité
+- **Historique Chronologique** : Suivez l’évolution de l’état dans le temps grâce à une représentation intuitive de l’historique
+- **Métriques Web** : Surveillez en temps réel les métriques de performance essentielles
+- **Aperçus d’Accessibilité** : Analysez l’arbre d’accessibilité de votre application pour chaque changement d’état
+
+
+Sur la page principale, vous disposez de deux choix principaux depuis le menu déroulant :
+
+- **Timejump** : Consultez et naviguez dans l’historique des snapshots de l’état de votre application. Vous pouvez revenir à n’importe quel point dans le temps pour observer l’évolution de l’état au fil des modifications. Vous pouvez également utiliser le bouton de lecture pour rejouer chaque changement d’état automatiquement.
+- **Providers / Consumers** : Comprenez mieux les dépendances de contexte de votre application et leurs interactions grâce à une visualisation des relations entre fournisseurs et consommateurs.
+
+
+
+
+
+
+
+### ⏱️ Débogage avec Retour dans le Temps
+
+- **Snapshots d’État** : Capturez et naviguez à travers l’historique d’état de votre application
+- **Commandes de Lecture** : Rejouez automatiquement les changements d’état avec une vitesse ajustable
+- **Points de Saut** : Naviguez instantanément vers n’importe quel état antérieur
+- **Comparaisons Diff** : Comparez l’état entre différents snapshots
+
+
+
+
+
+
+
+### 📊 Analyse de Performance
+
+- **Métriques de Composants** : Mesurez les temps de rendu et identifiez les goulets d’étranglement
+- **Comparaison de Séries** : Comparez les performances sur différentes séries de changements d’état
+- **Détection de Re-rendu** : Identifiez et corrigez les rendus inutiles
+- **Web Vitals** : Surveillez les Core Web Vitals et d’autres métriques de performance
+
+
+
+### 🔄 Prise en Charge des Frameworks Modernes
+
+
+
+ Compatibilité complète avec Gatsby, Next.js et Remix
+
+
+Prise en charge de TypeScript pour les composants de classe et fonctionnels
+
+
+Prise en charge des Hooks et de l’API Context de React
+
+
+
+
+### 💾 Persistance & Partage d’État
+
+Reactime facilite la sauvegarde et le partage de l’historique d’état de votre application :
+
+- **Exporter l’Historique d’État** : Enregistrez vos snapshots sous forme de fichier JSON pour une analyse ultérieure ou pour les partager
+- **Importer des Sessions Précédentes** : Chargez des snapshots enregistrés précédemment pour comparer les changements d’état entre différentes sessions
+- **Analyse Inter-Session** : Comparez les performances et les changements d’état entre différentes sessions de développement
+
+
+
+
+
+
+
+### 📚 Documentation Interactive
+
+Reactime propose une documentation complète pour aider les développeurs à comprendre son architecture et ses APIs.
+Après avoir cloné ce référentiel, les développeurs peuvent simplement exécuter `npm run docs` à la racine et servir le fichier `/docs/index.html` généré dynamiquement, offrant :
+
+
+
+ Des diagrammes interactifs de composants
+
+
+Des définitions de types et interfaces
+
+
+Une vue d’ensemble de l’architecture du code
+
+
+Des références d’API et des exemples
+
+
+
+
+🎉 Nouveautés !
+
+La version 26.0 de Reactime propose une refonte complète de l’expérience de débogage React, avec :
+
+- **Nouvelle Visualisation des Données de Contexte**
+
+ - Première visualisation des changements d’état du hook useContext
+ - Cartographie claire des relations fournisseur-consommateur
+ - Surveillance en temps réel de la valeur d’état du contexte
+ - Visualisation détaillée des données du fournisseur
+
+- **Débogage avec Retour dans le Temps Amélioré**
+
+ - Interface du curseur de temps repensée, positionnée à côté des snapshots
+ - Contrôles de vitesse de lecture variables
+ - Navigation plus intuitive dans l’état
+ - Visualisation de snapshot améliorée
+
+- **Refonte Complète de l’UI Moderne**
+
+ - Design élégant et contemporain avec composants arrondis
+ - Améliorations de la disposition pour une meilleure intuitivité
+ - Nouveau mode sombre
+ - Hiérarchie visuelle améliorée
+
+- **Améliorations Techniques Majeures**
+ - Correction de la persistance de connexion lors de périodes d’inactivité et de changements d’onglet
+ - Restauration de la visualisation de l’arbre d’accessibilité
+ - Résolution de problèmes de capture d’état pour les hooks useState basés sur des fonctions
+ - Fiabilité et performance globales de l’extension grandement améliorées
+
+Ces mises à jour rendent Reactime plus puissant, plus fiable et plus convivial que jamais, établissant un nouveau standard pour les outils de débogage React.
+
+Pour en savoir plus sur les versions précédentes, cliquez ici !
+
+
+
+🚀 Bien Commencer
+
+### Installation
+
+1. Installez l’[extension Reactime](https://chrome.google.com/webstore/detail/reactime/cgibknllccemdnfhfpmjhffpjfeidjga) depuis le Chrome Web Store
+2. Installez l’extension requise [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) si vous ne l’avez pas déjà
+
+### Prérequis
+
+- Votre application React doit fonctionner en **mode développement**
+- L’extension React Developer Tools doit être installée
+- Navigateur Chrome (version 80 ou supérieure recommandée)
+
+### Lancer Reactime
+
+Il existe deux manières d’ouvrir le panneau Reactime :
+
+1. **Menu Contextuel**
+
+ - Faites un clic droit n’importe où dans votre application React
+ - Sélectionnez "Reactime" dans le menu contextuel
+
+2. **DevTools**
+ - Ouvrez les DevTools de Chrome (F12 ou ⌘+⌥+I)
+ - Naviguez jusqu’à l’onglet "Reactime"
+
+Une fois lancé, Reactime commencera automatiquement à surveiller les changements d’état et les métriques de performance de votre application.
+
+
+
+🤝 Contribuer à Reactime
+
+Nous accueillons avec joie les contributions de développeurs de tous niveaux ! Voici comment vous pouvez aider à améliorer Reactime: 🙋 Contributing README
+
+1. **Commencer**
+
+ - Forkez le dépôt
+ - Consultez notre README Développeur détaillé
+ - Mettez en place votre environnement de développement local
+
+2. **Processus de Build**
+
+ - Suivez les instructions de build dans le README Développeur
+ - Testez soigneusement vos modifications
+ - Soumettez une Pull Request
+
+Rejoignez notre communauté grandissante de contributeurs et participez à façonner l’avenir des outils de débogage React ! Pour des lignes directrices de contribution plus détaillées et des informations sur l’architecture du projet, veuillez vous référer à notre 👩💻 README Développeur .
+
+
+
+🛠️ Dépannage
+
+### ❓ Pourquoi Reactime n’enregistre-t-il pas les nouveaux changements d’état ?
+
+Reactime a perdu sa connexion avec l’onglet que vous surveillez ; il vous suffit de cliquer sur le bouton "reconnecter"
+pour reprendre votre travail.
+
+### ❓ Pourquoi Reactime ne trouve-t-il pas mes hooks ?
+
+Reactime détecte et surveille les hooks en parcourant le code React non minifié de votre application en mode développement. Si votre processus de build minifie ou "uglifie" votre code — même pour les builds de développement — Reactime risque de ne pas pouvoir localiser et suivre correctement vos hooks. Pour résoudre ce problème :
+
+1. **Assurez-vous d’une vraie build de développement** : Vérifiez la configuration de votre bundler ou outil de build (par ex. Webpack, Babel, Vite, etc.) pour vous assurer que votre application n’est pas minimisée ou "uglifiée" en mode développement.
+
+ - Par exemple, avec Webpack, assurez-vous d’exécuter le mode : 'development', ce qui devrait désactiver la minification par défaut.
+ - Dans un projet Create React App, il suffit d’exécuter `npm start` ou `yarn start` pour configurer automatiquement une build de développement non minifiée.
+
+2. **Vérifiez les surcharges** : Assurez-vous qu’aucun plugin Babel ou Webpack personnalisé ne minifie votre code, surtout si vous utilisez des frameworks comme Next.js ou Gatsby. Parfois, des plugins ou scripts supplémentaires peuvent s’exécuter en arrière-plan.
+
+3. **Redémarrez & recompilez** : Après avoir modifié toute configuration de build, recompilez ou redémarrez votre serveur de développement pour vous assurer que la nouvelle configuration est prise en compte. Ensuite, rafraîchissez l’onglet de votre navigateur afin que Reactime puisse détecter vos hooks non minifiés.
+
+Après avoir modifié toute configuration de build, recompilez ou redémarrez votre serveur de développement pour vous assurer que la nouvelle configuration est prise en compte. Ensuite, rafraîchissez l’onglet de votre navigateur afin que Reactime puisse détecter vos hooks non minifiés.
+
+### ❓ Pourquoi Reactime m’indique qu’aucune application React n’a été trouvée ?
+
+Reactime s’exécute initialement grâce au hook global des dev tools de Chrome.
+Il faut du temps à Chrome pour le charger. Essayez de rafraîchir votre application plusieurs fois jusqu’à ce que vous voyiez Reactime en fonctionnement.
+
+### ❓ Pourquoi dois-je avoir les React Dev Tools activées ?
+
+Reactime fonctionne de concert avec les React Developer Tools pour accéder à l’arbre Fiber d’une application React ; en interne, Reactime parcourt l’arbre Fiber via le hook global des React Dev Tools, récupérant toutes les informations pertinentes à afficher au développeur.
+
+### ❓ J’ai trouvé un bug dans Reactime
+
+Reactime est un projet open-source, et nous serions ravis d’avoir vos retours pour améliorer l’expérience utilisateur. Veuillez consulter le 👩💻 README Développeur ,
+et créer une Pull Request (ou une issue) pour proposer et collaborer sur des modifications de Reactime.
+
+### ❓ Compatibilité avec les versions Node
+
+Depuis la sortie de Node v18.12.1 (LTS) le 04/11/22, le script a été mis à jour avec
+`npm run dev` | `npm run build` pour assurer une rétrocompatibilité.
+Pour la version Node v16.16.0, veuillez utiliser les scripts `npm run devlegacy` | `npm run buildlegacy`
+
+
+
+✍️ Auteurs
+
+- **Garrett Chow** - [@garrettlchow](https://github.com/garrettlchow)
+- **Ellie Simens** - [@elliesimens](https://github.com/elliesimens)
+- **Ragad Mohammed** - [@ragad-mohammed](https://github.com/ragad-mohammed)
+- **Daniel Ryczek** - [@dryczek14](https://github.com/dryczek01)
+- **Patrice Pinardo** - [@pinardo88](https://github.com/pinardo88)
+- **Haider Ali** - [@hali03](https://github.com/hali03)
+- **Jose Luis Sanchez** - [@JoseSanchez1996](https://github.com/JoseSanchez1996)
+- **Logan Nelsen** - [@ljn16](https://github.com/ljn16)
+- **Mel Koppens** - [@MelKoppens](https://github.com/MelKoppens)
+- **Amy Yang** - [@ay7991](https://github.com/ay7991)
+- **Eva Ury** - [@evaSUry](https://github.com/evaSUry)
+- **Jesse Guerrero** - [@jguerrero35](https://github.com/jguerrero35)
+- **Oliver Cho** - [@Oliver-Cho](https://github.com/Oliver-Cho)
+- **Ben Margolius** - [@benmarg](https://github.com/benmarg)
+- **Eric Yun** - [@ericsngyun](https://github.com/ericsngyun)
+- **James Nghiem** - [@jemzir](https://github.com/jemzir)
+- **Wilton Lee** - [@wiltonlee948](https://github.com/wiltonlee948)
+- **Louis Lam** - [@llam722](https://github.com/llam722)
+- **Samuel Tran** - [@leumastr](https://github.com/leumastr)
+- **Brian Yang** - [@yangbrian310](https://github.com/yangbrian310)
+- **Emin Tahirov** - [@eminthrv](https://github.com/eminthrv)
+- **Peng Dong** - [@d28601581](https://github.com/d28601581)
+- **Ozair Ghulam** - [@ozairgh](https://github.com/ozairgh)
+- **Christina Or** - [@christinaor](https://github.com/christinaor)
+- **Khanh Bui** - [@AndyB909](https://github.com/AndyB909)
+- **David Kim** - [@codejunkie7](https://github.com/codejunkie7)
+- **Robby Tipton** - [@RobbyTipton](https://github.com/RobbyTipton)
+- **Kevin HoEun Lee** - [@khobread](https://github.com/khobread)
+- **Christopher LeBrett** - [@fscgolden](https://github.com/fscgolden)
+- **Joseph Park** - [@joeepark](https://github.com/joeepark)
+- **Kris Sorensen** - [@kris-sorensen](https://github.com/kris-sorensen)
+- **Daljit Gill** - [@dgill05](https://github.com/dgill05)
+- **Ben Michareune** - [@bmichare](https://github.com/bmichare)
+- **Dane Corpion** - [@danecorpion](https://github.com/danecorpion)
+- **Harry Fox** -
+ [@StackOverFlowWhereArtThou](https://github.com/StackOverFlowWhereArtThou)
+- **Nathan Richardson** - [@BagelEnthusiast](https://github.com/BagelEnthusiast)
+- **David Bernstein** - [@dangitbobbeh](https://github.com/dangitbobbeh)
+- **Joseph Stern** - [@josephiswhere](https://github.com/josephiswhere)
+- **Dennis Lopez** - [@DennisLpz](https://github.com/DennisLpz)
+- **Cole Styron** - [@colestyron](https://github.com/C-STYR)
+- **Ali Rahman** - [@CourageWolf](https://github.com/CourageWolf)
+- **Caner Demir** - [@demircaner](https://github.com/demircaner)
+- **Kevin Ngo** - [@kev-ngo](https://github.com/kev-ngo)
+- **Becca Viner** - [@rtviner](https://github.com/rtviner)
+- **Caitlin Chan** - [@caitlinchan23](https://github.com/caitlinchan23)
+- **Kim Mai Nguyen** - [@Nkmai](https://github.com/Nkmai)
+- **Tania Lind** - [@lind-tania](https://github.com/lind-tania)
+- **Alex Landeros** - [@AlexanderLanderos](https://github.com/AlexanderLanderos)
+- **Chris Guizzetti** - [@guizzettic](https://github.com/guizzettic)
+- **Jason Victor** - [@theqwertypusher](https://github.com/Theqwertypusher)
+- **Sanjay Lavingia** - [@sanjaylavingia](https://github.com/sanjaylavingia)
+- **Vincent Nguyen** - [@VNguyenCode](https://github.com/VNguyenCode)
+- **Haejin Jo** - [@haejinjo](https://github.com/haejinjo)
+- **Hien Nguyen** - [@hienqn](https://github.com/hienqn)
+- **Jack Crish** - [@JackC27](https://github.com/JackC27)
+- **Kevin Fey** - [@kevinfey](https://github.com/kevinfey)
+- **Carlos Perez** - [@crperezt](https://github.com/crperezt)
+- **Edwin Menendez** - [@edwinjmenendez](https://github.com/edwinjmenendez)
+- **Gabriela Jardim Aquino** - [@aquinojardim](https://github.com/aquinojardim)
+- **Greg Panciera** - [@gpanciera](https://github.com/gpanciera)
+- **Nathanael Wa Mwenze** - [@nmwenz90](https://github.com/nmwenz90)
+- **Ryan Dang** - [@rydang](https://github.com/rydang)
+- **Bryan Lee** - [@mylee1995](https://github.com/mylee1995)
+- **Josh Kim** - [@joshua0308](https://github.com/joshua0308)
+- **Sierra Swaby** - [@starkspark](https://github.com/starkspark)
+- **Ruth Anam** - [@nusanam](https://github.com/nusanam)
+- **David Chai** - [@davidchaidev](https://github.com/davidchai717)
+- **Yujin Kang** - [@yujinkay](https://github.com/yujinkay)
+- **Andy Wong** - [@andynullwong](https://github.com/andynullwong)
+- **Chris Flannery** -
+ [@chriswillsflannery](https://github.com/chriswillsflannery)
+- **Rajeeb Banstola** - [@rajeebthegreat](https://github.com/rajeebthegreat)
+- **Prasanna Malla** - [@prasmalla](https://github.com/prasmalla)
+- **Rocky Lin** - [@rocky9413](https://github.com/rocky9413)
+- **Abaas Khorrami** - [@dubalol](https://github.com/dubalol)
+- **Ergi Shehu** - [@Ergi516](https://github.com/ergi516)
+- **Raymond Kwan** - [@rkwn](https://github.com/rkwn)
+- **Joshua Howard** - [@joshua-howard](https://github.com/joshua-howard)
+- **Lina Shin** - [@rxlina](https://github.com/rxlina)
+- **Andy Tsou** - [@andytsou19](https://github.com/andytsou19)
+- **Feiyi Wu** - [@FreyaWu](https://github.com/FreyaWu)
+- **Viet Nguyen** - [@vnguyen95](https://github.com/vnguyen95)
+- **Alex Gomez** - [@alexgomez9](https://github.com/alexgomez9)
+- **Edar Liu** - [@liuedar](https://github.com/liuedar)
+- **Kristina Wallen** - [@kristinawallen](https://github.com/kristinawallen)
+- **Quan Le** - [@Blachfog](https://github.com/Blachfog)
+- **Robert Maeda** - [@robmaeda](https://github.com/robmaeda)
+- **Lance Ziegler** - [@lanceziegler](https://github.com/lanceziegler)
+- **Ngoc Zwolinski** - [@ngoczwolinski](https://github.com/ngoczwolinski)
+- **Peter Lam** - [@dev-plam](https://github.com/dev-plam)
+- **Zachary Freeman** - [@zacharydfreeman](https://github.com/zacharydfreeman/)
+- **Jackie Yuan** - [@yuanjackie1](https://github.com/yuanjackie1)
+- **Jasmine Noor** - [@jasnoo](https://github.com/jasnoo)
+- **Minzo Kim** - [@minzo-kim](https://github.com/minzo-kim)
+- **Mark Teets** - [@MarkTeets](https://github.com/MarkTeets)
+- **Nick Huemmer** - [@NickHuemmer](https://github.com/ElDuke717)
+- **James McCollough** - [@j-mccoll](https://github.com/j-mccoll)
+- **Mike Bednarz** - [@mikebednarz](https://github.com/mikebednarz)
+- **Sergei Liubchenko** - [@sergeylvq](https://github.com/sergeylvq)
+- **Yididia Ketema** - [@yididiaketema](https://github.com/yididiaketema)
+- **Morah Geist** - [@morahgeist](https://github.com/morahgeist)
+- **Eivind Del Fierro** - [@EivindDelFierro](https://github.com/EivindDelFierro)
+- **Kyle Bell** - [@KyEBell](https://github.com/KyEBell)
+- **Sean Kelly** - [@brok3turtl3](https://github.com/brok3turtl3)
+- **Christopher Stamper** - [@ctstamper](https://github.com/ctstamper)
+- **Jimmy Phy** - [@jimmally](https://github.com/jimmally)
+- **Andrew Byun** - [@AndrewByun](https://github.com/AndrewByun)
+- **Kelvin Mirhan** - [@kelvinmirhan](https://github.com/kelvinmirhan)
+- **Jesse Rosengrant** - [@jrosengrant](https://github.com/jrosengrant)
+- **Liam Donaher** - [@leebology](https://github.com/leebology)
+- **David Moore** - [@Solodt55](https://github.com/Solodt55)
+- **John Banks** - [@Jbanks123](https://github.com/Jbanks123)
+
+
+⚖️ Licence
+
+Ce projet est distribué sous licence MIT - voir le fichier [LICENSE](LICENSE) pour plus de détails.
diff --git a/README.id.md b/README.id.md
new file mode 100644
index 000000000..80b2b8898
--- /dev/null
+++ b/README.id.md
@@ -0,0 +1,361 @@
+
+
+Ekstensi Chrome yang kuat untuk meningkatkan pengembangan React dengan debugging lintas-waktu dan pemantauan kinerja lanjutan
+
+Baca Artikel Kami di Medium untuk mempelajari lebih lanjut tentang proses di balik layar dan pengembangan Reactime!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## ✨ Fitur Utama
+
+### 🔍 Visualisasi State
+
+- **Beberapa Tampilan**: Visualisasikan state aplikasi Anda melalui Component Graphs, JSON Trees, Performance Graphs, dan Accessibility Trees
+- **History Timeline**: Lacak perubahan state seiring waktu melalui tampilan riwayat yang intuitif
+- **Web Metrics**: Pantau metrik kinerja penting secara real-time
+- **Insight Aksesibilitas**: Analisis accessibility tree aplikasi Anda pada setiap perubahan state
+
+
+Pada halaman utama, terdapat dua pilihan utama di panel dropdown:
+
+- **Timejump**: Melihat dan menavigasi riwayat snapshot state aplikasi Anda. Anda dapat melompat ke titik mana pun dalam waktu untuk melihat bagaimana state berubah di setiap perubahan. Anda juga dapat menggunakan tombol play untuk memutar ulang setiap perubahan state secara otomatis.
+- **Providers / Consumers**: Memahami dependensi context aplikasi Anda dan bagaimana elemen-elemen tersebut berinteraksi, melalui visualisasi hubungan provider dan consumer.
+
+
+
+
+
+
+
+### ⏱️ Debugging Lintas-Waktu
+
+- **Snapshot State**: Tangkap dan navigasi riwayat state aplikasi Anda
+- **Playback Controls**: Putar ulang perubahan state secara otomatis dengan kecepatan yang dapat disesuaikan
+- **Jump Points**: Langsung menuju state yang sebelumnya
+- **Perbandingan Diff**: Bandingkan state antar snapshot
+
+
+
+
+
+
+
+### 📊 Analisis Kinerja
+
+- **Metrik Komponen**: Lacak waktu render dan potensi bottleneck pada kinerja
+- **Perbandingan Seri**: Bandingkan kinerja di berbagai set perubahan state
+- **Deteksi Re-render**: Identifikasi dan perbaiki siklus render yang tidak diperlukan
+- **Web Vitals**: Pantau Core Web Vitals dan metrik kinerja lainnya
+
+
+
+### 🔄 Dukungan Framework Modern
+
+
+
+ Kompatibilitas penuh dengan Next.js, Remix, Recoil, dan Gatsby
+
+
+ Dukungan TypeScript untuk komponen berbasis class dan fungsional
+
+
+ Dukungan untuk React Hooks dan Context API
+
+
+
+
+### 💾 Persistensi & Berbagi State
+
+Reactime memudahkan proses menyimpan dan berbagi riwayat state aplikasi Anda:
+
+- **Ekspor Riwayat State**: Simpan snapshot yang direkam sebagai berkas JSON untuk analisis lebih lanjut atau dibagikan
+- **Impor Sesi Sebelumnya**: Unggah snapshot yang telah disimpan untuk membandingkan perubahan state di sesi yang berbeda
+- **Analisis Lintas-Sesi**: Bandingkan kinerja dan perubahan state antara berbagai sesi pengembangan
+
+
+
+
+
+
+
+### 📚 Dokumentasi Interaktif
+
+Reactime menyediakan dokumentasi komprehensif untuk membantu pengembang memahami arsitektur dan API-nya:
+Setelah melakukan clone pada repositori ini, pengembang dapat menjalankan `npm run docs` di
+level root, dan menyajikan `/docs/index.html` yang dihasilkan secara dinamis, yang menyediakan:
+
+
+ Diagram komponen interaktif
+ Definisi tipe dan interface
+ Gambaran umum arsitektur codebase
+ Referensi dan contoh API
+
+
+
+🎉 Apa yang Baru!
+
+Reactime 26.0 menghadirkan perombakan total pada pengalaman debugging React, dengan fitur-fitur:
+
+- **Tampilan Data Context Baru**
+ - Visualisasi pertama untuk perubahan state hook useContext
+ - Pemetaan jelas hubungan provider-consumer
+ - Pemantauan nilai state context secara real-time
+ - Visualisasi data provider yang terperinci
+
+- **Debugging Lintas-Waktu yang Ditingkatkan**
+ - Antarmuka slider yang didesain ulang, ditempatkan di samping snapshot
+ - Kontrol kecepatan pemutaran yang bervariasi
+ - Navigasi state yang lebih intuitif
+ - Visualisasi snapshot yang lebih baik
+
+- **Perombakan UI Modern**
+ - Desain yang lebih segar dengan komponen membulat
+ - Peningkatan tata letak yang intuitif
+ - Dukungan mode gelap baru
+ - Hierarki visual yang diperbarui
+
+- **Peningkatan Teknis Besar**
+ - Mengatasi masalah koneksi yang terputus saat idle dan perpindahan tab
+ - Mengembalikan visualisasi accessibility tree
+ - Memperbaiki masalah penangkapan state pada useState berbasis fungsi
+ - Meningkatkan keandalan dan kinerja ekstensi secara keseluruhan
+
+Pembaruan ini membuat Reactime lebih kuat, andal, dan ramah pengguna dari sebelumnya, menetapkan standar baru bagi alat debugging React.
+
+
+
+🚀 Memulai
+
+### Instalasi
+
+1. Pasang [Ekstensi Reactime](https://chrome.google.com/webstore/detail/reactime/cgibknllccemdnfhfpmjhffpjfeidjga) dari Chrome Web Store
+2. Pasang ekstensi [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) yang diperlukan jika Anda belum memasangnya
+
+### Prasyarat
+
+- Aplikasi React Anda harus berjalan dalam **mode development**
+- Ekstensi React Developer Tools harus terpasang
+- Peramban Chrome (disarankan versi 80 atau lebih tinggi)
+
+### Menjalankan Reactime
+
+Ada dua cara untuk membuka panel Reactime:
+
+1. **DevTools**
+ - Buka Chrome DevTools (F12 atau ⌘+⌥+I)
+ - Buka tab "Reactime"
+ - Ini akan membuka Reactime sebagai panel di dalam Chrome DevTools, terintegrasi bersama alat pengembangan Anda yang lain
+
+2. **Context Menu**
+ - Klik kanan di mana saja pada aplikasi React Anda
+ - Pilih "Reactime" dari context menu
+ - Ini akan membuka Reactime di jendela popup terpisah yang dapat Anda ubah ukuran dan posisinya secara mandiri
+
+Setelah diluncurkan, Reactime secara otomatis akan mulai memantau perubahan state dan metrik kinerja aplikasi Anda.
+
+
+
+🤝 Berkontribusi di Reactime
+
+Kami menyambut kontribusi dari pengembang di semua tingkatan! Untuk panduan terperinci tentang cara berkontribusi:
+
+1. **Mulai**
+ - Fork repositori
+ - Pelajari README Pengembang kami yang komprehensif
+ - Siapkan lingkungan pengembangan lokal Anda
+
+2. **Proses Build**
+ - Ikuti petunjuk build di README Pengembang kami
+ - Uji perubahan Anda secara menyeluruh
+ - Ajukan pull request
+
+Bergabunglah dengan komunitas kontributor kami yang terus berkembang dan bantu membentuk masa depan alat debugging React! Untuk panduan kontribusi dan informasi arsitektur proyek yang lebih rinci, silakan lihat 👩💻 README Pengembang dan 🙋 Contributing README .
+
+
+
+🛠️ Pemecahan Masalah
+
+### ❓ Mengapa Reactime tidak merekam perubahan state baru?
+
+Reactime kehilangan koneksi dengan tab yang Anda pantau. Cukup klik tombol "reconnect" untuk melanjutkan pekerjaan Anda.
+
+### ❓ Mengapa Reactime tidak menemukan hooks saya?
+
+Reactime mendeteksi dan memantau hooks dengan menelusuri kode React Anda yang belum di-minify di mode development. Jika proses build Anda meminifikasi atau meng-uglify kode—even untuk build development—Reactime mungkin tidak bisa mendeteksi dan melacak hooks Anda dengan benar. Untuk memperbaikinya:
+
+1. **Pastikan build benar-benar development**: Periksa konfigurasi bundler atau build tool Anda (misalnya, Webpack, Babel, Vite, dll.) untuk memastikan aplikasi tidak di-minify atau di-uglify dalam mode development.
+ - Misalnya, dengan Webpack, pastikan Anda menjalankan mode: 'development' yang menonaktifkan minification default.
+ - Pada proyek Create React App, cukup jalankan npm start atau yarn start yang secara otomatis mengonfigurasi build development yang tidak di-minify.
+2. **Periksa override**: Pastikan tidak ada plugin Babel atau Webpack kustom yang meminifikasi kode Anda, terutama jika Anda menggunakan framework seperti Next.js atau Gatsby. Terkadang ada plugin atau skrip tambahan yang berjalan di belakang layar.
+3. **Restart & rebuild**: Setelah mengubah konfigurasi build, lakukan rebuild atau restart server development Anda agar konfigurasi baru diterapkan. Lalu segarkan tab peramban Anda agar Reactime dapat mendeteksi hooks yang tidak di-minify.
+
+Setelah mengubah konfigurasi build, rebuild atau restart server pengembangan Anda agar konfigurasi baru diterapkan. Kemudian, refresh tab peramban sehingga Reactime dapat mendeteksi hooks Anda yang tidak di-minify.
+
+### ❓ Mengapa Reactime mengatakan bahwa tidak ada aplikasi React yang ditemukan?
+
+Reactime awalnya dijalankan menggunakan dev tools global hook dari API Chrome. Proses ini memerlukan waktu untuk dimuat. Coba refresh aplikasi Anda beberapa kali hingga Reactime muncul.
+
+### ❓ Mengapa saya harus mengaktifkan React Dev Tools?
+
+Reactime bekerja berdampingan dengan React Developer Tools untuk mengakses Fiber tree aplikasi React; di balik layar, Reactime menjelajahi Fiber tree melalui global hook dari React Developer Tools, dan mengambil semua informasi relevan yang diperlukan untuk ditampilkan kepada pengembang.
+
+### ❓ Saya menemukan bug di Reactime
+
+Reactime adalah proyek open-source, dan kami ingin mendengar masukan Anda untuk meningkatkan pengalaman pengguna. Silakan baca 👩💻 README Pengembang ,
+dan buat pull request (atau issue) untuk mengusulkan dan berkolaborasi dalam perubahan pada Reactime.
+
+### ❓ Kecocokan versi Node
+
+Dengan rilis Node v18.12.1 (LTS) pada 4/11/22, skrip telah diperbarui menjadi
+'npm run dev' | 'npm run build' untuk menjaga kompatibilitas mundur.
+Untuk versi Node v16.16.0, silakan gunakan skrip 'npm run devlegacy' | 'npm run buildlegacy'
+
+
+
+✍️ Penulis
+
+- **Garrett Chow** - [@garrettlchow](https://github.com/garrettlchow)
+- **Ellie Simens** - [@elliesimens](https://github.com/elliesimens)
+- **Ragad Mohammed** - [@ragad-mohammed](https://github.com/ragad-mohammed)
+- **Daniel Ryczek** - [@dryczek14](https://github.com/dryczek01)
+- **Patrice Pinardo** - [@pinardo88](https://github.com/pinardo88)
+- **Haider Ali** - [@hali03](https://github.com/hali03)
+- **Jose Luis Sanchez** - [@JoseSanchez1996](https://github.com/JoseSanchez1996)
+- **Logan Nelsen** - [@ljn16](https://github.com/ljn16)
+- **Mel Koppens** - [@MelKoppens](https://github.com/MelKoppens)
+- **Amy Yang** - [@ay7991](https://github.com/ay7991)
+- **Eva Ury** - [@evaSUry](https://github.com/evaSUry)
+- **Jesse Guerrero** - [@jguerrero35](https://github.com/jguerrero35)
+- **Oliver Cho** - [@Oliver-Cho](https://github.com/Oliver-Cho)
+- **Ben Margolius** - [@benmarg](https://github.com/benmarg)
+- **Eric Yun** - [@ericsngyun](https://github.com/ericsngyun)
+- **James Nghiem** - [@jemzir](https://github.com/jemzir)
+- **Wilton Lee** - [@wiltonlee948](https://github.com/wiltonlee948)
+- **Louis Lam** - [@llam722](https://github.com/llam722)
+- **Samuel Tran** - [@leumastr](https://github.com/leumastr)
+- **Brian Yang** - [@yangbrian310](https://github.com/yangbrian310)
+- **Emin Tahirov** - [@eminthrv](https://github.com/eminthrv)
+- **Peng Dong** - [@d28601581](https://github.com/d28601581)
+- **Ozair Ghulam** - [@ozairgh](https://github.com/ozairgh)
+- **Christina Or** - [@christinaor](https://github.com/christinaor)
+- **Khanh Bui** - [@AndyB909](https://github.com/AndyB909)
+- **David Kim** - [@codejunkie7](https://github.com/codejunkie7)
+- **Robby Tipton** - [@RobbyTipton](https://github.com/RobbyTipton)
+- **Kevin HoEun Lee** - [@khobread](https://github.com/khobread)
+- **Christopher LeBrett** - [@fscgolden](https://github.com/fscgolden)
+- **Joseph Park** - [@joeepark](https://github.com/joeepark)
+- **Kris Sorensen** - [@kris-sorensen](https://github.com/kris-sorensen)
+- **Daljit Gill** - [@dgill05](https://github.com/dgill05)
+- **Ben Michareune** - [@bmichare](https://github.com/bmichare)
+- **Dane Corpion** - [@danecorpion](https://github.com/danecorpion)
+- **Harry Fox** - [@StackOverFlowWhereArtThou](https://github.com/StackOverFlowWhereArtThou)
+- **Nathan Richardson** - [@BagelEnthusiast](https://github.com/BagelEnthusiast)
+- **David Bernstein** - [@dangitbobbeh](https://github.com/dangitbobbeh)
+- **Joseph Stern** - [@josephiswhere](https://github.com/josephiswhere)
+- **Dennis Lopez** - [@DennisLpz](https://github.com/DennisLpz)
+- **Cole Styron** - [@C-STYR](https://github.com/C-STYR)
+- **Ali Rahman** - [@CourageWolf](https://github.com/CourageWolf)
+- **Caner Demir** - [@demircaner](https://github.com/demircaner)
+- **Kevin Ngo** - [@kev-ngo](https://github.com/kev-ngo)
+- **Becca Viner** - [@rtviner](https://github.com/rtviner)
+- **Caitlin Chan** - [@caitlinchan23](https://github.com/caitlinchan23)
+- **Kim Mai Nguyen** - [@Nkmai](https://github.com/Nkmai)
+- **Tania Lind** - [@lind-tania](https://github.com/lind-tania)
+- **Alex Landeros** - [@AlexanderLanderos](https://github.com/AlexanderLanderos)
+- **Chris Guizzetti** - [@guizzettic](https://github.com/guizzettic)
+- **Jason Victor** - [@Theqwertypusher](https://github.com/Theqwertypusher)
+- **Sanjay Lavingia** - [@sanjaylavingia](https://github.com/sanjaylavingia)
+- **Vincent Nguyen** - [@VNguyenCode](https://github.com/VNguyenCode)
+- **Haejin Jo** - [@haejinjo](https://github.com/haejinjo)
+- **Hien Nguyen** - [@hienqn](https://github.com/hienqn)
+- **Jack Crish** - [@JackC27](https://github.com/JackC27)
+- **Kevin Fey** - [@kevinfey](https://github.com/kevinfey)
+- **Carlos Perez** - [@crperezt](https://github.com/crperezt)
+- **Edwin Menendez** - [@edwinjmenendez](https://github.com/edwinjmenendez)
+- **Gabriela Jardim Aquino** - [@aquinojardim](https://github.com/aquinojardim)
+- **Greg Panciera** - [@gpanciera](https://github.com/gpanciera)
+- **Nathanael Wa Mwenze** - [@nmwenz90](https://github.com/nmwenz90)
+- **Ryan Dang** - [@rydang](https://github.com/rydang)
+- **Bryan Lee** - [@mylee1995](https://github.com/mylee1995)
+- **Josh Kim** - [@joshua0308](https://github.com/joshua0308)
+- **Sierra Swaby** - [@starkspark](https://github.com/starkspark)
+- **Ruth Anam** - [@nusanam](https://github.com/nusanam)
+- **David Chai** - [@davidchai717](https://github.com/davidchai717)
+- **Yujin Kang** - [@yujinkay](https://github.com/yujinkay)
+- **Andy Wong** - [@andynullwong](https://github.com/andynullwong)
+- **Chris Flannery** - [@chriswillsflannery](https://github.com/chriswillsflannery)
+- **Rajeeb Banstola** - [@rajeebthegreat](https://github.com/rajeebthegreat)
+- **Prasanna Malla** - [@prasmalla](https://github.com/prasmalla)
+- **Rocky Lin** - [@rocky9413](https://github.com/rocky9413)
+- **Abaas Khorrami** - [@dubalol](https://github.com/dubalol)
+- **Ergi Shehu** - [@ergi516](https://github.com/ergi516)
+- **Raymond Kwan** - [@rkwn](https://github.com/rkwn)
+- **Joshua Howard** - [@joshua-howard](https://github.com/joshua-howard)
+- **Lina Shin** - [@rxlina](https://github.com/rxlina)
+- **Andy Tsou** - [@andytsou19](https://github.com/andytsou19)
+- **Feiyi Wu** - [@FreyaWu](https://github.com/FreyaWu)
+- **Viet Nguyen** - [@vnguyen95](https://github.com/vnguyen95)
+- **Alex Gomez** - [@alexgomez9](https://github.com/alexgomez9)
+- **Edar Liu** - [@liuedar](https://github.com/liuedar)
+- **Kristina Wallen** - [@kristinawallen](https://github.com/kristinawallen)
+- **Quan Le** - [@Blachfog](https://github.com/Blachfog)
+- **Robert Maeda** - [@robmaeda](https://github.com/robmaeda)
+- **Lance Ziegler** - [@lanceziegler](https://github.com/lanceziegler)
+- **Ngoc Zwolinski** - [@ngoczwolinski](https://github.com/ngoczwolinski)
+- **Peter Lam** - [@dev-plam](https://github.com/dev-plam)
+- **Zachary Freeman** - [@zacharydfreeman](https://github.com/zacharydfreeman/)
+- **Jackie Yuan** - [@yuanjackie1](https://github.com/yuanjackie1)
+- **Jasmine Noor** - [@jasnoo](https://github.com/jasnoo)
+- **Minzo Kim** - [@minzo-kim](https://github.com/minzo-kim)
+- **Mark Teets** - [@MarkTeets](https://github.com/MarkTeets)
+- **Nick Huemmer** - [@ElDuke717](https://github.com/ElDuke717)
+- **James McCollough** - [@j-mccoll](https://github.com/j-mccoll)
+- **Mike Bednarz** - [@mikebednarz](https://github.com/mikebednarz)
+- **Sergei Liubchenko** - [@sergeylvq](https://github.com/sergeylvq)
+- **Yididia Ketema** - [@yididiaketema](https://github.com/yididiaketema)
+- **Morah Geist** - [@morahgeist](https://github.com/morahgeist)
+- **Eivind Del Fierro** - [@EivindDelFierro](https://github.com/EivindDelFierro)
+- **Kyle Bell** - [@KyEBell](https://github.com/KyEBell)
+- **Sean Kelly** - [@brok3turtl3](https://github.com/brok3turtl3)
+- **Christopher Stamper** - [@ctstamper](https://github.com/ctstamper)
+- **Jimmy Phy** - [@jimmally](https://github.com/jimmally)
+- **Andrew Byun** - [@AndrewByun](https://github.com/AndrewByun)
+- **Kelvin Mirhan** - [@kelvinmirhan](https://github.com/kelvinmirhan)
+- **Jesse Rosengrant** - [@jrosengrant](https://github.com/jrosengrant)
+- **Liam Donaher** - [@leebology](https://github.com/leebology)
+- **David Moore** - [@Solodt55](https://github.com/Solodt55)
+- **John Banks** - [@Jbanks123](https://github.com/Jbanks123)
+
+
+⚖️ Lisensi
+
+Proyek ini dilisensikan di bawah MIT License - lihat berkas [LICENSE](LICENSE) untuk detail selengkapnya.
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..c9ac28701
--- /dev/null
+++ b/README.md
@@ -0,0 +1,384 @@
+
+
+A powerful Chrome extension that enhances React development with time-travel debugging and advanced performance monitoring
+
+Read our Medium Article to learn more about Reactime’s behind-the-scenes and development process!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## ✨ Key Features
+
+### 🔍 State Visualization
+
+- **Multiple Views**: Visualize your application state through Component Graphs, JSON Trees, Performance Graphs, and Accessibility Trees
+- **History Timeline**: Track state changes over time with an intuitive history visualization
+- **Web Metrics**: Monitor critical performance metrics in real-time
+- **Accessibility Insights**: Analyze your app's accessibility tree for each state change
+
+
+On the main page, there are two main selections from the dropdown panel:
+
+- **Timejump**: View and navigate through the snapshot history of your application's state. You can jump to any point in time to see how the state evolves across changes. You can also use the play button to replay each state change automatically.
+- **Providers / Consumers**: Understand your application's context dependencies and their interactions better through visualizing its provider and consumer relationships.
+
+
+
+
+
+
+
+### ⏱️ Time-Travel Debugging
+
+- **State Snapshots**: Capture and navigate through your application's state history
+- **Playback Controls**: Automatically replay state changes with adjustable speed
+- **Jump Points**: Instantly navigate to any previous state
+- **Diff Comparisons**: Compare states between snapshots
+
+
+
+
+
+
+
+### 📊 Performance Analysis
+
+- **Component Metrics**: Track render times and performance bottlenecks
+- **Series Comparison**: Compare performance across different sets of state changes
+- **Re-render Detection**: Identify and fix unnecessary render cycles
+- **Web Vitals**: Monitor Core Web Vitals and other performance metrics
+
+
+
+### 🔄 Modern Framework Support
+
+
+
+ Full compatibility with Next.js, Remix, Recoil, and Gatsby
+
+
+TypeScript support for class and functional components
+
+
+Support for React Hooks and Context API
+
+
+
+
+### 💾 State Persistence & Sharing
+
+Reactime makes it easy to save and share your application's state history:
+
+- **Export State History**: Save your recorded snapshots as a JSON file for later analysis or sharing
+- **Import Previous Sessions**: Upload previously saved snapshots to compare state changes across different sessions
+- **Cross-Session Analysis**: Compare performance and state changes between different development sessions
+
+
+
+
+
+
+
+### 📚 Interactive Documentation
+
+Reactime provides comprehensive documentation to help developers understand its architecture and APIs:
+After cloning this repository, developers can simply run `npm run docs` at the
+root level and serve the dynamically generated `/docs/index.html` provding:
+
+
+
+ Interactive component diagrams
+
+
+Type definitions and interfaces
+
+
+Codebase architecture overview
+
+
+API references and examples
+
+
+
+
+🎉 What's New!
+
+Reactime 26.0 brings a complete overhaul to the React debugging experience, featuring:
+
+- **New Context Data Display**
+
+ - First-ever visualization of useContext hook state changes
+ - Clear mapping of provider-consumer relationships
+ - Real-time context state value monitoring
+ - Detailed provider data visualization
+
+- **Enhanced Time Travel Debugging**
+
+ - Redesigned slider interface positioned alongside snapshots
+ - Variable playback speed controls
+ - More intuitive state navigation
+ - Improved snapshot visualization
+
+- **Modern UI Overhaul**
+
+ - Sleek, contemporary design with rounded components
+ - Intuitive layout improvements
+ - New dark mode support
+ - Enhanced visual hierarchy
+
+- **Major Technical Improvements**
+ - Fixed connection persistence during idle time and tab switches
+ - Restored accessibility tree visualization
+ - Resolved state capture issues for function-based useState hooks
+ - Improved overall extension reliability and performance
+
+These updates make Reactime more powerful, reliable, and user-friendly than ever before, setting a new standard for React debugging tools.
+
+
+
+🚀 Getting Started
+
+### Installation
+
+1. Install the [Reactime extension](https://chrome.google.com/webstore/detail/reactime/cgibknllccemdnfhfpmjhffpjfeidjga) from the Chrome Web Store
+2. Install the required [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) extension if you haven't already
+
+### Prerequisites
+
+- Your React application must be running in **development mode**
+- React Developer Tools extension must be installed
+- Chrome browser (version 80 or higher recommended)
+
+### Launch Reactime
+
+There are two ways to open the Reactime panel:
+
+1. **DevTools**
+
+ - Open Chrome DevTools (F12 or ⌘+⌥+I)
+ - Navigate to the "Reactime" tab
+ - This will open Reactime as a panel within Chrome DevTools, integrated alongside your other development tools
+
+2. **Context Menu**
+
+ - Right-click anywhere on your React application
+ - Select "Reactime" from the context menu
+ - This will open Reactime in a separate popup window which you can resize and position independently
+
+Once launched, Reactime will automatically begin monitoring your application's state changes and performance metrics.
+
+
+
+🤝 Contributing to Reactime
+
+We welcome contributions from developers of all skill levels! For detailed guidelines on how to contribute:
+
+1. **Get Started**
+
+ - Fork the repository
+ - Review our comprehensive Developer README
+ - Set up your local development environment
+
+2. **Build Process**
+
+ - Follow our build instructions in the Developer README
+ - Test your changes thoroughly
+ - Submit a pull request
+
+Join our growing community of contributors and help shape the future of React debugging tools! For detailed contribution guidelines and project architecture information, please refer to our 👩💻 Developer README and 🙋 Contributing README
+
+
+
+🛠️ Troubleshooting
+
+### ❓ Why is Reactime not recording new state changes?
+
+Reactime lost its connection to the tab you're monitoring, simply click the "reconnect" button to resume your work.
+
+### ❓ Why isn’t Reactime finding my hooks?
+
+Reactime detects and monitors hooks by traversing your application’s unminified React code in development mode. If your build process is minifying or uglifying your code—even for development builds—Reactime may not be able to properly locate and track your hooks. To fix this:
+
+1. **Ensure a true development build**: Double-check your bundler or build tool configuration (e.g., Webpack, Babel, Vite, etc.) to make sure that your application is not minimized or uglified in development mode.
+
+ - For example, with Webpack, make sure you’re running in mode: 'development', which should disable default minification.
+ - In a Create React App project, simply running npm start or yarn start will automatically configure a non-minified development build.
+
+2. **Check for overrides**: Ensure there are no custom Babel or Webpack plugins that minify your code, especially if you’re using frameworks like Next.js or Gatsby. Sometimes additional plugins or scripts might be running under the hood.
+
+3. **Restart & rebuild**: After changing any build configuration, rebuild or restart your development server to ensure the new configuration is applied. Then refresh your browser tab so Reactime can detect your unminified hooks.
+
+After changing any build configuration, rebuild or restart your development server to ensure the new configuration is applied. Then refresh your browser tab so Reactime can detect your unminified hooks.
+
+### ❓ Why is Reactime telling me that no React application is found?
+
+Reactime initially runs using the dev tools global hook from the Chrome API. It
+takes time for Chrome to load this. Try refreshing your application a couple of
+times until you see Reactime running.
+
+### ❓ Why do I need to have React Dev Tools enabled?
+
+Reactime works in tandem with the React Developer Tools to access a React application's Fiber tree; under the hood, Reactime traverses the Fiber tree through the React Developer Tool's global hook, pulling all relevant information needed to display to the developer
+
+### ❓ I found a bug in Reactime
+
+Reactime is an open-source project, and we'd love to hear from you about
+improving the user experience. Please read the 👩💻 Developer README ,
+and create a pull request (or issue) to propose and collaborate on changes to Reactime.
+
+### ❓ Node version compatibility
+
+With the release of Node v18.12.1(LTS) on 11/4/22, the script has been updated to
+'npm run dev' | 'npm run build' for backwards compatibility. For version
+Node v16.16.0, please use script 'npm run devlegacy' | 'npm run buildlegacy'
+
+
+
+✍️ Authors
+
+- **Garrett Chow** - [@garrettlchow](https://github.com/garrettlchow)
+- **Ellie Simens** - [@elliesimens](https://github.com/elliesimens)
+- **Ragad Mohammed** - [@ragad-mohammed](https://github.com/ragad-mohammed)
+- **Daniel Ryczek** - [@dryczek14](https://github.com/dryczek01)
+- **Patrice Pinardo** - [@pinardo88](https://github.com/pinardo88)
+- **Haider Ali** - [@hali03](https://github.com/hali03)
+- **Jose Luis Sanchez** - [@JoseSanchez1996](https://github.com/JoseSanchez1996)
+- **Logan Nelsen** - [@ljn16](https://github.com/ljn16)
+- **Mel Koppens** - [@MelKoppens](https://github.com/MelKoppens)
+- **Amy Yang** - [@ay7991](https://github.com/ay7991)
+- **Eva Ury** - [@evaSUry](https://github.com/evaSUry)
+- **Jesse Guerrero** - [@jguerrero35](https://github.com/jguerrero35)
+- **Oliver Cho** - [@Oliver-Cho](https://github.com/Oliver-Cho)
+- **Ben Margolius** - [@benmarg](https://github.com/benmarg)
+- **Eric Yun** - [@ericsngyun](https://github.com/ericsngyun)
+- **James Nghiem** - [@jemzir](https://github.com/jemzir)
+- **Wilton Lee** - [@wiltonlee948](https://github.com/wiltonlee948)
+- **Louis Lam** - [@llam722](https://github.com/llam722)
+- **Samuel Tran** - [@leumastr](https://github.com/leumastr)
+- **Brian Yang** - [@yangbrian310](https://github.com/yangbrian310)
+- **Emin Tahirov** - [@eminthrv](https://github.com/eminthrv)
+- **Peng Dong** - [@d28601581](https://github.com/d28601581)
+- **Ozair Ghulam** - [@ozairgh](https://github.com/ozairgh)
+- **Christina Or** - [@christinaor](https://github.com/christinaor)
+- **Khanh Bui** - [@AndyB909](https://github.com/AndyB909)
+- **David Kim** - [@codejunkie7](https://github.com/codejunkie7)
+- **Robby Tipton** - [@RobbyTipton](https://github.com/RobbyTipton)
+- **Kevin HoEun Lee** - [@khobread](https://github.com/khobread)
+- **Christopher LeBrett** - [@fscgolden](https://github.com/fscgolden)
+- **Joseph Park** - [@joeepark](https://github.com/joeepark)
+- **Kris Sorensen** - [@kris-sorensen](https://github.com/kris-sorensen)
+- **Daljit Gill** - [@dgill05](https://github.com/dgill05)
+- **Ben Michareune** - [@bmichare](https://github.com/bmichare)
+- **Dane Corpion** - [@danecorpion](https://github.com/danecorpion)
+- **Harry Fox** -
+ [@StackOverFlowWhereArtThou](https://github.com/StackOverFlowWhereArtThou)
+- **Nathan Richardson** - [@BagelEnthusiast](https://github.com/BagelEnthusiast)
+- **David Bernstein** - [@dangitbobbeh](https://github.com/dangitbobbeh)
+- **Joseph Stern** - [@josephiswhere](https://github.com/josephiswhere)
+- **Dennis Lopez** - [@DennisLpz](https://github.com/DennisLpz)
+- **Cole Styron** - [@colestyron](https://github.com/C-STYR)
+- **Ali Rahman** - [@CourageWolf](https://github.com/CourageWolf)
+- **Caner Demir** - [@demircaner](https://github.com/demircaner)
+- **Kevin Ngo** - [@kev-ngo](https://github.com/kev-ngo)
+- **Becca Viner** - [@rtviner](https://github.com/rtviner)
+- **Caitlin Chan** - [@caitlinchan23](https://github.com/caitlinchan23)
+- **Kim Mai Nguyen** - [@Nkmai](https://github.com/Nkmai)
+- **Tania Lind** - [@lind-tania](https://github.com/lind-tania)
+- **Alex Landeros** - [@AlexanderLanderos](https://github.com/AlexanderLanderos)
+- **Chris Guizzetti** - [@guizzettic](https://github.com/guizzettic)
+- **Jason Victor** - [@theqwertypusher](https://github.com/Theqwertypusher)
+- **Sanjay Lavingia** - [@sanjaylavingia](https://github.com/sanjaylavingia)
+- **Vincent Nguyen** - [@VNguyenCode](https://github.com/VNguyenCode)
+- **Haejin Jo** - [@haejinjo](https://github.com/haejinjo)
+- **Hien Nguyen** - [@hienqn](https://github.com/hienqn)
+- **Jack Crish** - [@JackC27](https://github.com/JackC27)
+- **Kevin Fey** - [@kevinfey](https://github.com/kevinfey)
+- **Carlos Perez** - [@crperezt](https://github.com/crperezt)
+- **Edwin Menendez** - [@edwinjmenendez](https://github.com/edwinjmenendez)
+- **Gabriela Jardim Aquino** - [@aquinojardim](https://github.com/aquinojardim)
+- **Greg Panciera** - [@gpanciera](https://github.com/gpanciera)
+- **Nathanael Wa Mwenze** - [@nmwenz90](https://github.com/nmwenz90)
+- **Ryan Dang** - [@rydang](https://github.com/rydang)
+- **Bryan Lee** - [@mylee1995](https://github.com/mylee1995)
+- **Josh Kim** - [@joshua0308](https://github.com/joshua0308)
+- **Sierra Swaby** - [@starkspark](https://github.com/starkspark)
+- **Ruth Anam** - [@nusanam](https://github.com/nusanam)
+- **David Chai** - [@davidchaidev](https://github.com/davidchai717)
+- **Yujin Kang** - [@yujinkay](https://github.com/yujinkay)
+- **Andy Wong** - [@andynullwong](https://github.com/andynullwong)
+- **Chris Flannery** -
+ [@chriswillsflannery](https://github.com/chriswillsflannery)
+- **Rajeeb Banstola** - [@rajeebthegreat](https://github.com/rajeebthegreat)
+- **Prasanna Malla** - [@prasmalla](https://github.com/prasmalla)
+- **Rocky Lin** - [@rocky9413](https://github.com/rocky9413)
+- **Abaas Khorrami** - [@dubalol](https://github.com/dubalol)
+- **Ergi Shehu** - [@Ergi516](https://github.com/ergi516)
+- **Raymond Kwan** - [@rkwn](https://github.com/rkwn)
+- **Joshua Howard** - [@Joshua-Howard](https://github.com/joshua-howard)
+- **Lina Shin** - [@rxlina](https://github.com/rxlina)
+- **Andy Tsou** - [@andytsou19](https://github.com/andytsou19)
+- **Feiyi Wu** - [@FreyaWu](https://github.com/FreyaWu)
+- **Viet Nguyen** - [@vnguyen95](https://github.com/vnguyen95)
+- **Alex Gomez** - [@alexgomez9](https://github.com/alexgomez9)
+- **Edar Liu** - [@liuedar](https://github.com/liuedar)
+- **Kristina Wallen** - [@kristinawallen](https://github.com/kristinawallen)
+- **Quan Le** - [@blachfog](https://github.com/Blachfog)
+- **Robert Maeda** - [@robmaeda](https://github.com/robmaeda)
+- **Lance Ziegler** - [@lanceziegler](https://github.com/lanceziegler)
+- **Ngoc Zwolinski** - [@ngoczwolinski](https://github.com/ngoczwolinski)
+- **Peter Lam** - [@dev-plam](https://github.com/dev-plam)
+- **Zachary Freeman** - [@zacharydfreeman](https://github.com/zacharydfreeman/)
+- **Jackie Yuan** - [@yuanjackie1](https://github.com/yuanjackie1)
+- **Jasmine Noor** - [@jasnoo](https://github.com/jasnoo)
+- **Minzo Kim** - [@minzo-kim](https://github.com/minzo-kim)
+- **Mark Teets** - [@MarkTeets](https://github.com/MarkTeets)
+- **Nick Huemmer** - [@NickHuemmer](https://github.com/ElDuke717)
+- **James McCollough** - [@j-mccoll](https://github.com/j-mccoll)
+- **Mike Bednarz** - [@mikebednarz](https://github.com/mikebednarz)
+- **Sergei Liubchenko** - [@sergeylvq](https://github.com/sergeylvq)
+- **Yididia Ketema** - [@yididiaketema](https://github.com/yididiaketema)
+- **Morah Geist** - [@morahgeist](https://github.com/morahgeist)
+- **Eivind Del Fierro** - [@EivindDelFierro](https://github.com/EivindDelFierro)
+- **Kyle Bell** - [@KyEBell](https://github.com/KyEBell)
+- **Sean Kelly** - [@brok3turtl3](https://github.com/brok3turtl3)
+- **Christopher Stamper** - [@ctstamper](https://github.com/ctstamper)
+- **Jimmy Phy** - [@jimmally](https://github.com/jimmally)
+- **Andrew Byun** - [@AndrewByun](https://github.com/AndrewByun)
+- **Kelvin Mirhan** - [@kelvinmirhan](https://github.com/kelvinmirhan)
+- **Jesse Rosengrant** - [@jrosengrant](https://github.com/jrosengrant)
+- **Liam Donaher** - [@leebology](https://github.com/leebology)
+- **David Moore** - [@Solodt55](https://github.com/Solodt55)
+- **John Banks** - [@Jbanks123](https://github.com/Jbanks123)
+
+
+⚖️ License
+
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
diff --git a/README.rus.md b/README.rus.md
new file mode 100644
index 000000000..508f69a1a
--- /dev/null
+++ b/README.rus.md
@@ -0,0 +1,366 @@
+
+
+Мощное расширение Chrome, которое улучшает процесс разработки React за счёт отладки с путешествиями во времени и углублённого мониторинга производительности
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## ✨ Ключевые особенности
+
+### 🔍 Визуализация состояния
+
+- **Разнообразные представления**: Отображение состояния приложения в виде графов компонентов, JSON-деревьев, графиков производительности и деревьев доступности
+- **История изменений**: Отслеживайте изменения состояния во времени с удобной визуализацией истории
+- **Метрики веб-приложения**: Отслеживайте важные метрики производительности в реальном времени
+- **Аналитика доступности**: Анализируйте дерево доступности вашего приложения на каждом этапе изменения состояния
+
+
+На главном экране доступны два основных выбора из выпадающего списка:
+
+- **Timejump**: Просматривайте и перемещайтесь по истории снимков состояния (snapshot) вашего приложения. Можно переместиться в любую точку во времени, чтобы увидеть, как состояние эволюционировало при изменениях. Также доступна кнопка воспроизведения, чтобы автоматически проиграть каждое изменение состояния.
+- **Providers / Consumers**: Глубже понимайте зависимости контекста приложения и его взаимодействия, визуализируя отношения провайдеров и потребителей.
+
+
+
+
+
+
+
+### ⏱️ Отладка с путешествиями во времени
+
+- **Снимки состояния**: Фиксируйте и перемещайтесь по истории состояния приложения
+- **Элементы управления воспроизведением**: Автоматически воспроизводите изменения состояния с регулировкой скорости
+- **Точки мгновенного перехода**: Мгновенно возвращайтесь к любому предыдущему состоянию
+- **Сравнение состояний**: Сравнивайте разницу между снимками состояния
+
+
+
+
+
+
+
+### 📊 Анализ производительности
+
+- **Метрики компонентов**: Отслеживайте время рендера и узкие места в производительности
+- **Сравнение серий**: Сопоставляйте производительность при разных наборах изменений состояния
+- **Определение перерисовок**: Находите и исправляйте избыточные циклы рендера
+- **Web Vitals**: Следите за Core Web Vitals и другими метриками
+
+
+
+### 🔄 Поддержка современных фреймворков
+
+
+
+ Полная совместимость с Gatsby, Next.js и Remix
+
+
+Поддержка TypeScript для классовых и функциональных компонентов
+
+
+Поддержка React Hooks и Context API
+
+
+
+
+### 💾 Сохранение и обмен состоянием
+
+Reactime упрощает сохранение и обмен историей состояния вашего приложения:
+
+- **Экспорт истории**: Сохраняйте записанные снимки в JSON-файл для дальнейшего анализа или передачи
+- **Импорт предыдущих сессий**: Загружайте ранее сохранённые снимки, чтобы сравнивать изменения состояния между разными сессиями
+- **Межсессионный анализ**: Сопоставляйте производительность и изменения состояния между разными этапами разработки
+
+
+
+
+
+
+
+### 📚 Интерактивная документация
+
+Reactime предлагает обширную документацию, помогающую разработчикам разобраться в архитектуре и API инструмента. После клонирования репозитория достаточно запустить `npm run docs` в корневой директории, а затем открыть сгенерированный файл `/docs/index.html`, в котором представлены:
+
+
+
+ Интерактивные диаграммы компонентов
+
+
+Типы и интерфейсы
+
+
+Обзор архитектуры кодовой базы
+
+
+API-справочник и примеры
+
+
+
+
+🎉 Что нового!
+
+Версия Reactime 26.0 предлагает полное обновление инструмента отладки React, включая:
+
+- **Новый показ данных контекста**
+
+ - Первая в своём роде визуализация состояния, основанного на хуке useContext
+ - Чёткое отображение отношений «провайдер – потребитель»
+ - Мониторинг значений контекста в реальном времени
+ - Подробная визуализация данных провайдеров
+
+- **Улучшенная отладка с путешествиями во времени**
+
+ - Переработанный интерфейс слайдера, размещённого вместе со снимками
+ - Элементы управления скоростью воспроизведения
+ - Более интуитивная навигация по состояниям
+ - Улучшенная визуализация снимков
+
+- **Современный переработанный интерфейс**
+
+ - Стильный, современный дизайн со скруглёнными элементами
+ - Интуитивная структура расположения элементов
+ - Новый тёмный режим
+ - Улучшенная визуальная иерархия
+
+- **Крупные технические улучшения**
+ - Исправлена проблема с сохранением соединения при бездействии или переключении вкладок
+ - Восстановлена визуализация дерева доступности (Accessibility Tree)
+ - Исправлены проблемы с захватом состояния для хуков useState в функциональных компонентах
+ - Общий рост стабильности и производительности расширения
+
+Благодаря этим обновлениям Reactime стал ещё более мощным, надёжным и удобным в использовании, устанавливая новый стандарт среди инструментов отладки React.
+
+Чтобы узнать больше о предыдущих релизах, перейдите по ссылке .
+
+
+
+🚀 Начало работы
+
+### Установка
+
+1. Установите [Reactime](https://chrome.google.com/webstore/detail/reactime/cgibknllccemdnfhfpmjhffpjfeidjga) из Интернет-магазина Chrome
+2. Установите необходимое расширение [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en), если у вас его ещё нет
+
+### Предварительные требования
+
+- Ваше React-приложение должно работать в **режиме разработки (development)**
+- Расширение React Developer Tools должно быть установлено
+- Браузер Chrome (рекомендуется версия 80 или выше)
+
+### Запуск Reactime
+
+Существует два способа открыть панель Reactime:
+
+1. **Контекстное меню**
+
+ - Кликните правой кнопкой мыши в любом месте вашего React-приложения
+ - Выберите «Reactime» в контекстном меню
+
+2. **Инструменты разработчика**
+ - Откройте Chrome DevTools (F12 или ⌘+⌥+I)
+ - Перейдите на вкладку «Reactime»
+
+После запуска Reactime автоматически начнёт отслеживать изменения состояния и метрики производительности вашего приложения.
+
+
+
+🤝 Участие в развитии Reactime
+
+Мы приглашаем всех желающих внести свой вклад в улучшение Reactime! Вот как вы можете помочь: 🙋 Contributing README
+
+1. **Начальный шаг**
+
+ - Сделайте форк репозитория
+ - Изучите наше подробное Руководство для разработчиков (Developer README)
+ - Настройте локальную среду разработки
+
+2. **Процесс сборки**
+ - Следуйте инструкциям по сборке в Руководстве для разработчиков
+ - Тщательно протестируйте свои изменения
+ - Создайте пул-реквест
+
+Присоединяйтесь к нашему растущему сообществу контрибьюторов и помогите формировать будущее инструментов отладки React! Подробные инструкции по участию и архитектуре проекта вы найдёте в 👩💻 Руководстве для разработчиков .
+
+
+
+🛠️ Устранение неполадок
+
+### ❓ Почему Reactime не записывает новые изменения состояния?
+
+Reactime потерял соединение с вкладкой, за которой ведётся наблюдение. Просто нажмите кнопку «reconnect», чтобы возобновить работу.
+
+### ❓ Почему Reactime не может найти мои хуки?
+
+Reactime обнаруживает и отслеживает хуки, просматривая «неминифицированный» код React в режиме разработки. Если ваш процесс сборки минифицирует или «уродует» (uglify) код — даже в режиме разработки — Reactime может не найти и не отследить ваши хуки. Чтобы исправить это:
+
+1. **Убедитесь, что сборка действительно предназначена для разработки**: Проверьте настройки вашего бандлера или инструмента сборки (например, Webpack, Babel, Vite и т. д.), чтобы приложение не было минимизировано и «уродовано» в режиме разработки.
+ - Например, в Webpack установите `mode: 'development'`, чтобы по умолчанию отключить минификацию.
+ - В Create React App, достаточно запустить `npm start` или `yarn start`, чтобы автоматически настроить неминифицированную сборку.
+2. **Проверьте, нет ли переопределений**: Убедитесь, что нет дополнительных Babel- или Webpack-плагинов, которые минифицируют ваш код, особенно если вы используете фреймворки вроде Next.js или Gatsby. Иногда дополнительные плагины или скрипты могут незаметно запускать минификацию.
+3. **Перезапустите и пересоберите**: После изменения настроек сборки перезапустите или пересоберите ваше приложение, чтобы новые настройки были применены. Затем обновите вкладку браузера, чтобы Reactime мог обнаружить ваши неминифицированные хуки.
+
+### ❓ Почему Reactime говорит, что React-приложение не найдено?
+
+Reactime изначально запускается, используя глобальный хук dev tools из API Chrome. Чтобы Chrome успел это загрузить, может потребоваться время. Попробуйте обновить (refresh) ваше приложение несколько раз, пока не увидите, что Reactime заработал.
+
+### ❓ Почему мне нужно включать React Dev Tools?
+
+Reactime работает в связке с React Developer Tools, чтобы получить доступ к дереву Fiber в React-приложении. Внутри Reactime просматривает дерево Fiber через глобальный хук из React Developer Tools, получая всю необходимую информацию для отображения разработчику.
+
+### ❓ Я нашёл(ла) баг в Reactime
+
+Reactime — это проект с открытым исходным кодом. Мы будем рады услышать ваши идеи по улучшению пользовательского опыта. Ознакомьтесь с 👩💻 Руководством для разработчиков и создайте пул-реквест (или Issue), чтобы предложить и совместно доработать изменения в Reactime.
+
+### ❓ Совместимость с версиями Node
+
+С выходом Node v18.12.1 (LTS) от 04.11.22 в скриптах появились команды
+`npm run dev` | `npm run build` для обратной совместимости. Для версии
+Node v16.16.0 используйте скрипты `npm run devlegacy` | `npm run buildlegacy`
+
+
+
+✍️ Авторы
+
+- **Garrett Chow** - [@garrettlchow](https://github.com/garrettlchow)
+- **Ellie Simens** - [@elliesimens](https://github.com/elliesimens)
+- **Ragad Mohammed** - [@ragad-mohammed](https://github.com/ragad-mohammed)
+- **Daniel Ryczek** - [@dryczek14](https://github.com/dryczek01)
+- **Patrice Pinardo** - [@pinardo88](https://github.com/pinardo88)
+- **Haider Ali** - [@hali03](https://github.com/hali03)
+- **Jose Luis Sanchez** - [@JoseSanchez1996](https://github.com/JoseSanchez1996)
+- **Logan Nelsen** - [@ljn16](https://github.com/ljn16)
+- **Mel Koppens** - [@MelKoppens](https://github.com/MelKoppens)
+- **Amy Yang** - [@ay7991](https://github.com/ay7991)
+- **Eva Ury** - [@evaSUry](https://github.com/evaSUry)
+- **Jesse Guerrero** - [@jguerrero35](https://github.com/jguerrero35)
+- **Oliver Cho** - [@Oliver-Cho](https://github.com/Oliver-Cho)
+- **Ben Margolius** - [@benmarg](https://github.com/benmarg)
+- **Eric Yun** - [@ericsngyun](https://github.com/ericsngyun)
+- **James Nghiem** - [@jemzir](https://github.com/jemzir)
+- **Wilton Lee** - [@wiltonlee948](https://github.com/wiltonlee948)
+- **Louis Lam** - [@llam722](https://github.com/llam722)
+- **Samuel Tran** - [@leumastr](https://github.com/leumastr)
+- **Brian Yang** - [@yangbrian310](https://github.com/yangbrian310)
+- **Emin Tahirov** - [@eminthrv](https://github.com/eminthrv)
+- **Peng Dong** - [@d28601581](https://github.com/d28601581)
+- **Ozair Ghulam** - [@ozairgh](https://github.com/ozairgh)
+- **Christina Or** - [@christinaor](https://github.com/christinaor)
+- **Khanh Bui** - [@AndyB909](https://github.com/AndyB909)
+- **David Kim** - [@codejunkie7](https://github.com/codejunkie7)
+- **Robby Tipton** - [@RobbyTipton](https://github.com/RobbyTipton)
+- **Kevin HoEun Lee** - [@khobread](https://github.com/khobread)
+- **Christopher LeBrett** - [@fscgolden](https://github.com/fscgolden)
+- **Joseph Park** - [@joeepark](https://github.com/joeepark)
+- **Kris Sorensen** - [@kris-sorensen](https://github.com/kris-sorensen)
+- **Daljit Gill** - [@dgill05](https://github.com/dgill05)
+- **Ben Michareune** - [@bmichare](https://github.com/bmichare)
+- **Dane Corpion** - [@danecorpion](https://github.com/danecorpion)
+- **Harry Fox** - [@StackOverFlowWhereArtThou](https://github.com/StackOverFlowWhereArtThou)
+- **Nathan Richardson** - [@BagelEnthusiast](https://github.com/BagelEnthusiast)
+- **David Bernstein** - [@dangitbobbeh](https://github.com/dangitbobbeh)
+- **Joseph Stern** - [@josephiswhere](https://github.com/josephiswhere)
+- **Dennis Lopez** - [@DennisLpz](https://github.com/DennisLpz)
+- **Cole Styron** - [@colestyron](https://github.com/C-STYR)
+- **Ali Rahman** - [@CourageWolf](https://github.com/CourageWolf)
+- **Caner Demir** - [@demircaner](https://github.com/demircaner)
+- **Kevin Ngo** - [@kev-ngo](https://github.com/kev-ngo)
+- **Becca Viner** - [@rtviner](https://github.com/rtviner)
+- **Caitlin Chan** - [@caitlinchan23](https://github.com/caitlinchan23)
+- **Kim Mai Nguyen** - [@Nkmai](https://github.com/Nkmai)
+- **Tania Lind** - [@lind-tania](https://github.com/lind-tania)
+- **Alex Landeros** - [@AlexanderLanderos](https://github.com/AlexanderLanderos)
+- **Chris Guizzetti** - [@guizzettic](https://github.com/guizzettic)
+- **Jason Victor** - [@theqwertypusher](https://github.com/Theqwertypusher)
+- **Sanjay Lavingia** - [@sanjaylavingia](https://github.com/sanjaylavingia)
+- **Vincent Nguyen** - [@VNguyenCode](https://github.com/VNguyenCode)
+- **Haejin Jo** - [@haejinjo](https://github.com/haejinjo)
+- **Hien Nguyen** - [@hienqn](https://github.com/hienqn)
+- **Jack Crish** - [@JackC27](https://github.com/JackC27)
+- **Kevin Fey** - [@kevinfey](https://github.com/kevinfey)
+- **Carlos Perez** - [@crperezt](https://github.com/crperezt)
+- **Edwin Menendez** - [@edwinjmenendez](https://github.com/edwinjmenendez)
+- **Gabriela Jardim Aquino** - [@aquinojardim](https://github.com/aquinojardim)
+- **Greg Panciera** - [@gpanciera](https://github.com/gpanciera)
+- **Nathanael Wa Mwenze** - [@nmwenz90](https://github.com/nmwenz90)
+- **Ryan Dang** - [@rydang](https://github.com/rydang)
+- **Bryan Lee** - [@mylee1995](https://github.com/mylee1995)
+- **Josh Kim** - [@joshua0308](https://github.com/joshua0308)
+- **Sierra Swaby** - [@starkspark](https://github.com/starkspark)
+- **Ruth Anam** - [@nusanam](https://github.com/nusanam)
+- **David Chai** - [@davidchaidev](https://github.com/davidchai717)
+- **Yujin Kang** - [@yujinkay](https://github.com/yujinkay)
+- **Andy Wong** - [@andynullwong](https://github.com/andynullwong)
+- **Chris Flannery** - [@chriswillsflannery](https://github.com/chriswillsflannery)
+- **Rajeeb Banstola** - [@rajeebthegreat](https://github.com/rajeebthegreat)
+- **Prasanna Malla** - [@prasmalla](https://github.com/prasmalla)
+- **Rocky Lin** - [@rocky9413](https://github.com/rocky9413)
+- **Abaas Khorrami** - [@dubalol](https://github.com/dubalol)
+- **Ergi Shehu** - [@Ergi516](https://github.com/ergi516)
+- **Raymond Kwan** - [@rkwn](https://github.com/rkwn)
+- **Joshua Howard** - [@Joshua-Howard](https://github.com/joshua-howard)
+- **Lina Shin** - [@rxlina](https://github.com/rxlina)
+- **Andy Tsou** - [@andytsou19](https://github.com/andytsou19)
+- **Feiyi Wu** - [@FreyaWu](https://github.com/FreyaWu)
+- **Viet Nguyen** - [@vnguyen95](https://github.com/vnguyen95)
+- **Alex Gomez** - [@alexgomez9](https://github.com/alexgomez9)
+- **Edar Liu** - [@liuedar](https://github.com/liuedar)
+- **Kristina Wallen** - [@kristinawallen](https://github.com/kristinawallen)
+- **Quan Le** - [@Blachfog](https://github.com/Blachfog)
+- **Robert Maeda** - [@robmaeda](https://github.com/robmaeda)
+- **Lance Ziegler** - [@lanceziegler](https://github.com/lanceziegler)
+- **Ngoc Zwolinski** - [@ngoczwolinski](https://github.com/ngoczwolinski)
+- **Peter Lam** - [@dev-plam](https://github.com/dev-plam)
+- **Zachary Freeman** - [@zacharydfreeman](https://github.com/zacharydfreeman/)
+- **Jackie Yuan** - [@yuanjackie1](https://github.com/yuanjackie1)
+- **Jasmine Noor** - [@jasnoo](https://github.com/jasnoo)
+- **Minzo Kim** - [@minzo-kim](https://github.com/minzo-kim)
+- **Mark Teets** - [@MarkTeets](https://github.com/MarkTeets)
+- **Nick Huemmer** - [@NickHuemmer](https://github.com/ElDuke717)
+- **James McCollough** - [@j-mccoll](https://github.com/j-mccoll)
+- **Mike Bednarz** - [@mikebednarz](https://github.com/mikebednarz)
+- **Sergei Liubchenko** - [@sergeylvq](https://github.com/sergeylvq)
+- **Yididia Ketema** - [@yididiaketema](https://github.com/yididiaketema)
+- **Morah Geist** - [@morahgeist](https://github.com/morahgeist)
+- **Eivind Del Fierro** - [@EivindDelFierro](https://github.com/EivindDelFierro)
+- **Kyle Bell** - [@KyEBell](https://github.com/KyEBell)
+- **Sean Kelly** - [@brok3turtl3](https://github.com/brok3turtl3)
+- **Christopher Stamper** - [@ctstamper](https://github.com/ctstamper)
+- **Jimmy Phy** - [@jimmally](https://github.com/jimmally)
+- **Andrew Byun** - [@AndrewByun](https://github.com/AndrewByun)
+- **Kelvin Mirhan** - [@kelvinmirhan](https://github.com/kelvinmirhan)
+- **Jesse Rosengrant** - [@jrosengrant](https://github.com/jrosengrant)
+- **Liam Donaher** - [@leebology](https://github.com/leebology)
+- **David Moore** - [@Solodt55](https://github.com/Solodt55)
+- **John Banks** - [@Jbanks123](https://github.com/Jbanks123)
+
+
+⚖️ Лицензия
+
+Этот проект распространяется по лицензии MIT — подробности см. в файле [LICENSE](LICENSE).
diff --git a/assets/Back_End_Dependency_Chart_v22.jpg b/assets/Back_End_Dependency_Chart_v22.jpg
new file mode 100644
index 000000000..c7dfb3511
Binary files /dev/null and b/assets/Back_End_Dependency_Chart_v22.jpg differ
diff --git a/assets/DataFlowDiagramV23.png b/assets/DataFlowDiagramV23.png
new file mode 100644
index 000000000..f835c0673
Binary files /dev/null and b/assets/DataFlowDiagramV23.png differ
diff --git a/assets/Front_End_Dependency_Chart_v22.jpg b/assets/Front_End_Dependency_Chart_v22.jpg
new file mode 100644
index 000000000..6f8d26937
Binary files /dev/null and b/assets/Front_End_Dependency_Chart_v22.jpg differ
diff --git a/assets/backend-recordSnapshot.png b/assets/backend-recordSnapshot.png
new file mode 100644
index 000000000..b3e9f1559
Binary files /dev/null and b/assets/backend-recordSnapshot.png differ
diff --git a/assets/backend-timeTravel.png b/assets/backend-timeTravel.png
new file mode 100644
index 000000000..eee1a4a23
Binary files /dev/null and b/assets/backend-timeTravel.png differ
diff --git a/assets/frontend-diagram.png b/assets/frontend-diagram.png
new file mode 100644
index 000000000..84a6b03cb
Binary files /dev/null and b/assets/frontend-diagram.png differ
diff --git a/assets/gifs/GeneralDemoGif_V26.gif b/assets/gifs/GeneralDemoGif_V26.gif
new file mode 100644
index 000000000..cae64e5de
Binary files /dev/null and b/assets/gifs/GeneralDemoGif_V26.gif differ
diff --git a/assets/gifs/ImportExportGif_V26.gif b/assets/gifs/ImportExportGif_V26.gif
new file mode 100644
index 000000000..4947ff1dd
Binary files /dev/null and b/assets/gifs/ImportExportGif_V26.gif differ
diff --git a/assets/gifs/ProviderConsumer_V26.gif b/assets/gifs/ProviderConsumer_V26.gif
new file mode 100644
index 000000000..ff436de8f
Binary files /dev/null and b/assets/gifs/ProviderConsumer_V26.gif differ
diff --git a/assets/gifs/TimeTravelGif_V26.gif b/assets/gifs/TimeTravelGif_V26.gif
new file mode 100644
index 000000000..e1d20bb71
Binary files /dev/null and b/assets/gifs/TimeTravelGif_V26.gif differ
diff --git a/assets/gifs/app_main_v26.png b/assets/gifs/app_main_v26.png
new file mode 100644
index 000000000..c0d9076fd
Binary files /dev/null and b/assets/gifs/app_main_v26.png differ
diff --git a/assets/gifs/console.gif b/assets/gifs/console.gif
new file mode 100644
index 000000000..0d4e5d312
Binary files /dev/null and b/assets/gifs/console.gif differ
diff --git a/assets/gifs/extension-console.gif b/assets/gifs/extension-console.gif
new file mode 100644
index 000000000..0652609a4
Binary files /dev/null and b/assets/gifs/extension-console.gif differ
diff --git a/assets/gifs/reactime-console.gif b/assets/gifs/reactime-console.gif
new file mode 100644
index 000000000..206be2336
Binary files /dev/null and b/assets/gifs/reactime-console.gif differ
diff --git a/assets/gifs/reactime-dev-setup.gif b/assets/gifs/reactime-dev-setup.gif
new file mode 100644
index 000000000..cbe915af0
Binary files /dev/null and b/assets/gifs/reactime-dev-setup.gif differ
diff --git a/assets/logos/blackWhiteSquareIcon128.png b/assets/logos/blackWhiteSquareIcon128.png
new file mode 100644
index 000000000..9ff5c33e0
Binary files /dev/null and b/assets/logos/blackWhiteSquareIcon128.png differ
diff --git a/assets/logos/blackWhiteSquareIcon48.png b/assets/logos/blackWhiteSquareIcon48.png
new file mode 100644
index 000000000..d054e2b6c
Binary files /dev/null and b/assets/logos/blackWhiteSquareIcon48.png differ
diff --git a/assets/logos/marqueePromoTitle.png b/assets/logos/marqueePromoTitle.png
new file mode 100644
index 000000000..7c13a6d7e
Binary files /dev/null and b/assets/logos/marqueePromoTitle.png differ
diff --git a/assets/logos/smallPromoTitle.png b/assets/logos/smallPromoTitle.png
new file mode 100644
index 000000000..0074165eb
Binary files /dev/null and b/assets/logos/smallPromoTitle.png differ
diff --git a/assets/logos/whiteBlackSquareIcon128.png b/assets/logos/whiteBlackSquareIcon128.png
new file mode 100644
index 000000000..3f95221cb
Binary files /dev/null and b/assets/logos/whiteBlackSquareIcon128.png differ
diff --git a/assets/logos/whiteBlackSquareIcon48.png b/assets/logos/whiteBlackSquareIcon48.png
new file mode 100644
index 000000000..6788a2d02
Binary files /dev/null and b/assets/logos/whiteBlackSquareIcon48.png differ
diff --git a/assets/obsolete/gifs/GeneralDemoGif_V23.gif b/assets/obsolete/gifs/GeneralDemoGif_V23.gif
new file mode 100644
index 000000000..4dc6f8924
Binary files /dev/null and b/assets/obsolete/gifs/GeneralDemoGif_V23.gif differ
diff --git a/assets/obsolete/gifs/ImportExportGif_V23.gif b/assets/obsolete/gifs/ImportExportGif_V23.gif
new file mode 100644
index 000000000..0b8d9f3da
Binary files /dev/null and b/assets/obsolete/gifs/ImportExportGif_V23.gif differ
diff --git a/assets/obsolete/gifs/PerformanceGif_V23.gif b/assets/obsolete/gifs/PerformanceGif_V23.gif
new file mode 100644
index 000000000..99b9bc1c4
Binary files /dev/null and b/assets/obsolete/gifs/PerformanceGif_V23.gif differ
diff --git a/assets/obsolete/gifs/TimeTravelGif_V23.gif b/assets/obsolete/gifs/TimeTravelGif_V23.gif
new file mode 100644
index 000000000..4e14f229b
Binary files /dev/null and b/assets/obsolete/gifs/TimeTravelGif_V23.gif differ
diff --git a/assets/obsolete/logos/blackWhiteSquareIcon.png b/assets/obsolete/logos/blackWhiteSquareIcon.png
new file mode 100644
index 000000000..4a15ecb29
Binary files /dev/null and b/assets/obsolete/logos/blackWhiteSquareIcon.png differ
diff --git a/assets/obsolete/logos/blackWhiteSquareIcon128_old.png b/assets/obsolete/logos/blackWhiteSquareIcon128_old.png
new file mode 100644
index 000000000..2e89a45da
Binary files /dev/null and b/assets/obsolete/logos/blackWhiteSquareIcon128_old.png differ
diff --git a/assets/obsolete/logos/blackWhiteSquareIcon48_old.png b/assets/obsolete/logos/blackWhiteSquareIcon48_old.png
new file mode 100644
index 000000000..298516b15
Binary files /dev/null and b/assets/obsolete/logos/blackWhiteSquareIcon48_old.png differ
diff --git a/assets/obsolete/logos/blackWhiteSquareLogo.png b/assets/obsolete/logos/blackWhiteSquareLogo.png
new file mode 100644
index 000000000..7203dabbf
Binary files /dev/null and b/assets/obsolete/logos/blackWhiteSquareLogo.png differ
diff --git a/assets/obsolete/logos/blackWhiteSquareLogoMedium.png b/assets/obsolete/logos/blackWhiteSquareLogoMedium.png
new file mode 100644
index 000000000..c283491ea
Binary files /dev/null and b/assets/obsolete/logos/blackWhiteSquareLogoMedium.png differ
diff --git a/assets/obsolete/logos/blackWhiteSquareSite.png b/assets/obsolete/logos/blackWhiteSquareSite.png
new file mode 100644
index 000000000..fc5cbb02a
Binary files /dev/null and b/assets/obsolete/logos/blackWhiteSquareSite.png differ
diff --git a/assets/obsolete/logos/marqueePromoTitle.png b/assets/obsolete/logos/marqueePromoTitle.png
new file mode 100644
index 000000000..e47d77771
Binary files /dev/null and b/assets/obsolete/logos/marqueePromoTitle.png differ
diff --git a/assets/obsolete/logos/marqueePromoTitle_old.png b/assets/obsolete/logos/marqueePromoTitle_old.png
new file mode 100644
index 000000000..e47d77771
Binary files /dev/null and b/assets/obsolete/logos/marqueePromoTitle_old.png differ
diff --git a/assets/obsolete/logos/smallPromoTitle.png b/assets/obsolete/logos/smallPromoTitle.png
new file mode 100644
index 000000000..254f85d69
Binary files /dev/null and b/assets/obsolete/logos/smallPromoTitle.png differ
diff --git a/assets/obsolete/logos/smallPromoTitle_old.png b/assets/obsolete/logos/smallPromoTitle_old.png
new file mode 100644
index 000000000..254f85d69
Binary files /dev/null and b/assets/obsolete/logos/smallPromoTitle_old.png differ
diff --git a/assets/obsolete/logos/whiteBlackSquareIcon.png b/assets/obsolete/logos/whiteBlackSquareIcon.png
new file mode 100644
index 000000000..70592ee34
Binary files /dev/null and b/assets/obsolete/logos/whiteBlackSquareIcon.png differ
diff --git a/assets/obsolete/logos/whiteBlackSquareIcon128.png b/assets/obsolete/logos/whiteBlackSquareIcon128.png
new file mode 100644
index 000000000..9749d8d41
Binary files /dev/null and b/assets/obsolete/logos/whiteBlackSquareIcon128.png differ
diff --git a/assets/obsolete/logos/whiteBlackSquareIcon48.png b/assets/obsolete/logos/whiteBlackSquareIcon48.png
new file mode 100644
index 000000000..b5b1ef559
Binary files /dev/null and b/assets/obsolete/logos/whiteBlackSquareIcon48.png differ
diff --git a/assets/obsolete/logos/whiteBlackSquareLogo.png b/assets/obsolete/logos/whiteBlackSquareLogo.png
new file mode 100644
index 000000000..c8756bb01
Binary files /dev/null and b/assets/obsolete/logos/whiteBlackSquareLogo.png differ
diff --git a/assets/obsolete/logos/whiteBlackSquareSite.png b/assets/obsolete/logos/whiteBlackSquareSite.png
new file mode 100644
index 000000000..15b4af999
Binary files /dev/null and b/assets/obsolete/logos/whiteBlackSquareSite.png differ
diff --git a/assets/reactime with website svg.svg b/assets/reactime with website svg.svg
deleted file mode 100644
index 3356a87b3..000000000
--- a/assets/reactime with website svg.svg
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/assets/readme-logo-svg.svg b/assets/readme-logo-svg.svg
deleted file mode 100644
index 3356a87b3..000000000
--- a/assets/readme-logo-svg.svg
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/assets/readme_logo.png b/assets/readme_logo.png
deleted file mode 100644
index 618512e11..000000000
Binary files a/assets/readme_logo.png and /dev/null differ
diff --git a/babel.config.js b/babel.config.js
deleted file mode 100644
index 677962651..000000000
--- a/babel.config.js
+++ /dev/null
@@ -1,13 +0,0 @@
-module.exports = {
- presets: [
- [
- '@babel/preset-env',
- {
- targets: {
- node: 'current',
- },
- },
- ],
- '@babel/preset-react',
- ],
-};
diff --git a/demo-app-next/.gitignore b/demo-app-next/.gitignore
new file mode 100644
index 000000000..20fccdd4b
--- /dev/null
+++ b/demo-app-next/.gitignore
@@ -0,0 +1,30 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
diff --git a/demo-app-next/README.md b/demo-app-next/README.md
new file mode 100644
index 000000000..02695bc1d
--- /dev/null
+++ b/demo-app-next/README.md
@@ -0,0 +1 @@
+This is a starter template for [Learn Next.js](https://nextjs.org/learn).
\ No newline at end of file
diff --git a/demo-app-next/next-env.d.ts b/demo-app-next/next-env.d.ts
new file mode 100644
index 000000000..a4a7b3f5c
--- /dev/null
+++ b/demo-app-next/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
diff --git a/demo-app-next/package.json b/demo-app-next/package.json
new file mode 100644
index 000000000..0f7a13bba
--- /dev/null
+++ b/demo-app-next/package.json
@@ -0,0 +1,18 @@
+{
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start"
+ },
+ "dependencies": {
+ "next": "latest",
+ "react": "18.2.0",
+ "react-dom": "18.2.0"
+ },
+ "devDependencies": {
+ "@types/node": "18.15.0",
+ "@types/react": "18.0.28",
+ "typescript": "4.9.5"
+ }
+}
diff --git a/demo-app-next/public/favicon.ico b/demo-app-next/public/favicon.ico
new file mode 100644
index 000000000..4965832f2
Binary files /dev/null and b/demo-app-next/public/favicon.ico differ
diff --git a/demo-app-next/public/vercel.svg b/demo-app-next/public/vercel.svg
new file mode 100644
index 000000000..fbf0e25a6
--- /dev/null
+++ b/demo-app-next/public/vercel.svg
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/demo-app-next/src/components/Board.tsx b/demo-app-next/src/components/Board.tsx
new file mode 100644
index 000000000..950f292df
--- /dev/null
+++ b/demo-app-next/src/components/Board.tsx
@@ -0,0 +1,136 @@
+import React, { Component } from 'react';
+import Row from './Row';
+import { BoardContent, Scoreboard, Player } from '../types/types';
+
+type BoardState = {
+ board: BoardContent;
+ currentPlayer: Player;
+ gameOver: boolean;
+ message: string;
+ scoreboard: Scoreboard;
+};
+
+class Board extends Component<{}, BoardState> {
+ constructor(props: unknown) {
+ super(props);
+ this.state = {
+ board: this.newBoard(),
+ currentPlayer: 'X',
+ gameOver: false,
+ message: '',
+ scoreboard: { X: 0, O: 0 },
+ };
+
+ this.resetBoard = this.resetBoard.bind(this);
+ this.handleBoxClick = this.handleBoxClick.bind(this);
+ }
+
+ componentDidUpdate(): void {
+ this.checkForWinner();
+ }
+
+ /**
+ * @method newBoard
+ * @description - returns a blank BoardContent array,
+ * for the start of a new game
+ */
+ newBoard(): BoardContent {
+ return [
+ ['-', '-', '-'],
+ ['-', '-', '-'],
+ ['-', '-', '-'],
+ ];
+ }
+
+ /**
+ * @method resetBoard
+ * @description - sets to board object to be all '-',
+ * and sets gameOver and message to default state
+ */
+ resetBoard(): void {
+ this.setState({
+ gameOver: false,
+ board: this.newBoard(),
+ message: '',
+ });
+ }
+
+ /**
+ * @method checkForWinner
+ * @description - checks to see if either player has filled a row
+ * if so, ends the game and updates the message to declare winner
+ */
+ checkForWinner(): void {
+ const { board, gameOver, currentPlayer } = this.state;
+
+ const spacesLeft = (): boolean => {
+ for (let i of board) {
+ if (i.includes('-')) return true;
+ }
+ return false;
+ };
+
+ if (!gameOver) {
+ // win conditions: matching rows, columns, or diagonals, that are not empty('-')
+ if (
+ (board[0][0] === board[0][1] && board[0][1] === board[0][2] && board[0][2] !== '-') ||
+ (board[1][0] === board[1][1] && board[1][1] === board[1][2] && board[1][2] !== '-') ||
+ (board[2][0] === board[2][1] && board[2][1] === board[2][2] && board[2][2] !== '-') ||
+ (board[0][0] === board[1][0] && board[1][0] === board[2][0] && board[2][0] !== '-') ||
+ (board[0][1] === board[1][1] && board[1][1] === board[2][1] && board[2][1] !== '-') ||
+ (board[0][2] === board[1][2] && board[1][2] === board[2][2] && board[2][2] !== '-') ||
+ (board[0][0] === board[1][1] && board[1][1] === board[2][2] && board[2][2] !== '-') ||
+ (board[2][0] === board[1][1] && board[1][1] === board[0][2] && board[0][2] !== '-')
+ ) {
+ // winner is the person who's turn was previous
+ const winner: Player = currentPlayer === 'X' ? 'O' : 'X';
+
+ this.setState({
+ gameOver: true,
+ message: `Player ${winner} wins!`,
+ });
+
+ // draw condition: no '-' remaining in board without above win condition triggering
+ } else if (!spacesLeft()) {
+ this.setState({
+ gameOver: true,
+ message: 'Draw!',
+ });
+ }
+ }
+ }
+
+ handleBoxClick(row: number, column: number): void {
+ const boardCopy: BoardContent = [
+ [...this.state.board[0]],
+ [...this.state.board[1]],
+ [...this.state.board[2]],
+ ];
+ boardCopy[row][column] = this.state.currentPlayer;
+ const newPlayer: Player = this.state.currentPlayer === 'X' ? 'O' : 'X';
+ this.setState({ board: boardCopy, currentPlayer: newPlayer });
+ }
+
+ render(): JSX.Element {
+ const rows: Array = [];
+ for (let i = 0; i < 3; i++) {
+ rows.push(
+
,
+ );
+ }
+ // const { X, O }: Scoreboard = this.state.scoreboard;
+
+ return (
+
+
Tic Tac Toe
+ {this.state.gameOver && {this.state.message} }
+ {rows}
+
+ Reset
+
+
+ );
+ }
+}
+
+export default Board;
diff --git a/demo-app-next/src/components/Box.tsx b/demo-app-next/src/components/Box.tsx
new file mode 100644
index 000000000..f44c00d62
--- /dev/null
+++ b/demo-app-next/src/components/Box.tsx
@@ -0,0 +1,18 @@
+import { BoardText } from '../types/types';
+
+type BoxProps = {
+ value: BoardText;
+ row: number;
+ column: number;
+ handleBoxClick: (row: number, column: number) => void;
+};
+
+const Box = (props: BoxProps): JSX.Element => {
+ return (
+ props.handleBoxClick(props.row, props.column)}>
+ {props.value}
+
+ );
+};
+
+export default Box;
diff --git a/demo-app-next/src/components/Buttons.tsx b/demo-app-next/src/components/Buttons.tsx
new file mode 100644
index 000000000..184e30acf
--- /dev/null
+++ b/demo-app-next/src/components/Buttons.tsx
@@ -0,0 +1,19 @@
+import Increment from './Increment';
+
+export default function Buttons(): JSX.Element {
+ const buttons = [];
+ for (let i = 0; i < 4; i++) {
+ buttons.push( );
+ }
+
+ return (
+
+
Stateful Buttons
+
+ These buttons are functional components that each manage their own state with the useState
+ hook.
+
+ {buttons}
+
+ );
+}
diff --git a/demo-app-next/src/components/Increment.tsx b/demo-app-next/src/components/Increment.tsx
new file mode 100644
index 000000000..791f08f23
--- /dev/null
+++ b/demo-app-next/src/components/Increment.tsx
@@ -0,0 +1,11 @@
+import React, { useState } from 'react';
+
+export default function Increment(): JSX.Element {
+ const [count, setCount] = useState(0);
+
+ return (
+ setCount(count + 1)}>
+ You clicked me {count} times.
+
+ );
+}
diff --git a/demo-app-next/src/components/Row.tsx b/demo-app-next/src/components/Row.tsx
new file mode 100644
index 000000000..961a83c73
--- /dev/null
+++ b/demo-app-next/src/components/Row.tsx
@@ -0,0 +1,27 @@
+import Box from './Box';
+import { BoardText } from '../types/types';
+
+type RowProps = {
+ handleBoxClick: (row: number, column: number) => void;
+ values: Array;
+ row: number;
+};
+
+const Row = (props: RowProps) => {
+ const boxes: Array = [];
+ for (let i = 0; i < 3; i++) {
+ boxes.push(
+ ,
+ );
+ }
+
+ return {boxes}
;
+};
+
+export default Row;
diff --git a/demo-app-next/src/components/navbar.tsx b/demo-app-next/src/components/navbar.tsx
new file mode 100644
index 000000000..b841698ea
--- /dev/null
+++ b/demo-app-next/src/components/navbar.tsx
@@ -0,0 +1,17 @@
+import Link from 'next/link';
+
+export default function Navbar(): JSX.Element {
+ return (
+
+
+ About
+
+
+ Tic-Tac-Toe
+
+
+ Counter
+
+
+ );
+}
diff --git a/demo-app-next/src/pages/_app.tsx b/demo-app-next/src/pages/_app.tsx
new file mode 100644
index 000000000..279f38d6b
--- /dev/null
+++ b/demo-app-next/src/pages/_app.tsx
@@ -0,0 +1,6 @@
+import '../../styles/style.css';
+import React from 'react';
+
+export default function MyApp({ Component, pageProps }): JSX.Element {
+ return ;
+}
diff --git a/demo-app-next/src/pages/buttons/index.tsx b/demo-app-next/src/pages/buttons/index.tsx
new file mode 100644
index 000000000..4e0b24237
--- /dev/null
+++ b/demo-app-next/src/pages/buttons/index.tsx
@@ -0,0 +1,11 @@
+import Buttons from '../../components/Buttons';
+import Navbar from '../../components/navbar';
+
+export default function ButtonsPage(): JSX.Element {
+ return (
+
+
+
+
+ );
+}
diff --git a/demo-app-next/src/pages/index.tsx b/demo-app-next/src/pages/index.tsx
new file mode 100644
index 000000000..5bcd002d3
--- /dev/null
+++ b/demo-app-next/src/pages/index.tsx
@@ -0,0 +1,29 @@
+import Navbar from '../components/navbar';
+import React from 'react';
+
+export default function Home(): JSX.Element {
+ return (
+
+
+
+
Lorem Ipsum
+
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
+ ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
+ sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
+ est laborum."
+
+
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
+ ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
+ sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
+ est laborum."
+
+
+
+ );
+}
diff --git a/demo-app-next/src/pages/tictactoe/index.tsx b/demo-app-next/src/pages/tictactoe/index.tsx
new file mode 100644
index 000000000..d2376d215
--- /dev/null
+++ b/demo-app-next/src/pages/tictactoe/index.tsx
@@ -0,0 +1,11 @@
+import Board from '../../components/Board';
+import Navbar from '../../components/navbar';
+
+export default function BoardPage(): JSX.Element {
+ return (
+
+
+
+
+ );
+}
diff --git a/demo-app-next/src/types/types.ts b/demo-app-next/src/types/types.ts
new file mode 100644
index 000000000..cc8252e42
--- /dev/null
+++ b/demo-app-next/src/types/types.ts
@@ -0,0 +1,10 @@
+export type Scoreboard = {
+ X: number;
+ O: number;
+};
+
+export type Player = 'X' | 'O';
+
+export type BoardText = 'X' | 'O' | '-';
+
+export type BoardContent = Array>;
diff --git a/demo-app-next/styles/Home.module.css b/demo-app-next/styles/Home.module.css
new file mode 100644
index 000000000..b82575626
--- /dev/null
+++ b/demo-app-next/styles/Home.module.css
@@ -0,0 +1,91 @@
+.container {
+ min-height: 100vh;
+ padding: 0 0.5rem;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+.title a {
+ color: #0070f3;
+ text-decoration: none;
+}
+
+.title a:hover,
+.title a:focus,
+.title a:active {
+ text-decoration: underline;
+}
+
+.title {
+ margin: 0 0 1rem;
+ line-height: 1.15;
+ font-size: 3.6rem;
+}
+
+.title {
+ text-align: center;
+}
+
+.title,
+.description {
+ text-align: center;
+}
+
+
+.description {
+ line-height: 1.5;
+ font-size: 1.5rem;
+}
+
+.grid {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-wrap: wrap;
+
+ max-width: 800px;
+ margin-top: 3rem;
+}
+
+.card {
+ margin: 1rem;
+ flex-basis: 45%;
+ padding: 1.5rem;
+ text-align: left;
+ color: inherit;
+ text-decoration: none;
+ border: 1px solid #eaeaea;
+ border-radius: 10px;
+ transition: color 0.15s ease, border-color 0.15s ease;
+}
+
+.card:hover,
+.card:focus,
+.card:active {
+ color: #0070f3;
+ border-color: #0070f3;
+}
+
+.card h3 {
+ margin: 0 0 1rem 0;
+ font-size: 1.5rem;
+}
+
+.card p {
+ margin: 0;
+ font-size: 1.25rem;
+ line-height: 1.5;
+}
+
+.logo {
+ height: 1em;
+}
+
+@media (max-width: 600px) {
+ .grid {
+ width: 100%;
+ flex-direction: column;
+ }
+}
diff --git a/demo-app-next/styles/globals.css b/demo-app-next/styles/globals.css
new file mode 100644
index 000000000..6c7e3c462
--- /dev/null
+++ b/demo-app-next/styles/globals.css
@@ -0,0 +1,149 @@
+body {
+ margin: 0;
+ font-family: Arial, Helvetica, sans-serif;
+ background-color: #fff4f4;
+}
+
+h1,
+h4 {
+ text-align: center;
+}
+
+/* Navbar */
+
+.nav {
+ background-color: #ff6569;
+
+ display: flex;
+ justify-content: space-evenly;
+
+ padding: 30px;
+ height: 30px;
+}
+
+.link {
+ flex-grow: 1;
+
+ font-size: 1.5em;
+ text-decoration: none;
+ text-align: center;
+
+ color: #fff4f4;
+}
+
+.link:hover {
+ font-size: 2em;
+}
+
+/* About */
+
+.about {
+ background-color: #ffffff;
+ color: #330002;
+
+ padding-top: 1em;
+ padding-bottom: 2em;
+ padding-right: 4em;
+ padding-left: 4em;
+ margin-top: 2em;
+
+ max-width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+}
+
+/* Tic-Tac-Toe */
+
+.board {
+ background-color: #ffffff;
+ color: #330002;
+
+ margin-top: 2em;
+
+ padding-top: 1em;
+ padding-bottom: 1em;
+ padding-left: 4em;
+ padding-right: 4em;
+
+ width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+}
+
+.box {
+ background-color: #62d6fb;
+ border-style: solid;
+ border-color: #ffffff;
+ border-radius: 5px;
+
+ height: 100px;
+ width: 100px;
+
+ font-size: 4em;
+}
+
+#reset {
+ color: #ff6569;
+ font-size: 1.5em;
+
+ background-color: #ffffff;
+ border-style: solid;
+ border-color: #ff6569;
+ border-radius: 5px;
+
+ margin-top: 20px;
+ margin-bottom: 20px;
+
+ width: 100%;
+
+ padding: 0.5em;
+}
+
+#reset:hover {
+ color: #ffffff;
+ background-color: #ff6569;
+}
+
+/* Counter */
+
+.buttons {
+ background-color: #ffffff;
+ color: #330002;
+
+ padding-top: 1em;
+ padding-bottom: 2em;
+ padding-right: 4em;
+ padding-left: 4em;
+ margin-top: 2em;
+
+ max-width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+}
+
+.increment {
+ color: #ffffff;
+ font-size: 1.5em;
+
+ background-color: #ff6569;
+ border-style: solid;
+ border-color: #ffffff;
+ border-radius: 5px;
+
+ margin-top: 20px;
+ margin-bottom: 20px;
+
+ width: 100%;
+
+ padding: 0.5em;
+}
+
+.increment:hover {
+ background-color: #62d6fb;
+}
diff --git a/demo-app-next/styles/style.css b/demo-app-next/styles/style.css
new file mode 100644
index 000000000..6c7e3c462
--- /dev/null
+++ b/demo-app-next/styles/style.css
@@ -0,0 +1,149 @@
+body {
+ margin: 0;
+ font-family: Arial, Helvetica, sans-serif;
+ background-color: #fff4f4;
+}
+
+h1,
+h4 {
+ text-align: center;
+}
+
+/* Navbar */
+
+.nav {
+ background-color: #ff6569;
+
+ display: flex;
+ justify-content: space-evenly;
+
+ padding: 30px;
+ height: 30px;
+}
+
+.link {
+ flex-grow: 1;
+
+ font-size: 1.5em;
+ text-decoration: none;
+ text-align: center;
+
+ color: #fff4f4;
+}
+
+.link:hover {
+ font-size: 2em;
+}
+
+/* About */
+
+.about {
+ background-color: #ffffff;
+ color: #330002;
+
+ padding-top: 1em;
+ padding-bottom: 2em;
+ padding-right: 4em;
+ padding-left: 4em;
+ margin-top: 2em;
+
+ max-width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+}
+
+/* Tic-Tac-Toe */
+
+.board {
+ background-color: #ffffff;
+ color: #330002;
+
+ margin-top: 2em;
+
+ padding-top: 1em;
+ padding-bottom: 1em;
+ padding-left: 4em;
+ padding-right: 4em;
+
+ width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+}
+
+.box {
+ background-color: #62d6fb;
+ border-style: solid;
+ border-color: #ffffff;
+ border-radius: 5px;
+
+ height: 100px;
+ width: 100px;
+
+ font-size: 4em;
+}
+
+#reset {
+ color: #ff6569;
+ font-size: 1.5em;
+
+ background-color: #ffffff;
+ border-style: solid;
+ border-color: #ff6569;
+ border-radius: 5px;
+
+ margin-top: 20px;
+ margin-bottom: 20px;
+
+ width: 100%;
+
+ padding: 0.5em;
+}
+
+#reset:hover {
+ color: #ffffff;
+ background-color: #ff6569;
+}
+
+/* Counter */
+
+.buttons {
+ background-color: #ffffff;
+ color: #330002;
+
+ padding-top: 1em;
+ padding-bottom: 2em;
+ padding-right: 4em;
+ padding-left: 4em;
+ margin-top: 2em;
+
+ max-width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+}
+
+.increment {
+ color: #ffffff;
+ font-size: 1.5em;
+
+ background-color: #ff6569;
+ border-style: solid;
+ border-color: #ffffff;
+ border-radius: 5px;
+
+ margin-top: 20px;
+ margin-bottom: 20px;
+
+ width: 100%;
+
+ padding: 0.5em;
+}
+
+.increment:hover {
+ background-color: #62d6fb;
+}
diff --git a/demo-app-next/tsconfig.json b/demo-app-next/tsconfig.json
new file mode 100644
index 000000000..1203d7cb2
--- /dev/null
+++ b/demo-app-next/tsconfig.json
@@ -0,0 +1,30 @@
+{
+ "compilerOptions": {
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "incremental": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "target": "ES2017"
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/demo-app-remix/.eslintrc.js b/demo-app-remix/.eslintrc.js
new file mode 100644
index 000000000..f2faf1470
--- /dev/null
+++ b/demo-app-remix/.eslintrc.js
@@ -0,0 +1,4 @@
+/** @type {import('eslint').Linter.Config} */
+module.exports = {
+ extends: ['@remix-run/eslint-config', '@remix-run/eslint-config/node'],
+};
diff --git a/demo-app-remix/.gitignore b/demo-app-remix/.gitignore
new file mode 100644
index 000000000..3f7bf98da
--- /dev/null
+++ b/demo-app-remix/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+
+/.cache
+/build
+/public/build
+.env
diff --git a/demo-app-remix/README.md b/demo-app-remix/README.md
new file mode 100644
index 000000000..a456eb3b6
--- /dev/null
+++ b/demo-app-remix/README.md
@@ -0,0 +1,51 @@
+# Welcome to Remix!
+
+- [Remix Docs](https://remix.run/docs)
+
+## Development
+
+Start the Remix development asset server and the Express server by running:
+
+```sh
+npm run dev
+```
+
+This starts your app in development mode, which will purge the server require cache when Remix rebuilds assets so you don't need a process manager restarting the express server.
+
+## Deployment
+
+First, build your app for production:
+
+```sh
+npm run build
+```
+
+Then run the app in production mode:
+
+```sh
+npm start
+```
+
+Now you'll need to pick a host to deploy it to.
+
+### DIY
+
+If you're familiar with deploying express applications you should be right at home just make sure to deploy the output of `remix build`
+
+- `build/`
+- `public/build/`
+
+### Using a Template
+
+When you ran `npx create-remix@latest` there were a few choices for hosting. You can run that again to create a new project, then copy over your `app/` folder to the new project that's pre-configured for your target server.
+
+```sh
+cd ..
+# create a new project, and pick a pre-configured host
+npx create-remix@latest
+cd my-new-remix-app
+# remove the new project's app (not the old one!)
+rm -rf app
+# copy your app over
+cp -R ../my-old-remix-app/app app
+```
diff --git a/demo-app-remix/app/components/Board.tsx b/demo-app-remix/app/components/Board.tsx
new file mode 100644
index 000000000..9eeb7ed58
--- /dev/null
+++ b/demo-app-remix/app/components/Board.tsx
@@ -0,0 +1,137 @@
+import React, { Component } from 'react';
+import Row from './Row';
+import type { BoardContent, Scoreboard, Player } from '../types/types';
+import type { BoardText } from '../types/types';
+
+type BoardState = {
+ board: BoardContent;
+ currentPlayer: Player;
+ gameOver: boolean;
+ message: string;
+ scoreboard: Scoreboard;
+};
+
+class Board extends Component<{}, BoardState> {
+ constructor(props: any) {
+ super(props);
+ this.state = {
+ board: this.newBoard(),
+ currentPlayer: 'X',
+ gameOver: false,
+ message: '',
+ scoreboard: { X: 0, O: 0 },
+ };
+
+ this.resetBoard = this.resetBoard.bind(this);
+ this.handleBoxClick = this.handleBoxClick.bind(this);
+ }
+
+ componentDidUpdate() {
+ this.checkForWinner();
+ }
+
+ /**
+ * @method newBoard
+ * @description - returns a blank BoardContent array,
+ * for the start of a new game
+ */
+ newBoard(): BoardContent {
+ return [
+ ['-', '-', '-'],
+ ['-', '-', '-'],
+ ['-', '-', '-'],
+ ];
+ }
+
+ /**
+ * @method resetBoard
+ * @description - sets to board object to be all '-',
+ * and sets gameOver and message to default state
+ */
+ resetBoard(): void {
+ this.setState({
+ gameOver: false,
+ board: this.newBoard(),
+ message: '',
+ });
+ }
+
+ /**
+ * @method checkForWinner
+ * @description - checks to see if either player has filled a row
+ * if so, ends the game and updates the message to declare winner
+ */
+ checkForWinner(): void {
+ const { board, gameOver, currentPlayer } = this.state;
+
+ const spacesLeft = (): boolean => {
+ for (let i of board) {
+ if (i.includes('-')) return true;
+ }
+ return false;
+ };
+
+ if (!gameOver) {
+ // win conditions: matching rows, columns, or diagonals, that are not empty('-')
+ if (
+ (board[0][0] === board[0][1] && board[0][1] === board[0][2] && board[0][2] !== '-') ||
+ (board[1][0] === board[1][1] && board[1][1] === board[1][2] && board[1][2] !== '-') ||
+ (board[2][0] === board[2][1] && board[2][1] === board[2][2] && board[2][2] !== '-') ||
+ (board[0][0] === board[1][0] && board[1][0] === board[2][0] && board[2][0] !== '-') ||
+ (board[0][1] === board[1][1] && board[1][1] === board[2][1] && board[2][1] !== '-') ||
+ (board[0][2] === board[1][2] && board[1][2] === board[2][2] && board[2][2] !== '-') ||
+ (board[0][0] === board[1][1] && board[1][1] === board[2][2] && board[2][2] !== '-') ||
+ (board[2][0] === board[1][1] && board[1][1] === board[0][2] && board[0][2] !== '-')
+ ) {
+ // winner is the person who's turn was previous
+ const winner: Player = currentPlayer === 'X' ? 'O' : 'X';
+
+ this.setState({
+ gameOver: true,
+ message: `Player ${winner} wins!`,
+ });
+
+ // draw condition: no '-' remaining in board without above win condition triggering
+ } else if (!spacesLeft()) {
+ this.setState({
+ gameOver: true,
+ message: 'Draw!',
+ });
+ }
+ }
+ }
+
+ handleBoxClick(row: number, column: number): void {
+ const boardCopy: BoardContent = [
+ [...this.state.board[0]],
+ [...this.state.board[1]],
+ [...this.state.board[2]],
+ ];
+ boardCopy[row][column] = this.state.currentPlayer;
+ const newPlayer: Player = this.state.currentPlayer === 'X' ? 'O' : 'X';
+ this.setState({ board: boardCopy, currentPlayer: newPlayer });
+ }
+
+ render() {
+ const rows: Array = [];
+ for (let i = 0; i < 3; i++) {
+ rows.push(
+
,
+ );
+ }
+ const { X, O }: Scoreboard = this.state.scoreboard;
+
+ return (
+
+
Tic Tac Toe
+ {this.state.gameOver && {this.state.message} }
+ {rows}
+
+ Reset
+
+
+ );
+ }
+}
+
+export default Board;
diff --git a/demo-app-remix/app/components/Box.tsx b/demo-app-remix/app/components/Box.tsx
new file mode 100644
index 000000000..038a557f3
--- /dev/null
+++ b/demo-app-remix/app/components/Box.tsx
@@ -0,0 +1,18 @@
+import type { BoardText } from '../types/types';
+
+type BoxProps = {
+ value: BoardText;
+ row: number;
+ column: number;
+ handleBoxClick: (row: number, column: number) => void;
+};
+
+const Box = (props: BoxProps) => {
+ return (
+ props.handleBoxClick(props.row, props.column)}>
+ {props.value}
+
+ );
+};
+
+export default Box;
diff --git a/demo-app-remix/app/components/Buttons.tsx b/demo-app-remix/app/components/Buttons.tsx
new file mode 100644
index 000000000..33f154acb
--- /dev/null
+++ b/demo-app-remix/app/components/Buttons.tsx
@@ -0,0 +1,21 @@
+import Increment from './Increment';
+
+function Buttons() {
+ const buttons = [];
+ for (let i = 0; i < 4; i++) {
+ buttons.push( );
+ }
+
+ return (
+
+
Stateful Buttons
+
+ These buttons are functional components that each manage their own state with the useState
+ hook.
+
+ {buttons}
+
+ );
+}
+
+export default Buttons;
diff --git a/demo-app-remix/app/components/Buttons2.tsx b/demo-app-remix/app/components/Buttons2.tsx
new file mode 100644
index 000000000..de0161a9b
--- /dev/null
+++ b/demo-app-remix/app/components/Buttons2.tsx
@@ -0,0 +1,38 @@
+// import StateButton from "./StateButton";
+// import EffectButton from "./EffectButton";
+// import RefComponent from "./RefComponent";
+// import ContextButton from "./ContextButton";
+// import { useState, useEffect, useRef, createContext } from "react";
+
+// export const ButtonContext: any = createContext(null);
+
+// export default function Buttons() {
+// const [count, setCount] = useState(0);
+// const [effectCount, setEffectCount] = useState(0);
+// const renders = useRef(-1);
+
+// useEffect(() => {
+// renders.current = renders.current + 1;
+// document.title = `You clicked ${effectCount} times`;
+// }, [count, effectCount]);
+
+// return (
+//
+//
React Hook Buttons
+//
+// These buttons are functional components that manage their own state with
+// different react hooks.
+//
+// {/* {buttons} */}
+//
+//
+//
A box renders once useRef count is 3
+//
= 3 ? "show" : "hide"}>
+//
+//
+//
+//
+//
+//
+// );
+// }
diff --git a/demo-app-remix/app/components/ContextButton.tsx b/demo-app-remix/app/components/ContextButton.tsx
new file mode 100644
index 000000000..175a8a44b
--- /dev/null
+++ b/demo-app-remix/app/components/ContextButton.tsx
@@ -0,0 +1,20 @@
+// import { validateHeaderValue } from "http";
+// import { useContext } from "react";
+// import { ButtonContext } from "./Buttons";
+
+// export default function ContextButton() {
+// //const [count, setCount] = useState(0);
+
+// // const [count, setCount] = useContext(ButtonContext);
+// const { value }: any = useContext(ButtonContext);
+// const [count, setCount] = value;
+
+// return (
+//
+//
This button tests the useContext hook
+// setCount(count + 1)}>
+// Click to increment: {count}
+//
+//
+// );
+// }
diff --git a/demo-app-remix/app/components/EffectButton.tsx b/demo-app-remix/app/components/EffectButton.tsx
new file mode 100644
index 000000000..b5c242247
--- /dev/null
+++ b/demo-app-remix/app/components/EffectButton.tsx
@@ -0,0 +1,18 @@
+// import React, { useState, useEffect, useRef } from "react";
+
+// export default function StateButton(props: any) {
+// //const [count, setCount] = useState(0);
+
+// return (
+//
+//
This button tests the UseEffect hook
+// props.setEffectCount(props.effectCount + 1)}
+// >
+// {/* You clicked me {count} times. */}
+// Click to update doc title
+//
+//
+// );
+// }
diff --git a/demo-app-remix/app/components/Increment.tsx b/demo-app-remix/app/components/Increment.tsx
new file mode 100644
index 000000000..6e6486908
--- /dev/null
+++ b/demo-app-remix/app/components/Increment.tsx
@@ -0,0 +1,13 @@
+import React, { useState } from 'react';
+
+function Increment() {
+ const [count, setCount] = useState(0);
+
+ return (
+ setCount(count + 1)}>
+ You clicked me {count} times.
+
+ );
+}
+
+export default Increment;
diff --git a/demo-app-remix/app/components/RefComponent.tsx b/demo-app-remix/app/components/RefComponent.tsx
new file mode 100644
index 000000000..938e82908
--- /dev/null
+++ b/demo-app-remix/app/components/RefComponent.tsx
@@ -0,0 +1,7 @@
+// export default function RefComponent(props: any) {
+// return (
+//
+//
{`Render count: ${props.counter}`}
+//
+// );
+// }
diff --git a/demo-app-remix/app/components/Row.tsx b/demo-app-remix/app/components/Row.tsx
new file mode 100644
index 000000000..011b7a8ae
--- /dev/null
+++ b/demo-app-remix/app/components/Row.tsx
@@ -0,0 +1,27 @@
+import Box from './Box';
+import type { BoardText } from '../types/types';
+
+type RowProps = {
+ handleBoxClick: (row: number, column: number) => void;
+ values: Array;
+ row: number;
+};
+
+const Row = (props: RowProps) => {
+ const boxes: Array = [];
+ for (let i = 0; i < 3; i++) {
+ boxes.push(
+ ,
+ );
+ }
+
+ return {boxes}
;
+};
+
+export default Row;
diff --git a/demo-app-remix/app/components/StateButton.tsx b/demo-app-remix/app/components/StateButton.tsx
new file mode 100644
index 000000000..4b898c87e
--- /dev/null
+++ b/demo-app-remix/app/components/StateButton.tsx
@@ -0,0 +1,17 @@
+// // import React, { useState } from "react";
+
+// export default function StateButton(props: any) {
+// //const [count, setCount] = useState(0);
+
+// return (
+//
+//
This button tests the UseState hook
+// props.setCount(props.count + 1)}
+// >
+// Click to increment: {props.count}
+//
+//
+// );
+// }
diff --git a/demo-app-remix/app/components/navbar.js b/demo-app-remix/app/components/navbar.js
new file mode 100644
index 000000000..885e6f473
--- /dev/null
+++ b/demo-app-remix/app/components/navbar.js
@@ -0,0 +1,17 @@
+import { Link } from '@remix-run/react';
+
+export default function Navbar() {
+ return (
+
+
+ About
+
+
+ Tic-Tac-Toe
+
+
+ Counter
+
+
+ );
+}
diff --git a/demo-app-remix/app/root.tsx b/demo-app-remix/app/root.tsx
new file mode 100644
index 000000000..26ac3df47
--- /dev/null
+++ b/demo-app-remix/app/root.tsx
@@ -0,0 +1,35 @@
+import type { MetaFunction, LinksFunction } from '@remix-run/node';
+import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react';
+import styles from './styles/style.css';
+
+export const meta: MetaFunction = () => ({
+ charset: 'utf-8',
+ title: 'New Remix App',
+ viewport: 'width=device-width,initial-scale=1',
+});
+
+export const links: LinksFunction = () => {
+ return [
+ {
+ rel: 'stylesheet',
+ href: styles,
+ },
+ ];
+};
+
+export default function App() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/demo-app-remix/app/routes/buttons/index.tsx b/demo-app-remix/app/routes/buttons/index.tsx
new file mode 100644
index 000000000..1842d8240
--- /dev/null
+++ b/demo-app-remix/app/routes/buttons/index.tsx
@@ -0,0 +1,11 @@
+import Button from '../../components/Buttons';
+import Navbar from '../../components/navbar';
+
+export default function ButtonsPage() {
+ return (
+
+
+
+
+ );
+}
diff --git a/demo-app-remix/app/routes/index.tsx b/demo-app-remix/app/routes/index.tsx
new file mode 100644
index 000000000..1e26e26d0
--- /dev/null
+++ b/demo-app-remix/app/routes/index.tsx
@@ -0,0 +1,28 @@
+import Navbar from '../components/navbar.js';
+
+export default function Home() {
+ return (
+
+
+
+
Lorem Ipsum
+
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
+ ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
+ sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
+ est laborum."
+
+
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
+ ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
+ sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
+ est laborum."
+
+
+
+ );
+}
diff --git a/demo-app-remix/app/routes/tictactoe/index.tsx b/demo-app-remix/app/routes/tictactoe/index.tsx
new file mode 100644
index 000000000..8cd5fae85
--- /dev/null
+++ b/demo-app-remix/app/routes/tictactoe/index.tsx
@@ -0,0 +1,13 @@
+import Board from '../../components/Board';
+import Box from '../../components/Box';
+import Row from '../../components/Row';
+import Navbar from '../../components/navbar';
+
+export default function Tictactoe() {
+ return (
+
+
+
+
+ );
+}
diff --git a/demo-app-remix/app/styles/style.css b/demo-app-remix/app/styles/style.css
new file mode 100644
index 000000000..b8a4b09d8
--- /dev/null
+++ b/demo-app-remix/app/styles/style.css
@@ -0,0 +1,179 @@
+body {
+ margin: 0;
+ font-family: Arial, Helvetica, sans-serif;
+ background-color: #fff4f4;
+}
+
+h1,
+h4 {
+ text-align: center;
+}
+
+/* Navbar */
+
+.nav {
+ background-color: #ff6569;
+
+ display: flex;
+ justify-content: space-evenly;
+
+ padding: 30px;
+ height: 30px;
+}
+
+.link {
+ flex-grow: 1;
+
+ font-size: 1.5em;
+ text-decoration: none;
+ text-align: center;
+
+ color: #fff4f4;
+}
+
+.link:hover {
+ font-size: 2em;
+}
+
+/* About */
+
+.about {
+ background-color: #ffffff;
+ color: #330002;
+
+ padding-top: 1em;
+ padding-bottom: 2em;
+ padding-right: 4em;
+ padding-left: 4em;
+ margin-top: 2em;
+
+ max-width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+}
+
+/* Tic-Tac-Toe */
+
+.board {
+ background-color: #ffffff;
+ color: #330002;
+
+ margin-top: 2em;
+
+ padding-top: 1em;
+ padding-bottom: 1em;
+ padding-left: 4em;
+ padding-right: 4em;
+
+ width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+}
+
+.box {
+ background-color: #62d6fb;
+ border-style: solid;
+ border-color: #ffffff;
+ border-radius: 5px;
+
+ height: 100px;
+ width: 100px;
+
+ font-size: 4em;
+}
+
+#reset {
+ color: #ff6569;
+ font-size: 1.5em;
+
+ background-color: #ffffff;
+ border-style: solid;
+ border-color: #ff6569;
+ border-radius: 5px;
+
+ margin-top: 20px;
+ margin-bottom: 20px;
+
+ width: 100%;
+
+ padding: 0.5em;
+}
+
+#reset:hover {
+ color: #ffffff;
+ background-color: #ff6569;
+}
+
+/* Counter */
+
+.buttons {
+ background-color: #ffffff;
+ color: #330002;
+
+ padding-top: 1em;
+ padding-bottom: 2em;
+ padding-right: 4em;
+ padding-left: 4em;
+ margin-top: 2em;
+
+ max-width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+}
+
+.buttonstyle {
+ color: #ffffff;
+ font-size: 1.5em;
+
+ background-color: #ff6569;
+ border-style: solid;
+ border-color: #ffffff;
+ border-radius: 5px;
+
+ margin-top: -20px;
+ margin-bottom: 10px;
+
+ width: 100%;
+
+ padding: 0.5em;
+}
+
+.description {
+ margin-bottom: 30px;
+}
+
+.increment {
+ color: #ffffff;
+ font-size: 1.5em;
+
+ background-color: #FF6569;
+ border-style: solid;
+ border-color: #ffffff;
+ border-radius: 5px;
+
+ margin-top: 20px;
+ margin-bottom: 20px;
+
+ width: 100%;
+
+ padding: .5em;
+}
+
+.increment:hover {
+ background-color: #62d6fb;
+}
+
+.hide {
+ display: none;
+}
+
+.show {
+ display: block;
+ background-color: lightblue;
+}
diff --git a/demo-app-remix/app/types/types.ts b/demo-app-remix/app/types/types.ts
new file mode 100644
index 000000000..cc8252e42
--- /dev/null
+++ b/demo-app-remix/app/types/types.ts
@@ -0,0 +1,10 @@
+export type Scoreboard = {
+ X: number;
+ O: number;
+};
+
+export type Player = 'X' | 'O';
+
+export type BoardText = 'X' | 'O' | '-';
+
+export type BoardContent = Array>;
diff --git a/demo-app-remix/package.json b/demo-app-remix/package.json
new file mode 100644
index 000000000..5add33863
--- /dev/null
+++ b/demo-app-remix/package.json
@@ -0,0 +1,39 @@
+{
+ "private": true,
+ "sideEffects": false,
+ "scripts": {
+ "build": "remix build",
+ "dev": "npm-run-all build --parallel \"dev:*\"",
+ "dev:node": "cross-env NODE_ENV=development nodemon --require dotenv/config ./server.ts --watch ./server.ts",
+ "dev:remix": "remix watch",
+ "start": "cross-env NODE_ENV=production node ./server.ts",
+ "typecheck": "tsc"
+ },
+ "dependencies": {
+ "@remix-run/express": "^1.14.1",
+ "@remix-run/node": "^1.14.1",
+ "@remix-run/react": "^1.14.1",
+ "compression": "^1.7.4",
+ "cross-env": "^7.0.3",
+ "express": "^4.18.2",
+ "isbot": "^3.6.5",
+ "morgan": "^1.10.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "ts-node": "^10.9.2"
+ },
+ "devDependencies": {
+ "@remix-run/dev": "^1.14.1",
+ "@remix-run/eslint-config": "^1.14.1",
+ "@types/react": "^18.0.25",
+ "@types/react-dom": "^18.0.8",
+ "dotenv": "^16.0.3",
+ "eslint": "^8.27.0",
+ "nodemon": "^2.0.20",
+ "npm-run-all": "^4.1.5",
+ "typescript": "^4.8.4"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+}
diff --git a/demo-app-remix/public/favicon.ico b/demo-app-remix/public/favicon.ico
new file mode 100644
index 000000000..8830cf682
Binary files /dev/null and b/demo-app-remix/public/favicon.ico differ
diff --git a/demo-app-remix/remix.config.js b/demo-app-remix/remix.config.js
new file mode 100644
index 000000000..ddeb138df
--- /dev/null
+++ b/demo-app-remix/remix.config.js
@@ -0,0 +1,8 @@
+/** @type {import('@remix-run/dev').AppConfig} */
+module.exports = {
+ ignoredRouteFiles: ['**/.*'],
+ // appDirectory: "app",
+ // assetsBuildDirectory: 'public/build',
+ // serverBuildPath: "build/index.js",
+ // publicPath: "/build/",
+};
diff --git a/demo-app-remix/remix.env.d.ts b/demo-app-remix/remix.env.d.ts
new file mode 100644
index 000000000..dcf8c45e1
--- /dev/null
+++ b/demo-app-remix/remix.env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/demo-app-remix/server.ts b/demo-app-remix/server.ts
new file mode 100644
index 000000000..1e24b658d
--- /dev/null
+++ b/demo-app-remix/server.ts
@@ -0,0 +1,59 @@
+export {}; //JR: added to fix this error message: 'server.ts' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module.
+const path = require('path');
+const express = require('express');
+const compression = require('compression');
+const morgan = require('morgan');
+const { createRequestHandler } = require('@remix-run/express');
+
+const BUILD_DIR = path.join(process.cwd(), 'build');
+
+const app = express();
+
+app.use(compression());
+
+// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
+app.disable('x-powered-by');
+
+// Remix fingerprints its assets so we can cache forever.
+app.use('/build', express.static('public/build', { immutable: true, maxAge: '1y' }));
+
+// Everything else (like favicon.ico) is cached for an hour. You may want to be
+// more aggressive with this caching.
+app.use(express.static('public', { maxAge: '1h' }));
+
+app.use(morgan('tiny'));
+
+app.all(
+ '*',
+ process.env.NODE_ENV === 'development'
+ ? (req: any, res: any, next: any) => {
+ purgeRequireCache();
+
+ return createRequestHandler({
+ build: require(BUILD_DIR),
+ mode: process.env.NODE_ENV,
+ })(req, res, next);
+ }
+ : createRequestHandler({
+ build: require(BUILD_DIR),
+ mode: process.env.NODE_ENV,
+ }),
+);
+const port: number | string = process.env.PORT || 3003;
+
+app.listen(port, () => {
+ console.log(`Express server listening on port ${port}`);
+});
+
+function purgeRequireCache() {
+ // purge require cache on requests for "server side HMR" this won't let
+ // you have in-memory objects between requests in development,
+ // alternatively you can set up nodemon/pm2-dev to restart the server on
+ // file changes, but then you'll have to reconnect to databases/etc on each
+ // change. We prefer the DX of this, so we've included it for you by default
+ for (const key in require.cache) {
+ if (key.startsWith(BUILD_DIR)) {
+ delete require.cache[key];
+ }
+ }
+}
diff --git a/demo-app-remix/tsconfig.json b/demo-app-remix/tsconfig.json
new file mode 100644
index 000000000..0de0b2ee4
--- /dev/null
+++ b/demo-app-remix/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx", "server.ts"],
+ "compilerOptions": {
+ "lib": ["DOM", "DOM.Iterable", "ES2019"],
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "target": "ES2019",
+ "strict": true,
+ "allowJs": true,
+ "forceConsistentCasingInFileNames": true,
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./app/*"]
+ },
+
+ // Remix takes care of building everything in `remix build`.
+ "noEmit": true
+ }
+}
diff --git a/demo-app/.babelrc b/demo-app/.babelrc
new file mode 100644
index 000000000..a45d619c1
--- /dev/null
+++ b/demo-app/.babelrc
@@ -0,0 +1,7 @@
+
+{
+ "presets": [
+ "@babel/preset-env",
+ "@babel/preset-react"
+ ]
+}
\ No newline at end of file
diff --git a/demo-app/.gitignore b/demo-app/.gitignore
new file mode 100644
index 000000000..40b878db5
--- /dev/null
+++ b/demo-app/.gitignore
@@ -0,0 +1 @@
+node_modules/
\ No newline at end of file
diff --git a/demo-app/package.json b/demo-app/package.json
new file mode 100644
index 000000000..4003e82fa
--- /dev/null
+++ b/demo-app/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "typescript-module",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "dev": "webpack-dev-server",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.16.7",
+ "@babel/plugin-transform-runtime": "^7.25.9",
+ "@babel/preset-env": "^7.16.7",
+ "@babel/preset-react": "^7.16.7",
+ "@types/express": "^4.17.13",
+ "@types/node": "^17.0.8",
+ "@types/react": "^17.0.38",
+ "@types/react-dom": "^17.0.19",
+ "babel-loader": "^8.2.3",
+ "copy-webpack-plugin": "^10.2.0",
+ "core-js": "^3.39.0",
+ "css-loader": "^6.5.1",
+ "html-webpack-plugin": "^5.5.0",
+ "node": "^16.0.0",
+ "nodemon": "^2.0.15",
+ "ts-loader": "^9.2.6",
+ "typescript": "^4.5.4",
+ "webpack": "^5.65.0",
+ "webpack-cli": "^4.9.1",
+ "webpack-dev-server": "^4.7.2"
+ },
+ "dependencies": {
+ "@mui/styled-engine-sc": "^5.12.0",
+ "express": "^4.17.2",
+ "react": "^18.1.0",
+ "react-dom": "^18.1.0",
+ "react-router-dom": "^6.3.0",
+ "ts-node": "^10.4.0",
+ "use-immer": "^0.9.0"
+ }
+}
diff --git a/demo-app/src/client/Components/Board.tsx b/demo-app/src/client/Components/Board.tsx
new file mode 100644
index 000000000..102f2a2bb
--- /dev/null
+++ b/demo-app/src/client/Components/Board.tsx
@@ -0,0 +1,141 @@
+import React, { Component } from 'react';
+import Row from './Row';
+import { BoardContent, Scoreboard, Player } from './../../types';
+//Took away BoardText from import
+
+//thinking about changing this to an interface
+type BoardState = {
+ board: BoardContent;
+ currentPlayer: Player;
+ gameOver: boolean;
+ message: string;
+ scoreboard: Scoreboard;
+};
+
+//changed props to unknown instead of any
+class Board extends Component<{}, BoardState> {
+ constructor(props: unknown) {
+ super(props);
+ this.state = {
+ board: this.newBoard(),
+ currentPlayer: 'X',
+ gameOver: false,
+ message: '',
+ scoreboard: { X: 0, O: 0 },
+ };
+
+ this.resetBoard = this.resetBoard.bind(this);
+ this.handleBoxClick = this.handleBoxClick.bind(this);
+ }
+
+ //added void
+ componentDidUpdate(): void {
+ this.checkForWinner()
+ }
+
+ /**
+ * @method newBoard
+ * @description - returns a blank BoardContent array,
+ * for the start of a new game
+ */
+ newBoard(): BoardContent {
+ return [
+ ['-', '-', '-'],
+ ['-', '-', '-'],
+ ['-', '-', '-'],
+ ];
+ }
+
+ /**
+ * @method resetBoard
+ * @description - sets to board object to be all '-',
+ * and sets gameOver and message to default state
+ */
+ resetBoard(): void {
+ this.setState({
+ gameOver: false,
+ board: this.newBoard(),
+ message: '',
+ });
+ }
+
+ /**
+ * @method checkForWinner
+ * @description - checks to see if either player has filled a row
+ * if so, ends the game and updates the message to declare winner
+ */
+ checkForWinner(): void {
+ const { board, gameOver, currentPlayer } = this.state;
+
+ const spacesLeft = (): boolean => {
+ for (let i of board) {
+ if (i.includes('-')) return true;
+ }
+ return false;
+ };
+
+ if (!gameOver) {
+ // win conditions: matching rows, columns, or diagonals, that are not empty('-')
+ if (
+ (board[0][0] === board[0][1] && board[0][1] === board[0][2] && board[0][2] !== '-') ||
+ (board[1][0] === board[1][1] && board[1][1] === board[1][2] && board[1][2] !== '-') ||
+ (board[2][0] === board[2][1] && board[2][1] === board[2][2] && board[2][2] !== '-') ||
+ (board[0][0] === board[1][0] && board[1][0] === board[2][0] && board[2][0] !== '-') ||
+ (board[0][1] === board[1][1] && board[1][1] === board[2][1] && board[2][1] !== '-') ||
+ (board[0][2] === board[1][2] && board[1][2] === board[2][2] && board[2][2] !== '-') ||
+ (board[0][0] === board[1][1] && board[1][1] === board[2][2] && board[2][2] !== '-') ||
+ (board[2][0] === board[1][1] && board[1][1] === board[0][2] && board[0][2] !== '-')
+ ) {
+ // winner is the person who's turn was previous
+ const winner: Player = currentPlayer === 'X' ? 'O' : 'X';
+
+ this.setState({
+ gameOver: true,
+ message: `Player ${winner} wins!`,
+ });
+
+ // draw condition: no '-' remaining in board without above win condition triggering
+ } else if (!spacesLeft()) {
+ this.setState({
+ gameOver: true,
+ message: 'Draw!',
+ });
+ }
+ }
+ }
+
+ handleBoxClick(row: number, column: number): void {
+ const boardCopy: BoardContent = [
+ [...this.state.board[0]],
+ [...this.state.board[1]],
+ [...this.state.board[2]],
+ ];
+ boardCopy[row][column] = this.state.currentPlayer;
+ const newPlayer: Player = this.state.currentPlayer === 'X' ? 'O' : 'X';
+ this.setState({ board: boardCopy, currentPlayer: newPlayer });
+ }
+
+ //added type for render
+ render(): JSX.Element {
+ const rows: Array = [];
+ for (let i = 0; i < 3; i++) {
+ rows.push(
+
,
+ );
+ }
+ // const { X, O }: Scoreboard = this.state.scoreboard;
+
+ return (
+
+
Tic Tac Toe
+ {this.state.gameOver && {this.state.message} }
+ {rows}
+
+ Reset
+
+
+ );
+ }
+}
+
+export default Board;
diff --git a/demo-app/src/client/Components/Box.tsx b/demo-app/src/client/Components/Box.tsx
new file mode 100644
index 000000000..d9c45512e
--- /dev/null
+++ b/demo-app/src/client/Components/Box.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import { BoardText } from '../../types';
+
+type BoxProps = {
+ value: BoardText;
+ row: number;
+ column: number;
+ handleBoxClick: (row: number, column: number) => void;
+};
+
+const Box = (props: BoxProps): JSX.Element => {
+ return (
+ <>
+ props.handleBoxClick(props.row, props.column)}>
+ {props.value}
+
+ >
+ );
+};
+
+export default Box;
diff --git a/demo-app/src/client/Components/Buttons.tsx b/demo-app/src/client/Components/Buttons.tsx
new file mode 100644
index 000000000..60235aa92
--- /dev/null
+++ b/demo-app/src/client/Components/Buttons.tsx
@@ -0,0 +1,82 @@
+import React, { Component, useState } from 'react';
+
+type ButtonProps = {
+ id: string;
+ label: string;
+ color?: string;
+ initialCount?: number;
+};
+
+type IncrementClassState = {
+ count: number;
+};
+
+class IncrementClass extends Component {
+ state = {
+ count: this.props.initialCount || 0,
+ };
+
+ handleClick = (): void => {
+ this.setState((prevState: IncrementClassState) => ({
+ count: prevState.count + 1,
+ }));
+ };
+
+ render(): JSX.Element {
+ return (
+
+
+ {this.props.label} {this.state.count} times.
+
+
+ );
+ }
+}
+
+const IncrementFunction = (props: ButtonProps): JSX.Element => {
+ const [count, setCount] = useState(props.initialCount || 0);
+
+ const handleClick = (): void => {
+ setCount((prev) => prev + 1);
+ };
+
+ return (
+
+
+ {props.label} {count} times.
+
+
+ );
+};
+
+class Buttons extends Component {
+ render(): JSX.Element {
+ return (
+
+
Mixed State Counter
+ First two buttons use class components, last two use function components.
+
+
+
+
+
+ );
+ }
+}
+
+export default Buttons;
diff --git a/demo-app/src/client/Components/FunctionalReducerCounter.tsx b/demo-app/src/client/Components/FunctionalReducerCounter.tsx
new file mode 100644
index 000000000..85effd454
--- /dev/null
+++ b/demo-app/src/client/Components/FunctionalReducerCounter.tsx
@@ -0,0 +1,231 @@
+import React, { useState, useReducer } from 'react';
+
+type CounterProps = {
+ initialCount?: number;
+ step?: number;
+ title?: string;
+ theme?: {
+ backgroundColor?: string;
+ textColor?: string;
+ };
+};
+
+type CounterState = {
+ count: number;
+ history: number[];
+ lastAction: string;
+};
+
+type CounterAction =
+ | { type: 'INCREMENT' }
+ | { type: 'DECREMENT' }
+ | { type: 'DOUBLE' }
+ | { type: 'RESET' }
+ | { type: 'ADD'; payload: number }
+ | { type: 'SET_STATE'; payload: CounterState };
+
+type SecondaryCounterState = {
+ count: number;
+ multiplier: number;
+ lastOperation: string;
+ history: number[];
+};
+
+type SecondaryCounterAction =
+ | { type: 'MULTIPLY' }
+ | { type: 'DIVIDE' }
+ | { type: 'SET_MULTIPLIER'; payload: number }
+ | { type: 'RESET' }
+ | { type: 'SET_STATE'; payload: SecondaryCounterState };
+
+function counterReducer(state: CounterState, action: CounterAction, step: number): CounterState {
+ switch (action.type) {
+ case 'INCREMENT':
+ return {
+ ...state,
+ count: state.count + step,
+ history: [...state.history, state.count + step],
+ lastAction: 'INCREMENT',
+ };
+ case 'DECREMENT':
+ return {
+ ...state,
+ count: state.count - step,
+ history: [...state.history, state.count - step],
+ lastAction: 'DECREMENT',
+ };
+ case 'DOUBLE':
+ return {
+ ...state,
+ count: state.count * 2,
+ history: [...state.history, state.count * 2],
+ lastAction: 'DOUBLE',
+ };
+ case 'RESET':
+ return {
+ count: 0,
+ history: [],
+ lastAction: 'RESET',
+ };
+ case 'ADD':
+ return {
+ ...state,
+ count: state.count + action.payload,
+ history: [...state.history, state.count + action.payload],
+ lastAction: `ADD ${action.payload}`,
+ };
+ case 'SET_STATE':
+ return {
+ ...action.payload,
+ lastAction: 'SET_STATE',
+ };
+ default:
+ return state;
+ }
+}
+
+function secondaryCounterReducer(
+ state: SecondaryCounterState,
+ action: SecondaryCounterAction,
+): SecondaryCounterState {
+ switch (action.type) {
+ case 'MULTIPLY':
+ return {
+ ...state,
+ count: state.count * state.multiplier,
+ history: [...state.history, state.count * state.multiplier],
+ lastOperation: `Multiplied by ${state.multiplier}`,
+ };
+ case 'DIVIDE':
+ return {
+ ...state,
+ count: state.count / state.multiplier,
+ history: [...state.history, state.count / state.multiplier],
+ lastOperation: `Divided by ${state.multiplier}`,
+ };
+ case 'SET_MULTIPLIER':
+ return {
+ ...state,
+ multiplier: action.payload,
+ history: [...state.history],
+ lastOperation: `Set multiplier to ${action.payload}`,
+ };
+ case 'RESET':
+ return {
+ count: 0,
+ multiplier: 2,
+ history: [],
+ lastOperation: 'Reset',
+ };
+ case 'SET_STATE':
+ return {
+ ...action.payload,
+ lastOperation: 'SET_STATE',
+ };
+ default:
+ return state;
+ }
+}
+
+function FunctionalReducerCounter({
+ initialCount = 0,
+ step = 1,
+ title = 'Function-based Reducer Counter',
+ theme = {
+ backgroundColor: '#ffffff',
+ textColor: '#330002',
+ },
+}: CounterProps): JSX.Element {
+ const [clickCount, setClickCount] = useState(0);
+ const [lastClickTime, setLastClickTime] = useState(null);
+ const [averageTimeBetweenClicks, setAverageTimeBetweenClicks] = useState(0);
+
+ const [state, dispatch] = useReducer(
+ (state: CounterState, action: CounterAction) => counterReducer(state, action, step),
+ {
+ count: initialCount,
+ history: [],
+ lastAction: 'none',
+ },
+ );
+
+ const [secondaryState, secondaryDispatch] = useReducer(secondaryCounterReducer, {
+ count: initialCount,
+ multiplier: 2,
+ history: [],
+ lastOperation: 'none',
+ });
+
+ return (
+
+
{title}
+
+
+
Primary Counter: {state.count}
+
+
+
+ dispatch({ type: 'INCREMENT' })}>Increment (+{step})
+ dispatch({ type: 'DECREMENT' })}>Decrement (-{step})
+ dispatch({ type: 'DOUBLE' })}>Double Value
+ dispatch({ type: 'ADD', payload: 5 })}>Add 5
+ dispatch({ type: 'RESET' })}>Reset
+
+
+
+
History:
+
+ {state.history.map((value, index) => (
+
+ {value}
+ {index < state.history.length - 1 ? ' → ' : ''}
+
+ ))}
+
+
+
+
+
Secondary Counter: {secondaryState.count}
+
+ secondaryDispatch({ type: 'MULTIPLY' })}>
+ Multiply by {secondaryState.multiplier}
+
+ secondaryDispatch({ type: 'DIVIDE' })}>
+ Divide by {secondaryState.multiplier}
+
+
+ secondaryDispatch({ type: 'SET_MULTIPLIER', payload: secondaryState.multiplier + 1 })
+ }
+ >
+ Increase Multiplier
+
+ secondaryDispatch({ type: 'RESET' })}>Reset
+
+
+
Current Multiplier: {secondaryState.multiplier}
+
History:
+
+ {secondaryState.history.map((value, index) => (
+
+ {value}
+ {index < secondaryState.history.length - 1 ? ' → ' : ''}
+
+ ))}
+
+
+
+
+ );
+}
+
+export default FunctionalReducerCounter;
diff --git a/demo-app/src/client/Components/FunctionalStateCounter.tsx b/demo-app/src/client/Components/FunctionalStateCounter.tsx
new file mode 100644
index 000000000..a49916177
--- /dev/null
+++ b/demo-app/src/client/Components/FunctionalStateCounter.tsx
@@ -0,0 +1,97 @@
+import React, { useState } from 'react';
+
+type CounterProps = {
+ initialCount?: number;
+ step?: number;
+ title?: string;
+ theme?: {
+ backgroundColor?: string;
+ textColor?: string;
+ };
+};
+
+function FunctionalStateCounter({
+ initialCount = 0,
+ step = 1,
+ title = 'Function-based State Counter',
+ theme = {
+ backgroundColor: '#ffffff',
+ textColor: '#330002',
+ },
+}: CounterProps): JSX.Element {
+ const [count, setCount] = useState(initialCount);
+ const [history, setHistory] = useState([]);
+ const [lastAction, setLastAction] = useState('none');
+
+ const handleAction = (type: string, payload?: number) => {
+ let newCount = count;
+ switch (type) {
+ case 'INCREMENT':
+ newCount = count + step;
+ setCount(newCount);
+ setHistory([...history, newCount]);
+ setLastAction('INCREMENT');
+ break;
+ case 'DECREMENT':
+ newCount = count - step;
+ setCount(newCount);
+ setHistory([...history, newCount]);
+ setLastAction('DECREMENT');
+ break;
+ case 'DOUBLE':
+ newCount = count * 2;
+ setCount(newCount);
+ setHistory([...history, newCount]);
+ setLastAction('DOUBLE');
+ break;
+ case 'ADD':
+ newCount = count + (payload || 0);
+ setCount(newCount);
+ setHistory([...history, newCount]);
+ setLastAction(`ADD ${payload}`);
+ break;
+ case 'RESET':
+ setCount(0);
+ setHistory([]);
+ setLastAction('RESET');
+ break;
+ }
+ };
+
+ return (
+
+
{title}
+
+
Current Count: {count}
+
+
+
+ handleAction('INCREMENT')}>Increment (+{step})
+ handleAction('DECREMENT')}>Decrement (-{step})
+ handleAction('DOUBLE')}>Double Value
+ handleAction('ADD', 5)}>Add 5
+ handleAction('RESET')}>Reset
+
+
+
+
History:
+
+ {history.map((value, index) => (
+
+ {value}
+ {index < history.length - 1 ? ' → ' : ''}
+
+ ))}
+
+
+
+ );
+}
+
+export default FunctionalStateCounter;
diff --git a/demo-app/src/client/Components/Home.tsx b/demo-app/src/client/Components/Home.tsx
new file mode 100644
index 000000000..9952f9e29
--- /dev/null
+++ b/demo-app/src/client/Components/Home.tsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import { useTheme } from '../../contexts/ThemeContext';
+import { useAuth } from '../../contexts/AuthContext';
+
+function Home(): JSX.Element {
+ const { theme } = useTheme();
+ const { user, login, logout } = useAuth();
+
+ return (
+
+
REACTIME - DEMO APP
+
+ {user ? (
+
+
Welcome, {user.username}!
+
+ Logout
+
+
+ ) : (
+
+
Please log in:
+
login('testUser')}
+ style={{
+ backgroundColor: theme.primaryColor,
+ color: theme.backgroundColor,
+ }}
+ >
+ Login as Test User
+
+
login('admin')}
+ style={{
+ backgroundColor: theme.secondaryColor,
+ color: theme.backgroundColor,
+ marginLeft: '8px',
+ }}
+ >
+ Login as Admin
+
+
+ )}
+
+ );
+}
+export default Home;
diff --git a/demo-app/src/client/Components/Nav.tsx b/demo-app/src/client/Components/Nav.tsx
new file mode 100644
index 000000000..274c5ae39
--- /dev/null
+++ b/demo-app/src/client/Components/Nav.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import ThemeToggle from './ThemeToggle';
+
+function Nav(): JSX.Element {
+ return (
+
+
+ About
+
+
+ Tic-Tac-Toe
+
+
+ State Counter
+
+
+ Reducer Counter
+
+
+
+ );
+}
+
+export default Nav;
diff --git a/demo-app/src/client/Components/ReducerCounter.tsx b/demo-app/src/client/Components/ReducerCounter.tsx
new file mode 100644
index 000000000..65b5d6155
--- /dev/null
+++ b/demo-app/src/client/Components/ReducerCounter.tsx
@@ -0,0 +1,141 @@
+import React, { Component } from 'react';
+
+type CounterProps = {
+ initialCount?: number;
+ step?: number;
+ title?: string;
+ theme?: {
+ backgroundColor?: string;
+ textColor?: string;
+ };
+};
+
+type CounterState = {
+ count: number;
+ history: number[];
+ lastAction: string;
+};
+
+type CounterAction =
+ | { type: 'INCREMENT' }
+ | { type: 'DECREMENT' }
+ | { type: 'DOUBLE' }
+ | { type: 'RESET' }
+ | { type: 'ADD'; payload: number };
+
+class ReducerCounter extends Component {
+ static defaultProps = {
+ initialCount: 0,
+ step: 1,
+ title: 'Class-based Reducer Counter',
+ theme: {
+ backgroundColor: '#ffffff',
+ textColor: '#330002',
+ },
+ };
+
+ static initialState(initialCount: number): CounterState {
+ return {
+ count: initialCount,
+ history: [],
+ lastAction: 'none',
+ };
+ }
+
+ static reducer(state: CounterState, action: CounterAction, step: number): CounterState {
+ switch (action.type) {
+ case 'INCREMENT':
+ return {
+ ...state,
+ count: state.count + step,
+ history: [...state.history, state.count + step],
+ lastAction: 'INCREMENT',
+ };
+ case 'DECREMENT':
+ return {
+ ...state,
+ count: state.count - step,
+ history: [...state.history, state.count - step],
+ lastAction: 'DECREMENT',
+ };
+ case 'DOUBLE':
+ return {
+ ...state,
+ count: state.count * 2,
+ history: [...state.history, state.count * 2],
+ lastAction: 'DOUBLE',
+ };
+ case 'RESET':
+ return {
+ ...ReducerCounter.initialState(0),
+ lastAction: 'RESET',
+ };
+ case 'ADD':
+ return {
+ ...state,
+ count: state.count + action.payload,
+ history: [...state.history, state.count + action.payload],
+ lastAction: `ADD ${action.payload}`,
+ };
+ default:
+ return state;
+ }
+ }
+
+ constructor(props: CounterProps) {
+ super(props);
+ this.state = ReducerCounter.initialState(props.initialCount || 0);
+ this.dispatch = this.dispatch.bind(this);
+ }
+
+ dispatch(action: CounterAction): void {
+ this.setState((currentState) =>
+ ReducerCounter.reducer(currentState, action, this.props.step || 1),
+ );
+ }
+
+ render(): JSX.Element {
+ const { title, theme } = this.props;
+
+ return (
+
+
{title}
+
+
Current Count: {this.state.count}
+
+
+
+ this.dispatch({ type: 'INCREMENT' })}>
+ Increment (+{this.props.step})
+
+ this.dispatch({ type: 'DECREMENT' })}>
+ Decrement (-{this.props.step})
+
+ this.dispatch({ type: 'DOUBLE' })}>Double Value
+ this.dispatch({ type: 'ADD', payload: 5 })}>Add 5
+ this.dispatch({ type: 'RESET' })}>Reset
+
+
+
+
History:
+
+ {this.state.history.map((value, index) => (
+
+ {value}
+ {index < this.state.history.length - 1 ? ' → ' : ''}
+
+ ))}
+
+
+
+ );
+ }
+}
+
+export default ReducerCounter;
diff --git a/demo-app/src/client/Components/Row.tsx b/demo-app/src/client/Components/Row.tsx
new file mode 100644
index 000000000..1fcc267b2
--- /dev/null
+++ b/demo-app/src/client/Components/Row.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import Box from './Box';
+import { BoardText } from '../../types';
+
+type RowProps = {
+ handleBoxClick: (row: number, column: number) => void;
+ values: Array;
+ row: number;
+};
+
+const Row = (props: RowProps): JSX.Element => {
+ const boxes: Array = [];
+ for (let i = 0; i < 3; i++) {
+ boxes.push(
+ ,
+ );
+ }
+
+ return (
+ <>
+ {boxes}
+ >
+ );
+};
+
+export default Row;
diff --git a/demo-app/src/client/Components/ThemeToggle.tsx b/demo-app/src/client/Components/ThemeToggle.tsx
new file mode 100644
index 000000000..81f203747
--- /dev/null
+++ b/demo-app/src/client/Components/ThemeToggle.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { useTheme } from '../../contexts/ThemeContext';
+
+const ThemeToggle = (): JSX.Element => {
+ const { theme, toggleTheme } = useTheme();
+ const isDark = theme.backgroundColor === '#1a202c';
+
+ return (
+
+ {isDark ? '☀️ Light Mode' : '🌙 Dark Mode'}
+
+ );
+};
+
+export default ThemeToggle;
diff --git a/demo-app/src/client/Router.tsx b/demo-app/src/client/Router.tsx
new file mode 100644
index 000000000..619dfce8b
--- /dev/null
+++ b/demo-app/src/client/Router.tsx
@@ -0,0 +1,59 @@
+// src/client/Router.tsx
+import * as React from 'react';
+import { createRoot } from 'react-dom/client';
+import { BrowserRouter, Routes, Route } from 'react-router-dom';
+import { ThemeProvider } from '../contexts/ThemeContext';
+import { AuthProvider } from '../contexts/AuthContext';
+import Nav from './Components/Nav';
+import Board from './Components/Board';
+import Home from './Components/Home';
+import Buttons from './Components/Buttons';
+import ReducerCounter from './Components/ReducerCounter';
+import FunctionalReducerCounter from './Components/FunctionalReducerCounter';
+import FunctionalStateCounter from './Components/FunctionalStateCounter';
+
+const domNode = document.getElementById('root');
+if (!domNode) throw new Error('Root element not found');
+const root = createRoot(domNode);
+
+const CounterPage = () => (
+
+
+
+
+
+);
+
+root.render(
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+
+
+
+ ,
+);
diff --git a/demo-app/src/client/index.html b/demo-app/src/client/index.html
new file mode 100644
index 000000000..180bd93fe
--- /dev/null
+++ b/demo-app/src/client/index.html
@@ -0,0 +1,11 @@
+
+
+
+ Demo App
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo-app/src/client/style.css b/demo-app/src/client/style.css
new file mode 100644
index 000000000..657b149d9
--- /dev/null
+++ b/demo-app/src/client/style.css
@@ -0,0 +1,653 @@
+@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap');
+
+:root {
+ --primary-red-color: #f00008;
+ --secondary-blue-color: #62d6fb;
+ --fire-rose-red: #ff6569;
+ --secondary-color: #6288fb;
+ --text-color: #330002;
+}
+
+body {
+ margin: 0;
+ font-family: 'Lato', sans-serif;
+ transition: all 0.3s ease;
+}
+
+/* Navbar */
+.nav {
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(10px);
+ box-shadow:
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 1rem 2rem;
+ position: sticky;
+ top: 0;
+ z-index: 1000;
+ height: auto;
+ gap: 2rem;
+}
+
+.link {
+ position: relative;
+ color: #4a5568;
+ text-decoration: none;
+ font-size: 1rem;
+ font-weight: 500;
+ padding: 0.5rem 1rem;
+ border-radius: 8px;
+ transition: all 0.2s ease;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.link::after {
+ content: '';
+ position: absolute;
+ bottom: -2px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 0;
+ height: 2px;
+ background: linear-gradient(90deg, #667eea, #764ba2);
+ transition: width 0.3s ease;
+}
+
+.link:hover {
+ color: #1a202c;
+ font-size: 1rem;
+ background: rgba(237, 242, 247, 0.5);
+}
+
+.link:hover::after {
+ width: calc(100% - 2rem);
+}
+
+/* Active link state */
+.link.active {
+ color: #1a202c;
+ background: rgba(237, 242, 247, 0.8);
+}
+
+.link.active::after {
+ width: calc(100% - 2rem);
+}
+
+@media (max-width: 768px) {
+ .nav {
+ flex-direction: column;
+ padding: 1rem;
+ gap: 1rem;
+ }
+
+ .link {
+ width: 100%;
+ text-align: center;
+ }
+
+ .link::after {
+ bottom: 0;
+ }
+}
+
+/* Theme Toggle Button Styles */
+.nav button {
+ position: absolute;
+ top: 50%;
+ right: 2rem;
+ transform: translateY(-50%);
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ border: none;
+ border-radius: 8px;
+ padding: 0.5rem 1rem;
+ font-size: 0.875rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.nav button:hover {
+ transform: translateY(-50%) scale(1.05);
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.25);
+}
+
+@media (max-width: 768px) {
+ .nav button {
+ position: static;
+ transform: none;
+ width: 100%;
+ margin-bottom: 1rem;
+ }
+}
+
+.theme-toggle {
+ position: absolute;
+ top: 50%;
+ right: 2rem;
+ transform: translateY(-50%);
+ padding: 0.75rem 1.25rem;
+ font-size: 0.875rem;
+ font-weight: 600;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.theme-toggle:hover {
+ transform: translateY(-50%) scale(1.05);
+ box-shadow:
+ 0 4px 6px rgba(0, 0, 0, 0.1),
+ 0 2px 4px rgba(0, 0, 0, 0.06);
+}
+
+.theme-toggle:active {
+ transform: translateY(-50%) scale(0.95);
+}
+
+@media (max-width: 768px) {
+ .theme-toggle {
+ position: static;
+ transform: none;
+ width: 100%;
+ margin-bottom: 1rem;
+ justify-content: center;
+ }
+
+ .theme-toggle:hover {
+ transform: scale(1.02);
+ }
+
+ .theme-toggle:active {
+ transform: scale(0.98);
+ }
+}
+
+/* About */
+.about {
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
+ border-radius: 24px;
+ padding: 3rem;
+ margin: 3rem auto;
+ max-width: 600px;
+ box-shadow:
+ 0 20px 25px -5px rgba(0, 0, 0, 0.1),
+ 0 10px 10px -5px rgba(0, 0, 0, 0.04),
+ 0 0 100px rgba(0, 0, 0, 0.05);
+ transition:
+ transform 0.3s ease,
+ box-shadow 0.3s ease;
+ position: relative;
+ overflow: hidden;
+}
+
+.about::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 4px;
+ background: linear-gradient(90deg, #667eea, #764ba2);
+ border-radius: 4px 4px 0 0;
+}
+
+.about:hover {
+ transform: translateY(-5px);
+ box-shadow:
+ 0 25px 30px -5px rgba(0, 0, 0, 0.1),
+ 0 15px 15px -5px rgba(0, 0, 0, 0.04),
+ 0 0 120px rgba(0, 0, 0, 0.05);
+}
+
+.about h2 {
+ color: #1a202c;
+ font-size: 2.5rem;
+ font-weight: 800;
+ margin-bottom: 2rem;
+ text-align: center;
+ background: linear-gradient(120deg, #2d3748 0%, #4a5568 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ letter-spacing: -0.02em;
+}
+
+.about p {
+ color: #4a5568;
+ font-size: 1.1rem;
+ line-height: 1.7;
+ margin: 1.5rem 0;
+}
+
+/* Login Section Styles */
+.about button {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ border: none;
+ border-radius: 12px;
+ font-size: 1rem;
+ font-weight: 600;
+ padding: 0.75rem 1.5rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ margin: 0.5rem 0;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.about button:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.25);
+}
+
+.about button:active {
+ transform: translateY(0);
+}
+
+.about button:nth-of-type(2) {
+ background: linear-gradient(135deg, #4299e1 0%, #3182ce 100%);
+ margin-left: 1rem;
+}
+
+.about button:nth-of-type(2):hover {
+ box-shadow: 0 4px 12px rgba(66, 153, 225, 0.25);
+}
+
+.about div p:first-of-type {
+ font-size: 1.25rem;
+ color: #2d3748;
+ font-weight: 600;
+ margin-bottom: 1.5rem;
+}
+
+@media (max-width: 768px) {
+ .about {
+ margin: 2rem 1rem;
+ padding: 2rem;
+ }
+
+ .about h2 {
+ font-size: 2rem;
+ }
+
+ .about button {
+ width: 100%;
+ margin: 0.5rem 0;
+ }
+
+ .about button:nth-of-type(2) {
+ margin-left: 0;
+ }
+}
+
+/* Animation for content loading */
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.about > * {
+ animation: fadeIn 0.5s ease-out forwards;
+}
+
+.about > *:nth-child(2) {
+ animation-delay: 0.1s;
+}
+
+.about > *:nth-child(3) {
+ animation-delay: 0.2s;
+}
+/* Tic-Tac-Toe */
+.board {
+ background: linear-gradient(135deg, #ffffff 0%, #f5f7fa 100%);
+ border-radius: 20px;
+ padding: 2rem;
+ width: 400px;
+ margin: 2em auto;
+ box-shadow:
+ 0 10px 20px rgba(0, 0, 0, 0.1),
+ 0 6px 6px rgba(0, 0, 0, 0.05),
+ 0 0 100px rgba(0, 0, 0, 0.1);
+ transition: transform 0.2s ease;
+}
+
+.board:hover {
+ transform: translateY(-5px);
+}
+
+.board h1 {
+ color: #2d3748;
+ font-size: 2.5rem;
+ margin-bottom: 1.5rem;
+ font-weight: 800;
+ background: linear-gradient(120deg, #2d3748 0%, #4a5568 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ text-align: center;
+}
+
+.board h4 {
+ color: #4a5568;
+ font-size: 1.25rem;
+ margin: 1rem 0;
+ text-align: center;
+ font-weight: 600;
+}
+
+.row {
+ display: flex;
+ justify-content: center;
+ gap: 0.75rem;
+ margin: 0.75rem 0;
+}
+
+.box {
+ background: white;
+ border: none !important;
+ border-radius: 12px !important;
+ height: 100px;
+ width: 100px;
+ font-size: 3rem !important;
+ font-weight: bold;
+ color: #4a5568;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ box-shadow:
+ 0 4px 6px rgba(0, 0, 0, 0.05),
+ 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.box:hover {
+ transform: scale(1.05);
+ box-shadow:
+ 0 10px 15px rgba(0, 0, 0, 0.1),
+ 0 4px 6px rgba(0, 0, 0, 0.05);
+}
+
+.box:active {
+ transform: scale(0.95);
+}
+
+/* Player X styling */
+.box:has(text='X') {
+ background: linear-gradient(135deg, #63b3ed 0%, #4299e1 100%);
+ color: white;
+}
+
+/* Player O styling */
+.box:has(text='O') {
+ background: linear-gradient(135deg, #f6ad55 0%, #ed8936 100%);
+ color: white;
+}
+
+#reset {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ border: none;
+ border-radius: 12px;
+ font-size: 1.25rem;
+ font-weight: 600;
+ padding: 1rem 2rem;
+ width: 100%;
+ margin-top: 2rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+#reset:hover {
+ transform: translateY(-2px);
+ box-shadow:
+ 0 10px 15px rgba(0, 0, 0, 0.1),
+ 0 4px 6px rgba(0, 0, 0, 0.05);
+}
+
+#reset:active {
+ transform: translateY(1px);
+}
+
+/* Counter */
+.buttons {
+ background-color: #ffffff;
+ color: #330002;
+
+ padding-top: 1em;
+ padding-bottom: 2em;
+ padding-right: 4em;
+ padding-left: 4em;
+ margin-top: 2em;
+
+ max-width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+}
+
+.increment {
+ color: #ffffff;
+ font-size: 1.5em;
+
+ background-color: var(--primary-red-color);
+ border-style: solid;
+ border-color: #ffffff;
+ border-radius: 5px;
+
+ margin-top: 20px;
+ margin-bottom: 20px;
+
+ width: 100%;
+
+ padding: 0.5em;
+}
+
+.increment:hover {
+ background-color: var(--secondary-blue-color);
+}
+
+.hook-data-section {
+ border: 2px solid --primary-red-color;
+ border-radius: 5px;
+ margin: 10px 0;
+ padding: 8px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.hook-data-section h4 {
+ margin: 2px 0;
+}
+
+.hook-data-section p {
+ text-align: center;
+ margin: 8px;
+}
+
+.reducer-counter {
+ background-color: #ffffff;
+ color: #330002;
+ padding: 2em;
+ margin-top: 2em;
+ max-width: 500px;
+ margin-left: auto;
+ margin-right: auto;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+}
+
+.counter-value {
+ text-align: center;
+ font-size: 1.2em;
+ margin: 1em 0;
+}
+
+.counter-buttons {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ justify-content: center;
+ margin: 1em 0;
+}
+
+.counter-buttons button {
+ color: #ffffff;
+ font-size: 1em;
+ background-color: var(--primary-red-color);
+ border: 2px solid #ffffff;
+ border-radius: 5px;
+ padding: 0.5em 1em;
+ cursor: pointer;
+}
+
+.counter-buttons button:hover {
+ background-color: var(--secondary-blue-color);
+}
+
+.counter-info {
+ text-align: center;
+ margin-top: 2em;
+}
+
+.history-list {
+ margin: 1em 0;
+ padding: 1em;
+ background-color: var(--background-color1);
+ border-radius: 5px;
+ word-wrap: break-word;
+}
+
+.nav {
+ background: var(--theme-nav-background, rgba(255, 255, 255, 0.95));
+ transition: all 0.3s ease;
+}
+
+.nav .link {
+ transition: all 0.3s ease;
+}
+
+[data-theme='dark'] .nav {
+ --theme-nav-background: rgba(26, 32, 44, 0.95);
+}
+
+[data-theme='dark'] .nav .link {
+ color: #f7fafc;
+}
+
+[data-theme='dark'] .nav .link:hover {
+ background: rgba(255, 255, 255, 0.1);
+}
+
+/* Light theme (default) */
+body[data-theme='light'] {
+ background-color: #f6f6f6;
+ color: #1a202c;
+}
+
+/* Dark theme */
+body[data-theme='dark'] {
+ background-color: #121826;
+ color: #f7fafc;
+}
+
+/* Update board styling for dark mode */
+body[data-theme='dark'] .board {
+ background: linear-gradient(135deg, #1e2837 0%, #2d3748 100%);
+ box-shadow:
+ 0 10px 20px rgba(0, 0, 0, 0.2),
+ 0 6px 6px rgba(0, 0, 0, 0.1),
+ 0 0 100px rgba(0, 0, 0, 0.2);
+}
+
+body[data-theme='dark'] .board h1 {
+ background: linear-gradient(120deg, #f7fafc 0%, #e2e8f0 100%);
+ -webkit-background-clip: text;
+}
+
+body[data-theme='dark'] .box {
+ background: #2d3748;
+ color: #f7fafc;
+ box-shadow:
+ 0 4px 6px rgba(0, 0, 0, 0.2),
+ 0 1px 3px rgba(0, 0, 0, 0.3);
+}
+
+/* Update buttons container for dark mode */
+body[data-theme='dark'] .buttons {
+ background-color: #2d3748;
+ color: #f7fafc;
+ box-shadow: rgba(0, 0, 0, 0.5) 0px 5px 15px;
+}
+
+/* Update reducer counter for dark mode */
+body[data-theme='dark'] .reducer-counter {
+ background-color: #2d3748;
+ color: #f7fafc;
+ box-shadow: rgba(0, 0, 0, 0.5) 0px 5px 15px;
+}
+
+body[data-theme='dark'] .history-list {
+ background-color: #1a202c;
+}
+
+/* Update about section for dark mode */
+body[data-theme='dark'] .about {
+ background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
+ box-shadow:
+ 0 20px 25px -5px rgba(0, 0, 0, 0.3),
+ 0 10px 10px -5px rgba(0, 0, 0, 0.2),
+ 0 0 100px rgba(0, 0, 0, 0.1);
+}
+
+body[data-theme='dark'] .about h2 {
+ background: linear-gradient(120deg, #f7fafc 0%, #e2e8f0 100%);
+ -webkit-background-clip: text;
+}
+
+body[data-theme='dark'] .about p {
+ color: #e2e8f0;
+}
+
+/* Update nav for dark mode */
+body[data-theme='dark'] .nav {
+ background: rgba(26, 32, 44, 0.95);
+ box-shadow:
+ 0 4px 6px -1px rgba(0, 0, 0, 0.2),
+ 0 2px 4px -1px rgba(0, 0, 0, 0.1);
+}
+
+body[data-theme='dark'] .link {
+ color: #e2e8f0;
+}
+
+body[data-theme='dark'] .link:hover {
+ color: #f7fafc;
+ background: rgba(255, 255, 255, 0.1);
+}
+
+/* Transition for all themed elements */
+.board,
+.buttons,
+.reducer-counter,
+.about,
+.nav,
+.link,
+.box,
+.history-list {
+ transition: all 0.3s ease;
+}
diff --git a/demo-app/src/contexts/AuthContext.tsx b/demo-app/src/contexts/AuthContext.tsx
new file mode 100644
index 000000000..a1109470c
--- /dev/null
+++ b/demo-app/src/contexts/AuthContext.tsx
@@ -0,0 +1,37 @@
+import React, { createContext, useState, useContext } from 'react';
+
+type User = {
+ username: string;
+ isAdmin: boolean;
+} | null;
+
+type AuthContextType = {
+ user: User;
+ login: (username: string) => void;
+ logout: () => void;
+};
+
+export const AuthContext = createContext({
+ user: null,
+ login: () => {},
+ logout: () => {},
+});
+
+export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [user, setUser] = useState(null);
+
+ const login = (username: string) => {
+ setUser({
+ username,
+ isAdmin: username === 'admin',
+ });
+ };
+
+ const logout = () => {
+ setUser(null);
+ };
+
+ return {children} ;
+};
+
+export const useAuth = () => useContext(AuthContext);
diff --git a/demo-app/src/contexts/ThemeContext.tsx b/demo-app/src/contexts/ThemeContext.tsx
new file mode 100644
index 000000000..a95bfb91f
--- /dev/null
+++ b/demo-app/src/contexts/ThemeContext.tsx
@@ -0,0 +1,55 @@
+import React, { createContext, useState, useContext } from 'react';
+
+type Theme = {
+ backgroundColor: string;
+ textColor: string;
+ primaryColor: string;
+ secondaryColor: string;
+};
+
+type ThemeContextType = {
+ theme: Theme;
+ toggleTheme: () => void;
+};
+
+const defaultTheme: Theme = {
+ backgroundColor: '#ffffff',
+ textColor: '#1a202c',
+ primaryColor: '#3182ce',
+ secondaryColor: '#805ad5',
+};
+
+const darkTheme: Theme = {
+ backgroundColor: '#1a202c',
+ textColor: '#f7fafc',
+ primaryColor: '#63b3ed',
+ secondaryColor: '#b794f4',
+};
+
+export const ThemeContext = createContext({
+ theme: defaultTheme,
+ toggleTheme: () => {},
+});
+
+export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [isDark, setIsDark] = useState(false);
+
+ const toggleTheme = () => {
+ setIsDark(!isDark);
+ document.body.setAttribute('data-theme', !isDark ? 'dark' : 'light');
+ };
+
+ // Set initial theme
+ React.useEffect(() => {
+ document.body.setAttribute('data-theme', isDark ? 'dark' : 'light');
+ }, []);
+
+ const value = {
+ theme: isDark ? darkTheme : defaultTheme,
+ toggleTheme,
+ };
+
+ return {children} ;
+};
+
+export const useTheme = () => useContext(ThemeContext);
diff --git a/demo-app/src/index.d.ts b/demo-app/src/index.d.ts
new file mode 100644
index 000000000..7f25dc3a3
--- /dev/null
+++ b/demo-app/src/index.d.ts
@@ -0,0 +1,4 @@
+declare module 'react-dom/client' {
+ const ReactDOMClient: { createRoot: any };
+ export = ReactDOMClient;
+}
diff --git a/demo-app/src/types.ts b/demo-app/src/types.ts
new file mode 100644
index 000000000..013fb8307
--- /dev/null
+++ b/demo-app/src/types.ts
@@ -0,0 +1,13 @@
+export type Scoreboard = {
+ X: number;
+ O: number;
+};
+
+export type Player = 'X' | 'O';
+
+export type BoardText = 'X' | 'O' | '-';
+
+export type BoardContent = Array>;
+
+//will move scoreboard and player into Board.tsx as these two types are only being used there
+//wont make a difference but this is for cleanliness sake
diff --git a/demo-app/tsconfig.json b/demo-app/tsconfig.json
new file mode 100644
index 000000000..4d9a1310c
--- /dev/null
+++ b/demo-app/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "jsx": "react",
+ "module": "commonjs",
+ "esModuleInterop": true,
+ "noImplicitAny": true,
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules"]
+}
\ No newline at end of file
diff --git a/demo-app/webpack.config.js b/demo-app/webpack.config.js
new file mode 100644
index 000000000..8dae226ab
--- /dev/null
+++ b/demo-app/webpack.config.js
@@ -0,0 +1,87 @@
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const CopyPlugin = require('copy-webpack-plugin');
+
+module.exports = {
+ mode: 'development',
+ entry: './src/client/Router.tsx',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ filename: 'bundle.js',
+ },
+ module: {
+ rules: [
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ presets: [
+ [
+ '@babel/preset-env',
+ {
+ targets: {
+ node: 'current',
+ browsers: ['last 2 versions', 'not dead', 'not < 2%', 'not ie 11'],
+ },
+ useBuiltIns: 'usage',
+ corejs: 3,
+ },
+ ],
+ [
+ '@babel/preset-react',
+ {
+ runtime: 'automatic',
+ },
+ ],
+ ],
+ plugins: ['@babel/plugin-transform-runtime'],
+ },
+ },
+ },
+ {
+ test: /\.tsx?$/,
+ use: [
+ {
+ loader: 'ts-loader',
+ options: {
+ transpileOnly: true,
+ compilerOptions: {
+ target: 'es2018',
+ module: 'esnext',
+ },
+ },
+ },
+ ],
+ exclude: /node_modules/,
+ },
+ ],
+ },
+ resolve: {
+ extensions: ['.jsx', '.js', '.ts', '.tsx'],
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: './src/client/index.html',
+ filename: './index.html',
+ }),
+ new CopyPlugin({
+ patterns: [{ from: './src/client/style.css' }],
+ }),
+ ],
+ devServer: {
+ historyApiFallback: true,
+ static: {
+ directory: path.join(__dirname, './dist'),
+ },
+ proxy: {
+ '/api': 'http://localhost:3000',
+ secure: false,
+ },
+ },
+ watchOptions: {
+ poll: true,
+ ignored: /node_modules/,
+ },
+};
diff --git a/demo.gif b/demo.gif
deleted file mode 100644
index 175888a3a..000000000
Binary files a/demo.gif and /dev/null differ
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index efae73c0b..000000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-version: '2'
-services:
- test:
- image: reacttt/test-lint
- container_name: reacttt-test-lint
- volumes:
- - .:/usr/src/app
- - /usr/src/app/node_modules
- command: npm run docker-test-lint
diff --git a/high-contrast-colors.md b/high-contrast-colors.md
new file mode 100644
index 000000000..69eb3dd6b
--- /dev/null
+++ b/high-contrast-colors.md
@@ -0,0 +1,17 @@
+## High Contrast Colors to Help with Color Blindness
+- Dark Blue (#000080)
+- Light Blue (#ADD8E6)
+- Dark Green (#006400)
+- Light Green (#90EE90)
+- Dark Red (#8B0000)
+- Light Red (#FFC0CB)
+- Dark Purple (#800080)
+- Light Purple (#BA55D3)
+- Dark Yellow (#FFD700)
+- Light Yellow (#FFFFE0)
+- Dark Orange (#FF8C00)
+- Light Orange (#FFA500)
+- Dark Brown (#8B4513)
+- Light Brown (#D2691E)
+- Dark Gray (#A9A9A9)
+- Light Gray (#D3D3D3)
\ No newline at end of file
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 000000000..9373f1aff
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,32 @@
+const { TextEncoder } = require('util');
+module.exports = {
+ globals: {
+ TextEncoder: TextEncoder,
+ },
+ transform: {
+ '^.+\\.(js|ts|tsx)$': 'ts-jest',
+ },
+ testPathIgnorePatterns: [
+ 'www',
+ './src/backend/__tests__/ignore',
+ './src/app/__tests__enzyme/ignore',
+ // './src/backend/__tests__/linkFiber.test.ts',
+ './src/app/slices/mainSlice.ts',
+ ],
+ coveragePathIgnorePatterns: [
+ '/src/backend/__tests__/ignore/',
+ '/src/app/__tests__enzyme/ignore',
+ // './src/backend/__tests__/linkFiber.test.ts',
+ './src/app/slices/mainSlice.ts',
+ ],
+ transformIgnorePatterns: ['/node_modules/(?!d3|d3-array|internmap|delaunator|robust-predicates)'],
+ testRegex: '(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$',
+ moduleFileExtensions: ['ts', 'tsx', 'js'],
+ setupFilesAfterEnv: ['@testing-library/jest-dom'],
+ testEnvironment: 'jsdom',
+ moduleNameMapper: {
+ '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
+ '/__mocks__/fileMock.js',
+ '\\.(scss|sass|css)$': 'identity-obj-proxy',
+ },
+};
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index d8fe158b7..000000000
--- a/package-lock.json
+++ /dev/null
@@ -1,12403 +0,0 @@
-{
- "name": "reactime",
- "requires": true,
- "lockfileVersion": 1,
- "dependencies": {
- "@babel/code-frame": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
- "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
- "requires": {
- "@babel/highlight": "^7.0.0"
- }
- },
- "@babel/core": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.5.5.tgz",
- "integrity": "sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg==",
- "requires": {
- "@babel/code-frame": "^7.5.5",
- "@babel/generator": "^7.5.5",
- "@babel/helpers": "^7.5.5",
- "@babel/parser": "^7.5.5",
- "@babel/template": "^7.4.4",
- "@babel/traverse": "^7.5.5",
- "@babel/types": "^7.5.5",
- "convert-source-map": "^1.1.0",
- "debug": "^4.1.0",
- "json5": "^2.1.0",
- "lodash": "^4.17.13",
- "resolve": "^1.3.2",
- "semver": "^5.4.1",
- "source-map": "^0.5.0"
- },
- "dependencies": {
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "json5": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz",
- "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==",
- "requires": {
- "minimist": "^1.2.0"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- }
- }
- },
- "@babel/generator": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz",
- "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==",
- "requires": {
- "@babel/types": "^7.5.5",
- "jsesc": "^2.5.1",
- "lodash": "^4.17.13",
- "source-map": "^0.5.0",
- "trim-right": "^1.0.1"
- }
- },
- "@babel/helper-annotate-as-pure": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz",
- "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==",
- "dev": true,
- "requires": {
- "@babel/types": "^7.0.0"
- }
- },
- "@babel/helper-builder-binary-assignment-operator-visitor": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz",
- "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==",
- "dev": true,
- "requires": {
- "@babel/helper-explode-assignable-expression": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "@babel/helper-builder-react-jsx": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.3.0.tgz",
- "integrity": "sha512-MjA9KgwCuPEkQd9ncSXvSyJ5y+j2sICHyrI0M3L+6fnS4wMSNDc1ARXsbTfbb2cXHn17VisSnU/sHFTCxVxSMw==",
- "dev": true,
- "requires": {
- "@babel/types": "^7.3.0",
- "esutils": "^2.0.0"
- }
- },
- "@babel/helper-call-delegate": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz",
- "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==",
- "dev": true,
- "requires": {
- "@babel/helper-hoist-variables": "^7.4.4",
- "@babel/traverse": "^7.4.4",
- "@babel/types": "^7.4.4"
- }
- },
- "@babel/helper-create-class-features-plugin": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.5.5.tgz",
- "integrity": "sha512-ZsxkyYiRA7Bg+ZTRpPvB6AbOFKTFFK4LrvTet8lInm0V468MWCaSYJE+I7v2z2r8KNLtYiV+K5kTCnR7dvyZjg==",
- "dev": true,
- "requires": {
- "@babel/helper-function-name": "^7.1.0",
- "@babel/helper-member-expression-to-functions": "^7.5.5",
- "@babel/helper-optimise-call-expression": "^7.0.0",
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/helper-replace-supers": "^7.5.5",
- "@babel/helper-split-export-declaration": "^7.4.4"
- }
- },
- "@babel/helper-define-map": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz",
- "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==",
- "dev": true,
- "requires": {
- "@babel/helper-function-name": "^7.1.0",
- "@babel/types": "^7.5.5",
- "lodash": "^4.17.13"
- }
- },
- "@babel/helper-explode-assignable-expression": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz",
- "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==",
- "dev": true,
- "requires": {
- "@babel/traverse": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "@babel/helper-function-name": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz",
- "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==",
- "requires": {
- "@babel/helper-get-function-arity": "^7.0.0",
- "@babel/template": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "@babel/helper-get-function-arity": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz",
- "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==",
- "requires": {
- "@babel/types": "^7.0.0"
- }
- },
- "@babel/helper-hoist-variables": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz",
- "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==",
- "dev": true,
- "requires": {
- "@babel/types": "^7.4.4"
- }
- },
- "@babel/helper-member-expression-to-functions": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz",
- "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==",
- "dev": true,
- "requires": {
- "@babel/types": "^7.5.5"
- }
- },
- "@babel/helper-module-imports": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
- "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==",
- "requires": {
- "@babel/types": "^7.0.0"
- }
- },
- "@babel/helper-module-transforms": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz",
- "integrity": "sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw==",
- "dev": true,
- "requires": {
- "@babel/helper-module-imports": "^7.0.0",
- "@babel/helper-simple-access": "^7.1.0",
- "@babel/helper-split-export-declaration": "^7.4.4",
- "@babel/template": "^7.4.4",
- "@babel/types": "^7.5.5",
- "lodash": "^4.17.13"
- }
- },
- "@babel/helper-optimise-call-expression": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz",
- "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==",
- "dev": true,
- "requires": {
- "@babel/types": "^7.0.0"
- }
- },
- "@babel/helper-plugin-utils": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz",
- "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA=="
- },
- "@babel/helper-regex": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz",
- "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==",
- "dev": true,
- "requires": {
- "lodash": "^4.17.13"
- }
- },
- "@babel/helper-remap-async-to-generator": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz",
- "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==",
- "dev": true,
- "requires": {
- "@babel/helper-annotate-as-pure": "^7.0.0",
- "@babel/helper-wrap-function": "^7.1.0",
- "@babel/template": "^7.1.0",
- "@babel/traverse": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "@babel/helper-replace-supers": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz",
- "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==",
- "dev": true,
- "requires": {
- "@babel/helper-member-expression-to-functions": "^7.5.5",
- "@babel/helper-optimise-call-expression": "^7.0.0",
- "@babel/traverse": "^7.5.5",
- "@babel/types": "^7.5.5"
- }
- },
- "@babel/helper-simple-access": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz",
- "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==",
- "dev": true,
- "requires": {
- "@babel/template": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "@babel/helper-split-export-declaration": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",
- "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",
- "requires": {
- "@babel/types": "^7.4.4"
- }
- },
- "@babel/helper-wrap-function": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz",
- "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==",
- "dev": true,
- "requires": {
- "@babel/helper-function-name": "^7.1.0",
- "@babel/template": "^7.1.0",
- "@babel/traverse": "^7.1.0",
- "@babel/types": "^7.2.0"
- }
- },
- "@babel/helpers": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.5.5.tgz",
- "integrity": "sha512-nRq2BUhxZFnfEn/ciJuhklHvFOqjJUD5wpx+1bxUF2axL9C+v4DE/dmp5sT2dKnpOs4orZWzpAZqlCy8QqE/7g==",
- "requires": {
- "@babel/template": "^7.4.4",
- "@babel/traverse": "^7.5.5",
- "@babel/types": "^7.5.5"
- }
- },
- "@babel/highlight": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
- "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
- "requires": {
- "chalk": "^2.0.0",
- "esutils": "^2.0.2",
- "js-tokens": "^4.0.0"
- }
- },
- "@babel/parser": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz",
- "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g=="
- },
- "@babel/plugin-proposal-async-generator-functions": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz",
- "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/helper-remap-async-to-generator": "^7.1.0",
- "@babel/plugin-syntax-async-generators": "^7.2.0"
- }
- },
- "@babel/plugin-proposal-class-properties": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.5.tgz",
- "integrity": "sha512-AF79FsnWFxjlaosgdi421vmYG6/jg79bVD0dpD44QdgobzHKuLZ6S3vl8la9qIeSwGi8i1fS0O1mfuDAAdo1/A==",
- "dev": true,
- "requires": {
- "@babel/helper-create-class-features-plugin": "^7.5.5",
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-proposal-decorators": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.4.4.tgz",
- "integrity": "sha512-z7MpQz3XC/iQJWXH9y+MaWcLPNSMY9RQSthrLzak8R8hCj0fuyNk+Dzi9kfNe/JxxlWQ2g7wkABbgWjW36MTcw==",
- "dev": true,
- "requires": {
- "@babel/helper-create-class-features-plugin": "^7.4.4",
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/plugin-syntax-decorators": "^7.2.0"
- }
- },
- "@babel/plugin-proposal-dynamic-import": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz",
- "integrity": "sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/plugin-syntax-dynamic-import": "^7.2.0"
- }
- },
- "@babel/plugin-proposal-json-strings": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz",
- "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/plugin-syntax-json-strings": "^7.2.0"
- }
- },
- "@babel/plugin-proposal-object-rest-spread": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz",
- "integrity": "sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/plugin-syntax-object-rest-spread": "^7.2.0"
- }
- },
- "@babel/plugin-proposal-optional-catch-binding": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz",
- "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/plugin-syntax-optional-catch-binding": "^7.2.0"
- }
- },
- "@babel/plugin-proposal-unicode-property-regex": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz",
- "integrity": "sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/helper-regex": "^7.4.4",
- "regexpu-core": "^4.5.4"
- }
- },
- "@babel/plugin-syntax-async-generators": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz",
- "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-syntax-decorators": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz",
- "integrity": "sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-syntax-dynamic-import": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz",
- "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-syntax-json-strings": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz",
- "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-syntax-jsx": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz",
- "integrity": "sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-syntax-object-rest-spread": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz",
- "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==",
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-syntax-optional-catch-binding": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz",
- "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-arrow-functions": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz",
- "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-async-to-generator": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz",
- "integrity": "sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==",
- "dev": true,
- "requires": {
- "@babel/helper-module-imports": "^7.0.0",
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/helper-remap-async-to-generator": "^7.1.0"
- }
- },
- "@babel/plugin-transform-block-scoped-functions": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz",
- "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-block-scoping": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz",
- "integrity": "sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "lodash": "^4.17.13"
- }
- },
- "@babel/plugin-transform-classes": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz",
- "integrity": "sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==",
- "dev": true,
- "requires": {
- "@babel/helper-annotate-as-pure": "^7.0.0",
- "@babel/helper-define-map": "^7.5.5",
- "@babel/helper-function-name": "^7.1.0",
- "@babel/helper-optimise-call-expression": "^7.0.0",
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/helper-replace-supers": "^7.5.5",
- "@babel/helper-split-export-declaration": "^7.4.4",
- "globals": "^11.1.0"
- }
- },
- "@babel/plugin-transform-computed-properties": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz",
- "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-destructuring": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.5.0.tgz",
- "integrity": "sha512-YbYgbd3TryYYLGyC7ZR+Tq8H/+bCmwoaxHfJHupom5ECstzbRLTch6gOQbhEY9Z4hiCNHEURgq06ykFv9JZ/QQ==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-dotall-regex": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz",
- "integrity": "sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/helper-regex": "^7.4.4",
- "regexpu-core": "^4.5.4"
- }
- },
- "@babel/plugin-transform-duplicate-keys": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz",
- "integrity": "sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-exponentiation-operator": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz",
- "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==",
- "dev": true,
- "requires": {
- "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0",
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-for-of": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz",
- "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-function-name": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz",
- "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==",
- "dev": true,
- "requires": {
- "@babel/helper-function-name": "^7.1.0",
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-literals": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz",
- "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-member-expression-literals": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz",
- "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-modules-amd": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz",
- "integrity": "sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==",
- "dev": true,
- "requires": {
- "@babel/helper-module-transforms": "^7.1.0",
- "@babel/helper-plugin-utils": "^7.0.0",
- "babel-plugin-dynamic-import-node": "^2.3.0"
- }
- },
- "@babel/plugin-transform-modules-commonjs": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.5.0.tgz",
- "integrity": "sha512-xmHq0B+ytyrWJvQTc5OWAC4ii6Dhr0s22STOoydokG51JjWhyYo5mRPXoi+ZmtHQhZZwuXNN+GG5jy5UZZJxIQ==",
- "dev": true,
- "requires": {
- "@babel/helper-module-transforms": "^7.4.4",
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/helper-simple-access": "^7.1.0",
- "babel-plugin-dynamic-import-node": "^2.3.0"
- }
- },
- "@babel/plugin-transform-modules-systemjs": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz",
- "integrity": "sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==",
- "dev": true,
- "requires": {
- "@babel/helper-hoist-variables": "^7.4.4",
- "@babel/helper-plugin-utils": "^7.0.0",
- "babel-plugin-dynamic-import-node": "^2.3.0"
- }
- },
- "@babel/plugin-transform-modules-umd": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz",
- "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==",
- "dev": true,
- "requires": {
- "@babel/helper-module-transforms": "^7.1.0",
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-named-capturing-groups-regex": {
- "version": "7.4.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz",
- "integrity": "sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg==",
- "dev": true,
- "requires": {
- "regexp-tree": "^0.1.6"
- }
- },
- "@babel/plugin-transform-new-target": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz",
- "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-object-super": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz",
- "integrity": "sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/helper-replace-supers": "^7.5.5"
- }
- },
- "@babel/plugin-transform-parameters": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz",
- "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==",
- "dev": true,
- "requires": {
- "@babel/helper-call-delegate": "^7.4.4",
- "@babel/helper-get-function-arity": "^7.0.0",
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-property-literals": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz",
- "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-react-display-name": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz",
- "integrity": "sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-react-jsx": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz",
- "integrity": "sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg==",
- "dev": true,
- "requires": {
- "@babel/helper-builder-react-jsx": "^7.3.0",
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/plugin-syntax-jsx": "^7.2.0"
- }
- },
- "@babel/plugin-transform-react-jsx-self": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.2.0.tgz",
- "integrity": "sha512-v6S5L/myicZEy+jr6ielB0OR8h+EH/1QFx/YJ7c7Ua+7lqsjj/vW6fD5FR9hB/6y7mGbfT4vAURn3xqBxsUcdg==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/plugin-syntax-jsx": "^7.2.0"
- }
- },
- "@babel/plugin-transform-react-jsx-source": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.5.0.tgz",
- "integrity": "sha512-58Q+Jsy4IDCZx7kqEZuSDdam/1oW8OdDX8f+Loo6xyxdfg1yF0GE2XNJQSTZCaMol93+FBzpWiPEwtbMloAcPg==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/plugin-syntax-jsx": "^7.2.0"
- }
- },
- "@babel/plugin-transform-regenerator": {
- "version": "7.4.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz",
- "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==",
- "dev": true,
- "requires": {
- "regenerator-transform": "^0.14.0"
- }
- },
- "@babel/plugin-transform-reserved-words": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz",
- "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-shorthand-properties": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz",
- "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-spread": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz",
- "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-sticky-regex": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz",
- "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/helper-regex": "^7.0.0"
- }
- },
- "@babel/plugin-transform-template-literals": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz",
- "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==",
- "dev": true,
- "requires": {
- "@babel/helper-annotate-as-pure": "^7.0.0",
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-typeof-symbol": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz",
- "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0"
- }
- },
- "@babel/plugin-transform-unicode-regex": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz",
- "integrity": "sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/helper-regex": "^7.4.4",
- "regexpu-core": "^4.5.4"
- }
- },
- "@babel/preset-env": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.5.5.tgz",
- "integrity": "sha512-GMZQka/+INwsMz1A5UEql8tG015h5j/qjptpKY2gJ7giy8ohzU710YciJB5rcKsWGWHiW3RUnHib0E5/m3Tp3A==",
- "dev": true,
- "requires": {
- "@babel/helper-module-imports": "^7.0.0",
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/plugin-proposal-async-generator-functions": "^7.2.0",
- "@babel/plugin-proposal-dynamic-import": "^7.5.0",
- "@babel/plugin-proposal-json-strings": "^7.2.0",
- "@babel/plugin-proposal-object-rest-spread": "^7.5.5",
- "@babel/plugin-proposal-optional-catch-binding": "^7.2.0",
- "@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
- "@babel/plugin-syntax-async-generators": "^7.2.0",
- "@babel/plugin-syntax-dynamic-import": "^7.2.0",
- "@babel/plugin-syntax-json-strings": "^7.2.0",
- "@babel/plugin-syntax-object-rest-spread": "^7.2.0",
- "@babel/plugin-syntax-optional-catch-binding": "^7.2.0",
- "@babel/plugin-transform-arrow-functions": "^7.2.0",
- "@babel/plugin-transform-async-to-generator": "^7.5.0",
- "@babel/plugin-transform-block-scoped-functions": "^7.2.0",
- "@babel/plugin-transform-block-scoping": "^7.5.5",
- "@babel/plugin-transform-classes": "^7.5.5",
- "@babel/plugin-transform-computed-properties": "^7.2.0",
- "@babel/plugin-transform-destructuring": "^7.5.0",
- "@babel/plugin-transform-dotall-regex": "^7.4.4",
- "@babel/plugin-transform-duplicate-keys": "^7.5.0",
- "@babel/plugin-transform-exponentiation-operator": "^7.2.0",
- "@babel/plugin-transform-for-of": "^7.4.4",
- "@babel/plugin-transform-function-name": "^7.4.4",
- "@babel/plugin-transform-literals": "^7.2.0",
- "@babel/plugin-transform-member-expression-literals": "^7.2.0",
- "@babel/plugin-transform-modules-amd": "^7.5.0",
- "@babel/plugin-transform-modules-commonjs": "^7.5.0",
- "@babel/plugin-transform-modules-systemjs": "^7.5.0",
- "@babel/plugin-transform-modules-umd": "^7.2.0",
- "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.5",
- "@babel/plugin-transform-new-target": "^7.4.4",
- "@babel/plugin-transform-object-super": "^7.5.5",
- "@babel/plugin-transform-parameters": "^7.4.4",
- "@babel/plugin-transform-property-literals": "^7.2.0",
- "@babel/plugin-transform-regenerator": "^7.4.5",
- "@babel/plugin-transform-reserved-words": "^7.2.0",
- "@babel/plugin-transform-shorthand-properties": "^7.2.0",
- "@babel/plugin-transform-spread": "^7.2.0",
- "@babel/plugin-transform-sticky-regex": "^7.2.0",
- "@babel/plugin-transform-template-literals": "^7.4.4",
- "@babel/plugin-transform-typeof-symbol": "^7.2.0",
- "@babel/plugin-transform-unicode-regex": "^7.4.4",
- "@babel/types": "^7.5.5",
- "browserslist": "^4.6.0",
- "core-js-compat": "^3.1.1",
- "invariant": "^2.2.2",
- "js-levenshtein": "^1.1.3",
- "semver": "^5.5.0"
- }
- },
- "@babel/preset-react": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz",
- "integrity": "sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/plugin-transform-react-display-name": "^7.0.0",
- "@babel/plugin-transform-react-jsx": "^7.0.0",
- "@babel/plugin-transform-react-jsx-self": "^7.0.0",
- "@babel/plugin-transform-react-jsx-source": "^7.0.0"
- }
- },
- "@babel/runtime": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz",
- "integrity": "sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==",
- "requires": {
- "regenerator-runtime": "^0.13.2"
- }
- },
- "@babel/template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz",
- "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==",
- "requires": {
- "@babel/code-frame": "^7.0.0",
- "@babel/parser": "^7.4.4",
- "@babel/types": "^7.4.4"
- }
- },
- "@babel/traverse": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz",
- "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==",
- "requires": {
- "@babel/code-frame": "^7.5.5",
- "@babel/generator": "^7.5.5",
- "@babel/helper-function-name": "^7.1.0",
- "@babel/helper-split-export-declaration": "^7.4.4",
- "@babel/parser": "^7.5.5",
- "@babel/types": "^7.5.5",
- "debug": "^4.1.0",
- "globals": "^11.1.0",
- "lodash": "^4.17.13"
- },
- "dependencies": {
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- }
- }
- },
- "@babel/types": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz",
- "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==",
- "requires": {
- "esutils": "^2.0.2",
- "lodash": "^4.17.13",
- "to-fast-properties": "^2.0.0"
- }
- },
- "@cnakazawa/watch": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz",
- "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==",
- "requires": {
- "exec-sh": "^0.3.2",
- "minimist": "^1.2.0"
- }
- },
- "@emotion/cache": {
- "version": "10.0.14",
- "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.14.tgz",
- "integrity": "sha512-HNGEwWnPlNyy/WPXBXzbjzkzeZFV657Z99/xq2xs5yinJHbMfi3ioCvBJ6Y8Zc8DQzO9F5jDmVXJB41Ytx3QMw==",
- "requires": {
- "@emotion/sheet": "0.9.3",
- "@emotion/stylis": "0.8.4",
- "@emotion/utils": "0.11.2",
- "@emotion/weak-memoize": "0.2.3"
- }
- },
- "@emotion/core": {
- "version": "10.0.14",
- "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.14.tgz",
- "integrity": "sha512-G9FbyxLm3lSnPfLDcag8fcOQBKui/ueXmWOhV+LuEQg9HrqExuWnWaO6gm6S5rNe+AMcqLXVljf8pYgAdFLNSg==",
- "requires": {
- "@babel/runtime": "^7.4.3",
- "@emotion/cache": "^10.0.14",
- "@emotion/css": "^10.0.14",
- "@emotion/serialize": "^0.11.8",
- "@emotion/sheet": "0.9.3",
- "@emotion/utils": "0.11.2"
- }
- },
- "@emotion/css": {
- "version": "10.0.14",
- "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.14.tgz",
- "integrity": "sha512-MozgPkBEWvorcdpqHZE5x1D/PLEHUitALQCQYt2wayf4UNhpgQs2tN0UwHYS4FMy5ROBH+0ALyCFVYJ/ywmwlg==",
- "requires": {
- "@emotion/serialize": "^0.11.8",
- "@emotion/utils": "0.11.2",
- "babel-plugin-emotion": "^10.0.14"
- }
- },
- "@emotion/hash": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.2.tgz",
- "integrity": "sha512-RMtr1i6E8MXaBWwhXL3yeOU8JXRnz8GNxHvaUfVvwxokvayUY0zoBeWbKw1S9XkufmGEEdQd228pSZXFkAln8Q=="
- },
- "@emotion/memoize": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.2.tgz",
- "integrity": "sha512-hnHhwQzvPCW1QjBWFyBtsETdllOM92BfrKWbUTmh9aeOlcVOiXvlPsK4104xH8NsaKfg86PTFsWkueQeUfMA/w=="
- },
- "@emotion/serialize": {
- "version": "0.11.8",
- "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.8.tgz",
- "integrity": "sha512-Qb6Us2Yk1ZW8SOYH6s5z7qzXXb2iHwVeqc6FjXtac0vvxC416ki0eTtHNw4Q5smoyxdyZh3519NKGrQvvvrZ/Q==",
- "requires": {
- "@emotion/hash": "0.7.2",
- "@emotion/memoize": "0.7.2",
- "@emotion/unitless": "0.7.4",
- "@emotion/utils": "0.11.2",
- "csstype": "^2.5.7"
- }
- },
- "@emotion/sheet": {
- "version": "0.9.3",
- "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.3.tgz",
- "integrity": "sha512-c3Q6V7Df7jfwSq5AzQWbXHa5soeE4F5cbqi40xn0CzXxWW9/6Mxq48WJEtqfWzbZtW9odZdnRAkwCQwN12ob4A=="
- },
- "@emotion/stylis": {
- "version": "0.8.4",
- "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.4.tgz",
- "integrity": "sha512-TLmkCVm8f8gH0oLv+HWKiu7e8xmBIaokhxcEKPh1m8pXiV/akCiq50FvYgOwY42rjejck8nsdQxZlXZ7pmyBUQ=="
- },
- "@emotion/unitless": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.4.tgz",
- "integrity": "sha512-kBa+cDHOR9jpRJ+kcGMsysrls0leukrm68DmFQoMIWQcXdr2cZvyvypWuGYT7U+9kAExUE7+T7r6G3C3A6L8MQ=="
- },
- "@emotion/utils": {
- "version": "0.11.2",
- "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.2.tgz",
- "integrity": "sha512-UHX2XklLl3sIaP6oiMmlVzT0J+2ATTVpf0dHQVyPJHTkOITvXfaSqnRk6mdDhV9pR8T/tHc3cex78IKXssmzrA=="
- },
- "@emotion/weak-memoize": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.3.tgz",
- "integrity": "sha512-zVgvPwGK7c1aVdUVc9Qv7SqepOGRDrqCw7KZPSZziWGxSlbII3gmvGLPzLX4d0n0BMbamBacUrN22zOMyFFEkQ=="
- },
- "@jest/console": {
- "version": "24.7.1",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz",
- "integrity": "sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg==",
- "requires": {
- "@jest/source-map": "^24.3.0",
- "chalk": "^2.0.1",
- "slash": "^2.0.0"
- }
- },
- "@jest/core": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.8.0.tgz",
- "integrity": "sha512-R9rhAJwCBQzaRnrRgAdVfnglUuATXdwTRsYqs6NMdVcAl5euG8LtWDe+fVkN27YfKVBW61IojVsXKaOmSnqd/A==",
- "dev": true,
- "requires": {
- "@jest/console": "^24.7.1",
- "@jest/reporters": "^24.8.0",
- "@jest/test-result": "^24.8.0",
- "@jest/transform": "^24.8.0",
- "@jest/types": "^24.8.0",
- "ansi-escapes": "^3.0.0",
- "chalk": "^2.0.1",
- "exit": "^0.1.2",
- "graceful-fs": "^4.1.15",
- "jest-changed-files": "^24.8.0",
- "jest-config": "^24.8.0",
- "jest-haste-map": "^24.8.0",
- "jest-message-util": "^24.8.0",
- "jest-regex-util": "^24.3.0",
- "jest-resolve-dependencies": "^24.8.0",
- "jest-runner": "^24.8.0",
- "jest-runtime": "^24.8.0",
- "jest-snapshot": "^24.8.0",
- "jest-util": "^24.8.0",
- "jest-validate": "^24.8.0",
- "jest-watcher": "^24.8.0",
- "micromatch": "^3.1.10",
- "p-each-series": "^1.0.0",
- "pirates": "^4.0.1",
- "realpath-native": "^1.1.0",
- "rimraf": "^2.5.4",
- "strip-ansi": "^5.0.0"
- }
- },
- "@jest/environment": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.8.0.tgz",
- "integrity": "sha512-vlGt2HLg7qM+vtBrSkjDxk9K0YtRBi7HfRFaDxoRtyi+DyVChzhF20duvpdAnKVBV6W5tym8jm0U9EfXbDk1tw==",
- "dev": true,
- "requires": {
- "@jest/fake-timers": "^24.8.0",
- "@jest/transform": "^24.8.0",
- "@jest/types": "^24.8.0",
- "jest-mock": "^24.8.0"
- }
- },
- "@jest/fake-timers": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.8.0.tgz",
- "integrity": "sha512-2M4d5MufVXwi6VzZhJ9f5S/wU4ud2ck0kxPof1Iz3zWx6Y+V2eJrES9jEktB6O3o/oEyk+il/uNu9PvASjWXQw==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.8.0",
- "jest-message-util": "^24.8.0",
- "jest-mock": "^24.8.0"
- }
- },
- "@jest/reporters": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.8.0.tgz",
- "integrity": "sha512-eZ9TyUYpyIIXfYCrw0UHUWUvE35vx5I92HGMgS93Pv7du+GHIzl+/vh8Qj9MCWFK/4TqyttVBPakWMOfZRIfxw==",
- "dev": true,
- "requires": {
- "@jest/environment": "^24.8.0",
- "@jest/test-result": "^24.8.0",
- "@jest/transform": "^24.8.0",
- "@jest/types": "^24.8.0",
- "chalk": "^2.0.1",
- "exit": "^0.1.2",
- "glob": "^7.1.2",
- "istanbul-lib-coverage": "^2.0.2",
- "istanbul-lib-instrument": "^3.0.1",
- "istanbul-lib-report": "^2.0.4",
- "istanbul-lib-source-maps": "^3.0.1",
- "istanbul-reports": "^2.1.1",
- "jest-haste-map": "^24.8.0",
- "jest-resolve": "^24.8.0",
- "jest-runtime": "^24.8.0",
- "jest-util": "^24.8.0",
- "jest-worker": "^24.6.0",
- "node-notifier": "^5.2.1",
- "slash": "^2.0.0",
- "source-map": "^0.6.0",
- "string-length": "^2.0.0"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
- }
- }
- },
- "@jest/source-map": {
- "version": "24.3.0",
- "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.3.0.tgz",
- "integrity": "sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag==",
- "requires": {
- "callsites": "^3.0.0",
- "graceful-fs": "^4.1.15",
- "source-map": "^0.6.0"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
- }
- }
- },
- "@jest/test-result": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.8.0.tgz",
- "integrity": "sha512-+YdLlxwizlfqkFDh7Mc7ONPQAhA4YylU1s529vVM1rsf67vGZH/2GGm5uO8QzPeVyaVMobCQ7FTxl38QrKRlng==",
- "dev": true,
- "requires": {
- "@jest/console": "^24.7.1",
- "@jest/types": "^24.8.0",
- "@types/istanbul-lib-coverage": "^2.0.0"
- }
- },
- "@jest/test-sequencer": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.8.0.tgz",
- "integrity": "sha512-OzL/2yHyPdCHXEzhoBuq37CE99nkme15eHkAzXRVqthreWZamEMA0WoetwstsQBCXABhczpK03JNbc4L01vvLg==",
- "dev": true,
- "requires": {
- "@jest/test-result": "^24.8.0",
- "jest-haste-map": "^24.8.0",
- "jest-runner": "^24.8.0",
- "jest-runtime": "^24.8.0"
- }
- },
- "@jest/transform": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.8.0.tgz",
- "integrity": "sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA==",
- "dev": true,
- "requires": {
- "@babel/core": "^7.1.0",
- "@jest/types": "^24.8.0",
- "babel-plugin-istanbul": "^5.1.0",
- "chalk": "^2.0.1",
- "convert-source-map": "^1.4.0",
- "fast-json-stable-stringify": "^2.0.0",
- "graceful-fs": "^4.1.15",
- "jest-haste-map": "^24.8.0",
- "jest-regex-util": "^24.3.0",
- "jest-util": "^24.8.0",
- "micromatch": "^3.1.10",
- "realpath-native": "^1.1.0",
- "slash": "^2.0.0",
- "source-map": "^0.6.1",
- "write-file-atomic": "2.4.1"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
- }
- }
- },
- "@jest/types": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.8.0.tgz",
- "integrity": "sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg==",
- "dev": true,
- "requires": {
- "@types/istanbul-lib-coverage": "^2.0.0",
- "@types/istanbul-reports": "^1.1.1",
- "@types/yargs": "^12.0.9"
- }
- },
- "@sinonjs/commons": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz",
- "integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==",
- "dev": true,
- "requires": {
- "type-detect": "4.0.8"
- }
- },
- "@sinonjs/formatio": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz",
- "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==",
- "dev": true,
- "requires": {
- "@sinonjs/commons": "^1",
- "@sinonjs/samsam": "^3.1.0"
- }
- },
- "@sinonjs/samsam": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.2.tgz",
- "integrity": "sha512-ILO/rR8LfAb60Y1Yfp9vxfYAASK43NFC2mLzpvLUbCQY/Qu8YwReboseu8aheCEkyElZF2L2T9mHcR2bgdvZyA==",
- "dev": true,
- "requires": {
- "@sinonjs/commons": "^1.0.2",
- "array-from": "^2.1.1",
- "lodash": "^4.17.11"
- }
- },
- "@sinonjs/text-encoding": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz",
- "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
- "dev": true
- },
- "@types/babel__core": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz",
- "integrity": "sha512-cfCCrFmiGY/yq0NuKNxIQvZFy9kY/1immpSpTngOnyIbD4+eJOG5mxphhHDv3CHL9GltO4GcKr54kGBg3RNdbg==",
- "requires": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
- }
- },
- "@types/babel__generator": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.0.2.tgz",
- "integrity": "sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ==",
- "requires": {
- "@babel/types": "^7.0.0"
- }
- },
- "@types/babel__template": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz",
- "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==",
- "requires": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "@types/babel__traverse": {
- "version": "7.0.7",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.7.tgz",
- "integrity": "sha512-CeBpmX1J8kWLcDEnI3Cl2Eo6RfbGvzUctA+CjZUhOKDFbLfcr7fc4usEqLNWetrlJd7RhAkyYe2czXop4fICpw==",
- "requires": {
- "@babel/types": "^7.3.0"
- }
- },
- "@types/istanbul-lib-coverage": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz",
- "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg=="
- },
- "@types/istanbul-lib-report": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz",
- "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==",
- "requires": {
- "@types/istanbul-lib-coverage": "*"
- }
- },
- "@types/istanbul-reports": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz",
- "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==",
- "requires": {
- "@types/istanbul-lib-coverage": "*",
- "@types/istanbul-lib-report": "*"
- }
- },
- "@types/json-schema": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz",
- "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==",
- "dev": true
- },
- "@types/node": {
- "version": "12.6.8",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz",
- "integrity": "sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==",
- "dev": true
- },
- "@types/stack-utils": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
- "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw=="
- },
- "@types/yargs": {
- "version": "12.0.12",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz",
- "integrity": "sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==",
- "dev": true
- },
- "@types/yargs-parser": {
- "version": "13.1.0",
- "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.1.0.tgz",
- "integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg=="
- },
- "@typescript-eslint/experimental-utils": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz",
- "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==",
- "dev": true,
- "requires": {
- "@types/json-schema": "^7.0.3",
- "@typescript-eslint/typescript-estree": "1.13.0",
- "eslint-scope": "^4.0.0"
- }
- },
- "@typescript-eslint/typescript-estree": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz",
- "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==",
- "dev": true,
- "requires": {
- "lodash.unescape": "4.0.1",
- "semver": "5.5.0"
- },
- "dependencies": {
- "semver": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
- "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
- "dev": true
- }
- }
- },
- "@webassemblyjs/ast": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
- "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==",
- "dev": true,
- "requires": {
- "@webassemblyjs/helper-module-context": "1.8.5",
- "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
- "@webassemblyjs/wast-parser": "1.8.5"
- }
- },
- "@webassemblyjs/floating-point-hex-parser": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz",
- "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==",
- "dev": true
- },
- "@webassemblyjs/helper-api-error": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz",
- "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==",
- "dev": true
- },
- "@webassemblyjs/helper-buffer": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz",
- "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==",
- "dev": true
- },
- "@webassemblyjs/helper-code-frame": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz",
- "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==",
- "dev": true,
- "requires": {
- "@webassemblyjs/wast-printer": "1.8.5"
- }
- },
- "@webassemblyjs/helper-fsm": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz",
- "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==",
- "dev": true
- },
- "@webassemblyjs/helper-module-context": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz",
- "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==",
- "dev": true,
- "requires": {
- "@webassemblyjs/ast": "1.8.5",
- "mamacro": "^0.0.3"
- }
- },
- "@webassemblyjs/helper-wasm-bytecode": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz",
- "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==",
- "dev": true
- },
- "@webassemblyjs/helper-wasm-section": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz",
- "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==",
- "dev": true,
- "requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/helper-buffer": "1.8.5",
- "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
- "@webassemblyjs/wasm-gen": "1.8.5"
- }
- },
- "@webassemblyjs/ieee754": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz",
- "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==",
- "dev": true,
- "requires": {
- "@xtuc/ieee754": "^1.2.0"
- }
- },
- "@webassemblyjs/leb128": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz",
- "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==",
- "dev": true,
- "requires": {
- "@xtuc/long": "4.2.2"
- }
- },
- "@webassemblyjs/utf8": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz",
- "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==",
- "dev": true
- },
- "@webassemblyjs/wasm-edit": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz",
- "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==",
- "dev": true,
- "requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/helper-buffer": "1.8.5",
- "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
- "@webassemblyjs/helper-wasm-section": "1.8.5",
- "@webassemblyjs/wasm-gen": "1.8.5",
- "@webassemblyjs/wasm-opt": "1.8.5",
- "@webassemblyjs/wasm-parser": "1.8.5",
- "@webassemblyjs/wast-printer": "1.8.5"
- }
- },
- "@webassemblyjs/wasm-gen": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz",
- "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==",
- "dev": true,
- "requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
- "@webassemblyjs/ieee754": "1.8.5",
- "@webassemblyjs/leb128": "1.8.5",
- "@webassemblyjs/utf8": "1.8.5"
- }
- },
- "@webassemblyjs/wasm-opt": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz",
- "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==",
- "dev": true,
- "requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/helper-buffer": "1.8.5",
- "@webassemblyjs/wasm-gen": "1.8.5",
- "@webassemblyjs/wasm-parser": "1.8.5"
- }
- },
- "@webassemblyjs/wasm-parser": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz",
- "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==",
- "dev": true,
- "requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/helper-api-error": "1.8.5",
- "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
- "@webassemblyjs/ieee754": "1.8.5",
- "@webassemblyjs/leb128": "1.8.5",
- "@webassemblyjs/utf8": "1.8.5"
- }
- },
- "@webassemblyjs/wast-parser": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz",
- "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==",
- "dev": true,
- "requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/floating-point-hex-parser": "1.8.5",
- "@webassemblyjs/helper-api-error": "1.8.5",
- "@webassemblyjs/helper-code-frame": "1.8.5",
- "@webassemblyjs/helper-fsm": "1.8.5",
- "@xtuc/long": "4.2.2"
- }
- },
- "@webassemblyjs/wast-printer": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz",
- "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==",
- "dev": true,
- "requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/wast-parser": "1.8.5",
- "@xtuc/long": "4.2.2"
- }
- },
- "@xtuc/ieee754": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
- "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
- "dev": true
- },
- "@xtuc/long": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
- "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
- "dev": true
- },
- "abab": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz",
- "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w=="
- },
- "abbrev": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
- "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
- "dev": true
- },
- "acorn": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
- "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
- "dev": true
- },
- "acorn-globals": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz",
- "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==",
- "requires": {
- "acorn": "^6.0.1",
- "acorn-walk": "^6.0.1"
- },
- "dependencies": {
- "acorn": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
- "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA=="
- }
- }
- },
- "acorn-jsx": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz",
- "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==",
- "dev": true
- },
- "acorn-walk": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
- "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA=="
- },
- "add-dom-event-listener": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz",
- "integrity": "sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==",
- "requires": {
- "object-assign": "4.x"
- }
- },
- "airbnb-prop-types": {
- "version": "2.14.0",
- "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.14.0.tgz",
- "integrity": "sha512-Yb09vUkr3KP9r9NqfRuYtDYZG76wt8mhTUi2Vfzsghk+qkg01/gOc9NU8n63ZcMCLzpAdMEXyKjCHlxV62yN1A==",
- "dev": true,
- "requires": {
- "array.prototype.find": "^2.1.0",
- "function.prototype.name": "^1.1.1",
- "has": "^1.0.3",
- "is-regex": "^1.0.4",
- "object-is": "^1.0.1",
- "object.assign": "^4.1.0",
- "object.entries": "^1.1.0",
- "prop-types": "^15.7.2",
- "prop-types-exact": "^1.2.0",
- "react-is": "^16.8.6"
- }
- },
- "ajv": {
- "version": "6.10.2",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
- "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
- "requires": {
- "fast-deep-equal": "^2.0.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- }
- },
- "ajv-errors": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
- "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
- "dev": true
- },
- "ajv-keywords": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
- "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
- "dev": true
- },
- "amdefine": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
- "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
- "dev": true
- },
- "ansi-escapes": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
- "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
- "dev": true
- },
- "ansi-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
- "dev": true
- },
- "ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "requires": {
- "color-convert": "^1.9.0"
- }
- },
- "anymatch": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
- "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
- "requires": {
- "micromatch": "^3.1.4",
- "normalize-path": "^2.1.1"
- },
- "dependencies": {
- "normalize-path": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
- "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
- "requires": {
- "remove-trailing-separator": "^1.0.1"
- }
- }
- }
- },
- "aproba": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
- "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
- "dev": true
- },
- "are-we-there-yet": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
- "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
- "dev": true,
- "requires": {
- "delegates": "^1.0.0",
- "readable-stream": "^2.0.6"
- }
- },
- "argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "requires": {
- "sprintf-js": "~1.0.2"
- }
- },
- "aria-query": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz",
- "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=",
- "dev": true,
- "requires": {
- "ast-types-flow": "0.0.7",
- "commander": "^2.11.0"
- }
- },
- "arr-diff": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
- "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
- },
- "arr-flatten": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
- "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg=="
- },
- "arr-union": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
- "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ="
- },
- "array-equal": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
- "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM="
- },
- "array-filter": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz",
- "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=",
- "dev": true
- },
- "array-find-index": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
- "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
- "dev": true
- },
- "array-from": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz",
- "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=",
- "dev": true
- },
- "array-includes": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
- "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.2",
- "es-abstract": "^1.7.0"
- }
- },
- "array-unique": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
- "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
- },
- "array.prototype.find": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.0.tgz",
- "integrity": "sha512-Wn41+K1yuO5p7wRZDl7890c3xvv5UBrfVXTVIe28rSQb6LS0fZMDrQB6PAcxQFRFy6vJTLDc3A2+3CjQdzVKRg==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.13.0"
- }
- },
- "array.prototype.flat": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz",
- "integrity": "sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.2",
- "es-abstract": "^1.10.0",
- "function-bind": "^1.1.1"
- }
- },
- "asn1": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
- "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
- "requires": {
- "safer-buffer": "~2.1.0"
- }
- },
- "asn1.js": {
- "version": "4.10.1",
- "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
- "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
- "dev": true,
- "requires": {
- "bn.js": "^4.0.0",
- "inherits": "^2.0.1",
- "minimalistic-assert": "^1.0.0"
- }
- },
- "assert": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
- "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
- "dev": true,
- "requires": {
- "object-assign": "^4.1.1",
- "util": "0.10.3"
- },
- "dependencies": {
- "inherits": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
- "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
- "dev": true
- },
- "util": {
- "version": "0.10.3",
- "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
- "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
- "dev": true,
- "requires": {
- "inherits": "2.0.1"
- }
- }
- }
- },
- "assert-plus": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
- },
- "assign-symbols": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
- "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
- },
- "ast-types-flow": {
- "version": "0.0.7",
- "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
- "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
- "dev": true
- },
- "astral-regex": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
- "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
- "dev": true
- },
- "async-each": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
- "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
- "dev": true
- },
- "async-foreach": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
- "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=",
- "dev": true
- },
- "async-limiter": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
- "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
- },
- "asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
- },
- "atob": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
- "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
- },
- "aws-sign2": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
- "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
- },
- "aws4": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
- "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
- },
- "axobject-query": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz",
- "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==",
- "dev": true,
- "requires": {
- "ast-types-flow": "0.0.7"
- }
- },
- "babel-jest": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.8.0.tgz",
- "integrity": "sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw==",
- "dev": true,
- "requires": {
- "@jest/transform": "^24.8.0",
- "@jest/types": "^24.8.0",
- "@types/babel__core": "^7.1.0",
- "babel-plugin-istanbul": "^5.1.0",
- "babel-preset-jest": "^24.6.0",
- "chalk": "^2.4.2",
- "slash": "^2.0.0"
- }
- },
- "babel-loader": {
- "version": "8.0.6",
- "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz",
- "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==",
- "dev": true,
- "requires": {
- "find-cache-dir": "^2.0.0",
- "loader-utils": "^1.0.2",
- "mkdirp": "^0.5.1",
- "pify": "^4.0.1"
- }
- },
- "babel-plugin-dynamic-import-node": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz",
- "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==",
- "dev": true,
- "requires": {
- "object.assign": "^4.1.0"
- }
- },
- "babel-plugin-emotion": {
- "version": "10.0.14",
- "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.14.tgz",
- "integrity": "sha512-T7hdxJ4xXkKW3OXcizK0pnUJlBeNj/emjQZPDIZvGOuwl2adIgicQWRNkz6BuwKdDTrqaXQn1vayaL6aL8QW5A==",
- "requires": {
- "@babel/helper-module-imports": "^7.0.0",
- "@emotion/hash": "0.7.2",
- "@emotion/memoize": "0.7.2",
- "@emotion/serialize": "^0.11.8",
- "babel-plugin-macros": "^2.0.0",
- "babel-plugin-syntax-jsx": "^6.18.0",
- "convert-source-map": "^1.5.0",
- "escape-string-regexp": "^1.0.5",
- "find-root": "^1.1.0",
- "source-map": "^0.5.7"
- }
- },
- "babel-plugin-istanbul": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz",
- "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==",
- "requires": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "find-up": "^3.0.0",
- "istanbul-lib-instrument": "^3.3.0",
- "test-exclude": "^5.2.3"
- }
- },
- "babel-plugin-jest-hoist": {
- "version": "24.6.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz",
- "integrity": "sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w==",
- "dev": true,
- "requires": {
- "@types/babel__traverse": "^7.0.6"
- }
- },
- "babel-plugin-macros": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.6.1.tgz",
- "integrity": "sha512-6W2nwiXme6j1n2erPOnmRiWfObUhWH7Qw1LMi9XZy8cj+KtESu3T6asZvtk5bMQQjX8te35o7CFueiSdL/2NmQ==",
- "requires": {
- "@babel/runtime": "^7.4.2",
- "cosmiconfig": "^5.2.0",
- "resolve": "^1.10.0"
- }
- },
- "babel-plugin-syntax-jsx": {
- "version": "6.18.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
- "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
- },
- "babel-preset-jest": {
- "version": "24.6.0",
- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz",
- "integrity": "sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw==",
- "dev": true,
- "requires": {
- "@babel/plugin-syntax-object-rest-spread": "^7.0.0",
- "babel-plugin-jest-hoist": "^24.6.0"
- }
- },
- "babel-runtime": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
- "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
- "requires": {
- "core-js": "^2.4.0",
- "regenerator-runtime": "^0.11.0"
- },
- "dependencies": {
- "core-js": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz",
- "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A=="
- },
- "regenerator-runtime": {
- "version": "0.11.1",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
- "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
- }
- }
- },
- "balanced-match": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
- "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
- },
- "base": {
- "version": "0.11.2",
- "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
- "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
- "requires": {
- "cache-base": "^1.0.1",
- "class-utils": "^0.3.5",
- "component-emitter": "^1.2.1",
- "define-property": "^1.0.0",
- "isobject": "^3.0.1",
- "mixin-deep": "^1.2.0",
- "pascalcase": "^0.1.1"
- },
- "dependencies": {
- "define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "requires": {
- "is-descriptor": "^1.0.0"
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- }
- }
- },
- "base16": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
- "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA="
- },
- "base64-js": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
- "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
- "dev": true
- },
- "bcrypt-pbkdf": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
- "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
- "requires": {
- "tweetnacl": "^0.14.3"
- }
- },
- "big.js": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
- "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
- "dev": true
- },
- "binary-extensions": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
- "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
- "dev": true
- },
- "block-stream": {
- "version": "0.0.9",
- "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
- "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
- "dev": true,
- "requires": {
- "inherits": "~2.0.0"
- }
- },
- "bluebird": {
- "version": "3.5.5",
- "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz",
- "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==",
- "dev": true
- },
- "bn.js": {
- "version": "4.11.8",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
- "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
- "dev": true
- },
- "boolbase": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
- "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
- "dev": true
- },
- "bower": {
- "version": "1.8.8",
- "resolved": "https://registry.npmjs.org/bower/-/bower-1.8.8.tgz",
- "integrity": "sha512-1SrJnXnkP9soITHptSO+ahx3QKp3cVzn8poI6ujqc5SeOkg5iqM1pK9H+DSc2OQ8SnO0jC/NG4Ur/UIwy7574A=="
- },
- "brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "braces": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
- "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
- "requires": {
- "arr-flatten": "^1.1.0",
- "array-unique": "^0.3.2",
- "extend-shallow": "^2.0.1",
- "fill-range": "^4.0.0",
- "isobject": "^3.0.1",
- "repeat-element": "^1.1.2",
- "snapdragon": "^0.8.1",
- "snapdragon-node": "^2.0.1",
- "split-string": "^3.0.2",
- "to-regex": "^3.0.1"
- },
- "dependencies": {
- "extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "requires": {
- "is-extendable": "^0.1.0"
- }
- }
- }
- },
- "brorand": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
- "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
- "dev": true
- },
- "browser-process-hrtime": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz",
- "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw=="
- },
- "browser-resolve": {
- "version": "1.11.3",
- "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz",
- "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==",
- "requires": {
- "resolve": "1.1.7"
- },
- "dependencies": {
- "resolve": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
- "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs="
- }
- }
- },
- "browserify-aes": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
- "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
- "dev": true,
- "requires": {
- "buffer-xor": "^1.0.3",
- "cipher-base": "^1.0.0",
- "create-hash": "^1.1.0",
- "evp_bytestokey": "^1.0.3",
- "inherits": "^2.0.1",
- "safe-buffer": "^5.0.1"
- }
- },
- "browserify-cipher": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
- "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
- "dev": true,
- "requires": {
- "browserify-aes": "^1.0.4",
- "browserify-des": "^1.0.0",
- "evp_bytestokey": "^1.0.0"
- }
- },
- "browserify-des": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
- "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
- "dev": true,
- "requires": {
- "cipher-base": "^1.0.1",
- "des.js": "^1.0.0",
- "inherits": "^2.0.1",
- "safe-buffer": "^5.1.2"
- }
- },
- "browserify-rsa": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
- "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
- "dev": true,
- "requires": {
- "bn.js": "^4.1.0",
- "randombytes": "^2.0.1"
- }
- },
- "browserify-sign": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
- "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
- "dev": true,
- "requires": {
- "bn.js": "^4.1.1",
- "browserify-rsa": "^4.0.0",
- "create-hash": "^1.1.0",
- "create-hmac": "^1.1.2",
- "elliptic": "^6.0.0",
- "inherits": "^2.0.1",
- "parse-asn1": "^5.0.0"
- }
- },
- "browserify-zlib": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
- "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
- "dev": true,
- "requires": {
- "pako": "~1.0.5"
- }
- },
- "browserslist": {
- "version": "4.6.6",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz",
- "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==",
- "dev": true,
- "requires": {
- "caniuse-lite": "^1.0.30000984",
- "electron-to-chromium": "^1.3.191",
- "node-releases": "^1.1.25"
- }
- },
- "bser": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.0.tgz",
- "integrity": "sha512-8zsjWrQkkBoLK6uxASk1nJ2SKv97ltiGDo6A3wA0/yRPz+CwmEyDo0hUrhIuukG2JHpAl3bvFIixw2/3Hi0DOg==",
- "requires": {
- "node-int64": "^0.4.0"
- }
- },
- "buffer": {
- "version": "4.9.1",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
- "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
- "dev": true,
- "requires": {
- "base64-js": "^1.0.2",
- "ieee754": "^1.1.4",
- "isarray": "^1.0.0"
- }
- },
- "buffer-from": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
- "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
- },
- "buffer-xor": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
- "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
- "dev": true
- },
- "builtin-status-codes": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
- "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
- "dev": true
- },
- "cacache": {
- "version": "12.0.2",
- "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.2.tgz",
- "integrity": "sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==",
- "dev": true,
- "requires": {
- "bluebird": "^3.5.5",
- "chownr": "^1.1.1",
- "figgy-pudding": "^3.5.1",
- "glob": "^7.1.4",
- "graceful-fs": "^4.1.15",
- "infer-owner": "^1.0.3",
- "lru-cache": "^5.1.1",
- "mississippi": "^3.0.0",
- "mkdirp": "^0.5.1",
- "move-concurrently": "^1.0.1",
- "promise-inflight": "^1.0.1",
- "rimraf": "^2.6.3",
- "ssri": "^6.0.1",
- "unique-filename": "^1.1.1",
- "y18n": "^4.0.0"
- }
- },
- "cache-base": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
- "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
- "requires": {
- "collection-visit": "^1.0.0",
- "component-emitter": "^1.2.1",
- "get-value": "^2.0.6",
- "has-value": "^1.0.0",
- "isobject": "^3.0.1",
- "set-value": "^2.0.0",
- "to-object-path": "^0.3.0",
- "union-value": "^1.0.0",
- "unset-value": "^1.0.0"
- }
- },
- "caller-callsite": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
- "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
- "requires": {
- "callsites": "^2.0.0"
- },
- "dependencies": {
- "callsites": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
- "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA="
- }
- }
- },
- "caller-path": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
- "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
- "requires": {
- "caller-callsite": "^2.0.0"
- }
- },
- "callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
- },
- "camelcase": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
- },
- "camelcase-keys": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
- "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
- "dev": true,
- "requires": {
- "camelcase": "^2.0.0",
- "map-obj": "^1.0.0"
- },
- "dependencies": {
- "camelcase": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
- "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
- "dev": true
- }
- }
- },
- "caniuse-lite": {
- "version": "1.0.30000985",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000985.tgz",
- "integrity": "sha512-1ngiwkgqAYPG0JSSUp3PUDGPKKY59EK7NrGGX+VOxaKCNzRbNc7uXMny+c3VJfZxtoK3wSImTvG9T9sXiTw2+w==",
- "dev": true
- },
- "capture-exit": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",
- "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==",
- "requires": {
- "rsvp": "^4.8.4"
- }
- },
- "caseless": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
- "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
- },
- "chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "requires": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
- }
- },
- "chardet": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
- "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
- "dev": true
- },
- "cheerio": {
- "version": "1.0.0-rc.3",
- "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz",
- "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==",
- "dev": true,
- "requires": {
- "css-select": "~1.2.0",
- "dom-serializer": "~0.1.1",
- "entities": "~1.1.1",
- "htmlparser2": "^3.9.1",
- "lodash": "^4.15.0",
- "parse5": "^3.0.1"
- },
- "dependencies": {
- "parse5": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
- "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==",
- "dev": true,
- "requires": {
- "@types/node": "*"
- }
- }
- }
- },
- "chokidar": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz",
- "integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==",
- "dev": true,
- "requires": {
- "anymatch": "^3.0.1",
- "braces": "^3.0.2",
- "fsevents": "^2.0.6",
- "glob-parent": "^5.0.0",
- "is-binary-path": "^2.1.0",
- "is-glob": "^4.0.1",
- "normalize-path": "^3.0.0",
- "readdirp": "^3.1.1"
- },
- "dependencies": {
- "anymatch": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.3.tgz",
- "integrity": "sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==",
- "dev": true,
- "requires": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- }
- },
- "braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "dev": true,
- "requires": {
- "fill-range": "^7.0.1"
- }
- },
- "fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
- "dev": true,
- "requires": {
- "to-regex-range": "^5.0.1"
- }
- },
- "fsevents": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz",
- "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==",
- "dev": true,
- "optional": true
- },
- "is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true
- },
- "to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
- "requires": {
- "is-number": "^7.0.0"
- }
- }
- }
- },
- "chownr": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz",
- "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==",
- "dev": true
- },
- "chrome-trace-event": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz",
- "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==",
- "dev": true,
- "requires": {
- "tslib": "^1.9.0"
- }
- },
- "ci-info": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
- "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="
- },
- "cipher-base": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
- "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
- "dev": true,
- "requires": {
- "inherits": "^2.0.1",
- "safe-buffer": "^5.0.1"
- }
- },
- "class-utils": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
- "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
- "requires": {
- "arr-union": "^3.1.0",
- "define-property": "^0.2.5",
- "isobject": "^3.0.0",
- "static-extend": "^0.1.1"
- },
- "dependencies": {
- "define-property": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
- "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
- "requires": {
- "is-descriptor": "^0.1.0"
- }
- }
- }
- },
- "classnames": {
- "version": "2.2.6",
- "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
- "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
- },
- "cli-cursor": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
- "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
- "dev": true,
- "requires": {
- "restore-cursor": "^2.0.0"
- }
- },
- "cli-width": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
- "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
- "dev": true
- },
- "cliui": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
- "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
- "requires": {
- "string-width": "^3.1.0",
- "strip-ansi": "^5.2.0",
- "wrap-ansi": "^5.1.0"
- },
- "dependencies": {
- "string-width": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
- "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
- "requires": {
- "emoji-regex": "^7.0.1",
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^5.1.0"
- }
- }
- }
- },
- "clone-deep": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
- "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
- "dev": true,
- "requires": {
- "is-plain-object": "^2.0.4",
- "kind-of": "^6.0.2",
- "shallow-clone": "^3.0.0"
- }
- },
- "co": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
- "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
- },
- "code-point-at": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
- "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
- "dev": true
- },
- "collection-visit": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
- "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
- "requires": {
- "map-visit": "^1.0.0",
- "object-visit": "^1.0.0"
- }
- },
- "color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "requires": {
- "color-name": "1.1.3"
- }
- },
- "color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
- },
- "colors": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz",
- "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==",
- "dev": true
- },
- "combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "requires": {
- "delayed-stream": "~1.0.0"
- }
- },
- "commander": {
- "version": "2.20.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
- "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ=="
- },
- "commondir": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
- "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
- "dev": true
- },
- "component-classes": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/component-classes/-/component-classes-1.2.6.tgz",
- "integrity": "sha1-xkI5TDYYpNiwuJGe/Mu9kw5c1pE=",
- "requires": {
- "component-indexof": "0.0.3"
- }
- },
- "component-emitter": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
- "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
- },
- "component-indexof": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/component-indexof/-/component-indexof-0.0.3.tgz",
- "integrity": "sha1-EdCRMSI5648yyPJa6csAL/6NPCQ="
- },
- "concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
- },
- "concat-stream": {
- "version": "1.6.2",
- "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
- "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
- "dev": true,
- "requires": {
- "buffer-from": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^2.2.2",
- "typedarray": "^0.0.6"
- }
- },
- "confusing-browser-globals": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz",
- "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==",
- "dev": true
- },
- "console-browserify": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
- "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
- "dev": true,
- "requires": {
- "date-now": "^0.1.4"
- }
- },
- "console-control-strings": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
- "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
- "dev": true
- },
- "constants-browserify": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
- "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
- "dev": true
- },
- "contains-path": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
- "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
- "dev": true
- },
- "convert-source-map": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
- "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
- "requires": {
- "safe-buffer": "~5.1.1"
- }
- },
- "copy-concurrently": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
- "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
- "dev": true,
- "requires": {
- "aproba": "^1.1.1",
- "fs-write-stream-atomic": "^1.0.8",
- "iferr": "^0.1.5",
- "mkdirp": "^0.5.1",
- "rimraf": "^2.5.4",
- "run-queue": "^1.0.0"
- }
- },
- "copy-descriptor": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
- "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
- },
- "core-js-compat": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.4.tgz",
- "integrity": "sha512-Z5zbO9f1d0YrJdoaQhphVAnKPimX92D6z8lCGphH89MNRxlL1prI9ExJPqVwP0/kgkQCv8c4GJGT8X16yUncOg==",
- "dev": true,
- "requires": {
- "browserslist": "^4.6.2",
- "core-js-pure": "3.1.4",
- "semver": "^6.1.1"
- },
- "dependencies": {
- "semver": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz",
- "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==",
- "dev": true
- }
- }
- },
- "core-js-pure": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.1.4.tgz",
- "integrity": "sha512-uJ4Z7iPNwiu1foygbcZYJsJs1jiXrTTCvxfLDXNhI/I+NHbSIEyr548y4fcsCEyWY0XgfAG/qqaunJ1SThHenA==",
- "dev": true
- },
- "core-util-is": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
- },
- "cosmiconfig": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
- "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==",
- "requires": {
- "import-fresh": "^2.0.0",
- "is-directory": "^0.3.1",
- "js-yaml": "^3.13.1",
- "parse-json": "^4.0.0"
- },
- "dependencies": {
- "import-fresh": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
- "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
- "requires": {
- "caller-path": "^2.0.0",
- "resolve-from": "^3.0.0"
- }
- },
- "parse-json": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
- "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
- "requires": {
- "error-ex": "^1.3.1",
- "json-parse-better-errors": "^1.0.1"
- }
- },
- "resolve-from": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
- "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g="
- }
- }
- },
- "create-ecdh": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
- "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
- "dev": true,
- "requires": {
- "bn.js": "^4.1.0",
- "elliptic": "^6.0.0"
- }
- },
- "create-hash": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
- "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
- "dev": true,
- "requires": {
- "cipher-base": "^1.0.1",
- "inherits": "^2.0.1",
- "md5.js": "^1.3.4",
- "ripemd160": "^2.0.1",
- "sha.js": "^2.4.0"
- }
- },
- "create-hmac": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
- "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
- "dev": true,
- "requires": {
- "cipher-base": "^1.0.3",
- "create-hash": "^1.1.0",
- "inherits": "^2.0.1",
- "ripemd160": "^2.0.0",
- "safe-buffer": "^5.0.1",
- "sha.js": "^2.4.8"
- }
- },
- "create-jest-runner": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/create-jest-runner/-/create-jest-runner-0.5.3.tgz",
- "integrity": "sha512-a9VY2doMBmzRollJB3Ft3/Y5fBceSWJ4gdyVsg4/d7nP1S4715VG939s2VnITDj79YBmRgKhjGjNRv1c+Kre1g==",
- "dev": true,
- "requires": {
- "chalk": "^2.4.2",
- "jest-worker": "^24.0.0",
- "throat": "^4.1.0"
- }
- },
- "cross-spawn": {
- "version": "6.0.5",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
- "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
- "requires": {
- "nice-try": "^1.0.4",
- "path-key": "^2.0.1",
- "semver": "^5.5.0",
- "shebang-command": "^1.2.0",
- "which": "^1.2.9"
- }
- },
- "crypto-browserify": {
- "version": "3.12.0",
- "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
- "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
- "dev": true,
- "requires": {
- "browserify-cipher": "^1.0.0",
- "browserify-sign": "^4.0.0",
- "create-ecdh": "^4.0.0",
- "create-hash": "^1.1.0",
- "create-hmac": "^1.1.0",
- "diffie-hellman": "^5.0.0",
- "inherits": "^2.0.1",
- "pbkdf2": "^3.0.3",
- "public-encrypt": "^4.0.0",
- "randombytes": "^2.0.0",
- "randomfill": "^1.0.3"
- }
- },
- "css-animation": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.5.0.tgz",
- "integrity": "sha512-hWYoWiOZ7Vr20etzLh3kpWgtC454tW5vn4I6rLANDgpzNSkO7UfOqyCEeaoBSG9CYWQpRkFWTWbWW8o3uZrNLw==",
- "requires": {
- "babel-runtime": "6.x",
- "component-classes": "^1.2.5"
- }
- },
- "css-loader": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.2.0.tgz",
- "integrity": "sha512-QTF3Ud5H7DaZotgdcJjGMvyDj5F3Pn1j/sC6VBEOVp94cbwqyIBdcs/quzj4MC1BKQSrTpQznegH/5giYbhnCQ==",
- "dev": true,
- "requires": {
- "camelcase": "^5.3.1",
- "cssesc": "^3.0.0",
- "icss-utils": "^4.1.1",
- "loader-utils": "^1.2.3",
- "normalize-path": "^3.0.0",
- "postcss": "^7.0.17",
- "postcss-modules-extract-imports": "^2.0.0",
- "postcss-modules-local-by-default": "^3.0.2",
- "postcss-modules-scope": "^2.1.0",
- "postcss-modules-values": "^3.0.0",
- "postcss-value-parser": "^4.0.0",
- "schema-utils": "^2.0.0"
- },
- "dependencies": {
- "schema-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.1.0.tgz",
- "integrity": "sha512-g6SViEZAfGNrToD82ZPUjq52KUPDYc+fN5+g6Euo5mLokl/9Yx14z0Cu4RR1m55HtBXejO0sBt+qw79axN+Fiw==",
- "dev": true,
- "requires": {
- "ajv": "^6.1.0",
- "ajv-keywords": "^3.1.0"
- }
- }
- }
- },
- "css-select": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
- "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
- "dev": true,
- "requires": {
- "boolbase": "~1.0.0",
- "css-what": "2.1",
- "domutils": "1.5.1",
- "nth-check": "~1.0.1"
- }
- },
- "css-what": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
- "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==",
- "dev": true
- },
- "cssesc": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
- "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
- "dev": true
- },
- "cssom": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
- "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
- },
- "cssstyle": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz",
- "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==",
- "requires": {
- "cssom": "0.3.x"
- }
- },
- "csstype": {
- "version": "2.6.6",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz",
- "integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg=="
- },
- "currently-unhandled": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
- "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
- "dev": true,
- "requires": {
- "array-find-index": "^1.0.1"
- }
- },
- "cyclist": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
- "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=",
- "dev": true
- },
- "d3": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/d3/-/d3-4.13.0.tgz",
- "integrity": "sha512-l8c4+0SldjVKLaE2WG++EQlqD7mh/dmQjvi2L2lKPadAVC+TbJC4ci7Uk9bRi+To0+ansgsS0iWfPjD7DBy+FQ==",
- "requires": {
- "d3-array": "1.2.1",
- "d3-axis": "1.0.8",
- "d3-brush": "1.0.4",
- "d3-chord": "1.0.4",
- "d3-collection": "1.0.4",
- "d3-color": "1.0.3",
- "d3-dispatch": "1.0.3",
- "d3-drag": "1.2.1",
- "d3-dsv": "1.0.8",
- "d3-ease": "1.0.3",
- "d3-force": "1.1.0",
- "d3-format": "1.2.2",
- "d3-geo": "1.9.1",
- "d3-hierarchy": "1.1.5",
- "d3-interpolate": "1.1.6",
- "d3-path": "1.0.5",
- "d3-polygon": "1.0.3",
- "d3-quadtree": "1.0.3",
- "d3-queue": "3.0.7",
- "d3-random": "1.1.0",
- "d3-request": "1.0.6",
- "d3-scale": "1.0.7",
- "d3-selection": "1.3.0",
- "d3-shape": "1.2.0",
- "d3-time": "1.0.8",
- "d3-time-format": "2.1.1",
- "d3-timer": "1.0.7",
- "d3-transition": "1.1.1",
- "d3-voronoi": "1.1.2",
- "d3-zoom": "1.7.1"
- },
- "dependencies": {
- "d3-zoom": {
- "version": "1.7.1",
- "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.7.1.tgz",
- "integrity": "sha512-sZHQ55DGq5BZBFGnRshUT8tm2sfhPHFnOlmPbbwTkAoPeVdRTkB4Xsf9GCY0TSHrTD8PeJPZGmP/TpGicwJDJQ==",
- "requires": {
- "d3-dispatch": "1",
- "d3-drag": "1",
- "d3-interpolate": "1",
- "d3-selection": "1",
- "d3-transition": "1"
- }
- }
- }
- },
- "d3-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.1.tgz",
- "integrity": "sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw=="
- },
- "d3-axis": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.8.tgz",
- "integrity": "sha1-MacFoLU15ldZ3hQXOjGTMTfxjvo="
- },
- "d3-brush": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.4.tgz",
- "integrity": "sha1-AMLyOAGfJPbAoZSibUGhUw/+e8Q=",
- "requires": {
- "d3-dispatch": "1",
- "d3-drag": "1",
- "d3-interpolate": "1",
- "d3-selection": "1",
- "d3-transition": "1"
- }
- },
- "d3-chord": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.4.tgz",
- "integrity": "sha1-fexPC6iG9xP+ERxF92NBT290yiw=",
- "requires": {
- "d3-array": "1",
- "d3-path": "1"
- }
- },
- "d3-collection": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.4.tgz",
- "integrity": "sha1-NC39EoN8kJdPM/HMCnha6lcNzcI="
- },
- "d3-color": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.0.3.tgz",
- "integrity": "sha1-vHZD/KjlOoNH4vva/6I2eWtYUJs="
- },
- "d3-dispatch": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.3.tgz",
- "integrity": "sha1-RuFJHqqbWMNY/OW+TovtYm54cfg="
- },
- "d3-drag": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.1.tgz",
- "integrity": "sha512-Cg8/K2rTtzxzrb0fmnYOUeZHvwa4PHzwXOLZZPwtEs2SKLLKLXeYwZKBB+DlOxUvFmarOnmt//cU4+3US2lyyQ==",
- "requires": {
- "d3-dispatch": "1",
- "d3-selection": "1"
- }
- },
- "d3-dsv": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.8.tgz",
- "integrity": "sha512-IVCJpQ+YGe3qu6odkPQI0KPqfxkhbP/oM1XhhE/DFiYmcXKfCRub4KXyiuehV1d4drjWVXHUWx4gHqhdZb6n/A==",
- "requires": {
- "commander": "2",
- "iconv-lite": "0.4",
- "rw": "1"
- }
- },
- "d3-ease": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.3.tgz",
- "integrity": "sha1-aL+8NJM4o4DETYrMT7wzBKotjA4="
- },
- "d3-force": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.1.0.tgz",
- "integrity": "sha512-2HVQz3/VCQs0QeRNZTYb7GxoUCeb6bOzMp/cGcLa87awY9ZsPvXOGeZm0iaGBjXic6I1ysKwMn+g+5jSAdzwcg==",
- "requires": {
- "d3-collection": "1",
- "d3-dispatch": "1",
- "d3-quadtree": "1",
- "d3-timer": "1"
- }
- },
- "d3-format": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.2.2.tgz",
- "integrity": "sha512-zH9CfF/3C8zUI47nsiKfD0+AGDEuM8LwBIP7pBVpyR4l/sKkZqITmMtxRp04rwBrlshIZ17XeFAaovN3++wzkw=="
- },
- "d3-geo": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.9.1.tgz",
- "integrity": "sha512-l9wL/cEQkyZQYXw3xbmLsH3eQ5ij+icNfo4r0GrLa5rOCZR/e/3am45IQ0FvQ5uMsv+77zBRunLc9ufTWSQYFA==",
- "requires": {
- "d3-array": "1"
- }
- },
- "d3-hierarchy": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz",
- "integrity": "sha1-ochFxC+Eoga88cAcAQmOpN2qeiY="
- },
- "d3-interpolate": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.1.6.tgz",
- "integrity": "sha512-mOnv5a+pZzkNIHtw/V6I+w9Lqm9L5bG3OTXPM5A+QO0yyVMQ4W1uZhR+VOJmazaOZXri2ppbiZ5BUNWT0pFM9A==",
- "requires": {
- "d3-color": "1"
- }
- },
- "d3-path": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.5.tgz",
- "integrity": "sha1-JB6xhJvZ6egCHA0KeZ+KDo5EF2Q="
- },
- "d3-polygon": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.3.tgz",
- "integrity": "sha1-FoiOkCZGCTPysXllKtN4Ik04LGI="
- },
- "d3-quadtree": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.3.tgz",
- "integrity": "sha1-rHmH4+I/6AWpkPKOG1DTj8uCJDg="
- },
- "d3-queue": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/d3-queue/-/d3-queue-3.0.7.tgz",
- "integrity": "sha1-yTouVLQXwJWRKdfXP2z31Ckudhg="
- },
- "d3-random": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.0.tgz",
- "integrity": "sha1-ZkLlBsb6OmSFldKyRpeIqNElKdM="
- },
- "d3-request": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/d3-request/-/d3-request-1.0.6.tgz",
- "integrity": "sha512-FJj8ySY6GYuAJHZMaCQ83xEYE4KbkPkmxZ3Hu6zA1xxG2GD+z6P+Lyp+zjdsHf0xEbp2xcluDI50rCS855EQ6w==",
- "requires": {
- "d3-collection": "1",
- "d3-dispatch": "1",
- "d3-dsv": "1",
- "xmlhttprequest": "1"
- }
- },
- "d3-scale": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.7.tgz",
- "integrity": "sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw==",
- "requires": {
- "d3-array": "^1.2.0",
- "d3-collection": "1",
- "d3-color": "1",
- "d3-format": "1",
- "d3-interpolate": "1",
- "d3-time": "1",
- "d3-time-format": "2"
- }
- },
- "d3-selection": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.0.tgz",
- "integrity": "sha512-qgpUOg9tl5CirdqESUAu0t9MU/t3O9klYfGfyKsXEmhyxyzLpzpeh08gaxBUTQw1uXIOkr/30Ut2YRjSSxlmHA=="
- },
- "d3-shape": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.2.0.tgz",
- "integrity": "sha1-RdAVOPBkuv0F6j1tLLdI/YxB93c=",
- "requires": {
- "d3-path": "1"
- }
- },
- "d3-time": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.8.tgz",
- "integrity": "sha512-YRZkNhphZh3KcnBfitvF3c6E0JOFGikHZ4YqD+Lzv83ZHn1/u6yGenRU1m+KAk9J1GnZMnKcrtfvSktlA1DXNQ=="
- },
- "d3-time-format": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.1.tgz",
- "integrity": "sha512-8kAkymq2WMfzW7e+s/IUNAtN/y3gZXGRrdGfo6R8NKPAA85UBTxZg5E61bR6nLwjPjj4d3zywSQe1CkYLPFyrw==",
- "requires": {
- "d3-time": "1"
- }
- },
- "d3-timer": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.7.tgz",
- "integrity": "sha512-vMZXR88XujmG/L5oB96NNKH5lCWwiLM/S2HyyAQLcjWJCloK5shxta4CwOFYLZoY3AWX73v8Lgv4cCAdWtRmOA=="
- },
- "d3-tip": {
- "version": "0.9.1",
- "resolved": "https://registry.npmjs.org/d3-tip/-/d3-tip-0.9.1.tgz",
- "integrity": "sha512-EVBfG9d+HnjIoyVXfhpytWxlF59JaobwizqMX9EBXtsFmJytjwHeYiUs74ldHQjE7S9vzfKTx2LCtvUrIbuFYg==",
- "requires": {
- "d3-collection": "^1.0.4",
- "d3-selection": "^1.3.0"
- }
- },
- "d3-transition": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.1.1.tgz",
- "integrity": "sha512-xeg8oggyQ+y5eb4J13iDgKIjUcEfIOZs2BqV/eEmXm2twx80wTzJ4tB4vaZ5BKfz7XsI/DFmQL5me6O27/5ykQ==",
- "requires": {
- "d3-color": "1",
- "d3-dispatch": "1",
- "d3-ease": "1",
- "d3-interpolate": "1",
- "d3-selection": "^1.1.0",
- "d3-timer": "1"
- }
- },
- "d3-voronoi": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz",
- "integrity": "sha1-Fodmfo8TotFYyAwUgMWinLDYlzw="
- },
- "d3-zoom": {
- "version": "1.8.3",
- "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz",
- "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==",
- "requires": {
- "d3-dispatch": "1",
- "d3-drag": "1",
- "d3-interpolate": "1",
- "d3-selection": "1",
- "d3-transition": "1"
- }
- },
- "damerau-levenshtein": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz",
- "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==",
- "dev": true
- },
- "dashdash": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
- "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
- "requires": {
- "assert-plus": "^1.0.0"
- }
- },
- "data-urls": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz",
- "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==",
- "requires": {
- "abab": "^2.0.0",
- "whatwg-mimetype": "^2.2.0",
- "whatwg-url": "^7.0.0"
- },
- "dependencies": {
- "whatwg-url": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz",
- "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==",
- "requires": {
- "lodash.sortby": "^4.7.0",
- "tr46": "^1.0.1",
- "webidl-conversions": "^4.0.2"
- }
- }
- }
- },
- "date-now": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
- "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
- "dev": true
- },
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "requires": {
- "ms": "2.0.0"
- }
- },
- "decamelize": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
- "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
- },
- "decode-uri-component": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
- "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
- },
- "deep-is": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
- "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
- },
- "define-properties": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
- "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
- "requires": {
- "object-keys": "^1.0.12"
- }
- },
- "define-property": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
- "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
- "requires": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- },
- "dependencies": {
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- }
- }
- },
- "delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
- },
- "delegates": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
- "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
- "dev": true
- },
- "des.js": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
- "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
- "dev": true,
- "requires": {
- "inherits": "^2.0.1",
- "minimalistic-assert": "^1.0.0"
- }
- },
- "detect-file": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
- "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
- "dev": true
- },
- "detect-newline": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz",
- "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I="
- },
- "diff": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
- "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
- "dev": true
- },
- "diff-match-patch": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.4.tgz",
- "integrity": "sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg=="
- },
- "diff-sequences": {
- "version": "24.3.0",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.3.0.tgz",
- "integrity": "sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==",
- "dev": true
- },
- "diffie-hellman": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
- "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
- "dev": true,
- "requires": {
- "bn.js": "^4.1.0",
- "miller-rabin": "^4.0.0",
- "randombytes": "^2.0.0"
- }
- },
- "discontinuous-range": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
- "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=",
- "dev": true
- },
- "doctrine": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
- "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2"
- }
- },
- "dom-align": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.9.0.tgz",
- "integrity": "sha512-HvPfXISxoU7dKrbqS4vIFa1hx88wD7VdKaZ7sHWeow8y76tuzsxXkiPGbeilemLXrTd9cWbPqR4MOl4y3dkcXA=="
- },
- "dom-helpers": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
- "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
- "requires": {
- "@babel/runtime": "^7.1.2"
- }
- },
- "dom-serializer": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
- "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==",
- "requires": {
- "domelementtype": "^1.3.0",
- "entities": "^1.1.1"
- }
- },
- "domain-browser": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
- "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
- "dev": true
- },
- "domelementtype": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
- "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
- },
- "domexception": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
- "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
- "requires": {
- "webidl-conversions": "^4.0.2"
- }
- },
- "domhandler": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
- "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
- "requires": {
- "domelementtype": "1"
- }
- },
- "domutils": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
- "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
- "requires": {
- "dom-serializer": "0",
- "domelementtype": "1"
- }
- },
- "duplexify": {
- "version": "3.7.1",
- "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
- "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
- "dev": true,
- "requires": {
- "end-of-stream": "^1.0.0",
- "inherits": "^2.0.1",
- "readable-stream": "^2.0.0",
- "stream-shift": "^1.0.0"
- }
- },
- "ecc-jsbn": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
- "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
- "requires": {
- "jsbn": "~0.1.0",
- "safer-buffer": "^2.1.0"
- }
- },
- "electron-to-chromium": {
- "version": "1.3.199",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.199.tgz",
- "integrity": "sha512-gachlDdHSK47s0N2e58GH9HMC6Z4ip0SfmYUa5iEbE50AKaOUXysaJnXMfKj0xB245jWbYcyFSH+th3rqsF8hA==",
- "dev": true
- },
- "elliptic": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz",
- "integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==",
- "dev": true,
- "requires": {
- "bn.js": "^4.4.0",
- "brorand": "^1.0.1",
- "hash.js": "^1.0.0",
- "hmac-drbg": "^1.0.0",
- "inherits": "^2.0.1",
- "minimalistic-assert": "^1.0.0",
- "minimalistic-crypto-utils": "^1.0.0"
- }
- },
- "emoji-regex": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
- "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
- },
- "emojis-list": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
- "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
- "dev": true
- },
- "end-of-stream": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
- "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
- "requires": {
- "once": "^1.4.0"
- }
- },
- "enhanced-resolve": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz",
- "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "memory-fs": "^0.4.0",
- "tapable": "^1.0.0"
- }
- },
- "entities": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
- "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
- },
- "enzyme": {
- "version": "3.10.0",
- "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.10.0.tgz",
- "integrity": "sha512-p2yy9Y7t/PFbPoTvrWde7JIYB2ZyGC+NgTNbVEGvZ5/EyoYSr9aG/2rSbVvyNvMHEhw9/dmGUJHWtfQIEiX9pg==",
- "dev": true,
- "requires": {
- "array.prototype.flat": "^1.2.1",
- "cheerio": "^1.0.0-rc.2",
- "function.prototype.name": "^1.1.0",
- "has": "^1.0.3",
- "html-element-map": "^1.0.0",
- "is-boolean-object": "^1.0.0",
- "is-callable": "^1.1.4",
- "is-number-object": "^1.0.3",
- "is-regex": "^1.0.4",
- "is-string": "^1.0.4",
- "is-subset": "^0.1.1",
- "lodash.escape": "^4.0.1",
- "lodash.isequal": "^4.5.0",
- "object-inspect": "^1.6.0",
- "object-is": "^1.0.1",
- "object.assign": "^4.1.0",
- "object.entries": "^1.0.4",
- "object.values": "^1.0.4",
- "raf": "^3.4.0",
- "rst-selector-parser": "^2.2.3",
- "string.prototype.trim": "^1.1.2"
- }
- },
- "enzyme-adapter-react-16": {
- "version": "1.14.0",
- "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.14.0.tgz",
- "integrity": "sha512-7PcOF7pb4hJUvjY7oAuPGpq3BmlCig3kxXGi2kFx0YzJHppqX1K8IIV9skT1IirxXlu8W7bneKi+oQ10QRnhcA==",
- "dev": true,
- "requires": {
- "enzyme-adapter-utils": "^1.12.0",
- "has": "^1.0.3",
- "object.assign": "^4.1.0",
- "object.values": "^1.1.0",
- "prop-types": "^15.7.2",
- "react-is": "^16.8.6",
- "react-test-renderer": "^16.0.0-0",
- "semver": "^5.7.0"
- }
- },
- "enzyme-adapter-utils": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.12.0.tgz",
- "integrity": "sha512-wkZvE0VxcFx/8ZsBw0iAbk3gR1d9hK447ebnSYBf95+r32ezBq+XDSAvRErkc4LZosgH8J7et7H7/7CtUuQfBA==",
- "dev": true,
- "requires": {
- "airbnb-prop-types": "^2.13.2",
- "function.prototype.name": "^1.1.0",
- "object.assign": "^4.1.0",
- "object.fromentries": "^2.0.0",
- "prop-types": "^15.7.2",
- "semver": "^5.6.0"
- }
- },
- "errno": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
- "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
- "dev": true,
- "requires": {
- "prr": "~1.0.1"
- }
- },
- "error-ex": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
- "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
- "requires": {
- "is-arrayish": "^0.2.1"
- }
- },
- "es-abstract": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz",
- "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==",
- "requires": {
- "es-to-primitive": "^1.2.0",
- "function-bind": "^1.1.1",
- "has": "^1.0.3",
- "is-callable": "^1.1.4",
- "is-regex": "^1.0.4",
- "object-keys": "^1.0.12"
- }
- },
- "es-to-primitive": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
- "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
- "requires": {
- "is-callable": "^1.1.4",
- "is-date-object": "^1.0.1",
- "is-symbol": "^1.0.2"
- }
- },
- "escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
- },
- "escodegen": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz",
- "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==",
- "requires": {
- "esprima": "^3.1.3",
- "estraverse": "^4.2.0",
- "esutils": "^2.0.2",
- "optionator": "^0.8.1",
- "source-map": "~0.6.1"
- },
- "dependencies": {
- "esprima": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
- "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "optional": true
- }
- }
- },
- "eslint": {
- "version": "6.5.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.5.1.tgz",
- "integrity": "sha512-32h99BoLYStT1iq1v2P9uwpyznQ4M2jRiFB6acitKz52Gqn+vPaMDUTB1bYi1WN4Nquj2w+t+bimYUG83DC55A==",
- "dev": true,
- "requires": {
- "@babel/code-frame": "^7.0.0",
- "ajv": "^6.10.0",
- "chalk": "^2.1.0",
- "cross-spawn": "^6.0.5",
- "debug": "^4.0.1",
- "doctrine": "^3.0.0",
- "eslint-scope": "^5.0.0",
- "eslint-utils": "^1.4.2",
- "eslint-visitor-keys": "^1.1.0",
- "espree": "^6.1.1",
- "esquery": "^1.0.1",
- "esutils": "^2.0.2",
- "file-entry-cache": "^5.0.1",
- "functional-red-black-tree": "^1.0.1",
- "glob-parent": "^5.0.0",
- "globals": "^11.7.0",
- "ignore": "^4.0.6",
- "import-fresh": "^3.0.0",
- "imurmurhash": "^0.1.4",
- "inquirer": "^6.4.1",
- "is-glob": "^4.0.0",
- "js-yaml": "^3.13.1",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "levn": "^0.3.0",
- "lodash": "^4.17.14",
- "minimatch": "^3.0.4",
- "mkdirp": "^0.5.1",
- "natural-compare": "^1.4.0",
- "optionator": "^0.8.2",
- "progress": "^2.0.0",
- "regexpp": "^2.0.1",
- "semver": "^6.1.2",
- "strip-ansi": "^5.2.0",
- "strip-json-comments": "^3.0.1",
- "table": "^5.2.3",
- "text-table": "^0.2.0",
- "v8-compile-cache": "^2.0.3"
- },
- "dependencies": {
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
- "dev": true,
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "eslint-scope": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
- "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
- "dev": true,
- "requires": {
- "esrecurse": "^4.1.0",
- "estraverse": "^4.1.1"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true
- }
- }
- },
- "eslint-config-airbnb": {
- "version": "18.0.1",
- "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.0.1.tgz",
- "integrity": "sha512-hLb/ccvW4grVhvd6CT83bECacc+s4Z3/AEyWQdIT2KeTsG9dR7nx1gs7Iw4tDmGKozCNHFn4yZmRm3Tgy+XxyQ==",
- "dev": true,
- "requires": {
- "eslint-config-airbnb-base": "^14.0.0",
- "object.assign": "^4.1.0",
- "object.entries": "^1.1.0"
- }
- },
- "eslint-config-airbnb-base": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.0.0.tgz",
- "integrity": "sha512-2IDHobw97upExLmsebhtfoD3NAKhV4H0CJWP3Uprd/uk+cHuWYOczPVxQ8PxLFUAw7o3Th1RAU8u1DoUpr+cMA==",
- "dev": true,
- "requires": {
- "confusing-browser-globals": "^1.0.7",
- "object.assign": "^4.1.0",
- "object.entries": "^1.1.0"
- }
- },
- "eslint-import-resolver-node": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz",
- "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==",
- "dev": true,
- "requires": {
- "debug": "^2.6.9",
- "resolve": "^1.5.0"
- }
- },
- "eslint-module-utils": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz",
- "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==",
- "dev": true,
- "requires": {
- "debug": "^2.6.8",
- "pkg-dir": "^2.0.0"
- },
- "dependencies": {
- "find-up": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
- "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
- "dev": true,
- "requires": {
- "locate-path": "^2.0.0"
- }
- },
- "locate-path": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
- "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
- "dev": true,
- "requires": {
- "p-locate": "^2.0.0",
- "path-exists": "^3.0.0"
- }
- },
- "p-limit": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
- "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
- "dev": true,
- "requires": {
- "p-try": "^1.0.0"
- }
- },
- "p-locate": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
- "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
- "dev": true,
- "requires": {
- "p-limit": "^1.1.0"
- }
- },
- "p-try": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
- "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
- "dev": true
- },
- "pkg-dir": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
- "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
- "dev": true,
- "requires": {
- "find-up": "^2.1.0"
- }
- }
- }
- },
- "eslint-plugin-import": {
- "version": "2.18.2",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz",
- "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==",
- "dev": true,
- "requires": {
- "array-includes": "^3.0.3",
- "contains-path": "^0.1.0",
- "debug": "^2.6.9",
- "doctrine": "1.5.0",
- "eslint-import-resolver-node": "^0.3.2",
- "eslint-module-utils": "^2.4.0",
- "has": "^1.0.3",
- "minimatch": "^3.0.4",
- "object.values": "^1.1.0",
- "read-pkg-up": "^2.0.0",
- "resolve": "^1.11.0"
- },
- "dependencies": {
- "doctrine": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
- "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2",
- "isarray": "^1.0.0"
- }
- }
- }
- },
- "eslint-plugin-jest": {
- "version": "22.15.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.15.0.tgz",
- "integrity": "sha512-hgnPbSqAIcLLS9ePb12hNHTRkXnkVaCfOwCt2pzQ8KpOKPWGA4HhLMaFN38NBa/0uvLfrZpcIRjT+6tMAfr58Q==",
- "dev": true,
- "requires": {
- "@typescript-eslint/experimental-utils": "^1.13.0"
- }
- },
- "eslint-plugin-jsx-a11y": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz",
- "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==",
- "dev": true,
- "requires": {
- "@babel/runtime": "^7.4.5",
- "aria-query": "^3.0.0",
- "array-includes": "^3.0.3",
- "ast-types-flow": "^0.0.7",
- "axobject-query": "^2.0.2",
- "damerau-levenshtein": "^1.0.4",
- "emoji-regex": "^7.0.2",
- "has": "^1.0.3",
- "jsx-ast-utils": "^2.2.1"
- }
- },
- "eslint-plugin-react": {
- "version": "7.15.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.15.1.tgz",
- "integrity": "sha512-YotSItgMPwLGlr3df44MGVyXnHkmKcpkHTzpte3QwJtocr3nFqCXCuoxFZeBtnT8RHdj038NlTvam3dcAFrMcA==",
- "dev": true,
- "requires": {
- "array-includes": "^3.0.3",
- "doctrine": "^2.1.0",
- "has": "^1.0.3",
- "jsx-ast-utils": "^2.2.1",
- "object.entries": "^1.1.0",
- "object.fromentries": "^2.0.0",
- "object.values": "^1.1.0",
- "prop-types": "^15.7.2",
- "resolve": "^1.12.0"
- },
- "dependencies": {
- "doctrine": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
- "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2"
- }
- },
- "resolve": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
- "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
- "dev": true,
- "requires": {
- "path-parse": "^1.0.6"
- }
- }
- }
- },
- "eslint-plugin-react-hooks": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz",
- "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==",
- "dev": true
- },
- "eslint-scope": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
- "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
- "dev": true,
- "requires": {
- "esrecurse": "^4.1.0",
- "estraverse": "^4.1.1"
- }
- },
- "eslint-utils": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
- "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
- "dev": true,
- "requires": {
- "eslint-visitor-keys": "^1.0.0"
- }
- },
- "eslint-visitor-keys": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
- "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
- "dev": true
- },
- "espree": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz",
- "integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==",
- "dev": true,
- "requires": {
- "acorn": "^7.0.0",
- "acorn-jsx": "^5.0.2",
- "eslint-visitor-keys": "^1.1.0"
- }
- },
- "esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
- },
- "esquery": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
- "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
- "dev": true,
- "requires": {
- "estraverse": "^4.0.0"
- }
- },
- "esrecurse": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
- "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
- "dev": true,
- "requires": {
- "estraverse": "^4.1.0"
- }
- },
- "estraverse": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
- "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
- },
- "esutils": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
- "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
- },
- "events": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
- "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==",
- "dev": true
- },
- "evp_bytestokey": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
- "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
- "dev": true,
- "requires": {
- "md5.js": "^1.3.4",
- "safe-buffer": "^5.1.1"
- }
- },
- "exec-sh": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz",
- "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg=="
- },
- "execa": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
- "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
- "requires": {
- "cross-spawn": "^6.0.0",
- "get-stream": "^4.0.0",
- "is-stream": "^1.1.0",
- "npm-run-path": "^2.0.0",
- "p-finally": "^1.0.0",
- "signal-exit": "^3.0.0",
- "strip-eof": "^1.0.0"
- }
- },
- "exit": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
- "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw="
- },
- "expand-brackets": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
- "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
- "requires": {
- "debug": "^2.3.3",
- "define-property": "^0.2.5",
- "extend-shallow": "^2.0.1",
- "posix-character-classes": "^0.1.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "dependencies": {
- "define-property": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
- "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
- "requires": {
- "is-descriptor": "^0.1.0"
- }
- },
- "extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "requires": {
- "is-extendable": "^0.1.0"
- }
- }
- }
- },
- "expand-tilde": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
- "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
- "dev": true,
- "requires": {
- "homedir-polyfill": "^1.0.1"
- }
- },
- "expect": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/expect/-/expect-24.8.0.tgz",
- "integrity": "sha512-/zYvP8iMDrzaaxHVa724eJBCKqSHmO0FA7EDkBiRHxg6OipmMn1fN+C8T9L9K8yr7UONkOifu6+LLH+z76CnaA==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.8.0",
- "ansi-styles": "^3.2.0",
- "jest-get-type": "^24.8.0",
- "jest-matcher-utils": "^24.8.0",
- "jest-message-util": "^24.8.0",
- "jest-regex-util": "^24.3.0"
- }
- },
- "extend": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
- },
- "extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "requires": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- },
- "dependencies": {
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- }
- }
- },
- "external-editor": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
- "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
- "dev": true,
- "requires": {
- "chardet": "^0.7.0",
- "iconv-lite": "^0.4.24",
- "tmp": "^0.0.33"
- }
- },
- "extglob": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
- "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
- "requires": {
- "array-unique": "^0.3.2",
- "define-property": "^1.0.0",
- "expand-brackets": "^2.1.4",
- "extend-shallow": "^2.0.1",
- "fragment-cache": "^0.2.1",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "dependencies": {
- "define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "requires": {
- "is-descriptor": "^1.0.0"
- }
- },
- "extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "requires": {
- "is-extendable": "^0.1.0"
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- }
- }
- },
- "extsprintf": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
- "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
- },
- "fast-deep-equal": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
- "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
- },
- "fast-json-stable-stringify": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
- "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
- },
- "fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
- },
- "fb-watchman": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz",
- "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=",
- "requires": {
- "bser": "^2.0.0"
- }
- },
- "figgy-pudding": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
- "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
- "dev": true
- },
- "figures": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
- "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
- "dev": true,
- "requires": {
- "escape-string-regexp": "^1.0.5"
- }
- },
- "file-entry-cache": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
- "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
- "dev": true,
- "requires": {
- "flat-cache": "^2.0.1"
- }
- },
- "fill-range": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
- "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
- "requires": {
- "extend-shallow": "^2.0.1",
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1",
- "to-regex-range": "^2.1.0"
- },
- "dependencies": {
- "extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "requires": {
- "is-extendable": "^0.1.0"
- }
- }
- }
- },
- "find-cache-dir": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
- "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
- "dev": true,
- "requires": {
- "commondir": "^1.0.1",
- "make-dir": "^2.0.0",
- "pkg-dir": "^3.0.0"
- }
- },
- "find-root": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
- "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
- },
- "find-up": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
- "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
- "requires": {
- "locate-path": "^3.0.0"
- }
- },
- "findup-sync": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
- "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==",
- "dev": true,
- "requires": {
- "detect-file": "^1.0.0",
- "is-glob": "^4.0.0",
- "micromatch": "^3.0.4",
- "resolve-dir": "^1.0.1"
- }
- },
- "flat-cache": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
- "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
- "dev": true,
- "requires": {
- "flatted": "^2.0.0",
- "rimraf": "2.6.3",
- "write": "1.0.3"
- }
- },
- "flatted": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
- "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
- "dev": true
- },
- "flush-write-stream": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
- "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
- "dev": true,
- "requires": {
- "inherits": "^2.0.3",
- "readable-stream": "^2.3.6"
- }
- },
- "for-in": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
- "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
- },
- "forever-agent": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
- "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
- },
- "form-data": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
- "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
- "requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.6",
- "mime-types": "^2.1.12"
- }
- },
- "fragment-cache": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
- "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
- "requires": {
- "map-cache": "^0.2.2"
- }
- },
- "from2": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
- "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
- "dev": true,
- "requires": {
- "inherits": "^2.0.1",
- "readable-stream": "^2.0.0"
- }
- },
- "fs-write-stream-atomic": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
- "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "iferr": "^0.1.5",
- "imurmurhash": "^0.1.4",
- "readable-stream": "1 || 2"
- }
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
- },
- "fsevents": {
- "version": "1.2.9",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz",
- "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==",
- "optional": true,
- "requires": {
- "nan": "^2.12.1",
- "node-pre-gyp": "^0.12.0"
- },
- "dependencies": {
- "abbrev": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
- "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
- "optional": true
- },
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "optional": true
- },
- "aproba": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
- "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
- "optional": true
- },
- "are-we-there-yet": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
- "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
- "optional": true,
- "requires": {
- "delegates": "^1.0.0",
- "readable-stream": "^2.0.6"
- }
- },
- "balanced-match": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
- "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
- "optional": true
- },
- "brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "optional": true,
- "requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "chownr": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
- "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==",
- "optional": true
- },
- "code-point-at": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
- "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
- "optional": true
- },
- "concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
- "optional": true
- },
- "console-control-strings": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
- "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
- "optional": true
- },
- "core-util-is": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
- "optional": true
- },
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
- "optional": true,
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "deep-extend": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
- "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
- "optional": true
- },
- "delegates": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
- "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
- "optional": true
- },
- "detect-libc": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
- "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
- "optional": true
- },
- "fs-minipass": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
- "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
- "optional": true,
- "requires": {
- "minipass": "^2.2.1"
- }
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
- "optional": true
- },
- "gauge": {
- "version": "2.7.4",
- "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
- "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
- "optional": true,
- "requires": {
- "aproba": "^1.0.3",
- "console-control-strings": "^1.0.0",
- "has-unicode": "^2.0.0",
- "object-assign": "^4.1.0",
- "signal-exit": "^3.0.0",
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1",
- "wide-align": "^1.1.0"
- }
- },
- "glob": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
- "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
- "optional": true,
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- },
- "has-unicode": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
- "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
- "optional": true
- },
- "iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "optional": true,
- "requires": {
- "safer-buffer": ">= 2.1.2 < 3"
- }
- },
- "ignore-walk": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz",
- "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
- "optional": true,
- "requires": {
- "minimatch": "^3.0.4"
- }
- },
- "inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "optional": true,
- "requires": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "inherits": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
- "optional": true
- },
- "ini": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
- "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
- "optional": true
- },
- "is-fullwidth-code-point": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "optional": true,
- "requires": {
- "number-is-nan": "^1.0.0"
- }
- },
- "isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
- "optional": true
- },
- "minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "optional": true,
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "minimist": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
- "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
- "optional": true
- },
- "minipass": {
- "version": "2.3.5",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
- "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
- "optional": true,
- "requires": {
- "safe-buffer": "^5.1.2",
- "yallist": "^3.0.0"
- }
- },
- "minizlib": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz",
- "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
- "optional": true,
- "requires": {
- "minipass": "^2.2.1"
- }
- },
- "mkdirp": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
- "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
- "optional": true,
- "requires": {
- "minimist": "0.0.8"
- }
- },
- "ms": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
- "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
- "optional": true
- },
- "needle": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz",
- "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==",
- "optional": true,
- "requires": {
- "debug": "^4.1.0",
- "iconv-lite": "^0.4.4",
- "sax": "^1.2.4"
- }
- },
- "node-pre-gyp": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz",
- "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==",
- "optional": true,
- "requires": {
- "detect-libc": "^1.0.2",
- "mkdirp": "^0.5.1",
- "needle": "^2.2.1",
- "nopt": "^4.0.1",
- "npm-packlist": "^1.1.6",
- "npmlog": "^4.0.2",
- "rc": "^1.2.7",
- "rimraf": "^2.6.1",
- "semver": "^5.3.0",
- "tar": "^4"
- }
- },
- "nopt": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
- "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
- "optional": true,
- "requires": {
- "abbrev": "1",
- "osenv": "^0.1.4"
- }
- },
- "npm-bundled": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz",
- "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==",
- "optional": true
- },
- "npm-packlist": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz",
- "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==",
- "optional": true,
- "requires": {
- "ignore-walk": "^3.0.1",
- "npm-bundled": "^1.0.1"
- }
- },
- "npmlog": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
- "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
- "optional": true,
- "requires": {
- "are-we-there-yet": "~1.1.2",
- "console-control-strings": "~1.1.0",
- "gauge": "~2.7.3",
- "set-blocking": "~2.0.0"
- }
- },
- "number-is-nan": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
- "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
- "optional": true
- },
- "object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
- "optional": true
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "optional": true,
- "requires": {
- "wrappy": "1"
- }
- },
- "os-homedir": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
- "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
- "optional": true
- },
- "os-tmpdir": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
- "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
- "optional": true
- },
- "osenv": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
- "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
- "optional": true,
- "requires": {
- "os-homedir": "^1.0.0",
- "os-tmpdir": "^1.0.0"
- }
- },
- "path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
- "optional": true
- },
- "process-nextick-args": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
- "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
- "optional": true
- },
- "rc": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
- "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
- "optional": true,
- "requires": {
- "deep-extend": "^0.6.0",
- "ini": "~1.3.0",
- "minimist": "^1.2.0",
- "strip-json-comments": "~2.0.1"
- },
- "dependencies": {
- "minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
- "optional": true
- }
- }
- },
- "readable-stream": {
- "version": "2.3.6",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
- "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
- "optional": true,
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "rimraf": {
- "version": "2.6.3",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
- "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
- "optional": true,
- "requires": {
- "glob": "^7.1.3"
- }
- },
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "optional": true
- },
- "safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "optional": true
- },
- "sax": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
- "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
- "optional": true
- },
- "semver": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
- "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
- "optional": true
- },
- "set-blocking": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
- "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
- "optional": true
- },
- "signal-exit": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
- "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
- "optional": true
- },
- "string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "optional": true,
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- }
- },
- "string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "optional": true,
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- },
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "optional": true,
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- },
- "strip-json-comments": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
- "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
- "optional": true
- },
- "tar": {
- "version": "4.4.8",
- "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz",
- "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
- "optional": true,
- "requires": {
- "chownr": "^1.1.1",
- "fs-minipass": "^1.2.5",
- "minipass": "^2.3.4",
- "minizlib": "^1.1.1",
- "mkdirp": "^0.5.0",
- "safe-buffer": "^5.1.2",
- "yallist": "^3.0.2"
- }
- },
- "util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
- "optional": true
- },
- "wide-align": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
- "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
- "optional": true,
- "requires": {
- "string-width": "^1.0.2 || 2"
- }
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
- "optional": true
- },
- "yallist": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
- "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
- "optional": true
- }
- }
- },
- "fstream": {
- "version": "1.0.12",
- "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
- "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "inherits": "~2.0.0",
- "mkdirp": ">=0.5 0",
- "rimraf": "2"
- }
- },
- "function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
- },
- "function.prototype.name": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.1.tgz",
- "integrity": "sha512-e1NzkiJuw6xqVH7YSdiW/qDHebcmMhPNe6w+4ZYYEg0VA+LaLzx37RimbPLuonHhYGFGPx1ME2nSi74JiaCr/Q==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.3",
- "function-bind": "^1.1.1",
- "functions-have-names": "^1.1.1",
- "is-callable": "^1.1.4"
- }
- },
- "functional-red-black-tree": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
- "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
- "dev": true
- },
- "functions-have-names": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.1.1.tgz",
- "integrity": "sha512-U0kNHUoxwPNPWOJaMG7Z00d4a/qZVrFtzWJRaK8V9goaVOCXBSQSJpt3MYGNtkScKEBKovxLjnNdC9MlXwo5Pw==",
- "dev": true
- },
- "gauge": {
- "version": "2.7.4",
- "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
- "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
- "dev": true,
- "requires": {
- "aproba": "^1.0.3",
- "console-control-strings": "^1.0.0",
- "has-unicode": "^2.0.0",
- "object-assign": "^4.1.0",
- "signal-exit": "^3.0.0",
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1",
- "wide-align": "^1.1.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true
- },
- "is-fullwidth-code-point": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "dev": true,
- "requires": {
- "number-is-nan": "^1.0.0"
- }
- },
- "string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "dev": true,
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- }
- },
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- }
- }
- },
- "gaze": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
- "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
- "dev": true,
- "requires": {
- "globule": "^1.0.0"
- }
- },
- "get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
- },
- "get-stdin": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
- "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
- "dev": true
- },
- "get-stream": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
- "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
- "requires": {
- "pump": "^3.0.0"
- }
- },
- "get-value": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
- "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
- },
- "getpass": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
- "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
- "requires": {
- "assert-plus": "^1.0.0"
- }
- },
- "glob": {
- "version": "7.1.4",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
- "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- },
- "glob-parent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz",
- "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==",
- "dev": true,
- "requires": {
- "is-glob": "^4.0.1"
- }
- },
- "global-modules": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
- "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
- "dev": true,
- "requires": {
- "global-prefix": "^3.0.0"
- },
- "dependencies": {
- "global-prefix": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
- "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
- "dev": true,
- "requires": {
- "ini": "^1.3.5",
- "kind-of": "^6.0.2",
- "which": "^1.3.1"
- }
- }
- }
- },
- "global-prefix": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
- "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=",
- "dev": true,
- "requires": {
- "expand-tilde": "^2.0.2",
- "homedir-polyfill": "^1.0.1",
- "ini": "^1.3.4",
- "is-windows": "^1.0.1",
- "which": "^1.2.14"
- }
- },
- "globals": {
- "version": "11.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
- "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
- },
- "globule": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
- "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
- "dev": true,
- "requires": {
- "glob": "~7.1.1",
- "lodash": "~4.17.10",
- "minimatch": "~3.0.2"
- }
- },
- "graceful-fs": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz",
- "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg=="
- },
- "growly": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
- "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
- "dev": true
- },
- "gud": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
- "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw=="
- },
- "handlebars": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.3.1.tgz",
- "integrity": "sha512-c0HoNHzDiHpBt4Kqe99N8tdLPKAnGCQ73gYMPWtAYM4PwGnf7xl8PBUHJqh9ijlzt2uQKaSRxbXRt+rZ7M2/kA==",
- "dev": true,
- "requires": {
- "neo-async": "^2.6.0",
- "optimist": "^0.6.1",
- "source-map": "^0.6.1",
- "uglify-js": "^3.1.4"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
- }
- }
- },
- "har-schema": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
- "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
- },
- "har-validator": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
- "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
- "requires": {
- "ajv": "^6.5.5",
- "har-schema": "^2.0.0"
- }
- },
- "has": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
- "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
- "requires": {
- "function-bind": "^1.1.1"
- }
- },
- "has-ansi": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
- "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
- "dev": true,
- "requires": {
- "ansi-regex": "^2.0.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true
- }
- }
- },
- "has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
- },
- "has-symbols": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
- "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
- },
- "has-unicode": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
- "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
- "dev": true
- },
- "has-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
- "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
- "requires": {
- "get-value": "^2.0.6",
- "has-values": "^1.0.0",
- "isobject": "^3.0.0"
- }
- },
- "has-values": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
- "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
- "requires": {
- "is-number": "^3.0.0",
- "kind-of": "^4.0.0"
- },
- "dependencies": {
- "kind-of": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
- "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "hash-base": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
- "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
- "dev": true,
- "requires": {
- "inherits": "^2.0.1",
- "safe-buffer": "^5.0.1"
- }
- },
- "hash.js": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
- "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
- "dev": true,
- "requires": {
- "inherits": "^2.0.3",
- "minimalistic-assert": "^1.0.1"
- }
- },
- "history": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz",
- "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==",
- "requires": {
- "@babel/runtime": "^7.1.2",
- "loose-envify": "^1.2.0",
- "resolve-pathname": "^2.2.0",
- "tiny-invariant": "^1.0.2",
- "tiny-warning": "^1.0.0",
- "value-equal": "^0.4.0"
- }
- },
- "hmac-drbg": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
- "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
- "dev": true,
- "requires": {
- "hash.js": "^1.0.3",
- "minimalistic-assert": "^1.0.0",
- "minimalistic-crypto-utils": "^1.0.1"
- }
- },
- "hoist-non-react-statics": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz",
- "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==",
- "requires": {
- "react-is": "^16.7.0"
- }
- },
- "homedir-polyfill": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
- "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
- "dev": true,
- "requires": {
- "parse-passwd": "^1.0.0"
- }
- },
- "hosted-git-info": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
- "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w=="
- },
- "html-element-map": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.1.0.tgz",
- "integrity": "sha512-iqiG3dTZmy+uUaTmHarTL+3/A2VW9ox/9uasKEZC+R/wAtUrTcRlXPSaPqsnWPfIu8wqn09jQNwMRqzL54jSYA==",
- "dev": true,
- "requires": {
- "array-filter": "^1.0.0"
- }
- },
- "html-encoding-sniffer": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
- "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==",
- "requires": {
- "whatwg-encoding": "^1.0.1"
- }
- },
- "htmlparser2": {
- "version": "3.10.1",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
- "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
- "requires": {
- "domelementtype": "^1.3.1",
- "domhandler": "^2.3.0",
- "domutils": "^1.5.1",
- "entities": "^1.1.1",
- "inherits": "^2.0.1",
- "readable-stream": "^3.1.1"
- },
- "dependencies": {
- "readable-stream": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
- "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
- "requires": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- }
- }
- }
- },
- "http-signature": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
- "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
- "requires": {
- "assert-plus": "^1.0.0",
- "jsprim": "^1.2.2",
- "sshpk": "^1.7.0"
- }
- },
- "https-browserify": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
- "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
- "dev": true
- },
- "iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "requires": {
- "safer-buffer": ">= 2.1.2 < 3"
- }
- },
- "icss-utils": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz",
- "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==",
- "dev": true,
- "requires": {
- "postcss": "^7.0.14"
- }
- },
- "ieee754": {
- "version": "1.1.13",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
- "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
- "dev": true
- },
- "iferr": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
- "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
- "dev": true
- },
- "ignore": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
- "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
- "dev": true
- },
- "immer": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/immer/-/immer-3.2.0.tgz",
- "integrity": "sha512-+a2R8z9eELHst6aht++nzVzJ8LJ+Hsg49qttfg9Kc/vmoxEdPXw5/rV6+4DYWGgnq+B36KbLr4OTaGtS9mDjtg=="
- },
- "import-fresh": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz",
- "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==",
- "dev": true,
- "requires": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- }
- },
- "import-local": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
- "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==",
- "dev": true,
- "requires": {
- "pkg-dir": "^3.0.0",
- "resolve-cwd": "^2.0.0"
- }
- },
- "imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
- },
- "in-publish": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
- "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=",
- "dev": true
- },
- "indent-string": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
- "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
- "dev": true,
- "requires": {
- "repeating": "^2.0.0"
- }
- },
- "indexes-of": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
- "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=",
- "dev": true
- },
- "infer-owner": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
- "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
- "dev": true
- },
- "inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "requires": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
- },
- "ini": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
- "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
- "dev": true
- },
- "inquirer": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
- "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
- "dev": true,
- "requires": {
- "ansi-escapes": "^3.2.0",
- "chalk": "^2.4.2",
- "cli-cursor": "^2.1.0",
- "cli-width": "^2.0.0",
- "external-editor": "^3.0.3",
- "figures": "^2.0.0",
- "lodash": "^4.17.12",
- "mute-stream": "0.0.7",
- "run-async": "^2.2.0",
- "rxjs": "^6.4.0",
- "string-width": "^2.1.0",
- "strip-ansi": "^5.1.0",
- "through": "^2.3.6"
- }
- },
- "interpret": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
- "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==",
- "dev": true
- },
- "invariant": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
- "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
- "requires": {
- "loose-envify": "^1.0.0"
- }
- },
- "invert-kv": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
- "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
- "dev": true
- },
- "is-accessor-descriptor": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
- "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "is-arrayish": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
- "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
- },
- "is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
- "requires": {
- "binary-extensions": "^2.0.0"
- }
- },
- "is-boolean-object": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.0.tgz",
- "integrity": "sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=",
- "dev": true
- },
- "is-buffer": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
- "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
- },
- "is-callable": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
- "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA=="
- },
- "is-ci": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
- "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
- "requires": {
- "ci-info": "^2.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
- "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "is-date-object": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
- "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
- },
- "is-descriptor": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
- "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
- "requires": {
- "is-accessor-descriptor": "^0.1.6",
- "is-data-descriptor": "^0.1.4",
- "kind-of": "^5.0.0"
- },
- "dependencies": {
- "kind-of": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
- "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="
- }
- }
- },
- "is-directory": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
- "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE="
- },
- "is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
- },
- "is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
- "dev": true
- },
- "is-finite": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
- "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
- "dev": true,
- "requires": {
- "number-is-nan": "^1.0.0"
- }
- },
- "is-fullwidth-code-point": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
- "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
- },
- "is-generator-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
- "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ=="
- },
- "is-glob": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
- "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
- "dev": true,
- "requires": {
- "is-extglob": "^2.1.1"
- }
- },
- "is-number": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
- "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "is-number-object": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.3.tgz",
- "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=",
- "dev": true
- },
- "is-plain-object": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
- "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
- "requires": {
- "isobject": "^3.0.1"
- }
- },
- "is-promise": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
- "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
- "dev": true
- },
- "is-regex": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
- "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
- "requires": {
- "has": "^1.0.1"
- }
- },
- "is-stream": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
- "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
- },
- "is-string": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.4.tgz",
- "integrity": "sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=",
- "dev": true
- },
- "is-subset": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz",
- "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=",
- "dev": true
- },
- "is-symbol": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
- "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
- "requires": {
- "has-symbols": "^1.0.0"
- }
- },
- "is-typedarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
- },
- "is-utf8": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
- "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
- "dev": true
- },
- "is-windows": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
- "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="
- },
- "is-wsl": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
- "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
- "dev": true
- },
- "isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
- },
- "isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
- },
- "isobject": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
- "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
- },
- "isstream": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
- "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
- },
- "istanbul-lib-coverage": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
- "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA=="
- },
- "istanbul-lib-instrument": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz",
- "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==",
- "requires": {
- "@babel/generator": "^7.4.0",
- "@babel/parser": "^7.4.3",
- "@babel/template": "^7.4.0",
- "@babel/traverse": "^7.4.3",
- "@babel/types": "^7.4.0",
- "istanbul-lib-coverage": "^2.0.5",
- "semver": "^6.0.0"
- },
- "dependencies": {
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
- }
- }
- },
- "istanbul-lib-report": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz",
- "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==",
- "dev": true,
- "requires": {
- "istanbul-lib-coverage": "^2.0.5",
- "make-dir": "^2.1.0",
- "supports-color": "^6.1.0"
- },
- "dependencies": {
- "supports-color": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
- "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
- "dev": true,
- "requires": {
- "has-flag": "^3.0.0"
- }
- }
- }
- },
- "istanbul-lib-source-maps": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz",
- "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==",
- "dev": true,
- "requires": {
- "debug": "^4.1.1",
- "istanbul-lib-coverage": "^2.0.5",
- "make-dir": "^2.1.0",
- "rimraf": "^2.6.3",
- "source-map": "^0.6.1"
- },
- "dependencies": {
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
- "dev": true,
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
- }
- }
- },
- "istanbul-reports": {
- "version": "2.2.6",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz",
- "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==",
- "dev": true,
- "requires": {
- "handlebars": "^4.1.2"
- }
- },
- "jest": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest/-/jest-24.9.0.tgz",
- "integrity": "sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==",
- "dev": true,
- "requires": {
- "import-local": "^2.0.0",
- "jest-cli": "^24.9.0"
- },
- "dependencies": {
- "@jest/core": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.9.0.tgz",
- "integrity": "sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==",
- "dev": true,
- "requires": {
- "@jest/console": "^24.7.1",
- "@jest/reporters": "^24.9.0",
- "@jest/test-result": "^24.9.0",
- "@jest/transform": "^24.9.0",
- "@jest/types": "^24.9.0",
- "ansi-escapes": "^3.0.0",
- "chalk": "^2.0.1",
- "exit": "^0.1.2",
- "graceful-fs": "^4.1.15",
- "jest-changed-files": "^24.9.0",
- "jest-config": "^24.9.0",
- "jest-haste-map": "^24.9.0",
- "jest-message-util": "^24.9.0",
- "jest-regex-util": "^24.3.0",
- "jest-resolve": "^24.9.0",
- "jest-resolve-dependencies": "^24.9.0",
- "jest-runner": "^24.9.0",
- "jest-runtime": "^24.9.0",
- "jest-snapshot": "^24.9.0",
- "jest-util": "^24.9.0",
- "jest-validate": "^24.9.0",
- "jest-watcher": "^24.9.0",
- "micromatch": "^3.1.10",
- "p-each-series": "^1.0.0",
- "realpath-native": "^1.1.0",
- "rimraf": "^2.5.4",
- "slash": "^2.0.0",
- "strip-ansi": "^5.0.0"
- }
- },
- "@jest/environment": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz",
- "integrity": "sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==",
- "dev": true,
- "requires": {
- "@jest/fake-timers": "^24.9.0",
- "@jest/transform": "^24.9.0",
- "@jest/types": "^24.9.0",
- "jest-mock": "^24.9.0"
- }
- },
- "@jest/fake-timers": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz",
- "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.9.0",
- "jest-message-util": "^24.9.0",
- "jest-mock": "^24.9.0"
- }
- },
- "@jest/reporters": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.9.0.tgz",
- "integrity": "sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==",
- "dev": true,
- "requires": {
- "@jest/environment": "^24.9.0",
- "@jest/test-result": "^24.9.0",
- "@jest/transform": "^24.9.0",
- "@jest/types": "^24.9.0",
- "chalk": "^2.0.1",
- "exit": "^0.1.2",
- "glob": "^7.1.2",
- "istanbul-lib-coverage": "^2.0.2",
- "istanbul-lib-instrument": "^3.0.1",
- "istanbul-lib-report": "^2.0.4",
- "istanbul-lib-source-maps": "^3.0.1",
- "istanbul-reports": "^2.2.6",
- "jest-haste-map": "^24.9.0",
- "jest-resolve": "^24.9.0",
- "jest-runtime": "^24.9.0",
- "jest-util": "^24.9.0",
- "jest-worker": "^24.6.0",
- "node-notifier": "^5.4.2",
- "slash": "^2.0.0",
- "source-map": "^0.6.0",
- "string-length": "^2.0.0"
- }
- },
- "@jest/source-map": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz",
- "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==",
- "dev": true,
- "requires": {
- "callsites": "^3.0.0",
- "graceful-fs": "^4.1.15",
- "source-map": "^0.6.0"
- }
- },
- "@jest/test-result": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz",
- "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==",
- "dev": true,
- "requires": {
- "@jest/console": "^24.9.0",
- "@jest/types": "^24.9.0",
- "@types/istanbul-lib-coverage": "^2.0.0"
- },
- "dependencies": {
- "@jest/console": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
- "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==",
- "dev": true,
- "requires": {
- "@jest/source-map": "^24.9.0",
- "chalk": "^2.0.1",
- "slash": "^2.0.0"
- }
- }
- }
- },
- "@jest/test-sequencer": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz",
- "integrity": "sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==",
- "dev": true,
- "requires": {
- "@jest/test-result": "^24.9.0",
- "jest-haste-map": "^24.9.0",
- "jest-runner": "^24.9.0",
- "jest-runtime": "^24.9.0"
- }
- },
- "@jest/transform": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz",
- "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==",
- "dev": true,
- "requires": {
- "@babel/core": "^7.1.0",
- "@jest/types": "^24.9.0",
- "babel-plugin-istanbul": "^5.1.0",
- "chalk": "^2.0.1",
- "convert-source-map": "^1.4.0",
- "fast-json-stable-stringify": "^2.0.0",
- "graceful-fs": "^4.1.15",
- "jest-haste-map": "^24.9.0",
- "jest-regex-util": "^24.9.0",
- "jest-util": "^24.9.0",
- "micromatch": "^3.1.10",
- "pirates": "^4.0.1",
- "realpath-native": "^1.1.0",
- "slash": "^2.0.0",
- "source-map": "^0.6.1",
- "write-file-atomic": "2.4.1"
- },
- "dependencies": {
- "jest-regex-util": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz",
- "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==",
- "dev": true
- }
- }
- },
- "@jest/types": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz",
- "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==",
- "dev": true,
- "requires": {
- "@types/istanbul-lib-coverage": "^2.0.0",
- "@types/istanbul-reports": "^1.1.1",
- "@types/yargs": "^13.0.0"
- }
- },
- "@types/yargs": {
- "version": "13.0.3",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz",
- "integrity": "sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ==",
- "dev": true,
- "requires": {
- "@types/yargs-parser": "*"
- }
- },
- "ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
- "dev": true
- },
- "babel-jest": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz",
- "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==",
- "dev": true,
- "requires": {
- "@jest/transform": "^24.9.0",
- "@jest/types": "^24.9.0",
- "@types/babel__core": "^7.1.0",
- "babel-plugin-istanbul": "^5.1.0",
- "babel-preset-jest": "^24.9.0",
- "chalk": "^2.4.2",
- "slash": "^2.0.0"
- }
- },
- "babel-plugin-jest-hoist": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz",
- "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==",
- "dev": true,
- "requires": {
- "@types/babel__traverse": "^7.0.6"
- }
- },
- "babel-preset-jest": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz",
- "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==",
- "dev": true,
- "requires": {
- "@babel/plugin-syntax-object-rest-spread": "^7.0.0",
- "babel-plugin-jest-hoist": "^24.9.0"
- }
- },
- "diff-sequences": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz",
- "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==",
- "dev": true
- },
- "expect": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz",
- "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.9.0",
- "ansi-styles": "^3.2.0",
- "jest-get-type": "^24.9.0",
- "jest-matcher-utils": "^24.9.0",
- "jest-message-util": "^24.9.0",
- "jest-regex-util": "^24.9.0"
- },
- "dependencies": {
- "jest-regex-util": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz",
- "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==",
- "dev": true
- }
- }
- },
- "jest-changed-files": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz",
- "integrity": "sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.9.0",
- "execa": "^1.0.0",
- "throat": "^4.0.0"
- }
- },
- "jest-cli": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.9.0.tgz",
- "integrity": "sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==",
- "dev": true,
- "requires": {
- "@jest/core": "^24.9.0",
- "@jest/test-result": "^24.9.0",
- "@jest/types": "^24.9.0",
- "chalk": "^2.0.1",
- "exit": "^0.1.2",
- "import-local": "^2.0.0",
- "is-ci": "^2.0.0",
- "jest-config": "^24.9.0",
- "jest-util": "^24.9.0",
- "jest-validate": "^24.9.0",
- "prompts": "^2.0.1",
- "realpath-native": "^1.1.0",
- "yargs": "^13.3.0"
- }
- },
- "jest-config": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz",
- "integrity": "sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==",
- "dev": true,
- "requires": {
- "@babel/core": "^7.1.0",
- "@jest/test-sequencer": "^24.9.0",
- "@jest/types": "^24.9.0",
- "babel-jest": "^24.9.0",
- "chalk": "^2.0.1",
- "glob": "^7.1.1",
- "jest-environment-jsdom": "^24.9.0",
- "jest-environment-node": "^24.9.0",
- "jest-get-type": "^24.9.0",
- "jest-jasmine2": "^24.9.0",
- "jest-regex-util": "^24.3.0",
- "jest-resolve": "^24.9.0",
- "jest-util": "^24.9.0",
- "jest-validate": "^24.9.0",
- "micromatch": "^3.1.10",
- "pretty-format": "^24.9.0",
- "realpath-native": "^1.1.0"
- }
- },
- "jest-diff": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz",
- "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==",
- "dev": true,
- "requires": {
- "chalk": "^2.0.1",
- "diff-sequences": "^24.9.0",
- "jest-get-type": "^24.9.0",
- "pretty-format": "^24.9.0"
- }
- },
- "jest-each": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz",
- "integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.9.0",
- "chalk": "^2.0.1",
- "jest-get-type": "^24.9.0",
- "jest-util": "^24.9.0",
- "pretty-format": "^24.9.0"
- }
- },
- "jest-environment-jsdom": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz",
- "integrity": "sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==",
- "dev": true,
- "requires": {
- "@jest/environment": "^24.9.0",
- "@jest/fake-timers": "^24.9.0",
- "@jest/types": "^24.9.0",
- "jest-mock": "^24.9.0",
- "jest-util": "^24.9.0",
- "jsdom": "^11.5.1"
- }
- },
- "jest-environment-node": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.9.0.tgz",
- "integrity": "sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==",
- "dev": true,
- "requires": {
- "@jest/environment": "^24.9.0",
- "@jest/fake-timers": "^24.9.0",
- "@jest/types": "^24.9.0",
- "jest-mock": "^24.9.0",
- "jest-util": "^24.9.0"
- }
- },
- "jest-get-type": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz",
- "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==",
- "dev": true
- },
- "jest-haste-map": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz",
- "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.9.0",
- "anymatch": "^2.0.0",
- "fb-watchman": "^2.0.0",
- "fsevents": "^1.2.7",
- "graceful-fs": "^4.1.15",
- "invariant": "^2.2.4",
- "jest-serializer": "^24.9.0",
- "jest-util": "^24.9.0",
- "jest-worker": "^24.9.0",
- "micromatch": "^3.1.10",
- "sane": "^4.0.3",
- "walker": "^1.0.7"
- },
- "dependencies": {
- "jest-worker": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz",
- "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==",
- "dev": true,
- "requires": {
- "merge-stream": "^2.0.0",
- "supports-color": "^6.1.0"
- }
- }
- }
- },
- "jest-jasmine2": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz",
- "integrity": "sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==",
- "dev": true,
- "requires": {
- "@babel/traverse": "^7.1.0",
- "@jest/environment": "^24.9.0",
- "@jest/test-result": "^24.9.0",
- "@jest/types": "^24.9.0",
- "chalk": "^2.0.1",
- "co": "^4.6.0",
- "expect": "^24.9.0",
- "is-generator-fn": "^2.0.0",
- "jest-each": "^24.9.0",
- "jest-matcher-utils": "^24.9.0",
- "jest-message-util": "^24.9.0",
- "jest-runtime": "^24.9.0",
- "jest-snapshot": "^24.9.0",
- "jest-util": "^24.9.0",
- "pretty-format": "^24.9.0",
- "throat": "^4.0.0"
- }
- },
- "jest-leak-detector": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz",
- "integrity": "sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==",
- "dev": true,
- "requires": {
- "jest-get-type": "^24.9.0",
- "pretty-format": "^24.9.0"
- }
- },
- "jest-matcher-utils": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz",
- "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==",
- "dev": true,
- "requires": {
- "chalk": "^2.0.1",
- "jest-diff": "^24.9.0",
- "jest-get-type": "^24.9.0",
- "pretty-format": "^24.9.0"
- }
- },
- "jest-message-util": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz",
- "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==",
- "dev": true,
- "requires": {
- "@babel/code-frame": "^7.0.0",
- "@jest/test-result": "^24.9.0",
- "@jest/types": "^24.9.0",
- "@types/stack-utils": "^1.0.1",
- "chalk": "^2.0.1",
- "micromatch": "^3.1.10",
- "slash": "^2.0.0",
- "stack-utils": "^1.0.1"
- }
- },
- "jest-mock": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz",
- "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.9.0"
- }
- },
- "jest-resolve": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.9.0.tgz",
- "integrity": "sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.9.0",
- "browser-resolve": "^1.11.3",
- "chalk": "^2.0.1",
- "jest-pnp-resolver": "^1.2.1",
- "realpath-native": "^1.1.0"
- }
- },
- "jest-resolve-dependencies": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz",
- "integrity": "sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.9.0",
- "jest-regex-util": "^24.3.0",
- "jest-snapshot": "^24.9.0"
- }
- },
- "jest-runner": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.9.0.tgz",
- "integrity": "sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==",
- "dev": true,
- "requires": {
- "@jest/console": "^24.7.1",
- "@jest/environment": "^24.9.0",
- "@jest/test-result": "^24.9.0",
- "@jest/types": "^24.9.0",
- "chalk": "^2.4.2",
- "exit": "^0.1.2",
- "graceful-fs": "^4.1.15",
- "jest-config": "^24.9.0",
- "jest-docblock": "^24.3.0",
- "jest-haste-map": "^24.9.0",
- "jest-jasmine2": "^24.9.0",
- "jest-leak-detector": "^24.9.0",
- "jest-message-util": "^24.9.0",
- "jest-resolve": "^24.9.0",
- "jest-runtime": "^24.9.0",
- "jest-util": "^24.9.0",
- "jest-worker": "^24.6.0",
- "source-map-support": "^0.5.6",
- "throat": "^4.0.0"
- }
- },
- "jest-runtime": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz",
- "integrity": "sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==",
- "dev": true,
- "requires": {
- "@jest/console": "^24.7.1",
- "@jest/environment": "^24.9.0",
- "@jest/source-map": "^24.3.0",
- "@jest/transform": "^24.9.0",
- "@jest/types": "^24.9.0",
- "@types/yargs": "^13.0.0",
- "chalk": "^2.0.1",
- "exit": "^0.1.2",
- "glob": "^7.1.3",
- "graceful-fs": "^4.1.15",
- "jest-config": "^24.9.0",
- "jest-haste-map": "^24.9.0",
- "jest-message-util": "^24.9.0",
- "jest-mock": "^24.9.0",
- "jest-regex-util": "^24.3.0",
- "jest-resolve": "^24.9.0",
- "jest-snapshot": "^24.9.0",
- "jest-util": "^24.9.0",
- "jest-validate": "^24.9.0",
- "realpath-native": "^1.1.0",
- "slash": "^2.0.0",
- "strip-bom": "^3.0.0",
- "yargs": "^13.3.0"
- }
- },
- "jest-serializer": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz",
- "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==",
- "dev": true
- },
- "jest-snapshot": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.9.0.tgz",
- "integrity": "sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==",
- "dev": true,
- "requires": {
- "@babel/types": "^7.0.0",
- "@jest/types": "^24.9.0",
- "chalk": "^2.0.1",
- "expect": "^24.9.0",
- "jest-diff": "^24.9.0",
- "jest-get-type": "^24.9.0",
- "jest-matcher-utils": "^24.9.0",
- "jest-message-util": "^24.9.0",
- "jest-resolve": "^24.9.0",
- "mkdirp": "^0.5.1",
- "natural-compare": "^1.4.0",
- "pretty-format": "^24.9.0",
- "semver": "^6.2.0"
- }
- },
- "jest-util": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz",
- "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==",
- "dev": true,
- "requires": {
- "@jest/console": "^24.9.0",
- "@jest/fake-timers": "^24.9.0",
- "@jest/source-map": "^24.9.0",
- "@jest/test-result": "^24.9.0",
- "@jest/types": "^24.9.0",
- "callsites": "^3.0.0",
- "chalk": "^2.0.1",
- "graceful-fs": "^4.1.15",
- "is-ci": "^2.0.0",
- "mkdirp": "^0.5.1",
- "slash": "^2.0.0",
- "source-map": "^0.6.0"
- },
- "dependencies": {
- "@jest/console": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
- "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==",
- "dev": true,
- "requires": {
- "@jest/source-map": "^24.9.0",
- "chalk": "^2.0.1",
- "slash": "^2.0.0"
- }
- }
- }
- },
- "jest-validate": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz",
- "integrity": "sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.9.0",
- "camelcase": "^5.3.1",
- "chalk": "^2.0.1",
- "jest-get-type": "^24.9.0",
- "leven": "^3.1.0",
- "pretty-format": "^24.9.0"
- }
- },
- "jest-watcher": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.9.0.tgz",
- "integrity": "sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==",
- "dev": true,
- "requires": {
- "@jest/test-result": "^24.9.0",
- "@jest/types": "^24.9.0",
- "@types/yargs": "^13.0.0",
- "ansi-escapes": "^3.0.0",
- "chalk": "^2.0.1",
- "jest-util": "^24.9.0",
- "string-length": "^2.0.0"
- }
- },
- "leven": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
- "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
- "dev": true
- },
- "merge-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
- "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
- "dev": true
- },
- "node-notifier": {
- "version": "5.4.3",
- "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz",
- "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==",
- "dev": true,
- "requires": {
- "growly": "^1.3.0",
- "is-wsl": "^1.1.0",
- "semver": "^5.5.0",
- "shellwords": "^0.1.1",
- "which": "^1.3.0"
- },
- "dependencies": {
- "semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
- "dev": true
- }
- }
- },
- "pretty-format": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz",
- "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.9.0",
- "ansi-regex": "^4.0.0",
- "ansi-styles": "^3.2.0",
- "react-is": "^16.8.4"
- }
- },
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
- },
- "string-width": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
- "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
- "dev": true,
- "requires": {
- "emoji-regex": "^7.0.1",
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^5.1.0"
- }
- },
- "supports-color": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
- "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
- "dev": true,
- "requires": {
- "has-flag": "^3.0.0"
- }
- },
- "yargs": {
- "version": "13.3.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
- "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==",
- "dev": true,
- "requires": {
- "cliui": "^5.0.0",
- "find-up": "^3.0.0",
- "get-caller-file": "^2.0.1",
- "require-directory": "^2.1.1",
- "require-main-filename": "^2.0.0",
- "set-blocking": "^2.0.0",
- "string-width": "^3.0.0",
- "which-module": "^2.0.0",
- "y18n": "^4.0.0",
- "yargs-parser": "^13.1.1"
- }
- }
- }
- },
- "jest-changed-files": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.8.0.tgz",
- "integrity": "sha512-qgANC1Yrivsq+UrLXsvJefBKVoCsKB0Hv+mBb6NMjjZ90wwxCDmU3hsCXBya30cH+LnPYjwgcU65i6yJ5Nfuug==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.8.0",
- "execa": "^1.0.0",
- "throat": "^4.0.0"
- }
- },
- "jest-cli": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.8.0.tgz",
- "integrity": "sha512-+p6J00jSMPQ116ZLlHJJvdf8wbjNbZdeSX9ptfHX06/MSNaXmKihQzx5vQcw0q2G6JsdVkUIdWbOWtSnaYs3yA==",
- "dev": true,
- "requires": {
- "@jest/core": "^24.8.0",
- "@jest/test-result": "^24.8.0",
- "@jest/types": "^24.8.0",
- "chalk": "^2.0.1",
- "exit": "^0.1.2",
- "import-local": "^2.0.0",
- "is-ci": "^2.0.0",
- "jest-config": "^24.8.0",
- "jest-util": "^24.8.0",
- "jest-validate": "^24.8.0",
- "prompts": "^2.0.1",
- "realpath-native": "^1.1.0",
- "yargs": "^12.0.2"
- },
- "dependencies": {
- "cliui": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
- "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
- "dev": true,
- "requires": {
- "string-width": "^2.1.1",
- "strip-ansi": "^4.0.0",
- "wrap-ansi": "^2.0.0"
- }
- },
- "get-caller-file": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
- "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
- "dev": true
- },
- "is-fullwidth-code-point": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "dev": true,
- "requires": {
- "number-is-nan": "^1.0.0"
- }
- },
- "require-main-filename": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
- "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
- "dev": true
- },
- "strip-ansi": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^3.0.0"
- }
- },
- "wrap-ansi": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
- "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
- "dev": true,
- "requires": {
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true
- },
- "string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "dev": true,
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- }
- },
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- }
- }
- },
- "yargs": {
- "version": "12.0.5",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
- "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
- "dev": true,
- "requires": {
- "cliui": "^4.0.0",
- "decamelize": "^1.2.0",
- "find-up": "^3.0.0",
- "get-caller-file": "^1.0.1",
- "os-locale": "^3.0.0",
- "require-directory": "^2.1.1",
- "require-main-filename": "^1.0.1",
- "set-blocking": "^2.0.0",
- "string-width": "^2.0.0",
- "which-module": "^2.0.0",
- "y18n": "^3.2.1 || ^4.0.0",
- "yargs-parser": "^11.1.1"
- }
- },
- "yargs-parser": {
- "version": "11.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
- "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
- "dev": true,
- "requires": {
- "camelcase": "^5.0.0",
- "decamelize": "^1.2.0"
- }
- }
- }
- },
- "jest-config": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.8.0.tgz",
- "integrity": "sha512-Czl3Nn2uEzVGsOeaewGWoDPD8GStxCpAe0zOYs2x2l0fZAgPbCr3uwUkgNKV3LwE13VXythM946cd5rdGkkBZw==",
- "dev": true,
- "requires": {
- "@babel/core": "^7.1.0",
- "@jest/test-sequencer": "^24.8.0",
- "@jest/types": "^24.8.0",
- "babel-jest": "^24.8.0",
- "chalk": "^2.0.1",
- "glob": "^7.1.1",
- "jest-environment-jsdom": "^24.8.0",
- "jest-environment-node": "^24.8.0",
- "jest-get-type": "^24.8.0",
- "jest-jasmine2": "^24.8.0",
- "jest-regex-util": "^24.3.0",
- "jest-resolve": "^24.8.0",
- "jest-util": "^24.8.0",
- "jest-validate": "^24.8.0",
- "micromatch": "^3.1.10",
- "pretty-format": "^24.8.0",
- "realpath-native": "^1.1.0"
- }
- },
- "jest-diff": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.8.0.tgz",
- "integrity": "sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g==",
- "dev": true,
- "requires": {
- "chalk": "^2.0.1",
- "diff-sequences": "^24.3.0",
- "jest-get-type": "^24.8.0",
- "pretty-format": "^24.8.0"
- }
- },
- "jest-docblock": {
- "version": "24.3.0",
- "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.3.0.tgz",
- "integrity": "sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg==",
- "requires": {
- "detect-newline": "^2.1.0"
- }
- },
- "jest-each": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.8.0.tgz",
- "integrity": "sha512-NrwK9gaL5+XgrgoCsd9svsoWdVkK4gnvyhcpzd6m487tXHqIdYeykgq3MKI1u4I+5Zf0tofr70at9dWJDeb+BA==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.8.0",
- "chalk": "^2.0.1",
- "jest-get-type": "^24.8.0",
- "jest-util": "^24.8.0",
- "pretty-format": "^24.8.0"
- }
- },
- "jest-environment-jsdom": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz",
- "integrity": "sha512-qbvgLmR7PpwjoFjM/sbuqHJt/NCkviuq9vus9NBn/76hhSidO+Z6Bn9tU8friecegbJL8gzZQEMZBQlFWDCwAQ==",
- "dev": true,
- "requires": {
- "@jest/environment": "^24.8.0",
- "@jest/fake-timers": "^24.8.0",
- "@jest/types": "^24.8.0",
- "jest-mock": "^24.8.0",
- "jest-util": "^24.8.0",
- "jsdom": "^11.5.1"
- }
- },
- "jest-environment-node": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.8.0.tgz",
- "integrity": "sha512-vIGUEScd1cdDgR6sqn2M08sJTRLQp6Dk/eIkCeO4PFHxZMOgy+uYLPMC4ix3PEfM5Au/x3uQ/5Tl0DpXXZsJ/Q==",
- "dev": true,
- "requires": {
- "@jest/environment": "^24.8.0",
- "@jest/fake-timers": "^24.8.0",
- "@jest/types": "^24.8.0",
- "jest-mock": "^24.8.0",
- "jest-util": "^24.8.0"
- }
- },
- "jest-get-type": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.8.0.tgz",
- "integrity": "sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ==",
- "dev": true
- },
- "jest-haste-map": {
- "version": "24.8.1",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.8.1.tgz",
- "integrity": "sha512-SwaxMGVdAZk3ernAx2Uv2sorA7jm3Kx+lR0grp6rMmnY06Kn/urtKx1LPN2mGTea4fCT38impYT28FfcLUhX0g==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.8.0",
- "anymatch": "^2.0.0",
- "fb-watchman": "^2.0.0",
- "fsevents": "^1.2.7",
- "graceful-fs": "^4.1.15",
- "invariant": "^2.2.4",
- "jest-serializer": "^24.4.0",
- "jest-util": "^24.8.0",
- "jest-worker": "^24.6.0",
- "micromatch": "^3.1.10",
- "sane": "^4.0.3",
- "walker": "^1.0.7"
- }
- },
- "jest-jasmine2": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.8.0.tgz",
- "integrity": "sha512-cEky88npEE5LKd5jPpTdDCLvKkdyklnaRycBXL6GNmpxe41F0WN44+i7lpQKa/hcbXaQ+rc9RMaM4dsebrYong==",
- "dev": true,
- "requires": {
- "@babel/traverse": "^7.1.0",
- "@jest/environment": "^24.8.0",
- "@jest/test-result": "^24.8.0",
- "@jest/types": "^24.8.0",
- "chalk": "^2.0.1",
- "co": "^4.6.0",
- "expect": "^24.8.0",
- "is-generator-fn": "^2.0.0",
- "jest-each": "^24.8.0",
- "jest-matcher-utils": "^24.8.0",
- "jest-message-util": "^24.8.0",
- "jest-runtime": "^24.8.0",
- "jest-snapshot": "^24.8.0",
- "jest-util": "^24.8.0",
- "pretty-format": "^24.8.0",
- "throat": "^4.0.0"
- }
- },
- "jest-leak-detector": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz",
- "integrity": "sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==",
- "requires": {
- "jest-get-type": "^24.9.0",
- "pretty-format": "^24.9.0"
- },
- "dependencies": {
- "@jest/types": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz",
- "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==",
- "requires": {
- "@types/istanbul-lib-coverage": "^2.0.0",
- "@types/istanbul-reports": "^1.1.1",
- "@types/yargs": "^13.0.0"
- }
- },
- "@types/yargs": {
- "version": "13.0.3",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz",
- "integrity": "sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ==",
- "requires": {
- "@types/yargs-parser": "*"
- }
- },
- "ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
- },
- "jest-get-type": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz",
- "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q=="
- },
- "pretty-format": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz",
- "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==",
- "requires": {
- "@jest/types": "^24.9.0",
- "ansi-regex": "^4.0.0",
- "ansi-styles": "^3.2.0",
- "react-is": "^16.8.4"
- }
- }
- }
- },
- "jest-matcher-utils": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz",
- "integrity": "sha512-lex1yASY51FvUuHgm0GOVj7DCYEouWSlIYmCW7APSqB9v8mXmKSn5+sWVF0MhuASG0bnYY106/49JU1FZNl5hw==",
- "dev": true,
- "requires": {
- "chalk": "^2.0.1",
- "jest-diff": "^24.8.0",
- "jest-get-type": "^24.8.0",
- "pretty-format": "^24.8.0"
- }
- },
- "jest-message-util": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.8.0.tgz",
- "integrity": "sha512-p2k71rf/b6ns8btdB0uVdljWo9h0ovpnEe05ZKWceQGfXYr4KkzgKo3PBi8wdnd9OtNh46VpNIJynUn/3MKm1g==",
- "dev": true,
- "requires": {
- "@babel/code-frame": "^7.0.0",
- "@jest/test-result": "^24.8.0",
- "@jest/types": "^24.8.0",
- "@types/stack-utils": "^1.0.1",
- "chalk": "^2.0.1",
- "micromatch": "^3.1.10",
- "slash": "^2.0.0",
- "stack-utils": "^1.0.1"
- }
- },
- "jest-mock": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.8.0.tgz",
- "integrity": "sha512-6kWugwjGjJw+ZkK4mDa0Df3sDlUTsV47MSrT0nGQ0RBWJbpODDQ8MHDVtGtUYBne3IwZUhtB7elxHspU79WH3A==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.8.0"
- }
- },
- "jest-pnp-resolver": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz",
- "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ=="
- },
- "jest-regex-util": {
- "version": "24.3.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz",
- "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==",
- "dev": true
- },
- "jest-resolve": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.8.0.tgz",
- "integrity": "sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.8.0",
- "browser-resolve": "^1.11.3",
- "chalk": "^2.0.1",
- "jest-pnp-resolver": "^1.2.1",
- "realpath-native": "^1.1.0"
- }
- },
- "jest-resolve-dependencies": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.8.0.tgz",
- "integrity": "sha512-hyK1qfIf/krV+fSNyhyJeq3elVMhK9Eijlwy+j5jqmZ9QsxwKBiP6qukQxaHtK8k6zql/KYWwCTQ+fDGTIJauw==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.8.0",
- "jest-regex-util": "^24.3.0",
- "jest-snapshot": "^24.8.0"
- }
- },
- "jest-runner": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.9.0.tgz",
- "integrity": "sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==",
- "requires": {
- "@jest/console": "^24.7.1",
- "@jest/environment": "^24.9.0",
- "@jest/test-result": "^24.9.0",
- "@jest/types": "^24.9.0",
- "chalk": "^2.4.2",
- "exit": "^0.1.2",
- "graceful-fs": "^4.1.15",
- "jest-config": "^24.9.0",
- "jest-docblock": "^24.3.0",
- "jest-haste-map": "^24.9.0",
- "jest-jasmine2": "^24.9.0",
- "jest-leak-detector": "^24.9.0",
- "jest-message-util": "^24.9.0",
- "jest-resolve": "^24.9.0",
- "jest-runtime": "^24.9.0",
- "jest-util": "^24.9.0",
- "jest-worker": "^24.6.0",
- "source-map-support": "^0.5.6",
- "throat": "^4.0.0"
- },
- "dependencies": {
- "@jest/environment": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz",
- "integrity": "sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==",
- "requires": {
- "@jest/fake-timers": "^24.9.0",
- "@jest/transform": "^24.9.0",
- "@jest/types": "^24.9.0",
- "jest-mock": "^24.9.0"
- }
- },
- "@jest/fake-timers": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz",
- "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==",
- "requires": {
- "@jest/types": "^24.9.0",
- "jest-message-util": "^24.9.0",
- "jest-mock": "^24.9.0"
- }
- },
- "@jest/source-map": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz",
- "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==",
- "requires": {
- "callsites": "^3.0.0",
- "graceful-fs": "^4.1.15",
- "source-map": "^0.6.0"
- }
- },
- "@jest/test-result": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz",
- "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==",
- "requires": {
- "@jest/console": "^24.9.0",
- "@jest/types": "^24.9.0",
- "@types/istanbul-lib-coverage": "^2.0.0"
- },
- "dependencies": {
- "@jest/console": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
- "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==",
- "requires": {
- "@jest/source-map": "^24.9.0",
- "chalk": "^2.0.1",
- "slash": "^2.0.0"
- }
- }
- }
- },
- "@jest/test-sequencer": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz",
- "integrity": "sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==",
- "requires": {
- "@jest/test-result": "^24.9.0",
- "jest-haste-map": "^24.9.0",
- "jest-runner": "^24.9.0",
- "jest-runtime": "^24.9.0"
- }
- },
- "@jest/transform": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz",
- "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==",
- "requires": {
- "@babel/core": "^7.1.0",
- "@jest/types": "^24.9.0",
- "babel-plugin-istanbul": "^5.1.0",
- "chalk": "^2.0.1",
- "convert-source-map": "^1.4.0",
- "fast-json-stable-stringify": "^2.0.0",
- "graceful-fs": "^4.1.15",
- "jest-haste-map": "^24.9.0",
- "jest-regex-util": "^24.9.0",
- "jest-util": "^24.9.0",
- "micromatch": "^3.1.10",
- "pirates": "^4.0.1",
- "realpath-native": "^1.1.0",
- "slash": "^2.0.0",
- "source-map": "^0.6.1",
- "write-file-atomic": "2.4.1"
- }
- },
- "@jest/types": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz",
- "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==",
- "requires": {
- "@types/istanbul-lib-coverage": "^2.0.0",
- "@types/istanbul-reports": "^1.1.1",
- "@types/yargs": "^13.0.0"
- }
- },
- "@types/yargs": {
- "version": "13.0.3",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz",
- "integrity": "sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ==",
- "requires": {
- "@types/yargs-parser": "*"
- }
- },
- "ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
- },
- "babel-jest": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz",
- "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==",
- "requires": {
- "@jest/transform": "^24.9.0",
- "@jest/types": "^24.9.0",
- "@types/babel__core": "^7.1.0",
- "babel-plugin-istanbul": "^5.1.0",
- "babel-preset-jest": "^24.9.0",
- "chalk": "^2.4.2",
- "slash": "^2.0.0"
- }
- },
- "babel-plugin-jest-hoist": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz",
- "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==",
- "requires": {
- "@types/babel__traverse": "^7.0.6"
- }
- },
- "babel-preset-jest": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz",
- "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==",
- "requires": {
- "@babel/plugin-syntax-object-rest-spread": "^7.0.0",
- "babel-plugin-jest-hoist": "^24.9.0"
- }
- },
- "diff-sequences": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz",
- "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew=="
- },
- "expect": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz",
- "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==",
- "requires": {
- "@jest/types": "^24.9.0",
- "ansi-styles": "^3.2.0",
- "jest-get-type": "^24.9.0",
- "jest-matcher-utils": "^24.9.0",
- "jest-message-util": "^24.9.0",
- "jest-regex-util": "^24.9.0"
- }
- },
- "jest-config": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz",
- "integrity": "sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==",
- "requires": {
- "@babel/core": "^7.1.0",
- "@jest/test-sequencer": "^24.9.0",
- "@jest/types": "^24.9.0",
- "babel-jest": "^24.9.0",
- "chalk": "^2.0.1",
- "glob": "^7.1.1",
- "jest-environment-jsdom": "^24.9.0",
- "jest-environment-node": "^24.9.0",
- "jest-get-type": "^24.9.0",
- "jest-jasmine2": "^24.9.0",
- "jest-regex-util": "^24.3.0",
- "jest-resolve": "^24.9.0",
- "jest-util": "^24.9.0",
- "jest-validate": "^24.9.0",
- "micromatch": "^3.1.10",
- "pretty-format": "^24.9.0",
- "realpath-native": "^1.1.0"
- }
- },
- "jest-diff": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz",
- "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==",
- "requires": {
- "chalk": "^2.0.1",
- "diff-sequences": "^24.9.0",
- "jest-get-type": "^24.9.0",
- "pretty-format": "^24.9.0"
- }
- },
- "jest-each": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz",
- "integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==",
- "requires": {
- "@jest/types": "^24.9.0",
- "chalk": "^2.0.1",
- "jest-get-type": "^24.9.0",
- "jest-util": "^24.9.0",
- "pretty-format": "^24.9.0"
- }
- },
- "jest-environment-jsdom": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz",
- "integrity": "sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==",
- "requires": {
- "@jest/environment": "^24.9.0",
- "@jest/fake-timers": "^24.9.0",
- "@jest/types": "^24.9.0",
- "jest-mock": "^24.9.0",
- "jest-util": "^24.9.0",
- "jsdom": "^11.5.1"
- }
- },
- "jest-environment-node": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.9.0.tgz",
- "integrity": "sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==",
- "requires": {
- "@jest/environment": "^24.9.0",
- "@jest/fake-timers": "^24.9.0",
- "@jest/types": "^24.9.0",
- "jest-mock": "^24.9.0",
- "jest-util": "^24.9.0"
- }
- },
- "jest-get-type": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz",
- "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q=="
- },
- "jest-haste-map": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz",
- "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==",
- "requires": {
- "@jest/types": "^24.9.0",
- "anymatch": "^2.0.0",
- "fb-watchman": "^2.0.0",
- "fsevents": "^1.2.7",
- "graceful-fs": "^4.1.15",
- "invariant": "^2.2.4",
- "jest-serializer": "^24.9.0",
- "jest-util": "^24.9.0",
- "jest-worker": "^24.9.0",
- "micromatch": "^3.1.10",
- "sane": "^4.0.3",
- "walker": "^1.0.7"
- },
- "dependencies": {
- "jest-worker": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz",
- "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==",
- "requires": {
- "merge-stream": "^2.0.0",
- "supports-color": "^6.1.0"
- }
- }
- }
- },
- "jest-jasmine2": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz",
- "integrity": "sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==",
- "requires": {
- "@babel/traverse": "^7.1.0",
- "@jest/environment": "^24.9.0",
- "@jest/test-result": "^24.9.0",
- "@jest/types": "^24.9.0",
- "chalk": "^2.0.1",
- "co": "^4.6.0",
- "expect": "^24.9.0",
- "is-generator-fn": "^2.0.0",
- "jest-each": "^24.9.0",
- "jest-matcher-utils": "^24.9.0",
- "jest-message-util": "^24.9.0",
- "jest-runtime": "^24.9.0",
- "jest-snapshot": "^24.9.0",
- "jest-util": "^24.9.0",
- "pretty-format": "^24.9.0",
- "throat": "^4.0.0"
- }
- },
- "jest-matcher-utils": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz",
- "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==",
- "requires": {
- "chalk": "^2.0.1",
- "jest-diff": "^24.9.0",
- "jest-get-type": "^24.9.0",
- "pretty-format": "^24.9.0"
- }
- },
- "jest-message-util": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz",
- "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==",
- "requires": {
- "@babel/code-frame": "^7.0.0",
- "@jest/test-result": "^24.9.0",
- "@jest/types": "^24.9.0",
- "@types/stack-utils": "^1.0.1",
- "chalk": "^2.0.1",
- "micromatch": "^3.1.10",
- "slash": "^2.0.0",
- "stack-utils": "^1.0.1"
- }
- },
- "jest-mock": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz",
- "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==",
- "requires": {
- "@jest/types": "^24.9.0"
- }
- },
- "jest-regex-util": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz",
- "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA=="
- },
- "jest-resolve": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.9.0.tgz",
- "integrity": "sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==",
- "requires": {
- "@jest/types": "^24.9.0",
- "browser-resolve": "^1.11.3",
- "chalk": "^2.0.1",
- "jest-pnp-resolver": "^1.2.1",
- "realpath-native": "^1.1.0"
- }
- },
- "jest-runtime": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz",
- "integrity": "sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==",
- "requires": {
- "@jest/console": "^24.7.1",
- "@jest/environment": "^24.9.0",
- "@jest/source-map": "^24.3.0",
- "@jest/transform": "^24.9.0",
- "@jest/types": "^24.9.0",
- "@types/yargs": "^13.0.0",
- "chalk": "^2.0.1",
- "exit": "^0.1.2",
- "glob": "^7.1.3",
- "graceful-fs": "^4.1.15",
- "jest-config": "^24.9.0",
- "jest-haste-map": "^24.9.0",
- "jest-message-util": "^24.9.0",
- "jest-mock": "^24.9.0",
- "jest-regex-util": "^24.3.0",
- "jest-resolve": "^24.9.0",
- "jest-snapshot": "^24.9.0",
- "jest-util": "^24.9.0",
- "jest-validate": "^24.9.0",
- "realpath-native": "^1.1.0",
- "slash": "^2.0.0",
- "strip-bom": "^3.0.0",
- "yargs": "^13.3.0"
- }
- },
- "jest-serializer": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz",
- "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ=="
- },
- "jest-snapshot": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.9.0.tgz",
- "integrity": "sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==",
- "requires": {
- "@babel/types": "^7.0.0",
- "@jest/types": "^24.9.0",
- "chalk": "^2.0.1",
- "expect": "^24.9.0",
- "jest-diff": "^24.9.0",
- "jest-get-type": "^24.9.0",
- "jest-matcher-utils": "^24.9.0",
- "jest-message-util": "^24.9.0",
- "jest-resolve": "^24.9.0",
- "mkdirp": "^0.5.1",
- "natural-compare": "^1.4.0",
- "pretty-format": "^24.9.0",
- "semver": "^6.2.0"
- }
- },
- "jest-util": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz",
- "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==",
- "requires": {
- "@jest/console": "^24.9.0",
- "@jest/fake-timers": "^24.9.0",
- "@jest/source-map": "^24.9.0",
- "@jest/test-result": "^24.9.0",
- "@jest/types": "^24.9.0",
- "callsites": "^3.0.0",
- "chalk": "^2.0.1",
- "graceful-fs": "^4.1.15",
- "is-ci": "^2.0.0",
- "mkdirp": "^0.5.1",
- "slash": "^2.0.0",
- "source-map": "^0.6.0"
- },
- "dependencies": {
- "@jest/console": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
- "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==",
- "requires": {
- "@jest/source-map": "^24.9.0",
- "chalk": "^2.0.1",
- "slash": "^2.0.0"
- }
- }
- }
- },
- "jest-validate": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz",
- "integrity": "sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==",
- "requires": {
- "@jest/types": "^24.9.0",
- "camelcase": "^5.3.1",
- "chalk": "^2.0.1",
- "jest-get-type": "^24.9.0",
- "leven": "^3.1.0",
- "pretty-format": "^24.9.0"
- }
- },
- "leven": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
- "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="
- },
- "merge-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
- "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
- },
- "pretty-format": {
- "version": "24.9.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz",
- "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==",
- "requires": {
- "@jest/types": "^24.9.0",
- "ansi-regex": "^4.0.0",
- "ansi-styles": "^3.2.0",
- "react-is": "^16.8.4"
- }
- },
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
- },
- "string-width": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
- "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
- "requires": {
- "emoji-regex": "^7.0.1",
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^5.1.0"
- }
- },
- "supports-color": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
- "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
- "requires": {
- "has-flag": "^3.0.0"
- }
- },
- "yargs": {
- "version": "13.3.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
- "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==",
- "requires": {
- "cliui": "^5.0.0",
- "find-up": "^3.0.0",
- "get-caller-file": "^2.0.1",
- "require-directory": "^2.1.1",
- "require-main-filename": "^2.0.0",
- "set-blocking": "^2.0.0",
- "string-width": "^3.0.0",
- "which-module": "^2.0.0",
- "y18n": "^4.0.0",
- "yargs-parser": "^13.1.1"
- }
- }
- }
- },
- "jest-runner-eslint": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/jest-runner-eslint/-/jest-runner-eslint-0.7.4.tgz",
- "integrity": "sha512-vGmJwmXHi/p8uQksm2B+QhVazoh/fCQdn2R52n+F2RBzEnccIOL6gL5kSE5vkwis9d903VJjEsA+rKg6fEoRPg==",
- "dev": true,
- "requires": {
- "chalk": "^2.4.1",
- "cosmiconfig": "^5.0.0",
- "create-jest-runner": "^0.5.3",
- "eslint": "^5.6.0"
- },
- "dependencies": {
- "acorn": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
- "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
- "dev": true
- },
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
- "dev": true,
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "eslint": {
- "version": "5.16.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz",
- "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==",
- "dev": true,
- "requires": {
- "@babel/code-frame": "^7.0.0",
- "ajv": "^6.9.1",
- "chalk": "^2.1.0",
- "cross-spawn": "^6.0.5",
- "debug": "^4.0.1",
- "doctrine": "^3.0.0",
- "eslint-scope": "^4.0.3",
- "eslint-utils": "^1.3.1",
- "eslint-visitor-keys": "^1.0.0",
- "espree": "^5.0.1",
- "esquery": "^1.0.1",
- "esutils": "^2.0.2",
- "file-entry-cache": "^5.0.1",
- "functional-red-black-tree": "^1.0.1",
- "glob": "^7.1.2",
- "globals": "^11.7.0",
- "ignore": "^4.0.6",
- "import-fresh": "^3.0.0",
- "imurmurhash": "^0.1.4",
- "inquirer": "^6.2.2",
- "js-yaml": "^3.13.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "levn": "^0.3.0",
- "lodash": "^4.17.11",
- "minimatch": "^3.0.4",
- "mkdirp": "^0.5.1",
- "natural-compare": "^1.4.0",
- "optionator": "^0.8.2",
- "path-is-inside": "^1.0.2",
- "progress": "^2.0.0",
- "regexpp": "^2.0.1",
- "semver": "^5.5.1",
- "strip-ansi": "^4.0.0",
- "strip-json-comments": "^2.0.1",
- "table": "^5.2.3",
- "text-table": "^0.2.0"
- }
- },
- "espree": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz",
- "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==",
- "dev": true,
- "requires": {
- "acorn": "^6.0.7",
- "acorn-jsx": "^5.0.0",
- "eslint-visitor-keys": "^1.0.0"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "strip-ansi": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^3.0.0"
- }
- },
- "strip-json-comments": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
- "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
- "dev": true
- }
- }
- },
- "jest-runtime": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.8.0.tgz",
- "integrity": "sha512-Mq0aIXhvO/3bX44ccT+czU1/57IgOMyy80oM0XR/nyD5zgBcesF84BPabZi39pJVA6UXw+fY2Q1N+4BiVUBWOA==",
- "dev": true,
- "requires": {
- "@jest/console": "^24.7.1",
- "@jest/environment": "^24.8.0",
- "@jest/source-map": "^24.3.0",
- "@jest/transform": "^24.8.0",
- "@jest/types": "^24.8.0",
- "@types/yargs": "^12.0.2",
- "chalk": "^2.0.1",
- "exit": "^0.1.2",
- "glob": "^7.1.3",
- "graceful-fs": "^4.1.15",
- "jest-config": "^24.8.0",
- "jest-haste-map": "^24.8.0",
- "jest-message-util": "^24.8.0",
- "jest-mock": "^24.8.0",
- "jest-regex-util": "^24.3.0",
- "jest-resolve": "^24.8.0",
- "jest-snapshot": "^24.8.0",
- "jest-util": "^24.8.0",
- "jest-validate": "^24.8.0",
- "realpath-native": "^1.1.0",
- "slash": "^2.0.0",
- "strip-bom": "^3.0.0",
- "yargs": "^12.0.2"
- },
- "dependencies": {
- "cliui": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
- "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
- "dev": true,
- "requires": {
- "string-width": "^2.1.1",
- "strip-ansi": "^4.0.0",
- "wrap-ansi": "^2.0.0"
- }
- },
- "get-caller-file": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
- "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
- "dev": true
- },
- "is-fullwidth-code-point": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "dev": true,
- "requires": {
- "number-is-nan": "^1.0.0"
- }
- },
- "require-main-filename": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
- "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
- "dev": true
- },
- "strip-ansi": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^3.0.0"
- }
- },
- "wrap-ansi": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
- "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
- "dev": true,
- "requires": {
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true
- },
- "string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "dev": true,
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- }
- },
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- }
- }
- },
- "yargs": {
- "version": "12.0.5",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
- "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
- "dev": true,
- "requires": {
- "cliui": "^4.0.0",
- "decamelize": "^1.2.0",
- "find-up": "^3.0.0",
- "get-caller-file": "^1.0.1",
- "os-locale": "^3.0.0",
- "require-directory": "^2.1.1",
- "require-main-filename": "^1.0.1",
- "set-blocking": "^2.0.0",
- "string-width": "^2.0.0",
- "which-module": "^2.0.0",
- "y18n": "^3.2.1 || ^4.0.0",
- "yargs-parser": "^11.1.1"
- }
- },
- "yargs-parser": {
- "version": "11.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
- "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
- "dev": true,
- "requires": {
- "camelcase": "^5.0.0",
- "decamelize": "^1.2.0"
- }
- }
- }
- },
- "jest-serializer": {
- "version": "24.4.0",
- "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.4.0.tgz",
- "integrity": "sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==",
- "dev": true
- },
- "jest-snapshot": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.8.0.tgz",
- "integrity": "sha512-5ehtWoc8oU9/cAPe6fez6QofVJLBKyqkY2+TlKTOf0VllBB/mqUNdARdcjlZrs9F1Cv+/HKoCS/BknT0+tmfPg==",
- "dev": true,
- "requires": {
- "@babel/types": "^7.0.0",
- "@jest/types": "^24.8.0",
- "chalk": "^2.0.1",
- "expect": "^24.8.0",
- "jest-diff": "^24.8.0",
- "jest-matcher-utils": "^24.8.0",
- "jest-message-util": "^24.8.0",
- "jest-resolve": "^24.8.0",
- "mkdirp": "^0.5.1",
- "natural-compare": "^1.4.0",
- "pretty-format": "^24.8.0",
- "semver": "^5.5.0"
- }
- },
- "jest-util": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.8.0.tgz",
- "integrity": "sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA==",
- "dev": true,
- "requires": {
- "@jest/console": "^24.7.1",
- "@jest/fake-timers": "^24.8.0",
- "@jest/source-map": "^24.3.0",
- "@jest/test-result": "^24.8.0",
- "@jest/types": "^24.8.0",
- "callsites": "^3.0.0",
- "chalk": "^2.0.1",
- "graceful-fs": "^4.1.15",
- "is-ci": "^2.0.0",
- "mkdirp": "^0.5.1",
- "slash": "^2.0.0",
- "source-map": "^0.6.0"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
- }
- }
- },
- "jest-validate": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.8.0.tgz",
- "integrity": "sha512-+/N7VOEMW1Vzsrk3UWBDYTExTPwf68tavEPKDnJzrC6UlHtUDU/fuEdXqFoHzv9XnQ+zW6X3qMZhJ3YexfeLDA==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.8.0",
- "camelcase": "^5.0.0",
- "chalk": "^2.0.1",
- "jest-get-type": "^24.8.0",
- "leven": "^2.1.0",
- "pretty-format": "^24.8.0"
- }
- },
- "jest-watcher": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.8.0.tgz",
- "integrity": "sha512-SBjwHt5NedQoVu54M5GEx7cl7IGEFFznvd/HNT8ier7cCAx/Qgu9ZMlaTQkvK22G1YOpcWBLQPFSImmxdn3DAw==",
- "dev": true,
- "requires": {
- "@jest/test-result": "^24.8.0",
- "@jest/types": "^24.8.0",
- "@types/yargs": "^12.0.9",
- "ansi-escapes": "^3.0.0",
- "chalk": "^2.0.1",
- "jest-util": "^24.8.0",
- "string-length": "^2.0.0"
- }
- },
- "jest-worker": {
- "version": "24.6.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.6.0.tgz",
- "integrity": "sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ==",
- "requires": {
- "merge-stream": "^1.0.1",
- "supports-color": "^6.1.0"
- },
- "dependencies": {
- "supports-color": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
- "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
- "requires": {
- "has-flag": "^3.0.0"
- }
- }
- }
- },
- "js-base64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
- "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==",
- "dev": true
- },
- "js-levenshtein": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
- "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
- "dev": true
- },
- "js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
- },
- "js-yaml": {
- "version": "3.13.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
- "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
- "requires": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
- }
- },
- "jsbn": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
- "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
- },
- "jsdom": {
- "version": "11.12.0",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz",
- "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==",
- "requires": {
- "abab": "^2.0.0",
- "acorn": "^5.5.3",
- "acorn-globals": "^4.1.0",
- "array-equal": "^1.0.0",
- "cssom": ">= 0.3.2 < 0.4.0",
- "cssstyle": "^1.0.0",
- "data-urls": "^1.0.0",
- "domexception": "^1.0.1",
- "escodegen": "^1.9.1",
- "html-encoding-sniffer": "^1.0.2",
- "left-pad": "^1.3.0",
- "nwsapi": "^2.0.7",
- "parse5": "4.0.0",
- "pn": "^1.1.0",
- "request": "^2.87.0",
- "request-promise-native": "^1.0.5",
- "sax": "^1.2.4",
- "symbol-tree": "^3.2.2",
- "tough-cookie": "^2.3.4",
- "w3c-hr-time": "^1.0.1",
- "webidl-conversions": "^4.0.2",
- "whatwg-encoding": "^1.0.3",
- "whatwg-mimetype": "^2.1.0",
- "whatwg-url": "^6.4.1",
- "ws": "^5.2.0",
- "xml-name-validator": "^3.0.0"
- },
- "dependencies": {
- "acorn": {
- "version": "5.7.3",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
- "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw=="
- },
- "ws": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
- "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
- "requires": {
- "async-limiter": "~1.0.0"
- }
- }
- }
- },
- "jsesc": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
- "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
- },
- "json-parse-better-errors": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
- "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
- },
- "json-schema": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
- "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
- },
- "json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
- },
- "json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
- "dev": true
- },
- "json-stringify-safe": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
- "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
- },
- "json5": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
- "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
- "dev": true,
- "requires": {
- "minimist": "^1.2.0"
- }
- },
- "jsondiffpatch": {
- "version": "0.3.11",
- "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.3.11.tgz",
- "integrity": "sha512-Xi3Iygdt/BGhml6bdUFhgDki1TgOsp3hG3iiH3KtzP+CahtGcdPfKRLlnZbSw+3b1umZkhmKrqXUgUcKenyhtA==",
- "requires": {
- "chalk": "^2.3.0",
- "diff-match-patch": "^1.0.0"
- }
- },
- "jsprim": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
- "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
- "requires": {
- "assert-plus": "1.0.0",
- "extsprintf": "1.3.0",
- "json-schema": "0.2.3",
- "verror": "1.10.0"
- }
- },
- "jsx-ast-utils": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz",
- "integrity": "sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==",
- "dev": true,
- "requires": {
- "array-includes": "^3.0.3",
- "object.assign": "^4.1.0"
- }
- },
- "just-extend": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz",
- "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==",
- "dev": true
- },
- "kind-of": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
- "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
- },
- "kleur": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
- "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
- "dev": true
- },
- "lcid": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
- "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
- "dev": true,
- "requires": {
- "invert-kv": "^2.0.0"
- }
- },
- "left-pad": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
- "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="
- },
- "leven": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
- "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=",
- "dev": true
- },
- "levn": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
- "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
- "requires": {
- "prelude-ls": "~1.1.2",
- "type-check": "~0.3.2"
- }
- },
- "load-json-file": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
- "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "parse-json": "^2.2.0",
- "pify": "^2.0.0",
- "strip-bom": "^3.0.0"
- },
- "dependencies": {
- "pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
- "dev": true
- }
- }
- },
- "loader-runner": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
- "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==",
- "dev": true
- },
- "loader-utils": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
- "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
- "dev": true,
- "requires": {
- "big.js": "^5.2.2",
- "emojis-list": "^2.0.0",
- "json5": "^1.0.1"
- }
- },
- "locate-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
- "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
- "requires": {
- "p-locate": "^3.0.0",
- "path-exists": "^3.0.0"
- }
- },
- "lodash": {
- "version": "4.17.15",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
- "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
- },
- "lodash._getnative": {
- "version": "3.9.1",
- "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
- "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U="
- },
- "lodash.curry": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
- "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA="
- },
- "lodash.escape": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz",
- "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=",
- "dev": true
- },
- "lodash.flattendeep": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
- "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
- "dev": true
- },
- "lodash.flow": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz",
- "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o="
- },
- "lodash.isarguments": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
- "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo="
- },
- "lodash.isarray": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
- "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U="
- },
- "lodash.isequal": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
- "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
- "dev": true
- },
- "lodash.keys": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
- "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
- "requires": {
- "lodash._getnative": "^3.0.0",
- "lodash.isarguments": "^3.0.0",
- "lodash.isarray": "^3.0.0"
- }
- },
- "lodash.sortby": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
- "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg="
- },
- "lodash.unescape": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
- "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=",
- "dev": true
- },
- "lolex": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz",
- "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==",
- "dev": true
- },
- "loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "requires": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- }
- },
- "loud-rejection": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
- "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
- "dev": true,
- "requires": {
- "currently-unhandled": "^0.4.1",
- "signal-exit": "^3.0.0"
- }
- },
- "lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "requires": {
- "yallist": "^3.0.2"
- }
- },
- "make-dir": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
- "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
- "dev": true,
- "requires": {
- "pify": "^4.0.1",
- "semver": "^5.6.0"
- }
- },
- "makeerror": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
- "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=",
- "requires": {
- "tmpl": "1.0.x"
- }
- },
- "mamacro": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz",
- "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==",
- "dev": true
- },
- "map-age-cleaner": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
- "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
- "dev": true,
- "requires": {
- "p-defer": "^1.0.0"
- }
- },
- "map-cache": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
- "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8="
- },
- "map-obj": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
- "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
- "dev": true
- },
- "map-visit": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
- "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
- "requires": {
- "object-visit": "^1.0.0"
- }
- },
- "md5.js": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
- "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
- "dev": true,
- "requires": {
- "hash-base": "^3.0.0",
- "inherits": "^2.0.1",
- "safe-buffer": "^5.1.2"
- }
- },
- "mem": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
- "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
- "dev": true,
- "requires": {
- "map-age-cleaner": "^0.1.1",
- "mimic-fn": "^2.0.0",
- "p-is-promise": "^2.0.0"
- },
- "dependencies": {
- "mimic-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
- "dev": true
- }
- }
- },
- "memoize-one": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.5.tgz",
- "integrity": "sha512-ey6EpYv0tEaIbM/nTDOpHciXUvd+ackQrJgEzBwemhZZIWZjcyodqEcrmqDy2BKRTM3a65kKBV4WtLXJDt26SQ=="
- },
- "memory-fs": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
- "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
- "dev": true,
- "requires": {
- "errno": "^0.1.3",
- "readable-stream": "^2.0.1"
- }
- },
- "meow": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
- "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
- "dev": true,
- "requires": {
- "camelcase-keys": "^2.0.0",
- "decamelize": "^1.1.2",
- "loud-rejection": "^1.0.0",
- "map-obj": "^1.0.1",
- "minimist": "^1.1.3",
- "normalize-package-data": "^2.3.4",
- "object-assign": "^4.0.1",
- "read-pkg-up": "^1.0.1",
- "redent": "^1.0.0",
- "trim-newlines": "^1.0.0"
- },
- "dependencies": {
- "find-up": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
- "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
- "dev": true,
- "requires": {
- "path-exists": "^2.0.0",
- "pinkie-promise": "^2.0.0"
- }
- },
- "load-json-file": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
- "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "parse-json": "^2.2.0",
- "pify": "^2.0.0",
- "pinkie-promise": "^2.0.0",
- "strip-bom": "^2.0.0"
- }
- },
- "path-exists": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
- "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
- "dev": true,
- "requires": {
- "pinkie-promise": "^2.0.0"
- }
- },
- "path-type": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
- "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "pify": "^2.0.0",
- "pinkie-promise": "^2.0.0"
- }
- },
- "pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
- "dev": true
- },
- "read-pkg": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
- "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
- "dev": true,
- "requires": {
- "load-json-file": "^1.0.0",
- "normalize-package-data": "^2.3.2",
- "path-type": "^1.0.0"
- }
- },
- "read-pkg-up": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
- "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
- "dev": true,
- "requires": {
- "find-up": "^1.0.0",
- "read-pkg": "^1.0.0"
- }
- },
- "strip-bom": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
- "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
- "dev": true,
- "requires": {
- "is-utf8": "^0.2.0"
- }
- }
- }
- },
- "merge-stream": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz",
- "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=",
- "requires": {
- "readable-stream": "^2.0.1"
- }
- },
- "micromatch": {
- "version": "3.1.10",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
- "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
- "requires": {
- "arr-diff": "^4.0.0",
- "array-unique": "^0.3.2",
- "braces": "^2.3.1",
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "extglob": "^2.0.4",
- "fragment-cache": "^0.2.1",
- "kind-of": "^6.0.2",
- "nanomatch": "^1.2.9",
- "object.pick": "^1.3.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.2"
- }
- },
- "miller-rabin": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
- "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
- "dev": true,
- "requires": {
- "bn.js": "^4.0.0",
- "brorand": "^1.0.1"
- }
- },
- "mime-db": {
- "version": "1.40.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
- "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
- },
- "mime-types": {
- "version": "2.1.24",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
- "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
- "requires": {
- "mime-db": "1.40.0"
- }
- },
- "mimic-fn": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
- "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
- "dev": true
- },
- "mini-create-react-context": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz",
- "integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==",
- "requires": {
- "@babel/runtime": "^7.4.0",
- "gud": "^1.0.0",
- "tiny-warning": "^1.0.2"
- }
- },
- "minimalistic-assert": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
- "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
- "dev": true
- },
- "minimalistic-crypto-utils": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
- "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
- "dev": true
- },
- "minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
- },
- "mississippi": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
- "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
- "dev": true,
- "requires": {
- "concat-stream": "^1.5.0",
- "duplexify": "^3.4.2",
- "end-of-stream": "^1.1.0",
- "flush-write-stream": "^1.0.0",
- "from2": "^2.1.0",
- "parallel-transform": "^1.1.0",
- "pump": "^3.0.0",
- "pumpify": "^1.3.3",
- "stream-each": "^1.1.0",
- "through2": "^2.0.0"
- }
- },
- "mixin-deep": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
- "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
- "requires": {
- "for-in": "^1.0.2",
- "is-extendable": "^1.0.1"
- },
- "dependencies": {
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- }
- }
- },
- "mkdirp": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
- "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
- "requires": {
- "minimist": "0.0.8"
- },
- "dependencies": {
- "minimist": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
- "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
- }
- }
- },
- "moo": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz",
- "integrity": "sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==",
- "dev": true
- },
- "move-concurrently": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
- "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
- "dev": true,
- "requires": {
- "aproba": "^1.1.1",
- "copy-concurrently": "^1.0.0",
- "fs-write-stream-atomic": "^1.0.8",
- "mkdirp": "^0.5.1",
- "rimraf": "^2.5.4",
- "run-queue": "^1.0.3"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- },
- "mute-stream": {
- "version": "0.0.7",
- "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
- "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
- "dev": true
- },
- "nan": {
- "version": "2.14.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
- "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
- },
- "nanomatch": {
- "version": "1.2.13",
- "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
- "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
- "requires": {
- "arr-diff": "^4.0.0",
- "array-unique": "^0.3.2",
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "fragment-cache": "^0.2.1",
- "is-windows": "^1.0.2",
- "kind-of": "^6.0.2",
- "object.pick": "^1.3.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- }
- },
- "natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc="
- },
- "nearley": {
- "version": "2.18.0",
- "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.18.0.tgz",
- "integrity": "sha512-/zQOMCeJcioI0xJtd5RpBiWw2WP7wLe6vq8/3Yu0rEwgus/G/+pViX80oA87JdVgjRt2895mZSv2VfZmy4W1uw==",
- "dev": true,
- "requires": {
- "commander": "^2.19.0",
- "moo": "^0.4.3",
- "railroad-diagrams": "^1.0.0",
- "randexp": "0.4.6",
- "semver": "^5.4.1"
- }
- },
- "neo-async": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
- "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
- "dev": true
- },
- "nice-try": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
- "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
- },
- "nise": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.1.tgz",
- "integrity": "sha512-edFWm0fsFG2n318rfEnKlTZTkjlbVOFF9XIA+fj+Ed+Qz1laYW2lobwavWoMzGrYDHH1EpiNJgDfvGnkZztR/g==",
- "dev": true,
- "requires": {
- "@sinonjs/formatio": "^3.2.1",
- "@sinonjs/text-encoding": "^0.7.1",
- "just-extend": "^4.0.2",
- "lolex": "^4.1.0",
- "path-to-regexp": "^1.7.0"
- }
- },
- "node-gyp": {
- "version": "3.8.0",
- "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
- "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
- "dev": true,
- "requires": {
- "fstream": "^1.0.0",
- "glob": "^7.0.3",
- "graceful-fs": "^4.1.2",
- "mkdirp": "^0.5.0",
- "nopt": "2 || 3",
- "npmlog": "0 || 1 || 2 || 3 || 4",
- "osenv": "0",
- "request": "^2.87.0",
- "rimraf": "2",
- "semver": "~5.3.0",
- "tar": "^2.0.0",
- "which": "1"
- },
- "dependencies": {
- "semver": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
- "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
- "dev": true
- }
- }
- },
- "node-int64": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
- "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs="
- },
- "node-libs-browser": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
- "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==",
- "dev": true,
- "requires": {
- "assert": "^1.1.1",
- "browserify-zlib": "^0.2.0",
- "buffer": "^4.3.0",
- "console-browserify": "^1.1.0",
- "constants-browserify": "^1.0.0",
- "crypto-browserify": "^3.11.0",
- "domain-browser": "^1.1.1",
- "events": "^3.0.0",
- "https-browserify": "^1.0.0",
- "os-browserify": "^0.3.0",
- "path-browserify": "0.0.1",
- "process": "^0.11.10",
- "punycode": "^1.2.4",
- "querystring-es3": "^0.2.0",
- "readable-stream": "^2.3.3",
- "stream-browserify": "^2.0.1",
- "stream-http": "^2.7.2",
- "string_decoder": "^1.0.0",
- "timers-browserify": "^2.0.4",
- "tty-browserify": "0.0.0",
- "url": "^0.11.0",
- "util": "^0.11.0",
- "vm-browserify": "^1.0.1"
- },
- "dependencies": {
- "punycode": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
- "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
- "dev": true
- }
- }
- },
- "node-modules-regexp": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz",
- "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA="
- },
- "node-notifier": {
- "version": "5.4.0",
- "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz",
- "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==",
- "dev": true,
- "requires": {
- "growly": "^1.3.0",
- "is-wsl": "^1.1.0",
- "semver": "^5.5.0",
- "shellwords": "^0.1.1",
- "which": "^1.3.0"
- }
- },
- "node-releases": {
- "version": "1.1.25",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.25.tgz",
- "integrity": "sha512-fI5BXuk83lKEoZDdH3gRhtsNgh05/wZacuXkgbiYkceE7+QIMXOg98n9ZV7mz27B+kFHnqHcUpscZZlGRSmTpQ==",
- "dev": true,
- "requires": {
- "semver": "^5.3.0"
- }
- },
- "node-sass": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz",
- "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==",
- "dev": true,
- "requires": {
- "async-foreach": "^0.1.3",
- "chalk": "^1.1.1",
- "cross-spawn": "^3.0.0",
- "gaze": "^1.0.0",
- "get-stdin": "^4.0.1",
- "glob": "^7.0.3",
- "in-publish": "^2.0.0",
- "lodash": "^4.17.11",
- "meow": "^3.7.0",
- "mkdirp": "^0.5.1",
- "nan": "^2.13.2",
- "node-gyp": "^3.8.0",
- "npmlog": "^4.0.0",
- "request": "^2.88.0",
- "sass-graph": "^2.2.4",
- "stdout-stream": "^1.4.0",
- "true-case-path": "^1.0.2"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true
- },
- "ansi-styles": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
- "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
- "dev": true
- },
- "chalk": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
- "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
- "dev": true,
- "requires": {
- "ansi-styles": "^2.2.1",
- "escape-string-regexp": "^1.0.2",
- "has-ansi": "^2.0.0",
- "strip-ansi": "^3.0.0",
- "supports-color": "^2.0.0"
- }
- },
- "cross-spawn": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
- "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
- "dev": true,
- "requires": {
- "lru-cache": "^4.0.1",
- "which": "^1.2.9"
- }
- },
- "lru-cache": {
- "version": "4.1.5",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
- "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
- "dev": true,
- "requires": {
- "pseudomap": "^1.0.2",
- "yallist": "^2.1.2"
- }
- },
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- },
- "supports-color": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
- "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
- "dev": true
- },
- "yallist": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
- "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
- "dev": true
- }
- }
- },
- "nopt": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
- "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
- "dev": true,
- "requires": {
- "abbrev": "1"
- }
- },
- "normalize-package-data": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
- "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
- "requires": {
- "hosted-git-info": "^2.1.4",
- "resolve": "^1.10.0",
- "semver": "2 || 3 || 4 || 5",
- "validate-npm-package-license": "^3.0.1"
- }
- },
- "normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true
- },
- "npm-run-path": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
- "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
- "requires": {
- "path-key": "^2.0.0"
- }
- },
- "npmlog": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
- "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
- "dev": true,
- "requires": {
- "are-we-there-yet": "~1.1.2",
- "console-control-strings": "~1.1.0",
- "gauge": "~2.7.3",
- "set-blocking": "~2.0.0"
- }
- },
- "nth-check": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
- "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
- "dev": true,
- "requires": {
- "boolbase": "~1.0.0"
- }
- },
- "number-is-nan": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
- "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
- "dev": true
- },
- "nwsapi": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz",
- "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw=="
- },
- "oauth-sign": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
- "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
- },
- "object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
- },
- "object-copy": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
- "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
- "requires": {
- "copy-descriptor": "^0.1.0",
- "define-property": "^0.2.5",
- "kind-of": "^3.0.3"
- },
- "dependencies": {
- "define-property": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
- "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
- "requires": {
- "is-descriptor": "^0.1.0"
- }
- },
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "object-inspect": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz",
- "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==",
- "dev": true
- },
- "object-is": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz",
- "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=",
- "dev": true
- },
- "object-keys": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
- "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
- },
- "object-visit": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
- "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
- "requires": {
- "isobject": "^3.0.0"
- }
- },
- "object.assign": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
- "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.2",
- "function-bind": "^1.1.1",
- "has-symbols": "^1.0.0",
- "object-keys": "^1.0.11"
- }
- },
- "object.entries": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz",
- "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.12.0",
- "function-bind": "^1.1.1",
- "has": "^1.0.3"
- }
- },
- "object.fromentries": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz",
- "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.2",
- "es-abstract": "^1.11.0",
- "function-bind": "^1.1.1",
- "has": "^1.0.1"
- }
- },
- "object.getownpropertydescriptors": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
- "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
- "requires": {
- "define-properties": "^1.1.2",
- "es-abstract": "^1.5.1"
- }
- },
- "object.pick": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
- "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
- "requires": {
- "isobject": "^3.0.1"
- }
- },
- "object.values": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz",
- "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.12.0",
- "function-bind": "^1.1.1",
- "has": "^1.0.3"
- }
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "requires": {
- "wrappy": "1"
- }
- },
- "onetime": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
- "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
- "dev": true,
- "requires": {
- "mimic-fn": "^1.0.0"
- }
- },
- "optimist": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
- "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
- "dev": true,
- "requires": {
- "minimist": "~0.0.1",
- "wordwrap": "~0.0.2"
- },
- "dependencies": {
- "minimist": {
- "version": "0.0.10",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
- "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
- "dev": true
- },
- "wordwrap": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
- "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
- "dev": true
- }
- }
- },
- "optionator": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
- "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
- "requires": {
- "deep-is": "~0.1.3",
- "fast-levenshtein": "~2.0.4",
- "levn": "~0.3.0",
- "prelude-ls": "~1.1.2",
- "type-check": "~0.3.2",
- "wordwrap": "~1.0.0"
- }
- },
- "os-browserify": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
- "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
- "dev": true
- },
- "os-homedir": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
- "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
- "dev": true
- },
- "os-locale": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
- "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
- "dev": true,
- "requires": {
- "execa": "^1.0.0",
- "lcid": "^2.0.0",
- "mem": "^4.0.0"
- }
- },
- "os-tmpdir": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
- "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
- "dev": true
- },
- "osenv": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
- "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
- "dev": true,
- "requires": {
- "os-homedir": "^1.0.0",
- "os-tmpdir": "^1.0.0"
- }
- },
- "p-defer": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
- "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
- "dev": true
- },
- "p-each-series": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz",
- "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=",
- "dev": true,
- "requires": {
- "p-reduce": "^1.0.0"
- }
- },
- "p-finally": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
- "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
- },
- "p-is-promise": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
- "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==",
- "dev": true
- },
- "p-limit": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
- "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
- "requires": {
- "p-try": "^2.0.0"
- }
- },
- "p-locate": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
- "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
- "requires": {
- "p-limit": "^2.0.0"
- }
- },
- "p-reduce": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz",
- "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=",
- "dev": true
- },
- "p-try": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
- "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
- },
- "pako": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz",
- "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==",
- "dev": true
- },
- "parallel-transform": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz",
- "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
- "dev": true,
- "requires": {
- "cyclist": "~0.2.2",
- "inherits": "^2.0.3",
- "readable-stream": "^2.1.5"
- }
- },
- "parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "requires": {
- "callsites": "^3.0.0"
- }
- },
- "parse-asn1": {
- "version": "5.1.4",
- "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz",
- "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==",
- "dev": true,
- "requires": {
- "asn1.js": "^4.0.0",
- "browserify-aes": "^1.0.0",
- "create-hash": "^1.1.0",
- "evp_bytestokey": "^1.0.0",
- "pbkdf2": "^3.0.3",
- "safe-buffer": "^5.1.1"
- }
- },
- "parse-json": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
- "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
- "dev": true,
- "requires": {
- "error-ex": "^1.2.0"
- }
- },
- "parse-passwd": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
- "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
- "dev": true
- },
- "parse5": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
- "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA=="
- },
- "pascalcase": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
- "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
- },
- "path-browserify": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
- "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
- "dev": true
- },
- "path-dirname": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
- "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
- "dev": true
- },
- "path-exists": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
- },
- "path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
- },
- "path-is-inside": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
- "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
- "dev": true
- },
- "path-key": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
- "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
- },
- "path-parse": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
- "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
- },
- "path-to-regexp": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
- "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
- "requires": {
- "isarray": "0.0.1"
- },
- "dependencies": {
- "isarray": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
- "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
- }
- }
- },
- "path-type": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
- "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
- "dev": true,
- "requires": {
- "pify": "^2.0.0"
- },
- "dependencies": {
- "pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
- "dev": true
- }
- }
- },
- "pbkdf2": {
- "version": "3.0.17",
- "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
- "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
- "dev": true,
- "requires": {
- "create-hash": "^1.1.2",
- "create-hmac": "^1.1.4",
- "ripemd160": "^2.0.1",
- "safe-buffer": "^5.0.1",
- "sha.js": "^2.4.8"
- }
- },
- "performance-now": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
- "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
- },
- "picomatch": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz",
- "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==",
- "dev": true
- },
- "pify": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
- "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
- "dev": true
- },
- "pinkie": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
- "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
- "dev": true
- },
- "pinkie-promise": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
- "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
- "dev": true,
- "requires": {
- "pinkie": "^2.0.0"
- }
- },
- "pirates": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz",
- "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==",
- "requires": {
- "node-modules-regexp": "^1.0.0"
- }
- },
- "pkg-dir": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
- "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
- "dev": true,
- "requires": {
- "find-up": "^3.0.0"
- }
- },
- "pn": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
- "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA=="
- },
- "posix-character-classes": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
- "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
- },
- "postcss": {
- "version": "7.0.17",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz",
- "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==",
- "dev": true,
- "requires": {
- "chalk": "^2.4.2",
- "source-map": "^0.6.1",
- "supports-color": "^6.1.0"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
- },
- "supports-color": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
- "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
- "dev": true,
- "requires": {
- "has-flag": "^3.0.0"
- }
- }
- }
- },
- "postcss-modules-extract-imports": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz",
- "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==",
- "dev": true,
- "requires": {
- "postcss": "^7.0.5"
- }
- },
- "postcss-modules-local-by-default": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz",
- "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==",
- "dev": true,
- "requires": {
- "icss-utils": "^4.1.1",
- "postcss": "^7.0.16",
- "postcss-selector-parser": "^6.0.2",
- "postcss-value-parser": "^4.0.0"
- }
- },
- "postcss-modules-scope": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz",
- "integrity": "sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A==",
- "dev": true,
- "requires": {
- "postcss": "^7.0.6",
- "postcss-selector-parser": "^6.0.0"
- }
- },
- "postcss-modules-values": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz",
- "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==",
- "dev": true,
- "requires": {
- "icss-utils": "^4.0.0",
- "postcss": "^7.0.6"
- }
- },
- "postcss-selector-parser": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz",
- "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==",
- "dev": true,
- "requires": {
- "cssesc": "^3.0.0",
- "indexes-of": "^1.0.1",
- "uniq": "^1.0.1"
- }
- },
- "postcss-value-parser": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz",
- "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==",
- "dev": true
- },
- "prelude-ls": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
- "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
- },
- "pretty-format": {
- "version": "24.8.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.8.0.tgz",
- "integrity": "sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw==",
- "dev": true,
- "requires": {
- "@jest/types": "^24.8.0",
- "ansi-regex": "^4.0.0",
- "ansi-styles": "^3.2.0",
- "react-is": "^16.8.4"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
- "dev": true
- }
- }
- },
- "private": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
- "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==",
- "dev": true
- },
- "process": {
- "version": "0.11.10",
- "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
- "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
- "dev": true
- },
- "process-nextick-args": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
- "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
- },
- "progress": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
- "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
- "dev": true
- },
- "promise-inflight": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
- "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
- "dev": true
- },
- "prompts": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.1.0.tgz",
- "integrity": "sha512-+x5TozgqYdOwWsQFZizE/Tra3fKvAoy037kOyU6cgz84n8f6zxngLOV4O32kTwt9FcLCxAqw0P/c8rOr9y+Gfg==",
- "dev": true,
- "requires": {
- "kleur": "^3.0.2",
- "sisteransi": "^1.0.0"
- }
- },
- "prop-types": {
- "version": "15.7.2",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
- "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
- "requires": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.8.1"
- }
- },
- "prop-types-exact": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz",
- "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==",
- "dev": true,
- "requires": {
- "has": "^1.0.3",
- "object.assign": "^4.1.0",
- "reflect.ownkeys": "^0.2.0"
- }
- },
- "prr": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
- "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
- "dev": true
- },
- "pseudomap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
- "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
- "dev": true
- },
- "psl": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz",
- "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA=="
- },
- "public-encrypt": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
- "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
- "dev": true,
- "requires": {
- "bn.js": "^4.1.0",
- "browserify-rsa": "^4.0.0",
- "create-hash": "^1.1.0",
- "parse-asn1": "^5.0.0",
- "randombytes": "^2.0.1",
- "safe-buffer": "^5.1.2"
- }
- },
- "pump": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
- "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
- "requires": {
- "end-of-stream": "^1.1.0",
- "once": "^1.3.1"
- }
- },
- "pumpify": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
- "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
- "dev": true,
- "requires": {
- "duplexify": "^3.6.0",
- "inherits": "^2.0.3",
- "pump": "^2.0.0"
- },
- "dependencies": {
- "pump": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
- "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
- "dev": true,
- "requires": {
- "end-of-stream": "^1.1.0",
- "once": "^1.3.1"
- }
- }
- }
- },
- "punycode": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
- },
- "pure-color": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz",
- "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4="
- },
- "qs": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
- "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
- },
- "querystring": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
- "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
- "dev": true
- },
- "querystring-es3": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
- "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
- "dev": true
- },
- "raf": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
- "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
- "requires": {
- "performance-now": "^2.1.0"
- }
- },
- "railroad-diagrams": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
- "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=",
- "dev": true
- },
- "randexp": {
- "version": "0.4.6",
- "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
- "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
- "dev": true,
- "requires": {
- "discontinuous-range": "1.0.0",
- "ret": "~0.1.10"
- }
- },
- "randombytes": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
- "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
- "dev": true,
- "requires": {
- "safe-buffer": "^5.1.0"
- }
- },
- "randomfill": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
- "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
- "dev": true,
- "requires": {
- "randombytes": "^2.0.5",
- "safe-buffer": "^5.1.0"
- }
- },
- "rc-align": {
- "version": "2.4.5",
- "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-2.4.5.tgz",
- "integrity": "sha512-nv9wYUYdfyfK+qskThf4BQUSIadeI/dCsfaMZfNEoxm9HwOIioQ+LyqmMK6jWHAZQgOzMLaqawhuBXlF63vgjw==",
- "requires": {
- "babel-runtime": "^6.26.0",
- "dom-align": "^1.7.0",
- "prop-types": "^15.5.8",
- "rc-util": "^4.0.4"
- }
- },
- "rc-animate": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.9.2.tgz",
- "integrity": "sha512-rkJjeJgfbDqVjVX1/QTRfS7PiCq3AnmeYo840cVcuC4pXq4k4yAQMsC2v5BPXXdawC04vnyO4/qHQdbx9ANaiw==",
- "requires": {
- "babel-runtime": "6.x",
- "classnames": "^2.2.6",
- "css-animation": "^1.3.2",
- "prop-types": "15.x",
- "raf": "^3.4.0",
- "rc-util": "^4.8.0",
- "react-lifecycles-compat": "^3.0.4"
- }
- },
- "rc-slider": {
- "version": "8.6.13",
- "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-8.6.13.tgz",
- "integrity": "sha512-fCUe8pPn8n9pq1ARX44nN2nzJoATtna4x/PdskUrxIvZXN8ja7HuceN/hq6kokZjo3FBD2B1yMZvZh6oi68l6Q==",
- "requires": {
- "babel-runtime": "6.x",
- "classnames": "^2.2.5",
- "prop-types": "^15.5.4",
- "rc-tooltip": "^3.7.0",
- "rc-util": "^4.0.4",
- "shallowequal": "^1.0.1",
- "warning": "^4.0.3"
- }
- },
- "rc-tooltip": {
- "version": "3.7.3",
- "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-3.7.3.tgz",
- "integrity": "sha512-dE2ibukxxkrde7wH9W8ozHKUO4aQnPZ6qBHtrTH9LoO836PjDdiaWO73fgPB05VfJs9FbZdmGPVEbXCeOP99Ww==",
- "requires": {
- "babel-runtime": "6.x",
- "prop-types": "^15.5.8",
- "rc-trigger": "^2.2.2"
- }
- },
- "rc-trigger": {
- "version": "2.6.5",
- "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-2.6.5.tgz",
- "integrity": "sha512-m6Cts9hLeZWsTvWnuMm7oElhf+03GOjOLfTuU0QmdB9ZrW7jR2IpI5rpNM7i9MvAAlMAmTx5Zr7g3uu/aMvZAw==",
- "requires": {
- "babel-runtime": "6.x",
- "classnames": "^2.2.6",
- "prop-types": "15.x",
- "rc-align": "^2.4.0",
- "rc-animate": "2.x",
- "rc-util": "^4.4.0",
- "react-lifecycles-compat": "^3.0.4"
- }
- },
- "rc-util": {
- "version": "4.8.4",
- "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.8.4.tgz",
- "integrity": "sha512-1B2h0/pMXfSUBRAgPdoDIKK5XBuzLBuLI9rLwUEW163SPoDvfb9jmg3ymBPtzne2jWgwtdNw4j0vIq/8Yo849A==",
- "requires": {
- "add-dom-event-listener": "^1.1.0",
- "babel-runtime": "6.x",
- "prop-types": "^15.5.10",
- "react-lifecycles-compat": "^3.0.4",
- "shallowequal": "^0.2.2"
- },
- "dependencies": {
- "shallowequal": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-0.2.2.tgz",
- "integrity": "sha1-HjL9W8q2rWiKSBLLDMBO/HXHAU4=",
- "requires": {
- "lodash.keys": "^3.1.2"
- }
- }
- }
- },
- "react": {
- "version": "16.9.0",
- "resolved": "https://registry.npmjs.org/react/-/react-16.9.0.tgz",
- "integrity": "sha512-+7LQnFBwkiw+BobzOF6N//BdoNw0ouwmSJTEm9cglOOmsg/TMiFHZLe2sEoN5M7LgJTj9oHH0gxklfnQe66S1w==",
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2"
- }
- },
- "react-dom": {
- "version": "16.9.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.9.0.tgz",
- "integrity": "sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ==",
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2",
- "scheduler": "^0.15.0"
- },
- "dependencies": {
- "scheduler": {
- "version": "0.15.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz",
- "integrity": "sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==",
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
- }
- }
- }
- },
- "react-html-parser": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/react-html-parser/-/react-html-parser-2.0.2.tgz",
- "integrity": "sha512-XeerLwCVjTs3njZcgCOeDUqLgNIt/t+6Jgi5/qPsO/krUWl76kWKXMeVs2LhY2gwM6X378DkhLjur0zUQdpz0g==",
- "requires": {
- "htmlparser2": "^3.9.0"
- }
- },
- "react-input-autosize": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.1.tgz",
- "integrity": "sha512-3+K4CD13iE4lQQ2WlF8PuV5htfmTRLH6MDnfndHM6LuBRszuXnuyIfE7nhSKt8AzRBZ50bu0sAhkNMeS5pxQQA==",
- "requires": {
- "prop-types": "^15.5.8"
- }
- },
- "react-is": {
- "version": "16.8.6",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
- "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
- },
- "react-json-tree": {
- "version": "0.11.2",
- "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.11.2.tgz",
- "integrity": "sha512-aYhUPj1y5jR3ZQ+G3N7aL8FbTyO03iLwnVvvEikLcNFqNTyabdljo9xDftZndUBFyyyL0aK3qGO9+8EilILHUw==",
- "requires": {
- "babel-runtime": "^6.6.1",
- "prop-types": "^15.5.8",
- "react-base16-styling": "^0.5.1"
- },
- "dependencies": {
- "react-base16-styling": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz",
- "integrity": "sha1-OFjyTpxN2MvT9wLz901YHKKRcmk=",
- "requires": {
- "base16": "^1.0.0",
- "lodash.curry": "^4.0.1",
- "lodash.flow": "^3.3.0",
- "pure-color": "^1.2.0"
- }
- }
- }
- },
- "react-lifecycles-compat": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
- "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
- },
- "react-router": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.1.tgz",
- "integrity": "sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg==",
- "requires": {
- "@babel/runtime": "^7.1.2",
- "history": "^4.9.0",
- "hoist-non-react-statics": "^3.1.0",
- "loose-envify": "^1.3.1",
- "mini-create-react-context": "^0.3.0",
- "path-to-regexp": "^1.7.0",
- "prop-types": "^15.6.2",
- "react-is": "^16.6.0",
- "tiny-invariant": "^1.0.2",
- "tiny-warning": "^1.0.0"
- }
- },
- "react-router-dom": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.0.1.tgz",
- "integrity": "sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA==",
- "requires": {
- "@babel/runtime": "^7.1.2",
- "history": "^4.9.0",
- "loose-envify": "^1.3.1",
- "prop-types": "^15.6.2",
- "react-router": "5.0.1",
- "tiny-invariant": "^1.0.2",
- "tiny-warning": "^1.0.0"
- }
- },
- "react-select": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/react-select/-/react-select-3.0.4.tgz",
- "integrity": "sha512-fbVISKa/lSUlLsltuatfUiKcWCNvdLXxFFyrzVQCBUsjxJZH/m7UMPdw/ywmRixAmwXAP++MdbNNZypOsiDEfA==",
- "requires": {
- "@babel/runtime": "^7.4.4",
- "@emotion/cache": "^10.0.9",
- "@emotion/core": "^10.0.9",
- "@emotion/css": "^10.0.9",
- "classnames": "^2.2.5",
- "memoize-one": "^5.0.0",
- "prop-types": "^15.6.0",
- "raf": "^3.4.0",
- "react-input-autosize": "^2.2.1",
- "react-transition-group": "^2.2.1"
- }
- },
- "react-test-renderer": {
- "version": "16.8.6",
- "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.6.tgz",
- "integrity": "sha512-H2srzU5IWYT6cZXof6AhUcx/wEyJddQ8l7cLM/F7gDXYyPr4oq+vCIxJYXVGhId1J706sqziAjuOEjyNkfgoEw==",
- "dev": true,
- "requires": {
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2",
- "react-is": "^16.8.6",
- "scheduler": "^0.13.6"
- }
- },
- "react-transition-group": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
- "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
- "requires": {
- "dom-helpers": "^3.4.0",
- "loose-envify": "^1.4.0",
- "prop-types": "^15.6.2",
- "react-lifecycles-compat": "^3.0.4"
- }
- },
- "read-pkg": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
- "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
- "dev": true,
- "requires": {
- "load-json-file": "^2.0.0",
- "normalize-package-data": "^2.3.2",
- "path-type": "^2.0.0"
- }
- },
- "read-pkg-up": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
- "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
- "dev": true,
- "requires": {
- "find-up": "^2.0.0",
- "read-pkg": "^2.0.0"
- },
- "dependencies": {
- "find-up": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
- "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
- "dev": true,
- "requires": {
- "locate-path": "^2.0.0"
- }
- },
- "locate-path": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
- "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
- "dev": true,
- "requires": {
- "p-locate": "^2.0.0",
- "path-exists": "^3.0.0"
- }
- },
- "p-limit": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
- "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
- "dev": true,
- "requires": {
- "p-try": "^1.0.0"
- }
- },
- "p-locate": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
- "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
- "dev": true,
- "requires": {
- "p-limit": "^1.1.0"
- }
- },
- "p-try": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
- "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
- "dev": true
- }
- }
- },
- "readable-stream": {
- "version": "2.3.6",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
- "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "readdirp": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.1.tgz",
- "integrity": "sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==",
- "dev": true,
- "requires": {
- "picomatch": "^2.0.4"
- }
- },
- "realpath-native": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz",
- "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==",
- "requires": {
- "util.promisify": "^1.0.0"
- }
- },
- "redent": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
- "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
- "dev": true,
- "requires": {
- "indent-string": "^2.1.0",
- "strip-indent": "^1.0.1"
- }
- },
- "reflect.ownkeys": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz",
- "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=",
- "dev": true
- },
- "regenerate": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
- "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
- "dev": true
- },
- "regenerate-unicode-properties": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz",
- "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==",
- "dev": true,
- "requires": {
- "regenerate": "^1.4.0"
- }
- },
- "regenerator-runtime": {
- "version": "0.13.3",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
- "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
- },
- "regenerator-transform": {
- "version": "0.14.1",
- "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz",
- "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==",
- "dev": true,
- "requires": {
- "private": "^0.1.6"
- }
- },
- "regex-not": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
- "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
- "requires": {
- "extend-shallow": "^3.0.2",
- "safe-regex": "^1.1.0"
- }
- },
- "regexp-tree": {
- "version": "0.1.11",
- "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.11.tgz",
- "integrity": "sha512-7/l/DgapVVDzZobwMCCgMlqiqyLFJ0cduo/j+3BcDJIB+yJdsYCfKuI3l/04NV+H/rfNRdPIDbXNZHM9XvQatg==",
- "dev": true
- },
- "regexpp": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
- "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
- "dev": true
- },
- "regexpu-core": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz",
- "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==",
- "dev": true,
- "requires": {
- "regenerate": "^1.4.0",
- "regenerate-unicode-properties": "^8.0.2",
- "regjsgen": "^0.5.0",
- "regjsparser": "^0.6.0",
- "unicode-match-property-ecmascript": "^1.0.4",
- "unicode-match-property-value-ecmascript": "^1.1.0"
- }
- },
- "regjsgen": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz",
- "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==",
- "dev": true
- },
- "regjsparser": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz",
- "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==",
- "dev": true,
- "requires": {
- "jsesc": "~0.5.0"
- },
- "dependencies": {
- "jsesc": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
- "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
- "dev": true
- }
- }
- },
- "remove-trailing-separator": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
- "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
- },
- "repeat-element": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
- "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g=="
- },
- "repeat-string": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
- "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
- },
- "repeating": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
- "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
- "dev": true,
- "requires": {
- "is-finite": "^1.0.0"
- }
- },
- "request": {
- "version": "2.88.0",
- "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
- "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
- "requires": {
- "aws-sign2": "~0.7.0",
- "aws4": "^1.8.0",
- "caseless": "~0.12.0",
- "combined-stream": "~1.0.6",
- "extend": "~3.0.2",
- "forever-agent": "~0.6.1",
- "form-data": "~2.3.2",
- "har-validator": "~5.1.0",
- "http-signature": "~1.2.0",
- "is-typedarray": "~1.0.0",
- "isstream": "~0.1.2",
- "json-stringify-safe": "~5.0.1",
- "mime-types": "~2.1.19",
- "oauth-sign": "~0.9.0",
- "performance-now": "^2.1.0",
- "qs": "~6.5.2",
- "safe-buffer": "^5.1.2",
- "tough-cookie": "~2.4.3",
- "tunnel-agent": "^0.6.0",
- "uuid": "^3.3.2"
- },
- "dependencies": {
- "punycode": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
- "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
- },
- "tough-cookie": {
- "version": "2.4.3",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
- "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
- "requires": {
- "psl": "^1.1.24",
- "punycode": "^1.4.1"
- }
- }
- }
- },
- "request-promise-core": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz",
- "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==",
- "requires": {
- "lodash": "^4.17.11"
- }
- },
- "request-promise-native": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz",
- "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==",
- "requires": {
- "request-promise-core": "1.1.2",
- "stealthy-require": "^1.1.1",
- "tough-cookie": "^2.3.3"
- }
- },
- "require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
- },
- "require-main-filename": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
- "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
- },
- "resolve": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz",
- "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==",
- "requires": {
- "path-parse": "^1.0.6"
- }
- },
- "resolve-cwd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
- "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
- "dev": true,
- "requires": {
- "resolve-from": "^3.0.0"
- },
- "dependencies": {
- "resolve-from": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
- "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
- "dev": true
- }
- }
- },
- "resolve-dir": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
- "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=",
- "dev": true,
- "requires": {
- "expand-tilde": "^2.0.0",
- "global-modules": "^1.0.0"
- },
- "dependencies": {
- "global-modules": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
- "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
- "dev": true,
- "requires": {
- "global-prefix": "^1.0.1",
- "is-windows": "^1.0.1",
- "resolve-dir": "^1.0.0"
- }
- }
- }
- },
- "resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true
- },
- "resolve-pathname": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz",
- "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg=="
- },
- "resolve-url": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
- "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
- },
- "restore-cursor": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
- "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
- "dev": true,
- "requires": {
- "onetime": "^2.0.0",
- "signal-exit": "^3.0.2"
- }
- },
- "ret": {
- "version": "0.1.15",
- "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
- "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
- },
- "rimraf": {
- "version": "2.6.3",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
- "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
- "dev": true,
- "requires": {
- "glob": "^7.1.3"
- }
- },
- "ripemd160": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
- "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
- "dev": true,
- "requires": {
- "hash-base": "^3.0.0",
- "inherits": "^2.0.1"
- }
- },
- "rst-selector-parser": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz",
- "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=",
- "dev": true,
- "requires": {
- "lodash.flattendeep": "^4.4.0",
- "nearley": "^2.7.10"
- }
- },
- "rsvp": {
- "version": "4.8.5",
- "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
- "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA=="
- },
- "run-async": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
- "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
- "dev": true,
- "requires": {
- "is-promise": "^2.1.0"
- }
- },
- "run-queue": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
- "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
- "dev": true,
- "requires": {
- "aproba": "^1.1.1"
- }
- },
- "rw": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
- "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q="
- },
- "rxjs": {
- "version": "6.5.3",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz",
- "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==",
- "dev": true,
- "requires": {
- "tslib": "^1.9.0"
- }
- },
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
- },
- "safe-regex": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
- "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
- "requires": {
- "ret": "~0.1.10"
- }
- },
- "safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
- },
- "sane": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz",
- "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==",
- "requires": {
- "@cnakazawa/watch": "^1.0.3",
- "anymatch": "^2.0.0",
- "capture-exit": "^2.0.0",
- "exec-sh": "^0.3.2",
- "execa": "^1.0.0",
- "fb-watchman": "^2.0.0",
- "micromatch": "^3.1.4",
- "minimist": "^1.1.1",
- "walker": "~1.0.5"
- }
- },
- "sass": {
- "version": "1.22.9",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.22.9.tgz",
- "integrity": "sha512-FzU1X2V8DlnqabrL4u7OBwD2vcOzNMongEJEx3xMEhWY/v26FFR3aG0hyeu2T965sfR0E9ufJwmG+Qjz78vFPQ==",
- "dev": true,
- "requires": {
- "chokidar": ">=2.0.0 <4.0.0"
- }
- },
- "sass-graph": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
- "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=",
- "dev": true,
- "requires": {
- "glob": "^7.0.0",
- "lodash": "^4.0.0",
- "scss-tokenizer": "^0.2.3",
- "yargs": "^7.0.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true
- },
- "camelcase": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
- "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
- "dev": true
- },
- "cliui": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
- "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
- "dev": true,
- "requires": {
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1",
- "wrap-ansi": "^2.0.0"
- }
- },
- "find-up": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
- "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
- "dev": true,
- "requires": {
- "path-exists": "^2.0.0",
- "pinkie-promise": "^2.0.0"
- }
- },
- "get-caller-file": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
- "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
- "dev": true
- },
- "invert-kv": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
- "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
- "dev": true
- },
- "is-fullwidth-code-point": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "dev": true,
- "requires": {
- "number-is-nan": "^1.0.0"
- }
- },
- "lcid": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
- "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
- "dev": true,
- "requires": {
- "invert-kv": "^1.0.0"
- }
- },
- "load-json-file": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
- "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "parse-json": "^2.2.0",
- "pify": "^2.0.0",
- "pinkie-promise": "^2.0.0",
- "strip-bom": "^2.0.0"
- }
- },
- "os-locale": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
- "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
- "dev": true,
- "requires": {
- "lcid": "^1.0.0"
- }
- },
- "path-exists": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
- "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
- "dev": true,
- "requires": {
- "pinkie-promise": "^2.0.0"
- }
- },
- "path-type": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
- "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "pify": "^2.0.0",
- "pinkie-promise": "^2.0.0"
- }
- },
- "pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
- "dev": true
- },
- "read-pkg": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
- "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
- "dev": true,
- "requires": {
- "load-json-file": "^1.0.0",
- "normalize-package-data": "^2.3.2",
- "path-type": "^1.0.0"
- }
- },
- "read-pkg-up": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
- "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
- "dev": true,
- "requires": {
- "find-up": "^1.0.0",
- "read-pkg": "^1.0.0"
- }
- },
- "require-main-filename": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
- "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
- "dev": true
- },
- "string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "dev": true,
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- }
- },
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- },
- "strip-bom": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
- "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
- "dev": true,
- "requires": {
- "is-utf8": "^0.2.0"
- }
- },
- "which-module": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
- "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
- "dev": true
- },
- "wrap-ansi": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
- "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
- "dev": true,
- "requires": {
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1"
- }
- },
- "y18n": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
- "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
- "dev": true
- },
- "yargs": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
- "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
- "dev": true,
- "requires": {
- "camelcase": "^3.0.0",
- "cliui": "^3.2.0",
- "decamelize": "^1.1.1",
- "get-caller-file": "^1.0.1",
- "os-locale": "^1.4.0",
- "read-pkg-up": "^1.0.1",
- "require-directory": "^2.1.1",
- "require-main-filename": "^1.0.1",
- "set-blocking": "^2.0.0",
- "string-width": "^1.0.2",
- "which-module": "^1.0.0",
- "y18n": "^3.2.1",
- "yargs-parser": "^5.0.0"
- }
- },
- "yargs-parser": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
- "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
- "dev": true,
- "requires": {
- "camelcase": "^3.0.0"
- }
- }
- }
- },
- "sass-loader": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.2.0.tgz",
- "integrity": "sha512-h8yUWaWtsbuIiOCgR9fd9c2lRXZ2uG+h8Dzg/AGNj+Hg/3TO8+BBAW9mEP+mh8ei+qBKqSJ0F1FLlYjNBc61OA==",
- "dev": true,
- "requires": {
- "clone-deep": "^4.0.1",
- "loader-utils": "^1.0.1",
- "neo-async": "^2.5.0",
- "pify": "^4.0.1",
- "semver": "^5.5.0"
- }
- },
- "sax": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
- "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
- },
- "scheduler": {
- "version": "0.13.6",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
- "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
- "dev": true,
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
- }
- },
- "schema-utils": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
- "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
- "dev": true,
- "requires": {
- "ajv": "^6.1.0",
- "ajv-errors": "^1.0.0",
- "ajv-keywords": "^3.1.0"
- }
- },
- "scss-tokenizer": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
- "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
- "dev": true,
- "requires": {
- "js-base64": "^2.1.8",
- "source-map": "^0.4.2"
- },
- "dependencies": {
- "source-map": {
- "version": "0.4.4",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
- "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
- "dev": true,
- "requires": {
- "amdefine": ">=0.0.4"
- }
- }
- }
- },
- "semver": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
- "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
- },
- "serialize-javascript": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.7.0.tgz",
- "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==",
- "dev": true
- },
- "set-blocking": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
- "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
- },
- "set-value": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
- "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
- "requires": {
- "extend-shallow": "^2.0.1",
- "is-extendable": "^0.1.1",
- "is-plain-object": "^2.0.3",
- "split-string": "^3.0.1"
- },
- "dependencies": {
- "extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "requires": {
- "is-extendable": "^0.1.0"
- }
- }
- }
- },
- "setimmediate": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
- "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
- "dev": true
- },
- "sha.js": {
- "version": "2.4.11",
- "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
- "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
- "dev": true,
- "requires": {
- "inherits": "^2.0.1",
- "safe-buffer": "^5.0.1"
- }
- },
- "shallow-clone": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
- "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.2"
- }
- },
- "shallowequal": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
- "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
- },
- "shebang-command": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
- "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
- "requires": {
- "shebang-regex": "^1.0.0"
- }
- },
- "shebang-regex": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
- "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
- },
- "shellwords": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
- "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
- "dev": true
- },
- "signal-exit": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
- "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
- },
- "sinon": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.4.1.tgz",
- "integrity": "sha512-7s9buHGHN/jqoy/v4bJgmt0m1XEkCEd/tqdHXumpBp0JSujaT4Ng84JU5wDdK4E85ZMq78NuDe0I3NAqXY8TFg==",
- "dev": true,
- "requires": {
- "@sinonjs/commons": "^1.4.0",
- "@sinonjs/formatio": "^3.2.1",
- "@sinonjs/samsam": "^3.3.2",
- "diff": "^3.5.0",
- "lolex": "^4.2.0",
- "nise": "^1.5.1",
- "supports-color": "^5.5.0"
- }
- },
- "sinon-chrome": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/sinon-chrome/-/sinon-chrome-3.0.1.tgz",
- "integrity": "sha512-NTEFhyuiWEMnRmIqldUiA2DhKn2EqnZxyEk5Ez5rBXj+Nl54aJ0MEmF4wjltrxecxd8zlNLxyE0HyLabev9JsQ==",
- "dev": true,
- "requires": {
- "lodash": "^4.16.3",
- "sinon": "^7.2.3",
- "urijs": "^1.18.2"
- }
- },
- "sisteransi": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.2.tgz",
- "integrity": "sha512-ZcYcZcT69nSLAR2oLN2JwNmLkJEKGooFMCdvOkFrToUt/WfcRWqhIg4P4KwY4dmLbuyXIx4o4YmPsvMRJYJd/w==",
- "dev": true
- },
- "slash": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
- "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="
- },
- "slice-ansi": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
- "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
- "dev": true,
- "requires": {
- "ansi-styles": "^3.2.0",
- "astral-regex": "^1.0.0",
- "is-fullwidth-code-point": "^2.0.0"
- }
- },
- "snapdragon": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
- "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
- "requires": {
- "base": "^0.11.1",
- "debug": "^2.2.0",
- "define-property": "^0.2.5",
- "extend-shallow": "^2.0.1",
- "map-cache": "^0.2.2",
- "source-map": "^0.5.6",
- "source-map-resolve": "^0.5.0",
- "use": "^3.1.0"
- },
- "dependencies": {
- "define-property": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
- "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
- "requires": {
- "is-descriptor": "^0.1.0"
- }
- },
- "extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "requires": {
- "is-extendable": "^0.1.0"
- }
- }
- }
- },
- "snapdragon-node": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
- "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
- "requires": {
- "define-property": "^1.0.0",
- "isobject": "^3.0.0",
- "snapdragon-util": "^3.0.1"
- },
- "dependencies": {
- "define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "requires": {
- "is-descriptor": "^1.0.0"
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- }
- }
- },
- "snapdragon-util": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
- "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
- "requires": {
- "kind-of": "^3.2.0"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "source-list-map": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
- "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
- "dev": true
- },
- "source-map": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
- },
- "source-map-resolve": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
- "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
- "requires": {
- "atob": "^2.1.1",
- "decode-uri-component": "^0.2.0",
- "resolve-url": "^0.2.1",
- "source-map-url": "^0.4.0",
- "urix": "^0.1.0"
- }
- },
- "source-map-support": {
- "version": "0.5.12",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz",
- "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==",
- "requires": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
- }
- }
- },
- "source-map-url": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
- "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM="
- },
- "spdx-correct": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
- "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
- "requires": {
- "spdx-expression-parse": "^3.0.0",
- "spdx-license-ids": "^3.0.0"
- }
- },
- "spdx-exceptions": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
- "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA=="
- },
- "spdx-expression-parse": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
- "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
- "requires": {
- "spdx-exceptions": "^2.1.0",
- "spdx-license-ids": "^3.0.0"
- }
- },
- "spdx-license-ids": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
- "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q=="
- },
- "split-string": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
- "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
- "requires": {
- "extend-shallow": "^3.0.0"
- }
- },
- "sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
- },
- "sshpk": {
- "version": "1.16.1",
- "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
- "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
- "requires": {
- "asn1": "~0.2.3",
- "assert-plus": "^1.0.0",
- "bcrypt-pbkdf": "^1.0.0",
- "dashdash": "^1.12.0",
- "ecc-jsbn": "~0.1.1",
- "getpass": "^0.1.1",
- "jsbn": "~0.1.0",
- "safer-buffer": "^2.0.2",
- "tweetnacl": "~0.14.0"
- }
- },
- "ssri": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
- "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
- "dev": true,
- "requires": {
- "figgy-pudding": "^3.5.1"
- }
- },
- "stack-utils": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz",
- "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA=="
- },
- "static-extend": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
- "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
- "requires": {
- "define-property": "^0.2.5",
- "object-copy": "^0.1.0"
- },
- "dependencies": {
- "define-property": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
- "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
- "requires": {
- "is-descriptor": "^0.1.0"
- }
- }
- }
- },
- "stdout-stream": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz",
- "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==",
- "dev": true,
- "requires": {
- "readable-stream": "^2.0.1"
- }
- },
- "stealthy-require": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
- "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
- },
- "stream-browserify": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
- "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
- "dev": true,
- "requires": {
- "inherits": "~2.0.1",
- "readable-stream": "^2.0.2"
- }
- },
- "stream-each": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
- "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
- "dev": true,
- "requires": {
- "end-of-stream": "^1.1.0",
- "stream-shift": "^1.0.0"
- }
- },
- "stream-http": {
- "version": "2.8.3",
- "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
- "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
- "dev": true,
- "requires": {
- "builtin-status-codes": "^3.0.0",
- "inherits": "^2.0.1",
- "readable-stream": "^2.3.6",
- "to-arraybuffer": "^1.0.0",
- "xtend": "^4.0.0"
- }
- },
- "stream-shift": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
- "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
- "dev": true
- },
- "string-length": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz",
- "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=",
- "dev": true,
- "requires": {
- "astral-regex": "^1.0.0",
- "strip-ansi": "^4.0.0"
- },
- "dependencies": {
- "strip-ansi": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^3.0.0"
- }
- }
- }
- },
- "string-width": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
- "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
- "dev": true,
- "requires": {
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^4.0.0"
- },
- "dependencies": {
- "strip-ansi": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^3.0.0"
- }
- }
- }
- },
- "string.prototype.trim": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.0.tgz",
- "integrity": "sha512-9EIjYD/WdlvLpn987+ctkLf0FfvBefOCuiEr2henD8X+7jfwPnyvTdmW8OJhj5p+M0/96mBdynLWkxUr+rHlpg==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.13.0",
- "function-bind": "^1.1.1"
- }
- },
- "string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- },
- "strip-ansi": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
- "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
- "requires": {
- "ansi-regex": "^4.1.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
- }
- }
- },
- "strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM="
- },
- "strip-eof": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
- "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
- },
- "strip-indent": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
- "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
- "dev": true,
- "requires": {
- "get-stdin": "^4.0.1"
- }
- },
- "strip-json-comments": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
- "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
- "dev": true
- },
- "style-loader": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz",
- "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==",
- "dev": true,
- "requires": {
- "loader-utils": "^1.1.0",
- "schema-utils": "^1.0.0"
- }
- },
- "supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "requires": {
- "has-flag": "^3.0.0"
- }
- },
- "symbol-tree": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
- "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
- },
- "table": {
- "version": "5.4.6",
- "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
- "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
- "dev": true,
- "requires": {
- "ajv": "^6.10.2",
- "lodash": "^4.17.14",
- "slice-ansi": "^2.1.0",
- "string-width": "^3.0.0"
- },
- "dependencies": {
- "string-width": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
- "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
- "dev": true,
- "requires": {
- "emoji-regex": "^7.0.1",
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^5.1.0"
- }
- }
- }
- },
- "tapable": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
- "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
- "dev": true
- },
- "tar": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
- "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
- "dev": true,
- "requires": {
- "block-stream": "*",
- "fstream": "^1.0.12",
- "inherits": "2"
- }
- },
- "terser": {
- "version": "4.1.4",
- "resolved": "https://registry.npmjs.org/terser/-/terser-4.1.4.tgz",
- "integrity": "sha512-+ZwXJvdSwbd60jG0Illav0F06GDJF0R4ydZ21Q3wGAFKoBGyJGo34F63vzJHgvYxc1ukOtIjvwEvl9MkjzM6Pg==",
- "dev": true,
- "requires": {
- "commander": "^2.20.0",
- "source-map": "~0.6.1",
- "source-map-support": "~0.5.12"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
- }
- }
- },
- "terser-webpack-plugin": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz",
- "integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==",
- "dev": true,
- "requires": {
- "cacache": "^12.0.2",
- "find-cache-dir": "^2.1.0",
- "is-wsl": "^1.1.0",
- "schema-utils": "^1.0.0",
- "serialize-javascript": "^1.7.0",
- "source-map": "^0.6.1",
- "terser": "^4.1.2",
- "webpack-sources": "^1.4.0",
- "worker-farm": "^1.7.0"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
- },
- "webpack-sources": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
- "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
- "dev": true,
- "requires": {
- "source-list-map": "^2.0.0",
- "source-map": "~0.6.1"
- }
- }
- }
- },
- "test-exclude": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz",
- "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==",
- "requires": {
- "glob": "^7.1.3",
- "minimatch": "^3.0.4",
- "read-pkg-up": "^4.0.0",
- "require-main-filename": "^2.0.0"
- },
- "dependencies": {
- "load-json-file": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
- "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
- "requires": {
- "graceful-fs": "^4.1.2",
- "parse-json": "^4.0.0",
- "pify": "^3.0.0",
- "strip-bom": "^3.0.0"
- }
- },
- "parse-json": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
- "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
- "requires": {
- "error-ex": "^1.3.1",
- "json-parse-better-errors": "^1.0.1"
- }
- },
- "path-type": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
- "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
- "requires": {
- "pify": "^3.0.0"
- }
- },
- "pify": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
- "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
- },
- "read-pkg": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
- "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
- "requires": {
- "load-json-file": "^4.0.0",
- "normalize-package-data": "^2.3.2",
- "path-type": "^3.0.0"
- }
- },
- "read-pkg-up": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz",
- "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==",
- "requires": {
- "find-up": "^3.0.0",
- "read-pkg": "^3.0.0"
- }
- }
- }
- },
- "text-table": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
- "dev": true
- },
- "throat": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz",
- "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo="
- },
- "through": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
- "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
- "dev": true
- },
- "through2": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
- "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
- "dev": true,
- "requires": {
- "readable-stream": "~2.3.6",
- "xtend": "~4.0.1"
- }
- },
- "timers-browserify": {
- "version": "2.0.11",
- "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz",
- "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==",
- "dev": true,
- "requires": {
- "setimmediate": "^1.0.4"
- }
- },
- "tiny-invariant": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
- "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA=="
- },
- "tiny-warning": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
- "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
- },
- "tmp": {
- "version": "0.0.33",
- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
- "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
- "dev": true,
- "requires": {
- "os-tmpdir": "~1.0.2"
- }
- },
- "tmpl": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
- "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE="
- },
- "to-arraybuffer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
- "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
- "dev": true
- },
- "to-fast-properties": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
- "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
- },
- "to-object-path": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
- "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "to-regex": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
- "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
- "requires": {
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "regex-not": "^1.0.2",
- "safe-regex": "^1.1.0"
- }
- },
- "to-regex-range": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
- "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
- "requires": {
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1"
- }
- },
- "tough-cookie": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
- "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
- "requires": {
- "psl": "^1.1.28",
- "punycode": "^2.1.1"
- }
- },
- "tr46": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
- "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
- "requires": {
- "punycode": "^2.1.0"
- }
- },
- "trim-newlines": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
- "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
- "dev": true
- },
- "trim-right": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
- "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM="
- },
- "true-case-path": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz",
- "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==",
- "dev": true,
- "requires": {
- "glob": "^7.1.2"
- }
- },
- "tslib": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
- "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
- "dev": true
- },
- "tty-browserify": {
- "version": "0.0.0",
- "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
- "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
- "dev": true
- },
- "tunnel-agent": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
- "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
- "requires": {
- "safe-buffer": "^5.0.1"
- }
- },
- "tweetnacl": {
- "version": "0.14.5",
- "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
- "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
- },
- "type-check": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
- "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
- "requires": {
- "prelude-ls": "~1.1.2"
- }
- },
- "type-detect": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
- "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
- "dev": true
- },
- "typedarray": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
- "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
- "dev": true
- },
- "uglify-js": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
- "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==",
- "dev": true,
- "optional": true,
- "requires": {
- "commander": "~2.20.0",
- "source-map": "~0.6.1"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "optional": true
- }
- }
- },
- "unicode-canonical-property-names-ecmascript": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
- "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==",
- "dev": true
- },
- "unicode-match-property-ecmascript": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
- "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
- "dev": true,
- "requires": {
- "unicode-canonical-property-names-ecmascript": "^1.0.4",
- "unicode-property-aliases-ecmascript": "^1.0.4"
- }
- },
- "unicode-match-property-value-ecmascript": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz",
- "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==",
- "dev": true
- },
- "unicode-property-aliases-ecmascript": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz",
- "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==",
- "dev": true
- },
- "union-value": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
- "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
- "requires": {
- "arr-union": "^3.1.0",
- "get-value": "^2.0.6",
- "is-extendable": "^0.1.1",
- "set-value": "^2.0.1"
- }
- },
- "uniq": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
- "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
- "dev": true
- },
- "unique-filename": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
- "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
- "dev": true,
- "requires": {
- "unique-slug": "^2.0.0"
- }
- },
- "unique-slug": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
- "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
- "dev": true,
- "requires": {
- "imurmurhash": "^0.1.4"
- }
- },
- "unset-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
- "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
- "requires": {
- "has-value": "^0.3.1",
- "isobject": "^3.0.0"
- },
- "dependencies": {
- "has-value": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
- "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
- "requires": {
- "get-value": "^2.0.3",
- "has-values": "^0.1.4",
- "isobject": "^2.0.0"
- },
- "dependencies": {
- "isobject": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
- "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
- "requires": {
- "isarray": "1.0.0"
- }
- }
- }
- },
- "has-values": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
- "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E="
- }
- }
- },
- "upath": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz",
- "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==",
- "dev": true
- },
- "uri-js": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
- "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
- "requires": {
- "punycode": "^2.1.0"
- }
- },
- "urijs": {
- "version": "1.19.1",
- "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.1.tgz",
- "integrity": "sha512-xVrGVi94ueCJNrBSTjWqjvtgvl3cyOTThp2zaMaFNGp3F542TR6sM3f2o8RqZl+AwteClSVmoCyt0ka4RjQOQg==",
- "dev": true
- },
- "urix": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
- "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI="
- },
- "url": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
- "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
- "dev": true,
- "requires": {
- "punycode": "1.3.2",
- "querystring": "0.2.0"
- },
- "dependencies": {
- "punycode": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
- "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
- "dev": true
- }
- }
- },
- "use": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
- "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
- },
- "util": {
- "version": "0.11.1",
- "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
- "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
- "dev": true,
- "requires": {
- "inherits": "2.0.3"
- },
- "dependencies": {
- "inherits": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
- "dev": true
- }
- }
- },
- "util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
- },
- "util.promisify": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
- "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==",
- "requires": {
- "define-properties": "^1.1.2",
- "object.getownpropertydescriptors": "^2.0.3"
- }
- },
- "uuid": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
- "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
- },
- "v8-compile-cache": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz",
- "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==",
- "dev": true
- },
- "validate-npm-package-license": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
- "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
- "requires": {
- "spdx-correct": "^3.0.0",
- "spdx-expression-parse": "^3.0.0"
- }
- },
- "value-equal": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz",
- "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw=="
- },
- "verror": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
- "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
- "requires": {
- "assert-plus": "^1.0.0",
- "core-util-is": "1.0.2",
- "extsprintf": "^1.2.0"
- }
- },
- "vm-browserify": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz",
- "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==",
- "dev": true
- },
- "w3c-hr-time": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
- "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=",
- "requires": {
- "browser-process-hrtime": "^0.1.2"
- }
- },
- "walker": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz",
- "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=",
- "requires": {
- "makeerror": "1.0.x"
- }
- },
- "warning": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
- "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
- "requires": {
- "loose-envify": "^1.0.0"
- }
- },
- "watchpack": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
- "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==",
- "dev": true,
- "requires": {
- "chokidar": "^2.0.2",
- "graceful-fs": "^4.1.2",
- "neo-async": "^2.5.0"
- },
- "dependencies": {
- "binary-extensions": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
- "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
- "dev": true
- },
- "chokidar": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz",
- "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==",
- "dev": true,
- "requires": {
- "anymatch": "^2.0.0",
- "async-each": "^1.0.1",
- "braces": "^2.3.2",
- "fsevents": "^1.2.7",
- "glob-parent": "^3.1.0",
- "inherits": "^2.0.3",
- "is-binary-path": "^1.0.0",
- "is-glob": "^4.0.0",
- "normalize-path": "^3.0.0",
- "path-is-absolute": "^1.0.0",
- "readdirp": "^2.2.1",
- "upath": "^1.1.1"
- }
- },
- "glob-parent": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
- "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
- "dev": true,
- "requires": {
- "is-glob": "^3.1.0",
- "path-dirname": "^1.0.0"
- },
- "dependencies": {
- "is-glob": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
- "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
- "dev": true,
- "requires": {
- "is-extglob": "^2.1.0"
- }
- }
- }
- },
- "is-binary-path": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
- "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
- "dev": true,
- "requires": {
- "binary-extensions": "^1.0.0"
- }
- },
- "readdirp": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
- "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.11",
- "micromatch": "^3.1.10",
- "readable-stream": "^2.0.2"
- }
- }
- }
- },
- "webidl-conversions": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
- "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="
- },
- "webpack": {
- "version": "4.39.1",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.39.1.tgz",
- "integrity": "sha512-/LAb2TJ2z+eVwisldp3dqTEoNhzp/TLCZlmZm3GGGAlnfIWDgOEE758j/9atklNLfRyhKbZTCOIoPqLJXeBLbQ==",
- "dev": true,
- "requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/helper-module-context": "1.8.5",
- "@webassemblyjs/wasm-edit": "1.8.5",
- "@webassemblyjs/wasm-parser": "1.8.5",
- "acorn": "^6.2.1",
- "ajv": "^6.10.2",
- "ajv-keywords": "^3.4.1",
- "chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^4.1.0",
- "eslint-scope": "^4.0.3",
- "json-parse-better-errors": "^1.0.2",
- "loader-runner": "^2.4.0",
- "loader-utils": "^1.2.3",
- "memory-fs": "^0.4.1",
- "micromatch": "^3.1.10",
- "mkdirp": "^0.5.1",
- "neo-async": "^2.6.1",
- "node-libs-browser": "^2.2.1",
- "schema-utils": "^1.0.0",
- "tapable": "^1.1.3",
- "terser-webpack-plugin": "^1.4.1",
- "watchpack": "^1.6.0",
- "webpack-sources": "^1.4.1"
- },
- "dependencies": {
- "acorn": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
- "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
- "dev": true
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
- },
- "webpack-sources": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
- "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
- "dev": true,
- "requires": {
- "source-list-map": "^2.0.0",
- "source-map": "~0.6.1"
- }
- }
- }
- },
- "webpack-chrome-extension-reloader": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/webpack-chrome-extension-reloader/-/webpack-chrome-extension-reloader-1.3.0.tgz",
- "integrity": "sha512-Q111eKEAUwltk/fgJPcWaGwJb8yBQ/uu/PtK8q1qlxB/AtFuubYqSAd8hlICciadtQExYefYrYaDSnTjFgZDcg==",
- "dev": true,
- "requires": {
- "colors": "^1.1.2",
- "lodash": "^4.17.4",
- "minimist": "^1.2.0",
- "webpack-sources": "^1.0.1",
- "ws": "^6.1.2"
- }
- },
- "webpack-cli": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.6.tgz",
- "integrity": "sha512-0vEa83M7kJtxK/jUhlpZ27WHIOndz5mghWL2O53kiDoA9DIxSKnfqB92LoqEn77cT4f3H2cZm1BMEat/6AZz3A==",
- "dev": true,
- "requires": {
- "chalk": "2.4.2",
- "cross-spawn": "6.0.5",
- "enhanced-resolve": "4.1.0",
- "findup-sync": "3.0.0",
- "global-modules": "2.0.0",
- "import-local": "2.0.0",
- "interpret": "1.2.0",
- "loader-utils": "1.2.3",
- "supports-color": "6.1.0",
- "v8-compile-cache": "2.0.3",
- "yargs": "13.2.4"
- },
- "dependencies": {
- "supports-color": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
- "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
- "dev": true,
- "requires": {
- "has-flag": "^3.0.0"
- }
- }
- }
- },
- "webpack-sources": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz",
- "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==",
- "dev": true,
- "requires": {
- "source-list-map": "^2.0.0",
- "source-map": "~0.6.1"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
- }
- }
- },
- "whatwg-encoding": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz",
- "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==",
- "requires": {
- "iconv-lite": "0.4.24"
- }
- },
- "whatwg-mimetype": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
- "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g=="
- },
- "whatwg-url": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
- "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
- "requires": {
- "lodash.sortby": "^4.7.0",
- "tr46": "^1.0.1",
- "webidl-conversions": "^4.0.2"
- }
- },
- "which": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
- "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
- "requires": {
- "isexe": "^2.0.0"
- }
- },
- "which-module": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
- "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
- },
- "wide-align": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
- "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
- "dev": true,
- "requires": {
- "string-width": "^1.0.2 || 2"
- }
- },
- "wordwrap": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
- "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
- },
- "worker-farm": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
- "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
- "dev": true,
- "requires": {
- "errno": "~0.1.7"
- }
- },
- "wrap-ansi": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
- "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
- "requires": {
- "ansi-styles": "^3.2.0",
- "string-width": "^3.0.0",
- "strip-ansi": "^5.0.0"
- },
- "dependencies": {
- "string-width": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
- "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
- "requires": {
- "emoji-regex": "^7.0.1",
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^5.1.0"
- }
- }
- }
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
- },
- "write": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
- "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
- "dev": true,
- "requires": {
- "mkdirp": "^0.5.1"
- }
- },
- "write-file-atomic": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz",
- "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==",
- "requires": {
- "graceful-fs": "^4.1.11",
- "imurmurhash": "^0.1.4",
- "signal-exit": "^3.0.2"
- }
- },
- "ws": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
- "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
- "dev": true,
- "requires": {
- "async-limiter": "~1.0.0"
- }
- },
- "xml-name-validator": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
- "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw=="
- },
- "xmlhttprequest": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",
- "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw="
- },
- "xtend": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
- "dev": true
- },
- "y18n": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
- "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
- },
- "yallist": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
- "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
- "dev": true
- },
- "yargs": {
- "version": "13.2.4",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz",
- "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==",
- "dev": true,
- "requires": {
- "cliui": "^5.0.0",
- "find-up": "^3.0.0",
- "get-caller-file": "^2.0.1",
- "os-locale": "^3.1.0",
- "require-directory": "^2.1.1",
- "require-main-filename": "^2.0.0",
- "set-blocking": "^2.0.0",
- "string-width": "^3.0.0",
- "which-module": "^2.0.0",
- "y18n": "^4.0.0",
- "yargs-parser": "^13.1.0"
- },
- "dependencies": {
- "string-width": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
- "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
- "dev": true,
- "requires": {
- "emoji-regex": "^7.0.1",
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^5.1.0"
- }
- }
- }
- },
- "yargs-parser": {
- "version": "13.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
- "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
- "requires": {
- "camelcase": "^5.0.0",
- "decamelize": "^1.2.0"
- }
- }
- }
-}
diff --git a/package.json b/package.json
index 6857b6f5f..b1e3c6dd5 100644
--- a/package.json
+++ b/package.json
@@ -3,79 +3,214 @@
"description": "build web extension bundle.js",
"scripts": {
"build": "webpack --mode production",
- "dev": "webpack --mode development --watch",
- "test": "jest --verbose --coverage --watchAll",
- "lint": "eslint --ext .js --ext .jsx src",
- "docker-build": "docker build -t reacttt/test-lint .",
- "docker-check": "docker-compose up --abort-on-container-exit",
- "docker-test-lint": "eslint --ext .js --ext .jsx src package && jest --verbose"
+ "dev": "cross-env NODE_ENV=development webpack --mode development --watch",
+ "buildlegacy": "NODE_OPTIONS=--openssl-legacy-provider webpack --mode production",
+ "devlegacy": "NODE_OPTIONS=--openssl-legacy-provider webpack --mode development --watch",
+ "test": "jest --verbose --coverage",
+ "test-backend": "jest --verbose --coverage src/backend",
+ "test-frontend": "jest --verbose --coverage src/app",
+ "test-on": "./node_modules/.bin/jest $1",
+ "docker-test-lint": "eslint --ext .js --ext .jsx src",
+ "docs": "typedoc",
+ "format": "prettier --config .prettierrc './**/*.{ts,tsx,js,jsx}' --write"
},
"keywords": [
"react",
- "time",
- "time travel",
- "reactime"
+ "useState",
+ "state",
+ "debug",
+ "react dev tool",
+ "reactime",
+ "performance",
+ "gatsby",
+ "next.js",
+ "rendering",
+ "optimization"
],
"repository": {
"type": "git",
- "url": "https://github.com/oslabs-beta/reactime"
+ "url": "https://github.com/open-source-labs/reactime"
},
"contributors": [
+ "Jesse Guerrero",
+ "Oliver Cho",
+ "Eva Ury",
+ "Amy Yang",
+ "Abaas Khorrami",
+ "Alex Gomez",
+ "Alexander Landeros",
+ "Ali Rahman",
+ "Andrew Byun",
+ "Andy Tsou",
"Andy Wong",
+ "Becca Viner",
+ "Ben Margolius",
"Bryan Lee",
+ "Caitlin Chan",
+ "Caner Demir",
+ "Carlos Perez",
+ "Chris Flannery",
+ "Chris Guizzetti",
+ "Christopher LeBrett",
+ "Christopher Stamper",
+ "Cole Styron",
+ "Daljit Gill",
+ "Dane Corpion",
+ "Daniel Ryczek",
+ "David Bernstein",
"David Chai",
+ "David Kim",
+ "David Moore",
+ "Dennis Lopez",
+ "Edar Liu",
+ "Edwin Menendez",
+ "Eivind Del Fierro",
+ "Ellie Simens",
+ "Ergi Shehu",
+ "Eric Yun",
+ "Freya Wu",
+ "Gabriela Jardim Aquino",
+ "Garrett Chow",
+ "Gregory Panciera",
+ "Haejin Jo",
+ "Harry Fox",
+ "Hien Nguyen",
+ "Jack Crish",
+ "Jackie Yuan",
+ "James McCollough",
+ "James Nghiem",
+ "Jasmine Noor",
+ "Jason Victor",
+ "Jesse Rosengrant",
+ "Jimmy Phy",
+ "John Banks",
+ "Joseph Park",
+ "Joseph Stern",
"Josh Kim",
+ "Joshua Howard",
+ "Kelvin Mirhan",
+ "Kevin Fey",
+ "Kevin HoEun Lee",
+ "Kevin Ngo",
+ "Kim Mai Nguyen",
+ "Kris Sorensen",
+ "Kristina Wallen",
+ "Kyle Bell",
+ "Lance Ziegler",
+ "Liam Donaher",
+ "Lina Shin",
+ "Mark Teets",
+ "Mike Bednarz",
+ "Minzo Kim",
+ "Morah Geist",
+ "Nathanael Wa Mwenze",
+ "Nathan Richardson",
+ "Ngoc Zwolinski",
+ "Nick Huemmer",
+ "Patrice Pinardo",
+ "Peter Lam",
+ "Prasanna Malla",
+ "Quan Le",
+ "Ragad Mohammed",
+ "Rajeeb Banstola",
+ "Raymond Kwan",
+ "Robby Tipton",
+ "Robert Maeda",
+ "Rocky Lin",
"Ruth Anam",
"Ryan Dang",
+ "Sergei Liubchenko",
+ "Sean Kelly",
"Sierra Swaby",
- "Yujin Kang"
+ "Tania Lind",
+ "Viet Nguyen",
+ "Vincent Nguyen",
+ "Wilton Lee",
+ "Yididia Ketema",
+ "Yujin Kang",
+ "Zachary Freeman"
],
"license": "ISC",
"devDependencies": {
- "@babel/core": "^7.5.5",
- "@babel/plugin-proposal-class-properties": "^7.5.5",
- "@babel/plugin-proposal-decorators": "^7.4.4",
- "@babel/preset-env": "^7.5.5",
- "@babel/preset-react": "^7.0.0",
- "babel-loader": "^8.0.6",
- "css-loader": "^3.2.0",
- "enzyme": "^3.10.0",
- "enzyme-adapter-react-16": "^1.14.0",
- "eslint": "^6.5.1",
- "eslint-config-airbnb": "^18.0.1",
- "eslint-plugin-import": "^2.18.2",
- "eslint-plugin-jest": "^22.15.0",
- "eslint-plugin-jsx-a11y": "^6.2.3",
- "eslint-plugin-react": "^7.15.1",
- "eslint-plugin-react-hooks": "^1.7.0",
- "jest": "^24.9.0",
- "jest-cli": "^24.8.0",
- "jest-runner-eslint": "^0.7.4",
- "node-sass": "^4.12.0",
- "sass": "^1.22.9",
- "sass-loader": "^7.2.0",
- "sinon-chrome": "^3.0.1",
- "style-loader": "^0.23.1",
- "webpack": "^4.39.1",
- "webpack-chrome-extension-reloader": "^1.3.0",
- "webpack-cli": "^3.3.6"
+ "@babel/parser": "^7.26.2",
+ "@babel/types": "^7.26.0",
+ "@testing-library/jest-dom": "^6.1.5",
+ "@testing-library/react": "^14.1.2",
+ "@testing-library/user-event": "^14.5.1",
+ "@types/chrome": "^0.0.254",
+ "@types/jest": "^29.5.11",
+ "@types/node": "^20.10.5",
+ "@typescript-eslint/eslint-plugin": "^6.15.0",
+ "copy-webpack-plugin": "^11.0.0",
+ "css-loader": "^6.8.1",
+ "eslint-plugin-jest": "^27.6.0",
+ "eslint-plugin-jest-dom": "^5.1.0",
+ "eslint-plugin-react": "^7.33.2",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-testing-library": "^6.2.0",
+ "html-webpack-plugin": "^5.5.4",
+ "identity-obj-proxy": "^3.0.0",
+ "jest": "^29.7.0",
+ "jsdom": "^23.0.1",
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^6.21.1",
+ "sass": "^1.83.0",
+ "sass-loader": "^16.0.4",
+ "style-loader": "^3.3.3",
+ "ts-jest": "^29.1.2",
+ "ts-loader": "^9.5.1",
+ "typedoc": "^0.25.4",
+ "typescript": "^5.3.3",
+ "webpack": "^5.89.0",
+ "webpack-cli": "^5.1.4"
},
"dependencies": {
- "bower": "^1.8.8",
- "d3": "^4.13.0",
- "d3-tip": "^0.9.1",
- "d3-zoom": "^1.8.3",
- "immer": "^3.2.0",
- "jest-runner": "^24.9.0",
- "jsondiffpatch": "^0.3.11",
- "prop-types": "^15.7.2",
- "rc-slider": "^8.6.13",
- "rc-tooltip": "^3.7.3",
- "react": "^16.9.0",
- "react-dom": "^16.9.0",
- "react-html-parser": "^2.0.2",
- "react-json-tree": "^0.11.2",
- "react-router-dom": "^5.0.1",
- "react-select": "^3.0.4"
+ "@emotion/react": "^11.11.1",
+ "@emotion/styled": "^11.11.0",
+ "@mui/icons-material": "^5.15.1",
+ "@mui/material": "^5.15.1",
+ "@mui/system": "^5.15.1",
+ "@reduxjs/toolkit": "^2.0.1",
+ "@visx/axis": "^3.5.0",
+ "@visx/event": "^3.3.0",
+ "@visx/gradient": "^3.3.0",
+ "@visx/grid": "^3.5.0",
+ "@visx/group": "^3.3.0",
+ "@visx/hierarchy": "^3.3.0",
+ "@visx/responsive": "^3.3.0",
+ "@visx/scale": "^3.5.0",
+ "@visx/shape": "^3.5.0",
+ "@visx/text": "^3.3.0",
+ "@visx/tooltip": "^3.3.0",
+ "cross-env": "^7.0.3",
+ "d3": "^7.8.5",
+ "d3-scale-chromatic": "^3.0.0",
+ "d3-shape": "^3.2.0",
+ "dotenv": "^16.3.1",
+ "express": "^4.18.2",
+ "html-react-parser": "^5.0.11",
+ "intro.js": "^7.2.0",
+ "intro.js-react": "^1.0.0",
+ "jest-environment-jsdom": "^29.7.0",
+ "jsondiffpatch": "^0.5.0",
+ "lodash": "^4.17.21",
+ "lucide-react": "^0.468.0",
+ "node": "^18.12.1",
+ "prettier": "^3.1.1",
+ "rc-slider": "^10.5.0",
+ "rc-tooltip": "^6.1.3",
+ "react": "^18.2.0",
+ "react-apexcharts": "^1.4.1",
+ "react-hot-toast": "^2.4.1",
+ "react-hover": "^3.0.1",
+ "react-json-tree": "^0.18.0",
+ "react-redux": "^9.0.4",
+ "react-select": "^5.8.0",
+ "react-spinners": "^0.13.8",
+ "redux": "^5.0.0",
+ "redux-mock-store": "^1.5.5",
+ "regenerator-runtime": "^0.14.1",
+ "styled-components": "^6.1.2",
+ "web-vitals": "^3.5.0"
}
}
diff --git a/package/__tests__/astParser.test.js b/package/__tests__/astParser.test.js
deleted file mode 100644
index 96a40166b..000000000
--- a/package/__tests__/astParser.test.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// import { configure, shallow } from 'enzyme';
-
-// Test 1: Should take in a function definition with 1 hook
-// and return an object with the getter / setter
-// EXAMPLE INPUT FOR TEST
-/**
- * function UseStateHookTest() {
- * const [testCount, setTestCount] = useState(0);
- * return (
- *
- *
You clicked this {useStateCount} times
- *
setTestCount(testCount + 1)}>+1
- *
setTestCount(testCount - 1)}>-1
- *
- *
- * );
- * }
-*/
-
-// EXPECTED RESULT of astParser(input)
-/**
- * {
- * _useState: "testCount",
- * _useState2: "setTestCount"
- * }
- */
-
-// TEST 2: Should take in multiple function definitions
-// with hooks and return an object with all 4 properties
-// TEST 3: Should ignore any non-hook definitions
-// Test 4: Should return an empty object if no hooks found
-// Test 5: Should throw an error if input is invalid javascript
-
-describe('placeholder', () => {
- it.skip('placeholder for tests', () => {
- expect(1 + 1).toEqual(2);
- });
-});
diff --git a/package/__tests__/index.test.js b/package/__tests__/index.test.js
deleted file mode 100644
index 1579cdddb..000000000
--- a/package/__tests__/index.test.js
+++ /dev/null
@@ -1,28 +0,0 @@
-const timeJumpRequire = require('../timeJump');
-
-jest.mock('../timeJump');
-const timeJump = jest.fn();
-timeJumpRequire.mockReturnValue(timeJump);
-
-const index = require('../index');
-
-describe('unit testing for index.js', () => {
- test('index.js should be exporting a function', () => {
- expect(typeof index).toBe('function');
- });
-
- test('index.js should be calling timeJump for every jumpToSnap message', done => {
- const calls = 10;
- let count = 0;
- global.addEventListener('message', ({ data: { action } }) => {
- if (action === 'jumpToSnap') {
- count += 1;
- expect(timeJump.mock.calls.length).toBe(count);
- if (count === calls) done();
- }
- });
- for (let i = 0; i < calls; i += 1) {
- global.postMessage({ action: 'jumpToSnap', payload: ['test'] }, '*');
- }
- });
-});
diff --git a/package/__tests__/linkFiber.test.js b/package/__tests__/linkFiber.test.js
deleted file mode 100644
index fd3032352..000000000
--- a/package/__tests__/linkFiber.test.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/* eslint-disable react/jsx-filename-extension */
-import React, { Component } from 'react';
-import { render } from 'react-dom';
-
-const linkFiberRequire = require('../linkFiber');
-
-let linkFiber;
-let mode;
-let snapShot;
-
-class App extends Component {
- constructor(props) {
- super(props);
- this.state = { foo: 'bar' };
- }
-
- render() {
- const { foo } = this.state;
- return {foo}
;
- }
-}
-
-// Need to create a functioanl component instance to test
-// Would need to be revised but here's the gist
-// const funcComponent = () => {
-// const [ number, setNumber ] = useState(0);
-// const newNumber = setNumber(1);
-
-// return (
-// {newNumber}
-// )
-// }
-
-describe('unit test for linkFiber', () => {
- beforeEach(() => {
- snapShot = { tree: null };
- mode = {
- jumping: false,
- paused: false,
- locked: false,
- };
- linkFiber = linkFiberRequire(snapShot, mode);
-
- const container = document.createElement('div');
- render( , container);
- linkFiber(container);
- });
-
- test('linkFiber should mutate the snapshot tree property', () => {
- // linkFiber mutates the snapshot
- expect(typeof snapShot.tree).toBe('object');
- expect(snapShot.tree.component.state).toBe('root');
- expect(snapShot.tree.children).toHaveLength(1);
- expect(snapShot.tree.children[0].component.state.foo).toBe('bar');
- });
-
- test('linkFiber should modify the setState of the stateful component', () => {
- expect(snapShot.tree.children[0].component.setState.linkFiberChanged).toBe(true);
- });
-});
diff --git a/package/__tests__/timeJump.test.js b/package/__tests__/timeJump.test.js
deleted file mode 100644
index 215498dba..000000000
--- a/package/__tests__/timeJump.test.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/* eslint-disable max-classes-per-file */
-const timeJumpRequire = require('../timeJump');
-
-class Component {
- constructor(mockfn) {
- this.mockfn = mockfn;
- }
-
- setState(state, func = () => { }) {
- this.mockfn(state);
- func();
- }
-}
-
-class FiberNode {
- constructor(mockfn, state) {
- this.state = state;
- this.children = [];
- this.component = new Component(mockfn);
- }
-}
-
-// MVP FEATURE: Additional Testing
-// Testing for useState and useContext
-
-
-describe('unit testing for timeJump.js', () => {
- let timeJump;
- let snapShot;
- let mode;
- let mockFuncs;
-
- beforeEach(() => {
- mode = { jumping: false };
- mockFuncs = [];
- for (let i = 0; i < 4; i += 1) mockFuncs.push(jest.fn());
-
- const tree = new FiberNode(mockFuncs[0]);
- tree.children = [
- new FiberNode(mockFuncs[1]),
- new FiberNode(mockFuncs[2]),
- new FiberNode(mockFuncs[3]),
- ];
-
- snapShot = { tree };
- timeJump = timeJumpRequire(snapShot, mode);
- });
-
- test('calling the initial require should return a function', () => {
- expect(typeof timeJumpRequire).toBe('function');
- });
-
- describe('testing iteration through snapshot tree', () => {
- const states = ['root', 'firstChild', 'secondChild', 'thirdChild'];
- const target = new FiberNode(null, states[0]);
- target.children = [
- new FiberNode(null, states[1]),
- new FiberNode(null, states[2]),
- new FiberNode(null, states[3]),
- ];
-
- beforeEach(() => {
- timeJump(target);
- });
- test('timeJump should call setState on each state in origin', () => {
- mockFuncs.forEach(mockFunc => expect(mockFunc.mock.calls.length).toBe(1));
- });
-
- test('timeJump should pass target state to origin setState', () => {
- mockFuncs.forEach((mockFunc, i) => expect(mockFunc.mock.calls[0][0]).toBe(states[i]));
- });
- });
-
- test('jumping mode should be set while timeJumping', () => {
- const logMode = jest.fn();
- logMode.mockImplementation(() => expect(mode.jumping).toBe(true));
-
- snapShot.tree = new FiberNode(logMode);
- const target = new FiberNode(null, 'test');
- timeJump(target);
- expect(logMode).toHaveBeenCalled();
- });
-});
diff --git a/package/astParser.js b/package/astParser.js
deleted file mode 100644
index cc9986e9d..000000000
--- a/package/astParser.js
+++ /dev/null
@@ -1,38 +0,0 @@
-const acorn = require('acorn');
-const jsx = require('acorn-jsx');
-
-const JSXParser = acorn.Parser.extend(jsx());
-
-// Helper function to grab the getters/setters from `elementType`
-module.exports = elementType => {
- // Initialize empty object to store the setters and getter
- let ast = JSXParser.parse(elementType);
- const hookState = {};
- // All module exports will always start off as a single 'FunctionDeclaration' type
- while (Object.hasOwnProperty.call(ast, 'body')) {
- // Traverse down .body once before invoking parsing logic and will loop through any .body after
- ast = ast.body;
- // Iterate through AST of every function declaration
- // Check within each function declaration if there are hook declarations
- ast.forEach(functionDec => {
- const { body } = functionDec.body;
- const statements = [];
- // Traverse through the function's funcDecs and Expression Statements
- body.forEach(program => {
- // Hook Declarations will only be under 'VariableDeclaration' type
- if (program.type === 'VariableDeclaration') {
- program.declarations.forEach(dec => {
- statements.push(dec.id.name);
- });
- }
- });
- // Iterate through the array and determine getter/setters based on pattern
- for (let i = 0; i < statements.length; i += 1) {
- if (statements[i].match(/_use/)) {
- hookState[statements[i]] = statements[i + 2];
- }
- }
- });
- }
- return hookState;
-};
diff --git a/package/demo.gif b/package/demo.gif
deleted file mode 100644
index 175888a3a..000000000
Binary files a/package/demo.gif and /dev/null differ
diff --git a/package/index.js b/package/index.js
deleted file mode 100644
index 812937bc2..000000000
--- a/package/index.js
+++ /dev/null
@@ -1,28 +0,0 @@
-const snapShot = { tree: null };
-
-const mode = {
- jumping: false,
- paused: false,
- locked: false,
-};
-
-const linkFiber = require('./linkFiber')(snapShot, mode);
-const timeJump = require('./timeJump')(snapShot, mode);
-
-window.addEventListener('message', ({ data: { action, payload } }) => {
- switch (action) {
- case 'jumpToSnap':
- timeJump(payload);
- break;
- case 'setLock':
- mode.locked = payload;
- break;
- case 'setPause':
- mode.paused = payload;
- break;
- default:
- break;
- }
-});
-
-module.exports = linkFiber;
diff --git a/package/linkFiber.js b/package/linkFiber.js
deleted file mode 100644
index 237117da6..000000000
--- a/package/linkFiber.js
+++ /dev/null
@@ -1,150 +0,0 @@
-/* eslint-disable func-names */
-/* eslint-disable no-use-before-define */
-/* eslint-disable no-param-reassign */
-// links component state tree to library
-// changes the setState method to also update our snapshot
-const Tree = require('./tree');
-const astParser = require('./astParser');
-const { saveState } = require('./masterState');
-
-module.exports = (snap, mode) => {
- let fiberRoot = null;
- let astHooks;
-
- function sendSnapshot() {
- // don't send messages while jumping or while paused
- // DEV: So that when we are jumping to an old snapshot it
- // wouldn't think we want to create new snapshots
- if (mode.jumping || mode.paused) return;
- const payload = snap.tree.getCopy();
- // console.log('payload', payload);
- window.postMessage({
- action: 'recordSnap',
- payload,
- });
- }
-
- function changeSetState(component) {
- // check that setState hasn't been changed yet
- if (component.setState.linkFiberChanged) return;
- // make a copy of setState
- const oldSetState = component.setState.bind(component);
- // replace component's setState so developer doesn't change syntax
- // component.setState = newSetState.bind(component);
- component.setState = (state, callback = () => { }) => {
- // don't do anything if state is locked
- // UNLESS we are currently jumping through time
- if (mode.locked && !mode.jumping) return;
- // continue normal setState functionality, except add sending message middleware
- oldSetState(state, () => {
- updateSnapShotTree();
- sendSnapshot();
- callback.bind(component)();
- });
- };
- component.setState.linkFiberChanged = true;
- }
-
- function changeUseState(component) {
- if (component.queue.dispatch.linkFiberChanged) return;
- // store the original dispatch function definition
- const oldDispatch = component.queue.dispatch.bind(component.queue);
- // redefine the dispatch function so we can inject our code
- component.queue.dispatch = (fiber, queue, action) => {
- // don't do anything if state is locked
- if (mode.locked && !mode.jumping) return;
- oldDispatch(fiber, queue, action);
- setTimeout(() => {
- updateSnapShotTree();
- sendSnapshot();
- }, 100);
- };
- component.queue.dispatch.linkFiberChanged = true;
- }
-
- // Helper function to traverse through the memoized state
- // TODO: WE NEED TO CLEAN IT UP A BIT
- function traverseHooks(memoizedState) {
- // Declare variables and assigned to 0th index and an empty object, respectively
- const memoized = {};
- let index = 0;
- astHooks = Object.values(astHooks);
- // while memoizedState is truthy, save the value to the object
- while (memoizedState) {
- changeUseState(memoizedState);
- // memoized[astHooks[index]] = memoizedState.memoizedState;
- memoized[astHooks[index]] = memoizedState.memoizedState;
- // Reassign memoizedState to its next value
- memoizedState = memoizedState.next;
- // Increment the index by 2
- index += 2;
- }
- return memoized;
- }
-
- function createTree(currentFiber, tree = new Tree('root')) {
- if (!currentFiber) return tree;
-
- const {
- sibling,
- stateNode,
- child,
- memoizedState,
- elementType,
- } = currentFiber;
-
- let nextTree = tree;
- // check if stateful component
- if (stateNode && stateNode.state) {
- // add component to tree
- nextTree = tree.appendChild(stateNode);
- // change setState functionality
- changeSetState(stateNode);
- }
- // Check if the component uses hooks
- if (memoizedState && Object.hasOwnProperty.call(memoizedState, 'baseState')) {
- // Traverse through the currentFiber and extract the getters/setters
- astHooks = astParser(elementType);
- saveState(astHooks);
- // Create a traversed property and assign to the evaluated result of
- // invoking traverseHooks with memoizedState
- memoizedState.traversed = traverseHooks(memoizedState);
- nextTree = tree.appendChild(memoizedState);
- }
- // iterate through siblings
- createTree(sibling, tree);
- // iterate through children
- createTree(child, nextTree);
-
- return tree;
- }
- // runs when page initially loads
- // but skips 1st hook click
- function updateSnapShotTree() {
- const { current } = fiberRoot;
- snap.tree = createTree(current);
- }
-
- return container => {
- const {
- _reactRootContainer: { _internalRoot },
- _reactRootContainer,
- } = container;
- // only assign internal rootp if it actually exists
- fiberRoot = _internalRoot || _reactRootContainer;
-
- updateSnapShotTree();
- // send the initial snapshot once the content script has started up
- window.addEventListener('message', ({ data: { action } }) => {
- if (action === 'contentScriptStarted') sendSnapshot();
- });
- // Testing sending back a function def to client
- // function getNextImport(filePath) {
- // return loadable(() => import(`${filePath}`))
- // Got relative file path to return back to client code
- // return (`myTestString${filePath}`);
- // return getNextImport('./UseStateHook');
- // return 'Testing outside';
- // const OtherComponent = loadable(() => import('./OtherComponent'))
- };
-};
diff --git a/package/masterState.js b/package/masterState.js
deleted file mode 100644
index 5e740f45b..000000000
--- a/package/masterState.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/* eslint-disable guard-for-in */
-/* eslint-disable no-restricted-syntax */
-// Export two functions that either saves the AST state object into an array
-// or returns the array for use
-const masterState = [];
-
-module.exports = {
- saveState: state => {
- for (const key in state) {
- masterState.push(state[key]);
- }
- return masterState;
- },
- returnState: () => masterState,
-};
diff --git a/package/package-lock.json b/package/package-lock.json
deleted file mode 100644
index fd45598f4..000000000
--- a/package/package-lock.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "reactime",
- "version": "2.0.4",
- "lockfileVersion": 1,
- "requires": true,
- "dependencies": {
- "acorn": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
- "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ=="
- },
- "acorn-jsx": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz",
- "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw=="
- }
- }
-}
diff --git a/package/package.json b/package/package.json
deleted file mode 100644
index b6c9baa23..000000000
--- a/package/package.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "name": "reactime",
- "version": "2.0.4",
- "description": "A library that helps debug React by memorizing the state of components with every render.",
- "main": "index.js",
- "repository": {
- "type": "git",
- "url": "https://github.com/oslabs-beta/reactime"
- },
- "scripts": {
- "test": "echo \"Error: no test specified\""
- },
- "peerDependencies": {
- "react": "~16.0.0",
- "react-dom": "~16.0.0"
- },
- "keywords": [
- "react",
- "state",
- "useState",
- "setState",
- "debug",
- "reactime",
- "react devtool"
- ],
- "contributors": [
- "Andy Wong",
- "Bryan Lee",
- "David Chai",
- "Josh Kim",
- "Ruthba Anam",
- "Ryan Dang",
- "Sierra Swaby",
- "Yujin Kang"
- ],
- "license": "MIT",
- "dependencies": {
- "acorn": "^7.1.0",
- "acorn-jsx": "^5.0.2"
- }
-}
diff --git a/package/readme.md b/package/readme.md
deleted file mode 100644
index ab7a97f15..000000000
--- a/package/readme.md
+++ /dev/null
@@ -1,97 +0,0 @@
-
-
-
-
-# Reactime: A Time Travel Debugger for React
-
-[](https://github.com/oslabs-beta/reactime)
-[](https://travis-ci.com/oslabs-beta/reactime)
-[](http://badge.fury.io/js/reactime)
-[](https://david-dm.org/oslabs-beta/reactime#info=dependencies)
-[](https://david-dm.org/oslabs-beta/reactime?type=dev)
-[](https://snyk.io/test/github/oslabs-beta/reactime)
-
-[](https://nodei.co/npm/reactime/)
-
-
-
-
-
-Reactime is a debugging tool for React developers. It records state whenever state is changed and allows user to jump to any previous recorded state.
-
-One important thing to note. This devtool is for React apps using only stateful components and prop drilling. If you're using Redux, Hooks, Context, or functional components, this devtool will not function on your app.
-
-Another thing is that this library does not work well when mixing React with direct DOM manipulation. Since DOM manipulation doesn't change any React state, this library cannot record or even detect that change. Of course, you should be avoiding mixing the two in the first place.
-
-Two parts are needed for this tool to function. The chrome extension must be installed, and the NPM package must be installed and used in the React code.
-
-After successfully installing the chrome extension, you can test Reactime functionalities in the demo repositories below.
-
-- Calculator
-- Bitcoin Price Index
-
-## Installing
-
-1. Download the [extension](https://chrome.google.com/webstore/detail/reactime/cgibknllccemdnfhfpmjhffpjfeidjga) from Chrome Web Store.
-
-2. Install the [npm package](https://www.npmjs.com/package/reactime) in your code.
-
-```
-npm i reactime
-```
-
-3. Call the library method on your root container after rendering your App.
-
-```
-const reactime = require('reactime');
-
-const rootContainer = document.getElementById('root');
-ReactDOM.render( , rootContainer);
-
-reactime(rootContainer);
-```
-
-4. Done! That's all you have to do to link your React project to our library.
-
-## How to Use
-
-After installing both the Chrome extension and the npm package, just open up your project in the browser.
-
-Then open up your Chrome DevTools. There'll be a new tab called Reactime.
-
-## Features
-
-### Recording
-
-Whenever state is changed (whenever setState is called), this extension will create a snapshot of the current state tree and record it. Each snapshot will be displayed in Chrome DevTools under the Reactime panel.
-
-### Viewing
-
-You can click on a snapshot to view your app's state. State can be visualized in a JSON or a tree. Also, snapshots can be diffed with the previous snapshot, which can be viewed under the Diff tab.
-
-### Jumping
-
-Jumping is the most important feature of all. It allows you to jump to any previous recorded snapshots. Hitting the jump button on any snapshot will change the DOM by setting their state.
-
-### Others
-
-Other handy features include:
-
-- multiple tabs support
-- a slider to move through snapshots quickly
-- a play button to move through snapshots automatically
-- a pause which button stops recording each snapshot
-- a lock button to freeze the DOM in place. setState will lose all functionality while the extension is locked
-- a persist button to keep snapshots upon refresh (handy when changing code and debugging)
-- export/import the current snapshots in memory
-
-## Authors
-
-- **Ryan Dang** - [@rydang](https://github.com/rydang)
-- **Bryan Lee** - [@mylee1995](https://github.com/mylee1995)
-- **Josh Kim** - [@joshua0308](https://github.com/joshua0308)
-- **Sierra Swaby** - [@starkspark](https://github.com/starkspark)
-
-## License
-
-This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
diff --git a/package/timeJump.js b/package/timeJump.js
deleted file mode 100644
index a06c8656f..000000000
--- a/package/timeJump.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/* eslint-disable no-param-reassign */
-// traverses given tree by accessing children through coords array
-const { returnState } = require('./masterState');
-
-function traverseTree(tree, coords) {
- let curr = tree;
- coords.forEach(coord => {
- curr = curr.children[coord];
- });
- return curr;
-}
-
-module.exports = (origin, mode) => {
- // recursively change state of tree
- function jump(target, coords = []) {
- const originNode = traverseTree(origin.tree, coords);
- // set the state of the origin tree if the component is stateful
- if (originNode.component.setState) {
- originNode.component.setState(target.state, () => {
- // iterate through new children once state has been set
- target.children.forEach((child, i) => {
- jump(child, coords.concat(i));
- });
- });
- } else {
- // if component uses hooks, traverse through the memoize tree
- let current = originNode.component;
- let index = 0;
- const hooks = returnState();
- // while loop through the memoize tree
- while (current) {
- current.queue.dispatch(target.state[hooks[index]]);
- // Reassign the current value
- current = current.next;
- index += 2;
- }
- }
- }
-
- return target => {
- // setting mode disables setState from posting messages to window
- mode.jumping = true;
- jump(target);
- setTimeout(() => {
- mode.jumping = false;
- }, 100);
- };
-};
diff --git a/package/tree.js b/package/tree.js
deleted file mode 100644
index 55929f4fd..000000000
--- a/package/tree.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/* eslint-disable no-console */
-/* eslint-disable no-param-reassign */
-
-// this is the current snapshot that is being sent to the snapshots array. it is an object
-class Tree {
- constructor(component, useStateInstead = false, name) {
- // special case when component is root
- // give it a special state = 'root'
- // a setState function that just calls the callback instantly
- if (!useStateInstead) {
- this.component = component === 'root'
- ? { state: 'root', setState: (partial, callback) => callback() }
- : component;
- } else {
- this.state = component;
- this.name = name;
- }
- this.children = [];
- // DEV: Added print() for debugging purposes
- // this.print();
- }
-
- appendChild(component) {
- const child = new Tree(component);
- this.children.push(child);
- return child;
- }
-
- // deep copies only the state of each component and creates a new tree
- getCopy(copy = new Tree('root', true)) {
- // copy state of children
- copy.children = this.children.map(
- child => new Tree(child.component.state
- || child.component.traversed, true, child.component.constructor.name),
- );
-
- // copy children's children recursively
- this.children.forEach((child, i) => child.getCopy(copy.children[i]));
- return copy;
- }
-
- // print out the tree in the console
- // DEV: Process may be different for useState components
- // BUG FIX: Don't print the Router as a component
- // Change how the children are printed
- print() {
- const children = ['children: '];
- // DEV: What should we push instead for components using hooks (it wouldn't be state)
- this.children.forEach(child => {
- children.push(child.state || child.component.state);
- });
- if (this.name) console.log(this.name);
- if (children.length === 1) {
- console.log(this.state || this.component.state);
- } else console.log(this.state || this.component.state, ...children);
- this.children.forEach(child => {
- child.print();
- });
- }
-}
-
-module.exports = Tree;
diff --git a/readme.md b/readme.md
deleted file mode 100644
index 823b3bc77..000000000
--- a/readme.md
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-
-State Debugger for React
-
-[](https://github.com/oslabs-beta/reactime)
-[](https://travis-ci.com/oslabs-beta/reactime)
-[](http://badge.fury.io/js/reactime)
-[](https://david-dm.org/oslabs-beta/reactime#info=dependencies)
-[](https://david-dm.org/oslabs-beta/reactime?type=dev)
-[](https://snyk.io/test/github/oslabs-beta/reactime)
-
-
-
-
-
-
-Reactime is a debugging tool for React developers. It records state whenever it is changed and allows the user to jump to any previously recorded state.
-
-This dev tool is for React apps using stateful components and prop drilling, and has beta support for Context API, conditional state routing, Hooks (useState) and functional components.
-
-One thing to note is that this library does not work well when mixing React with direct DOM manipulation. Since DOM manipulation doesn't change any React state, this library cannot record or even detect that change. Of course, you should be avoiding mixing the two in the first place.
-
-Two parts are needed for this tool to function. The chrome extension must be installed, and the NPM package must be installed and used in the React code.
-
-After successfully installing the chrome extension, you can test Reactime functionalities in the demo repositories below.
-
-- Calculator
-- Bitcoin Price Index
-
-## Installing
-
-1. Download the [extension](https://chrome.google.com/webstore/detail/reactime/cgibknllccemdnfhfpmjhffpjfeidjga) from Chrome Web Store.
-
-2. Install the [npm package](https://www.npmjs.com/package/reactime) in your code.
-
-```
-npm i reactime
-```
-
-3. Call the library method on your root container and App after rendering your App.
-
-```
-const reactime = require('reactime');
-
-const rootContainer = document.getElementById('root');
-ReactDOM.render( , rootContainer);
-
-reactime(rootContainer, App);
-```
-
-4. Done! That's all you have to do to link your React project to our library.
-
-## How to Use
-
-After installing both the Chrome extension and the npm package, just open up your project in the browser.
-
-Then open up your Chrome DevTools. There'll be a new tab called Reactime.
-
-## Features
-
-### Recording
-
-Whenever state is changed (whenever setState or useState is called), this extension will create a snapshot of the current state tree and record it. Each snapshot will be displayed in Chrome DevTools under the Reactime panel.
-
-### Viewing
-
-You can click on a snapshot to view your app's state. State can be visualized in a JSON or a tree. Also, snapshots can be diffed with the previous snapshot, which can be viewed under the Diff tab.
-
-### Jumping
-
-Jumping is the most important feature of all. It allows you to jump to any previous recorded snapshots. Hitting the jump button on any snapshot will change the DOM by setting their state.
-
-### And Much More
-
-- multiple tree graph branches depicting state changes
-- tree graph hover functionality to view state changes
-- ability to pan and zoom tree graph
-- multiple tabs support
-- a slider to move through snapshots quickly
-- a play button to move through snapshots automatically
-- a pause button, which stops recording each snapshot
-- a lock button to freeze the DOM in place. setState will lose all functionality while the extension is locked
-- a persist button to keep snapshots upon refresh (handy when changing code and debugging)
-- export/import the current snapshots in memory
-
-## Authors
-
-- **Ryan Dang** - [@rydang](https://github.com/rydang)
-- **Bryan Lee** - [@mylee1995](https://github.com/mylee1995)
-- **Josh Kim** - [@joshua0308](https://github.com/joshua0308)
-- **Sierra Swaby** - [@starkspark](https://github.com/starkspark)
-- **Ruth Anam** - [@peachiecodes](https://github.com/peachiecodes)
-- **David Chai** - [@davidchaidev](https://github.com/davidchaidev)
-- **Yujin Kang** - [@yujinkay](https://github.com/yujinkay)
-- **Andy Wong** - [@andywongdev](https://github.com/andywongdev)
-
-## License
-
-This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
diff --git a/src/DEVELOPER_README.md b/src/DEVELOPER_README.md
new file mode 100644
index 000000000..6a9491529
--- /dev/null
+++ b/src/DEVELOPER_README.md
@@ -0,0 +1,302 @@
+# Developer README
+
+## Brief
+
+Our mission at Reactime is to maintain and iterate constantly, but never at the expense of future developers. We know how hard it is to quickly get up to speed and onboard in a new codebase. So here are some helpful pointers to help you hit the ground running. 🏃🏾
+
+## Building from source
+
+1. [Download]("https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en") React Dev Tools from the Chrome Webstore Here
+
+2. Clone down the Reactime repo onto your machine.
+
+```
+git clone https://github.com/open-source-labs/reactime.git
+```
+
+3. Install dependencies and build.
+
+```
+cd reactime
+npm install
+npm run dev
+```
+
+With release of Node v18.12.1 (LTS) on 11/4/22, the script has been updated to 'npm run dev' || 'npm run build' for backwards compatibility.
+For version Node v16.16.0, please use script 'npm run devlegacy' || 'npm run buildlegacy'
+
+4. Spin up the demo application.
+
+```
+cd demo-app
+npm install
+npm run dev
+```
+
+Similar approach for Next.js and Remix demo apps
+
+5. Add Reactime to your Chrome extensions.
+
+- Navigate to chrome://extensions
+- Select “Load Unpacked”
+- Choose reactime > src > extension > build
+- Navigate to http://localhost:8080/ to inspect the demo application using Reactime!
+- Once the initial build has been completed and loaded into chrome as an unpacked extension, you may enter the root directory and run ‘npm run dev’ to hot load the chrome extension. You should see ‘[ Starting the Chrome Hot Plugin Reload Server... ]’. You should now be able to see changes without having to rebuild the extension. If for some reason it does not, feel free to rebuild and then try the Hot Plugin Reload Server again.
+
+
+
+## Linting
+
+_Before_ beginning development, especially on teams, make sure to configure your linter and code formatting to conform to one unified setting (We recommend [the Airbnb style guide](https://github.com/airbnb/javascript)!) This will make reviewing PRs much more readable and less error-prone.
+
+
+# Possible Avenues for Future Iterators
+
+Here are some notes on the current state of Reactime and considerations for future development.
+
+## Address open issues on the main OSLabs Reactime Github
+
+There are a variety of open issues on the [OSLabs Reactime Github](https://github.com/open-source-labs/reactime) that remain to be addressed.
+
+## Support for useReducer Time Travel
+
+Reactime currently shows data stored via useState, but has limited support for other hooks such as useReducer. While Reactime can now display state data from useReducer hooks in its component view, maintaining full time travel functionality for reducer states presents significant challenges.
+
+Current Implementation
+
+Reactime successfully captures and displays the current state of useReducer hooks in components
+The state data and last dispatched action for reducers are captured in the component snapshot
+This data is accessible in the component tooltips when hovering over nodes in the component map
+Reducer states are stored separately from useState data in the componentData structure
+
+Challenges with Time Travel
+
+Unlike useState which has a simple state setter function, useReducer state changes must go through the reducer function
+The reducer function defines the valid state transitions through actions
+Simply setting a new state value would bypass the reducer's action-based state management
+Maintaining the correct action history and ensuring state validity through time travel becomes complex
+The current implementation can show reducer states at different points in time but cannot reliably replay state transitions
+
+Future Considerations
+Teams working on expanding reducer support should consider:
+
+How to capture and replay sequences of reducer actions
+Ways to maintain reducer state validity during time travel
+Potential approaches for reconstructing reducer state history
+Methods to handle complex reducer patterns like middleware or side effects
+Trade-offs between full reducer state management and simplified state snapshots
+
+## Expanding Support for Custom Hooks
+
+Reactime currently has a robust system for detecting and handling built-in React hooks, but custom hooks present unique opportunities and challenges for state tracking and visualization.
+
+Current Implementation
+
+Reactime uses AST parsing via @babel/parser to analyze hook usage in components
+The system identifies hook patterns through memoizedState examination in the Fiber tree
+Hook names and state variables are extracted and matched with their corresponding state values
+This works well for direct useState and useReducer calls but may miss custom hook implementations
+
+Challenges
+
+Custom hooks can contain multiple internal state management hooks
+The relationship between custom hook state and component state is not always clear in the Fiber tree
+Hook naming patterns may vary across different custom hook implementations
+State updates in custom hooks might use complex patterns or composition
+The current AST parsing system is optimized for standard hook patterns
+
+Future Considerations
+Teams looking to expand custom hook support should consider:
+
+How to identify and group state belonging to the same custom hook
+Ways to visualize the relationship between custom hook state and component state
+Methods to track state flow through custom hook composition
+Approaches for handling custom hooks that combine multiple state management methods
+Strategies for maintaining time travel functionality with custom hook state
+
+## Newsletter functionality on the Reactime website
+
+As noted in the [Reactime Webite Github](https://github.com/reactimetravel/reactime-website), a newsletter functionality would be nice but has not been implemented yet.
+
+# File Structure
+
+In the _src_ folder, there are three directories we care about: _app_, _backend_, and _extension_.
+
+```
+src/
+├── app/ # Frontend code
+│ ├── __tests__/ # React Testing Library
+│ ├── components/ # React components
+│ ├── containers/ # More React components
+│ ├── slices/ # Redux Toolkit mechanism for updating state
+│ ├── styles/ #
+| ├── App.tsx
+│ ├── FrontendTypes.ts # Library of typescript interfaces
+│ ├── index.tsx # Starting point for root App component
+│ ├── module.d.ts #
+│ └── store.ts #
+│
+├── backend/ # "Backend" code (injected into target app)
+│ │ # Focus especially on linkFiber, timeJump, tree, and helpers
+│ ├── __tests__/ #
+│ ├── controllers/ #
+│ ├── createComponentActionsRecord.ts # Update the componentActionsRecord with new bound state-update methods
+│ ├── createTree.ts # Construct a tree snapshot from the FiberRoot tree given by ReactFiber.
+│ ├── statePropExtractor.ts # Helper functions to extract & format prop, state, and context data
+│ ├── throttle.ts #
+│ ├── timeJump.ts # Rerenders DOM based on snapshot from background script
+│ ├── models/
+│ ├── filterConditions.ts #
+│ ├── masterState.ts # Component action record interface
+│ ├── routes.ts # Interfaces with the browser history stack
+│ ├── tree.ts # Custom structure to send to background
+│ ├── routers/
+│ ├── linkFiber.ts # Check for all requirement to start Reactime and
+│ ├── snapShot.ts #
+│ ├── types/ # Typescript interfaces
+│ ├── index.ts # Starting point for backend functionality
+│ ├── index.d.ts # Definitely Type file for Index
+│ ├── module.d.ts #
+│ ├── puppeteerServer.ts #
+│
+├── extension/ # Chrome Extension code
+│ ├── build/ # Destination for bundles and manifest.json (Chrome config file)
+│ │ #
+│ ├── background.js # Chrome Background Script
+│ └── contentScript.ts # Chrome Content Script
+└──
+```
+
+# Diagrams
+
+All the diagrams of data flows are available on [MIRO](https://miro.com/app/board/uXjVPictrsM=/)
+
+1. The _app_ folder is responsible for the Single Page Application that you see when you open the chrome dev tools under the Reactime tab.
+
+
+
+
+
+
+
+2. The _backend_ folder contains the set of all scripts that we inject into our "target" application via `background.js`
+ - In Reactime, its main role is to generate data and handle time-jump requests from the background script in our _extension_ folder.
+
+
+
+
+
+3. The _extension_ folder is where the `contentScript.js` and `background.js` are located.
+ - Like regular web apps, Chrome Extensions are event-based. The background script (aka service worker) is where one typically monitors for browser triggers (e.g. events like closing a tab, for example). The content script is what allows us to read or write to our target web application, usually as a result of [messages passed](https://developer.chrome.com/extensions/messaging) from the background script.
+ - These two files help us handle requests both from the web browser and from the Reactime extension itself
+
+## Data Flow Architecture
+
+The general flow of data is described in the following steps:
+
+
+
+1. When the background bundle is loaded by the browser, it executes a script injection into the dom. (see section on _backend_). This script uses a technique called [throttle](https://medium.com/@bitupon.211/debounce-and-throttle-160affa5457b) to send state data from the app to the content script every specified milliseconds (in our case, this interval is 70ms).
+
+2. The content script always listens for messages being passed from the extension's target application. Upon receiving data from the target app, the content script will immediately forward this data to the background script which then updates an object called `tabsObj`. Each time `tabsObj` is updated, its latest version will be passed to Reactime, where it is processed for displaying to the user by the _app_ folder scripts.
+
+3. Likewise, when Reactime emits an action due to user interaction -- a "jump" request for example -- a message will be passed from Reactime via the background script to the content script. Then, the content script will pass a message to the target application containing a payload that represents the state the user wants the DOM to reflect or "jump" to.
+ - One important thing to note here is that this jump action must be dispatched in the target application (i.e. _backend_ land), because only there do we have direct access to the DOM.
+
+# Reacti.me Website:
+
+See [Reacti.me README](https://github.com/reactimetravel/reactime-website/blob/main/README.md) for instruction of how to update the website.
+Note: all other domain names that may still function are no longer registered/paid for by Codesmith. These websites may be removed at any time. Please focus on renewing Reacti.me as the primary domain for future iterations to remain consistent.
+
+# Console logs
+
+Navigation between different console.log panels can be confusing when running Reactime. We created a short instruction where you can find the results for your console.log
+
+### /src/extension
+
+Console.logs from the Extension folder you can find here:
+
+- Chrome Extension (Developer mode)
+- Background page
+
+
+
+### /src/app
+
+Console.logs from the App folder you can find here:
+
+- Chrome Browser
+- Inspect
+
+
+
+### /src/backend
+
+Console.logs from the App folder you can find here:
+
+- Open the Reactime extension in Chrome
+- Click "Inspect" on Reactime
+
+
+
+# Chrome Developer Resources
+
+Still unsure about what content scripts and background scripts do for Reactime, or for a chrome extensions in general?
+
+- The implementation details [can be found](./extension/background.js) [in the source files](./extension/contentScript.ts) themselves.
+- We also encourage you to dive into [the official Chrome Developer Docs](https://developer.chrome.com/home).
+
+Some relevant sections are reproduced below:
+
+> Content scripts are files that run in the context of web pages.
+>
+> By using the standard Document Object Model (DOM), they are able to **read** details of the web pages the browser visits, **make changes** to them and **pass information back** to their parent extension. ([Source](https://developer.chrome.com/extensions/content_scripts))
+
+- One helpful way to remember a content script's role in the Chrome ecosystem is to think: a _content_ script is used to read and modify a target web page's rendered _content_.
+
+> A background page is loaded when it is needed, and unloaded when it goes idle.
+>
+> Some examples of events include:
+> The extension is first installed or updated to a new version.
+> The background page was listening for an event, and the event is dispatched.
+> A content script or other extension sends a message.
+> Another view in the extension, such as a popup, calls `runtime.getBackgroundPage`.
+>
+> Once it has been loaded, a background page will stay running as long as it is performing an action, such as calling a Chrome API or issuing a network request.
+>
+> Additionally, the background page will not unload until all visible views and all message ports are closed. Note that opening a view does not cause the event page to load, but only prevents it from closing once loaded. ([Source](https://developer.chrome.com/extensions/background_pages))
+
+- You can think of background scripts serving a purpose analogous to that of a **server** in the client/server paradigm. Much like a server, our `background.js` listens constantly for messages (i.e. requests) from two main places:
+ 1. The content script
+ 2. The chrome extension "front-end" **(_NOT_ the interface of the browser, this is an important distinction.)**
+- In other words, a background script works as a sort of middleman, directly maintaining connection with its parent extension, and acting as a proxy enabling communication between it and the content script.
+
+# Launching to Chrome Web Store
+
+Once you are ready for launch, follow these steps to simplify deployment to the Chrome Web Store:
+
+1. Run npm run build in Reactime to build the production version of Reactime
+2. Right click on the build folder and click “compress” to make a compressed zip version of the build folder. The compressed zip is what you will upload to the Chrome Web Store
+3. Navigate to the Chrome Web Store Developer Dashboard (logged in with Reactime credentials). Go to Build > Package > Upload new package, and when prompted, upload the build.zip file
+4. Update the Store Listing and that’s it! Click “Submit for review” and wait for the Chrome store to process your request
+
+# Past Medium Articles for Reference
+
+- [Reactime Reimagined: A Major Leap Forward in React Debugging](https://medium.com/@elliesimens/reactime-reimagined-a-major-leap-forward-in-react-debugging-7b76a0a66f42)
+- [Reactime v25: The Time to React is Now!](https://medium.com/@loganjnelsen/reactime-v25-the-time-to-react-is-now-ace90e45a9c7)
+- [Relaunching Reactime: Updates and a New Accessibility Feature!](https://medium.com/@evaury/relaunching-reactime-updates-and-a-new-accessibility-feature-1f0fd3a5bd8c)
+- [Reactime renovation: Updates Coming in Version 23.0!](https://medium.com/@liam.donaher/reactime-renovation-updates-coming-in-version-23-0-37b2ef2a2771)
+- [Reactime 22: Reactime: Real-time Debugging, Timless Results](https://medium.com/@kelvinmirhan/reactime-real-time-debugging-timeless-results-3f163b721d01)
+- [Reactime 21: Cheers to Reactime, Version 21!](https://medium.com/@brok3turtl3/cheers-to-reactime-version-21-fa4dafa4bc74)
+- [Reactime 20: Reactime just keeps getting better!](https://medium.com/@njhuemmer/reactime-just-keeps-getting-better-b37659ff8b71)
+- [Reactime 19: What time is it? It’s still Reactime!](https://medium.com/@minzo.kim/what-time-is-it-its-still-reactime-d496adfa908c)
+- [Reactime 18.0. Better than ever](https://medium.com/@zdf2424/reactime-18-0-better-than-ever-148b81606257)
+- [Reactime v17.0.0: Now with support for the Context API, and a modern UI](https://medium.com/@reactime/reactime-v17-0-0-now-with-support-for-the-context-api-and-a-modern-ui-f0edf9e54dae)
+- [Reactime XVI: Clean-up Time](https://medium.com/@emintahirov1996/reactime-xvi-cleanup-time-a14ba3dcc8a6)
+- [Inter-Route Time Travel with Reactime](https://medium.com/@robbytiptontol/inter-route-time-travel-with-reactime-d84cd55ec73b)
+- [Time-Travel State with Reactime](https://medium.com/better-programming/time-traveling-state-with-reactime-6-0-53fdc3ae2a20)
+- [Reactime 4: React Fiber and Reactime](https://medium.com/@aquinojardim/react-fiber-reactime-4-0-f200f02e7fa8)
+- [Meet Reactime - a time-traveling State Debugger for React](https://medium.com/@yujinkay/meet-reactime-a-time-traveling-state-debugger-for-react-24f0fce96802)
+- [Deep in Weeds with Reactime, Concurrent React_fiberRoot, and Browser History Caching](https://itnext.io/deep-in-the-weeds-with-reactime-concurrent-react-fiberroot-and-browser-history-caching-7ce9d7300abb)
+- [Time-Traveling Through React State with Reactime 9.0](https://rxlina.medium.com/time-traveling-through-react-state-with-reactime-9-0-371dbdc99319)
+- [What time is it? Reactime!](https://medium.com/@liuedar/what-time-is-it-reactime-fd7267b9eb89)
diff --git a/src/app/App.tsx b/src/app/App.tsx
new file mode 100644
index 000000000..561a14322
--- /dev/null
+++ b/src/app/App.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import { MemoryRouter as Router } from 'react-router-dom';
+import MainContainer from './containers/MainContainer';
+import { Toaster } from 'react-hot-toast';
+import { ThemeProvider } from './ThemeProvider';
+
+function App(): JSX.Element {
+ return (
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/app/FrontendTypes.ts b/src/app/FrontendTypes.ts
new file mode 100644
index 000000000..2fb4bb3b4
--- /dev/null
+++ b/src/app/FrontendTypes.ts
@@ -0,0 +1,414 @@
+import { SeriesPoint } from '@visx/shape/lib/types';
+import { Dispatch } from 'redux';
+
+export interface ActionObj {
+ name: string;
+ seriesName: string;
+ currentTab: string;
+}
+export interface Series {
+ data: {
+ barStack: ActionObj[];
+ };
+ name: string;
+}
+
+export interface PerfData {
+ barStack: BarStackProp[];
+ componentData?: Record;
+ maxTotalRender: number;
+}
+
+export interface PerformanceVisxProps {
+ width: number;
+ height: number;
+ snapshots: any[];
+ hierarchy: any;
+}
+
+export interface TreeProps {
+ snapshot: {
+ name?: string;
+ componentData?: object;
+ state?: string | object;
+ stateSnaphot?: object;
+ children?: any[];
+ };
+ snapshots?: [];
+ currLocation?: object;
+}
+
+export interface BarStackProp {
+ snapshotId: string;
+ route: string;
+ currentTab?: string;
+}
+
+export interface TooltipData {
+ bar: SeriesPoint;
+ key: string;
+ index: number;
+ height: number;
+ width: number;
+ x: number;
+ y: number;
+ color: string;
+ name?: string;
+}
+
+export interface snapshot {
+ snapshotId?: string;
+ children: [];
+ componentData: { actualDuration: number } | undefined;
+ name: string;
+ state: string;
+}
+
+export interface Margin {
+ top: number;
+ right: number;
+ bottom: number;
+ left: number;
+}
+
+export interface BarGraphBase {
+ width: number;
+ height: number;
+ data: PerfData;
+ comparison: Series[];
+}
+
+export interface BarGraphComparisonProps extends BarGraphBase {
+ setSeries: (e: boolean | string) => void;
+ series: number;
+ setAction: (e: boolean | string) => void;
+}
+
+export interface BarGraphProps extends BarGraphBase {
+ setRoute: () => void;
+ allRoutes: unknown;
+ filteredSnapshots: unknown;
+ snapshot: unknown;
+ setSnapshot: () => void;
+}
+
+export interface BarGraphComparisonAction {
+ action: ActionObj;
+ data: ActionObj[];
+ width: number;
+ height: number;
+ comparison: Series[];
+ setSeries: (e: boolean | string) => void;
+ series?: number;
+ setAction: (e: boolean | string) => void;
+}
+
+export interface ActionContainerProps {
+ snapshots?: any;
+ currLocation?: any;
+}
+
+export interface ProvConContainerProps {
+ currentSnapshot: any;
+}
+
+export interface StateContainerProps {
+ snapshot: Record<
+ number,
+ {
+ name?: string;
+ componentData?: Record;
+ state?: Record;
+ stateSnaphot?: Record;
+ children?: unknown[];
+ }
+ >;
+ toggleActionContainer?: any;
+ webMetrics?: object;
+ hierarchy: Record;
+ snapshots?: [];
+ viewIndex?: number;
+ currLocation?: object;
+}
+
+export interface TravelContainerProps {
+ snapshotsLength: number;
+}
+
+export interface Obj {
+ stateSnapshot: {
+ route: any;
+ children: any[];
+ };
+ name: number;
+ branch: number;
+ index: number;
+ children?: [];
+}
+
+export interface RootState {
+ main: MainState;
+}
+
+export interface InitialState {
+ port: null | chrome.runtime.Port;
+ currentTab: null | number;
+ currentTitle: null | string;
+ tabs: {} | { [k: string]: { [k: string]: unknown } };
+ currentTabInApp: null | string;
+ connectionStatus: boolean;
+ connectRequested: boolean;
+}
+
+export interface MainState {
+ port: null | chrome.runtime.Port;
+ currentTab: number;
+ currentTitle: string;
+ tabs: { [k: string]: { [k: string]: unknown } };
+ currentTabInApp: string;
+ connectionStatus: boolean;
+ connectRequested: boolean;
+}
+
+export interface CurrentTab {
+ currBranch: number;
+ currLocation: { [k: string]: any };
+ currParent: number;
+ hierarchy: {
+ stateSnapshot: {
+ route: any;
+ children: any[];
+ };
+ name: number;
+ branch: number;
+ index: number;
+ children?: [];
+ };
+ index: number;
+ intervalId: null | number;
+ mode: { paused: boolean };
+ playing: boolean;
+ seriesSavedStatus: boolean;
+ sliderIndex: number;
+ snapshots: { [k: string]: any }[];
+ status: { [k: string]: any };
+ title: string;
+ viewIndex: number;
+ webMetrics: {
+ LCP: undefined | number;
+ FID: undefined | number;
+ FCP: undefined | number;
+ TTFB: undefined | number;
+ CLS: undefined | number;
+ INP: undefined | number;
+ };
+}
+
+export interface DiffProps {
+ snapshot: { state?: Record };
+ show?: boolean | undefined;
+}
+
+/**
+ * @template ActionProps Props for the action component
+ */
+
+export interface ActionProps {
+ key: string;
+ selected: boolean;
+ last: boolean;
+ index: number;
+ sliderIndex: number;
+ displayName: string;
+ componentName: string;
+ componentData: { actualDuration: number } | undefined;
+ routePath: unknown;
+ state?: Record;
+ viewIndex: number | undefined;
+ isCurrIndex: boolean;
+}
+
+export interface DiffRouteProps {
+ snapshot: Record<
+ string,
+ {
+ name?: string;
+ componentData?: Record;
+ state?: string | unknown;
+ stateSnaphot?: Record;
+ children?: unknown[];
+ }
+ >;
+}
+
+export interface HandleProps {
+ value: number;
+ dragging: boolean;
+ index: number;
+}
+
+export interface MainSliderProps {
+ className?: string;
+ snapshots?: any[];
+}
+
+export interface DefaultMargin {
+ top: number;
+ left: number;
+ right: number;
+ bottom: number;
+}
+
+export interface StateRouteProps {
+ snapshot: {
+ name?: string;
+ componentData?: object;
+ state?: string | object;
+ stateSnaphot?: object;
+ children?: any[];
+ };
+ hierarchy: any;
+ snapshots: [];
+ viewIndex: number;
+ webMetrics: object;
+ currLocation: object;
+}
+
+export interface DropdownProps {
+ selectedSpeed: { value: number; label: string };
+ speeds: { value: number; label: string }[];
+ setSpeed: () => void;
+}
+
+export interface TutorialProps {
+ dispatch: Dispatch;
+ currentTabInApp: string;
+}
+
+export interface TutorialState {
+ stepsEnabled: boolean;
+}
+
+export interface StepsObj {
+ title: string;
+ element?: string | Element;
+ intro: string;
+ position: string;
+}
+
+export interface LinkControlProps {
+ orientation: string;
+ linkType: string;
+ stepPercent: number;
+ selectedNode: string;
+ setOrientation: (orientation: string) => void;
+ setLinkType: (linkType: string) => void;
+ setStepPercent: (percent: number) => void;
+ setSelectedNode: (selectedNode: string) => void;
+ snapShots: Record;
+}
+
+export interface ControlStyles {
+ padding: string;
+}
+
+export interface DropDownStyle {
+ margin: string;
+ fontFamily: string;
+ borderRadius: string;
+ borderStyle: string;
+ borderWidth: string;
+ backgroundColor: string;
+ color: string;
+ padding: string;
+}
+
+export interface Node {
+ children?: Node[];
+ name?: string;
+}
+
+export interface LinkComponent {
+ linkType: string;
+ orientation: string;
+}
+
+export interface LinkTypesProps {
+ width: number;
+ height: number;
+ margin?: { top: number; right: number; bottom: number; left: number };
+ snapshots: Record;
+ currentSnapshot?: Record;
+}
+
+export interface ToolTipStyles {
+ defaultStyles: React.CSSProperties;
+ minWidth: number;
+ maxWidth: number;
+ backgroundColor: string;
+ color: string;
+ fontSize: string;
+ lineHeight: string;
+ fontFamily: string;
+ zIndex: number;
+ pointerEvents: string;
+}
+
+export interface OptionsCursorTrueWithMargin {
+ followCursor: boolean;
+ shiftX: number;
+ shiftY: number;
+}
+
+export interface StatelessCleaning {
+ name?: string;
+ componentData?: Record;
+ state?: string | {};
+ stateSnaphot?: Record;
+ children?: StatelessCleaning[];
+}
+
+export interface Snapshots {
+ snapshot: number;
+ component1: number;
+ component2: number;
+ component3: number;
+ 'all others': number;
+}
+
+export interface ErrorContainerProps {
+ port: chrome.runtime.Port | null;
+}
+
+export interface AxContainer {
+ axSnapshots: [];
+ snapshot: {
+ name?: string;
+ componentData?: object;
+ state?: string | object;
+ stateSnaphot?: object;
+ children?: any[];
+ };
+ snapshots: [];
+ currLocation: object;
+ setShowTree: any;
+ setShowParagraph: any;
+}
+
+export interface FilteredNode {
+ name?: string;
+ state?: any;
+ hooksState?: any;
+ props?: any;
+ componentData?: {
+ context?: any;
+ hooksState?: any;
+ props?: any;
+ };
+ children?: FilteredNode[];
+ context?: any;
+}
+
+export interface FilteredNodeChildren {
+ [key: string]: FilteredNode;
+}
diff --git a/src/app/ThemeProvider.tsx b/src/app/ThemeProvider.tsx
new file mode 100644
index 000000000..3d435faac
--- /dev/null
+++ b/src/app/ThemeProvider.tsx
@@ -0,0 +1,39 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+
+const ThemeContext = createContext({
+ isDark: false,
+ toggleTheme: () => {},
+});
+
+export const ThemeProvider = ({ children }) => {
+ const [isDark, setIsDark] = useState(false);
+
+ // Check for system preference on mount
+ useEffect(() => {
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
+ setIsDark(prefersDark);
+ }, []);
+
+ // Update body class when theme changes
+ useEffect(() => {
+ if (isDark) {
+ document.documentElement.classList.add('dark');
+ } else {
+ document.documentElement.classList.remove('dark');
+ }
+ }, [isDark]);
+
+ const toggleTheme = () => {
+ setIsDark((prev) => !prev);
+ };
+
+ return {children} ;
+};
+
+export const useTheme = () => {
+ const context = useContext(ThemeContext);
+ if (context === undefined) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+};
diff --git a/src/app/__tests__/ActionsButtons.test.tsx b/src/app/__tests__/ActionsButtons.test.tsx
new file mode 100644
index 000000000..76213f702
--- /dev/null
+++ b/src/app/__tests__/ActionsButtons.test.tsx
@@ -0,0 +1,324 @@
+import React from 'react';
+import { render as rtlRender, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { Provider } from 'react-redux';
+import { store } from '../store';
+import { useDispatch } from 'react-redux';
+import { mainSlice, changeSlider, emptySnapshots } from '../slices/mainSlice';
+
+import { useTheme } from '../ThemeProvider';
+import { configureStore } from '@reduxjs/toolkit';
+
+import DropDown from '../components/Actions/DropDown';
+import RecordButton from '../components/Actions/RecordButton';
+import ThemeToggle from '../components/Actions/ThemeToggle';
+import ProvConContainer from '../containers/ProvConContainer';
+import ActionContainer from '../containers/ActionContainer';
+
+// @ts-ignore
+const useDispatchMock = useDispatch as jest.Mock;
+const dummyDispatch = jest.fn();
+
+// Mock ThemeToggle for RecordButton tests
+jest.mock('../components/Actions/ThemeToggle', () => {
+ return function MockThemeToggle() {
+ return Theme Toggle
;
+ };
+});
+
+// Mock useTheme hook
+jest.mock('../ThemeProvider', () => ({
+ useTheme: jest.fn(),
+}));
+
+// Mock react-redux
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'),
+ useDispatch: jest.fn(),
+}));
+
+window.HTMLElement.prototype.scrollIntoView = jest.fn();
+
+// Setup mock before tests
+beforeAll(() => {
+ useDispatchMock.mockReturnValue(dummyDispatch);
+});
+
+// Clear mocks after each test
+afterEach(() => {
+ dummyDispatch.mockClear();
+});
+
+// Helper function for redux wrapped renders
+const render = (component) => rtlRender({component} );
+
+// DropDown Component Tests
+describe('DropDown Component', () => {
+ const mockSetDropdownSelection = jest.fn();
+
+ beforeEach(() => {
+ mockSetDropdownSelection.mockClear();
+ });
+
+ test('renders with placeholder text', () => {
+ render( );
+ expect(screen.getByText('Select Hook')).toBeInTheDocument();
+ });
+
+ test('shows correct options when clicked', () => {
+ render( );
+ const dropdown = screen.getByText('Select Hook');
+ fireEvent.mouseDown(dropdown);
+ expect(screen.getByText('Time Jump')).toBeInTheDocument();
+ expect(screen.getByText('Providers / Consumers')).toBeInTheDocument();
+ });
+
+ test('displays selected value', () => {
+ render(
+ ,
+ );
+ expect(screen.getByText('Time Jump')).toBeInTheDocument();
+ });
+});
+
+// RecordButton Component Tests
+describe('RecordButton Component', () => {
+ const mockOnToggle = jest.fn();
+
+ beforeEach(() => {
+ mockOnToggle.mockClear();
+ });
+
+ test('renders record button with initial state', () => {
+ render( );
+ expect(screen.getByText('Record')).toBeInTheDocument();
+ });
+
+ test('calls onToggle when switch is clicked', () => {
+ render( );
+ const switchElement = screen.getByRole('checkbox');
+ fireEvent.click(switchElement);
+ expect(mockOnToggle).toHaveBeenCalled();
+ });
+
+ test('renders ThemeToggle component', () => {
+ render( );
+ expect(screen.getByTestId('mock-theme-toggle')).toBeInTheDocument();
+ });
+});
+
+// // ThemeToggle Component Tests
+describe('ThemeToggle Component', () => {
+ const mockToggleTheme = jest.fn();
+
+ beforeEach(() => {
+ mockToggleTheme.mockClear();
+ (useTheme as jest.Mock).mockImplementation(() => ({
+ isDark: false,
+ toggleTheme: mockToggleTheme,
+ }));
+ });
+
+ test('renders theme toggle', () => {
+ render( );
+
+ // Check if mock theme toggle exists
+ const themeToggle = screen.getByTestId('mock-theme-toggle');
+ expect(themeToggle).toBeInTheDocument();
+ expect(themeToggle).toHaveTextContent('Theme Toggle');
+ });
+
+ test('renders with correct text', () => {
+ render( );
+ expect(screen.getByTestId('mock-theme-toggle')).toHaveTextContent('Theme Toggle');
+ });
+
+ test('toggles between light and dark mode classes', () => {
+ // First render in light mode
+ (useTheme as jest.Mock).mockImplementation(() => ({
+ isDark: false,
+ toggleTheme: mockToggleTheme,
+ }));
+
+ const { rerender } = render( );
+ const toggle = screen.getByTestId('mock-theme-toggle');
+ expect(toggle).toBeInTheDocument();
+
+ // Since isDark is false, verify the content
+ expect(toggle).toHaveTextContent('Theme Toggle');
+
+ // Rerender in dark mode
+ (useTheme as jest.Mock).mockImplementation(() => ({
+ isDark: true,
+ toggleTheme: mockToggleTheme,
+ }));
+ rerender( );
+
+ // Verify it's still rendered with the same content in dark mode
+ expect(toggle).toBeInTheDocument();
+ expect(toggle).toHaveTextContent('Theme Toggle');
+ });
+});
+
+// ProvConContainer Component Tests
+describe('ProvConContainer Component', () => {
+ const mockSnapshot = {
+ componentData: {
+ context: {
+ theme: { dark: true },
+ user: { id: 1, name: 'Test' },
+ },
+ hooksState: {
+ useState: [{ value: 'test' }],
+ },
+ },
+ children: [
+ {
+ name: 'ThemeProvider',
+ componentData: {
+ context: { theme: 'dark' },
+ },
+ children: [],
+ },
+ ],
+ };
+
+ test('renders empty state message when no providers/consumers found', () => {
+ render( );
+ expect(screen.getByText(/No providers or consumers found/)).toBeInTheDocument();
+ });
+
+ test('renders context data correctly', () => {
+ render( );
+
+ // Use getAllByText to get all theme spans and verify at least one exists
+ const themeElements = screen.getAllByText((content, element) => {
+ return element?.tagName.toLowerCase() === 'span' && element?.textContent === 'theme:';
+ });
+ expect(themeElements.length).toBeGreaterThan(0);
+
+ // Do the same for user spans
+ const userElements = screen.getAllByText((content, element) => {
+ return element?.tagName.toLowerCase() === 'span' && element?.textContent === 'user:';
+ });
+ expect(userElements.length).toBeGreaterThan(0);
+ });
+
+ test('renders provider components correctly', () => {
+ render( );
+
+ // Get all theme elements and use the first one to find its parent
+ const themeElements = screen.getAllByText((content, element) => {
+ return element?.tagName.toLowerCase() === 'span' && element?.textContent === 'theme:';
+ });
+ const parentElement = themeElements[0].closest('div');
+
+ expect(parentElement).toBeInTheDocument();
+ });
+
+ test('correctly parses stringified JSON values', () => {
+ const snapshotWithStringifiedJSON = {
+ componentData: {
+ context: {
+ data: JSON.stringify({ key: 'value' }),
+ },
+ },
+ };
+ render( );
+
+ // Look for the key-value pair in the rendered structure
+ expect(
+ screen.getByText((content, element) => {
+ return element?.tagName.toLowerCase() === 'span' && element?.textContent === 'key:';
+ }),
+ ).toBeInTheDocument();
+
+ expect(
+ screen.getByText((content, element) => {
+ return element?.tagName.toLowerCase() === 'span' && element?.textContent === '"value"';
+ }),
+ ).toBeInTheDocument();
+ });
+});
+
+// Clear Button Tests
+describe('Clear Button', () => {
+ // Create mock store
+ const mockStore = configureStore({
+ reducer: {
+ main: mainSlice.reducer,
+ },
+ preloadedState: {
+ main: {
+ port: null,
+ currentTab: 0,
+ currentTitle: 'No Target',
+ tabs: {
+ 0: {
+ currLocation: {
+ index: 0,
+ stateSnapshot: {
+ children: [],
+ route: {
+ url: '/test',
+ },
+ },
+ },
+ hierarchy: {
+ index: 0,
+ stateSnapshot: {
+ children: [],
+ route: {
+ url: '/test',
+ },
+ },
+ children: [],
+ },
+ sliderIndex: 0,
+ viewIndex: 0,
+ snapshots: [],
+ playing: false,
+ intervalId: null,
+ mode: { paused: false },
+ status: {
+ reactDevToolsInstalled: true,
+ targetPageisaReactApp: true,
+ },
+ },
+ },
+ currentTabInApp: null,
+ connectionStatus: true,
+ connectRequested: true,
+ },
+ },
+ });
+
+ // @ts-ignore
+ const useDispatchMock = useDispatch as jest.Mock;
+ const dummyDispatch = jest.fn();
+
+ beforeEach(() => {
+ useDispatchMock.mockReturnValue(dummyDispatch);
+ dummyDispatch.mockClear();
+ });
+
+ test('renders clear button with correct text', () => {
+ render(
+
+
+ ,
+ );
+ expect(screen.getByText('Clear')).toBeInTheDocument();
+ });
+
+ test('dispatches both emptySnapshots and changeSlider actions when clicked', () => {
+ render(
+
+
+ ,
+ );
+ fireEvent.click(screen.getByText('Clear'));
+ expect(dummyDispatch).toHaveBeenCalledWith(emptySnapshots());
+ expect(dummyDispatch).toHaveBeenCalledWith(changeSlider(0));
+ });
+});
diff --git a/src/app/__tests__/ButtonContainer.test.tsx b/src/app/__tests__/ButtonContainer.test.tsx
new file mode 100644
index 000000000..2b6b13bf1
--- /dev/null
+++ b/src/app/__tests__/ButtonContainer.test.tsx
@@ -0,0 +1,203 @@
+import React from 'react';
+import { render as rtlRender, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { TextEncoder } from 'util';
+global.TextEncoder = TextEncoder;
+import ButtonsContainer from '../containers/ButtonsContainer';
+import userEvent from '@testing-library/user-event';
+import { toggleMode, mainSlice } from '../slices/mainSlice';
+import { useDispatch, Provider } from 'react-redux';
+import { configureStore } from '@reduxjs/toolkit';
+
+const customTabs = {
+ 87: {
+ snapshots: [1, 2, 3, 4],
+ hierarchy: {
+ index: 0,
+ name: 1,
+ branch: 0,
+ stateSnapshot: {
+ state: {},
+ children: [
+ {
+ state: { test: 'test' },
+ name: 'App',
+ componentData: { actualDuration: 3.5 },
+ },
+ ],
+ route: {
+ id: 1,
+ url: 'http://localhost:8080/',
+ },
+ },
+ children: [
+ {
+ index: 1,
+ name: 2,
+ branch: 0,
+ stateSnapshot: {
+ state: {},
+ children: [
+ {
+ state: { test: 'test' },
+ name: 'App',
+ componentData: { actualDuration: 3.5 },
+ },
+ ],
+ route: {
+ id: 2,
+ url: 'http://localhost:8080/',
+ },
+ },
+ children: [
+ {
+ index: 2,
+ name: 3,
+ branch: 0,
+ stateSnapshot: {
+ state: {},
+ children: [
+ {
+ state: { test: 'test' },
+ name: 'App',
+ componentData: { actualDuration: 3.5 },
+ },
+ ],
+ route: {
+ id: 3,
+ url: 'http://localhost:8080/',
+ },
+ },
+ children: [
+ {
+ index: 3,
+ name: 4,
+ branch: 0,
+ stateSnapshot: {
+ state: {},
+ children: [
+ {
+ state: { test: 'test' },
+ name: 'App',
+ componentData: { actualDuration: 3.5 },
+ },
+ ],
+ route: {
+ id: 4,
+ url: 'http://localhost:8080/test/',
+ },
+ },
+ children: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ currLocation: {
+ index: 0,
+ name: 1,
+ branch: 0,
+ },
+ sliderIndex: 0,
+ viewIndex: -1,
+ },
+};
+
+const customInitialState = {
+ main: {
+ port: null,
+ currentTab: 87, // Update with your desired value
+ currentTitle: 'test string',
+ tabs: customTabs, // Replace with the actual (testing) tab data
+ currentTabInApp: 'test string',
+ connectionStatus: false,
+ connectRequested: true,
+ },
+};
+
+const customStore = configureStore({
+ reducer: {
+ main: mainSlice.reducer,
+ },
+ preloadedState: customInitialState, // Provide custom initial state
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false }),
+});
+
+const render = (component) => rtlRender({component} );
+
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'), // Use the actual react-redux module except for the functions you want to mock
+ useDispatch: jest.fn(), // set up a mock function for useDispatch
+}));
+
+//these are needed for the Clicking pause-button toggles locked/unlocked test, as the onClick triggers the exportHandler, which uses the .creatObjectURL and .revokeObjectURL methods, so we declare them as jest functions here
+global.URL.createObjectURL = jest.fn(() => 'https://pdf.com');
+global.URL.revokeObjectURL = jest.fn();
+
+describe('Unit testing for ButtonContainer', () => {
+ const useDispatchMock = (useDispatch as unknown) as jest.Mock; // make the test run
+ const dummyDispatch = jest.fn(); //separate mock function created because we need to explicitly define on line 30 what
+ useDispatchMock.mockReturnValue(dummyDispatch); //exactly useDispatchMock returns (which is a jest.fn())
+ beforeEach;
+
+ const currentTab = customInitialState.main.tabs[customInitialState.main.currentTab];
+
+ beforeEach(() => {
+
+ currentTab.mode = {
+ paused: true,
+ };
+ });
+
+ describe('When button container is loaded', () => {
+ test('it should have 5 buttons', () => {
+ customInitialState.main.connectionStatus = true;
+ render( );
+ expect(screen.getAllByRole('button')).toHaveLength(5);
+ expect(screen.getAllByRole('button')[0]).toHaveTextContent('Locked');
+ expect(screen.getAllByRole('button')[1]).toHaveTextContent('Download');
+ expect(screen.getAllByRole('button')[2]).toHaveTextContent('Upload');
+ expect(screen.getAllByRole('button')[3]).toHaveTextContent('Tutorial');
+ expect(screen.getAllByRole('button')[4]).toHaveTextContent('Reconnect');
+ });
+ });
+
+ describe('When view is unlock', () => {
+ test('Button should show as unlocked', () => {
+ customInitialState.main.connectionStatus = true;
+ currentTab.mode.paused = false;
+ render( );
+ expect(screen.getAllByRole('button')[0]).toHaveTextContent('Unlocked');
+ });
+ });
+
+ describe('When view is lock', () => {
+ test('Button should show as locked', () => {
+ customInitialState.main.connectionStatus = true;
+ currentTab.mode.paused = true;
+ render( );
+ expect(screen.getAllByRole('button')[0]).toHaveTextContent('Locked');
+ });
+ });
+
+ describe('Clicking pause-button toggles locked/unlocked', () => {
+ test('When button is unlocked and it is clicked', async () => {
+ render( );
+ const button = screen.getAllByRole('button')[0];
+ await userEvent.click(button);
+ expect(dummyDispatch).toHaveBeenCalledWith(toggleMode('paused'));
+ });
+ });
+
+ describe('Upload/Download', () => {
+ test('Clicking upload and download buttons', async () => {
+ render( );
+ fireEvent.click(screen.getAllByRole('button')[1]);
+ fireEvent.click(screen.getAllByRole('button')[2]);
+ expect(screen.getAllByRole('button')[1]).toBeInTheDocument();
+ expect(screen.getAllByRole('button')[2]).toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/app/__tests__/ButtonsContainer.test.js b/src/app/__tests__/ButtonsContainer.test.js
deleted file mode 100644
index 04dcaa0b4..000000000
--- a/src/app/__tests__/ButtonsContainer.test.js
+++ /dev/null
@@ -1,109 +0,0 @@
-/* eslint-disable react/jsx-filename-extension */
-import { shallow, configure } from 'enzyme';
-import React from 'react';
-import Adapter from 'enzyme-adapter-react-16';
-import ButtonsContainer from '../containers/ButtonsContainer';
-import { useStoreContext } from '../store';
-import { toggleMode } from '../actions/actions';
-
-configure({ adapter: new Adapter() });
-
-const state = {
- tabs: {
- 87: {
- snapshots: [1, 2, 3, 4],
- sliderIndex: 0,
- viewIndex: -1,
- mode: {
- paused: false,
- locked: false,
- persist: false,
- },
- },
- },
- currentTab: 87,
-};
-
-const currentTab = state.tabs[state.currentTab];
-
-const dispatch = jest.fn();
-
-jest.mock('../store');
-useStoreContext.mockImplementation(() => [state, dispatch]);
-
-let wrapper;
-
-describe('testing the bottom buttons', () => {
- beforeEach(() => {
- wrapper = shallow( );
- dispatch.mockClear();
- useStoreContext.mockClear();
- currentTab.mode = {
- locked: false,
- paused: false,
- persist: false,
- };
- });
-
- describe('pause button testing', () => {
- beforeEach(() => {
- wrapper.find('.pause-button').simulate('click');
- });
- test('pause button dispatches upon click', () => {
- expect(dispatch.mock.calls.length).toBe(1);
- });
-
- test('pause button dispatches toggleMode action', () => {
- expect(dispatch.mock.calls[0][0]).toEqual(toggleMode('paused'));
- });
-
- test('pause button displays state', () => {
- expect(wrapper.find('.pause-button').text()).toBe('Pause');
- state.tabs[state.currentTab].mode.paused = true;
- wrapper = shallow( );
- expect(wrapper.find('.pause-button').text()).toBe('Resume');
- });
- });
-
-
- describe('lock button testing', () => {
- beforeEach(() => {
- wrapper.find('.lock-button').simulate('click');
- });
- test('lock button dispatches upon click', () => {
- expect(dispatch.mock.calls.length).toBe(1);
- });
-
- test('lock button dispatches toggleMode action', () => {
- expect(dispatch.mock.calls[0][0]).toEqual(toggleMode('locked'));
- });
-
- test('lock button displays state', () => {
- expect(wrapper.find('.lock-button').text()).toBe('Lock');
- state.tabs[state.currentTab].mode.locked = true;
- wrapper = shallow( );
- expect(wrapper.find('.lock-button').text()).toBe('Unlock');
- });
- });
-
- describe('persist button testing', () => {
- beforeEach(() => {
- wrapper.find('.persist-button').simulate('click');
- });
-
- test('persist button dispatches upon click', () => {
- expect(dispatch.mock.calls.length).toBe(1);
- });
-
- test('persist button dispatches toggleMode action', () => {
- expect(dispatch.mock.calls[0][0]).toEqual(toggleMode('persist'));
- });
-
- test('persist button displays state', () => {
- expect(wrapper.find('.persist-button').text()).toBe('Persist');
- state.tabs[state.currentTab].mode.persist = true;
- wrapper = shallow( );
- expect(wrapper.find('.persist-button').text()).toBe('Unpersist');
- });
- });
-});
diff --git a/src/app/__tests__/Chart.test.jsx b/src/app/__tests__/Chart.test.jsx
deleted file mode 100644
index 03e1f8942..000000000
--- a/src/app/__tests__/Chart.test.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-// import TestRunner from 'jest-runner';
-
-// Unit Test Cases for Charts
-// description: lifecycle methods
-// Component should call make3dTree upon mounting
-// object 'root' should be a deep clone of the snapshot
-// i.e.: this.props.snapshot !== root
-
-// description: maked3Tree
-// Should call function 'removed3tree' only once
-// Should call appropriate function upon triggering a certain event on the tooltip div
-// i.e.:
-// 'mouseover' event -> 'tipMouseover' function
-// 'mouseout' event -> 'tipMouseout' function
-// Should call appropriate function upon triggering a certain event on a node
-// (nested under the 'update' function)
-// i.e.:
-// 'mouseover' event -> 'mouseover' function
-// 'mouseout' event -> 'mouseout' function
-// 'click' event -> 'click' function
-// Should call function 'update' at least once
-
-describe('placeholder', () => {
- it.skip('placeholder for tests', () => {
- expect(1 + 1).toEqual(2);
- });
-});
diff --git a/src/app/__tests__/MainContainer.test.js b/src/app/__tests__/MainContainer.test.js
deleted file mode 100644
index e9ae421c5..000000000
--- a/src/app/__tests__/MainContainer.test.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/* eslint-disable react/jsx-filename-extension */
-
-import { shallow, configure } from 'enzyme';
-import React from 'react';
-import Adapter from 'enzyme-adapter-react-16';
-import MainContainer from '../containers/MainContainer';
-import { useStoreContext } from '../store';
-
-import HeadContainer from '../containers/HeadContainer';
-import ActionContainer from '../containers/ActionContainer';
-import StateContainer from '../containers/StateContainer';
-import TravelContainer from '../containers/TravelContainer';
-import ButtonsContainer from '../containers/ButtonsContainer';
-
-const chrome = require('sinon-chrome');
-
-configure({ adapter: new Adapter() });
-
-const state = {
- tabs: {},
- currentTab: null,
-};
-
-const dispatch = jest.fn();
-jest.mock('../store');
-useStoreContext.mockImplementation(() => [state, dispatch]);
-
-let wrapper;
-global.chrome = chrome;
-const port = {
- onMessage: {
- addListener: () => {},
- },
- onDisconnect: {
- addListener: () => {},
- },
-};
-chrome.runtime.connect.returns(port);
-
-beforeEach(() => {
- wrapper = shallow( );
- useStoreContext.mockClear();
- dispatch.mockClear();
-});
-
-describe('MainContainer rendering', () => {
- test('With no snapshots, should not render any containers', () => {
- expect(wrapper.text()).toEqual(
- 'No React application found. Please install our npm package in your app.',
- );
- expect(wrapper.find(HeadContainer).length).toBe(0);
- expect(wrapper.find(ActionContainer).length).toBe(0);
- expect(wrapper.find(StateContainer).length).toBe(0);
- expect(wrapper.find(TravelContainer).length).toBe(0);
- expect(wrapper.find(ButtonsContainer).length).toBe(0);
- });
- test('With snapshots, should render all containers', () => {
- state.currentTab = 87;
- state.tabs[87] = {
- snapshots: [{}],
- viewIndex: -1,
- sliderIndex: 0,
- mode: {},
- };
-
- wrapper = shallow( );
- expect(wrapper.find(HeadContainer).length).toBe(1);
- expect(wrapper.find(ActionContainer).length).toBe(1);
- expect(wrapper.find(StateContainer).length).toBe(1);
- expect(wrapper.find(TravelContainer).length).toBe(1);
- expect(wrapper.find(ButtonsContainer).length).toBe(1);
- });
-});
diff --git a/src/app/__tests__/MainContainer.test.tsx b/src/app/__tests__/MainContainer.test.tsx
new file mode 100644
index 000000000..b17bab830
--- /dev/null
+++ b/src/app/__tests__/MainContainer.test.tsx
@@ -0,0 +1,227 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import { configureStore } from '@reduxjs/toolkit';
+import { MemoryRouter } from 'react-router-dom';
+import '@testing-library/jest-dom';
+
+import MainContainer from '../containers/MainContainer';
+import { mainSlice } from '../slices/mainSlice';
+
+// Mock ResizeObserver
+class ResizeObserverMock {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+}
+
+global.ResizeObserver = ResizeObserverMock;
+
+// Mock components that use visx/tooltips
+jest.mock('../components/StateRoute/ComponentMap/ComponentMap', () => {
+ return function MockComponentMap() {
+ return
;
+ };
+});
+
+jest.mock('../containers/ActionContainer', () => {
+ return function MockActionContainer({ snapshots, currLocation }) {
+ return
;
+ };
+});
+
+jest.mock('../components/StateRoute/StateRoute', () => {
+ return function MockStateRoute() {
+ return
;
+ };
+});
+
+// Mock chrome API
+const mockChrome = {
+ runtime: {
+ connect: jest.fn(() => ({
+ onMessage: {
+ addListener: jest.fn(),
+ hasListener: jest.fn(() => false),
+ removeListener: jest.fn(),
+ },
+ postMessage: jest.fn(),
+ })),
+ onMessage: {
+ addListener: jest.fn(),
+ hasListener: jest.fn(() => false),
+ removeListener: jest.fn(),
+ },
+ },
+};
+
+global.chrome = mockChrome as any;
+
+// Mock proper state hierarchy structure
+const mockStateTree = {
+ index: 0,
+ stateSnapshot: {
+ name: 'Root',
+ children: [
+ {
+ name: 'TestComponent',
+ state: { testData: 'value' },
+ componentData: {
+ props: {},
+ state: null,
+ },
+ children: [],
+ },
+ ],
+ route: {
+ url: '/test',
+ },
+ },
+ children: [],
+};
+
+const mockSnapshots = [
+ {
+ index: 0,
+ stateSnapshot: {
+ name: 'Root',
+ children: [
+ {
+ name: 'TestComponent',
+ state: { testData: 'value' },
+ componentData: {},
+ children: [],
+ },
+ ],
+ route: {
+ url: '/test',
+ },
+ },
+ },
+];
+
+// Create a mock store with complete structure
+const createMockStore = (initialState = {}) => {
+ return configureStore({
+ reducer: {
+ // @ts-ignore
+ main: mainSlice.reducer,
+ },
+ preloadedState: {
+ main: {
+ currentTab: 1,
+ port: null,
+ connectionStatus: true,
+ currentTitle: 'Test Tab',
+ tabs: {
+ 1: {
+ status: {
+ reactDevToolsInstalled: true,
+ targetPageisaReactApp: true,
+ },
+ axSnapshots: [],
+ currLocation: {
+ index: 0,
+ stateSnapshot: mockStateTree.stateSnapshot,
+ },
+ viewIndex: -1,
+ sliderIndex: 0,
+ snapshots: mockSnapshots,
+ hierarchy: mockStateTree,
+ webMetrics: {},
+ mode: {
+ paused: false,
+ },
+ snapshotDisplay: mockSnapshots,
+ playing: false,
+ intervalId: null,
+ },
+ },
+ ...initialState,
+ },
+ },
+ });
+};
+
+// Helper function to render with store and router
+const renderWithProvider = (ui: JSX.Element, store = createMockStore()) => {
+ return render(
+
+ {ui}
+ ,
+ );
+};
+
+describe('MainContainer', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders without crashing', () => {
+ const { container } = renderWithProvider( );
+ expect(container.querySelector('.main-container')).toBeInTheDocument();
+ });
+
+ it('establishes connection with chrome runtime on mount', () => {
+ renderWithProvider( );
+ expect(chrome.runtime.connect).toHaveBeenCalledWith({ name: 'panel' });
+ });
+
+ it('renders ErrorContainer when React DevTools are not installed', () => {
+ const store = createMockStore({
+ tabs: {
+ 1: {
+ status: {
+ reactDevToolsInstalled: false,
+ targetPageisaReactApp: true,
+ },
+ },
+ },
+ });
+
+ renderWithProvider( , store);
+ expect(screen.getByText(/Welcome to Reactime/i)).toBeInTheDocument();
+ });
+
+ it('renders ErrorContainer when page is not a React app', () => {
+ const store = createMockStore({
+ tabs: {
+ 1: {
+ status: {
+ reactDevToolsInstalled: true,
+ targetPageisaReactApp: false,
+ },
+ },
+ },
+ });
+
+ renderWithProvider( , store);
+ expect(screen.getByText(/Welcome to Reactime/i)).toBeInTheDocument();
+ });
+
+ it('renders main content when all conditions are met', () => {
+ const { container } = renderWithProvider( );
+ expect(container.querySelector('.main-container')).toBeInTheDocument();
+ expect(container.querySelector('.bottom-controls')).toBeInTheDocument();
+ });
+
+ it('handles port disconnection', () => {
+ const store = createMockStore();
+ renderWithProvider( , store);
+
+ // Verify that onMessage.addListener was called
+ expect(chrome.runtime.onMessage.addListener).toHaveBeenCalled();
+
+ // Get the last registered listener
+ const lastCall = (chrome.runtime.onMessage.addListener as jest.Mock).mock.calls.length - 1;
+ const handleDisconnect = (chrome.runtime.onMessage.addListener as jest.Mock).mock.calls[
+ lastCall
+ ][0];
+
+ // Call the disconnect handler directly
+ handleDisconnect('portDisconnect');
+
+ // Check if store was updated correctly
+ expect(store.getState().main.connectionStatus).toBeFalsy();
+ });
+});
diff --git a/src/app/__tests__/Performance.test.tsx b/src/app/__tests__/Performance.test.tsx
new file mode 100644
index 000000000..3daa0c4a7
--- /dev/null
+++ b/src/app/__tests__/Performance.test.tsx
@@ -0,0 +1,264 @@
+import '@testing-library/jest-dom';
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import { MemoryRouter } from 'react-router-dom';
+import configureStore from 'redux-mock-store';
+
+import BarGraph from '../components/StateRoute/PerformanceVisx/BarGraph';
+import PerformanceVisx from '../components/StateRoute/PerformanceVisx/PerformanceVisx';
+
+// Mock ResizeObserver
+class ResizeObserver {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+}
+
+// Mock VisxTooltip Portal
+jest.mock('@visx/tooltip', () => ({
+ ...jest.requireActual('@visx/tooltip'),
+ useTooltipInPortal: () => ({
+ tooltipLeft: 0,
+ tooltipTop: 0,
+ tooltipData: null,
+ TooltipInPortal: ({ children }) => children,
+ containerRef: { current: null },
+ }),
+}));
+
+// Mock window.ResizeObserver
+window.ResizeObserver = ResizeObserver;
+
+// Mock createPortal since JSDOM doesn't support it
+jest.mock('react-dom', () => ({
+ ...jest.requireActual('react-dom'),
+ createPortal: (node) => node,
+}));
+
+// Mock SVG elements that JSDOM doesn't support
+const createElementNSOrig = global.document.createElementNS;
+global.document.createElementNS = function (namespaceURI, qualifiedName) {
+ if (namespaceURI === 'http://www.w3.org/2000/svg' && qualifiedName === 'svg') {
+ const element = createElementNSOrig.apply(this, arguments);
+ element.createSVGRect = function () {};
+ return element;
+ }
+ return createElementNSOrig.apply(this, arguments);
+};
+
+// Mock getComputedStyle for SVG elements
+window.getComputedStyle = (element) => {
+ // Create an object with string index signatures
+ const cssProperties = Array.from({ length: 1000 }).reduce<{ [key: string]: string }>(
+ (acc, _, index) => {
+ acc[index.toString()] = '';
+ return acc;
+ },
+ {},
+ );
+
+ const cssStyleDeclaration: CSSStyleDeclaration = {
+ getPropertyValue: (prop: string) => '',
+ item: (index: number) => '',
+ removeProperty: (property: string) => '',
+ setProperty: (property: string, value: string) => {},
+ parentRule: null,
+ length: 0,
+ [Symbol.iterator]: function* () {},
+ ...cssProperties,
+ } as CSSStyleDeclaration;
+
+ return cssStyleDeclaration;
+};
+
+// Suppress specific console warnings
+const originalError = console.error;
+console.error = (...args) => {
+ if (args[0].includes('Warning: ReactDOM.render is no longer supported')) {
+ return;
+ }
+ originalError.call(console, ...args);
+};
+
+const mockStore = configureStore([]);
+
+// Test fixtures
+const mockBarGraphProps = {
+ width: 800,
+ height: 600,
+ data: {
+ barStack: [
+ {
+ snapshotId: 'snapshot1',
+ 'Component-1': 100,
+ 'Component-2': 150,
+ },
+ {
+ snapshotId: 'snapshot2',
+ 'Component-1': 120,
+ 'Component-2': 140,
+ },
+ ],
+ componentData: {
+ 'Component-1': {
+ stateType: 'stateless',
+ renderFrequency: 2,
+ totalRenderTime: 220,
+ rtid: 'rt1',
+ information: {},
+ },
+ 'Component-2': {
+ stateType: 'stateful',
+ renderFrequency: 2,
+ totalRenderTime: 290,
+ rtid: 'rt2',
+ information: {},
+ },
+ },
+ maxTotalRender: 290,
+ },
+ comparison: [],
+ setRoute: jest.fn(),
+ allRoutes: ['/home', '/about'],
+ filteredSnapshots: [
+ {
+ snapshotId: 'snapshot1',
+ 'Component-1': 100,
+ 'Component-2': 150,
+ },
+ {
+ snapshotId: 'snapshot2',
+ 'Component-1': 120,
+ 'Component-2': 140,
+ },
+ ],
+ setSnapshot: jest.fn(),
+ snapshot: 'All Snapshots',
+};
+
+const mockPerformanceVisxProps = {
+ width: 800,
+ height: 600,
+ snapshots: [
+ {
+ name: 'Root',
+ branch: '1',
+ route: { url: 'http://localhost:3000/home' },
+ children: [
+ {
+ name: 'Component1',
+ componentData: {
+ actualDuration: '100.5',
+ props: { test: 'prop' },
+ },
+ children: [],
+ rtid: 'rt1',
+ state: 'stateless',
+ },
+ ],
+ },
+ ],
+ hierarchy: {
+ name: 'Root',
+ branch: '1',
+ children: [],
+ },
+};
+
+const mockReduxState = {
+ main: {
+ tabs: {
+ 0: { title: 'Test Tab' },
+ },
+ currentTab: 0,
+ currentTabInApp: 'performance',
+ },
+};
+
+describe('Performance Components', () => {
+ let store;
+
+ beforeEach(() => {
+ store = mockStore(mockReduxState);
+ Storage.prototype.getItem = jest.fn(() => null);
+ Storage.prototype.setItem = jest.fn();
+ // Clear mock calls before each test
+ mockBarGraphProps.setSnapshot.mockClear();
+ mockBarGraphProps.setRoute.mockClear();
+ });
+
+ describe('BarGraph Component', () => {
+ const renderBarGraph = () => {
+ return render(
+
+ {/* @ts-ignore */}
+
+ ,
+ );
+ };
+
+ it('renders without crashing', () => {
+ const { container } = renderBarGraph();
+ expect(screen.getByText('Route:')).toBeInTheDocument();
+ expect(screen.getByText('Snapshot:')).toBeInTheDocument();
+ expect(container.querySelector('svg')).toBeInTheDocument();
+ });
+
+ it('displays correct axis labels', () => {
+ renderBarGraph();
+ expect(screen.getByText('Rendering Time (ms)')).toBeInTheDocument();
+ expect(screen.getByText('Snapshot ID')).toBeInTheDocument();
+ });
+
+ it('handles route selection', () => {
+ renderBarGraph();
+ const routeSelect = screen.getByLabelText('Route:');
+ fireEvent.change(routeSelect, { target: { value: '/home' } });
+ expect(mockBarGraphProps.setRoute).toHaveBeenCalledWith('/home');
+ });
+
+ it('handles snapshot selection', () => {
+ renderBarGraph();
+ const snapshotSelect = screen.getByLabelText('Snapshot:');
+ fireEvent.change(snapshotSelect, { target: { value: 'snapshot1' } });
+ expect(mockBarGraphProps.setSnapshot).toHaveBeenCalledWith('snapshot1');
+ expect(mockBarGraphProps.setSnapshot).toHaveBeenCalledTimes(1);
+ });
+
+ it('renders correct number of bars', () => {
+ const { container } = renderBarGraph();
+ const bars = container.querySelectorAll('rect[width]');
+ // Each snapshot has 2 components, so we expect 4 bars total
+ expect(bars.length).toBe(5);
+ });
+ });
+
+ describe('PerformanceVisx Component', () => {
+ const renderPerformanceVisx = () => {
+ return render(
+
+
+
+
+ ,
+ );
+ };
+
+ it('renders without crashing', () => {
+ const { container } = renderPerformanceVisx();
+ expect(container.querySelector('svg')).toBeInTheDocument();
+ });
+
+ it('dispatches setCurrentTabInApp on mount', () => {
+ renderPerformanceVisx();
+ const actions = store.getActions();
+ expect(actions).toEqual([{ type: 'main/setCurrentTabInApp', payload: 'performance' }]);
+ });
+
+ it('processes route data correctly', () => {
+ renderPerformanceVisx();
+ expect(screen.getByText('/home')).toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/app/__tests__/StateContainer.test.tsx b/src/app/__tests__/StateContainer.test.tsx
new file mode 100644
index 000000000..ca91d5b9d
--- /dev/null
+++ b/src/app/__tests__/StateContainer.test.tsx
@@ -0,0 +1,265 @@
+// State.test.tsx
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import { configureStore } from '@reduxjs/toolkit';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
+import '@testing-library/jest-dom';
+
+import StateRoute from '../components/StateRoute/StateRoute';
+import StateContainer from '../containers/StateContainer';
+import { mainSlice } from '../slices/mainSlice';
+
+// Mock ResizeObserver
+class ResizeObserverMock {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+}
+
+global.ResizeObserver = ResizeObserverMock;
+
+// Mock child components
+jest.mock('../components/StateRoute/Tree', () => () => (
+ Tree Component
+));
+
+jest.mock('../components/StateRoute/ComponentMap/ComponentMap', () => () => (
+ Component Map
+));
+
+jest.mock('../components/StateRoute/PerformanceVisx/PerformanceVisx', () => () => (
+ Performance Component
+));
+
+jest.mock('../components/StateRoute/WebMetrics/WebMetricsContainer', () => () => (
+ Web Metrics Component
+));
+
+jest.mock('../components/StateRoute/AxMap/AxContainer', () => () => (
+ Ax Container
+));
+
+jest.mock('../components/StateRoute/History', () => ({
+ default: () => History Component
,
+}));
+
+// Mock StateRoute with proper routing and navigation
+jest.mock('../components/StateRoute/StateRoute', () => {
+ const { Link, useLocation } = require('react-router-dom');
+
+ return function MockStateRoute({ hierarchy }) {
+ const location = useLocation();
+
+ return (
+
+
+
+ Map
+
+
+ History
+
+
+ Performance
+
+
+ Web Metrics
+
+
+ Tree
+
+
+ Accessibility
+
+
+
+
+ {location.pathname === '/accessibility' && (
+ <>
+
+ A Note to Developers: Reactime is using the Chrome Debugger API in order to grab the
+ Accessibility Tree. Enabling this option will allow you to record Accessibility Tree
+ snapshots, but will result in the Chrome browser notifying you that the Chrome
+ Debugger has started.
+
+
+
+ Enable
+
+
Ax Container
+ >
+ )}
+ {location.pathname === '/' && hierarchy && (
+
Component Map
+ )}
+ {location.pathname === '/history' && (
+
History Component
+ )}
+ {location.pathname === '/performance' && (
+
Performance Component
+ )}
+
+
+ );
+ };
+});
+
+// Mock ParentSize component
+jest.mock('@visx/responsive', () => ({
+ ParentSize: ({ children }) => children({ width: 100, height: 100 }),
+}));
+
+const createMockStore = (initialState = {}) => {
+ return configureStore({
+ reducer: {
+ main: mainSlice.reducer,
+ },
+ preloadedState: {
+ main: {
+ tabs: [
+ {
+ hierarchy: {},
+ sliderIndex: 0,
+ viewIndex: 0,
+ },
+ ],
+ currentTab: 0,
+ // @ts-ignore
+ port: {
+ postMessage: jest.fn(),
+ },
+ ...initialState,
+ },
+ },
+ });
+};
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
+
+describe('State Components', () => {
+ const defaultProps = {
+ axSnapshots: [],
+ snapshot: {},
+ hierarchy: {},
+ snapshots: [],
+ viewIndex: 0,
+ webMetrics: {},
+ currLocation: { stateSnapshot: {} },
+ };
+
+ describe('StateRoute Component', () => {
+ const renderStateRoute = (props = {}, initialState = {}, initialPath = '/') => {
+ const store = createMockStore(initialState);
+ return render(
+
+
+
+ {/* @ts-ignore */}
+ } />
+
+
+ ,
+ );
+ };
+
+ it('renders navigation links correctly', () => {
+ renderStateRoute();
+ expect(screen.getByRole('link', { name: /map/i })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: /history/i })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: /performance/i })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: /web metrics/i })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: /tree/i })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: /accessibility/i })).toBeInTheDocument();
+ });
+
+ it('toggles accessibility tree view when enable radio is clicked', () => {
+ renderStateRoute({}, { port: { postMessage: jest.fn() } }, '/accessibility');
+
+ // Check initial state
+ expect(screen.getByText(/a note to developers/i)).toBeInTheDocument();
+
+ // Find and click enable radio button
+ const enableRadio = screen.getByRole('radio', { name: /enable/i });
+ fireEvent.click(enableRadio);
+
+ // Verify the accessibility container is shown
+ expect(screen.getByTestId('mock-ax-container')).toBeInTheDocument();
+ });
+
+ it('renders component map when hierarchy is provided', () => {
+ renderStateRoute({
+ hierarchy: { some: 'data' },
+ currLocation: { stateSnapshot: { some: 'data' } },
+ });
+
+ expect(screen.getByTestId('mock-component-map')).toBeInTheDocument();
+ });
+
+ it('handles route changes correctly', () => {
+ renderStateRoute({
+ hierarchy: { some: 'data' },
+ });
+
+ const historyLink = screen.getByRole('link', { name: /history/i });
+ fireEvent.click(historyLink);
+ expect(screen.getByTestId('mock-history')).toBeInTheDocument();
+
+ const performanceLink = screen.getByRole('link', { name: /performance/i });
+ fireEvent.click(performanceLink);
+ expect(screen.getByTestId('mock-performance')).toBeInTheDocument();
+ });
+ });
+
+ describe('StateContainer Component', () => {
+ const renderStateContainer = (props = {}) => {
+ const store = createMockStore();
+ return render(
+
+
+ {/* @ts-ignore */}
+
+
+ ,
+ );
+ };
+
+ it('renders without crashing', () => {
+ renderStateContainer();
+ expect(screen.getByTestId('mock-state-route')).toBeInTheDocument();
+ });
+
+ it('renders structural navbar container', () => {
+ renderStateContainer();
+ expect(document.querySelector('.main-navbar-container--structural')).toBeInTheDocument();
+ });
+
+ it('passes props correctly to StateRoute', () => {
+ const testProps = {
+ snapshot: { test: 'snapshot' },
+ hierarchy: { test: 'hierarchy' },
+ snapshots: [{ test: 'snapshots' }],
+ viewIndex: 1,
+ webMetrics: { test: 'metrics' },
+ currLocation: { test: 'location' },
+ axSnapshots: [{ test: 'ax' }],
+ };
+
+ renderStateContainer(testProps);
+ expect(screen.getByTestId('mock-state-route')).toBeInTheDocument();
+ });
+
+ it('handles nested routes correctly', () => {
+ renderStateContainer();
+ expect(screen.getByTestId('mock-state-route')).toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/app/__tests__/TimeTravel.test.tsx b/src/app/__tests__/TimeTravel.test.tsx
new file mode 100644
index 000000000..145e86aeb
--- /dev/null
+++ b/src/app/__tests__/TimeTravel.test.tsx
@@ -0,0 +1,382 @@
+import React from 'react';
+import { render as rtlRender, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { Provider } from 'react-redux';
+import { store } from '../store';
+import { useDispatch } from 'react-redux';
+import { changeView, changeSlider } from '../slices/mainSlice';
+import { configureStore } from '@reduxjs/toolkit';
+import { mainSlice } from '../slices/mainSlice';
+
+import Action from '../components/Actions/Action';
+import RouteDescription from '../components/Actions/RouteDescription';
+import VerticalSlider from '../components/TimeTravel/VerticalSlider';
+
+
+// Mock react-redux
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'),
+ useDispatch: jest.fn(),
+}));
+
+// Helper function for redux wrapped renders
+const render = (component) => rtlRender({component} );
+
+// RouteDescription Component Tests
+describe('RouteDescription Component', () => {
+ // Mock the vertical slider component
+ jest.mock('../components/TimeTravel/VerticalSlider.tsx', () => {
+ return function MockVerticalSlider({ snapshots }) {
+ return {snapshots.length} snapshots
;
+ };
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('renders route path correctly', () => {
+ const mockActions = [
+ ,
+ ];
+
+ // Create store with initial state
+ const mockStore = configureStore({
+ reducer: {
+ main: mainSlice.reducer,
+ },
+ preloadedState: {
+ main: {
+ port: null,
+ currentTab: 0,
+ currentTitle: 'No Target',
+ tabs: {
+ 0: {
+ currLocation: {
+ index: 0,
+ stateSnapshot: {},
+ },
+ hierarchy: {},
+ sliderIndex: 0,
+ viewIndex: 0,
+ snapshots: [],
+ playing: false,
+ intervalId: null,
+ mode: { paused: false },
+ status: {
+ reactDevToolsInstalled: true,
+ targetPageisaReactApp: true,
+ },
+ },
+ },
+ currentTabInApp: null,
+ connectionStatus: true,
+ connectRequested: true,
+ },
+ },
+ });
+
+ render(
+
+
+ ,
+ );
+
+ // Check if the route path is displayed correctly
+ expect(screen.getByText('Route: /test-route')).toBeInTheDocument();
+ });
+
+ test('renders actions container with correct height', () => {
+ // Create multiple actions to test height calculation
+ const mockActions = [
+ ,
+ ,
+ ];
+
+ // Create store with initial state
+ const mockStore = configureStore({
+ reducer: {
+ main: mainSlice.reducer,
+ },
+ preloadedState: {
+ main: {
+ port: null,
+ currentTab: 0,
+ currentTitle: 'No Target',
+ tabs: {
+ 0: {
+ currLocation: {
+ index: 0,
+ stateSnapshot: {},
+ },
+ hierarchy: {},
+ sliderIndex: 0,
+ viewIndex: 0,
+ snapshots: [],
+ playing: false,
+ intervalId: null,
+ mode: { paused: false },
+ status: {
+ reactDevToolsInstalled: true,
+ targetPageisaReactApp: true,
+ },
+ },
+ },
+ currentTabInApp: null,
+ connectionStatus: true,
+ connectRequested: true,
+ },
+ },
+ });
+
+ render(
+
+
+ ,
+ );
+
+ // Get the route content container
+ const routeContent = document.querySelector('.route-content');
+
+ // Check if height is calculated correctly (40.5px * number of actions)
+ expect(routeContent).toHaveStyle({ height: '81px' }); // 40.5 * 2 = 81
+ });
+});
+
+// Action Component Tests
+describe('Action Component', () => {
+ // @ts-ignore
+ const useDispatchMock = useDispatch as jest.Mock;
+ const dummyDispatch = jest.fn();
+ useDispatchMock.mockReturnValue(dummyDispatch);
+
+ const props = {
+ key: 'actions2',
+ selected: true,
+ last: false,
+ index: 2,
+ sliderIndex: 2,
+ isCurrIndex: false,
+ routePath: '',
+ displayName: '3.0',
+ componentName: 'App',
+ logChangedState: jest.fn(),
+ componentData: {
+ actualDuration: 3.5,
+ },
+ state: { test: 'test' },
+ viewIndex: 2,
+ handleOnkeyDown: jest.fn(),
+ };
+
+ beforeEach(() => {
+ props.isCurrIndex = false;
+ props.componentData = { actualDuration: 3.5 };
+ });
+
+ test('Action snapshot should be shown as Snapshot: 3.0', () => {
+ render( );
+ expect(screen.getByPlaceholderText('Snapshot: 3.0')).toBeInTheDocument();
+ });
+
+ test('Two buttons with Time and Jump when not at current snapshot', () => {
+ props.isCurrIndex = false;
+ render( );
+ expect(screen.getAllByRole('button')).toHaveLength(2);
+ expect(screen.getAllByRole('button')[0]).toHaveTextContent('+00:03.50');
+ expect(screen.getAllByRole('button')[1]).toHaveTextContent('Jump');
+ });
+
+ test('Two buttons with Time and Current when at current snapshot', () => {
+ props.isCurrIndex = true;
+ render( );
+ expect(screen.getAllByRole('button')).toHaveLength(1);
+ expect(screen.getAllByRole('button')[0]).toHaveTextContent('Current');
+ });
+
+ test('When there is no duration data', () => {
+ // @ts-ignore
+ props.componentData = undefined;
+ render( );
+ expect(screen.getAllByRole('button')[0]).toHaveTextContent('NO TIME');
+ });
+
+ test('When actualDuration exceeds 60, time should be formatted correctly', () => {
+ props.componentData.actualDuration = 75;
+ render( );
+ expect(screen.getAllByRole('button')[0]).toHaveTextContent('+01:15.00');
+ });
+
+ test('Clicking the snapshot should trigger onClick', () => {
+ render( );
+ fireEvent.click(screen.getByRole('presentation'));
+ expect(dummyDispatch).toHaveBeenCalledWith(changeView(props.index));
+ });
+
+ test('Clicking Jump button should trigger changeSlider and changeView', () => {
+ render( );
+ fireEvent.click(screen.getAllByRole('button')[1]);
+ expect(dummyDispatch).toHaveBeenCalledWith(changeSlider(props.index));
+ expect(dummyDispatch).toHaveBeenCalledWith(changeView(props.index));
+ });
+});
+
+// VerticalSlider Component Tests
+describe('VerticalSlider Component', () => {
+ const useDispatchMock = jest.fn();
+ const dummyDispatch = jest.fn();
+
+ // Define the mock state
+ const mockState = {
+ main: {
+ tabs: {
+ 0: {
+ currLocation: { index: 1 },
+ },
+ },
+ currentTab: 0,
+ },
+ };
+
+ const mockStore = configureStore({
+ reducer: {
+ // @ts-ignore
+ main: mainSlice.reducer,
+ },
+ preloadedState: mockState,
+ });
+
+ // Helper function to create store with custom state
+ const createMockStore = (state: any) =>
+ configureStore({
+ reducer: {
+ // @ts-ignore
+ main: mainSlice.reducer,
+ },
+ preloadedState: state,
+ });
+
+ const mockSnapshots = [{ props: { index: 0 } }, { props: { index: 1 } }, { props: { index: 2 } }];
+
+ beforeEach(() => {
+ useDispatchMock.mockClear();
+ useDispatchMock.mockReturnValue(dummyDispatch);
+ });
+
+ test('renders slider with correct min and max values', () => {
+ render(
+
+
+ ,
+ );
+
+ const slider = screen.getByRole('slider');
+ expect(slider).toHaveAttribute('aria-valuemin', '0');
+ expect(slider).toHaveAttribute('aria-valuemax', '2');
+ });
+
+ test('updates slider index when currLocation changes', () => {
+ const { rerender } = render(
+
+
+ ,
+ );
+
+ const updatedState = {
+ ...mockState,
+ main: {
+ ...mockState.main,
+ tabs: {
+ 0: {
+ currLocation: { index: 2 },
+ },
+ },
+ },
+ };
+
+ rerender(
+
+
+ ,
+ );
+
+ const slider = screen.getByRole('slider');
+ expect(slider).toHaveAttribute('aria-valuenow', '2');
+ });
+
+ test('handles empty snapshots array', () => {
+ render(
+
+
+ ,
+ );
+
+ const slider = screen.getByRole('slider');
+ expect(slider).toHaveAttribute('aria-valuemin', '0');
+ expect(slider).toHaveAttribute('aria-valuemax', '-1');
+ expect(slider).not.toHaveAttribute('aria-valuenow');
+ });
+
+ test('maintains slider position within bounds', () => {
+ const outOfBoundsState = {
+ ...mockState,
+ main: {
+ ...mockState.main,
+ tabs: {
+ 0: {
+ currLocation: { index: 999 }, // intentionally out of bounds
+ },
+ },
+ },
+ };
+
+ render(
+
+
+ ,
+ );
+
+ const slider = screen.getByRole('slider');
+ // It seems the component defaults to 0 for out-of-bounds values
+ expect(slider).toHaveAttribute('aria-valuenow', '0');
+ });
+});
diff --git a/src/app/__tests__/TravelContainer.test.js b/src/app/__tests__/TravelContainer.test.js
deleted file mode 100644
index a3be43b72..000000000
--- a/src/app/__tests__/TravelContainer.test.js
+++ /dev/null
@@ -1,105 +0,0 @@
-/* eslint-disable react/jsx-filename-extension */
-import { shallow, configure } from 'enzyme';
-import React from 'react';
-import Adapter from 'enzyme-adapter-react-16';
-import TravelContainer from '../containers/TravelContainer';
-import MainSlider from '../components/MainSlider';
-import Dropdown from '../components/Dropdown';
-import { useStoreContext } from '../store';
-import { moveBackward, moveForward } from '../actions/actions';
-
-configure({ adapter: new Adapter() });
-
-const state = {
- tabs: {
- 87: {
- snapshots: [1, 2, 3, 4],
- sliderIndex: 2,
- playing: true,
- },
- },
- currentTab: 87,
-};
-
-const dispatch = jest.fn();
-jest.mock('../store');
-useStoreContext.mockImplementation(() => [state, dispatch]);
-
-let wrapper;
-
-beforeEach(() => {
- wrapper = shallow( );
- useStoreContext.mockClear();
- dispatch.mockClear();
-});
-
-describe(' rendering', () => {
- test('should render three buttons', () => {
- expect(wrapper.find('button')).toHaveLength(3);
- });
- test('should render one MainSlider', () => {
- expect(wrapper.find(MainSlider)).toHaveLength(1);
- });
- test('should render one Dropdown', () => {
- expect(wrapper.find(Dropdown)).toHaveLength(1);
- });
-});
-
-describe('testing the backward-button', () => {
- test('should dispatch action upon click', () => {
- wrapper.find('.backward-button').simulate('click');
- expect(dispatch.mock.calls.length).toBe(1);
- });
-
- test('should send moveBackward action to dispatch', () => {
- wrapper.find('.backward-button').simulate('click');
- expect(dispatch.mock.calls[0][0]).toEqual(moveBackward());
- });
-});
-
-describe('testing the forward-button', () => {
- test('should dispatch action upon click', () => {
- wrapper.find('.forward-button').simulate('click');
- expect(dispatch.mock.calls.length).toBe(1);
- });
-
- test('should send moveforward action to dispatch', () => {
- wrapper.find('.forward-button').simulate('click');
- expect(dispatch.mock.calls[0][0]).toEqual(moveForward());
- });
-});
-
-describe('testing the play-button', () => {
- test("should display 'pause' if playing is true", () => {
- state.tabs[87].playing = true;
- wrapper = shallow( );
- expect(wrapper.find('.play-button').text()).toBe('Pause');
- });
-
- test('should display play if playing is false', () => {
- state.tabs[87].playing = false;
- wrapper = shallow( );
- expect(wrapper.find('.play-button').text()).toBe('Play');
- });
-});
-
-// describe('testing the playback speed', () => {
-// test('if the playback dropdown states 0.5x the speed should be 0.5x', () => {
-// const wrapper = shallow( );
-// wrapper.find('Dropdown').simulate('change', { value: ['val'] });
-// wrapper.find('select').simulate('change', { value: 'hello' });
-// console.log('val', wrapper.find('Dropdown').simulate('select', { value: ['val'] }));
-// expect(wrapper.find('Dropdown').text()).toBe('0.5x');
-// expect(wrapper.find('select [selected]').val()).toEqual('key');
-// });
-// test('if the playback dropdown states 1x the speed should be 1x', () => {
-// const wrapper = shallow( );
-
-// expect(wrapper.find('Dropdown').label).toBe('1.0x');
-// });
-// test('if the playback dropdown states 2x the speed should be 2x', () => {
-// const wrapper = shallow( );
-// wrapper.find('Dropdown').simulate('click');
-// expect(wrapper.find('Dropdown').label).toBe('2.0x');
-// });
-// });
diff --git a/src/app/__tests__/TravelContainer.test.tsx b/src/app/__tests__/TravelContainer.test.tsx
new file mode 100644
index 000000000..6e5e791e2
--- /dev/null
+++ b/src/app/__tests__/TravelContainer.test.tsx
@@ -0,0 +1,167 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import '@testing-library/jest-dom';
+
+import configureStore from 'redux-mock-store';
+import TravelContainer from '../containers/TravelContainer';
+import { playForward, pause, startPlaying, resetSlider, changeSlider } from '../slices/mainSlice';
+
+const mockStore = configureStore([]);
+
+describe('TravelContainer', () => {
+ let store;
+
+ beforeEach(() => {
+ store = mockStore({
+ main: {
+ tabs: {
+ tab1: {
+ sliderIndex: 0,
+ playing: false,
+ currLocation: null,
+ },
+ },
+ currentTab: 'tab1',
+ },
+ });
+
+ store.dispatch = jest.fn();
+ });
+
+ const renderComponent = (props = {}) => {
+ const defaultProps = {
+ snapshotsLength: 5,
+ };
+
+ return render(
+
+
+ ,
+ );
+ };
+
+ it('renders play button and dropdown', () => {
+ renderComponent();
+
+ expect(screen.getByRole('button')).toBeInTheDocument();
+ expect(screen.getByText('Play')).toBeInTheDocument();
+ });
+
+ it('changes play button text and icon when clicked', () => {
+ renderComponent();
+
+ const playButton = screen.getByRole('button');
+ fireEvent.click(playButton);
+
+ // Should dispatch startPlaying action
+ expect(store.dispatch).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: startPlaying.type,
+ }),
+ );
+ });
+
+ it('resets slider when playing from last snapshot', () => {
+ store = mockStore({
+ main: {
+ tabs: {
+ tab1: {
+ sliderIndex: 4, // Last index (snapshotsLength - 1)
+ playing: false,
+ currLocation: null,
+ },
+ },
+ currentTab: 'tab1',
+ },
+ });
+ store.dispatch = jest.fn();
+
+ renderComponent();
+
+ const playButton = screen.getByRole('button');
+ fireEvent.click(playButton);
+
+ // Should dispatch resetSlider action
+ expect(store.dispatch).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: resetSlider.type,
+ }),
+ );
+ });
+
+ it('pauses playback when play button is clicked while playing', () => {
+ store = mockStore({
+ main: {
+ tabs: {
+ tab1: {
+ sliderIndex: 2,
+ playing: true,
+ currLocation: null,
+ },
+ },
+ currentTab: 'tab1',
+ },
+ });
+ store.dispatch = jest.fn();
+
+ renderComponent();
+
+ const pauseButton = screen.getByRole('button');
+ fireEvent.click(pauseButton);
+
+ // Should dispatch pause action
+ expect(store.dispatch).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: pause.type,
+ }),
+ );
+ });
+
+ it('handles speed change from dropdown', () => {
+ renderComponent();
+
+ // Find and click the dropdown
+ const dropdown = screen.getByRole('combobox');
+ fireEvent.keyDown(dropdown, { key: 'ArrowDown' });
+
+ // Select a different speed
+ const speedOption = screen.getByText('0.5x');
+ fireEvent.click(speedOption);
+
+ // The selected speed should be updated in the component state
+ expect(screen.getByText('0.5x')).toBeInTheDocument();
+ });
+
+ it('updates slider index when playing forward', () => {
+ const { rerender } = renderComponent();
+
+ // Simulate playing forward
+ store.dispatch(playForward(true));
+ store.dispatch(changeSlider(1));
+
+ // Update store state
+ store = mockStore({
+ main: {
+ tabs: {
+ tab1: {
+ sliderIndex: 1,
+ playing: true,
+ currLocation: null,
+ },
+ },
+ currentTab: 'tab1',
+ },
+ });
+
+ // Rerender with new store state
+ rerender(
+
+
+ ,
+ );
+
+ // Verify the slider index was updated
+ expect(store.getState().main.tabs.tab1.sliderIndex).toBe(1);
+ });
+});
diff --git a/src/app/__tests__/Tutorial.test.tsx b/src/app/__tests__/Tutorial.test.tsx
new file mode 100644
index 000000000..662e2f938
--- /dev/null
+++ b/src/app/__tests__/Tutorial.test.tsx
@@ -0,0 +1,175 @@
+import { render, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import * as React from 'react';
+
+import Tutorial from '../components/Buttons/Tutorial';
+import { setCurrentTabInApp, tutorialSaveSeriesToggle } from '../slices/mainSlice';
+
+// Create a mock for updateStepElement
+const mockUpdateStepElement = jest.fn();
+
+// Keep track of the latest Steps instance
+let currentStepsInstance: any = null;
+
+// Mock the intro.js-react package
+jest.mock('intro.js-react', () => {
+ return {
+ Steps: class MockSteps extends React.Component {
+ constructor(props: any) {
+ super(props);
+ // @ts-ignore
+ this.updateStepElement = mockUpdateStepElement;
+ // Store the instance so we can access it in tests
+ currentStepsInstance = this;
+ // Call the ref with this instance if provided
+ if (props.ref) {
+ props.ref(this);
+ }
+ }
+
+ render() {
+ const { enabled, steps, onExit, onBeforeChange } = this.props;
+ return enabled ? (
+
+ {steps.map((step: any, index: number) => (
+
+
{step.title}
+
{step.intro}
+
+ ))}
+
onExit()}>Exit
+
onBeforeChange && onBeforeChange(1)}>Next
+
+ ) : null;
+ }
+ },
+ };
+});
+
+// Mock the dispatch function
+const mockDispatch = jest.fn();
+
+describe('Tutorial Component', () => {
+ const defaultProps = {
+ currentTabInApp: 'map',
+ dispatch: mockDispatch,
+ };
+
+ beforeEach(() => {
+ // Clear mock function calls before each test
+ jest.clearAllMocks();
+ currentStepsInstance = null;
+ });
+
+ it('renders without crashing', () => {
+ render( );
+ expect(screen.getByRole('button', { name: /tutorial/i })).toBeInTheDocument();
+ });
+
+ it('starts tutorial when Tutorial button is clicked', () => {
+ render( );
+ const tutorialButton = screen.getByRole('button', { name: /tutorial/i });
+
+ fireEvent.click(tutorialButton);
+
+ expect(screen.getByTestId('mock-steps')).toBeInTheDocument();
+ });
+
+ it('navigates to performance tab before starting tutorial when in performance views', () => {
+ const performanceProps = {
+ ...defaultProps,
+ currentTabInApp: 'performance-comparison',
+ };
+
+ render( );
+ const tutorialButton = screen.getByRole('button', { name: /tutorial/i });
+
+ fireEvent.click(tutorialButton);
+
+ expect(mockDispatch).toHaveBeenCalledWith(setCurrentTabInApp('performance'));
+ });
+
+ describe('Step Navigation', () => {
+ it('handles performance tab tutorial steps correctly', () => {
+ const performanceProps = {
+ ...defaultProps,
+ currentTabInApp: 'performance',
+ };
+
+ render( );
+ const tutorialButton = screen.getByRole('button', { name: /tutorial/i });
+
+ // Start the tutorial
+ fireEvent.click(tutorialButton);
+
+ // Simulate step change by clicking the Next button
+ const nextButton = screen.getByText('Next');
+ fireEvent.click(nextButton);
+
+ // Verify the dispatch was called
+ expect(mockDispatch).toHaveBeenCalledWith(tutorialSaveSeriesToggle('inputBoxOpen'));
+
+ // Verify updateStepElement was called
+ expect(mockUpdateStepElement).toHaveBeenCalledWith(1);
+ });
+ });
+
+ describe('Tutorial Steps Content', () => {
+ it('loads correct steps for map tab', () => {
+ render( );
+ const tutorialButton = screen.getByRole('button', { name: /tutorial/i });
+
+ fireEvent.click(tutorialButton);
+
+ const firstStep = screen.getByTestId('step-0');
+ expect(firstStep).toHaveTextContent('Reactime Tutorial');
+ });
+
+ it('loads correct steps for performance tab', () => {
+ const performanceProps = {
+ ...defaultProps,
+ currentTabInApp: 'performance',
+ };
+
+ render( );
+ const tutorialButton = screen.getByRole('button', { name: /tutorial/i });
+
+ fireEvent.click(tutorialButton);
+
+ const firstStep = screen.getByTestId('step-0');
+ expect(firstStep).toHaveTextContent('Performance Tab');
+ });
+
+ it('shows default message for undefined tabs', () => {
+ const undefinedTabProps = {
+ ...defaultProps,
+ currentTabInApp: 'undefined-tab',
+ };
+
+ render( );
+ const tutorialButton = screen.getByRole('button', { name: /tutorial/i });
+
+ fireEvent.click(tutorialButton);
+
+ const firstStep = screen.getByTestId('step-0');
+ expect(firstStep).toHaveTextContent('No Tutorial For This Tab');
+ });
+ });
+
+ describe('Tutorial Exit', () => {
+ it('handles tutorial exit correctly', () => {
+ render( );
+ const tutorialButton = screen.getByRole('button', { name: /tutorial/i });
+
+ // Start tutorial
+ fireEvent.click(tutorialButton);
+
+ // Find and click the exit button
+ const exitButton = screen.getByText('Exit');
+ fireEvent.click(exitButton);
+
+ // Check that the steps are no longer visible
+ expect(screen.queryByTestId('mock-steps')).not.toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/app/__tests__/WebMetrics.test.tsx b/src/app/__tests__/WebMetrics.test.tsx
new file mode 100644
index 000000000..9775135b6
--- /dev/null
+++ b/src/app/__tests__/WebMetrics.test.tsx
@@ -0,0 +1,123 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import WebMetrics from '../components/StateRoute/WebMetrics/WebMetrics';
+import { Provider } from 'react-redux';
+import { configureStore } from '@reduxjs/toolkit';
+import { mainSlice } from '../slices/mainSlice';
+
+// Mock react-redux hooks
+const mockDispatch = jest.fn();
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'),
+ useDispatch: () => mockDispatch,
+}));
+
+// Mock ApexCharts
+jest.mock('react-apexcharts', () => ({
+ __esModule: true,
+ default: () =>
,
+}));
+
+// Mock react-hover
+jest.mock('react-hover', () => ({
+ __esModule: true,
+ default: ({ children }) => {children}
,
+ Trigger: ({ children }) => {children}
,
+ Hover: ({ children }) => {children}
,
+}));
+
+describe('WebMetrics Component', () => {
+ // Clear all mocks before each test
+ beforeEach(() => {
+ mockDispatch.mockClear();
+ });
+
+ // Setup function to create consistent test environment
+ const setupTest = (customProps = {}) => {
+ const defaultProps = {
+ color: '#0bce6b',
+ series: [75],
+ formatted: (value) => `${value} ms`,
+ score: ['100 ms', '300 ms'],
+ overLimit: false,
+ label: 'Test Metric',
+ name: 'Test Metric Name',
+ description: 'Test metric description',
+ };
+
+ const props = { ...defaultProps, ...customProps };
+
+ const store = configureStore({
+ reducer: {
+ main: mainSlice.reducer,
+ },
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false }),
+ });
+
+ return render(
+
+
+ ,
+ );
+ };
+
+ test('renders chart container', () => {
+ const { container } = setupTest();
+ expect(container.getElementsByClassName('chart-container').length).toBe(1);
+ });
+
+ test('renders ApexCharts component', () => {
+ setupTest();
+ expect(screen.getByTestId('apex-chart')).toBeInTheDocument();
+ });
+
+ test('applies correct color prop to chart options', () => {
+ const testColor = '#ff0000';
+ const { container } = setupTest({ color: testColor });
+ const chartContainer = container.querySelector('.chart-container');
+ expect(chartContainer).toBeInTheDocument();
+ });
+
+ test('handles overLimit prop correctly', () => {
+ const { container } = setupTest({ overLimit: true });
+ const chartContainer = container.querySelector('.chart-container');
+ expect(chartContainer).toBeInTheDocument();
+ });
+
+ test('renders hover content with correct information', () => {
+ const testProps = {
+ name: 'Custom Test Metric',
+ description: 'Custom Test Description',
+ score: ['100 ms', '300 ms'],
+ };
+ const { container } = setupTest(testProps);
+ const hoverBox = container.querySelector('.hover-box');
+ expect(hoverBox).toBeInTheDocument();
+ expect(hoverBox).toHaveTextContent('Custom Test Metric');
+ expect(hoverBox).toHaveTextContent('Custom Test Description');
+ });
+
+ test('displays correct threshold colors in hover box', () => {
+ const { container } = setupTest();
+ const hoverBox = container.querySelector('.hover-box');
+ expect(hoverBox).toBeInTheDocument();
+ });
+
+ test('formats values correctly using formatted prop', () => {
+ const customFormatted = jest.fn((value) => `Custom ${value}`);
+ setupTest({ formatted: customFormatted });
+ expect(screen.getByTestId('apex-chart')).toBeInTheDocument();
+ });
+
+ test('dispatches setCurrentTabInApp on mount', () => {
+ setupTest();
+ expect(mockDispatch).toHaveBeenCalledWith(expect.any(Object));
+ expect(mockDispatch).toHaveBeenCalledTimes(1);
+ });
+
+ test('handles empty series data gracefully', () => {
+ const { container } = setupTest({ series: [] });
+ expect(container.getElementsByClassName('chart-container').length).toBe(1);
+ });
+});
diff --git a/src/app/__tests__/action.test.jsx b/src/app/__tests__/action.test.jsx
deleted file mode 100644
index 8b42f143c..000000000
--- a/src/app/__tests__/action.test.jsx
+++ /dev/null
@@ -1,69 +0,0 @@
-/* eslint-disable react/jsx-props-no-spreading */
-import React from 'react';
-import { configure, shallow } from 'enzyme';
-import Adapter from 'enzyme-adapter-react-16';
-import Action from '../components/Action';
-import { changeView, changeSlider } from '../actions/actions';
-
-configure({ adapter: new Adapter() });
-
-describe('unit testing for Action.jsx', () => {
- let wrapper;
- const props = {
- selected: true,
- sliderIndex: 1,
- index: 1,
- dispatch: jest.fn(),
- };
- beforeEach(() => {
- wrapper = shallow( );
- props.dispatch.mockClear();
- });
-
- describe('Component', () => {
- test("should have a className 'action-component selected' if props.selected is true", () => {
- wrapper.setProps({ selected: true });
- expect(wrapper.hasClass('action-component selected')).toEqual(true);
- });
-
- test("shouldn't have a className 'action-component selected' if props.selected is false", () => {
- wrapper.setProps({ selected: false });
- expect(wrapper.hasClass('action-component selected')).toEqual(false);
- });
-
- test('should have a text that is equal to props.index', () => {
- expect(wrapper.find('.action-component-text').text()).toEqual(props.index.toString());
- });
-
- test('should invoke dispatch method when clicked', () => {
- wrapper.find('.action-component').simulate('click');
- expect(props.dispatch).toHaveBeenCalled();
- });
-
- test('dispatch should send a changeView action', () => {
- wrapper.find('.action-component').simulate('click');
- expect(props.dispatch.mock.calls[0][0]).toEqual(changeView(props.index));
- });
- });
-
- describe('Jump Button', () => {
- test("should render a div with a className 'jump-button' inside action-component", () => {
- expect(
- wrapper
- .find('.action-component')
- .children()
- .find('.jump-button'),
- ).toHaveLength(1);
- });
-
- test('should invoke dispatch method when clicked', () => {
- wrapper.find('.jump-button').simulate('click', { stopPropagation() {} });
- expect(props.dispatch).toHaveBeenCalled();
- });
-
- test('dispatch should send a changeSlider action', () => {
- wrapper.find('.jump-button').simulate('click', { stopPropagation() {} });
- expect(props.dispatch.mock.calls[0][0]).toEqual(changeSlider(props.index));
- });
- });
-});
diff --git a/src/app/__tests__/actionContainer.test.js b/src/app/__tests__/actionContainer.test.js
deleted file mode 100644
index d5382be57..000000000
--- a/src/app/__tests__/actionContainer.test.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/* eslint-disable react/jsx-filename-extension */
-
-import { shallow, configure } from 'enzyme';
-import React from 'react';
-import Adapter from 'enzyme-adapter-react-16';
-import ActionContainer from '../containers/ActionContainer';
-import { useStoreContext } from '../store';
-import { emptySnapshots } from '../actions/actions';
-import Action from '../components/Action';
-
-configure({ adapter: new Adapter() });
-
-const state = {
- tabs: {
- 87: {
- snapshots: [1, 2, 3, 4],
- sliderIndex: 0,
- viewIndex: -1,
- },
- },
- currentTab: 87,
-};
-
-const dispatch = jest.fn();
-
-jest.mock('../store');
-useStoreContext.mockImplementation(() => [state, dispatch]);
-
-let wrapper;
-
-beforeEach(() => {
- wrapper = shallow( );
- useStoreContext.mockClear();
- dispatch.mockClear();
-});
-
-describe('testing the emptySnapshot button', () => {
- test('emptySnapshot button should dispatch action upon click', () => {
- wrapper.find('.empty-button').simulate('click');
- expect(dispatch.mock.calls.length).toBe(1);
- });
- test('emptying snapshots should send emptySnapshot action to dispatch', () => {
- wrapper.find('.empty-button').simulate('click');
- expect(dispatch.mock.calls[0][0]).toEqual(emptySnapshots());
- });
-});
-
-test('number of actions should reflect snapshots array', () => {
- expect(wrapper.find(Action).length).toBe(state.tabs[state.currentTab].snapshots.length);
-});
diff --git a/src/app/__tests__/dropdown.test.js b/src/app/__tests__/dropdown.test.js
deleted file mode 100644
index 403901ad0..000000000
--- a/src/app/__tests__/dropdown.test.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/* eslint-disable react/jsx-props-no-spreading */
-/* eslint-disable react/jsx-filename-extension */
-import React from 'react';
-import { configure, shallow } from 'enzyme';
-import Adapter from 'enzyme-adapter-react-16';
-import Dropdown from '../components/Dropdown';
-
-configure({ adapter: new Adapter() });
-
-describe('unit testing for Dropdown.jsx', () => {
- let wrapper;
- const props = {
- speeds: [
- { value: 1234, label: '0.5x' },
- { value: 312, label: '1.0x' },
- { value: 23, label: '2.0x' },
- ],
- setSpeed: jest.fn(),
- selectedSpeed: { value: 312, label: '1.0x' },
- };
- beforeEach(() => {
- wrapper = shallow( );
- });
-
- describe('Component', () => {
- test('array of objects that have value and label should be options props', () => {
- expect(wrapper.props().options).toEqual(props.speeds);
- });
- test('selectedOption should be value property', () => {
- expect(wrapper.props().value).toEqual(props.selectedSpeed);
- });
- });
-
- describe('setSpeed', () => {
- test('should invoke setSpeed on change', () => {
- wrapper.simulate('change', { value: 2000, label: '0.5x' });
- expect(props.setSpeed).toHaveBeenCalled();
- });
- });
-});
diff --git a/src/app/__tests__/mainReducer.test.jsx b/src/app/__tests__/mainReducer.test.jsx
deleted file mode 100644
index ca993b4cc..000000000
--- a/src/app/__tests__/mainReducer.test.jsx
+++ /dev/null
@@ -1,262 +0,0 @@
-/* eslint-disable max-len */
-import mainReducer from '../reducers/mainReducer';
-import {
- toggleMode, addNewSnapshots, initialConnect, setPort, emptySnapshots, changeView, changeSlider, moveBackward, moveForward, playForward, pause, startPlaying, importSnapshots, setTab, deleteTab,
-} from '../actions/actions';
-
-
-describe('mainReducer testing', () => {
- let state;
- let currentTab;
- beforeEach(() => {
- state = {
- tabs: {
- 87: {
- snapshots: [1, 2, 3, 4],
- sliderIndex: 2,
- viewIndex: -1,
- mode: {
- paused: false,
- locked: false,
- persist: false,
- },
- intervalId: 87,
- playing: true,
- index: 3,
- hierarchy: null, // should be a linked list with four nodes
- currLocation: null, // should point to the last node in hierarchy
- },
- 75: {
- snapshots: [1, 2, 3, 4],
- sliderIndex: 3,
- viewIndex: -1,
- mode: {
- paused: false,
- locked: false,
- persist: false,
- },
- intervalId: 75,
- playing: false,
- },
- },
- currentTab: 87,
- port: {
- postMessage: () => {},
- },
- };
-
- // eslint-disable-next-line prefer-destructuring
- currentTab = state.currentTab;
- });
-
- describe('moveBackward', () => {
- it('should decrement sliderIndex by 1', () => {
- expect(mainReducer(state, moveBackward()).tabs[currentTab].sliderIndex).toEqual(1);
- expect(mainReducer(state, moveBackward()).tabs[currentTab].playing).toEqual(false);
- });
- it('should not decrement if sliderIndex is zero', () => {
- state.tabs[currentTab].sliderIndex = 0;
- const { sliderIndex } = mainReducer(state, moveBackward()).tabs[currentTab];
- expect(sliderIndex).toBe(0);
- });
- });
-
- describe('moveForward', () => {
- it('should increment sliderIndex by 1', () => {
- expect(mainReducer(state, moveForward()).tabs[currentTab].sliderIndex).toEqual(3);
- expect(mainReducer(state, moveForward()).tabs[currentTab].playing).toEqual(false);
- });
- it('should not increment if sliderIndex at end', () => {
- state.tabs[currentTab].sliderIndex = 3;
- const { sliderIndex } = mainReducer(state, moveForward()).tabs[currentTab];
- expect(sliderIndex).toBe(3);
- });
- it('should not change playing if not coming from user', () => {
- const { playing } = mainReducer(state, playForward()).tabs[currentTab];
- expect(playing).toBe(true);
- });
- });
-
- describe('changeView', () => {
- it('unselect view if same index was selected', () => {
- state.tabs[currentTab].viewIndex = 1;
- expect(mainReducer(state, changeView(1)).tabs[currentTab].viewIndex).toEqual(-1);
- });
- it('change viewIndex when unselected', () => {
- expect(mainReducer(state, changeView(2)).tabs[currentTab].viewIndex).toEqual(2);
- });
- });
-
- describe('changeSlider', () => {
- it('should change sliderIndex', () => {
- expect(mainReducer(state, changeSlider(2)).tabs[currentTab].sliderIndex).toEqual(2);
- });
- });
-
- describe('empty', () => {
- it('should empty snapshots except the first one', () => {
- expect(mainReducer(state, emptySnapshots()).tabs[currentTab].sliderIndex).toEqual(0);
- expect(mainReducer(state, emptySnapshots()).tabs[currentTab].viewIndex).toEqual(-1);
- expect(mainReducer(state, emptySnapshots()).tabs[currentTab].playing).toEqual(false);
- expect(mainReducer(state, emptySnapshots()).tabs[currentTab]
- .snapshots).toEqual(state.tabs[currentTab].snapshots.slice(0, 1));
- });
- });
-
- describe('setPort', () => {
- it('should set port when connection', () => {
- expect(mainReducer(state, setPort('newPort')).port).toEqual('newPort');
- });
- });
-
- describe('Import', () => {
- it('impoting file should replace snapshots of devtool', () => {
- expect(mainReducer(state, importSnapshots([100, 101])).tabs[currentTab].snapshots).toEqual([100, 101]);
- });
- });
-
- describe('Toggle Mode', () => {
- it('clicking pause button should only change pause mode', () => {
- const { mode } = mainReducer(state, toggleMode('paused')).tabs[currentTab];
- expect(mode.paused).toBe(true);
- expect(mode.locked).toBe(false);
- expect(mode.persist).toBe(false);
- });
- it('clicking lock button should only change lock mode', () => {
- const { mode } = mainReducer(state, toggleMode('locked')).tabs[currentTab];
- expect(mode.paused).toBe(false);
- expect(mode.locked).toBe(true);
- expect(mode.persist).toBe(false);
- });
- it('clicking persist button should only change persist mode', () => {
- const { mode } = mainReducer(state, toggleMode('persist')).tabs[currentTab];
- expect(mode.paused).toBe(false);
- expect(mode.locked).toBe(false);
- expect(mode.persist).toBe(true);
- });
- it('undefined payload does nothing', () => {
- const { mode } = mainReducer(state, toggleMode('undefined')).tabs[currentTab];
- expect(mode.paused).toBe(false);
- expect(mode.locked).toBe(false);
- expect(mode.persist).toBe(false);
- });
- });
-
- describe('slider pause', () => {
- it('should set playing to false and intervalId to null', () => {
- const playedTab = mainReducer(state, pause()).tabs[currentTab];
- expect(playedTab.playing).toBe(false);
- expect(playedTab.intervalId).toBe(null);
- });
- });
-
- describe('slider play', () => {
- it('should set playing to true and intervalId to payload', () => {
- const playedTab = mainReducer(state, startPlaying(333)).tabs[currentTab];
- expect(playedTab.playing).toBe(true);
- expect(playedTab.intervalId).toBe(333);
- });
- });
-
- describe('Initial Connect', () => {
- const newTab = {
- 104: {
- snapshots: [1, 2, 3, 8],
- sliderIndex: 3,
- viewIndex: -1,
- mode: {
- paused: false,
- locked: false,
- persist: false,
- },
- intervalId: 912,
- playing: true,
- },
- };
- it('should add new tab', () => {
- const addedTab = mainReducer(state, initialConnect(newTab)).tabs[104];
- expect(addedTab).not.toBe(undefined);
- });
- it('should force some values to default', () => {
- const addedTab = mainReducer(state, initialConnect(newTab)).tabs[104];
- expect(addedTab.sliderIndex).toBe(0);
- expect(addedTab.viewIndex).toBe(-1);
- expect(addedTab.intervalId).toBe(null);
- expect(addedTab.playing).toBe(false);
- });
- it('snapshots should match the payload', () => {
- const addedTab = mainReducer(state, initialConnect(newTab)).tabs[104];
- expect(addedTab.snapshots).toEqual(newTab[104].snapshots);
- });
- it('if currentTab undefined currentTab becomes firstTab', () => {
- state.currentTab = undefined;
- const addedTab = mainReducer(state, initialConnect(newTab));
- expect(addedTab.currentTab).toBe(104);
- });
- });
-
- describe('new snapshots', () => {
- const newSnapshots = {
- 87: {
- snapshots: [1, 2, 3, 4, 5],
- sliderIndex: 2,
- viewIndex: -1,
- mode: {
- paused: false,
- locked: false,
- persist: false,
- },
- intervalId: 87,
- playing: true,
- },
- };
- it('update snapshots of corresponding tabId', () => {
- const updated = mainReducer(state, addNewSnapshots(newSnapshots));
- expect(updated.tabs[87].snapshots).toEqual(newSnapshots[87].snapshots);
- });
- it('should delete tabs that are deleted from background script', () => {
- const updated = mainReducer(state, addNewSnapshots(newSnapshots));
- expect(updated.tabs[75]).toBe(undefined);
- });
- it('if currentTab undefined currentTab becomes first Tab', () => {
- state.currentTab = undefined;
- const updated = mainReducer(state, addNewSnapshots(newSnapshots));
- expect(updated.currentTab).toBe(87);
- });
- });
-
- describe('set_tab', () => {
- it('should set tab to payload', () => {
- const newCurrentTab = mainReducer(state, setTab(75)).currentTab;
- expect(newCurrentTab).toBe(75);
- });
- });
-
- describe('delete_tab', () => {
- it('should delete only payload tab from state', () => {
- const afterDelete = mainReducer(state, deleteTab(75));
- expect(afterDelete.tabs[75]).toBe(undefined);
- expect(afterDelete.tabs[87]).not.toBe(undefined);
- });
- it('should change current tab if deleted tab matches current tab', () => {
- const afterDelete = mainReducer(state, deleteTab(87));
- expect(afterDelete.tabs[87]).toBe(undefined);
- expect(afterDelete.tabs[75]).not.toBe(undefined);
- expect(afterDelete.currentTab).toBe(75);
- });
- });
-
- describe('default', () => {
- const action = {
- type: 'doesNotExist',
- payload: 'trigger',
- };
- it('if there are no match of action types, throw error', () => {
- try {
- mainReducer(state, action);
- } catch (err) {
- expect(err).toBeInstanceOf(Error);
- }
- });
- });
-});
diff --git a/src/app/__tests__/switchState.test.jsx b/src/app/__tests__/switchState.test.jsx
deleted file mode 100644
index 6e81e482d..000000000
--- a/src/app/__tests__/switchState.test.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-// import React from 'react';
-// import { configure, shallow } from 'enzyme';
-// import SwitchState from '../components/SwitchState';
-
-// Should describe unit testing for SwitchState
-// Should create a shallow copy of SwitchState
-// The component should have an array of objects that have value
-// and the label should be the name of the state being captured
-
-// If the label is selected, it should only display relevant components with the same state name
-
-
-describe('placeholder', () => {
- it.skip('placeholder for tests', () => {
- expect(1 + 1).toEqual(2);
- });
-});
diff --git a/src/app/actions/actions.js b/src/app/actions/actions.js
deleted file mode 100644
index 97b5bda0b..000000000
--- a/src/app/actions/actions.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as types from '../constants/actionTypes';
-
-export const toggleMode = mode => ({
- type: types.TOGGLE_MODE,
- payload: mode,
-});
-
-export const addNewSnapshots = tabsObj => ({
- type: types.NEW_SNAPSHOTS,
- payload: tabsObj,
-});
-
-export const initialConnect = tabsObj => ({
- type: types.INITIAL_CONNECT,
- payload: tabsObj,
-});
-
-export const setPort = port => ({
- type: types.SET_PORT,
- payload: port,
-});
-
-export const emptySnapshots = () => ({
- type: types.EMPTY,
-});
-
-export const changeView = index => ({
- type: types.CHANGE_VIEW,
- payload: index,
-});
-
-export const changeSlider = index => ({
- type: types.CHANGE_SLIDER,
- payload: index,
-});
-
-export const moveBackward = () => ({
- type: types.MOVE_BACKWARD,
- payload: false,
-});
-
-export const moveForward = () => ({
- type: types.MOVE_FORWARD,
- payload: false,
-});
-
-export const playForward = () => ({
- type: types.MOVE_FORWARD,
- payload: true,
-});
-
-export const pause = () => ({
- type: types.PAUSE,
-});
-
-export const startPlaying = intervalId => ({
- type: types.PLAY,
- payload: intervalId,
-});
-
-export const importSnapshots = newSnaps => ({
- type: types.IMPORT,
- payload: newSnaps,
-});
-
-export const setTab = tab => ({
- type: types.SET_TAB,
- payload: tab,
-});
-
-export const deleteTab = tab => ({
- type: types.DELETE_TAB,
- payload: tab,
-});
diff --git a/src/app/components/Action.jsx b/src/app/components/Action.jsx
deleted file mode 100644
index d6e03e786..000000000
--- a/src/app/components/Action.jsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { changeView, changeSlider } from '../actions/actions';
-
-// Launch Feature: Figure out changeView vs changeSlider
-// Should we make the btn bigger instead and keep the functionality?
-// div onclick event triggers the changeView method
-
-const Action = props => {
- const {
- selected, index, sliderIndex, dispatch,
- } = props;
-
- return (
- dispatch(changeView(index))}
- role="presentation"
- style={index > sliderIndex ? { color: '#5f6369' } : {}}
- >
-
{index}
-
{
- e.stopPropagation();
- dispatch(changeSlider(index));
- }}
- tabIndex={index}
- type="button"
- >
- Jump
-
-
- );
-};
-
-Action.propTypes = {
- sliderIndex: PropTypes.number.isRequired,
- selected: PropTypes.bool.isRequired,
- index: PropTypes.number.isRequired,
- dispatch: PropTypes.func.isRequired,
-};
-
-export default Action;
diff --git a/src/app/components/Actions/Action.tsx b/src/app/components/Actions/Action.tsx
new file mode 100644
index 000000000..8aa49ba26
--- /dev/null
+++ b/src/app/components/Actions/Action.tsx
@@ -0,0 +1,119 @@
+import React from 'react';
+import ReactHover, { Trigger, Hover } from 'react-hover';
+import { changeView, changeSlider } from '../../slices/mainSlice';
+import { ActionProps, OptionsCursorTrueWithMargin } from '../../FrontendTypes';
+import { useDispatch } from 'react-redux';
+
+const Action = (props: ActionProps): JSX.Element => {
+ const dispatch = useDispatch();
+
+ const { selected, last, index, sliderIndex, displayName, componentData, viewIndex, isCurrIndex } =
+ props;
+
+ const cleanTime = (): string => {
+ if (!componentData || !componentData.actualDuration) {
+ return 'NO TIME';
+ }
+
+ let seconds: number | string;
+ let milliseconds: any = componentData.actualDuration;
+
+ if (Math.floor(componentData.actualDuration) > 60) {
+ seconds = Math.floor(componentData.actualDuration / 60);
+ seconds = JSON.stringify(seconds);
+
+ if (seconds.length < 2) {
+ seconds = '0'.concat(seconds);
+ }
+ milliseconds = Math.floor(componentData.actualDuration % 60);
+ } else {
+ seconds = '00';
+ }
+
+ milliseconds = Number.parseFloat(milliseconds as string).toFixed(2);
+ const arrayMilliseconds: [string, number] = milliseconds.split('.');
+
+ if (arrayMilliseconds[0].length < 2) {
+ arrayMilliseconds[0] = '0'.concat(arrayMilliseconds[0]);
+ }
+
+ if (index === 0) {
+ return `${seconds}:${arrayMilliseconds[0]}.${arrayMilliseconds[1]}`;
+ }
+ return `+${seconds}:${arrayMilliseconds[0]}.${arrayMilliseconds[1]}`;
+ };
+
+ const displayTime: string = cleanTime();
+
+ const optionsCursorTrueWithMargin: OptionsCursorTrueWithMargin = {
+ followCursor: true,
+ shiftX: 20,
+ shiftY: 0,
+ };
+
+ return (
+
+
{
+ dispatch(changeView(index));
+ }}
+ role='presentation'
+ style={index > sliderIndex ? { color: '#5f6369' } : {}}
+ tabIndex={index}
+ >
+
+
+ sliderIndex ? { color: '#5f6369' } : {}}
+ >
+
+
+
+ {isCurrIndex ? (
+
+ ) : (
+
+ {displayTime}
+
+ )}
+ {isCurrIndex ? (
+
+ Current
+
+ ) : (
+
{
+ e.stopPropagation();
+ dispatch(changeSlider(index));
+ dispatch(changeView(index));
+ }}
+ tabIndex={index}
+ type='button'
+ >
+ Jump
+
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default Action;
diff --git a/src/app/components/Actions/DropDown.tsx b/src/app/components/Actions/DropDown.tsx
new file mode 100644
index 000000000..b53320971
--- /dev/null
+++ b/src/app/components/Actions/DropDown.tsx
@@ -0,0 +1,33 @@
+import Select from 'react-select';
+import React from 'react';
+
+const DropDown = ({
+ dropdownSelection,
+ setDropdownSelection,
+}: {
+ dropdownSelection: string;
+ setDropdownSelection: (value: string) => void;
+}): JSX.Element => {
+ const handleChange = (selected: { value: string; label: string }) => {
+ setDropdownSelection(selected.value);
+ };
+
+ const options = [
+ { value: 'Time Jump', label: 'Time Jump' },
+ { value: 'Providers / Consumers', label: 'Providers / Consumers' },
+ ];
+
+ return (
+
+ option.value === dropdownSelection)}
+ />
+
+ );
+};
+
+export default DropDown;
diff --git a/src/app/components/Actions/RecordButton.tsx b/src/app/components/Actions/RecordButton.tsx
new file mode 100644
index 000000000..1f1446144
--- /dev/null
+++ b/src/app/components/Actions/RecordButton.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import { Switch } from '@mui/material';
+import ThemeToggle from './ThemeToggle';
+
+const RecordButton = ({ isRecording, onToggle }) => {
+ return (
+
+ );
+};
+
+export default RecordButton;
diff --git a/src/app/components/Actions/RouteDescription.tsx b/src/app/components/Actions/RouteDescription.tsx
new file mode 100644
index 000000000..aa1c75eea
--- /dev/null
+++ b/src/app/components/Actions/RouteDescription.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import VerticalSlider from '../TimeTravel/VerticalSlider';
+
+/*
+ Render's the red route description on app's left sided column between the clear button and the list of state snapshots. The route description is derived from the first state snapshot.
+*/
+
+type RouteProps = {
+ actions: JSX.Element[];
+};
+
+const RouteDescription = (props: RouteProps): JSX.Element => {
+ const { actions } = props;
+
+ const url: URL = new URL(actions[0].props.routePath); // Use new URL to use the url.pathname method.
+
+ return (
+
+
Route: {url.pathname}
+
+
+
+
+
+ {/* actual snapshots per route */}
+ {actions}
+
+
+
+ );
+};
+
+export default RouteDescription;
diff --git a/src/app/components/Actions/ThemeToggle.tsx b/src/app/components/Actions/ThemeToggle.tsx
new file mode 100644
index 000000000..c8288e757
--- /dev/null
+++ b/src/app/components/Actions/ThemeToggle.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { Moon, Sun } from 'lucide-react';
+import { useTheme } from '../../ThemeProvider';
+
+const ThemeToggle = () => {
+ const { isDark, toggleTheme } = useTheme();
+
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default ThemeToggle;
diff --git a/src/app/components/App.jsx b/src/app/components/App.jsx
deleted file mode 100644
index 64d11cd62..000000000
--- a/src/app/components/App.jsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import React, { useReducer } from 'react';
-import MainContainer from '../containers/MainContainer';
-import { StoreContext } from '../store';
-import mainReducer from '../reducers/mainReducer';
-
-const initialState = {
- port: null,
- currentTab: null,
- tabs: {},
-};
-
-function App() {
- return (
-
-
-
- );
-}
-
-export default App;
diff --git a/src/app/components/Buttons/StatusDot.tsx b/src/app/components/Buttons/StatusDot.tsx
new file mode 100644
index 000000000..1f427e5ed
--- /dev/null
+++ b/src/app/components/Buttons/StatusDot.tsx
@@ -0,0 +1,7 @@
+import React from 'react';
+
+const StatusDot = ({ status }) => {
+ return ;
+};
+
+export default StatusDot;
diff --git a/src/app/components/Buttons/Tutorial.tsx b/src/app/components/Buttons/Tutorial.tsx
new file mode 100644
index 000000000..29d526e90
--- /dev/null
+++ b/src/app/components/Buttons/Tutorial.tsx
@@ -0,0 +1,340 @@
+/* eslint-disable react/sort-comp */
+/* eslint-disable lines-between-class-members */
+/* eslint-disable react/static-property-placement */
+
+import * as React from 'react';
+import { Component } from 'react';
+import 'intro.js/introjs.css';
+import { TutorialProps, TutorialState, StepsObj } from '../../FrontendTypes';
+import { Button } from '@mui/material';
+const { Steps } = require('intro.js-react');
+import { setCurrentTabInApp, tutorialSaveSeriesToggle } from '../../slices/mainSlice';
+import { HelpCircle } from 'lucide-react';
+
+/*
+ This is the tutorial displayed when the "How to use" button is clicked
+ This needs to be a class component to be compatible with updateStepElement from intro.js
+
+ currently written as class components vs functional components.
+*/
+
+export default class Tutorial extends Component {
+ constructor(props: TutorialProps) {
+ super(props);
+ this.state = {
+ stepsEnabled: false,
+ };
+ }
+
+ //tutorial class needs these public variables to be a valid class component for ts when rendered in buttonscontainer.tsx
+ public context: any;
+ public setState: any;
+ public forceUpdate: any;
+ public props: any;
+ public state: any;
+ public refs: any;
+
+ render(): JSX.Element {
+ const {
+ currentTabInApp, // 'currentTabInApp' from 'ButtonsContainer' after useSelector()
+ dispatch, // 'dispatch' from 'ButtonsContainer' after useDispatch()
+ } = this.props;
+
+ // This updates the steps so that they can target dynamically rendered elements
+ const onChangeHandler = (currentStepIndex: number) => {
+ // takes in the current step and updates the tab[currentTab]'s seriesSavedStatus based on conditions and updates the element associated with the step.
+ if (currentTabInApp === 'performance' && currentStepIndex === 1) {
+ dispatch(tutorialSaveSeriesToggle('inputBoxOpen')); // sends a dispatch that update's tab[currentTab]'s 'seriesSavedStatus' to 'inputBoxOpen'
+ this.steps.updateStepElement(currentStepIndex); // Built in intro.js API that updates element associated with step
+ }
+
+ if (currentTabInApp === 'performance' && currentStepIndex === 2) {
+ this.steps.updateStepElement(currentStepIndex);
+ }
+
+ if (currentTabInApp === 'performance' && currentStepIndex === 4) {
+ dispatch(tutorialSaveSeriesToggle('saved')); // sends a dispatch that update's tab[currentTab]'s 'seriesSavedStatus' to 'saved'
+ this.steps.updateStepElement(currentStepIndex);
+ }
+
+ if (currentTabInApp === 'performance' && currentStepIndex === 5) {
+ this.steps.updateStepElement(currentStepIndex);
+ dispatch(setCurrentTabInApp('performance-comparison')); // dispatch sent at initial page load allowing changing "immer's" draft.currentTabInApp to 'performance-comparison.' to facilitate render.
+ }
+
+ if (currentTabInApp === 'performance-comparison' && currentStepIndex === 6) {
+ dispatch(tutorialSaveSeriesToggle(false)); // sends a dispatch that update's tab[currentTab]'s 'seriesSavedStatus' to the boolean 'false'
+ }
+ };
+
+ const onExit = () => {
+ // This callback is called when the tutorial exits
+ this.setState({ stepsEnabled: false }); // sets stepsEnabled to false in this component's state
+ };
+
+ const startIntro = () => {
+ // If "How to use" is clicked while in the performance tab, we'll navigate to the snapshops view before starting the tutorial. This is because the tutorial steps are designed to begin on the snapshots sub-tab. Check out the 'PerformanceVisx' component to see the route redirect logic
+ if (
+ currentTabInApp === 'performance' ||
+ currentTabInApp === 'performance-comparison' ||
+ currentTabInApp === 'performance-component-details'
+ ) {
+ dispatch(setCurrentTabInApp('performance')); // dispatch sent at initial page load allowing changing "immer's" draft.currentTabInApp to 'performance' to facilitate render.
+ }
+ this.setState({ stepsEnabled: true }); // sets stepsEnabled to false in this component's state
+ };
+
+ let steps: StepsObj[] = []; // the steps array will be populated with tutorial elements based on the 'currentTabInApp' case. This allows for specific tutorials based on the current page the user is viewing.
+
+ switch (currentTabInApp) {
+ case 'map':
+ steps = [
+ {
+ title: 'Reactime Tutorial',
+ intro:
+ 'A tool for time travel debugging and performance monitoring in React applications.',
+ position: 'top',
+ },
+ {
+ title: 'Actions',
+ element: '.action-container',
+ intro:
+ "Reactime records a snapshot whenever a target application's state is changed ",
+ position: 'right',
+ },
+ {
+ title: 'Toggles',
+ element: '.record-button-container',
+ intro:
+ 'Toggle record button to pause state changes on target application Toggle theme button to switch between light and dark themes ',
+ position: 'right',
+ },
+ {
+ title: 'Dropdown Menu',
+ element: '.css-13cymwt-control',
+ intro: 'Dropdown Menu for picking between Timejump and UseContext ',
+ position: 'right',
+ },
+ {
+ element: '.individual-action',
+ title: 'Snapshot',
+ intro:
+ 'Each snapshot allows the user to jump to any previously recorded state. It also detects the amount of renders of each component and average time of rendering .',
+ position: 'right',
+ },
+ {
+ title: 'Timejump',
+ element: '.rc-slider',
+ intro:
+ 'Use the slider to go back in time to a particular state change ',
+ position: 'top',
+ },
+ {
+ title: 'Play',
+ element: '.travel-container',
+ intro:
+ 'Click the Play button to run through each state change automatically Select playback speed from the dropdown menu (0.5x, 1.0x, or 2.0x) to control how fast states change during playback ',
+ position: 'top',
+ },
+ {
+ title: 'Lock Button',
+ element: '.pause-button',
+ intro:
+ "Use button to lock Reactime to the target application's tab in the Chrome Browser ",
+ position: 'top',
+ },
+ {
+ title: 'Download Button',
+ element: '.export-button',
+ intro: 'Use button to download a JSON file of all snapshots ',
+ position: 'top',
+ },
+ {
+ title: 'Upload Button',
+ element: '.import-button',
+ intro:
+ 'Use button to upload a previously downloaded JSON file for snapshot comparisons ',
+ position: 'top',
+ },
+ {
+ title: 'Reconnect button',
+ element: '.reconnect-button',
+ intro:
+ 'Click the Reconnect button if connection is lost to reestablish communication with your application. ',
+ position: 'top',
+ },
+ {
+ element: '.map-tab',
+ title: 'Map Tab',
+ intro:
+ 'This tab visually displays a component hierarchy tree for your app ',
+ position: 'bottom',
+ },
+ {
+ title: 'History Tab',
+ element: '.history-tab',
+ intro: 'This tab visually displays a history of each snapshot ',
+ position: 'bottom',
+ },
+ {
+ title: 'Performance Tab',
+ element: '.performance-tab',
+ intro:
+ 'User can save a series of state snapshots and use it to analyze changes in component, render performance between current, and previous series of snapshots. User can save a series of state snapshots and use it to analyze changes in component render performance between current and previous series of snapshots. TIP: Click the how to use button within the performance tab for more details. ',
+ position: 'bottom',
+ },
+
+ {
+ title: 'Web Metrics Tab',
+ element: '.web-metrics-tab',
+ intro:
+ ' This tab visually displays performance metrics and allows the user to gauge efficiency of their application ',
+ position: 'bottom',
+ },
+ {
+ title: 'Tree Tab',
+ element: '.tree-tab',
+ intro:
+ 'This tab visually displays a JSON Tree containing the different components and states ',
+ position: 'bottom',
+ },
+ {
+ title: 'Accessibility Tree',
+ element: '.accessibility-tab', //'This tab visually displays a Accessibility Tree '
+ intro:
+ 'Nodes from the accessibility tree have either a role or a internal role refers to ARIA roles, which indicate the purpose of the element to assistive technologies, like screen readers.All of the nodes rendered in this tree have a role of Role.InternalRole refers to browser-specific roles Chrome for its own accessibility processing. Each node is given a property labeled ignored . Nodes read by the screen reader have their ignored property evaluate to false .Nodes not read by the screen reader evaluate to true .
Nodes labeled as no name are visible to a screen reader, but were not given a name label.
',
+ position: 'bottom',
+ },
+ {
+ title: 'Tutorial Complete',
+ intro:
+ '',
+ position: 'top',
+ },
+ ];
+ break;
+ case 'performance':
+ steps = [
+ {
+ title: 'Performance Tab',
+ element: '.bargraph-position',
+ intro:
+ 'Here we can analyze the render times of our app This is the current series of state changes within our app Mouse over the bargraph elements for details on each specific component ',
+ position: 'top',
+ },
+ ];
+ break;
+ case 'accessibility': //'AxTree'
+ steps = [
+ {
+ title: 'Accessibility Tree',
+ element: '.display',
+ intro:
+ 'Nodes from the accessibility tree have either a role role or internalRole Role refers to ARIA roles, which indicate the purpose of the element to assistive technologies, like screen readers.All of the nodes rendered in this tree have a role of Role.internalRole refers to browser-specific roles Chrome for its own accessibility processing. Each node is given a property labeled ignored . Nodes read by the screen reader have their ignored property evaluate to false .Nodes not read by the screen reader evaluate to true .
Nodes labeled as no name are visible to a screen reader, but were not given a name label.
',
+ position: 'top',
+ },
+ ];
+ break;
+ case 'webmetrics':
+ steps = [
+ {
+ title: 'Webmetrics Tab',
+ element: '.web-metrics-container',
+ intro: 'Additional info can be found be hovering over desired metrics',
+ position: 'top',
+ },
+ ];
+ break;
+ case 'history':
+ steps = [
+ {
+ title: 'History Tab',
+ element: '.display',
+ intro:
+ 'The history tab shows all snapshots as a timeline and includes branches to represent divergent state history created from time traveling backwards and making new state changes.',
+ position: 'top',
+ },
+ {
+ title: 'Viewing History Snapshot',
+ element: '.display', //document.querySelectorAll('.snapshotNode')[0]
+ intro:
+ 'Each node will represent a snapshot in the page. A single snapshot will show as a node while multiple snapshots will be represented as a timeline. Highlighting over one will show any state changes compared to the previous snapshot. Clicking a node will set the snapshot as the current one. ',
+ position: 'top',
+ },
+ {
+ title: 'Navigating through Snapshots',
+ element: '.routedescription',
+ intro: 'All snapshots can also be seen and navigated here as well.',
+ position: 'right',
+ },
+
+ {
+ title: 'Clicking on Jump Button',
+ element: document.querySelectorAll('.individual-action')[0],
+ intro:
+ 'The button on the right of each snapshot can be used to jump to a given point in state to view the state history at that point.',
+ position: 'right',
+ },
+ {
+ title: 'Renaming The Snapshot',
+ element: document.querySelectorAll('.action-component-text')[0],
+ intro:
+ 'A snapshot can be renamed to provided more clarity or distinguish specific snapshots.',
+ position: 'left',
+ },
+ ];
+ break;
+ case 'tree':
+ steps = [
+ {
+ title: 'Tree Tab',
+ element: '.display',
+ intro:
+ 'The tree tab can be used to view a text display of the state snapshots in a JSON format.',
+ position: 'top',
+ },
+ ];
+ break;
+ default:
+ steps = [
+ {
+ title: 'No Tutorial For This Tab',
+ intro:
+ 'A tutorial for this tab has not yet been created Please visit our official Github Repo for more information Reactime Github ',
+ position: 'top',
+ },
+ ];
+ break;
+ }
+
+ return (
+ <>
+ onChangeHandler(currentStepIndex)} // Callback called before changing the current step.
+ ref={(steps) => (this.steps = steps)} // ref allows access to intro.js API
+ />
+ startIntro()}>
+ Tutorial
+
+ >
+ );
+ }
+}
diff --git a/src/app/components/Chart.jsx b/src/app/components/Chart.jsx
deleted file mode 100644
index d7e6c2c94..000000000
--- a/src/app/components/Chart.jsx
+++ /dev/null
@@ -1,199 +0,0 @@
-/* eslint-disable eqeqeq */
-/* eslint-disable react/prop-types */
-/* eslint-disable no-mixed-operators */
-/* eslint-disable prefer-template */
-/* eslint-disable no-return-assign */
-/* eslint-disable prefer-arrow-callback */
-/* eslint-disable func-names */
-/* eslint-disable no-underscore-dangle */
-/* eslint-disable no-param-reassign */
-/* eslint-disable no-use-before-define */
-/* eslint-disable react/no-string-refs */
-import React, { Component } from 'react';
-// import PropTypes from 'prop-types';
-import * as d3 from 'd3';
-// import d3Tip from 'd3-tip';
-
-let root = {};
-class Chart extends Component {
- constructor(props) {
- super(props);
- this.chartRef = React.createRef();
- this.maked3Tree = this.maked3Tree.bind(this);
- this.removed3Tree = this.removed3Tree.bind(this);
- }
-
- componentDidMount() {
- const { hierarchy } = this.props;
- root = JSON.parse(JSON.stringify(hierarchy));
- this.maked3Tree();
- }
-
- componentDidUpdate() {
- const { hierarchy } = this.props;
- root = JSON.parse(JSON.stringify(hierarchy));
- this.maked3Tree();
- }
-
- removed3Tree() {
- const { current } = this.chartRef;
- while (current.hasChildNodes()) {
- current.removeChild(current.lastChild);
- }
- }
-
- maked3Tree() {
- this.removed3Tree();
- const margin = {
- top: 0,
- right: 60,
- bottom: 200,
- left: 120,
- };
- const width = 600 - margin.right - margin.left;
- const height = 700 - margin.top - margin.bottom;
-
- const chartContainer = d3.select(this.chartRef.current)
- .append('svg') // chartContainer is now pointing to svg
- .attr('width', width)
- .attr('height', height);
-
- const g = chartContainer.append('g')
- // this is changing where the graph is located physically
- .attr('transform', `translate(${width / 2 + 4}, ${height / 2 + 2})`);
-
- // if we consider the container for our radial node graph as a box encapsulating
- // half of this container width is essentially the radius of our radial node graph
- const radius = width / 2;
-
- // d3.hierarchy constructs a root node from the specified hierarchical data
- // (our object titled dataset), which must be an object representing the root node
- const hierarchy = d3.hierarchy(root);
-
- const tree = d3.tree()
- // this assigns width of tree to be 2pi
- .size([2 * Math.PI, radius / 1.3])
- .separation(function (a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
-
- const d3root = tree(hierarchy);
-
- g.selectAll('.link')
- // root.links() gets an array of all the links,
- // where each element is an object containing a
- // source property, which represents the link's source node,
- // and a target property, which represents the link's target node.
- .data(d3root.links())
- .enter()
- .append('path')
- .attr('class', 'link')
- .attr('d', d3.linkRadial()
- .angle(d => d.x)
- .radius(d => d.y));
-
- const node = g.selectAll('.node')
- // root.descendants gets an array of of all nodes
- .data(d3root.descendants())
- .enter()
- .append('g')
- // assigning class to the node based on whether node has children or not
- .attr('class', function (d) {
- return 'node' + (d.children ? ' node--internal' : ' node--leaf');
- })
- .attr('transform', function (d) {
- return 'translate(' + reinfeldTidierAlgo(d.x, d.y) + ')';
- });
-
- node.append('circle')
- .attr('r', 10)
- .on('mouseover', function (d) {
- d3.select(this)
- .transition(100)
- .duration(20)
- .attr('r', 20);
-
- tooltipDiv.transition()
- .duration(50)
- .style('opacity', 0.9);
-
- tooltipDiv.html(JSON.stringify(d.data.stateSnapshot.children[0].state), this)
- .style('left', (d3.event.pageX - 90) + 'px')
- .style('top', (d3.event.pageY - 65) + 'px');
- })
- // eslint-disable-next-line no-unused-vars
- .on('mouseout', function (d) {
- d3.select(this)
- .transition()
- .duration(300)
- .attr('r', 12);
-
- tooltipDiv.transition()
- .duration(400)
- .style('opacity', 0);
- });
- node
- .append('text')
- // adjusts the y coordinates for the node text
- .attr('dy', '-1.5em')
- .attr('x', function (d) {
- // this positions how far the text is from leaf nodes (ones without children)
- // negative number before the colon moves the text of rightside nodes,
- // positive number moves the text for the leftside nodes
- return d.x < Math.PI === !d.children ? -4 : 5;
- })
- .attr('text-anchor', function (d) { return d.x < Math.PI === !d.children ? 'start' : 'end'; })
- // this arranges the angle of the text
- .attr('transform', function (d) { return 'rotate(' + (d.x < Math.PI ? d.x - Math.PI / 2 : d.x + Math.PI / 2) * 1 / Math.PI + ')'; })
- .text(function (d) {
- return d.data.index;
- });
-
- // allows svg to be dragged around
- node.call(d3.drag()
- .on('start', dragstarted)
- .on('drag', dragged)
- .on('end', dragended));
-
- chartContainer.call(d3.zoom()
- .extent([[0, 0], [width, height]])
- .scaleExtent([1, 8])
- .on('zoom', zoomed));
-
- function dragstarted() {
- d3.select(this).raise();
- g.attr('cursor', 'grabbing');
- }
-
- function dragged(d) {
- d3.select(this).attr('dx', d.x = d3.event.x).attr('dy', d.y = d3.event.y);
- }
-
- function dragended() {
- g.attr('cursor', 'grab');
- }
-
- function zoomed() {
- g.attr('transform', d3.event.transform);
- }
-
- // define the div for the tooltip
- const tooltipDiv = d3.select('body').append('div')
- .attr('class', 'tooltip')
- .style('opacity', 0);
-
- // applying tooltip on mouseover and removes it when mouse cursor moves away
-
- function reinfeldTidierAlgo(x, y) {
- return [(y = +y) * Math.cos(x -= Math.PI / 2), y * Math.sin(x)];
- }
- }
-
- render() {
- return
;
- }
-}
-
-// Chart.propTypes = {
-// snapshot: PropTypes.arrayOf(PropTypes.object).isRequired,
-// };
-
-export default Chart;
diff --git a/src/app/components/Diff.jsx b/src/app/components/Diff.jsx
deleted file mode 100644
index 5b165bb90..000000000
--- a/src/app/components/Diff.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { diff, formatters } from 'jsondiffpatch';
-import ReactHtmlParser from 'react-html-parser';
-
-import { useStoreContext } from '../store';
-
-function Diff({ snapshot, show }) {
- const [mainState] = useStoreContext();
- const { currentTab, tabs } = mainState;
- const { snapshots, viewIndex, sliderIndex } = tabs[currentTab];
- let previous;
-
- // previous follows viewIndex or sliderIndex
- if (viewIndex !== -1) {
- previous = snapshots[viewIndex - 1];
- } else {
- previous = snapshots[sliderIndex - 1];
- }
-
- const delta = diff(previous, snapshot);
- // returns html in string
- const html = formatters.html.format(delta, previous);
- if (show) formatters.html.showUnchanged();
- else formatters.html.hideUnchanged();
-
- if (previous === undefined || delta === undefined) return No state change detected.
;
- return (
-
- {ReactHtmlParser(html)}
-
- );
-}
-
-Diff.propTypes = {
- snapshot: PropTypes.shape({
- state: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
- children: PropTypes.arrayOf(PropTypes.object),
- }).isRequired,
- show: PropTypes.bool.isRequired,
-};
-
-export default Diff;
diff --git a/src/app/components/DiffRoute.jsx b/src/app/components/DiffRoute.jsx
deleted file mode 100644
index ea01eb60e..000000000
--- a/src/app/components/DiffRoute.jsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {
- MemoryRouter as Router, Route, NavLink, Switch,
-} from 'react-router-dom';
-
-import Diff from './Diff';
-
-const DiffRoute = ({ snapshot }) => (
-
-
-
- Tree
-
-
- Raw
-
-
-
- } />
- } />
-
-
-);
-
-DiffRoute.propTypes = {
- snapshot: PropTypes.shape({
- state: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
- children: PropTypes.arrayOf(PropTypes.object),
- }).isRequired,
-};
-
-export default DiffRoute;
diff --git a/src/app/components/Dropdown.jsx b/src/app/components/Dropdown.jsx
deleted file mode 100644
index 6cf427d91..000000000
--- a/src/app/components/Dropdown.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React from 'react';
-import Select from 'react-select';
-import PropTypes from 'prop-types';
-
-const Dropdown = props => {
- const { speeds, setSpeed, selectedSpeed } = props;
- return (
-
- );
-};
-
-Dropdown.propTypes = {
- selectedSpeed: PropTypes.shape({ value: PropTypes.number, label: PropTypes.string }).isRequired,
- speeds: PropTypes.arrayOf(PropTypes.object).isRequired,
- setSpeed: PropTypes.func.isRequired,
-};
-
-export default Dropdown;
diff --git a/src/app/components/MainSlider.jsx b/src/app/components/MainSlider.jsx
deleted file mode 100644
index 2d4faa9bc..000000000
--- a/src/app/components/MainSlider.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-/* eslint-disable react/jsx-props-no-spreading */
-/* eslint-disable react/prop-types */
-
-import React from 'react';
-import Slider from 'rc-slider';
-import Tooltip from 'rc-tooltip';
-import PropTypes from 'prop-types';
-
-import { changeSlider, pause } from '../actions/actions';
-import { useStoreContext } from '../store';
-
-const { Handle } = Slider;
-
-const handle = props => {
- const {
- value, dragging, index, ...restProps
- } = props;
-
- return (
-
-
-
- );
-};
-
-function MainSlider({ snapshotsLength }) {
- const [{ tabs, currentTab }, dispatch] = useStoreContext();
- const { sliderIndex } = tabs[currentTab];
-
- return (
- {
- const newIndex = index === -1 ? 0 : index;
- dispatch(changeSlider(newIndex));
- dispatch(pause());
- }}
- handle={handle}
- />
- );
-}
-
-MainSlider.propTypes = {
- snapshotsLength: PropTypes.number.isRequired,
-};
-
-export default MainSlider;
diff --git a/src/app/components/StateRoute.jsx b/src/app/components/StateRoute.jsx
deleted file mode 100644
index ef58a54a5..000000000
--- a/src/app/components/StateRoute.jsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {
- MemoryRouter as Router, Route, NavLink, Switch,
-} from 'react-router-dom';
-
-import Chart from './Chart';
-import Tree from './Tree';
-
-// eslint-disable-next-line react/prop-types
-const StateRoute = ({ snapshot, hierarchy }) => (
-
-
-
- Tree
-
-
- Chart
-
-
-
- } />
- } />
-
-
-);
-
-StateRoute.propTypes = {
- snapshot: PropTypes.shape({
- state: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
- children: PropTypes.arrayOf(PropTypes.object),
- }).isRequired,
-};
-
-export default StateRoute;
diff --git a/src/app/components/StateRoute/AxMap/Ax.tsx b/src/app/components/StateRoute/AxMap/Ax.tsx
new file mode 100644
index 000000000..9da91cea8
--- /dev/null
+++ b/src/app/components/StateRoute/AxMap/Ax.tsx
@@ -0,0 +1,425 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { useDispatch } from 'react-redux';
+import { Group } from '@visx/group';
+import { hierarchy, Tree } from '@visx/hierarchy';
+import { LinearGradient } from '@visx/gradient';
+import LinkControls from './axLinkControls';
+import getLinkComponent from './getAxLinkComponents';
+import { useTooltip, useTooltipInPortal, defaultStyles } from '@visx/tooltip';
+import ToolTipDataDisplay from './ToolTipDataDisplay';
+import { ToolTipStyles } from '../../../FrontendTypes';
+import { localPoint } from '@visx/event';
+import { toggleExpanded, setCurrentTabInApp } from '../../../slices/mainSlice';
+
+const defaultMargin = {
+ top: 30,
+ left: 20,
+ right: 50,
+ bottom: 20,
+};
+
+const nodeCoords: object = {};
+let count: number = 0;
+let aspect: number = 1; // aspect resizes the component map container to accommodate large node trees on complex sites
+let nodeCoordTier = 0;
+let nodeOneLeft = 0;
+let nodeTwoLeft = 2;
+
+export type LinkTypesProps = {
+ width: number;
+ height: number;
+ margin?: { top: number; right: number; bottom: number; left: number };
+};
+
+export default function AxTree(props) {
+ const { currLocation, axSnapshots, width, height, setShowTree, setShowParagraph } = props;
+
+ let margin = defaultMargin;
+ let totalWidth = width;
+ let totalHeight = height;
+
+ if (axSnapshots[currLocation.index] === 'emptyAxSnap') return;
+
+ const toolTipTimeoutID = useRef(null); //useRef stores stateful data that’s not needed for rendering.
+ const {
+ tooltipData, // value/data that tooltip may need to render
+ tooltipLeft, // number used for tooltip positioning
+ tooltipTop, // number used for tooltip positioning
+ tooltipOpen, // boolean whether the tooltip state is open or closed
+ showTooltip, // function to set tooltip state
+ hideTooltip, // function to close a tooltip
+ } = useTooltip(); // returns an object with several properties that you can use to manage the tooltip state of your component
+
+ const {
+ containerRef, // Access to the container's bounding box. This will be empty on first render.
+ TooltipInPortal, // TooltipWithBounds in a Portal, outside of your component DOM tree
+ } = useTooltipInPortal({
+ // Visx hook
+ detectBounds: true, // use TooltipWithBounds
+ scroll: true, // when tooltip containers are scrolled, this will correctly update the Tooltip position
+ });
+
+ const tooltipStyles: ToolTipStyles = {
+ ...defaultStyles,
+ minWidth: 60,
+ maxWidth: 250,
+ maxHeight: '300px',
+ lineHeight: '18px',
+ pointerEvents: 'all !important',
+ margin: 0,
+ padding: 0,
+ borderRadius: '8px',
+ overflowY: 'auto',
+ overflowX: 'auto',
+ backgroundColor: 'transparent',
+ };
+
+ const [orientation, setOrientation] = useState('horizontal');
+ const [linkType, setLinkType] = useState('diagonal');
+ const [stepPercent, setStepPercent] = useState(0.0);
+
+ const innerWidth: number = totalWidth - margin.left - margin.right;
+ const innerHeight: number = totalHeight - margin.top - margin.bottom - 60;
+
+ let origin: { x: number; y: number };
+ let sizeWidth: number;
+ let sizeHeight: number;
+
+ origin = { x: 0, y: 0 };
+ if (orientation === 'vertical') {
+ sizeWidth = innerWidth;
+ sizeHeight = innerHeight;
+ } else {
+ sizeWidth = innerHeight;
+ sizeHeight = innerWidth;
+ }
+
+ const LinkComponent = getLinkComponent({ linkType, orientation });
+
+ const currAxSnapshot = JSON.parse(JSON.stringify(axSnapshots[currLocation.index]));
+
+ // root node of currAxSnapshot
+ const rootAxNode = JSON.parse(JSON.stringify(currAxSnapshot[0]));
+
+ // array that holds each ax tree node with children property
+ const nodeAxArr = [];
+
+ // populates ax nodes with children property; visx recognizes 'children' in order to properly render a nested tree
+ const organizeAxTree = (currNode, currAxSnapshot) => {
+ // checks if current ax node has children nodes through childId
+ if (currNode.childIds && currNode.childIds.length > 0) {
+ // if yes, add children property to current ax node
+ currNode.children = [];
+ for (let j = 0; j < currAxSnapshot.length; j++) {
+ // locate ax node associated with childId
+ for (const childEle of currNode.childIds) {
+ if (childEle === currAxSnapshot[j].nodeId) {
+ // store ax node in children array
+ currNode.children.push(currAxSnapshot[j]);
+ // recursively call organizeAxTree with child ax node passed in to check for further nested children nodes
+ organizeAxTree(currAxSnapshot[j], currAxSnapshot);
+ }
+ }
+ }
+ }
+ };
+
+ organizeAxTree(rootAxNode, currAxSnapshot);
+
+ // stores each individual ax node with populated children property in array
+ const populateNodeAxArr = (currNode) => {
+ nodeAxArr.splice(0, nodeAxArr.length);
+ nodeAxArr.push(currNode);
+ for (let i = 0; i < nodeAxArr.length; i += 1) {
+ // iterate through the nodeAxArr that contains the root ax node
+ const cur = nodeAxArr[i];
+ if (cur.children && cur.children.length > 0) {
+ // if the current ax node evaluated has non-zero children...
+ for (const child of cur.children) {
+ // iterate through each child ax node in the children array
+ nodeAxArr.push(child); // add the child to the nodeAxArr
+ }
+ }
+ }
+ };
+
+ populateNodeAxArr(rootAxNode);
+
+ // Conditionally render ax legend component (RTK)
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(setCurrentTabInApp('Axtree')); // dispatch sent at initial page load allowing changing
+ }, [dispatch]);
+
+ return totalWidth < 10 ? null : (
+
+
+
+
+
+
+
+
+ {
+ hideTooltip();
+ }}
+ />
+
+ (d.isExpanded ? null : d.children))}
+ size={[sizeWidth / aspect, sizeHeight / aspect]}
+ separation={(a, b) => (a.parent === b.parent ? 0.5 : 0.5) / a.depth}
+ >
+ {(tree) => (
+
+ {tree.links().map((link, i) => (
+
+ ))}
+ {tree.descendants().map((node, key) => {
+ const widthFunc = (name): number => {
+ // returns a number that is related to the length of the name. Used for determining the node width.
+ const nodeLength = name.length;
+ if (nodeLength <= 5) return nodeLength + 60;
+ if (nodeLength <= 10) return nodeLength + 130;
+ return nodeLength + 160;
+ };
+
+ const width: number = widthFunc(node.data.name.value); // the width is determined by the length of the node.name
+ const height: number = 25;
+ let top: number;
+ let left: number;
+
+ if (orientation === 'vertical') {
+ top = node.y;
+ left = node.x;
+ } else {
+ top = node.x;
+ left = node.y;
+ }
+
+ //setup a nodeCoords Object that will have keys of unique y coordinates and value arrays of all the left and right x coordinates of the nodes on that level
+ count < nodeAxArr.length
+ ? !nodeCoords[top]
+ ? (nodeCoords[top] = [left - width / 2, left + width / 2])
+ : nodeCoords[top].push(left - width / 2, left + width / 2)
+ : null;
+ count++;
+
+ if (count === nodeAxArr.length) {
+ //check if there is still a tier of the node tree to collision check
+ while (Object.values(nodeCoords)[nodeCoordTier]) {
+ //check if there are atleast two nodes on the current tier
+ if (
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft] &&
+ Object.values(nodeCoords)[nodeCoordTier][nodeTwoLeft]
+ ) {
+ //check if the left side of the righthand node is to the right of the right side of the lefthand node (i.e. collision)
+ if (
+ Object.values(nodeCoords)[nodeCoordTier][nodeTwoLeft] <
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft + 1]
+ ) {
+ //check if the visible percentage of the left hand node is less than the current lowest (this will be used to resize and rescale the map)
+ if (
+ Math.abs(
+ Object.values(nodeCoords)[nodeCoordTier][nodeTwoLeft] -
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft],
+ ) /
+ Math.abs(
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft + 1] -
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft],
+ ) <
+ aspect
+ ) {
+ //assign a new lowest percentage if one is found
+ aspect =
+ Math.abs(
+ Object.values(nodeCoords)[nodeCoordTier][nodeTwoLeft] -
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft],
+ ) /
+ Math.abs(
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft + 1] -
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft],
+ );
+ }
+ //move the node pointers down the list after checking the current overlap percentage
+ else {
+ nodeOneLeft += 2;
+ nodeTwoLeft += 2;
+ }
+ }
+ //move the node pointers if no collision is found
+ else {
+ nodeOneLeft += 2;
+ nodeTwoLeft += 2;
+ }
+ }
+ //move to the next tier of the node tree if done checking the current one
+ else {
+ nodeOneLeft = 0;
+ nodeTwoLeft = 2;
+ nodeCoordTier++;
+ }
+ }
+ } else {
+ aspect = Math.max(aspect, 0.5);
+ }
+ const handleMouseAndClickOver = (event): void => {
+ const coords = localPoint(event.target.ownerSVGElement, event);
+ const tooltipObj = { ...node.data };
+
+ showTooltip({
+ tooltipLeft: coords.x,
+ tooltipTop: coords.y,
+ tooltipData: tooltipObj,
+ // this is where the data for state and render time is displayed
+ // but does not show props functions and etc
+ });
+ };
+
+ return (
+
+ {node.depth === 0 && (
+ {
+ dispatch(toggleExpanded(node.data));
+ hideTooltip();
+ }}
+ />
+ )}
+ {node.depth !== 0 && (
+ {
+ dispatch(toggleExpanded(node.data));
+ hideTooltip();
+ }}
+ // Mouse Enter Rect (Component Node) -----------------------------------------------------------------------
+ /** This onMouseEnter event fires when the mouse first moves/hovers over a component node.
+ * The supplied event listener callback produces a Tooltip element for the current node. */
+
+ onMouseEnter={(event) => {
+ /** This 'if' statement block checks to see if you've just left another component node
+ * by seeing if there's a current setTimeout waiting to close that component node's
+ * tooltip (see onMouseLeave immediately below). If so it clears the tooltip generated
+ * from that component node so a new tooltip for the node you've just entered can render. */
+ if (toolTipTimeoutID.current !== null) {
+ clearTimeout(toolTipTimeoutID.current);
+ hideTooltip();
+ }
+ // Removes the previous timeoutID to avoid errors
+ toolTipTimeoutID.current = null;
+ //This generates a tooltip for the component node the mouse has entered.
+ handleMouseAndClickOver(event);
+ }}
+ // Mouse Leave Rect (Component Node) --------------------------------------------------------------------------
+ /** This onMouseLeave event fires when the mouse leaves a component node.
+ * The supplied event listener callback generates a setTimeout call which gives the
+ * mouse a certain amount of time between leaving the current component node and
+ * closing the tooltip for that node.
+ * If the mouse enters the tooltip before the timeout delay has passed, the
+ * setTimeout event will be canceled. */
+ onMouseLeave={() => {
+ // Store setTimeout ID so timeout can be cleared if necessary
+ toolTipTimeoutID.current = setTimeout(() => {
+ // hideTooltip unmounts the tooltip
+ hideTooltip();
+ toolTipTimeoutID.current = null;
+ }, 300);
+ }}
+ />
+ )}
+
+ {node.data.name.value}
+
+
+ );
+ })}
+
+ )}
+
+
+
+ {tooltipOpen && tooltipData && (
+
{
+ clearTimeout(toolTipTimeoutID.current);
+ toolTipTimeoutID.current = null;
+ }}
+ //------------- Mouse Leave TooltipInPortal -----------------------------------------------------------------
+ /** When the mouse leaves the tooltip, the tooltip unmounts */
+ onMouseLeave={() => {
+ hideTooltip();
+ }}
+ >
+
+
+
{tooltipData['name'].value}
+
+
+ {/* Ax Node Info below names the tooltip title because of how its passed to the ToolTipDataDisplay container*/}
+
+
+
+
+ )}
+
+ );
+}
diff --git a/src/app/components/StateRoute/AxMap/AxContainer.tsx b/src/app/components/StateRoute/AxMap/AxContainer.tsx
new file mode 100644
index 000000000..1edbba476
--- /dev/null
+++ b/src/app/components/StateRoute/AxMap/AxContainer.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import { ParentSize } from '@visx/responsive';
+import AxTree from './Ax';
+import type { AxContainer } from '../../../FrontendTypes';
+
+// Container to hold AxTree. AxTree is conditionally rendered based on the state of the setter function "showTree" in StateRoute
+
+const AxContainer = (props: AxContainer) => {
+ const {
+ axSnapshots, // from 'tabs[currentTab]' object in 'MainContainer'
+ snapshot, // from 'tabs[currentTab]' object in 'MainContainer'
+ snapshots, // from 'tabs[currentTab].snapshotDisplay' object in 'MainContainer'
+ currLocation, // from 'tabs[currentTab]' object in 'MainContainer'
+ setShowTree,
+ setShowParagraph,
+ } = props;
+
+ return (
+
+
+ {({ width, height }) => {
+ // eslint-disable-next-line react/prop-types
+ const maxHeight: number = 1200;
+ const h = Math.min(height, maxHeight);
+ return (
+
+ );
+ }}
+
+
+ );
+};
+
+export default AxContainer;
diff --git a/src/app/components/StateRoute/AxMap/ToolTipDataDisplay.tsx b/src/app/components/StateRoute/AxMap/ToolTipDataDisplay.tsx
new file mode 100644
index 000000000..eb942dbd3
--- /dev/null
+++ b/src/app/components/StateRoute/AxMap/ToolTipDataDisplay.tsx
@@ -0,0 +1,81 @@
+import React from 'react';
+import { JSONTree } from 'react-json-tree';
+
+/*
+ Code that show's the tooltip of our JSON tree
+*/
+
+const colors = {
+ scheme: 'paraiso',
+ author: 'jan t. sott',
+ base00: '#2f1e2e',
+ base01: '#41323f',
+ base02: '#4f424c',
+ base03: '#776e71',
+ base04: '#8d8687',
+ base05: '#a39e9b',
+ base06: '#b9b6b0',
+ base07: '#e7e9db',
+ base08: '#ef6155',
+ base09: '#824508',
+ base0A: '#fec418',
+ base0B: '#48b685',
+ base0C: '#5bc4bf',
+ base0D: '#06b6ef',
+ base0E: '#815ba4',
+ base0F: '#e96ba8',
+};
+
+const ToolTipDataDisplay = ({ containerName, dataObj }) => {
+ const printableObject = {}; // The key:value properties of printableObject will be rendered in the JSON Tree
+
+ if (!dataObj) {
+ // If state is null rather than an object, print "State: null" in tooltip
+ printableObject[containerName] = dataObj;
+ } else {
+ /*
+ Props often contain circular references.
+ Messages from the backend must be sent as JSON objects (strings).
+ JSON objects can't contain circular ref's, so the backend filters out problematic values by stringifying the values of object properties and ignoring any values that fail the conversion due to a circular ref. The following logic converts these values back to JS so they display clearly and are collapsible.
+ */
+ const data = {};
+ //ignored false vs true
+ //ignored reasons here
+ //&& key = name? / order?
+ for (const key in dataObj) {
+ if (key === 'properties' || key === 'ignored' || key === 'ignoredReasons') {
+ // loop through properties, adding them to the data object
+
+ if (typeof dataObj[key] === 'string') {
+ //if 'key' is ignored, put the ignored key and its value on the data object
+ //if ignoredReasons has length it should loop through adding the reasons names to the data object
+ //actually might only need to give it the properties and ignored and ignored reasons and it'll take care of the rest
+ try {
+ data[key] = JSON.parse(dataObj[key]);
+ } catch {
+ data[key] = dataObj[key];
+ }
+ } else {
+ data[key] = dataObj[key];
+ }
+ }
+ }
+ /*
+ Adds container name (State, Props, future different names for hooks) at top of object so everything nested in it will collapse when you click on it.
+ */
+ printableObject[containerName] = data;
+ }
+
+ return (
+
+ ({ className: `tooltipData-JSONTree` }) }} // theme set to a base16 theme that has been extended to include "className: 'json-tree'"
+ shouldExpandNodeInitially={() => true} // determines if node should be expanded when it first renders (root is expanded by default)
+ hideRoot={true} // hides the root node
+ />
+
+ );
+};
+
+export default ToolTipDataDisplay;
diff --git a/src/app/components/StateRoute/AxMap/axLinkControls.tsx b/src/app/components/StateRoute/AxMap/axLinkControls.tsx
new file mode 100644
index 000000000..650e8e55e
--- /dev/null
+++ b/src/app/components/StateRoute/AxMap/axLinkControls.tsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import { useDispatch } from 'react-redux';
+import { toggleAxTree, setCurrentTabInApp } from '../../../slices/mainSlice';
+
+const AxLinkControls = ({
+ orientation,
+ linkType,
+ stepPercent,
+ setOrientation,
+ setLinkType,
+ setStepPercent,
+ setShowTree,
+ setShowParagraph,
+}) => {
+ const dispatch = useDispatch();
+ const disableAxTree = () => {
+ dispatch(toggleAxTree('toggleAxRecord'));
+ setShowTree(false);
+ setShowParagraph(true);
+ };
+
+ return (
+
+
+
+ Disable
+
+
+
+ Orientation:
+ e.stopPropagation()}
+ onChange={(e) => setOrientation(e.target.value)}
+ value={orientation}
+ >
+ Vertical
+ Horizontal
+
+
+
+
+ Link:
+ e.stopPropagation()}
+ onChange={(e) => setLinkType(e.target.value)}
+ value={linkType}
+ >
+ Diagonal
+ Step
+ Curve
+ Line
+
+
+
+ {linkType === 'step' && (
+
+ Step:
+ e.stopPropagation()}
+ type='range'
+ min={0}
+ max={1}
+ step={0.1}
+ onChange={(e) => setStepPercent(Number(e.target.value))}
+ value={stepPercent}
+ disabled={linkType !== 'step'}
+ className='control-range'
+ />
+
+ )}
+
+ );
+};
+
+export default AxLinkControls;
diff --git a/src/app/components/StateRoute/AxMap/getAxLinkComponents.tsx b/src/app/components/StateRoute/AxMap/getAxLinkComponents.tsx
new file mode 100644
index 000000000..26ce0426f
--- /dev/null
+++ b/src/app/components/StateRoute/AxMap/getAxLinkComponents.tsx
@@ -0,0 +1,46 @@
+import { ComponentType } from 'react';
+import {
+ LinkHorizontal,
+ LinkVertical,
+ LinkRadial,
+ LinkHorizontalStep,
+ LinkVerticalStep,
+ LinkRadialStep,
+ LinkHorizontalCurve,
+ LinkVerticalCurve,
+ LinkRadialCurve,
+ LinkHorizontalLine,
+ LinkVerticalLine,
+ LinkRadialLine,
+} from '@visx/shape';
+
+export default function getLinkComponent({
+ linkType,
+ orientation,
+}: {
+ linkType: string;
+ orientation: string;
+}) {
+ let LinkComponent;
+
+ if (orientation === 'vertical') {
+ if (linkType === 'step') {
+ LinkComponent = LinkVerticalStep;
+ } else if (linkType === 'curve') {
+ LinkComponent = LinkVerticalCurve;
+ } else if (linkType === 'line') {
+ LinkComponent = LinkVerticalLine;
+ } else {
+ LinkComponent = LinkVertical;
+ }
+ } else if (linkType === 'step') {
+ LinkComponent = LinkHorizontalStep;
+ } else if (linkType === 'curve') {
+ LinkComponent = LinkHorizontalCurve;
+ } else if (linkType === 'line') {
+ LinkComponent = LinkHorizontalLine;
+ } else {
+ LinkComponent = LinkHorizontal;
+ }
+ return LinkComponent;
+}
diff --git a/src/app/components/StateRoute/ComponentMap/ComponentMap.tsx b/src/app/components/StateRoute/ComponentMap/ComponentMap.tsx
new file mode 100644
index 000000000..140f04d35
--- /dev/null
+++ b/src/app/components/StateRoute/ComponentMap/ComponentMap.tsx
@@ -0,0 +1,584 @@
+/* eslint-disable react/no-array-index-key */
+/* eslint-disable react/prop-types */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable no-nested-ternary */
+/* eslint-disable no-unused-expressions */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable no-restricted-syntax */
+/* eslint-disable guard-for-in */
+// @ts-nocheck
+
+import React, { useState, useEffect, useRef } from 'react';
+import { Group } from '@visx/group';
+import { hierarchy, Tree } from '@visx/hierarchy';
+import { LinearGradient } from '@visx/gradient';
+import { pointRadial } from 'd3-shape';
+import { localPoint } from '@visx/event';
+import { useTooltip, useTooltipInPortal, defaultStyles } from '@visx/tooltip';
+import LinkControls from './LinkControls';
+import getLinkComponent from './getLinkComponent';
+import ToolTipDataDisplay from './ToolTipDataDisplay';
+import { toggleExpanded, setCurrentTabInApp } from '../../../slices/mainSlice';
+import { useDispatch } from 'react-redux';
+import { LinkTypesProps, DefaultMargin, ToolTipStyles } from '../../../FrontendTypes';
+
+let stroke = '';
+
+const lightWeight = '#94a3b8'; // Lightest gray for minimal props
+const mediumWeight = '#64748b'; // Medium gray for light prop load
+const heavyWeight = '#556579';
+const veryHeavy = '#475569'; // Darker gray for medium load
+
+const defaultMargin: DefaultMargin = {
+ top: 30,
+ left: 20,
+ right: 50,
+ bottom: 20,
+};
+
+const nodeCoords: object = {};
+let count: number = 0;
+let aspect: number = 1;
+let nodeCoordTier = 0;
+let nodeOneLeft = 0;
+let nodeTwoLeft = 2;
+
+export default function ComponentMap({
+ // imported props to be used to display the dendrogram
+ width: totalWidth,
+ height: totalHeight,
+ margin = defaultMargin,
+ currentSnapshot, // from 'tabs[currentTab].stateSnapshot object in 'MainContainer'
+}: LinkTypesProps): JSX.Element {
+ const [orientation, setOrientation] = useState('vertical'); // We create a local state "orientation" and set it to a string 'vertical'.
+ const [linkType, setLinkType] = useState('step'); // We create a local state "linkType" and set it to a string 'step'.
+ const [stepPercent, setStepPercent] = useState(0.0); // We create a local state "stepPercent" and set it to a number '0.0'. This will be used to scale the Map component's link: Step to 0%
+ const [selectedNode, setSelectedNode] = useState('root'); // We create a local state "selectedNode" and set it to a string 'root'.
+ const [forceUpdate, setForceUpdate] = useState(false);
+
+ const dispatch = useDispatch();
+
+ const toolTipTimeoutID = useRef(null); //useRef stores stateful data that’s not needed for rendering.
+
+ useEffect(() => {
+ dispatch(setCurrentTabInApp('map')); // dispatch sent at initial page load allowing changing "immer's" draft.currentTabInApp to 'map' to facilitate render.
+ }, [dispatch]);
+
+ // force app to re-render to accurately calculate aspect ratio upon initial load
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setForceUpdate((prev) => !prev);
+ }, 100);
+ return () => clearTimeout(timer);
+ }, []);
+
+ // setting the margins for the Map to render in the tab window.
+ const innerWidth: number = totalWidth - margin.left - margin.right;
+ const innerHeight: number = totalHeight - margin.top - margin.bottom - 60;
+
+ let origin: { x: number; y: number };
+ let sizeWidth: number;
+ let sizeHeight: number;
+
+ /*
+ We begin setting the starting position for the root node on the maps display.
+ The default view sets the root nodes location either in the left middle *or top middle of the browser window relative to the size of the browser.
+ */
+
+ origin = { x: 0, y: 0 };
+ if (orientation === 'vertical') {
+ sizeWidth = innerWidth;
+ sizeHeight = innerHeight;
+ } else {
+ // if the orientation isn't vertical, swap the width and the height
+ sizeWidth = innerHeight;
+ sizeHeight = innerWidth;
+ }
+ //}
+ const {
+ tooltipData, // value/data that tooltip may need to render
+ tooltipLeft, // number used for tooltip positioning
+ tooltipTop, // number used for tooltip positioning
+ tooltipOpen, // boolean whether the tooltip state is open or closed
+ showTooltip, // function to set tooltip state
+ hideTooltip, // function to close a tooltip
+ } = useTooltip(); // returns an object with several properties that you can use to manage the tooltip state of your component
+
+ const {
+ containerRef, // Access to the container's bounding box. This will be empty on first render.
+ TooltipInPortal, // TooltipWithBounds in a Portal, outside of your component DOM tree
+ } = useTooltipInPortal({
+ // Visx hook
+ detectBounds: true, // use TooltipWithBounds
+ scroll: true, // when tooltip containers are scrolled, this will correctly update the Tooltip position
+ });
+
+ const tooltipStyles: ToolTipStyles = {
+ ...defaultStyles,
+ minWidth: 60,
+ maxWidth: 250,
+ maxHeight: '300px',
+ lineHeight: '18px',
+ pointerEvents: 'all !important',
+ margin: 0,
+ padding: 0,
+ borderRadius: '8px',
+ overflowY: 'auto',
+ backgroundColor: 'transparent',
+ };
+
+ const nodeList: [] = []; // create a nodeList array to store our nodes as a flat array
+
+ const collectNodes: void = (node) => {
+ // function that takes in a node (snapshot) as it's argument and modifies 'nodeList' so that the node and it's children are all within the flattened 'nodeList'.
+ nodeList.splice(0, nodeList.length); // deletes all the nodes in nodelist
+ nodeList.push(node); // pushes the snapshot into nodeList
+ for (let i = 0; i < nodeList.length; i += 1) {
+ // iterate through the nodeList that contains our snapshot
+ const cur = nodeList[i];
+ if (cur.children && cur.children.length > 0) {
+ // if the currently itereated snapshot has non-zero children...
+ for (const child of cur.children) {
+ // iterate through each child in the children array
+ nodeList.push(child); // add the child to the nodeList
+ }
+ }
+ }
+ };
+
+ // check if any data should be displayed in tool tip display
+ const hasDisplayableData = (nodeData) => {
+ // Check if the node has props
+ const hasProps =
+ nodeData.componentData?.props && Object.keys(nodeData.componentData.props).length > 0;
+
+ // Check if the node has state
+ const hasState =
+ (nodeData.componentData?.state && Object.keys(nodeData.componentData.state).length > 0) ||
+ (nodeData.componentData?.hooksState &&
+ Object.keys(nodeData.componentData.hooksState).length > 0);
+
+ // Check if the node has reducer states
+ const hasReducers =
+ nodeData.componentData?.reducerStates && nodeData.componentData.reducerStates.length > 0;
+
+ return hasProps || hasState || hasReducers;
+ };
+
+ const shouldIncludeNode = (node) => {
+ // Return false if node has any context properties
+ if (node?.componentData?.context && Object.keys(node.componentData.context).length > 0) {
+ return false;
+ }
+ // Return false if node name ends with 'Provider'
+ if (node?.name && node.name.endsWith('Provider')) {
+ return false;
+ }
+ return true;
+ };
+
+ const processTreeData = (node) => {
+ if (!node) return null;
+
+ // Create a new node
+ const newNode = { ...node };
+
+ if (node.children) {
+ // Process all children first
+ const processedChildren = node.children
+ .map((child) => processTreeData(child))
+ .filter(Boolean); // Remove null results
+
+ // For each child that shouldn't be included, replace it with its children
+ newNode.children = processedChildren.reduce((acc, child) => {
+ if (shouldIncludeNode(child)) {
+ // If child should be included, add it directly
+ acc.push(child);
+ } else {
+ // If child should be filtered out, add its children instead
+ if (child.children) {
+ acc.push(...child.children);
+ }
+ }
+ return acc;
+ }, []);
+ }
+
+ return newNode;
+ };
+ // filter out Conext Providers
+ let filtered = processTreeData(currentSnapshot);
+ collectNodes(filtered);
+
+ // @ts
+ // find the node that has been selected and use it as the root
+ let startNode = null;
+ let rootNode;
+
+ const findSelectedNode = () => {
+ // iterates through each node of nodeList and sets the rootNode and startNode to a node with the name root
+ for (const node of nodeList) {
+ if (node.name === 'root') rootNode = node;
+ if (node.name === selectedNode) startNode = node; // selectedNode label initialized as 'root'
+ }
+ if (startNode === null) startNode = rootNode;
+ };
+
+ findSelectedNode(); // locates the rootNode
+
+ // controls for the map
+ const LinkComponent: React.ComponentType = getLinkComponent({
+ linkType,
+ orientation,
+ });
+ return totalWidth < 10 ? null : (
+
+
+
+
+
+
+ {
+ hideTooltip();
+ }}
+ width={sizeWidth / aspect}
+ height={sizeHeight / aspect + 0}
+ rx={14}
+ />
+
+ (d.isExpanded ? d.children : null))}
+ size={[sizeWidth / aspect, sizeHeight / aspect]}
+ separation={(a, b) => (a.parent === b.parent ? 0.5 : 0.5) / a.depth}
+ >
+ {(tree) => (
+
+ {tree.links().map((link, i) => {
+ const linkName = link.source.data.name;
+ const propsObj = link.source.data.componentData.props;
+ const childPropsObj = link.target.data.componentData.props;
+ let propsLength;
+ let childPropsLength;
+
+ if (propsObj) {
+ propsLength = Object.keys(propsObj).length;
+ }
+ if (childPropsObj) {
+ childPropsLength = Object.keys(childPropsObj).length;
+ }
+ // go to https://en.wikipedia.org/wiki/Logistic_function
+ // for an explanation of Logistic functions and parameters used
+ const yshift = -3;
+ const x0 = 5;
+ const L = 25;
+ const k = 0.4;
+ const strokeWidthIndex =
+ yshift + L / (1 + Math.exp(-k * (childPropsLength - x0)));
+
+ if (strokeWidthIndex <= 1) {
+ stroke = '#808080';
+ } else {
+ if (childPropsLength <= 1) {
+ stroke = lightWeight;
+ } else if (childPropsLength <= 2) {
+ stroke = mediumWeight;
+ } else if (childPropsLength <= 3) {
+ stroke = heavyWeight;
+ } else {
+ stroke = veryHeavy;
+ }
+ }
+
+ return (
+
+ );
+ })}
+
+ {tree.descendants().map((node, key) => {
+ const calculateNodeWidth = (text: string): number => {
+ const nodeLength = text.length;
+ if (nodeLength <= 5) return nodeLength + 50;
+ if (nodeLength <= 10) return nodeLength + 120;
+ return nodeLength + 140;
+ };
+
+ // Find the maximum width for any node
+ const findMaxNodeWidth = (nodeData: any): number => {
+ // If no children, return current node width
+ if (!nodeData.children) {
+ return calculateNodeWidth(nodeData.name);
+ }
+
+ // Get width of current node
+ const currentWidth = calculateNodeWidth(nodeData.name);
+
+ // Get max width from children
+ const childrenWidths = nodeData.children.map((child) =>
+ findMaxNodeWidth(child),
+ );
+
+ // Return the maximum width found
+ return Math.max(currentWidth, ...childrenWidths);
+ };
+
+ // Truncate text for nodes that exceed a certain length
+ const truncateText = (text: string, width: number, maxWidth: number): string => {
+ // Calculate approximate text width
+ const estimatedTextWidth = text.length * 8;
+
+ // If this node's width is close to the max width (within 10%), truncate it
+ if (width >= maxWidth * 0.9) {
+ const maxChars = Math.floor((width - 30) / 8); // -30 for padding + ellipsis
+ return `${text.slice(0, maxChars)}...`;
+ }
+
+ return text;
+ };
+
+ const getNodeDimensions = (
+ name: string,
+ rootNode: any,
+ ): { width: number; displayText: string } => {
+ const width = calculateNodeWidth(name);
+ const maxWidth = findMaxNodeWidth(rootNode);
+ const displayText = truncateText(name, width, maxWidth);
+
+ return { width, displayText };
+ };
+
+ // Usage in your render function:
+ const { width, displayText } = getNodeDimensions(node.data.name, startNode);
+
+ const height: number = 35;
+ let top: number;
+ let left: number;
+
+ if (orientation === 'vertical') {
+ top = node.y;
+ left = node.x;
+ } else {
+ top = node.x;
+ left = node.y;
+ }
+ //setup a nodeCoords Object that will have keys of unique y coordinates and value arrays of all the left and right x coordinates of the nodes on that level
+ count < nodeList.length
+ ? !nodeCoords[top]
+ ? (nodeCoords[top] = [left - width / 2, left + width / 2])
+ : nodeCoords[top].push(left - width / 2, left + width / 2)
+ : null;
+ count++;
+
+ //check if the node coordinate object has been constructed
+ if (count === nodeList.length) {
+ //check if there is still a tier of the node tree to collision check
+ while (Object.values(nodeCoords)[nodeCoordTier]) {
+ //check if there are atleast two nodes on the current tier
+ if (
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft] &&
+ Object.values(nodeCoords)[nodeCoordTier][nodeTwoLeft]
+ ) {
+ //check if the left side of the righthand node is to the right of the right side of the lefthand node (i.e. collision)
+ if (
+ Object.values(nodeCoords)[nodeCoordTier][nodeTwoLeft] <
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft + 1]
+ ) {
+ //check if the visible percentage of the left hand node is less than the current lowest (this will be used to resize and rescale the map)
+ if (
+ Math.abs(
+ Object.values(nodeCoords)[nodeCoordTier][nodeTwoLeft] -
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft],
+ ) /
+ Math.abs(
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft + 1] -
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft],
+ ) <
+ aspect
+ ) {
+ //assign a new lowest percentage if one is found
+ aspect =
+ Math.abs(
+ Object.values(nodeCoords)[nodeCoordTier][nodeTwoLeft] -
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft],
+ ) /
+ Math.abs(
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft + 1] -
+ Object.values(nodeCoords)[nodeCoordTier][nodeOneLeft],
+ );
+ }
+ //move the node pointers down the list after checking the current overlap percentage
+ else {
+ nodeOneLeft += 2;
+ nodeTwoLeft += 2;
+ }
+ }
+ //move the node pointers if no collision is found
+ else {
+ nodeOneLeft += 2;
+ nodeTwoLeft += 2;
+ }
+ }
+ //move to the next tier of the node tree if done checking the current one
+ else {
+ nodeOneLeft = 0;
+ nodeTwoLeft = 2;
+ nodeCoordTier++;
+ }
+ }
+ } else {
+ aspect = Math.max(aspect, 1);
+ }
+
+ // mousing controls & Tooltip display logic
+ const handleMouseAndClickOver = (event, nodeData) => {
+ // Only show tooltip if the node has data to display
+ if (hasDisplayableData(nodeData)) {
+ const coords = localPoint(event.target.ownerSVGElement, event);
+ const tooltipObj = { ...nodeData };
+
+ showTooltip({
+ tooltipLeft: coords.x,
+ tooltipTop: coords.y,
+ tooltipData: tooltipObj,
+ });
+ }
+ };
+
+ return (
+
+ // Replace the root node rect rendering block with this:
+ {node.depth === 0 && (
+ {
+ dispatch(toggleExpanded(node.data));
+ hideTooltip();
+ }}
+ onMouseEnter={(event) => {
+ if (hasDisplayableData(node.data)) {
+ if (toolTipTimeoutID.current !== null) {
+ clearTimeout(toolTipTimeoutID.current);
+ hideTooltip();
+ }
+ toolTipTimeoutID.current = null;
+ handleMouseAndClickOver(event, node.data);
+ }
+ }}
+ onMouseLeave={() => {
+ if (hasDisplayableData(node.data)) {
+ toolTipTimeoutID.current = setTimeout(() => {
+ hideTooltip();
+ toolTipTimeoutID.current = null;
+ }, 300);
+ }
+ }}
+ />
+ )}
+ {/* This creates the rectangle boxes for each component
+ and sets it relative position to other parent nodes of the same level. */}
+ {node.depth !== 0 && (
+ {
+ dispatch(toggleExpanded(node.data));
+ hideTooltip();
+ }}
+ onMouseEnter={(event) => {
+ if (hasDisplayableData(node.data)) {
+ if (toolTipTimeoutID.current !== null) {
+ clearTimeout(toolTipTimeoutID.current);
+ hideTooltip();
+ }
+ toolTipTimeoutID.current = null;
+ handleMouseAndClickOver(event, node.data);
+ }
+ }}
+ onMouseLeave={() => {
+ if (hasDisplayableData(node.data)) {
+ toolTipTimeoutID.current = setTimeout(() => {
+ hideTooltip();
+ toolTipTimeoutID.current = null;
+ }, 300);
+ }
+ }}
+ />
+ )}
+ {/* Display text inside of each component node */}
+
+ {displayText}
+
+
+ );
+ })}
+
+ )}
+
+
+
+ {tooltipOpen && tooltipData && (
+
{
+ clearTimeout(toolTipTimeoutID.current);
+ toolTipTimeoutID.current = null;
+ }}
+ onMouseLeave={() => {
+ hideTooltip();
+ }}
+ >
+
+
+
{tooltipData.name}
+
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/src/app/components/StateRoute/ComponentMap/LinkControls.tsx b/src/app/components/StateRoute/ComponentMap/LinkControls.tsx
new file mode 100644
index 000000000..ceaa948fd
--- /dev/null
+++ b/src/app/components/StateRoute/ComponentMap/LinkControls.tsx
@@ -0,0 +1,133 @@
+import React from 'react';
+
+const LinkControls = ({
+ linkType,
+ stepPercent,
+ setOrientation,
+ setLinkType,
+ setStepPercent,
+ setSelectedNode,
+ snapShots,
+}) => {
+ const collectNodes = (node) => {
+ const nodeList = [];
+ nodeList.push(node);
+ for (let i = 0; i < nodeList.length; i += 1) {
+ const cur = nodeList[i];
+ if (cur.children?.length > 0) {
+ cur.children.forEach((child) => nodeList.push(child));
+ }
+ }
+ return nodeList;
+ };
+
+ const shouldIncludeNode = (node) => {
+ // Return false if node has any context properties
+ if (node?.componentData?.context && Object.keys(node.componentData.context).length > 0) {
+ return false;
+ }
+ // Return false if node name ends with 'Provider'
+ if (node?.name && node.name.endsWith('Provider')) {
+ return false;
+ }
+ return true;
+ };
+
+ const processTreeData = (node) => {
+ if (!node) return null;
+
+ // Create a new node
+ const newNode = { ...node };
+
+ if (node.children) {
+ // Process all children first
+ const processedChildren = node.children
+ .map((child) => processTreeData(child))
+ .filter(Boolean); // Remove null results
+
+ // For each child that shouldn't be included, replace it with its children
+ newNode.children = processedChildren.reduce((acc, child) => {
+ if (shouldIncludeNode(child)) {
+ // If child should be included, add it directly
+ acc.push(child);
+ } else {
+ // If child should be filtered out, add its children instead
+ if (child.children) {
+ acc.push(...child.children);
+ }
+ }
+ return acc;
+ }, []);
+ }
+
+ return newNode;
+ };
+ const filtered = processTreeData(snapShots);
+ const nodeList = collectNodes(filtered);
+
+ return (
+
+
+ Orientation:
+ e.stopPropagation()}
+ onChange={(e) => setOrientation(e.target.value)}
+ className='control-select'
+ >
+ Vertical
+ Horizontal
+
+
+
+
+ Link:
+ e.stopPropagation()}
+ onChange={(e) => setLinkType(e.target.value)}
+ className='control-select'
+ >
+ Step
+ Diagonal
+ Line
+
+
+
+
+ Select:
+ setSelectedNode(e.target.value)}
+ className='control-select'
+ >
+ {nodeList.map((node) =>
+ node.children.length > 0 ? (
+
+ {node.name}
+
+ ) : null,
+ )}
+
+
+
+ {linkType === 'step' && (
+
+ Step:
+ e.stopPropagation()}
+ type='range'
+ min={0}
+ max={1}
+ step={0.1}
+ onChange={(e) => setStepPercent(Number(e.target.value))}
+ value={stepPercent}
+ disabled={linkType !== 'step'}
+ className='control-range'
+ />
+
+ )}
+
+ );
+};
+
+export default LinkControls;
diff --git a/src/app/components/StateRoute/ComponentMap/ToolTipDataDisplay.tsx b/src/app/components/StateRoute/ComponentMap/ToolTipDataDisplay.tsx
new file mode 100644
index 000000000..29bcfbfc7
--- /dev/null
+++ b/src/app/components/StateRoute/ComponentMap/ToolTipDataDisplay.tsx
@@ -0,0 +1,112 @@
+import React from 'react';
+import { JSONTree } from 'react-json-tree';
+
+const ToolTipDataDisplay = ({ data }) => {
+ if (!data) return null;
+
+ const jsonTheme = {
+ scheme: 'custom',
+ base00: 'transparent',
+ base0B: '#14b8a6', // dark navy for strings
+ base0D: '#60a5fa', // Keys
+ base09: '#f59e0b', // Numbers
+ base0C: '#EF4444', // Null values
+ };
+
+ // Helper function to parse stringified JSON in object values
+ const parseStringifiedValues = (obj) => {
+ if (!obj || typeof obj !== 'object') return obj;
+
+ const parsed = { ...obj };
+ for (const key in parsed) {
+ if (typeof parsed[key] === 'string') {
+ try {
+ // Check if the string looks like JSON
+ if (parsed[key].startsWith('{') || parsed[key].startsWith('[')) {
+ const parsedValue = JSON.parse(parsed[key]);
+ parsed[key] = parsedValue;
+ }
+ } catch (e) {
+ // If parsing fails, keep original value
+ continue;
+ }
+ } else if (typeof parsed[key] === 'object') {
+ parsed[key] = parseStringifiedValues(parsed[key]);
+ }
+ }
+ return parsed;
+ };
+
+ const formatReducerData = (reducerStates) => {
+ // Check if reducerStates exists and is an object
+ if (!reducerStates || typeof reducerStates !== 'object') {
+ return {};
+ }
+
+ // Handle both array and object formats
+ const statesArray = Array.isArray(reducerStates) ? reducerStates : Object.values(reducerStates);
+
+ return statesArray.reduce((acc, reducer) => {
+ // Add additional type checking for reducer object
+ if (reducer && typeof reducer === 'object') {
+ acc[reducer.hookName || 'Reducer'] = reducer.state;
+ }
+ return acc;
+ }, {});
+ };
+
+ const renderSection = (title, content, isReducer = false) => {
+ if (
+ !content ||
+ (Array.isArray(content) && content.length === 0) ||
+ Object.keys(content).length === 0
+ ) {
+ return null;
+ }
+
+ // Parse any stringified JSON before displaying
+ const parsedContent = parseStringifiedValues(content);
+
+ if (isReducer && parsedContent) {
+ // Only try to format if we have valid content
+ const formattedData = formatReducerData(parsedContent);
+
+ // Check if we have any formatted data to display
+ if (Object.keys(formattedData).length === 0) {
+ return null;
+ }
+
+ return (
+
+ {Object.entries(formattedData).map(([hookName, state]) => (
+
+
{hookName}
+
+ true} />
+
+
+ ))}
+
+ );
+ }
+
+ return (
+
+
{title}
+
+ true} />
+
+
+ );
+ };
+
+ return (
+
+ {renderSection('Props', data.componentData?.props)}
+ {renderSection('State', data.componentData?.state || data.componentData?.hooksState)}
+ {renderSection(null, data.componentData?.reducerStates, true)}
+
+ );
+};
+
+export default ToolTipDataDisplay;
diff --git a/src/app/components/StateRoute/ComponentMap/getLinkComponent.ts b/src/app/components/StateRoute/ComponentMap/getLinkComponent.ts
new file mode 100644
index 000000000..600c884fa
--- /dev/null
+++ b/src/app/components/StateRoute/ComponentMap/getLinkComponent.ts
@@ -0,0 +1,49 @@
+import {
+ LinkHorizontal,
+ LinkVertical,
+ LinkRadial,
+ LinkHorizontalStep,
+ LinkVerticalStep,
+ LinkRadialStep,
+ LinkHorizontalCurve,
+ LinkVerticalCurve,
+ LinkRadialCurve,
+ LinkHorizontalLine,
+ LinkVerticalLine,
+ LinkRadialLine,
+} from '@visx/shape';
+import { LinkComponent } from '../../../FrontendTypes';
+
+/*
+ Changes the shape of the LinkComponent based on the linkType, and orientation
+*/
+
+export default function getLinkComponent({
+ linkType,
+ orientation,
+}: LinkComponent): React.ComponentType {
+ let LinkComponent: React.ComponentType;
+
+if (orientation === 'vertical') {
+ // if the orientation is vertical, linkType can be either step, curve, line, or a plain LinkVertical
+ if (linkType === 'step') {
+ LinkComponent = LinkVerticalStep;
+ } else if (linkType === 'curve') {
+ LinkComponent = LinkVerticalCurve;
+ } else if (linkType === 'line') {
+ LinkComponent = LinkVerticalLine;
+ } else {
+ LinkComponent = LinkVertical;
+ }
+ } else if (linkType === 'step') {
+ // if orientation and layout still haven't matched, linkType will determine our linkComponent type based on if linkType is step, curve, line, or a plain LinkHorizontal
+ LinkComponent = LinkHorizontalStep;
+ } else if (linkType === 'curve') {
+ LinkComponent = LinkHorizontalCurve;
+ } else if (linkType === 'line') {
+ LinkComponent = LinkHorizontalLine;
+ } else {
+ LinkComponent = LinkHorizontal;
+ }
+ return LinkComponent;
+}
diff --git a/src/app/components/StateRoute/History.tsx b/src/app/components/StateRoute/History.tsx
new file mode 100644
index 000000000..c83962d04
--- /dev/null
+++ b/src/app/components/StateRoute/History.tsx
@@ -0,0 +1,268 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+/* eslint-disable */
+// @ts-nocheck
+import React, { useEffect, useRef } from 'react';
+// formatting findDiff return data to show the changes with colors, aligns with actions.tsx
+import { diff, formatters } from 'jsondiffpatch';
+import * as d3 from 'd3';
+import { DefaultMargin } from '../../FrontendTypes';
+import { useDispatch } from 'react-redux';
+import { changeView, changeSlider, setCurrentTabInApp } from '../../slices/mainSlice';
+
+/*
+ Render's history page after history button has been selected. Allows user to traverse state history and relevant state branches.
+*/
+
+const defaultMargin: DefaultMargin = {
+ top: 60,
+ left: 30,
+ right: 55,
+ bottom: 70,
+};
+
+// Fixed node separation distances
+const FIXED_NODE_HEIGHT = 150; // Vertical distance between nodes
+const FIXED_NODE_WIDTH = 200; // Horizontal distance between nodes
+
+// main function exported to StateRoute
+// below we destructure the props
+function History(props: Record): JSX.Element {
+ const {
+ width: totalWidth, // from ParentSize provided in StateRoute
+ height: totalHeight, // from ParentSize provided in StateRoute
+ margin = defaultMargin, //default margin is used when margins aren't passed into props
+ hierarchy, // from 'tabs[currentTab]' object in 'MainContainer'
+ currLocation, // from 'tabs[currentTab]' object in 'MainContainer'
+ snapshots, // from 'tabs[currentTab].snapshotDisplay' object in 'MainContainer'
+ } = props;
+
+ //here we are adding useSelector and useDispatch for RTK state conversion
+ const dispatch = useDispatch();
+
+ const svgRef = React.useRef(null);
+ const root = JSON.parse(JSON.stringify(hierarchy));
+ const historyEndRef = useRef(null);
+
+ // setting the margins for the Map to render in the tab window.
+ const innerWidth: number = totalWidth - margin.left - margin.right;
+ const innerHeight: number = totalHeight - margin.top - margin.bottom - 60;
+
+ function findDiff(index) {
+ const statelessCleaning = (obj: {
+ name?: string;
+ componentData?: object;
+ state?: string | any;
+ stateSnaphot?: object;
+ children?: any[];
+ }) => {
+ if (!obj) return {}; // Add null check
+
+ const newObj = { ...obj };
+ if (newObj.name === 'nameless') delete newObj.name;
+ if (newObj.componentData) delete newObj.componentData;
+ if (newObj.state === 'stateless') delete newObj.state;
+ if (newObj.stateSnaphot) {
+ newObj.stateSnaphot = statelessCleaning(obj.stateSnaphot);
+ }
+ if (newObj.children) {
+ newObj.children = [];
+ // Add null check for children array
+ if (Array.isArray(obj.children) && obj.children.length > 0) {
+ obj.children.forEach((element: { state?: object | string; children?: [] }) => {
+ // Add null check for element
+ if (
+ element &&
+ ((element.state && element.state !== 'stateless') ||
+ (element.children && element.children.length > 0))
+ ) {
+ const clean = statelessCleaning(element);
+ newObj.children.push(clean);
+ }
+ });
+ }
+ }
+ return newObj;
+ };
+
+ function findStateChangeObj(delta, changedState = []) {
+ if (!delta) return changedState; // Add null check
+ if (!delta.children && !delta.state) return changedState;
+ if (delta.state && delta.state[0] !== 'stateless') {
+ changedState.push(delta.state);
+ }
+ if (!delta.children) return changedState;
+ Object.keys(delta.children).forEach((child) => {
+ if (delta.children[child]) {
+ // Add null check
+ changedState.push(...findStateChangeObj(delta.children[child]));
+ }
+ });
+ return changedState;
+ }
+
+ if (index === 0) return 'Initial State ';
+
+ // Add null checks for snapshots
+ if (!snapshots || !snapshots[index] || !snapshots[index - 1]) {
+ return 'No state changes';
+ }
+
+ try {
+ const delta = diff(
+ statelessCleaning(snapshots[index - 1]),
+ statelessCleaning(snapshots[index]),
+ );
+
+ if (!delta) return 'No State Changes ';
+
+ const changedState = findStateChangeObj(delta);
+ return changedState.length > 0 ? formatters.html.format(changedState[0]) : 'No state changes';
+ } catch (error) {
+ console.error('Error in findDiff:', error);
+ return 'Error comparing states';
+ }
+ }
+
+ /**
+ * @method makeD3Tree :Creates a new D3 Tree
+ */
+
+ const makeD3Tree = () => {
+ const svg = d3.select(svgRef.current);
+ svg.selectAll('*').remove();
+
+ // Create tree layout with fixed node size
+ const treeLayout = d3
+ .tree()
+ .nodeSize([FIXED_NODE_WIDTH, FIXED_NODE_HEIGHT])
+ .separation((a, b) => {
+ // Increase separation between unrelated subtrees
+ return a.parent === b.parent ? 1.2 : 2;
+ });
+
+ // Create hierarchy and compute initial layout
+ const d3root = d3.hierarchy(root);
+ treeLayout(d3root);
+
+ // Calculate the bounds of the tree
+ let minX = Infinity;
+ let maxX = -Infinity;
+ let minY = Infinity;
+ let maxY = -Infinity;
+
+ d3root.each((d) => {
+ minX = Math.min(minX, d.x);
+ maxX = Math.max(maxX, d.x);
+ minY = Math.min(minY, d.y);
+ maxY = Math.max(maxY, d.y);
+ });
+
+ // Calculate the actual dimensions needed
+ const actualWidth = maxX - minX + FIXED_NODE_WIDTH;
+ const actualHeight = maxY - minY + FIXED_NODE_HEIGHT;
+
+ // Set SVG dimensions to accommodate the tree
+ const svgWidth = Math.max(actualWidth + margin.left + margin.right, totalWidth);
+ svg
+ .attr('width', svgWidth)
+ .attr('height', Math.max(actualHeight + margin.top + margin.bottom, totalHeight));
+
+ // Calculate center offset to keep root centered
+ const rootOffset = -d3root.x;
+ const horizontalCenter = svgWidth / 2;
+
+ // Create container group and apply transforms
+ const g = svg
+ .append('g')
+ .attr('transform', `translate(${horizontalCenter + rootOffset},${margin.top})`);
+
+ // Draw links
+ const link = g
+ .selectAll('.link')
+ .data(d3root.descendants().slice(1))
+ .enter()
+ .append('path')
+ .attr('class', (d) => {
+ return d.data.index === currLocation.index ? 'link current-link' : 'link';
+ })
+ .attr('d', (d) => {
+ return `M${d.x},${d.y}
+ C${d.x},${(d.y + d.parent.y) / 2}
+ ${d.parent.x},${(d.y + d.parent.y) / 2}
+ ${d.parent.x},${d.parent.y}`;
+ });
+
+ // Create node groups
+ const node = g
+ .selectAll('.node')
+ .data(d3root.descendants())
+ .enter()
+ .append('g')
+ .attr('class', (d) => {
+ const baseClass = 'node';
+ const internalClass = d.children ? ' node--internal' : '';
+ const activeClass = d.data.index === currLocation.index ? ' active' : '';
+ return baseClass + internalClass + activeClass;
+ })
+ .attr('transform', (d) => `translate(${d.x},${d.y})`)
+ .on('click', (event, d) => {
+ dispatch(changeView(d.data.index));
+ dispatch(changeSlider(d.data.index));
+ });
+
+ // Add rectangles for nodes with consistent size
+ node
+ .append('rect')
+ .attr('width', 200)
+ .attr('height', 120)
+ .attr('x', -100)
+ .attr('y', -40)
+ .attr('rx', 8)
+ .attr('ry', 8);
+
+ // Add snapshot titles
+ node
+ .append('text')
+ .attr('dy', '-20')
+ .attr('text-anchor', 'middle')
+ .attr('class', 'snapshot-title')
+ .text((d) => `Snapshot ${d.data.index + 1}`);
+
+ // Add state changes text
+ node
+ .append('foreignObject')
+ .attr('x', -85)
+ .attr('y', -15)
+ .attr('width', 170)
+ .attr('height', 90)
+ .append('xhtml:div')
+ .style('font-size', '12px')
+ .style('text-align', 'left')
+ .style('padding-left', '8px')
+ .html((d) => findDiff(d.data.index));
+
+ if (historyEndRef.current) {
+ historyEndRef.current.scrollIntoView({ behavior: 'smooth' });
+ }
+ return svg.node();
+ };
+
+ useEffect(() => {
+ makeD3Tree();
+ }, [root, currLocation]);
+
+ useEffect(() => {
+ dispatch(setCurrentTabInApp('history')); // dispatch sent at initial page load allowing changing "immer's" draft.currentTabInApp to 'webmetrics' to facilitate render.
+ }, []);
+
+ return (
+
+ );
+}
+
+export default History;
diff --git a/src/app/components/StateRoute/PerformanceVisx/BarGraph.tsx b/src/app/components/StateRoute/PerformanceVisx/BarGraph.tsx
new file mode 100644
index 000000000..de162650f
--- /dev/null
+++ b/src/app/components/StateRoute/PerformanceVisx/BarGraph.tsx
@@ -0,0 +1,317 @@
+// @ts-nocheck
+import React, { useEffect, useState } from 'react';
+import { BarStack } from '@visx/shape';
+import { Group } from '@visx/group';
+import { Grid } from '@visx/grid';
+import { AxisBottom, AxisLeft } from '@visx/axis';
+import { scaleBand, scaleLinear, scaleOrdinal } from '@visx/scale';
+import { useTooltip, useTooltipInPortal, defaultStyles } from '@visx/tooltip';
+import { Text } from '@visx/text';
+import { schemeSet1 } from 'd3-scale-chromatic';
+import { onHover, onHoverExit, save } from '../../../slices/mainSlice';
+import { useDispatch, useSelector } from 'react-redux';
+import {
+ snapshot,
+ TooltipData,
+ Margin,
+ BarGraphProps,
+ RootState,
+ MainState,
+} from '../../../FrontendTypes';
+
+/* DEFAULTS */
+const margin = {
+ top: 30,
+ right: 30,
+ bottom: 0,
+ left: 70,
+};
+const axisColor = 'var(--text-primary)';
+const axisTickLabelColor = 'var(--text-secondary)';
+const axisLabelColor = 'var(--text-primary)';
+const tooltipStyles = {
+ ...defaultStyles,
+ minWidth: 60,
+ lineHeight: '18px',
+ pointerEvents: 'all !important',
+ padding: '8px',
+ borderRadius: '8px',
+};
+
+const BarGraph = (props: BarGraphProps): JSX.Element => {
+ const dispatch = useDispatch();
+ const { tabs, currentTab }: MainState = useSelector((state: RootState) => state.main);
+
+ const {
+ width, // from stateRoute container
+ height, // from stateRoute container
+ data, // Acquired from getPerfMetrics(snapshots, getSnapshotIds(hierarchy)) in 'PerformanceVisx'
+ comparison, // result from invoking 'allStorage' in 'PerformanceVisx'
+ setRoute, // updates the 'route' state in 'PerformanceVisx'
+ allRoutes, // array containing urls from 'PerformanceVisx'
+ filteredSnapshots, // array containing url's that exist and with route === url.pathname
+ snapshot, // state that is initialized to 'All Snapshots' in 'PerformanceVisx'
+ setSnapshot, // updates the 'snapshot' state in 'PerformanceVisx'
+ } = props;
+ const [seriesNameInput, setSeriesNameInput] = useState(`Series ${comparison.length + 1}`);
+ const {
+ tooltipOpen, // boolean whether the tooltip state is open or closed
+ tooltipLeft, // number used for tooltip positioning
+ tooltipTop, // number used for tooltip positioning
+ tooltipData, // value/data that tooltip may need to render
+ hideTooltip, // function to close a tooltip
+ showTooltip, // function to set tooltip state
+ } = useTooltip(); // returns an object with several properties that you can use to manage the tooltip state of your component
+ let tooltipTimeout: number;
+ const {
+ containerRef, // Access to the container's bounding box. This will be empty on first render.
+ TooltipInPortal, // TooltipWithBounds in a Portal, outside of your component DOM tree
+ } = useTooltipInPortal({
+ // Visx hook
+ detectBounds: true, // use TooltipWithBounds
+ scroll: true, // when tooltip containers are scrolled, this will correctly update the Tooltip position
+ });
+
+ const keys = Object.keys(data.componentData);
+ const getSnapshotId = (d: snapshot) => d.snapshotId; // data accessor (used to generate scales) and formatter (add units for on hover box). d comes from data.barstack post filtered data
+ const formatSnapshotId = (id) => `ID: ${id}`; // returns snapshot id when invoked in tooltip section
+ const formatRenderTime = (time) => `${time} ms `; // returns render time when invoked in tooltip section
+
+ const snapshotIdScale = scaleBand({
+ // create visualization SCALES with cleaned data
+ domain: data.barStack.map(getSnapshotId),
+ padding: 0.2,
+ });
+
+ const renderingScale = scaleLinear({
+ // Adjusts y axis to match/ bar height
+ domain: [0, data.maxTotalRender],
+ nice: true,
+ });
+
+ const LMcolorScale = [
+ '#14b8a6', // Teal (matching existing accent)
+ '#0d9488', // Darker teal (matching existing accent)
+ '#3c6e71', // Primary strong teal
+ '#284b63', // Primary blue
+ '#2c5282', // Deeper blue
+ '#1a365d', // Navy
+ '#2d3748', // Blue gray
+ '#4a5568', // Darker blue gray
+ '#718096', // Medium blue gray
+ '#a0aec0', // Light blue gray
+ ];
+
+ const colorScale = scaleOrdinal({
+ // Gives each bar on the graph a color using schemeSet1 imported from D3
+ domain: keys,
+ range: LMcolorScale,
+ });
+
+ // setting max dimensions and scale ranges
+ const xMax = width - margin.left - margin.right;
+ snapshotIdScale.rangeRound([0, xMax]);
+ const yMax = height - margin.top - 100;
+ renderingScale.range([yMax, 0]);
+
+ const toStorage = {
+ currentTab,
+ title: tabs[currentTab].title,
+ data,
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {(barStacks) =>
+ barStacks.map((barStack) =>
+ barStack.bars.map((bar) => {
+ if (Number.isNaN(bar.bar[1]) || bar.height < 0) {
+ // Hides new components if components don't exist in previous snapshots.
+ bar.height = 0;
+ }
+ return (
+ {
+ // Hides tool tip once cursor moves off the current rect.
+ dispatch(
+ onHoverExit(data.componentData[bar.key].rtid),
+ (tooltipTimeout = window.setTimeout(() => {
+ hideTooltip();
+ }, 300)),
+ );
+ }}
+ onMouseMove={(event) => {
+ // Cursor position in window updates position of the tool tip.
+ dispatch(onHover(data.componentData[bar.key].rtid));
+ if (tooltipTimeout) clearTimeout(tooltipTimeout);
+ let top;
+ if (snapshot === 'All Snapshots') {
+ top = event.clientY - margin.top - bar.height;
+ } else {
+ top = event.clientY - margin.top;
+ }
+
+ const left = bar.x + bar.width / 2;
+ showTooltip({
+ tooltipData: bar,
+ tooltipTop: top,
+ tooltipLeft: left,
+ });
+ }}
+ />
+ );
+ }),
+ )
+ }
+
+
+ ({
+ fill: axisTickLabelColor,
+ fontSize: 11,
+ verticalAnchor: 'middle',
+ textAnchor: 'end',
+ })}
+ />
+ ({
+ fill: axisTickLabelColor,
+ fontSize: 11,
+ textAnchor: 'middle',
+ })}
+ tickFormat={() => ''} // Add this line to hide tick labels
+ />
+
+ Rendering Time (ms)
+
+
+ {snapshot === 'All Snapshots' ? (
+
+ Snapshot ID
+
+ ) : (
+
+ Components
+
+ )}
+
+
+ {/* FOR HOVER OVER DISPLAY */}
+ {tooltipOpen &&
+ tooltipData && ( // Ths conditional statement displays a different tooltip configuration depending on if we are trying do display a specific snapshot through options menu or all snapshots together in bargraph
+
+
+ {' '}
+ {tooltipData.key} {' '}
+
+ {'Render time: ' + formatRenderTime(tooltipData.bar.data[tooltipData.key])}
+
+ {' '}
+ {formatSnapshotId(getSnapshotId(tooltipData.bar.data))}
+
+
+ )}
+
+ );
+};
+
+export default BarGraph;
diff --git a/src/app/components/StateRoute/PerformanceVisx/PerformanceVisx.tsx b/src/app/components/StateRoute/PerformanceVisx/PerformanceVisx.tsx
new file mode 100644
index 000000000..e13f649e7
--- /dev/null
+++ b/src/app/components/StateRoute/PerformanceVisx/PerformanceVisx.tsx
@@ -0,0 +1,275 @@
+/* eslint-disable no-param-reassign */
+/* eslint-disable guard-for-in */
+/* eslint-disable no-restricted-syntax */
+/* eslint-disable max-len */
+import React, { useState, useEffect } from 'react';
+import { MemoryRouter as Router, Route, NavLink, Routes, Navigate } from 'react-router-dom';
+import BarGraph from './BarGraph';
+import { useDispatch, useSelector } from 'react-redux';
+import { setCurrentTabInApp } from '../../../slices/mainSlice';
+import {
+ PerfData,
+ Series,
+ PerformanceVisxProps,
+ RootState,
+ MainState,
+} from '../../../FrontendTypes';
+
+const collectNodes = (snaps, componentName) => {
+ const componentsResult = [];
+ const renderResult = [];
+ let trackChanges = 0;
+ let newChange = true;
+ for (let x = 0; x < snaps.length; x += 1) {
+ const snapshotList = [];
+ snapshotList.push(snaps[x]);
+ for (let i = 0; i < snapshotList.length; i += 1) {
+ const cur = snapshotList[i];
+ if (cur.name === componentName) {
+ const renderTime = Number(
+ Number.parseFloat(cur.componentData.actualDuration).toPrecision(5),
+ );
+ if (renderTime === 0) {
+ break;
+ } else {
+ renderResult.push(renderTime);
+ }
+ // compare the last pushed component Data from the array to
+ // the current one to see if there are differences
+ if (x !== 0 && componentsResult.length !== 0) {
+ // needs to be stringified because values are hard to determine if
+ // true or false if in they're seen as objects
+ if (
+ JSON.stringify(
+ Object.values(
+ componentsResult[newChange ? componentsResult.length - 1 : trackChanges],
+ )[0],
+ ) !== JSON.stringify(cur.componentData.props)
+ ) {
+ newChange = true;
+ const props = { [`snapshot${x}`]: { ...cur.componentData.props } };
+ componentsResult.push(props);
+ } else {
+ newChange = false;
+ trackChanges = componentsResult.length - 1;
+ const props = { [`snapshot${x}`]: { state: 'Same state as prior snapshot' } };
+ componentsResult.push(props);
+ }
+ } else {
+ const props = { [`snapshot${x}`]: { ...cur.componentData.props } };
+ componentsResult.push(props);
+ }
+ break;
+ }
+ if (cur.children?.length > 0) {
+ for (const child of cur.children) {
+ snapshotList.push(child);
+ }
+ }
+ }
+ }
+
+ const finalResults = componentsResult.map((e, index) => {
+ const name = Object.keys(e)[0];
+ e[name].rendertime = renderResult[index];
+ return e;
+ });
+ return finalResults;
+};
+
+type currNum = number;
+
+/* DATA HANDLING HELPER FUNCTIONS */
+const traverse = (snapshot, data, snapshots, currTotalRender: currNum = 0): void => {
+ if (!snapshot.children[0]) return;
+
+ // loop through snapshots
+ snapshot.children.forEach((child, idx: number) => {
+ const componentName = child.name + -[idx + 1];
+
+ // Get component Rendering Time
+ const renderTime = Number(Number.parseFloat(child.componentData.actualDuration).toPrecision(5));
+ // sums render time for all children
+ const childrenRenderTime = currTotalRender + renderTime;
+ // components as keys and set the value to their rendering time
+ data.barStack[data.barStack.length - 1][componentName] = renderTime;
+
+ // Get component stateType
+ if (!data.componentData[componentName]) {
+ data.componentData[componentName] = {
+ stateType: 'stateless',
+ renderFrequency: 0,
+ totalRenderTime: 0,
+ rtid: '',
+ information: {},
+ };
+ if (child.state !== 'stateless') data.componentData[componentName].stateType = 'stateful';
+ }
+ // increment render frequencies
+ if (renderTime > 0) {
+ data.componentData[componentName].renderFrequency += 1;
+ }
+
+ // add to total render time
+ data.componentData[componentName].totalRenderTime += renderTime;
+ // Get rtid for the hovering feature
+ data.componentData[componentName].rtid = child.rtid;
+ data.componentData[componentName].information = collectNodes(snapshots, child.name);
+ traverse(snapshot.children[idx], data, snapshots, childrenRenderTime);
+ });
+ // reassigns total render time to max render time
+ data.maxTotalRender = Math.max(currTotalRender, data.maxTotalRender);
+};
+
+// Retrieve snapshot series data from Chrome's local storage.
+const allStorage = (): Series[] => {
+ let values = localStorage.getItem('project');
+ const newValues: Series[] = values === null ? [] : JSON.parse(values);
+ return newValues;
+};
+
+// Gets snapshot Ids for the regular bar graph view.
+const getSnapshotIds = (obj, snapshotIds = []): string[] => {
+ snapshotIds.push(`${obj.name}.${obj.branch}`);
+ if (obj.children) {
+ obj.children.forEach((child) => {
+ getSnapshotIds(child, snapshotIds);
+ });
+ }
+ return snapshotIds;
+};
+
+// Returns array of snapshot objs each with components and corresponding render times.
+const getPerfMetrics = (snapshots, snapshotsIds): PerfData => {
+ const perfData: PerfData = {
+ barStack: [],
+ componentData: {},
+ maxTotalRender: 0,
+ };
+ snapshots.forEach((snapshot, i: number) => {
+ perfData.barStack.push({ snapshotId: snapshotsIds[i], route: snapshot.route.url });
+ traverse(snapshot, perfData, snapshots);
+ });
+ return perfData;
+};
+
+const getActions = () => {
+ // Creates the actions array used to populate the compare actions dropdown (WORK IN PROGRESS)
+ const project = localStorage.getItem('project');
+ const seriesArr: Series[] = project === null ? [] : JSON.parse(project);
+ const actionsArr = [];
+
+ if (seriesArr.length) {
+ for (let i = 0; i < seriesArr.length; i += 1) {
+ for (const actionObj of seriesArr[i].data.barStack) {
+ if (actionObj.name === 'action') {
+ actionObj.seriesName = seriesArr[i].name;
+ actionsArr.push(actionObj);
+ }
+ }
+ }
+ }
+ return actionsArr;
+};
+
+/* EXPORT COMPONENT */
+const PerformanceVisx = (props: PerformanceVisxProps): JSX.Element => {
+ // hook used to dispatch onhover action in react
+ const {
+ width, // from ParentSize provided in StateRoute
+ height, // from ParentSize provided in StateRoute
+ snapshots, // from 'tabs[currentTab]' object in 'MainContainer'
+ hierarchy, // from 'tabs[currentTab]' object in 'MainContainer'
+ } = props;
+ const dispatch = useDispatch();
+ const { currentTabInApp }: MainState = useSelector((state: RootState) => state.main);
+ const data = getPerfMetrics(snapshots, getSnapshotIds(hierarchy));
+ const [route, setRoute] = useState('All Routes');
+ const [snapshot, setSnapshot] = useState('All Snapshots');
+
+ getActions();
+
+ useEffect(() => {
+ dispatch(setCurrentTabInApp('performance')); // dispatch sent at initial page load allowing changing "immer's" draft.currentTabInApp to 'performance' to facilitate render.
+ renderForTutorial();
+ }, []);
+
+ const allRoutes = []; // create allRoutes variable to hold urls
+ const filteredSnapshots = [];
+
+ for (let i = 0; i < data.barStack.length; i += 1) {
+ // loop through data.barStack
+ const url = new URL(data.barStack[i].route); // set url variable to new route url
+ if (!allRoutes.includes(url.pathname)) {
+ // if all the routes do not have the pathname property on url then push it onto all routes array
+ allRoutes.push(url.pathname);
+ }
+ if (route && route === url.pathname) {
+ // if the route exists and it is equal to url.pathname then push data.barstack at i into filteredSnapshots array
+ filteredSnapshots.push(data.barStack[i]);
+ }
+ }
+ if (route !== 'All Routes') {
+ // if route does not equal to All Routes, set data.barstack to filteredSnapshots array
+ data.barStack = filteredSnapshots;
+ }
+
+ let maxHeight = 0; // maxheight is referring to the max height in render time to choose the scaling size for graph
+ if (snapshot !== 'All Snapshots') {
+ const checkData = [data.barStack.find((comp) => comp.snapshotId === snapshot)]; // filter barStack to make it equal to an array of length 1 with object matching snapshot ID to mirror the data.barStack object's shape
+ const holdData = [];
+
+ for (const key in checkData[0]) {
+ // looping through checkData which is composed of a single snapshot while pushing key/values to a new object and setting maxHeight
+ if (key !== 'route' && key !== 'snapshotId') {
+ if (maxHeight < checkData[0][key]) maxHeight = checkData[0][key];
+ const name = {};
+ name[key] = checkData[0][key];
+ holdData.push(name);
+ holdData[holdData.length - 1].route = checkData[0].route;
+ holdData[holdData.length - 1].snapshotId = key;
+ }
+ }
+
+ data.maxTotalRender = maxHeight * 1.15; // maxTotalRender height of bar is aligned to y-axis. 1.15 adjusts the numbers on the y-axis so the max bar's true height never reaches the max of the y-axis
+ if (holdData) data.barStack = holdData; // assign holdData to data.barStack to be used later to create graph
+ }
+
+ const renderForTutorial = () => {
+ // This will redirect to the proper tabs during the tutorial
+ // Updated redirect to Navigate v23 redirect no longer supported in react router dom after v6
+ if (currentTabInApp === 'performance') return ;
+ if (currentTabInApp === '/performance-comparison')
+ return ;
+ return null;
+ };
+
+ return (
+ <>
+
+
+
+
+ ) : null
+ }
+ />
+
+ >
+ );
+};
+
+export default PerformanceVisx;
diff --git a/src/app/components/StateRoute/StateRoute.tsx b/src/app/components/StateRoute/StateRoute.tsx
new file mode 100644
index 000000000..6b08ba027
--- /dev/null
+++ b/src/app/components/StateRoute/StateRoute.tsx
@@ -0,0 +1,229 @@
+// @ts-nocheck
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable @typescript-eslint/ban-types */
+/* eslint-disable @typescript-eslint/no-var-requires */
+/* eslint-disable max-len */
+/* eslint-disable object-curly-newline */
+import React, { useState } from 'react';
+import { MemoryRouter as Router, Route, NavLink, Routes, Outlet, Link } from 'react-router-dom';
+import { ParentSize } from '@visx/responsive';
+import Tree from './Tree';
+import ComponentMap from './ComponentMap/ComponentMap';
+import { changeView, changeSlider, toggleAxTree, setCurrentTabInApp } from '../../slices/mainSlice';
+import { useDispatch, useSelector } from 'react-redux';
+import PerformanceVisx from './PerformanceVisx/PerformanceVisx';
+import WebMetricsContainer from './WebMetrics/WebMetricsContainer';
+import { MainState, RootState, StateRouteProps } from '../../FrontendTypes';
+import AxContainer from './AxMap/AxContainer';
+
+const History = require('./History').default;
+const NO_STATE_MSG = 'No state change detected. Trigger an event to change state';
+
+const StateRoute = (props: StateRouteProps) => {
+ const {
+ axSnapshots,
+ snapshot,
+ hierarchy: propsHierarchy,
+ snapshots,
+ viewIndex: propsViewIndex,
+ webMetrics,
+ currLocation,
+ } = props;
+
+ const { tabs, currentTab }: MainState = useSelector((state: RootState) => state.main);
+ const { hierarchy: tabsHierarchy, sliderIndex, viewIndex: tabsViewIndex } = tabs[currentTab];
+ const hierarchy = propsHierarchy || tabsHierarchy;
+ const viewIndex = propsViewIndex || tabsViewIndex;
+
+ const dispatch = useDispatch();
+ const [showTree, setShowTree] = useState(false);
+ const [showParagraph, setShowParagraph] = useState(true);
+
+ const enableAxTreeButton = () => {
+ dispatch(toggleAxTree('toggleAxRecord'));
+ setShowParagraph(false);
+ setShowTree(true);
+ };
+
+ return (
+
+
+
+
+ navData.isActive ? 'is-active router-link map-tab' : 'router-link map-tab'
+ }
+ end
+ >
+ Map
+
+
+ navData.isActive ? 'is-active router-link history-tab' : 'router-link history-tab'
+ }
+ to='/history'
+ >
+ History
+
+
+ navData.isActive ? 'is-active router-link performance' : 'router-link performance-tab'
+ }
+ to='/performance'
+ >
+ Performance
+
+
+
+ navData.isActive
+ ? 'is-active router-link web-metrics-tab'
+ : 'router-link web-metrics-tab'
+ }
+ to='/webMetrics'
+ >
+ Web Metrics
+
+
+ navData.isActive ? 'is-active router-link tree-tab' : 'router-link tree-tab'
+ }
+ to='/tree'
+ >
+ Tree
+
+
+ navData.isActive
+ ? 'is-active router-link accessibility-tab'
+ : 'router-link accessibility-tab'
+ }
+ to='/accessibility'
+ >
+ Accessibility
+
+
+
+
+
+ ) : (
+
+ {showParagraph && (
+
+
+ A Note to Developers: Reactime is using the Chrome Debugger API in order to
+ grab the Accessibility Tree. Enabling this option will allow you to record
+ Accessibility Tree snapshots, but will result in the Chrome browser
+ notifying you that the Chrome Debugger has started.
+
+
+ )}
+
+
+ Enable
+
+
+ )
+ }
+ />
+
+ {({ width, height }) => (
+
+ )}
+
+ ) : (
+ {NO_STATE_MSG}
+ )
+ }
+ />
+
+
+ {({ width, height }) => (
+
+ )}
+
+
+
+ ) : (
+ {NO_STATE_MSG}
+ )
+ }
+ />
+ } />
+ }
+ />
+
+ {({ width, height }) => {
+ const maxHeight = 1200;
+ const h = Math.min(height, maxHeight);
+ return (
+
+ );
+ }}
+
+ ) : null
+ }
+ />
+
+
+
+ );
+};
+
+export default StateRoute;
diff --git a/src/app/components/StateRoute/Tree.tsx b/src/app/components/StateRoute/Tree.tsx
new file mode 100644
index 000000000..7d83c30d5
--- /dev/null
+++ b/src/app/components/StateRoute/Tree.tsx
@@ -0,0 +1,78 @@
+import React, { useEffect } from 'react';
+import { JSONTree } from 'react-json-tree'; // React JSON Viewer Component
+import { setCurrentTabInApp } from '../../slices/mainSlice';
+import { useDispatch } from 'react-redux';
+import { TreeProps } from '../../FrontendTypes';
+
+/*
+ Creats a component based on the JSON. Options may be passed into the props of the JSONTree component
+*/
+
+const colors = {
+ scheme: 'paraiso',
+ author: 'jan t. sott',
+ base00: '#2f1e2e',
+ base01: '#41323f',
+ base02: '#4f424c',
+ base03: '#776e71',
+ base04: '#8d8687',
+ base05: '#a39e9b',
+ base06: '#b9b6b0',
+ base07: '#e7e9db',
+ base08: '#ef6155',
+ base09: '#f99b15',
+ base0A: '#fec418',
+ base0B: '#48b685',
+ base0C: '#5bc4bf',
+ base0D: '#06b6ef',
+ base0E: '#815ba4',
+ base0F: '#e96ba8',
+};
+
+const getItemString = (
+ type: any,
+ data: { state?: object | string; name: string; children: [] },
+) => {
+ // function that allows the customization of how arrays, objects, and iterable nodes are displayed.
+ if (data && data.name) {
+ return {data.name} ;
+ }
+ return ;
+};
+
+const Tree = (props: TreeProps) => {
+ const {
+ snapshot, // from 'tabs[currentTab]' object in 'MainContainer'
+ snapshots, // from 'tabs[currentTab].snapshotDisplay' object in 'MainContainer'
+ currLocation, // from 'tabs[currentTab]' object in 'MainContainer'
+ } = props;
+ // @ts-ignore
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(setCurrentTabInApp('tree')); // dispatch sent at initial page load allowing changing "immer's" draft.currentTabInApp to 'tree' to facilitate render.
+ }, []);
+
+ return (
+ <>
+
+ {snapshot && (
+ // @ts-ignore
+ ({ className: 'json-tree' }) }} // theme set to a base16 theme that has been extended to include "className: 'json-tree'"
+ shouldExpandNodeInitially={() => true} // determines if node should be expanded when it first renders (root is expanded by default)
+ getItemString={getItemString} // allows the customization of how arrays, objects, and iterable nodes are displayed.
+ labelRenderer={(raw: any[]) => {
+ // renders a label if the first element of raw is a number.
+ return typeof raw[0] !== 'number' ? {raw[0]} : null;
+ }}
+ />
+ )}
+
+ >
+ );
+};
+
+export default Tree;
diff --git a/src/app/components/StateRoute/WebMetrics/WebMetrics.tsx b/src/app/components/StateRoute/WebMetrics/WebMetrics.tsx
new file mode 100644
index 000000000..673fe3e7a
--- /dev/null
+++ b/src/app/components/StateRoute/WebMetrics/WebMetrics.tsx
@@ -0,0 +1,151 @@
+import React, { useEffect } from 'react';
+import Charts from 'react-apexcharts';
+import ReactHover, { Trigger, Hover } from 'react-hover';
+import { setCurrentTabInApp } from '../../../slices/mainSlice';
+import { useDispatch } from 'react-redux';
+
+const WebMetrics = (props) => {
+ const dispatch = useDispatch();
+
+ const state = {
+ series: props.series,
+ options: {
+ colors: [props.color],
+ chart: {
+ height: 100,
+ width: 100,
+ type: 'radialBar',
+ toolbar: {
+ show: false,
+ },
+ foreColor: 'var(--text-primary)',
+ },
+ plotOptions: {
+ radialBar: {
+ startAngle: -135,
+ endAngle: 135,
+ hollow: {
+ margin: 0,
+ size: '75%',
+ background: 'transparent',
+ image: props.overLimit
+ ? 'https://static.vecteezy.com/system/resources/thumbnails/012/042/301/small/warning-sign-icon-transparent-background-free-png.png'
+ : undefined,
+ imageWidth: 32,
+ imageHeight: 32,
+ imageOffsetX: 0,
+ imageOffsetY: -64,
+ imageClipped: false,
+ position: 'front',
+ dropShadow: {
+ enabled: false,
+ top: 3,
+ left: 0,
+ blur: 4,
+ opacity: 0.24,
+ },
+ },
+ track: {
+ background: 'var(--border-color-dark)',
+ strokeWidth: '10%',
+ margin: 0,
+ },
+ dataLabels: {
+ show: true,
+ name: {
+ offsetY: -10,
+ show: true,
+ color: 'var(--text-primary)',
+ fontSize: '24px',
+ },
+ value: {
+ formatter: props.formatted,
+ color: 'var(--color-primary)',
+ fontSize: '16px',
+ show: true,
+ },
+ },
+ },
+ },
+ fill: {
+ type: 'solid',
+ gradient: {
+ shade: 'dark',
+ type: 'horizontal',
+ shadeIntensity: 0.1,
+ inverseColors: false,
+ opacityFrom: 1,
+ opacityTo: 1,
+ stops: [0, 100],
+ },
+ },
+ stroke: {
+ lineCap: 'flat',
+ },
+ labels: [props.label],
+ },
+ };
+
+ useEffect(() => {
+ dispatch(setCurrentTabInApp('webmetrics'));
+ }, []);
+
+ const getThresholdColor = (type: string): string => {
+ switch (type) {
+ case 'good':
+ return '#0bce6b';
+ case 'improvement':
+ return '#fc5a03';
+ case 'poor':
+ return '#fc2000';
+ default:
+ return 'var(--text-primary)';
+ }
+ };
+
+ const hoverOptions = {
+ followCursor: false,
+ shiftX: 20,
+ shiftY: 0,
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {props.name}
+
+
{props.description}
+
+ Good:
+ {`< ${props.score[0]}`}
+
+
+ Needs Improvement:
+ {`< ${props.score[1]}`}
+
+
+ Poor:
+ {`> ${props.score[1]}`}
+
+
+
+
+
+ );
+};
+
+export default WebMetrics;
diff --git a/src/app/components/StateRoute/WebMetrics/WebMetricsContainer.tsx b/src/app/components/StateRoute/WebMetrics/WebMetricsContainer.tsx
new file mode 100644
index 000000000..1ffd8efeb
--- /dev/null
+++ b/src/app/components/StateRoute/WebMetrics/WebMetricsContainer.tsx
@@ -0,0 +1,135 @@
+import React from 'react';
+import WebMetrics from './WebMetrics';
+
+const WebMetricsContainer = (props) => {
+ const { webMetrics } = props;
+ // Add default values for all color variables
+ let LCPColor = '#fc2000'; // Default to poor/red
+ let FIDColor = '#fc2000';
+ let FCPColor = '#fc2000';
+ let TTFBColor = '#fc2000';
+ let CLSColor = '#fc2000';
+ let INPColor = '#fc2000';
+
+ // Ensure webMetrics exists and has valid values
+ if (webMetrics) {
+ // LCP color logic
+ if (typeof webMetrics.LCP === 'number') {
+ if (webMetrics.LCP <= 2500) LCPColor = '#0bce6b';
+ else if (webMetrics.LCP <= 4000) LCPColor = '#fc5a03';
+ }
+
+ // FID color logic
+ if (typeof webMetrics.FID === 'number') {
+ if (webMetrics.FID <= 100) FIDColor = '#0bce6b';
+ else if (webMetrics.FID <= 300) FIDColor = '#fc5a03';
+ }
+
+ // FCP color logic
+ if (typeof webMetrics.FCP === 'number') {
+ if (webMetrics.FCP <= 1800) FCPColor = '#0bce6b';
+ else if (webMetrics.FCP <= 3000) FCPColor = '#fc5a03';
+ }
+
+ // TTFB color logic
+ if (typeof webMetrics.TTFB === 'number') {
+ if (webMetrics.TTFB <= 800) TTFBColor = '#0bce6b';
+ else if (webMetrics.TTFB <= 1800) TTFBColor = '#fc5a03';
+ }
+
+ // CLS color logic
+ if (typeof webMetrics.CLS === 'number') {
+ if (webMetrics.CLS <= 0.1) CLSColor = '#0bce6b';
+ else if (webMetrics.CLS <= 0.25) CLSColor = '#fc5a03';
+ }
+
+ // INP color logic
+ if (typeof webMetrics.INP === 'number') {
+ if (webMetrics.INP <= 200) INPColor = '#0bce6b';
+ else if (webMetrics.INP <= 500) INPColor = '#fc5a03';
+ }
+ }
+
+ return (
+
+
+ typeof webMetrics.LCP !== 'number' ? '- ms' : `${webMetrics.LCP.toFixed(2)} ms`
+ }
+ score={['2500 ms', '4000 ms']}
+ overLimit={webMetrics.LCP ? webMetrics.LCP > 7000 : false}
+ label='Largest Contentful Paint'
+ name='Largest Contentful Paint'
+ description='Measures loading performance.'
+ />
+
+ typeof webMetrics.FID !== 'number' ? '- ms' : `${webMetrics.FID.toFixed(2)} ms`
+ }
+ score={['100 ms', '300 ms']}
+ overLimit={webMetrics.FID ? webMetrics.FID > 500 : false}
+ label='First Input Delay'
+ name='First Input Delay'
+ description='Measures interactivity.'
+ />
+
+ typeof webMetrics.FCP !== 'number' ? '- ms' : `${webMetrics.FCP.toFixed(2)} ms`
+ }
+ score={['1800 ms', '3000 ms']}
+ overLimit={webMetrics.FCP ? webMetrics.FCP > 5000 : false}
+ label='First Contentful Paint'
+ name='First Contentful Paint'
+ description='Measures the time it takes the browser to render the first piece of DOM content.'
+ />
+
+ typeof webMetrics.TTFB !== 'number' ? '- ms' : `${webMetrics.TTFB.toFixed(2)} ms`
+ }
+ score={['800 ms', '1800 ms']}
+ overLimit={webMetrics.TTFB ? webMetrics.TTFB > 3000 : false}
+ label='Time To First Byte'
+ name='Time to First Byte'
+ description='Measures the time it takes for a browser to receive the first byte of page content.'
+ />
+
+ `CLS Score: ${
+ typeof webMetrics.CLS !== 'number'
+ ? 'N/A'
+ : `${webMetrics.CLS < 0.01 ? '~0' : webMetrics.CLS.toFixed(2)}`
+ }`
+ }
+ score={['0.1', '0.25']}
+ overLimit={webMetrics.CLS ? webMetrics.CLS > 0.5 : false}
+ label='Cumulative Layout Shift'
+ name='Cumulative Layout Shift'
+ description={`Quantifies the visual stability of a web page by measuring layout shifts during the application's loading and interaction.`}
+ />
+
+ typeof webMetrics.INP !== 'number' ? '- ms' : `${webMetrics.INP.toFixed(2)} ms`
+ }
+ score={['200 ms', '500 ms']}
+ overLimit={webMetrics.INP ? webMetrics.INP > 700 : false}
+ label='Interaction to Next Paint'
+ name='Interaction to Next Paint'
+ description={`Assesses a page's overall responsiveness to user interactions by observing the latency of all click, tap, and keyboard interactions that occur throughout the lifespan of a user's visit to a page`}
+ />
+
+ );
+};
+
+export default WebMetricsContainer;
diff --git a/src/app/components/SwitchApp.jsx b/src/app/components/SwitchApp.jsx
deleted file mode 100644
index 239efa480..000000000
--- a/src/app/components/SwitchApp.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-import Select from 'react-select';
-import { useStoreContext } from '../store';
-import { setTab } from '../actions/actions';
-
-
-// Launch Feature: Fix the dropdown styling to make it more distinguishable
-
-const SwitchAppDropdown = () => {
- const [{ currentTab, tabs }, dispatch] = useStoreContext();
-
- const tabsArray = [];
-
- Object.keys(tabs).forEach(tab => {
- tabsArray.push({ value: tab, label: tabs[tab].title });
- });
-
- const currTab = {
- value: currentTab,
- label: tabs[currentTab].title,
- };
-
-
- return (
- {
- dispatch(setTab(parseInt(e.value, 10)));
- }}
- options={tabsArray}
- />
- );
-};
-
-export default SwitchAppDropdown;
diff --git a/src/app/components/SwitchState.jsx b/src/app/components/SwitchState.jsx
deleted file mode 100644
index 9ad4b3875..000000000
--- a/src/app/components/SwitchState.jsx
+++ /dev/null
@@ -1,36 +0,0 @@
-// import React from 'react';
-// import Select from 'react-select';
-// import Action from './Action';
-// import { useStoreContext } from '../store';
-
-// // SwitchStateDropdown should display options including all
-// // and specific states being used
-// // TODO: Need to get the getter name (for functional components)
-// // and state name for stateful components
-// const SwitchStateDropdown = () => {
-// const [{ tabs, currentTab}, dispatch] = useStoreContext();
-// const { snapshots } = tabs[currentTab];
-// // Requirements:
-// // Should be able to filter the Actions by the name of the state being changed
-// // Should consider how to import the state names being changed
-// // Should display the "All state", as well as specific state names as the options
-// // When clicked on "All state" >> should display all Actions
-// // When clicked on a specific "state"
-// >> should only display Actions corresponding to the state
-
-// // const sampleDropdown = [
-// // {label: 'Overview'},
-// // {label: 'setUsername'},
-// // {label: 'setPassword'}
-// // ];
-
-// return (
-//
-// )
-// }
-
-// export default SwitchStateDropdown;
diff --git a/src/app/components/TimeTravel/Dropdown.tsx b/src/app/components/TimeTravel/Dropdown.tsx
new file mode 100644
index 000000000..db3d86350
--- /dev/null
+++ b/src/app/components/TimeTravel/Dropdown.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import Select from 'react-select';
+import { DropdownProps } from '../../FrontendTypes';
+
+/*
+ Allows the user to change the speed of the time-travel based on the selected dropdown value
+ Component is created in the TravelContainer.tsx
+*/
+
+const Dropdown = (props: DropdownProps): JSX.Element => {
+ const { speeds, setSpeed, selectedSpeed } = props;
+ return (
+
+ );
+};
+
+export default Dropdown;
diff --git a/src/app/components/TimeTravel/VerticalSlider.tsx b/src/app/components/TimeTravel/VerticalSlider.tsx
new file mode 100644
index 000000000..df41058f6
--- /dev/null
+++ b/src/app/components/TimeTravel/VerticalSlider.tsx
@@ -0,0 +1,73 @@
+import React, { useState, useEffect } from 'react';
+import Slider from 'rc-slider';
+import Tooltip from 'rc-tooltip';
+import { changeSlider } from '../../slices/mainSlice';
+import { useDispatch, useSelector } from 'react-redux';
+import { HandleProps, MainSliderProps, MainState, RootState } from '../../FrontendTypes';
+
+const { Handle } = Slider; // component constructor of Slider that allows customization of the handle
+//takes in snapshot length
+const handle = (props: HandleProps): JSX.Element => {
+ const { value, dragging, index, ...restProps } = props;
+
+ return (
+
+
+
+ );
+};
+
+function VerticalSlider(props: MainSliderProps): JSX.Element {
+ const dispatch = useDispatch();
+ const { snapshots } = props; // destructure props to get our total number of snapshots
+ const [sliderIndex, setSliderIndex] = useState(0); // create a local state 'sliderIndex' and set it to 0.
+ const { tabs, currentTab }: MainState = useSelector((state: RootState) => state.main);
+ const { currLocation } = tabs[currentTab]; // we destructure the currentTab object
+
+ useEffect(() => {
+ if (currLocation) {
+ // if we have a 'currLocation'
+ let correctedSliderIndex;
+
+ for (let i = 0; i < snapshots.length; i++) {
+ //@ts-ignore -- ignores the errors on the next line
+ if (snapshots[i].props.index === currLocation.index) {
+ correctedSliderIndex = i;
+ }
+ }
+ setSliderIndex(correctedSliderIndex);
+ } else {
+ setSliderIndex(0); // just set the thumb position to the beginning
+ }
+ }, [currLocation]); // if currLocation changes, rerun useEffect
+
+ return (
+ {
+ // when the slider position changes
+ setSliderIndex(index); // update the sliderIndex
+ dispatch(changeSlider(snapshots[index].props.index));
+ }}
+ handle={handle}
+ />
+ );
+}
+
+export default VerticalSlider;
diff --git a/src/app/components/Tree.jsx b/src/app/components/Tree.jsx
deleted file mode 100644
index aef82eb66..000000000
--- a/src/app/components/Tree.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from 'react';
-import JSONTree from 'react-json-tree';
-import PropTypes from 'prop-types';
-
-const getItemString = (type, data) => {
- // check to make sure that we are on the tree node, not anything else
- if (
- Object.keys(data).length === 3
- && typeof data.state === 'object'
- && typeof data.name === 'string'
- && Array.isArray(data.children)
- ) {
- return {data.name} ;
- }
- return null;
-};
-
-const Tree = props => {
- const { snapshot } = props;
- return (
- <>
- {snapshot && (
- ({ className: 'json-tree' }) }}
- shouldExpandNode={() => true}
- getItemString={getItemString}
- labelRenderer={raw => (typeof raw[0] !== 'number' ? {raw[0]} : null)}
- />
- )}
- >
- );
-};
-
-Tree.propTypes = {
- snapshot: PropTypes.shape({
- state: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
- children: PropTypes.arrayOf(PropTypes.object),
- name: PropTypes.string,
- }).isRequired,
-};
-
-export default Tree;
diff --git a/src/app/constants/actionTypes.js b/src/app/constants/actionTypes.js
deleted file mode 100644
index bf020e15d..000000000
--- a/src/app/constants/actionTypes.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export const TOGGLE_MODE = 'TOGGLE_MODE';
-export const MOVE_BACKWARD = 'MOVE_BACKWARD';
-export const MOVE_FORWARD = 'MOVEFORWARD';
-export const IMPORT = 'IMPORT';
-export const EMPTY = 'EMPTY';
-export const CHANGE_VIEW = 'CHANGE_VIEW';
-export const CHANGE_SLIDER = 'CHANGE_SLIDER';
-export const SET_PORT = 'SET_PORT';
-export const PAUSE = 'PAUSE';
-export const PLAY = 'PLAY';
-export const INITIAL_CONNECT = 'INITIAL_CONNECT';
-export const NEW_SNAPSHOTS = 'NEW_SNAPSHOTS';
-export const SET_TAB = 'SET_TAB';
-export const DELETE_TAB = 'DELETE_TAB';
diff --git a/src/app/containers/ActionContainer.jsx b/src/app/containers/ActionContainer.jsx
deleted file mode 100644
index a4a387dc0..000000000
--- a/src/app/containers/ActionContainer.jsx
+++ /dev/null
@@ -1,42 +0,0 @@
-/* eslint-disable react/no-array-index-key */
-import React from 'react';
-// import SwitchStateDropdown from '../components/SwitchState';
-import Action from '../components/Action';
-
-import { emptySnapshots } from '../actions/actions';
-import { useStoreContext } from '../store';
-
-// Launch: render a dropdown filterable list
-function ActionContainer() {
- const [{ tabs, currentTab }, dispatch] = useStoreContext();
- const { snapshots, sliderIndex, viewIndex } = tabs[currentTab];
-
- let actionsArr = [];
- // build actions array
- if (snapshots.length > 0) {
- actionsArr = snapshots.map((snapshot, index) => {
- const selected = index === viewIndex;
- return (
-
- );
- });
- }
- return (
-
-
- dispatch(emptySnapshots())} type="button">
- Empty
-
-
-
{actionsArr}
-
- );
-}
-
-export default ActionContainer;
diff --git a/src/app/containers/ActionContainer.tsx b/src/app/containers/ActionContainer.tsx
new file mode 100644
index 000000000..147d53bf9
--- /dev/null
+++ b/src/app/containers/ActionContainer.tsx
@@ -0,0 +1,180 @@
+/* eslint-disable max-len */
+import React, { useEffect, useState, useRef } from 'react';
+import Action from '../components/Actions/Action';
+import { emptySnapshots, changeSlider } from '../slices/mainSlice';
+import { useDispatch, useSelector } from 'react-redux';
+import RouteDescription from '../components/Actions/RouteDescription';
+import DropDown from '../components/Actions/DropDown';
+import ProvConContainer from './ProvConContainer';
+import { ActionContainerProps, CurrentTab, MainState, Obj, RootState } from '../FrontendTypes';
+import { Button } from '@mui/material';
+import RecordButton from '../components/Actions/RecordButton';
+
+/*
+ This file renders the 'ActionContainer'. The action container is the leftmost column in the application. It includes the button that shrinks and expands the action container, a dropdown to select the active site, a clear button, the current selected Route, and a list of selectable snapshots with timestamps.
+*/
+
+function ActionContainer(props: ActionContainerProps): JSX.Element {
+ const [dropdownSelection, setDropdownSelection] = useState('Time Jump');
+ const actionsEndRef = useRef(null as unknown as HTMLDivElement);
+
+ const dispatch = useDispatch();
+ const { currentTab, tabs, port }: MainState = useSelector((state: RootState) => state.main);
+
+ const { currLocation, hierarchy, sliderIndex, viewIndex }: Partial = tabs[currentTab]; // we destructure the currentTab object
+ const { snapshots } = props;
+ const [recordingActions, setRecordingActions] = useState(true); // We create a local state 'recordingActions' and set it to true
+ let actionsArr: JSX.Element[] = []; // we create an array 'actionsArr' that will hold elements we create later on
+ // we create an array 'hierarchyArr' that will hold objects and numbers
+ const hierarchyArr: (number | {})[] = [];
+
+ // Auto scroll when snapshots change
+ useEffect(() => {
+ if (actionsEndRef.current) {
+ actionsEndRef.current.scrollIntoView({ behavior: 'smooth' });
+ }
+ }, [snapshots]);
+
+ const displayArray = (obj: Obj): void => {
+ if (
+ obj.stateSnapshot.children.length > 0 && // if the 'stateSnapshot' has a non-empty 'children' array
+ obj.stateSnapshot.children[0] && // and there is an element
+ obj.stateSnapshot.children[0].state && // with a 'state'
+ obj.stateSnapshot.children[0].name // and a 'name'
+ ) {
+ const newObj: Record = {
+ // we create a new Record object (whose property keys are Keys and whose property values are Type.
+ //This utility can be used to map the properties of a type to another type) and populate it's properties with
+ //relevant values from our argument 'obj'.
+ index: obj.index,
+ displayName: `${obj.index + 1}`,
+ state: obj.stateSnapshot.children[0].state,
+ componentName: obj.stateSnapshot.children[0].name,
+ routePath: obj.stateSnapshot.route.url,
+ componentData:
+ JSON.stringify(obj.stateSnapshot.children[0].componentData) === '{}'
+ ? ''
+ : obj.stateSnapshot.children[0].componentData,
+ };
+ hierarchyArr.push(newObj); // we push our record object into 'hiearchyArr' defined on line 35
+ }
+
+ if (obj.children) {
+ // if argument has a 'children' array, we iterate through it and run 'displayArray' on each element
+ obj.children.forEach((element): void => {
+ //recursive call
+ displayArray(element);
+ });
+ }
+ };
+
+ // the hierarchy gets set on the first click in the page
+ if (hierarchy) displayArray(hierarchy); // when page is refreshed we may not have a hierarchy so we need to check if hierarchy was initialized. If it was initialized, invoke displayArray to display the hierarchy
+
+ // Sort hierarchyArr by index property of each object. This will be useful when later when we build our components so that our components will be displayed in index/chronological order
+ hierarchyArr.sort((a: Obj, b: Obj): number => a.index - b.index);
+
+ // we create a map of components that are constructed from "hierarchyArr's" elements/snapshots
+ actionsArr = hierarchyArr.map(
+ (snapshot: {
+ routePath: any;
+ state?: Record;
+ index: number;
+ displayName: string;
+ componentName: string;
+ componentData: { actualDuration: number } | undefined;
+ }) => {
+ const { index } = snapshot; // destructure index from snapshot
+ const selected = index === viewIndex; // boolean on whether the current index is the same as the viewIndex
+ const last = viewIndex === -1 && index === hierarchyArr.length - 1; // boolean on whether the view index is less than 0 and if the index is the same as the last snapshot's index value in hierarchyArr
+ const isCurrIndex = index === currLocation.index;
+
+ return (
+
+ );
+ },
+ );
+
+ // Function sends message to background.js which sends message to the content script
+ const toggleRecord = (): void => {
+ port.postMessage({
+ action: 'toggleRecord',
+ tabId: currentTab,
+ });
+ setRecordingActions(!recordingActions); // Record button's icon is being togggled on click
+ };
+
+ type routes = {
+ [route: string]: [];
+ };
+
+ const routes: {} = {}; // Logic to create the route description components begin
+
+ for (let i = 0; i < actionsArr.length; i += 1) {
+ // iterate through our actionsArr
+ if (!routes.hasOwnProperty(actionsArr[i].props.routePath)) {
+ routes[actionsArr[i].props.routePath] = [actionsArr[i]]; // if 'routes' doesn't have a property key that is the same as the current component at index[i] routePath we create an array with the first element being the component at index [i].
+ } else {
+ routes[actionsArr[i].props.routePath].push(actionsArr[i]); // If it does exist, we push the component at index [i] to the apporpriate routes[routePath]
+ }
+ }
+
+ // the conditional logic below will cause ActionContainer.test.tsx to fail as it cannot find the Empty button
+ // UNLESS actionView={true} is passed into in the beforeEach() call in ActionContainer.test.tsx
+ return (
+
+
+
{
+ toggleRecord();
+ setRecordingActions(!recordingActions);
+ }}
+ />
+
+
+ {
+ dispatch(emptySnapshots()); // set slider back to zero, visually
+ dispatch(changeSlider(0));
+ }}
+ type='button'
+ >
+ Clear
+
+
+
+ {dropdownSelection === 'Providers / Consumers' && (
+
+ )}
+ {dropdownSelection === 'Time Jump' &&
+ Object.keys(routes).map((route, i) => (
+
+ ))}
+ {/* Add ref for scrolling */}
+
+
+
+
+ );
+}
+
+export default ActionContainer;
diff --git a/src/app/containers/ButtonsContainer.jsx b/src/app/containers/ButtonsContainer.jsx
deleted file mode 100644
index aad24b389..000000000
--- a/src/app/containers/ButtonsContainer.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import React from 'react';
-
-import { importSnapshots, toggleMode } from '../actions/actions';
-import { useStoreContext } from '../store';
-
-function exportHandler(snapshots) {
- // create invisible download anchor link
- const fileDownload = document.createElement('a');
-
- // set file in anchor link
- fileDownload.href = URL.createObjectURL(
- new Blob([JSON.stringify(snapshots)], { type: 'application/json' }),
- );
-
- // set anchor as file download and click it
- fileDownload.setAttribute('download', 'snapshot.json');
- fileDownload.click();
-
- // remove file url
- URL.revokeObjectURL(fileDownload.href);
-}
-
-function importHandler(dispatch) {
- const fileUpload = document.createElement('input');
- fileUpload.setAttribute('type', 'file');
-
- fileUpload.onchange = event => {
- const reader = new FileReader();
- reader.onload = () => dispatch(importSnapshots(JSON.parse(reader.result)));
- reader.readAsText(event.target.files[0]);
- };
-
- fileUpload.click();
-}
-
-function ButtonsContainer() {
- const [{ tabs, currentTab }, dispatch] = useStoreContext();
- const {
- snapshots,
- mode: { paused, locked, persist },
- } = tabs[currentTab];
-
- return (
-
- dispatch(toggleMode('paused'))}>
- {paused ? 'Resume' : 'Pause'}
-
- dispatch(toggleMode('locked'))}>
- {locked ? 'Unlock' : 'Lock'}
-
- dispatch(toggleMode('persist'))}
- >
- {persist ? 'Unpersist' : 'Persist'}
-
- exportHandler(snapshots)}>
- Export
-
- importHandler(dispatch)}>
- Import
-
-
- );
-}
-
-export default ButtonsContainer;
diff --git a/src/app/containers/ButtonsContainer.tsx b/src/app/containers/ButtonsContainer.tsx
new file mode 100644
index 000000000..b74edeba4
--- /dev/null
+++ b/src/app/containers/ButtonsContainer.tsx
@@ -0,0 +1,117 @@
+import * as React from 'react';
+import { Button } from '@mui/material';
+import Tutorial from '../components/Buttons/Tutorial';
+import { toggleMode, importSnapshots, startReconnect } from '../slices/mainSlice';
+import { useDispatch, useSelector } from 'react-redux';
+import StatusDot from '../components/Buttons/StatusDot';
+import { MainState, RootState } from '../FrontendTypes';
+import { Lock, Unlock, Download, Upload, RefreshCw } from 'lucide-react';
+import { toast } from 'react-hot-toast';
+
+function exportHandler(snapshots: []): void {
+ const fileDownload: HTMLAnchorElement = document.createElement('a');
+ fileDownload.href = URL.createObjectURL(
+ new Blob([JSON.stringify(snapshots)], { type: 'application/json' }),
+ );
+ fileDownload.setAttribute('download', 'snapshot.json');
+ fileDownload.click();
+ URL.revokeObjectURL(fileDownload.href);
+}
+
+function importHandler(dispatch: (a: unknown) => void): void {
+ const fileUpload = document.createElement('input');
+ fileUpload.setAttribute('type', 'file');
+
+ fileUpload.onchange = (e: Event) => {
+ const reader = new FileReader();
+ const eventFiles = e.target as HTMLInputElement;
+
+ if (eventFiles) {
+ reader.readAsText(eventFiles.files[0]);
+ }
+
+ reader.onload = () => {
+ const test = reader.result.toString();
+ return dispatch(importSnapshots(JSON.parse(test)));
+ };
+ };
+
+ fileUpload.click();
+}
+
+function ButtonsContainer(): JSX.Element {
+ const dispatch = useDispatch();
+ const { currentTab, tabs, currentTabInApp, connectionStatus }: MainState = useSelector(
+ (state: RootState) => state.main,
+ );
+
+ //@ts-ignore
+ const {
+ //@ts-ignore
+ mode: { paused },
+ } = tabs[currentTab];
+
+ const handleReconnect = () => {
+ dispatch(startReconnect());
+ toast.success('Successfully reconnected', {
+ duration: 2000,
+ position: 'top-right',
+ style: {
+ background: 'var(--bg-primary)',
+ color: 'var(--text-primary)',
+ },
+ iconTheme: {
+ primary: 'var(--color-primary)',
+ secondary: 'var(--text-primary)',
+ },
+ });
+ };
+
+ return (
+
+
+ dispatch(toggleMode('paused'))}
+ >
+ {paused ? (
+
+ ) : (
+
+ )}
+ {paused ? 'Locked' : 'Unlocked'}
+
+ exportHandler(tabs[currentTab])}
+ >
+
+ Download
+
+ importHandler(dispatch)}>
+
+ Upload
+
+
+
+
+
+ }
+ >
+
+ Reconnect
+
+
+
+ );
+}
+
+export default ButtonsContainer;
diff --git a/src/app/containers/ErrorContainer.tsx b/src/app/containers/ErrorContainer.tsx
new file mode 100644
index 000000000..61c22378b
--- /dev/null
+++ b/src/app/containers/ErrorContainer.tsx
@@ -0,0 +1,156 @@
+import React, { useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { launchContentScript, setTab } from '../slices/mainSlice';
+import { MainState, RootState, ErrorContainerProps } from '../FrontendTypes';
+import { RefreshCw, Github, PlayCircle } from 'lucide-react';
+
+function ErrorContainer(props: ErrorContainerProps): JSX.Element {
+ const dispatch = useDispatch();
+ const { tabs, currentTitle, currentTab }: MainState = useSelector(
+ (state: RootState) => state.main,
+ );
+
+ // Helper function to check if a URL is localhost
+ const isLocalhost = (url: string): boolean => {
+ return url.startsWith('http://localhost:') || url.startsWith('https://localhost:');
+ };
+
+ // Add effect to initialize currentTab if not set
+ useEffect(() => {
+ const initializeCurrentTab = async () => {
+ if (!currentTab) {
+ try {
+ // Query specifically for localhost tabs first
+ const tabs = await chrome.tabs.query({ currentWindow: true });
+ const localhostTab = tabs.find(tab => tab.url && isLocalhost(tab.url));
+
+ if (localhostTab?.id) {
+ dispatch(setTab(localhostTab.id));
+ } else {
+ // Fallback to active tab if no localhost found
+ const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
+ if (activeTab?.id) {
+ dispatch(setTab(activeTab.id));
+ }
+ }
+ } catch (error) {
+ console.error('Error getting tab:', error);
+ }
+ }
+ };
+
+ initializeCurrentTab();
+ }, [currentTab, dispatch]);
+
+ // function that launches the main app and refreshes the page
+ async function launch(): Promise {
+ try {
+ // If no current tab, try to find localhost tab first
+ if (!currentTab) {
+ const tabs = await chrome.tabs.query({ currentWindow: true });
+ const localhostTab = tabs.find(tab => tab.url && isLocalhost(tab.url));
+
+ if (localhostTab?.id) {
+ dispatch(setTab(localhostTab.id));
+ await initializeLaunch(localhostTab.id);
+ } else {
+ console.warn('No localhost tab found');
+ }
+ return;
+ }
+
+ // Verify current tab is still localhost
+ const tab = await chrome.tabs.get(currentTab);
+ if (!tab.url || !isLocalhost(tab.url)) {
+ // Try to find a localhost tab
+ const tabs = await chrome.tabs.query({ currentWindow: true });
+ const localhostTab = tabs.find(tab => tab.url && isLocalhost(tab.url));
+
+ if (localhostTab?.id) {
+ dispatch(setTab(localhostTab.id));
+ await initializeLaunch(localhostTab.id);
+ } else {
+ console.warn('No localhost tab found');
+ }
+ return;
+ }
+
+ await initializeLaunch(currentTab);
+ } catch (error) {
+ console.error('Error during launch:', error);
+ }
+ }
+
+ async function initializeLaunch(tabId: number): Promise {
+ const defaultPayload = {
+ status: {
+ contentScriptLaunched: false,
+ reactDevToolsInstalled: false,
+ targetPageisaReactApp: false,
+ },
+ };
+
+ dispatch(launchContentScript(defaultPayload));
+
+ // Allow the dispatch to complete before refreshing
+ setTimeout(() => {
+ chrome.tabs.reload(tabId);
+ }, 100);
+ }
+
+ return (
+
+
+
+
+
+
+
+ Welcome to Reactime
+
+
+
+ To ensure Reactime works correctly with your React application, please either refresh
+ your development page or click the launch button below. This allows Reactime to properly
+ connect with your app and start monitoring state changes.
+
+
+ Important: Reactime requires React Developer Tools to be installed and will only track state
+ changes on localhost development servers. If you haven't already, please{' '}
+
+ install React Developer Tools
+ {' '}
+ first.
+
+
+
+
+ Note: Reactime only works with React applications and by default only launches on URLs
+ starting with localhost.
+
+
+
+
+ Launch Reactime
+
+
+
+
+ Visit Reactime Github for more information
+
+
+
+ );
+}
+
+export default ErrorContainer;
\ No newline at end of file
diff --git a/src/app/containers/HeadContainer.jsx b/src/app/containers/HeadContainer.jsx
deleted file mode 100644
index c9082a99f..000000000
--- a/src/app/containers/HeadContainer.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import SwitchAppDropdown from '../components/SwitchApp';
-
-function HeadContainer() {
- return (
-
-
-
- );
-}
-
-export default HeadContainer;
diff --git a/src/app/containers/MainContainer.jsx b/src/app/containers/MainContainer.jsx
deleted file mode 100644
index 7b81b8eff..000000000
--- a/src/app/containers/MainContainer.jsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import React, { useEffect } from 'react';
-import HeadContainer from './HeadContainer';
-import ActionContainer from './ActionContainer';
-import StateContainer from './StateContainer';
-import TravelContainer from './TravelContainer';
-import ButtonsContainer from './ButtonsContainer';
-
-import {
- addNewSnapshots, initialConnect, setPort, setTab, deleteTab,
-} from '../actions/actions';
-import { useStoreContext } from '../store';
-
-function MainContainer() {
- const [store, dispatch] = useStoreContext();
- const { tabs, currentTab, port: currentPort } = store;
-
- // add event listeners to background script
- useEffect(() => {
- // only open port once
- if (currentPort) return;
- // open connection with background script
- const port = chrome.runtime.connect();
-
- // listen for a message containing snapshots from the background script
- port.onMessage.addListener(message => {
- const { action, payload, sourceTab } = message;
- switch (action) {
- case 'deleteTab': {
- dispatch(deleteTab(payload));
- break;
- }
-
- case 'sendSnapshots': {
- dispatch(setTab(sourceTab));
- // set state with the information received from the background script
- dispatch(addNewSnapshots(payload));
- break;
- }
- case 'initialConnectSnapshots': {
- dispatch(initialConnect(payload));
- break;
- }
- default:
- }
- });
-
- port.onDisconnect.addListener(() => {
- // disconnecting
- });
-
- // assign port to state so it could be used by other components
- dispatch(setPort(port));
- });
-
- if (!tabs[currentTab]) {
- return (
-
- );
- }
- const {
- viewIndex, sliderIndex, snapshots, hierarchy,
- } = tabs[currentTab];
-
- // if viewIndex is -1, then use the sliderIndex instead
- const snapshotView = viewIndex === -1 ? snapshots[sliderIndex] : snapshots[viewIndex];
- return (
-
-
-
-
- {snapshots.length ?
: null}
-
-
-
-
- );
-}
-
-export default MainContainer;
diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx
new file mode 100644
index 000000000..01028fd5d
--- /dev/null
+++ b/src/app/containers/MainContainer.tsx
@@ -0,0 +1,207 @@
+/* eslint-disable max-len */
+import React, { useState } from 'react';
+import ActionContainer from './ActionContainer';
+import TravelContainer from './TravelContainer';
+import ButtonsContainer from './ButtonsContainer';
+import ErrorContainer from './ErrorContainer';
+import StateContainer from './StateContainer';
+import {
+ addNewSnapshots,
+ initialConnect,
+ setPort,
+ setTab,
+ deleteTab,
+ noDev,
+ aReactApp,
+ setCurrentLocation,
+ disconnected,
+ endConnect,
+} from '../slices/mainSlice';
+import { useDispatch, useSelector } from 'react-redux';
+import { MainState, RootState } from '../FrontendTypes';
+
+/*
+ This is the main container where everything in our application is rendered
+*/
+
+function MainContainer(): JSX.Element {
+ const dispatch = useDispatch();
+
+ const { currentTab, tabs, port }: MainState = useSelector((state: RootState) => state.main);
+
+ // Function handles when Reactime unexpectedly disconnects
+ const handleDisconnect = (msg): void => {
+ if (msg === 'portDisconnect') dispatch(disconnected());
+ };
+
+ // Function to listen for a message containing snapshots from the /extension/build/background.js service worker
+ const messageListener = ({
+ action,
+ payload,
+ sourceTab,
+ }: {
+ action: string;
+ payload: Record;
+ sourceTab: number;
+ }) => {
+ let maxTab: number;
+
+ // Add validation check
+ if (!sourceTab && action !== 'keepAlive') {
+ // Ensure payload exists and is an object
+ if (payload && typeof payload === 'object') {
+ const tabsArray = Object.keys(payload);
+ const numTabsArray = tabsArray.map((tab) => Number(tab));
+ maxTab = numTabsArray.length > 0 ? Math.max(...numTabsArray) : 0;
+ } else {
+ console.warn('Invalid payload received:', payload);
+ maxTab = 0;
+ }
+ }
+
+ switch (action) {
+ case 'deleteTab': {
+ dispatch(deleteTab(payload));
+ break;
+ }
+ case 'devTools': {
+ dispatch(noDev(payload));
+ break;
+ }
+ case 'aReactApp': {
+ dispatch(aReactApp(payload));
+ break;
+ }
+ case 'changeTab': {
+ dispatch(setTab(payload));
+ break;
+ }
+ case 'sendSnapshots': {
+ dispatch(setTab(payload));
+ dispatch(addNewSnapshots(payload));
+ break;
+ }
+ case 'initialConnectSnapshots': {
+ dispatch(initialConnect(payload));
+ break;
+ }
+ case 'setCurrentLocation': {
+ dispatch(setCurrentLocation(payload));
+ break;
+ }
+ default:
+ }
+ };
+
+ async function awaitPortConnection() {
+ if (port) return; // only open port once so if it exists, do not run useEffect again
+
+ // Connect ot port and assign evaluated result (obj) to currentPort
+ const currentPort = await chrome.runtime.connect({ name: 'panel' });
+
+ // If messageListener exists on currentPort, remove it
+ while (currentPort.onMessage.hasListener(messageListener))
+ currentPort.onMessage.removeListener(messageListener);
+
+ // Add messageListener to the currentPort
+ currentPort.onMessage.addListener(messageListener);
+
+ // If handleDisconnect exists on chrome.runtime, remove it
+ while (chrome.runtime.onMessage.hasListener(handleDisconnect))
+ chrome.runtime.onMessage.removeListener(handleDisconnect);
+
+ // add handleDisconnect to chrome.runtime
+ chrome.runtime.onMessage.addListener(handleDisconnect);
+
+ // assign port to state so it could be used by other components
+ dispatch(setPort(currentPort));
+
+ dispatch(endConnect());
+ }
+ awaitPortConnection();
+
+ if (
+ !tabs[currentTab] ||
+ //@ts-ignore
+ !tabs[currentTab].status.reactDevToolsInstalled ||
+ //@ts-ignore
+ !tabs[currentTab].status.targetPageisaReactApp
+ ) {
+ // @ts-ignore
+ return ;
+ }
+
+ const { axSnapshots, currLocation, viewIndex, sliderIndex, snapshots, hierarchy, webMetrics } =
+ tabs[currentTab]; // we destructure the currentTab object which is passed in from background.js
+ //@ts-ignore
+ const snapshotView = viewIndex === -1 ? snapshots[sliderIndex] : snapshots[viewIndex]; // if viewIndex is -1, then use the sliderIndex instead
+ // cleaning hierarchy and snapshotView from stateless data
+ const statelessCleaning = (obj: {
+ name?: string;
+ componentData?: object;
+ state?: string | any;
+ stateSnapshot?: object;
+ children?: any[];
+ }) => {
+ const newObj = { ...obj };
+ if (newObj.name === 'nameless') {
+ delete newObj.name;
+ }
+ if (newObj.componentData) {
+ delete newObj.componentData;
+ }
+ if (newObj.state === 'stateless') {
+ delete newObj.state;
+ }
+ if (newObj.stateSnapshot) {
+ newObj.stateSnapshot = statelessCleaning(obj.stateSnapshot);
+ }
+ if (newObj.children) {
+ newObj.children = [];
+ if (obj.children.length > 0) {
+ obj.children.forEach((element: { state?: object | string; children?: [] }) => {
+ if (element.state !== 'stateless' || element.children.length > 0) {
+ const clean = statelessCleaning(element);
+ newObj.children.push(clean);
+ }
+ });
+ }
+ }
+ return newObj;
+ };
+ const snapshotDisplay = statelessCleaning(snapshotView);
+ const hierarchyDisplay = statelessCleaning(hierarchy);
+
+ return (
+
+
+
+ {/* @ts-ignore */}
+ {snapshots.length ? (
+
+
+
+ ) : null}
+
+ {/* @ts-ignore */}
+
+
+
+
+
+ );
+}
+
+export default MainContainer;
diff --git a/src/app/containers/ProvConContainer.tsx b/src/app/containers/ProvConContainer.tsx
new file mode 100644
index 000000000..c2e6b7044
--- /dev/null
+++ b/src/app/containers/ProvConContainer.tsx
@@ -0,0 +1,196 @@
+import React from 'react';
+import { ProvConContainerProps, FilteredNode } from '../FrontendTypes';
+import { JSONTree } from 'react-json-tree';
+
+const ProvConContainer = (props: ProvConContainerProps): JSX.Element => {
+ const jsonTheme = {
+ scheme: 'custom',
+ base00: 'transparent',
+ base0B: '#14b8a6', // dark navy for strings
+ base0D: '#60a5fa', // Keys
+ base09: '#f59e0b', // Numbers
+ base0C: '#EF4444', // Null values
+ };
+
+ //deconstruct props
+ const { currentSnapshot } = props;
+
+ //parse through node
+ const keepContextAndProviderNodes = (node) => {
+ if (!node) return null;
+
+ // Check if this node should be kept
+ const hasContext =
+ node?.componentData?.context && Object.keys(node.componentData.context).length > 0;
+ const isProvider = node?.name && node.name.endsWith('Provider');
+ const shouldKeepNode = hasContext || isProvider;
+
+ // Process children first
+ let processedChildren = [];
+ if (node.children) {
+ processedChildren = node.children
+ .map((child) => keepContextAndProviderNodes(child))
+ .filter(Boolean); // Remove null results
+ }
+
+ // If this node should be kept or has kept children, return it
+ if (shouldKeepNode || processedChildren.length > 0) {
+ return {
+ ...node,
+ children: processedChildren,
+ };
+ }
+
+ // If neither the node should be kept nor it has kept children, filter it out
+ return null;
+ };
+ const contextProvidersOnly = keepContextAndProviderNodes(currentSnapshot);
+
+ const filterComponentProperties = (node: any): FilteredNode | null => {
+ if (!node) return null;
+
+ // Helper function to check if an object is empty (including nested objects)
+ const isEmptyObject = (obj: any): boolean => {
+ if (!obj) return true;
+ if (Array.isArray(obj)) return obj.length === 0;
+ if (typeof obj !== 'object') return false;
+
+ // Check each property recursively
+ for (const key in obj) {
+ const value = obj[key];
+ if (typeof value === 'object') {
+ if (!isEmptyObject(value)) return false;
+ } else if (value !== undefined && value !== null) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ // Create a new object for the filtered node
+ const filteredNode: FilteredNode = {};
+
+ // Flatten root level props if they exist
+ if (node.props && !isEmptyObject(node.props)) {
+ Object.entries(node.props).forEach(([key, value]) => {
+ if (!isEmptyObject(value)) {
+ filteredNode[`${key}`] = value;
+ }
+ });
+ }
+
+ // Flatten componentData properties into root level if they exist
+ if (node.componentData.context && !isEmptyObject(node.componentData.context)) {
+ // Add context directly if it exists
+ Object.entries(node.componentData.context).forEach(([key, value]) => {
+ if (!isEmptyObject(value)) {
+ filteredNode[`${key}`] = value;
+ }
+ });
+
+ // Flatten componentData.props if they exist
+ if (node.componentData.props && !isEmptyObject(node.componentData.props)) {
+ Object.entries(node.componentData.props).forEach(([key, value]) => {
+ if (!isEmptyObject(value)) {
+ filteredNode[`${key}`] = value;
+ }
+ });
+ }
+
+ // Flatten componentData.hooksState if it exists
+ if (node.componentData.hooksState && !isEmptyObject(node.componentData.hooksState)) {
+ Object.entries(node.componentData.hooksState).forEach(([key, value]) => {
+ if (!isEmptyObject(value)) {
+ filteredNode[`${key}`] = value;
+ }
+ });
+ }
+ }
+
+ // Flatten root level hooksState if it exists
+ if (node.componentData.hooksState && !isEmptyObject(node.componentData.hooksState)) {
+ filteredNode['State'] = node.componentData.hooksState;
+ }
+
+ // Process children and add them using the node's name as the key
+ if (node.hasOwnProperty('children') && Array.isArray(node.children)) {
+ for (const child of node.children) {
+ const filteredChild = filterComponentProperties(child);
+ if (filteredChild && !isEmptyObject(filteredChild) && child.name) {
+ filteredNode[child.name] = filteredChild;
+ }
+ }
+ }
+
+ // Only return the node if it has at least one non-empty property
+ return isEmptyObject(filteredNode) ? null : filteredNode;
+ };
+
+ const filteredProviders = filterComponentProperties(contextProvidersOnly);
+
+ const parseStringifiedValues = (obj) => {
+ if (!obj || typeof obj !== 'object') return obj;
+
+ const parsed = { ...obj };
+ for (const key in parsed) {
+ if (typeof parsed[key] === 'string') {
+ try {
+ // Check if the string looks like JSON
+ if (parsed[key].startsWith('{') || parsed[key].startsWith('[')) {
+ const parsedValue = JSON.parse(parsed[key]);
+ parsed[key] = parsedValue;
+ }
+ } catch (e) {
+ // If parsing fails, keep original value
+ continue;
+ }
+ } else if (typeof parsed[key] === 'object') {
+ parsed[key] = parseStringifiedValues(parsed[key]);
+ }
+ }
+ return parsed;
+ };
+
+ const renderSection = (title, content, isReducer = false) => {
+ if (
+ !content ||
+ (Array.isArray(content) && content.length === 0) ||
+ Object.keys(content).length === 0
+ ) {
+ return null;
+ }
+
+ // Parse any stringified JSON before displaying
+ const parsedContent = parseStringifiedValues(content);
+
+ return (
+
+
{title}
+
+ true}
+ shouldExpandNodeInitially={() => true}
+ />
+
+
+ );
+ };
+
+ return (
+
+
Providers / Consumers
+ {filteredProviders ? (
+
{renderSection(null, filteredProviders)}
+ ) : (
+
+
No providers or consumers found in the current component tree.
+
+ )}
+
+ );
+};
+
+export default ProvConContainer;
diff --git a/src/app/containers/StateContainer.jsx b/src/app/containers/StateContainer.jsx
deleted file mode 100644
index 84183a649..000000000
--- a/src/app/containers/StateContainer.jsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import {
- MemoryRouter as Router, Route, NavLink, Switch,
-} from 'react-router-dom';
-import StateRoute from '../components/StateRoute';
-import DiffRoute from '../components/DiffRoute';
-
-
-// eslint-disable-next-line react/prop-types
-const StateContainer = ({ snapshot, hierarchy }) => {
- const [Text, setText] = useState('State');
- return (
-
-
-
-
- {Text}
-
-
-
- State
-
-
- Diff
-
-
-
-
- { setText('Diff'); return ; }} />
- { setText('State'); return ; }} />
-
-
-
- );
-};
-
-StateContainer.propTypes = {
- snapshot: PropTypes.shape({
- state: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
- children: PropTypes.arrayOf(PropTypes.object),
- }).isRequired,
-};
-
-export default StateContainer;
diff --git a/src/app/containers/StateContainer.tsx b/src/app/containers/StateContainer.tsx
new file mode 100644
index 000000000..06f1a58b7
--- /dev/null
+++ b/src/app/containers/StateContainer.tsx
@@ -0,0 +1,49 @@
+// @ts-nocheck
+import React, { useState } from 'react';
+/* that keeps the history of your “URL” in memory (does not read/write to the address bar)
+ Useful in tests and non-browser environments like React Native.
+*/
+import { MemoryRouter as Router, Route, NavLink, Routes, Outlet } from 'react-router-dom';
+import StateRoute from '../components/StateRoute/StateRoute';
+import DiffRoute from '../components/DiffRoute/DiffRoute';
+import { StateContainerProps } from '../FrontendTypes';
+import { Outlet } from 'react-router';
+
+// eslint-disable-next-line react/prop-types
+const StateContainer = (props: StateContainerProps): JSX.Element => {
+ const {
+ snapshot, // from 'tabs[currentTab]' object in 'MainContainer'
+ hierarchy, // from 'tabs[currentTab]' object in 'MainContainer'
+ snapshots, // from 'tabs[currentTab].snapshotDisplay' object in 'MainContainer'
+ viewIndex, // from 'tabs[currentTab]' object in 'MainContainer'
+ webMetrics, // from 'tabs[currentTab]' object in 'MainContainer'
+ currLocation, // from 'tabs[currentTab]' object in 'MainContainer'
+ axSnapshots, // from 'tabs[currentTab]' object in 'MainContainer'
+ } = props;
+
+ return (
+ <>
+
+ >
+ );
+};
+
+export default StateContainer;
diff --git a/src/app/containers/TravelContainer.jsx b/src/app/containers/TravelContainer.jsx
deleted file mode 100644
index 13ae9a402..000000000
--- a/src/app/containers/TravelContainer.jsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import MainSlider from '../components/MainSlider';
-import Dropdown from '../components/Dropdown';
-import {
- playForward, pause, startPlaying, moveForward, moveBackward,
-} from '../actions/actions';
-import { useStoreContext } from '../store';
-
-const speeds = [
- { value: 2000, label: '0.5x' },
- { value: 1000, label: '1.0x' },
- { value: 500, label: '2.0x' },
-];
-
-// start slider movement
-function play(speed, playing, dispatch, snapshotsLength, sliderIndex) {
- if (playing) {
- dispatch(pause());
- } else {
- let currentIndex = sliderIndex;
- const intervalId = setInterval(() => {
- if (currentIndex < snapshotsLength - 1) {
- dispatch(playForward());
- currentIndex += 1;
- } else {
- dispatch(pause());
- }
- }, speed);
- dispatch(startPlaying(intervalId));
- }
-}
-
-function TravelContainer({ snapshotsLength }) {
- const [selectedSpeed, setSpeed] = useState(speeds[1]);
- const [{ tabs, currentTab }, dispatch] = useStoreContext();
- const { sliderIndex, playing } = tabs[currentTab];
-
- return (
-
- play(selectedSpeed.value, playing, dispatch, snapshotsLength, sliderIndex)}
- >
- {playing ? 'Pause' : 'Play'}
-
-
- dispatch(moveBackward())} type="button">
- {'<'}
-
- dispatch(moveForward())} type="button">
- {'>'}
-
-
-
- );
-}
-
-TravelContainer.propTypes = {
- snapshotsLength: PropTypes.number.isRequired,
-};
-export default TravelContainer;
diff --git a/src/app/containers/TravelContainer.tsx b/src/app/containers/TravelContainer.tsx
new file mode 100644
index 000000000..428a96918
--- /dev/null
+++ b/src/app/containers/TravelContainer.tsx
@@ -0,0 +1,81 @@
+/* eslint-disable max-len */
+import React, { useState } from 'react';
+import Dropdown from '../components/TimeTravel/Dropdown';
+import { playForward, pause, startPlaying, resetSlider, changeSlider } from '../slices/mainSlice';
+import { useDispatch, useSelector } from 'react-redux';
+import { MainState, RootState, TravelContainerProps } from '../FrontendTypes';
+import { Play, Pause } from 'lucide-react';
+
+/*
+ This container renders the time-travel play button, seek bar, playback controls, and the playback speed dropdown, located towards the bottom of the application, above the locked, download, upload, and tutorial buttons
+*/
+
+// This object is used to create the options in the playback speed 'Dropdown' component
+const speeds: {
+ value: number;
+ label: string;
+}[] = [
+ { value: 2000, label: '0.5x' },
+ { value: 1000, label: '1.0x' },
+ { value: 500, label: '2.0x' },
+];
+//NOTE HERE REMOVED THE DISPATCH FUNCTION IN THE TYPE SCRIPT:
+//USING THE BUILT IN DISPATCH
+function play( // function that will start/pause slider movement
+ speed: number,
+ playing: boolean,
+ dispatch: (a: any) => void,
+ snapshotsLength: number,
+ sliderIndex: number,
+): void {
+ if (playing) {
+ // if already playing, clicking the button will pause the slider
+ dispatch(pause());
+ } else {
+ let currentIndex = sliderIndex; // the 'currentIndex' will be wherever the 'sliderIndex' is
+ if (currentIndex === snapshotsLength - 1) {
+ // if we reach the last snapshot, reset the slider and the currentIndex
+ dispatch(resetSlider());
+ currentIndex = 0;
+ }
+ const intervalId: NodeJS.Timeout = setInterval(() => {
+ // sets interval period to wait before advancing 'currentIndex'/slider
+ if (currentIndex < snapshotsLength - 1) {
+ // as long as we're not the last snapshot, increment slider up through our dispatch and increment index
+ dispatch(playForward(true));
+ currentIndex += 1;
+ dispatch(changeSlider(currentIndex));
+ } else {
+ dispatch(pause()); // pause the slider when we reach the end
+ }
+ }, speed);
+ dispatch(startPlaying(intervalId)); // set's tabs[currentTab].playing to true and tabs[currentTab].intervalId to line 45's 'setInterval()'
+ }
+}
+
+function TravelContainer(props: TravelContainerProps): JSX.Element {
+ const { snapshotsLength } = props;
+ const [selectedSpeed, setSpeed] = useState(speeds[1]); // create a new local state selectedSpeed and set it to the second element of the 'speeds' array (1.0x speed)
+
+ const dispatch = useDispatch();
+ const { tabs, currentTab }: MainState = useSelector((state: RootState) => state.main);
+ const { sliderIndex, playing } = tabs[currentTab];
+
+ return (
+
+
play(selectedSpeed.value, playing, dispatch, snapshotsLength, sliderIndex)}
+ >
+
+ {playing ?
:
}
+
{playing ? 'Pause' : 'Play'}
+
+
+
+
+ );
+}
+
+export default TravelContainer;
diff --git a/src/app/index.js b/src/app/index.js
deleted file mode 100644
index 93d63cc4f..000000000
--- a/src/app/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/* eslint-disable react/jsx-filename-extension */
-import React from 'react';
-import ReactDOM from 'react-dom';
-import App from './components/App';
-import './styles/main.scss';
-
-ReactDOM.render( , document.getElementById('root'));
diff --git a/src/app/index.tsx b/src/app/index.tsx
new file mode 100644
index 000000000..b24b57132
--- /dev/null
+++ b/src/app/index.tsx
@@ -0,0 +1,14 @@
+/* eslint-disable react/jsx-filename-extension */
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+import App from './App';
+import './styles/main.scss';
+import { store } from './store'; //imported RTK Store
+import { Provider } from 'react-redux'; //imported Provider
+
+const root = createRoot(document.getElementById('root'));
+root.render(
+
+
+ ,
+);
diff --git a/src/app/module.d.ts b/src/app/module.d.ts
new file mode 100644
index 000000000..8b5ed8d9c
--- /dev/null
+++ b/src/app/module.d.ts
@@ -0,0 +1,14 @@
+declare module 'react';
+declare module 'react-dom';
+declare module 'react-select';
+declare module 'rc-slider';
+declare module 'rc-tooltip';
+declare module 'd3';
+declare module 'react-spinners';
+declare module 'immer';
+declare module 'jsondiffpatch';
+declare module 'html-react-parser';
+declare module 'react-json-tree';
+declare module 'react-router-dom';
+declare module 'react-apexcharts';
+declare module 'react-hover';
diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js
deleted file mode 100644
index b9bccb939..000000000
--- a/src/app/reducers/mainReducer.js
+++ /dev/null
@@ -1,177 +0,0 @@
-/* eslint-disable no-param-reassign */
-import produce from 'immer';
-import * as types from '../constants/actionTypes';
-
-export default (state, action) => produce(state, draft => {
- const { port, currentTab, tabs } = draft;
- const {
- snapshots, mode, intervalId, viewIndex, sliderIndex,
- } = tabs[currentTab] || {};
-
- switch (action.type) {
- case types.MOVE_BACKWARD: {
- if (snapshots.length > 0 && sliderIndex > 0) {
- const newIndex = sliderIndex - 1;
-
- port.postMessage({
- action: 'jumpToSnap',
- payload: snapshots[newIndex],
- index: newIndex,
- tabId: currentTab,
- });
- clearInterval(intervalId);
-
- tabs[currentTab].sliderIndex = newIndex;
- tabs[currentTab].playing = false;
- }
- break;
- }
- case types.MOVE_FORWARD: {
- if (sliderIndex < snapshots.length - 1) {
- const newIndex = sliderIndex + 1;
-
- port.postMessage({
- action: 'jumpToSnap',
- index: newIndex,
- payload: snapshots[newIndex],
- tabId: currentTab,
- });
-
- tabs[currentTab].sliderIndex = newIndex;
-
- // message is coming from the user
- if (!action.payload) {
- clearInterval(intervalId);
- tabs[currentTab].playing = false;
- }
- }
- break;
- }
- case types.CHANGE_VIEW: {
- // unselect view if same index was selected
- if (viewIndex === action.payload) tabs[currentTab].viewIndex = -1;
- else tabs[currentTab].viewIndex = action.payload;
- break;
- }
- case types.CHANGE_SLIDER: {
- port.postMessage({
- action: 'jumpToSnap',
- payload: snapshots[action.payload],
- index: action.payload,
- tabId: currentTab,
- });
- tabs[currentTab].sliderIndex = action.payload;
- break;
- }
- case types.EMPTY: {
- port.postMessage({ action: 'emptySnap', tabId: currentTab });
- tabs[currentTab].sliderIndex = 0;
- tabs[currentTab].viewIndex = -1;
- tabs[currentTab].playing = false;
- tabs[currentTab].snapshots.splice(1);
- // reset children in root node to reset graph
- if (tabs[currentTab].hierarchy) tabs[currentTab].hierarchy.children = [];
- // reassigning pointer to the appropriate node to branch off of
- tabs[currentTab].currLocation = tabs[currentTab].hierarchy;
- // reset index
- tabs[currentTab].index = 0;
- break;
- }
- case types.SET_PORT: {
- draft.port = action.payload;
- break;
- }
- case types.IMPORT: {
- port.postMessage({ action: 'import', payload: action.payload, tabId: currentTab });
- tabs[currentTab].snapshots = action.payload;
- break;
- }
- case types.TOGGLE_MODE: {
- mode[action.payload] = !mode[action.payload];
- const newMode = mode[action.payload];
- let actionText;
- switch (action.payload) {
- case 'paused':
- actionText = 'setPause';
- break;
- case 'locked':
- actionText = 'setLock';
- break;
- case 'persist':
- actionText = 'setPersist';
- break;
- default:
- break;
- }
- port.postMessage({ action: actionText, payload: newMode, tabId: currentTab });
- break;
- }
- case types.PAUSE: {
- clearInterval(intervalId);
- tabs[currentTab].playing = false;
- tabs[currentTab].intervalId = null;
- break;
- }
- case types.PLAY: {
- tabs[currentTab].playing = true;
- tabs[currentTab].intervalId = action.payload;
- break;
- }
- case types.INITIAL_CONNECT: {
- const { payload } = action;
- Object.keys(payload).forEach(tab => {
- // check if tab exists in memory
- // add new tab
- tabs[tab] = {
- ...payload[tab],
- sliderIndex: 0,
- viewIndex: -1,
- intervalId: null,
- playing: false,
- };
- });
-
- // only set first tab if current tab is non existent
- const firstTab = parseInt(Object.keys(payload)[0], 10);
- if (currentTab === undefined || currentTab === null) draft.currentTab = firstTab;
- break;
- }
- case types.NEW_SNAPSHOTS: {
- const { payload } = action;
-
- Object.keys(tabs).forEach(tab => {
- if (!payload[tab]) {
- delete tabs[tab];
- } else {
- const { snapshots: newSnaps } = payload[tab];
- tabs[tab] = {
- ...tabs[tab],
- ...payload[tab],
- sliderIndex: newSnaps.length - 1,
- };
- }
- });
-
- // only set first tab if current tab is non existent
- const firstTab = parseInt(Object.keys(payload)[0], 10);
- if (currentTab === undefined || currentTab === null) draft.currentTab = firstTab;
- break;
- }
- case types.SET_TAB: {
- draft.currentTab = action.payload;
- break;
- }
- case types.DELETE_TAB: {
- delete draft.tabs[action.payload];
- if (draft.currentTab === action.payload) {
- // if the deleted tab was set to currentTab, replace currentTab with
- // the first tabId within tabs obj
- const newCurrentTab = parseInt(Object.keys(draft.tabs)[0], 10);
- draft.currentTab = newCurrentTab;
- }
- break;
- }
- default:
- throw new Error(`nonexistent action: ${action.type}`);
- }
-});
diff --git a/src/app/slices/mainSlice.ts b/src/app/slices/mainSlice.ts
new file mode 100644
index 000000000..740e9b718
--- /dev/null
+++ b/src/app/slices/mainSlice.ts
@@ -0,0 +1,447 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { InitialState } from '../FrontendTypes';
+import _ from 'lodash';
+import Action from '../components/Actions/Action';
+
+const initialState: InitialState = {
+ // we initialize what our initialState is here
+ port: null,
+ currentTab: null,
+ currentTitle: 'No Target',
+ tabs: {},
+ currentTabInApp: null,
+ connectionStatus: true,
+ connectRequested: true,
+};
+
+const findName = (index: number, obj) => {
+ // eslint-disable-next-line eqeqeq
+ if (obj && obj.index == index) {
+ return obj.name;
+ }
+ const objChildArray = [];
+ if (obj) {
+ // eslint-disable-next-line no-restricted-syntax
+ for (const objChild of obj.children) {
+ objChildArray.push(findName(index, objChild));
+ }
+ }
+ // eslint-disable-next-line no-restricted-syntax
+ for (const objChildName of objChildArray) {
+ if (objChildName) {
+ return objChildName;
+ }
+ }
+};
+
+export const mainSlice = createSlice({
+ name: 'main',
+ initialState,
+ reducers: {
+ emptySnapshots: (state) => {
+ const { tabs, currentTab, port } = state;
+
+ port.postMessage({ action: 'emptySnap', tabId: currentTab }); //communicate with background.js (service worker)
+ // properties associated with timetravel + seek bar
+ tabs[currentTab].sliderIndex = 0;
+ tabs[currentTab].viewIndex = 0;
+ tabs[currentTab].playing = false;
+
+ const currSnapshot = tabs[currentTab].snapshots[tabs[currentTab].currLocation.index]; // current snapshot
+ const currAxSnapshot = tabs[currentTab].axSnapshots[tabs[currentTab].currLocation.index]; // current accessibility tree snapshot
+
+ tabs[currentTab].hierarchy.stateSnapshot = { ...currSnapshot }; // resets hierarchy to current snapshot
+ tabs[currentTab].hierarchy.axSnapshot = { ...currAxSnapshot }; // resets hierarchy to current accessibility tree snapshot
+ tabs[currentTab].hierarchy.children = []; // resets hierarchy
+ tabs[currentTab].snapshots = [currSnapshot]; // resets snapshots to current snapshot
+ tabs[currentTab].axSnapshots = [currAxSnapshot]; // resets snapshots to current snapshot
+
+ // resets currLocation to current snapshot
+ tabs[currentTab].index = 1;
+ tabs[currentTab].currParent = 0;
+ tabs[currentTab].currBranch = 1;
+ tabs[currentTab].currLocation = tabs[currentTab].hierarchy; // reset currLocation
+ tabs[currentTab].seriesSavedStatus = false;
+ },
+
+ addNewSnapshots: (state, action) => {
+ const { tabs, currentTab } = state;
+
+ const { payload } = action;
+ Object.keys(tabs).forEach((tab) => {
+ if (!payload[tab]) delete tabs[tab];
+ else {
+ // maintain isExpanded prop from old stateSnapshot to preserve componentMap expansion
+ const persistIsExpanded = (newNode, oldNode) => {
+ newNode.isExpanded = oldNode ? oldNode.isExpanded : true;
+ if (newNode.children) {
+ newNode.children.forEach((child, i) => {
+ persistIsExpanded(child, oldNode?.children[i]);
+ });
+ }
+ };
+ persistIsExpanded(
+ payload[tab].currLocation.stateSnapshot,
+ tabs[tab].currLocation.stateSnapshot,
+ );
+
+ const { snapshots: newSnaps } = payload[tab];
+ tabs[tab] = {
+ ...payload[tab],
+ intervalId: tabs[tab].intervalId,
+ playing: tabs[tab].playing,
+ sliderIndex: tabs[tab].sliderIndex,
+ seriesSavedStatus: false,
+ };
+ }
+ });
+
+ // only set first tab if current tab is non existent
+ const firstTab = parseInt(Object.keys(payload)[0], 10);
+ if (currentTab === undefined || currentTab === null) state.currentTab = firstTab;
+ },
+
+ initialConnect: (state, action) => {
+ const { tabs, tab, currentTab } = state;
+ const { hierarchy, snapshots, mode, intervalId, viewIndex, sliderIndex } =
+ tabs[currentTab] || {};
+ const { payload } = action;
+
+ Object.keys(payload).forEach((tab) => {
+ // check if tab exists in memory
+ // add new tab
+ tabs[tab] = {
+ ...payload[tab],
+ sliderIndex: 0,
+ viewIndex: -1,
+ intervalId: null,
+ playing: false,
+ };
+ });
+
+ // only set first tab if current tab is non existent
+ const firstTab = parseInt(Object.keys(payload)[0], 10);
+ if (currentTab === undefined || currentTab === null) state.currentTab = firstTab;
+ },
+
+ setPort: (state, action) => {
+ state.port = action.payload;
+ },
+
+ setTab: (state, action) => {
+ const { tabs, currentTab } = state;
+ const { mode } = tabs[currentTab] || {};
+ if (!mode?.paused) {
+ if (typeof action.payload === 'number') {
+ state.currentTab = action.payload;
+ return;
+ } else if (typeof action.payload === 'object') {
+ state.currentTab = action.payload.tabId;
+ if (action.payload?.title) state.currentTitle = action.payload.title;
+ return;
+ }
+ }
+ },
+
+ deleteTab: (state, action) => {
+ delete state.tabs[action.payload];
+ },
+
+ noDev: (state, action) => {
+ const { payload } = action;
+ const { tabs, currentTab } = state;
+
+ if (tabs[currentTab]) {
+ const { reactDevToolsInstalled } = payload[currentTab].status;
+ // JR 12.20. 9.47pm this was not applying to state before
+ state.tabs[currentTab].status.reactDevToolsInstalled = reactDevToolsInstalled;
+ }
+ },
+
+ aReactApp: (state, action) => {
+ const { payload } = action;
+ const { tabs, currentTab } = state;
+
+ if (tabs[currentTab]) {
+ // JR 12.20. 9.47pm this was not applying to state before
+ state.tabs[currentTab].status.targetPageisaReactApp = true;
+ }
+ },
+
+ setCurrentLocation: (state, action) => {
+ const { tabs, currentTab } = state;
+ const { payload } = action;
+
+ const persistIsExpanded = (newNode, oldNode) => {
+ newNode.isExpanded = oldNode ? oldNode.isExpanded : true;
+ if (newNode.children) {
+ newNode.children.forEach((child, i) => {
+ persistIsExpanded(child, oldNode?.children[i]);
+ });
+ }
+ };
+ persistIsExpanded(
+ payload[currentTab].currLocation.stateSnapshot,
+ tabs[currentTab].currLocation.stateSnapshot,
+ );
+ tabs[currentTab].currLocation = payload[currentTab].currLocation;
+ },
+
+ changeView: (state, action) => {
+ const { tabs, currentTab } = state;
+ const { viewIndex } = tabs[currentTab] || {};
+
+ // unselect view if same index was selected
+ tabs[currentTab].viewIndex = viewIndex === action.payload ? -1 : action.payload;
+ },
+
+ changeSlider: (state, action) => {
+ //should really be called jump to snapshot
+ const { port, currentTab, tabs } = state;
+ const { hierarchy, snapshots } = tabs[currentTab] || {};
+
+ // finds the name by the action.payload parsing through the hierarchy to send to background.js the current name in the jump action
+ const nameFromIndex = findName(action.payload, hierarchy);
+ // nameFromIndex is a number based on which jump button is pushed
+
+ port.postMessage({
+ action: 'jumpToSnap',
+ payload: snapshots[action.payload],
+ index: action.payload,
+ name: nameFromIndex,
+ tabId: currentTab,
+ });
+
+ tabs[currentTab].sliderIndex = action.payload;
+ },
+
+ setCurrentTabInApp: (state, action) => {
+ state.currentTabInApp = action.payload;
+ },
+
+ pause: (state) => {
+ const { tabs, currentTab } = state;
+ const { intervalId } = tabs[currentTab] || {};
+
+ clearInterval(intervalId);
+ tabs[currentTab].playing = false;
+ tabs[currentTab].intervalId = null;
+ },
+
+ launchContentScript: (state, action) => {
+ const { tabs, currentTab, port } = state;
+
+ // Fired when user clicks launch button on the error page. Send msg to background to launch
+ port.postMessage({
+ action: 'launchContentScript',
+ payload: action.payload,
+ tabId: currentTab,
+ });
+ },
+
+ playForward: (state, action) => {
+ const { port, tabs, currentTab } = state;
+ const { hierarchy, snapshots, sliderIndex, intervalId } = tabs[currentTab] || {};
+
+ if (sliderIndex < snapshots.length - 1) {
+ const newIndex = sliderIndex + 1;
+ // eslint-disable-next-line max-len
+ // finds the name by the newIndex parsing through the hierarchy to send to background.js the current name in the jump action
+ const nameFromIndex = findName(newIndex, hierarchy);
+
+ port.postMessage({
+ action: 'jumpToSnap',
+ payload: snapshots[newIndex],
+ index: newIndex,
+ name: nameFromIndex,
+ tabId: currentTab,
+ });
+
+ tabs[currentTab].sliderIndex = newIndex;
+
+ // message is coming from the user
+ if (!action.payload) {
+ clearInterval(intervalId);
+ tabs[currentTab].playing = false;
+ }
+ }
+ },
+
+ startPlaying: (state, action) => {
+ const { tabs, currentTab } = state;
+
+ tabs[currentTab].playing = true;
+ tabs[currentTab].intervalId = action.payload;
+ },
+
+ resetSlider: (state) => {
+ const { port, tabs, currentTab } = state;
+ const { snapshots, sliderIndex } = tabs[currentTab] || {};
+
+ // eslint-disable-next-line max-len
+ // resets name to 0 to send to background.js the current name in the jump action
+ port.postMessage({
+ action: 'jumpToSnap',
+ index: 0,
+ name: 0,
+ payload: snapshots[0],
+ tabId: currentTab,
+ });
+ tabs[currentTab].sliderIndex = 0;
+ },
+
+ toggleMode: (state, action) => {
+ const { port, tabs, currentTab } = state;
+ const { mode } = tabs[currentTab] || {};
+ mode[action.payload] = !mode[action.payload];
+ const newMode = mode[action.payload];
+ let actionText;
+ switch (action.payload) {
+ case 'paused':
+ actionText = 'setPause';
+ break;
+ default:
+ break;
+ }
+ port.postMessage({
+ action: actionText,
+ payload: newMode,
+ tabId: currentTab,
+ });
+ },
+
+ importSnapshots: (state, action) => {
+ const { port, tabs, currentTab } = state;
+
+ // Log the value of tabs[currentTab].snapshots before the update
+ port.postMessage({
+ action: 'import',
+ payload: action.payload,
+ tabId: currentTab,
+ });
+
+ const savedSnapshot = action.payload;
+
+ tabs[currentTab].sliderIndex = savedSnapshot.sliderIndex;
+ tabs[currentTab].viewIndex = savedSnapshot.viewIndex;
+ tabs[currentTab].playing = false;
+
+ // resets hierarchy to page last state recorded
+ tabs[currentTab].hierarchy.stateSnapshot = savedSnapshot.hierarchy.stateSnapshot;
+
+ // resets hierarchy
+ tabs[currentTab].hierarchy.children = savedSnapshot.hierarchy.children;
+
+ // resets snapshots to page last state recorded
+ tabs[currentTab].snapshots = savedSnapshot.snapshots;
+
+ // resets currLocation to page last state recorded
+ tabs[currentTab].currLocation = tabs[currentTab].hierarchy;
+ tabs[currentTab].index = savedSnapshot.index;
+ tabs[currentTab].currParent = savedSnapshot.currParent;
+ tabs[currentTab].currBranch = savedSnapshot.Branch;
+ tabs[currentTab].seriesSavedStatus = false;
+ },
+
+ tutorialSaveSeriesToggle: (state, action) => {
+ const { tabs, currentTab } = state;
+ tabs[currentTab] = { ...tabs[currentTab], seriesSavedStatus: action.payload }; // sets the tab[currentTab]'s 'seriesSavedStatus' property to the payload.
+ },
+
+ onHover: (state, action) => {
+ const { currentTab, port } = state;
+
+ port.postMessage({
+ action: 'onHover',
+ payload: action.payload,
+ tabId: currentTab,
+ });
+ },
+
+ onHoverExit: (state, action) => {
+ const { currentTab, port } = state;
+
+ port.postMessage({
+ action: 'onHoverExit',
+ payload: action.payload,
+ tabId: currentTab,
+ });
+ },
+
+ toggleExpanded: (state, action) => {
+ const { tabs, currentTab } = state;
+ const snapshot = tabs[currentTab].currLocation.stateSnapshot;
+
+ // Special case for root node
+ if (action.payload.name === 'root' && snapshot.name === 'root') {
+ snapshot.isExpanded = !snapshot.isExpanded;
+ return;
+ }
+
+ // Regular case for other nodes
+ const checkChildren = (node) => {
+ if (_.isEqual(node, action.payload)) {
+ node.isExpanded = !node.isExpanded;
+ } else if (node.children) {
+ node.children.forEach((child) => {
+ checkChildren(child);
+ });
+ }
+ };
+ checkChildren(snapshot);
+ },
+
+ disconnected: (state) => {
+ state.connectionStatus = false;
+ },
+
+ startReconnect: (state) => {
+ state.connectRequested = true;
+ state.port = initialState.port;
+ },
+
+ endConnect: (state) => {
+ state.connectRequested = false;
+ state.connectionStatus = true;
+ },
+
+ toggleAxTree: (state, action) => {
+ const { port, payload, tabs, currentTab } = state;
+ port.postMessage({
+ action: 'toggleAxRecord',
+ payload: action.payload,
+ tabId: currentTab,
+ });
+ },
+ },
+});
+
+export const {
+ emptySnapshots,
+ addNewSnapshots,
+ initialConnect,
+ setPort,
+ setTab,
+ deleteTab,
+ noDev,
+ aReactApp,
+ setCurrentLocation,
+ changeView,
+ changeSlider,
+ setCurrentTabInApp,
+ pause,
+ launchContentScript,
+ playForward,
+ startPlaying,
+ resetSlider,
+ toggleMode,
+ importSnapshots,
+ tutorialSaveSeriesToggle,
+ onHover,
+ onHoverExit,
+ toggleExpanded,
+ disconnected,
+ startReconnect,
+ endConnect,
+ toggleAxTree,
+} = mainSlice.actions;
diff --git a/src/app/store.js b/src/app/store.js
deleted file mode 100644
index ae0534a00..000000000
--- a/src/app/store.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import React, { useContext } from 'react';
-
-export const StoreContext = React.createContext();
-
-export const useStoreContext = () => useContext(StoreContext);
diff --git a/src/app/store.ts b/src/app/store.ts
new file mode 100644
index 000000000..9ed9da56e
--- /dev/null
+++ b/src/app/store.ts
@@ -0,0 +1,13 @@
+//Import store from redux tool kit
+import { configureStore } from '@reduxjs/toolkit';
+import { mainSlice } from './slices/mainSlice';
+
+//Export Store
+export const store = configureStore({
+ reducer: {
+ main: mainSlice.reducer,
+ },
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false }),
+});
+
+export type RootState = ReturnType
\ No newline at end of file
diff --git a/src/app/styles/abstracts/_variables.scss b/src/app/styles/abstracts/_variables.scss
deleted file mode 100644
index f46538573..000000000
--- a/src/app/styles/abstracts/_variables.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-/// fontFamily: 'monaco, Consolas, Lucida Console, monospace'
-/// @type List
-$text-font-stack: monaco, Consolas, 'Lucida Console', monospace, Arial,
- sans-serif !default;
-
-/// @type Color
-$text-color: rgb(231, 231, 231);
-
-/// @type Color
-$brand-color: #2a2f3a !default;
-
-/// Light grey
-/// @type Color
-$light-grey-one: #474c55 !default;
-
-/// @type Color
-$light-grey-two: #5f6369 !default;
-
-$light-grey-three: rgb(101, 104, 110) !default;
-
-/// @type Color
-$navbar-color: rgb(68, 72, 78) !default;
-
-/// @type Color
-$head-color: #353b46 !default;
-
-/// @type Color
-$border-color: rgba(190, 190, 190, 0.5) !default;
-
-/// @type Color
-$highlight-color: rgba(224, 224, 224, 0.5) !default;
-
-/// @type Font Size
-$button-text-size: 10px;
diff --git a/src/app/styles/base/_base.scss b/src/app/styles/base/_base.scss
deleted file mode 100644
index 7d86d5bf8..000000000
--- a/src/app/styles/base/_base.scss
+++ /dev/null
@@ -1,33 +0,0 @@
-html {
- height: 100%;
-}
-
-body {
- margin: 0;
- height: 100%;
-}
-
-#root {
- height: 100%;
-}
-
-.travel-container {
- grid-area: travel;
-}
-.action-container {
- grid-area: actions;
-}
-.state-container {
- grid-area: states;
-}
-.buttons-container {
- grid-area: buttons;
-}
-
-.action-container,
-.state-container,
-.travel-container {
- border-style: solid;
- border-color: $border-color;
- border-width: 1px;
-}
diff --git a/src/app/styles/base/_typography.scss b/src/app/styles/base/_typography.scss
deleted file mode 100644
index e9fd0a50d..000000000
--- a/src/app/styles/base/_typography.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-body {
- color: $text-color;
- font: normal 13px $text-font-stack;
- // font: normal 125% / 1.4 $text-font-stack;
-}
diff --git a/src/app/styles/components/_actionComponent.scss b/src/app/styles/components/_actionComponent.scss
index 86f9c539e..4cdf0ceaa 100644
--- a/src/app/styles/components/_actionComponent.scss
+++ b/src/app/styles/components/_actionComponent.scss
@@ -1,23 +1,128 @@
+.route-container {
+ width: 100%;
+}
+
+.route-header {
+ background-color: var(--button-primary-bg);
+ color: var(--button-primary-text);
+ padding: 10px;
+ font-size: 14px;
+}
+
+.route-content {
+ display: flex;
+ flex-direction: row;
+ margin-bottom: 50px;
+}
+
+/* Container for individual action */
+.individual-action {
+ margin: 0;
+ padding: 0;
+}
+
.action-component {
- padding: 5px 10px;
- display: grid;
- grid-template-columns: 5fr 1fr;
- align-items: center;
- height: 20px;
- background-color: $brand-color;
- border-bottom-style: solid;
- border-bottom-width: 1px;
- border-color: $border-color;
- cursor: pointer;
- text-overflow: ellipsis;
- @extend %disable-highlight
+ display: flex;
+ padding: 6px 16px;
+ background: var(--bg-primary);
+ border-bottom: 1px solid var(--border-color);
+ transition: background-color 200ms ease;
+}
+
+.action-component:hover {
+ background-color: var(--hover-bg);
}
.action-component.selected {
- background-color: $light-grey-one;
+ background-color: var(--selected-bg);
+}
+
+.action-component-trigger {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.action-component-text {
+ flex: 1;
+}
+
+/* Input styling */
+.actionname {
+ border: none;
+ background: transparent;
+ font-size: 1rem;
+ color: var(--text-primary);
+ width: 100%;
+ padding: 2px 0;
+}
+
+.actionname::placeholder {
+ color: var(--text-secondary);
}
-.action-component.exclude {
+.actionname:focus {
+ outline: none;
+}
+
+/* Button styling */
+.time-button,
+.jump-button,
+.current-location {
+ min-width: 90px;
+ height: 28px;
+ padding: 0 8px;
+ border-radius: 6px;
+ font-size: 0.9375rem;
+ font-weight: 500;
display: flex;
+ align-items: center;
justify-content: center;
+ border: none;
+ transition: all 200ms ease;
+ margin: 0;
+}
+
+.time-button {
+ color: var(--text-secondary);
+ background: transparent;
+}
+
+.jump-button {
+ color: var(--button-primary-text);
+ background: var(--button-primary-bg);
+ display: none;
+ transition: all 200ms ease;
+}
+
+.jump-button:hover {
+ background: var(--text-primary);
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ cursor: pointer;
+}
+
+.current-location {
+ color: var(--color-primary);
+ background: var(--bg-tertiary);
+ border: 1px solid var(--color-primary);
+}
+
+/* Hide/show button transitions */
+.action-component:hover .time-button {
+ display: none;
+}
+
+.action-component:hover .jump-button {
+ display: block;
+}
+
+.current-snap {
+ font-weight: 700;
+ border: none;
+ background: transparent;
+ font-size: 1rem;
+ color: var(--text-primary);
+ width: 100%;
+ padding: 2px 0px;
}
diff --git a/src/app/styles/components/_ax.scss b/src/app/styles/components/_ax.scss
new file mode 100644
index 000000000..db9b06c4b
--- /dev/null
+++ b/src/app/styles/components/_ax.scss
@@ -0,0 +1,80 @@
+/* Container for the radio controls */
+.accessibility-controls {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 12px 24px;
+}
+
+/* Hide the default radio inputs */
+.accessibility-controls input[type='radio'] {
+ display: none;
+}
+
+/* Style the labels as buttons */
+.accessibility-controls label {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 60px;
+ padding: 8px 16px;
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--bg-primary);
+ background-color: var(--color-primary);
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 200ms ease;
+ user-select: none;
+}
+
+.accessibility-controls label:hover {
+ background-color: var(--color-primary-dark);
+}
+
+.accessibility-disable input[type='radio'] {
+ display: none;
+}
+
+.accessibility-disable label {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 60px;
+ padding: 6px 12px;
+ font-size: 14px;
+ font-weight: 500;
+ background-color: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 200ms ease;
+ user-select: none;
+}
+
+.accessibility-disable label:hover {
+ border-color: var(--border-color-dark);
+ background-color: var(--bg-tertiary);
+}
+
+.accessibility-text {
+ padding: 12px 24px;
+ max-width: 800px;
+ line-height: 1.5;
+}
+
+.accessibility-text p {
+ margin: 0;
+ color: var(--text-primary);
+ font-size: 16px;
+}
+
+.tooltipData-JSONTree {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ padding: 8px 12px;
+ border-bottom: 1px solid var(--border-color);
+ background-color: var(--bg-primary);
+}
diff --git a/src/app/styles/components/_buttons.scss b/src/app/styles/components/_buttons.scss
index 1fe84f598..aea1c6560 100644
--- a/src/app/styles/components/_buttons.scss
+++ b/src/app/styles/components/_buttons.scss
@@ -1,77 +1,181 @@
-.empty-button {
- @extend %button-shared;
- width: 120px;
+.play-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ width: 100px;
+ height: 40px;
+ padding: 8px 12x;
+ border-radius: 8px;
+ border: none;
+ background-color: var(--button-primary-bg);
+ color: var(--button-primary-text);
+ font-size: 14px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 200ms ease;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ position: relative;
+ overflow: hidden;
+
+ &:hover {
+ background-color: var(--text-primary);
+ }
+
+ &:focus {
+ outline: none;
+ box-shadow:
+ 0 0 0 2px white,
+ 0 0 0 4px #1f2937;
+ }
+
+ &-content {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ position: relative;
+ z-index: 1;
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ width: 5px;
+ height: 5px;
+ background: rgba(255, 255, 255, 0.5);
+ opacity: 1;
+ border-radius: 50%;
+ transform: scale(1);
+ animation: ripple 600ms linear;
+ display: none;
+ }
+
+ &:active::after {
+ display: block;
+ }
+}
+
+.record-button-container {
+ display: flex;
+ align-items: center;
+ padding: 4px 16px;
+ background: transparent;
+ justify-content: space-between;
}
-.state-dropdown {
- width: 240px;
- margin-left: 20px;
+.record-controls {
+ display: flex;
+ align-items: center;
}
-.action-component:hover .jump-button {
- opacity: 1;
- transform: rotateX(0deg);
- transition: opacity 300ms, transform 0.15s linear;
+.record-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ color: var(--text-primary);
+ font-size: 14px;
+ font-weight: 500;
}
-.jump-button {
- @extend %button-shared;
- width: 50px;
- opacity: 0;
- transform: rotateX(90deg);
- transition: opacity 300ms, transform 0.15s linear;
+.record-icon {
+ width: 10px;
+ height: 10px;
+ background-color: #ef4444;
+ border-radius: 50%;
+ transition: opacity 0.3s ease;
}
-.jump-button:hover {
- // remove the blue border when button is clicked
- background-color: $highlight-color;
+.record-icon.recording {
+ animation: pulse 2s infinite;
}
-.empty-button:hover {
- background-color: $highlight-color;
+@keyframes pulse {
+ 0% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.5;
+ }
+ 100% {
+ opacity: 1;
+ }
}
-.play-button {
- @extend %button-shared;
- width: 100px;
- margin: 0 1% 0 2%;
+.clear-button-modern {
+ width: 100% !important;
+ background-color: var(--bg-tertiary) !important;
+ color: var(--text-primary) !important;
+ font-size: 0.875rem !important;
+ font-weight: 500 !important;
+ text-transform: uppercase !important;
+ padding: 0.375rem 1rem !important;
+ border-radius: 0.375rem !important;
+ transition: background-color 200ms ease-in-out !important;
}
-.backward-button {
- @extend %button-shared;
- width: 30px;
- margin: 7px;
+.clear-button-modern:hover {
+ background-color: var(--border-color) !important;
}
-.forward-button {
- @extend %button-shared;
- width: 30px;
- margin: 7px;
+/* Theme toggle button styling */
+.theme-toggle {
+ position: relative;
+ width: 48px;
+ height: 24px;
+ border-radius: 16px;
+ border: 2px solid var(--border-color);
+ background-color: var(--bg-tertiary);
+ cursor: pointer;
+ transition: all 300ms ease;
+ padding: 2px;
}
-.import-button,
-.export-button,
-.lock-button,
-.pause-button,
-.persist-button {
- @extend %button-shared;
+.theme-toggle.dark {
+ background-color: var(--bg-primary);
+ border-color: var(--border-color-dark);
}
-%button-shared {
- outline: none;
- background-color: $brand-color;
- color: white;
+.theme-toggle-slider {
+ position: relative;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background-color: var(--bg-primary);
+ transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
+ align-items: center;
justify-content: center;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.theme-toggle.dark .theme-toggle-slider {
+ transform: translateX(24px);
+ background-color: var(--color-primary);
+}
+
+.theme-toggle-icons {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
align-items: center;
- height: 20px;
- border-style: solid;
- border-width: 1px;
- border-radius: 3px;
- cursor: pointer;
- line-height: 1.5em;
- font: normal 13px monaco, Consolas, "Lucida Console", monospace, Arial, sans-serif;
- font-size: $button-text-size;
+ justify-content: space-between;
+ padding: 0 4px;
+ pointer-events: none;
+}
+
+.theme-toggle-icon {
+ width: 14px;
+ height: 14px;
+ transition: color 300ms ease;
+}
+
+.theme-toggle.dark .theme-toggle-icon.moon {
+ color: var(--color-primary);
+}
- @extend %disable-highlight;
+.theme-toggle .theme-toggle-icon.sun {
+ color: var(--color-primary);
}
diff --git a/src/app/styles/components/_componentMap.scss b/src/app/styles/components/_componentMap.scss
new file mode 100644
index 000000000..7167fee01
--- /dev/null
+++ b/src/app/styles/components/_componentMap.scss
@@ -0,0 +1,148 @@
+/* Component Map Container */
+.componentMapContainer {
+ fill: var(--bg-secondary);
+ transition: all 0.3s ease;
+ overflow: auto;
+}
+.componentMapContainer svg {
+ background-color: var(--bg-secondary);
+}
+
+#root {
+ height: 100%;
+}
+
+/* Node Styling */
+.compMapParent,
+.compMapChild {
+ fill: var(--bg-primary);
+ stroke: var(--border-color);
+ filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.05));
+ transition: all 0.2s ease;
+}
+
+.compMapParent:hover,
+.compMapChild:hover {
+ stroke: var(--color-primary);
+ stroke-width: 2px;
+ cursor: pointer;
+ filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1));
+}
+
+/* Root Node Styling */
+.compMapRoot {
+ fill: var(--color-primary);
+ stroke: var(--color-primary-dark);
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
+ transition: all 0.2s ease;
+}
+
+.compMapRoot:hover {
+ filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.15));
+ cursor: pointer;
+ transform: scale(1.05);
+}
+
+/* Text Styling */
+.compMapRootText {
+ fill: var(--bg-primary);
+ font-weight: 500;
+ user-select: none;
+}
+
+.compMapParentText,
+.compMapChildText {
+ fill: var(--text-primary);
+ font-weight: 500;
+ user-select: none;
+}
+
+/* Link Styling */
+.compMapLink {
+ stroke-linecap: round;
+ transition: all 0.3s ease;
+}
+
+.compMapLink:hover {
+ stroke-width: 2;
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
+}
+
+.link-controls {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 12px 16px;
+ background: var(--bg-primary);
+ border-bottom: 1px solid var(--border-color);
+ justify-content: space-between;
+ max-width: 1200px;
+}
+
+.control-group {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.control-label {
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ user-select: none;
+}
+
+.control-select {
+ background-color: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ font-size: 14px;
+ padding: 6px 12px;
+ border-radius: 6px;
+ min-width: 100px;
+ cursor: pointer;
+ transition: all 200ms ease;
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%236b7280'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 8px center;
+ background-size: 16px;
+}
+
+.control-select:hover {
+ border-color: var(--border-color-dark);
+ background-color: var(--bg-tertiary);
+}
+
+.control-select:focus {
+ outline: none;
+ border-color: var(--color-primary);
+ box-shadow: 0 0 0 2px rgba(20, 184, 166, 0.1);
+}
+
+.control-range {
+ appearance: none;
+ width: 120px;
+ height: 4px;
+ border-radius: 2px;
+ background: var(--border-color);
+ outline: none;
+ margin: 0;
+ cursor: pointer;
+
+ &::-webkit-slider-thumb {
+ appearance: none;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: var(--color-primary);
+ cursor: pointer;
+ transition: all 200ms ease;
+ border: none;
+
+ &:hover {
+ background: var(--color-primary-dark);
+ transform: scale(1.1);
+ }
+ }
+}
diff --git a/src/app/styles/components/_d3TreeLEGACY.css b/src/app/styles/components/_d3TreeLEGACY.css
deleted file mode 100644
index 15dee64ac..000000000
--- a/src/app/styles/components/_d3TreeLEGACY.css
+++ /dev/null
@@ -1,40 +0,0 @@
-/* .d3Container{
- height: 100%;
- width: 100%;
-}
-
-.node {
- cursor: pointer;
-}
-
-.node circle {
- fill: #fff;
- stroke: black;
- stroke-width: 2px;
-}
-
-.node text {
- font: 15px sans-serif;
- color: white;
-}
-
-.link {
- fill: none;
- stroke: black;
- stroke-width: 1.5px;
-}
-
-div.tooltip {
- white-space: pre;
- position: absolute;
- overflow-y: scroll;
- overscroll-behavior: contain;
- flex: auto;
- max-height: 100px;
- padding: 15px;
- font: 15px sans-serif;
- color: black;
- background: #ffff;
- border: solid 1px #aaa;
- border-radius: 8px;
-} */
\ No newline at end of file
diff --git a/src/app/styles/components/_jsonTree.scss b/src/app/styles/components/_jsonTree.scss
deleted file mode 100644
index bd182e90c..000000000
--- a/src/app/styles/components/_jsonTree.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-.json-tree {
- margin: 10px;
- padding: 0;
- background-color: $brand-color;
- list-style: none;
-}
diff --git a/src/app/styles/components/_performanceVisx.scss b/src/app/styles/components/_performanceVisx.scss
new file mode 100644
index 000000000..7442dd944
--- /dev/null
+++ b/src/app/styles/components/_performanceVisx.scss
@@ -0,0 +1,75 @@
+.saveSeriesContainer {
+ display: flex;
+ align-items: center;
+ padding: 12px 16px;
+ background-color: var(--bg-primary);
+ max-width: 1200px;
+ justify-content: space-around;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.routesForm {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+#routes-dropdown {
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ user-select: none;
+ white-space: nowrap;
+}
+
+.performance-dropdown,
+#routes-select,
+#snapshot-select,
+.control-select {
+ background-color: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ font-size: 14px;
+ padding: 6px 12px;
+ border-radius: 6px;
+ min-width: 140px;
+ cursor: pointer;
+ transition: all 200ms ease;
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%236b7280'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 8px center;
+ background-size: 16px;
+}
+
+.performance-dropdown:hover,
+#routes-select:hover,
+#snapshot-select:hover,
+.control-select:hover {
+ border-color: var(--border-color-dark);
+ background-color: var(--bg-tertiary);
+}
+
+.performance-dropdown:focus,
+#routes-select:focus,
+#snapshot-select:focus,
+.control-select:focus {
+ outline: none;
+ border-color: var(--color-primary);
+ box-shadow: 0 0 0 2px rgba(20, 184, 166, 0.1);
+}
+
+.routes,
+.control-select option {
+ padding: 0.5rem;
+ color: var(--text-primary);
+}
+
+// bar graph background
+.perf-rect {
+ fill: var(--bg-secondary);
+}
+
+.bargraph-position {
+ background-color: var(--bg-secondary);
+}
diff --git a/src/app/styles/components/_rc-slider.scss b/src/app/styles/components/_rc-slider.scss
index 7b8aaea08..c868f3349 100644
--- a/src/app/styles/components/_rc-slider.scss
+++ b/src/app/styles/components/_rc-slider.scss
@@ -1,254 +1,54 @@
.rc-slider {
position: relative;
width: 100%;
- margin: 20px;
+ margin: 12px;
border-radius: 6px;
- -ms-touch-action: none;
- touch-action: none;
- box-sizing: border-box;
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
-.rc-slider * {
- box-sizing: border-box;
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-}
.rc-slider-rail {
position: absolute;
width: 100%;
- background-color: #e9e9e9;
+ background-color: #d9d9d9;
height: 4px;
border-radius: 6px;
}
+
.rc-slider-track {
position: absolute;
left: 0;
height: 4px;
border-radius: 6px;
- background-color: #78909C;
+ background-color: #284b63;
}
+
.rc-slider-handle {
position: absolute;
- margin-left: -10px;
- margin-top: -10px;
- width: 25px;
- height: 25px;
+ margin-left: -8px;
+ width: 20px;
+ height: 20px;
cursor: pointer;
cursor: -webkit-grab;
cursor: grab;
border-radius: 50%;
- // border: solid 2px #96dbfa;
- background-color: #78909B;
- -ms-touch-action: pan-x;
- touch-action: pan-x;
-}
-.rc-slider-handle:focus {
- border-color: #57c5f7;
- box-shadow: 0 0 0 5px #96dbfa;
- outline: none;
-}
-.rc-slider-handle-click-focused:focus {
- border-color: #96dbfa;
- box-shadow: unset;
-}
-.rc-slider-handle:hover {
- border-color: #57c5f7;
-}
-.rc-slider-handle:active {
- border-color: #57c5f7;
- box-shadow: 0 0 5px #57c5f7;
- cursor: -webkit-grabbing;
- cursor: grabbing;
-}
-.rc-slider-mark {
- position: absolute;
- top: 18px;
- left: 0;
- width: 100%;
- font-size: 12px;
-}
-.rc-slider-mark-text {
- position: absolute;
- display: inline-block;
- vertical-align: middle;
- text-align: center;
- cursor: pointer;
- color: #999;
-}
-.rc-slider-mark-text-active {
- color: #666;
-}
-.rc-slider-step {
- position: absolute;
- width: 100%;
- height: 4px;
- background: transparent;
-}
-.rc-slider-dot {
- position: absolute;
- bottom: -2px;
- margin-left: -4px;
- width: 8px;
- height: 8px;
- border: 2px solid #e9e9e9;
- background-color: #fff;
- cursor: pointer;
- border-radius: 50%;
- vertical-align: middle;
-}
-.rc-slider-dot-active {
- border-color: #96dbfa;
-}
-.rc-slider-disabled {
- background-color: #e9e9e9;
-}
-.rc-slider-disabled .rc-slider-track {
- background-color: #ccc;
-}
-.rc-slider-disabled .rc-slider-handle,
-.rc-slider-disabled .rc-slider-dot {
- border-color: #ccc;
- box-shadow: none;
- background-color: #fff;
- cursor: not-allowed;
-}
-.rc-slider-disabled .rc-slider-mark-text,
-.rc-slider-disabled .rc-slider-dot {
- cursor: not-allowed !important;
+ background: #374151;
+ border: none;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ transition:
+ transform 0.2s ease,
+ box-shadow 0.2s ease;
}
+
.rc-slider-vertical {
- width: 14px;
height: 100%;
padding: 0 5px;
}
+
.rc-slider-vertical .rc-slider-rail {
height: 100%;
width: 4px;
}
+
.rc-slider-vertical .rc-slider-track {
left: 5px;
- bottom: 0;
- width: 4px;
-}
-.rc-slider-vertical .rc-slider-handle {
- margin-left: -5px;
- margin-bottom: -7px;
- -ms-touch-action: pan-y;
- touch-action: pan-y;
-}
-.rc-slider-vertical .rc-slider-mark {
- top: 0;
- left: 18px;
- height: 100%;
-}
-.rc-slider-vertical .rc-slider-step {
- height: 100%;
width: 4px;
}
-.rc-slider-vertical .rc-slider-dot {
- left: 2px;
- margin-bottom: -4px;
-}
-.rc-slider-vertical .rc-slider-dot:first-child {
- margin-bottom: -4px;
-}
-.rc-slider-vertical .rc-slider-dot:last-child {
- margin-bottom: -4px;
-}
-.rc-slider-tooltip-zoom-down-enter,
-.rc-slider-tooltip-zoom-down-appear {
- animation-duration: .3s;
- animation-fill-mode: both;
- display: block !important;
- animation-play-state: paused;
-}
-.rc-slider-tooltip-zoom-down-leave {
- animation-duration: .3s;
- animation-fill-mode: both;
- display: block !important;
- animation-play-state: paused;
-}
-.rc-slider-tooltip-zoom-down-enter.rc-slider-tooltip-zoom-down-enter-active,
-.rc-slider-tooltip-zoom-down-appear.rc-slider-tooltip-zoom-down-appear-active {
- animation-name: rcSliderTooltipZoomDownIn;
- animation-play-state: running;
-}
-.rc-slider-tooltip-zoom-down-leave.rc-slider-tooltip-zoom-down-leave-active {
- animation-name: rcSliderTooltipZoomDownOut;
- animation-play-state: running;
-}
-.rc-slider-tooltip-zoom-down-enter,
-.rc-slider-tooltip-zoom-down-appear {
- transform: scale(0, 0);
- animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
-}
-.rc-slider-tooltip-zoom-down-leave {
- animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
-}
-@keyframes rcSliderTooltipZoomDownIn {
- 0% {
- opacity: 0;
- transform-origin: 50% 100%;
- transform: scale(0, 0);
- }
- 100% {
- transform-origin: 50% 100%;
- transform: scale(1, 1);
- }
-}
-@keyframes rcSliderTooltipZoomDownOut {
- 0% {
- transform-origin: 50% 100%;
- transform: scale(1, 1);
- }
- 100% {
- opacity: 0;
- transform-origin: 50% 100%;
- transform: scale(0, 0);
- }
-}
-.rc-slider-tooltip {
- position: absolute;
- left: -9999px;
- top: -9999px;
- visibility: visible;
- box-sizing: border-box;
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-}
-.rc-slider-tooltip * {
- box-sizing: border-box;
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-}
-.rc-slider-tooltip-hidden {
- display: none;
-}
-.rc-slider-tooltip-placement-top {
- padding: 4px 0 8px 0;
-}
-.rc-slider-tooltip-inner {
- padding: 6px 2px;
- min-width: 24px;
- height: 24px;
- font-size: 12px;
- line-height: 1;
- color: #fff;
- text-align: center;
- text-decoration: none;
- background-color: #6c6c6c;
- border-radius: 6px;
- box-shadow: 0 0 4px #d9d9d9;
-}
-.rc-slider-tooltip-arrow {
- position: absolute;
- width: 0;
- height: 0;
- border-color: transparent;
- border-style: solid;
-}
-.rc-slider-tooltip-placement-top .rc-slider-tooltip-arrow {
- bottom: 4px;
- left: 50%;
- margin-left: -4px;
- border-width: 4px 4px 0;
- border-top-color: #6c6c6c;
-}
diff --git a/src/app/styles/components/_sliderHandle.scss b/src/app/styles/components/_sliderHandle.scss
deleted file mode 100644
index f89497794..000000000
--- a/src/app/styles/components/_sliderHandle.scss
+++ /dev/null
@@ -1,170 +0,0 @@
-.rc-tooltip.rc-tooltip-zoom-enter,
-.rc-tooltip.rc-tooltip-zoom-leave {
- display: block;
-}
-.rc-tooltip-zoom-enter,
-.rc-tooltip-zoom-appear {
- opacity: 0;
- animation-duration: 0.3s;
- animation-fill-mode: both;
- animation-timing-function: cubic-bezier(0.18, 0.89, 0.32, 1.28);
- animation-play-state: paused;
-}
-.rc-tooltip-zoom-leave {
- animation-duration: 0.3s;
- animation-fill-mode: both;
- animation-timing-function: cubic-bezier(0.6, -0.3, 0.74, 0.05);
- animation-play-state: paused;
-}
-.rc-tooltip-zoom-enter.rc-tooltip-zoom-enter-active,
-.rc-tooltip-zoom-appear.rc-tooltip-zoom-appear-active {
- animation-name: rcToolTipZoomIn;
- animation-play-state: running;
-}
-.rc-tooltip-zoom-leave.rc-tooltip-zoom-leave-active {
- animation-name: rcToolTipZoomOut;
- animation-play-state: running;
-}
-@keyframes rcToolTipZoomIn {
- 0% {
- opacity: 0;
- transform-origin: 50% 50%;
- transform: scale(0, 0);
- }
- 100% {
- opacity: 1;
- transform-origin: 50% 50%;
- transform: scale(1, 1);
- }
-}
-@keyframes rcToolTipZoomOut {
- 0% {
- opacity: 1;
- transform-origin: 50% 50%;
- transform: scale(1, 1);
- }
- 100% {
- opacity: 0;
- transform-origin: 50% 50%;
- transform: scale(0, 0);
- }
-}
-.rc-tooltip {
- position: absolute;
- z-index: 1070;
- display: block;
- visibility: visible;
- font-size: 12px;
- line-height: 1.5;
- opacity: 0.9;
-}
-.rc-tooltip-hidden {
- display: none;
-}
-.rc-tooltip-placement-top,
-.rc-tooltip-placement-topLeft,
-.rc-tooltip-placement-topRight {
- padding: 5px 0 9px 0;
-}
-.rc-tooltip-placement-right,
-.rc-tooltip-placement-rightTop,
-.rc-tooltip-placement-rightBottom {
- padding: 0 5px 0 9px;
-}
-.rc-tooltip-placement-bottom,
-.rc-tooltip-placement-bottomLeft,
-.rc-tooltip-placement-bottomRight {
- padding: 9px 0 5px 0;
-}
-.rc-tooltip-placement-left,
-.rc-tooltip-placement-leftTop,
-.rc-tooltip-placement-leftBottom {
- padding: 0 9px 0 5px;
-}
-.rc-tooltip-inner {
- padding: 8px 10px;
- color: #fff;
- text-align: left;
- text-decoration: none;
- background-color: #373737;
- border-radius: 6px;
- box-shadow: 0 0 4px rgba(0, 0, 0, 0.17);
- min-height: 34px;
-}
-.rc-tooltip-arrow {
- position: absolute;
- width: 0;
- height: 0;
- border-color: transparent;
- border-style: solid;
-}
-.rc-tooltip-placement-top .rc-tooltip-arrow,
-.rc-tooltip-placement-topLeft .rc-tooltip-arrow,
-.rc-tooltip-placement-topRight .rc-tooltip-arrow {
- bottom: 4px;
- margin-left: -5px;
- border-width: 5px 5px 0;
- border-top-color: #373737;
-}
-.rc-tooltip-placement-top .rc-tooltip-arrow {
- left: 50%;
-}
-.rc-tooltip-placement-topLeft .rc-tooltip-arrow {
- left: 15%;
-}
-.rc-tooltip-placement-topRight .rc-tooltip-arrow {
- right: 15%;
-}
-.rc-tooltip-placement-right .rc-tooltip-arrow,
-.rc-tooltip-placement-rightTop .rc-tooltip-arrow,
-.rc-tooltip-placement-rightBottom .rc-tooltip-arrow {
- left: 4px;
- margin-top: -5px;
- border-width: 5px 5px 5px 0;
- border-right-color: #373737;
-}
-.rc-tooltip-placement-right .rc-tooltip-arrow {
- top: 50%;
-}
-.rc-tooltip-placement-rightTop .rc-tooltip-arrow {
- top: 15%;
- margin-top: 0;
-}
-.rc-tooltip-placement-rightBottom .rc-tooltip-arrow {
- bottom: 15%;
-}
-.rc-tooltip-placement-left .rc-tooltip-arrow,
-.rc-tooltip-placement-leftTop .rc-tooltip-arrow,
-.rc-tooltip-placement-leftBottom .rc-tooltip-arrow {
- right: 4px;
- margin-top: -5px;
- border-width: 5px 0 5px 5px;
- border-left-color: #373737;
-}
-.rc-tooltip-placement-left .rc-tooltip-arrow {
- top: 50%;
-}
-.rc-tooltip-placement-leftTop .rc-tooltip-arrow {
- top: 15%;
- margin-top: 0;
-}
-.rc-tooltip-placement-leftBottom .rc-tooltip-arrow {
- bottom: 15%;
-}
-.rc-tooltip-placement-bottom .rc-tooltip-arrow,
-.rc-tooltip-placement-bottomLeft .rc-tooltip-arrow,
-.rc-tooltip-placement-bottomRight .rc-tooltip-arrow {
- top: 4px;
- margin-left: -5px;
- border-width: 0 5px 5px;
- border-bottom-color: #373737;
-}
-.rc-tooltip-placement-bottom .rc-tooltip-arrow {
- left: 50%;
-}
-.rc-tooltip-placement-bottomLeft .rc-tooltip-arrow {
- left: 15%;
-}
-.rc-tooltip-placement-bottomRight .rc-tooltip-arrow {
- right: 15%;
-}
diff --git a/src/app/styles/components/d3graph.css b/src/app/styles/components/d3graph.css
index 6766723b9..319d14ddd 100644
--- a/src/app/styles/components/d3graph.css
+++ b/src/app/styles/components/d3graph.css
@@ -1,90 +1,122 @@
-/* @import url('https://fonts.googleapis.com/css?family=Raleway&display=swap'); */
-@import url("https://fonts.googleapis.com/css?family=Overpass+Mono&display=swap");
-body {
- background-color: black;
-}
-
+/* Base node styling */
.node {
cursor: pointer;
+ fill-opacity: 1;
+ transition: all 200ms ease;
}
-/* this represents leaf nodes aka nodes with no children */
-.node circle {
- fill: #5249f7;
+/* Node rectangle styling */
+.node rect {
+ fill: var(--bg-primary);
+ stroke: var(--border-color);
+ stroke-width: 1px;
+ transition: all 200ms ease;
}
-.node circle:hover {
- fill: #9cf4df;
+.node:hover rect {
+ stroke: var(--color-primary);
+ stroke-width: 2px;
+ filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1));
}
+/* Node text styling */
.node text {
- font-size: 10px;
- font-family: "Overpass Mono", monospace;
+ font-size: 14px;
+ font-weight: 500;
+ fill: var(--text-primary);
}
-/* this represents text for leaf nodes aka the ones with no children */
-.node--leaf {
- fill: #71e9e1;
- fill-opacity: 0.8;
+
+/* Parent node specific styling */
+.node--internal rect {
+ fill: var(--bg-primary);
+ stroke: var(--border-color);
}
-/* this represents those nodes that have children */
-.node--internal circle {
- fill: #d317c9;
+.node--internal:hover rect {
+ stroke: var(--color-primary);
+ stroke-width: 2px;
}
-/* modifies text of parent nodes (has children) */
+
.node--internal text {
- fill: #fae6e4;
- font-size: 11px;
+ font-size: 14px;
+ font-weight: 500;
+ fill: var(--text-primary);
}
+
+/* Current/active node styling */
+.node.active rect {
+ stroke: var(--color-primary);
+ stroke-width: 2px;
+}
+
+/* Link styling */
.link {
fill: none;
- stroke: #3853ea;
- stroke-opacity: 0.4;
- stroke-width: 5px;
+ stroke: var(--border-color);
+ stroke-width: 2px;
+ transition: stroke 200ms ease;
}
+.link:hover {
+ stroke: var(--border-color-dark);
+}
+
+/* Current path highlight */
+.link.current-link {
+ stroke: var(--color-primary);
+ stroke-opacity: 0.6;
+}
+
+/* Tooltip styling */
div.tooltip {
position: absolute;
- text-align: center;
- display: inline;
- max-width: 250px;
- overflow-wrap: break-word;
- padding: 6px;
- color: #320a5c;
- font-size: 12px;
- font-family: "Overpass Mono", monospace;
- background: #9cf4df;
+ padding: 12px;
+ color: var(--text-primary);
+ z-index: 100;
+ font-size: 14px;
+ font-weight: 500;
+ background: var(--bg-primary);
+ border: 1px solid var(--border-color);
+ box-shadow:
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
border-radius: 8px;
- pointer-events: none;
-}
-
-/* .d3-tip {
- line-height: 1;
- padding: 6px;
- background: #9cf4df;
- color: #320a5c;
- border-radius: 4px;
- font-size: 13px;
- max-width: 400px;
- overflow-wrap: break-word;
- font-family: "Overpass Mono", monospace;
-} */
-
-/* Creates a small triangle extender for the tooltip
-.d3-tip:after {
- box-sizing: border-box;
- display: inline;
- font-size: 15px;
- line-height: 1;
- color: #9cf4df;
- content: "\25BC";
- position: absolute;
- text-align: center;
-} */
-
-/* Style northward tooltips specifically */
-/* .d3-tip.n:after {
- margin: -2px 0 0 0;
- top: 100%;
- left: 0;
-} */
+ max-width: 250px;
+}
+
+/* Container styling */
+.display {
+ background-color: var(--bg-secondary);
+ flex: 1;
+ min-height: 0;
+ overflow: auto;
+}
+
+/* State changes text container styling */
+.node foreignObject div {
+ max-height: 100%; /* Fixed height for scroll container */
+ overflow-y: scroll;
+ overflow-x: hidden;
+ scrollbar-width: thin;
+ padding-right: 6px;
+ scrollbar-color: var(--border-color-dark) var(--bg-secondary);
+}
+
+/* Custom scrollbar styling for Webkit browsers */
+.node foreignObject div::-webkit-scrollbar {
+ width: 6px;
+}
+
+.node foreignObject div::-webkit-scrollbar-track {
+ background: var(--bg-secondary);
+ border-radius: 3px;
+}
+
+.node foreignObject div::-webkit-scrollbar-thumb {
+ background: var(--border-color-dark);
+ border-radius: 3px;
+}
+
+.node foreignObject div::-webkit-scrollbar-thumb:hover {
+ background: var(--text-tertiary);
+}
diff --git a/src/app/styles/components/diff.css b/src/app/styles/components/diff.css
index eb13af9e3..17a164469 100644
--- a/src/app/styles/components/diff.css
+++ b/src/app/styles/components/diff.css
@@ -1,149 +1,96 @@
.jsondiffpatch-delta {
- font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, Courier, monospace;
- font-size: 12px;
+ font-size: 14px;
margin: 0;
- padding: 0 0 0 12px;
+ padding: 0;
display: inline-block;
+ color: var(--text-primary);
}
.jsondiffpatch-delta pre {
- font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, Courier, monospace;
- font-size: 12px;
+ font-size: 14px;
margin: 0;
padding: 0;
display: inline-block;
+ color: var(--text-primary);
}
ul.jsondiffpatch-delta {
list-style-type: none;
- padding: 0 0 0 20px;
+ padding: 0;
margin: 0;
+ color: var(--text-primary);
}
.jsondiffpatch-delta ul {
list-style-type: none;
- padding: 0 0 0 20px;
+ padding: 0;
margin: 0;
+ color: var(--text-primary);
+}
+
+.node foreignObject div .initial-state,
+.node foreignObject div .no-changes {
+ color: var(--text-secondary);
+ font-style: italic;
+ padding: 4px 0;
+ display: block;
}
+
.jsondiffpatch-added .jsondiffpatch-property-name,
.jsondiffpatch-added .jsondiffpatch-value pre,
.jsondiffpatch-modified .jsondiffpatch-right-value pre,
.jsondiffpatch-textdiff-added {
- background: #5A6C46;
+ color: #14b8a6;
}
.jsondiffpatch-deleted .jsondiffpatch-property-name,
.jsondiffpatch-deleted pre,
.jsondiffpatch-modified .jsondiffpatch-left-value pre,
.jsondiffpatch-textdiff-deleted {
- background: #7E5C69;
text-decoration: line-through;
+ color: #dc2626;
}
-.jsondiffpatch-unchanged,
-.jsondiffpatch-movedestination {
- color: gray;
-}
-.jsondiffpatch-unchanged,
-.jsondiffpatch-movedestination > .jsondiffpatch-value {
- transition: all 0.5s;
- -webkit-transition: all 0.5s;
- overflow-y: hidden;
-}
-.jsondiffpatch-unchanged-showing .jsondiffpatch-unchanged,
-.jsondiffpatch-unchanged-showing .jsondiffpatch-movedestination > .jsondiffpatch-value {
- max-height: 100px;
-}
-.jsondiffpatch-unchanged-hidden .jsondiffpatch-unchanged,
-.jsondiffpatch-unchanged-hidden .jsondiffpatch-movedestination > .jsondiffpatch-value {
- max-height: 0;
-}
-.jsondiffpatch-unchanged-hiding .jsondiffpatch-movedestination > .jsondiffpatch-value,
-.jsondiffpatch-unchanged-hidden .jsondiffpatch-movedestination > .jsondiffpatch-value {
- display: block;
-}
-.jsondiffpatch-unchanged-visible .jsondiffpatch-unchanged,
-.jsondiffpatch-unchanged-visible .jsondiffpatch-movedestination > .jsondiffpatch-value {
- max-height: 100px;
-}
-.jsondiffpatch-unchanged-hiding .jsondiffpatch-unchanged,
-.jsondiffpatch-unchanged-hiding .jsondiffpatch-movedestination > .jsondiffpatch-value {
- max-height: 0;
-}
-.jsondiffpatch-unchanged-showing .jsondiffpatch-arrow,
-.jsondiffpatch-unchanged-hiding .jsondiffpatch-arrow {
- display: none;
-}
+
.jsondiffpatch-value {
display: inline-block;
}
.jsondiffpatch-property-name {
display: inline-block;
- padding-right: 5px;
- vertical-align: top;
+ padding-right: 4px;
}
+
.jsondiffpatch-property-name:after {
content: ': ';
}
-.jsondiffpatch-child-node-type-array > .jsondiffpatch-property-name:after {
- content: ': [';
-}
-.jsondiffpatch-child-node-type-array:after {
- content: '],';
-}
-div.jsondiffpatch-child-node-type-array:before {
- content: '[';
-}
-div.jsondiffpatch-child-node-type-array:after {
- content: ']';
-}
-.jsondiffpatch-child-node-type-object > .jsondiffpatch-property-name:after {
- content: ': {';
-}
-.jsondiffpatch-child-node-type-object:after {
- content: '},';
-}
-div.jsondiffpatch-child-node-type-object:before {
- content: '{';
-}
-div.jsondiffpatch-child-node-type-object:after {
- content: '}';
-}
-.jsondiffpatch-value pre:after {
- content: ',';
-}
+
li:last-child > .jsondiffpatch-value pre:after,
.jsondiffpatch-modified > .jsondiffpatch-left-value pre:after {
content: '';
}
+
.jsondiffpatch-modified .jsondiffpatch-value {
display: inline-block;
}
+
.jsondiffpatch-modified .jsondiffpatch-right-value {
- margin-left: 5px;
-}
-.jsondiffpatch-moved .jsondiffpatch-value {
- display: none;
+ margin-left: 6px;
}
+
.jsondiffpatch-moved .jsondiffpatch-moved-destination {
display: inline-block;
- background: #ffffbb;
- color: #888;
+ color: #ffffbb;
}
+
.jsondiffpatch-moved .jsondiffpatch-moved-destination:before {
content: ' => ';
}
-ul.jsondiffpatch-textdiff {
- padding: 0;
-}
+
.jsondiffpatch-textdiff-location {
- color: #bbb;
+ color: #ffffbb;
display: inline-block;
min-width: 60px;
}
+
.jsondiffpatch-textdiff-line {
display: inline-block;
}
+
.jsondiffpatch-textdiff-line-number:after {
content: ',';
}
-.jsondiffpatch-error {
- background: red;
- color: white;
- font-weight: bold;
-}
diff --git a/src/app/styles/layout/_actionContainer.scss b/src/app/styles/layout/_actionContainer.scss
index 41c70ae86..08a46b361 100644
--- a/src/app/styles/layout/_actionContainer.scss
+++ b/src/app/styles/layout/_actionContainer.scss
@@ -1,4 +1,24 @@
.action-container {
- overflow: auto;
- background-color: $brand-color;
+ background: var(--bg-primary);
+ border-right: 1px solid var(--border-color);
+ transition: width 0.3s ease;
+ overflow-x: hidden;
+ overflow-y: auto;
+ height: 100%;
+
+ .action-button-wrapper {
+ opacity: 1;
+ visibility: visible;
+ transition:
+ opacity 0.2s ease,
+ visibility 0.2s ease;
+ }
+}
+
+.clear-button-container {
+ padding: 16px;
+}
+
+.dropdown-container {
+ padding: 4px 16px;
}
diff --git a/src/app/styles/layout/_bodyContainer.scss b/src/app/styles/layout/_bodyContainer.scss
index e17b7b9c7..5e030eb8f 100644
--- a/src/app/styles/layout/_bodyContainer.scss
+++ b/src/app/styles/layout/_bodyContainer.scss
@@ -1,23 +1,18 @@
.body-container {
- height: 94%;
+ height: 100%;
+ overflow: hidden;
display: grid;
- grid-template-columns: 1fr 2fr;
- grid-template-rows: 8fr 1fr;
+ grid-template-columns: 280px 1fr;
+ grid-template-rows: 1fr auto;
grid-template-areas:
'actions states'
- 'travel travel'
- 'buttons buttons';
+ 'bottom bottom';
+ transition: grid-template-columns 0.3s ease;
}
-/* if extension width is less than 500px, stack the body containers */
-@media (max-width: 500px) {
- .body-container {
- grid-template-rows: 3fr 5fr 1fr;
- grid-template-columns: auto;
- grid-template-areas:
- 'actions'
- 'states'
- 'travel'
- 'buttons';
- }
+.bottom-controls {
+ grid-area: bottom;
+ display: flex;
+ width: 100%;
+ border-top: 1px solid var(--border-color);
}
diff --git a/src/app/styles/layout/_buttonsContainer.scss b/src/app/styles/layout/_buttonsContainer.scss
index b9852199b..c4f3cb9ab 100644
--- a/src/app/styles/layout/_buttonsContainer.scss
+++ b/src/app/styles/layout/_buttonsContainer.scss
@@ -1,13 +1,104 @@
.buttons-container {
- margin: 0 1% 0 1%;
- display: grid;
- grid-template-columns: repeat(5, 1fr);
- grid-gap: 1%;
- padding: 1% 0 1% 0;
+ display: flex;
+ align-items: center;
+ background: var(--bg-primary);
+ border-top: 1px solid var(--border-color);
+ width: 100%;
}
-@media (max-width: 500px) {
- .buttons-container {
- grid-template-columns: repeat(2, 1fr);
+.buttons-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ max-width: 1200px;
+ width: 100%;
+ margin: 0 auto;
+ padding: 0 24px;
+ gap: 16px;
+}
+
+.introjs-button {
+ padding: 8px 16px;
+ font-size: 14px;
+ border-radius: 8px;
+}
+
+.introjs-tooltip-title {
+ font-size: 18px;
+ font-weight: bold;
+ color: var(--text-primary);
+}
+
+.introjs-progressbar {
+ background-color: var(--color-primary);
+}
+
+.introjs-tooltip {
+ color: var(--text-primary);
+ background-color: var(--bg-primary);
+ min-width: 20rem;
+}
+
+.introjs-tooltiptext ul {
+ padding-left: 20px;
+}
+
+.introjs-nextbutton {
+ background-color: var(--color-primary);
+ color: var(--button-primary-text);
+}
+
+.introjs-prevbutton {
+ background-color: var(--bg-secondary);
+ color: var(--text-primary);
+}
+
+.introjs-skipbutton {
+ color: #d72828;
+ margin-right: 8px;
+ font-size: 14px;
+}
+
+.buttons-container button {
+ display: flex;
+ align-items: center;
+ color: var(--text-secondary);
+ font-size: 1rem;
+ font-weight: 500;
+ background: transparent;
+ border: none;
+ border-radius: 0.375rem;
+ transition: all 200ms ease;
+ gap: 8px;
+}
+
+.status-dot {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+}
+
+.status-dot.active {
+ background-color: var(--color-primary);
+ animation: pulse 2s infinite;
+}
+
+.status-dot.inactive {
+ background-color: var(--text-tertiary);
+}
+
+@keyframes pulse {
+ 0% {
+ opacity: 1;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 0.7;
+ transform: scale(1.1);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1);
}
}
diff --git a/src/app/styles/layout/_errorContainer.scss b/src/app/styles/layout/_errorContainer.scss
index 5df1f7930..9c41044e2 100644
--- a/src/app/styles/layout/_errorContainer.scss
+++ b/src/app/styles/layout/_errorContainer.scss
@@ -1,14 +1,102 @@
.error-container {
height: 100%;
+ background-color: var(--bg-secondary);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.error-logo {
+ height: 50px;
+ margin-bottom: 2rem;
+}
+
+.error-content {
+ max-width: 600px;
+ width: 100%;
+}
+
+.error-alert {
+ background-color: var(--bg-primary);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 1.5rem;
+ margin-bottom: 2rem;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.error-title {
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin-bottom: 0.75rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.error-description {
+ color: var(--text-secondary);
+ line-height: 1.5;
+ margin-bottom: 1rem;
+}
+
+.error-note {
+ text-align: center;
+ color: var(--text-tertiary);
+ font-size: 0.875rem;
+ margin-bottom: 1rem;
+}
+
+.launch-button {
+ background-color: var(--button-primary-bg);
+ color: var(--button-primary-text);
+ border: none;
+ padding: 12px 24px;
+ border-radius: 8px;
+ font-size: 1.125rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
margin: 0 auto;
- background-color: $brand-color;
- overflow: hidden;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.launch-button:hover {
+ background-color: var(--text-primary);
+}
+
+.launch-button:active {
+ background-color: var(--button-primary-bg);
+}
- display: grid;
+.github-link {
+ display: flex;
+ align-items: center;
justify-content: center;
- a {
- color: white;
- margin-top: 5%;
- height: 3%;
- }
+ gap: 0.5rem;
+ margin-top: 2rem;
+ color: var(--color-primary);
+ text-decoration: none;
+ transition: color 0.2s ease;
+}
+
+.github-link:hover {
+ color: var(--color-primary-dark);
+ text-decoration: underline;
+}
+
+.devtools-link {
+ color: var(--color-primary);
+ text-decoration: none;
+ transition: color 0.2s ease;
+}
+
+.devtools-link:hover {
+ color: var(--color-primary-dark);
+ text-decoration: underline;
}
diff --git a/src/app/styles/layout/_headContainer.scss b/src/app/styles/layout/_headContainer.scss
deleted file mode 100644
index 3c270cb42..000000000
--- a/src/app/styles/layout/_headContainer.scss
+++ /dev/null
@@ -1,63 +0,0 @@
-.head-container {
- height: 5%;
- background-color: $head-color;
-}
-
-.head-container {
- display: flex;
- flex-direction: row-reverse;
- align-items: center;
- justify-content: space-between;
-}
-
-.tab-select-container {
- font-size: 12px;
- min-width: 90px;
- margin: 2px 7px;
-}
-
-.tab-select-container {
- height: 70%;
-
- .tab-select__control,
- .tab-select__control--is-focused,
- .tab-select__menu {
- border-style: none;
- width: 300px;
- background-color: $light-grey-one;
- z-index: 2;
- @extend %disable-highlight;
- }
- .tab-select__single-value {
- color: white;
- }
- .tab-select__value-container {
- padding: 0px;
- }
- .tab-select__option:hover {
- background-color: #2683ff;
- }
- .tab-select__option--is-selected,
- .tab-select__option--is-focused {
- background-color: transparent;
- }
- .tab-select__indicator {
- padding: 0;
- }
- .tab-select__indicator-separator {
- margin-top: 3px;
- margin-bottom: 3px;
- }
-
- // removes the cursor from blinking
- .css-w8afj7-Input {
- color: transparent;
- }
-
- // removes min-height of dropdown and change it to 100%
- .css-yk16xz-control,
- .css-1pahdxg-control {
- min-height: initial;
- height: 100%;
- }
-}
diff --git a/src/app/styles/layout/_mainContainer.scss b/src/app/styles/layout/_mainContainer.scss
index 42b22f7be..b8da1cd86 100644
--- a/src/app/styles/layout/_mainContainer.scss
+++ b/src/app/styles/layout/_mainContainer.scss
@@ -1,6 +1,25 @@
.main-container {
height: 100%;
margin: 0 auto;
- background-color: $brand-color;
+ background-color: var(--bg-secondary);
overflow: hidden;
}
+
+.state-container-container {
+ display: contents;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+}
+
+.history-view {
+ height: 100%;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.history-end-anchor {
+ height: 1px;
+ width: 100%;
+}
diff --git a/src/app/styles/layout/_stateContainer.scss b/src/app/styles/layout/_stateContainer.scss
index 33b724b16..2a29e913a 100644
--- a/src/app/styles/layout/_stateContainer.scss
+++ b/src/app/styles/layout/_stateContainer.scss
@@ -1,82 +1,189 @@
.state-container {
- font-size: 12px;
overflow: auto;
- background-color: $brand-color;
-}
-
-.state-container .navbar {
- background-color: $navbar-color;
+ height: 100%;
display: flex;
- flex-direction: row;
- justify-content: flex-start;
- align-items: center;
- height: 30px;
+ flex-direction: column;
}
-.state-container .main-navbar{
- background-color: #565A61;
+.app-body {
+ height: 100%;
display: flex;
- flex-direction: row;
- align-items: center;
- height: 40px;
- margin: 6px;
+ flex-direction: column;
}
-.state-container .main-navbar-container{
- background-color: #565A61;
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
- height: 40px;
+.app-content {
+ height: 100%;
+ min-height: 0;
}
-.navbar {
- // prevent navbar from scrolling with state/tree display
- position: sticky;
- top: 0px;
- left: 0px;
- z-index: 1;
- @extend %disable-highlight
+.main-navbar-container--structural {
+ height: 0;
+ padding: 0;
+ border: none;
+ overflow: hidden;
+ visibility: hidden;
+ pointer-events: none;
}
.state-container {
- .main-navbar-text {
- margin: 6px;
- }
- .main-router-link {
- height: 80%;
- width: 80px;
- display: flex;
- justify-content: center;
- align-items: center;
- background-color: $navbar-color;
+ .router-link {
+ padding: 8px 16px;
+ font-size: 14px;
+ font-weight: 500;
+ border: none;
+ border-bottom: 2px solid transparent;
+ color: var(--text-secondary);
text-decoration: none;
- color: $text-color;
- }
- .main-router-link:hover {
- background-color: $light-grey-three;
- }
+ background-color: transparent;
+ cursor: pointer;
+ transition:
+ color 200ms ease,
+ border-color 200ms ease;
+ margin: 0;
+ position: relative;
+ white-space: nowrap;
+
+ &::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ width: 0;
+ height: 2px;
+ background-color: var(--color-primary);
+ transition: all 0.3s ease;
+ transform: translateX(-50%);
+ }
+
+ &:hover {
+ color: var(--text-primary);
+ background-color: transparent;
- .main-router-link.is-active {
- background-color: $brand-color;
+ &::after {
+ width: 100%;
+ }
+ }
+
+ &.is-active {
+ color: var(--color-primary);
+ background-color: transparent;
+
+ &::after {
+ width: 100%;
+ background-color: var(--color-primary);
+ }
+ }
}
- .router-link {
- height: 100%;
- width: 80px;
+ .main-navbar {
display: flex;
- justify-content: center;
+ flex-direction: row;
+ justify-content: space-between;
align-items: center;
- background-color: $navbar-color;
- text-decoration: none;
- color: $text-color;
- }
- .router-link:hover {
- background-color: $light-grey-three;
+ gap: 8px;
+ width: 100%;
}
- .router-link.is-active {
- background-color: $brand-color;
+ .main-navbar-container {
+ border-bottom: 1px solid var(--border-color);
+ background: var(--bg-primary);
+ padding: 4.5px 24px;
}
}
+
+// tool tip styles
+.tooltip-header {
+ padding: 8px 8px 8px 12px;
+ background-color: var(--color-primary);
+ border: 1px solid var(--color-primary-dark);
+ border-radius: 8px;
+}
+
+.tooltip-title {
+ margin: 0;
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--bg-primary);
+}
+
+.tooltip-container {
+ width: 250px;
+ background-color: var(--bg-primary);
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.tooltip-section {
+ padding: 8px 12px;
+ border-bottom: 1px solid var(--border-color);
+ transition: background-color 150ms ease;
+}
+
+.tooltip-section:last-child {
+ border-bottom: none;
+}
+
+.tooltip-section-title {
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--color-primary);
+}
+
+.tooltip-content {
+ max-height: 400px;
+ overflow-y: auto;
+ scrollbar-width: thin;
+ scrollbar-color: #cbd5e1 transparent;
+}
+
+.tooltip-item {
+ padding: 8px 0;
+ border-bottom: 1px solid rgba(107, 114, 128, 0.1);
+}
+
+.tooltip-item:last-child {
+ border-bottom: none;
+}
+
+.tooltip-data {
+ border-left: 2px solid var(--color-primary-dark);
+}
+
+.tooltip-container {
+ animation: tooltipFade 150ms ease-out;
+}
+
+// Web Metrics Container
+.web-metrics-container {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+}
+
+.metric {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.hover-box {
+ max-width: 250px;
+ background-color: #51565e;
+ border-radius: 8px;
+ color: white;
+ padding: 2px 8px;
+ line-height: 16px;
+}
+
+/* Tree styling */
+.json-tree {
+ overflow: auto;
+ list-style: none;
+ padding: 16px;
+ margin: 0;
+}
+
+.tree-component {
+ height: 100%;
+ overflow: auto;
+}
diff --git a/src/app/styles/layout/_travelContainer.scss b/src/app/styles/layout/_travelContainer.scss
index 7dbd5055d..58f53a23b 100644
--- a/src/app/styles/layout/_travelContainer.scss
+++ b/src/app/styles/layout/_travelContainer.scss
@@ -1,35 +1,129 @@
.travel-container {
+ background: var(--bg-primary);
display: flex;
flex-direction: row;
align-items: center;
- justify-content: space-around;
+ border-top: 1px solid var(--border-color);
+ padding-left: 16px;
+ gap: 8px;
}
.react-select-container {
- font-size: 12px;
+ font-size: 16px;
min-width: 90px;
- margin: 10px;
+ margin: 8px;
}
-.react-select-container {
- .react-select__control,
- .react-select__menu {
- background-color: $light-grey-one;
- @extend %disable-highlight;
- }
- .react-select__single-value {
- color: white;
- }
- .react-select__option:hover {
- background-color: #2683ff;
- }
- .react-select__option--is-selected,
- .react-select__option--is-focused {
- background-color: transparent;
- }
-
- // removes the cursor from blinking
- .css-w8afj7-Input {
- color: transparent;
- }
+.react-select__control {
+ background-color: var(--bg-secondary) !important;
+ border: 1px solid var(--border-color) !important;
+ border-radius: 6px !important;
+ min-height: 36px !important;
+ box-shadow: none !important;
+ cursor: pointer !important;
+ transition: all 200ms ease !important;
+}
+
+.react-select__control:hover {
+ border-color: var(--border-color-dark) !important;
+ background-color: var(--bg-tertiary) !important;
+}
+
+.react-select__control--is-focused {
+ border-color: var(--color-primary) !important;
+ box-shadow: 0 0 0 2px rgba(20, 184, 166, 0.1) !important;
+}
+
+.react-select__menu {
+ background-color: var(--bg-primary) !important;
+ border: 1px solid var(--border-color) !important;
+ border-radius: 6px !important;
+ box-shadow:
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
+ margin-top: 4px !important;
+ z-index: 100 !important;
+}
+
+.react-select__option {
+ background-color: var(--bg-primary) !important;
+ color: var(--text-primary) !important;
+ cursor: pointer !important;
+ padding: 8px 12px !important;
+ font-size: 14px !important;
+ transition: all 200ms ease !important;
+}
+
+.react-select__option:hover {
+ background-color: var(--bg-tertiary) !important;
+}
+
+.react-select__option--is-selected {
+ background-color: var(--color-primary) !important;
+ color: white !important;
+}
+
+.react-select__option--is-focused {
+ background-color: var(--bg-tertiary) !important;
+}
+
+.react-select__single-value {
+ color: var(--text-primary) !important;
+ font-size: 14px !important;
+}
+
+.react-select__indicator-separator {
+ background-color: var(--border-color) !important;
+}
+
+.react-select__dropdown-indicator {
+ color: var(--text-secondary) !important;
+ transition: transform 200ms ease !important;
+}
+
+.react-select__dropdown-indicator:hover {
+ color: var(--text-primary) !important;
+}
+
+.react-select__control--menu-is-open .react-select__dropdown-indicator {
+ transform: rotate(180deg) !important;
+}
+
+.react-select__placeholder {
+ color: var(--text-secondary) !important;
+ font-size: 14px !important;
+}
+
+.react-select__multi-value {
+ background-color: var(--bg-tertiary) !important;
+ border-radius: 4px !important;
+}
+
+.react-select__multi-value__label {
+ color: var(--text-primary) !important;
+ font-size: 14px !important;
+ padding: 2px 6px !important;
+}
+
+.react-select__multi-value__remove {
+ color: var(--text-secondary) !important;
+ cursor: pointer !important;
+ padding: 0 4px !important;
+ transition: all 200ms ease !important;
+}
+
+.react-select__multi-value__remove:hover {
+ background-color: var(--border-color) !important;
+ color: var(--text-primary) !important;
+}
+
+.react-select__clear-indicator {
+ color: var(--text-secondary) !important;
+ cursor: pointer !important;
+ padding: 0 8px !important;
+ transition: all 200ms ease !important;
+}
+
+.react-select__clear-indicator:hover {
+ color: var(--text-primary) !important;
}
diff --git a/src/app/styles/main.scss b/src/app/styles/main.scss
index c5d14a8d6..a63383eaf 100644
--- a/src/app/styles/main.scss
+++ b/src/app/styles/main.scss
@@ -1,24 +1,90 @@
-@charset 'UTF-8';
+@use 'base/helpers';
-// 1. Configuration and helpers
-@import 'abstracts/variables';
+@use 'layout/mainContainer';
+@use 'layout/bodyContainer';
+@use 'layout/actionContainer';
+@use 'layout/errorContainer';
+@use 'layout/stateContainer';
+@use 'layout/travelContainer';
+@use 'layout/buttonsContainer';
-// 3. Base stuff
-@import 'base/base', 'base/helpers', 'base/typography';
+@use 'components/buttons';
+@use 'components/actionComponent';
+@use 'components/performanceVisx';
+@use 'components/componentMap';
+@use 'components/ax';
-// 4. Layout-related sections
-@import 'layout/mainContainer', 'layout/bodyContainer', 'layout/actionContainer',
- 'layout/errorContainer', 'layout/stateContainer', 'layout/travelContainer',
- 'layout/buttonsContainer', 'layout/headContainer.scss';
+@use './components/rc-slider';
+@use './components/d3graph';
+@use './components/diff';
-// 5. Components
-@import 'components/buttons', 'components/actionComponent', 'components/jsonTree';
+@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@100;200;300;400;500;600;700;800;900&display=swap');
-// slider component
-@import './components/rc-slider', './components/sliderHandle';
+* {
+ font-family: 'Outfit', sans-serif;
+ font-size: 14px;
+}
-// d3 chart component
-@import './components/d3graph.css';
+html {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+}
-// diff component
-@import './components/diff';
+body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+}
+
+:root {
+ // Base colors
+ --color-primary: #14b8a6;
+ --color-primary-dark: #0d9488;
+ --color-primary-light: #2dd4bf;
+
+ // Background colors
+ --bg-primary: #ffffff;
+ --bg-secondary: #f9fafb;
+ --bg-tertiary: #f3f4f6;
+
+ // Border colors
+ --border-color: #e5e7eb;
+ --border-color-dark: #d1d5db;
+
+ // Text colors
+ --text-primary: #374151;
+ --text-secondary: #6b7280;
+ --text-tertiary: #9ca3af;
+
+ // Interactive colors
+ --hover-bg: #f9fafb;
+ --selected-bg: #f3f4f6;
+ --button-primary-bg: #111827;
+ --button-primary-text: #ffffff;
+
+ // Transitions
+ --theme-transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
+}
+
+:root.dark {
+ // Background colors
+ --bg-primary: #1f2937;
+ --bg-secondary: #2d3748;
+ --bg-tertiary: #374151;
+
+ // Border colors
+ --border-color: #374151;
+ --border-color-dark: #4b5563;
+
+ // Text colors
+ --text-primary: #f3f4f6;
+ --text-secondary: #9ca3af;
+ --text-tertiary: #6b7280;
+
+ // Interactive colors
+ --hover-bg: #2d3748;
+ --selected-bg: #374151;
+ --button-primary-bg: #0f172a;
+ --button-primary-text: #ffffff;
+}
diff --git a/package/.npmignore b/src/backend/.npmignore
similarity index 100%
rename from package/.npmignore
rename to src/backend/.npmignore
diff --git a/src/backend/__tests__/ignore/IncrementClass.tsx b/src/backend/__tests__/ignore/IncrementClass.tsx
new file mode 100644
index 000000000..0d41d8975
--- /dev/null
+++ b/src/backend/__tests__/ignore/IncrementClass.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+
+export default class IncrementClass extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { count: 0 };
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick() {
+ this.setState({ count: this.state.count + 1 });
+ }
+
+ render() {
+ return (
+
+
+ You clicked me {this.state.count} times.
+
+
+ );
+ }
+}
diff --git a/src/backend/__tests__/ignore/IncrementFunc.tsx b/src/backend/__tests__/ignore/IncrementFunc.tsx
new file mode 100644
index 000000000..f3a436817
--- /dev/null
+++ b/src/backend/__tests__/ignore/IncrementFunc.tsx
@@ -0,0 +1,25 @@
+import React, { useState } from 'react';
+function IncrementFunc() {
+ const [count, setCount] = useState(0);
+ return (
+
+ setCount(count + 1)}>
+ You clicked me {count} times.
+
+
+ );
+}
+
+export default IncrementFunc;
+
+export function IncrementFuncMultiStates() {
+ const [count, setCount] = useState(0);
+ const [count1, setCount1] = useState(1);
+ return (
+
+ setCount(count + 1)}>
+ You clicked me {count + count1} times
+
+
+ );
+}
diff --git a/src/backend/__tests__/ignore/deepCopy.ts b/src/backend/__tests__/ignore/deepCopy.ts
new file mode 100644
index 000000000..6ea468be9
--- /dev/null
+++ b/src/backend/__tests__/ignore/deepCopy.ts
@@ -0,0 +1,14 @@
+export default function deepCopy(obj: T): T {
+ if (obj === null || typeof obj !== 'object') {
+ return obj;
+ }
+ const copy = Array.isArray(obj) ? [] : {};
+ Object.keys(obj).forEach((key) => {
+ if (typeof obj[key] === 'function') {
+ copy[key] = obj[key];
+ } else {
+ copy[key] = deepCopy(obj[key]);
+ }
+ });
+ return copy as T;
+}
diff --git a/src/backend/__tests__/ignore/propComponent-testcase.ts b/src/backend/__tests__/ignore/propComponent-testcase.ts
new file mode 100644
index 000000000..f517eb5da
--- /dev/null
+++ b/src/backend/__tests__/ignore/propComponent-testcase.ts
@@ -0,0 +1,35 @@
+import { Fiber } from '../../types/backendTypes';
+import { FunctionComponent } from '../../types/backendTypes';
+
+// -------------------TEST CASE FOR COMPONENT WITH PROPS-----------------------
+export const Router: Fiber = {
+ tag: FunctionComponent,
+ elementType: { name: 'Router' },
+ sibling: null,
+ stateNode: null,
+ child: null,
+ memoizedState: {
+ memoizedState: null,
+ queue: null,
+ },
+ memoizedProps: { location: { pathname: '/tictactoe' } },
+ actualDuration: 1,
+ actualStartTime: 2,
+ selfBaseDuration: 3,
+ treeBaseDuration: 4,
+ _debugHookTypes: ['useContext', 'useMemo', 'useMemo'],
+};
+export const RenderedRoute: Fiber = {
+ tag: FunctionComponent,
+ elementType: { name: 'RenderedRoute' },
+ sibling: null,
+ stateNode: null,
+ child: null,
+ memoizedState: null,
+ memoizedProps: { match: { pathname: '/tictactoe' } },
+ actualDuration: 1,
+ actualStartTime: 2,
+ selfBaseDuration: 3,
+ treeBaseDuration: 4,
+ _debugHookTypes: ['useContext'],
+};
diff --git a/src/backend/__tests__/ignore/stateComponents-testcases.ts b/src/backend/__tests__/ignore/stateComponents-testcases.ts
new file mode 100644
index 000000000..401311b72
--- /dev/null
+++ b/src/backend/__tests__/ignore/stateComponents-testcases.ts
@@ -0,0 +1,177 @@
+import Tree from '../../models/tree';
+import routes from '../../models/routes';
+import { ComponentData, Fiber } from '../../types/backendTypes';
+import { FunctionComponent, ClassComponent, HostRoot } from '../../types/backendTypes';
+import IncrementFunc from './IncrementFunc';
+import IncrementClass from './IncrementClass';
+import componentActionsRecord from '../../models/masterState';
+import deepCopy from './deepCopy';
+
+// ----------------------------TEST CASES FOR ROOT------------------------------
+export const root: Fiber = {
+ tag: HostRoot,
+ elementType: null,
+ key: null,
+ sibling: null,
+ stateNode: null,
+ child: null,
+ memoizedState: null,
+ memoizedProps: null,
+ actualDuration: 1,
+ actualStartTime: 2,
+ selfBaseDuration: 3,
+ treeBaseDuration: 4,
+ _debugHookTypes: null,
+};
+export const rootPayload = new Tree('root', 'root');
+rootPayload.route = routes.addRoute('http://localhost/');
+
+// ----------------------TEST CASE FOR FUNCTIONAL COMPONENT---------------------
+export const functionalComponent: Fiber = {
+ tag: FunctionComponent,
+ elementType: IncrementFunc,
+ sibling: null,
+ stateNode: null,
+ key: null,
+ child: null,
+ memoizedState: {
+ memoizedState: 0,
+ queue: {
+ dispatch: function (newState) {
+ this.memoizedState = newState;
+ },
+ },
+ },
+ memoizedProps: {},
+ actualDuration: 1,
+ actualStartTime: 2,
+ selfBaseDuration: 3,
+ treeBaseDuration: 4,
+ _debugHookTypes: ['useState'],
+};
+
+const functionalComponentData: ComponentData = {
+ actualDuration: 1,
+ actualStartTime: 2,
+ selfBaseDuration: 3,
+ treeBaseDuration: 4,
+ key: null,
+ context: {},
+ hooksIndex: [0],
+ hooksState: { count: 0 },
+ index: null,
+ props: {},
+ state: null,
+};
+
+componentActionsRecord.clear();
+export const functionalPayload: Tree = new Tree('root', 'root');
+functionalPayload.route = rootPayload.route;
+functionalPayload.addChild({ count: 0 }, 'IncrementFunc', functionalComponentData, null);
+
+// -----------------------TEST CASE FOR CLASS COMPONENT-------------------------
+
+export const classComponent: Fiber = {
+ tag: ClassComponent,
+ elementType: IncrementClass,
+ sibling: null,
+ key: null,
+ stateNode: {
+ state: { count: 0 },
+ setState: function (callback) {
+ this.state = { ...callback() };
+ },
+ },
+ child: null,
+ memoizedState: {
+ count: 0,
+ },
+ memoizedProps: {},
+ actualDuration: 1,
+ actualStartTime: 2,
+ selfBaseDuration: 3,
+ treeBaseDuration: 4,
+ _debugHookTypes: null,
+};
+classComponent.stateNode.setState = classComponent.stateNode.setState.bind(
+ classComponent.stateNode,
+);
+
+const classComponentData: ComponentData = {
+ actualDuration: 1,
+ actualStartTime: 2,
+ selfBaseDuration: 3,
+ treeBaseDuration: 4,
+ key: null,
+ context: {},
+ hooksIndex: null,
+ hooksState: null,
+ index: 0,
+ props: {},
+ state: { count: 0 },
+};
+
+componentActionsRecord.clear();
+export const classPayload = new Tree('root', 'root');
+classPayload.route = rootPayload.route;
+classPayload.addChild({ count: 0 }, 'IncrementClass', classComponentData, null);
+
+componentActionsRecord.clear();
+export const updateClassPayload = new Tree('root', 'root');
+updateClassPayload.route = rootPayload.route;
+updateClassPayload.addChild(
+ { count: 2 },
+ 'IncrementClass',
+ { ...classComponentData, state: { count: 2 } },
+ null,
+);
+
+// -----------------------TEST CASE FOR MIX OF COMPONENTS-----------------------
+componentActionsRecord.clear();
+export const mixComponents: Fiber = deepCopy(root);
+mixComponents.child = deepCopy(functionalComponent);
+mixComponents.sibling = deepCopy(classComponent);
+mixComponents.child!.child = deepCopy(functionalComponent);
+mixComponents.child!.child!.sibling = deepCopy(classComponent);
+// console.dir(mixComponents, { depth: null });
+
+export const mixPayload = new Tree('root', 'root');
+mixPayload.route = rootPayload.route;
+
+// Outer Func Comp
+let funcPayloadMix = new Tree({ count: 0 }, 'IncrementFunc1', functionalComponentData, null);
+funcPayloadMix.componentData = {
+ ...funcPayloadMix.componentData,
+ hooksState: { count: 0 },
+ hooksIndex: [0],
+};
+mixPayload.children.push(deepCopy(funcPayloadMix));
+
+// Outer Class Comp
+let classPayloadMix = new Tree({ count: 0 }, 'IncrementClass', classComponentData, null);
+classPayloadMix.componentData = {
+ ...classPayloadMix.componentData,
+ state: { count: 0 },
+ index: 3,
+};
+mixPayload.children.push(deepCopy(classPayloadMix));
+
+// Inner Func Comp
+funcPayloadMix = new Tree({ count: 0 }, 'IncrementFunc2', functionalComponentData, null);
+funcPayloadMix.componentData = {
+ ...funcPayloadMix.componentData,
+ hooksState: { count: 0 },
+ hooksIndex: [1],
+};
+mixPayload.children[0].children.push(deepCopy(funcPayloadMix));
+
+// Inner Class Comp
+classPayloadMix = new Tree({ count: 0 }, 'IncrementClass', classComponentData, null);
+classPayloadMix.componentData = {
+ ...classPayloadMix.componentData,
+ state: { count: 0 },
+ index: 2,
+};
+mixPayload.children[0].children.push(deepCopy(classPayloadMix));
+
+// console.dir(mixPayload, { depth: null });
diff --git a/src/backend/__tests__/index.html b/src/backend/__tests__/index.html
new file mode 100644
index 000000000..3bcdac167
--- /dev/null
+++ b/src/backend/__tests__/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+ Testing LinkFiber
+
+
+
+
+
diff --git a/src/backend/__tests__/linkFiber.test.ts b/src/backend/__tests__/linkFiber.test.ts
new file mode 100644
index 000000000..f2dcf7028
--- /dev/null
+++ b/src/backend/__tests__/linkFiber.test.ts
@@ -0,0 +1,311 @@
+/**
+ * @jest-environment node
+ */
+import linkFiberInitialization from '../routers/linkFiber';
+import timeJumpInitialization from '../controllers/timeJump';
+import componentActionsRecord from '../models/masterState';
+import {
+ root,
+ rootPayload,
+ classComponent,
+ classPayload,
+ updateClassPayload,
+ functionalComponent,
+ functionalPayload,
+ mixComponents,
+ mixPayload,
+} from './ignore/stateComponents-testcases';
+import { Status, FiberRoot } from '../types/backendTypes';
+import Tree from '../models/tree';
+import { DevTools } from '../types/linkFiberTypes';
+import { JSDOM } from 'jsdom';
+import path from 'path';
+import fs from 'fs';
+
+describe('linkFiber', () => {
+ let mode: Status;
+ let linkFiber: () => Promise;
+ let linkFiberDelayed: (resolve: any) => NodeJS.Timeout;
+ let timeJump: (targetSnapshot: Tree) => Promise;
+ let fiberRoot: FiberRoot;
+ let devTools: DevTools;
+ let onCommitFiberRootDelayed: (resolve: any) => NodeJS.Timeout;
+ const DELAY = 75; //ms
+ const mockPostMessage = jest.fn();
+ let dom: JSDOM;
+
+ beforeAll(() => {
+ // Set up a fake DOM environment with JSDOM
+ const indexHTML = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf-8');
+ dom = new JSDOM(indexHTML, { url: 'http://localhost' });
+ global.window = dom.window as unknown as Window & typeof globalThis;
+ global.document = dom.window._document;
+ });
+
+ afterAll(() => {
+ // Clean up the fake DOM environment
+ dom.window.close();
+ });
+
+ beforeEach(() => {
+ mode = {
+ jumping: false,
+ };
+ // Initialize Fiber Root:
+ fiberRoot = { current: root };
+
+ // Initialize linkFiber
+ linkFiber = linkFiberInitialization(mode);
+ // Since linkFiber invoke a throttle function that get delay for 70 ms, between each test, linkFiber need to be delayed for 75 ms to ensure no overlapping async calls.
+ linkFiberDelayed = (resolve) => setTimeout(async () => resolve(await linkFiber()), DELAY);
+
+ // Initialize timeJump
+ timeJump = timeJumpInitialization(mode);
+
+ // Set up mock postMessage function
+ window.postMessage = mockPostMessage;
+
+ // Set up mock React DevTools global hook
+ window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
+ renderers: new Map<1, { version: string }>([[1, { version: '16' }]]),
+ onCommitFiberRoot: (renderID = 0, root = fiberRoot, priortyLevel) => {},
+ getFiberRoots: (renderID = 0) => new Set([fiberRoot]),
+ };
+ devTools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
+ // Since onCommitFiberRoot invoke a throttle function that get delay for 70 ms, between each test, onCommitFiberRoot need to be delayed for 75 ms to ensure no overlapping async calls.
+ onCommitFiberRootDelayed = (resolve) =>
+ setTimeout(
+ async () => resolve(await devTools.onCommitFiberRoot(0, fiberRoot, 'high')),
+ DELAY,
+ );
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ // Clear the compoennt action record
+ componentActionsRecord.clear();
+ delete window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
+ });
+
+ describe('link fiber initiliaztion', () => {
+ it('link fiber should return a function', () => {
+ expect(typeof linkFiber).toBe('function');
+ });
+ it('returned function should not throw an error', async () => {
+ await expect(new Promise(linkFiberDelayed)).resolves.not.toThrowError();
+ });
+ });
+
+ describe('React dev tools and react app check', () => {
+ it('should not do anything if React Devtools is not installed', async () => {
+ delete window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
+ await expect(new Promise(linkFiberDelayed)).resolves.not.toThrowError();
+ expect(mockPostMessage).not.toHaveBeenCalled();
+ });
+
+ it('should post a message to front end that React DevTools is installed', async () => {
+ await new Promise(linkFiberDelayed);
+ expect(mockPostMessage).toHaveBeenCalled();
+ expect(mockPostMessage).toHaveBeenCalledWith(
+ {
+ action: 'devToolsInstalled',
+ payload: 'devToolsInstalled',
+ },
+ '*',
+ );
+ });
+
+ it('should post a message & send snapshot to the front end if the target application is a React App', async () => {
+ await new Promise(linkFiberDelayed);
+
+ expect(mockPostMessage).toHaveBeenCalledTimes(3);
+ // Post message for devTool Installed
+ expect(mockPostMessage).toHaveBeenCalledWith(
+ {
+ action: 'devToolsInstalled',
+ payload: 'devToolsInstalled',
+ },
+ '*',
+ );
+
+ // Post message for target is a react app
+ expect(mockPostMessage).toHaveBeenCalledWith(
+ {
+ action: 'aReactApp',
+ payload: 'aReactApp',
+ },
+ '*',
+ );
+ // Post message to send a snapshot of react app to front end
+ expect(mockPostMessage).toHaveBeenCalledWith(
+ {
+ action: 'recordSnap',
+ payload: rootPayload,
+ },
+ '*',
+ );
+ });
+
+ it('should not do anything if the target application is not a React App', async () => {
+ (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers = new Map();
+ await new Promise(linkFiberDelayed);
+ expect(mockPostMessage).toHaveBeenCalledTimes(1);
+ expect(mockPostMessage).not.toHaveBeenCalledTimes(3);
+ });
+ });
+
+ describe('document visibility', () => {
+ it('should initiate an event listener for visibility change', async () => {
+ const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
+ await new Promise(linkFiberDelayed);
+ expect(addEventListenerSpy).toHaveBeenCalledWith('visibilitychange', expect.any(Function));
+ });
+ it('should not send snapshot when document is hidden', async () => {
+ // Initialize linkFiber:
+ await new Promise(linkFiberDelayed);
+ expect(mockPostMessage).toHaveBeenCalledTimes(3);
+ // Simulate document hidden
+ Object.defineProperty(document, 'hidden', { value: true });
+ const visibilityChangeEvent = new window.Event('visibilitychange');
+ document.dispatchEvent(visibilityChangeEvent);
+ // Reset count of mockPostMessage
+ mockPostMessage.mockClear();
+ await new Promise(onCommitFiberRootDelayed);
+ // If document hidden, no message/snapshot will be posted
+ expect(mockPostMessage).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('addOneMoreStep', () => {
+ it('should monkey patch on onCommitFiberRoot', async () => {
+ // Obtain the original onCommitFiberRoot
+ const orginalOnCommitFiberRoot =
+ window.__REACT_DEVTOOLS_GLOBAL_HOOK__?.onCommitFiberRoot.toString();
+ await new Promise(linkFiberDelayed);
+ // Obtain the monkey patch (modified) onCommitFiberRoot
+ const monkeyPatchOnCommitFiberRoot =
+ window.__REACT_DEVTOOLS_GLOBAL_HOOK__?.onCommitFiberRoot.toString();
+ // The onCommitFiberRoot method should be been modifed
+ expect(orginalOnCommitFiberRoot).not.toEqual(monkeyPatchOnCommitFiberRoot);
+ });
+
+ it('should send a snapshot when new fiberRoot is committed for a class component', async () => {
+ // When first initialize linkFiber, should send snapShot of rootPayload
+ await new Promise(linkFiberDelayed);
+ expect(mockPostMessage).toHaveBeenCalledWith(
+ {
+ action: 'recordSnap',
+ payload: rootPayload,
+ },
+ '*',
+ );
+ mockPostMessage.mockClear();
+ // After modified fiberRoot to classComponent, onCommitFiberRoot should send snapSot of classPayload
+ fiberRoot = { current: classComponent };
+ await new Promise(onCommitFiberRootDelayed);
+ expect(mockPostMessage).toHaveBeenCalledWith(
+ {
+ action: 'recordSnap',
+ payload: classPayload,
+ },
+ '*',
+ );
+ });
+
+ it('should send a snapshot when new fiberRoot is committed for a functional component', async () => {
+ // When first initialize linkFiber, should send snapShot of rootPayload
+ await new Promise(linkFiberDelayed);
+ expect(mockPostMessage).toHaveBeenCalledWith(
+ {
+ action: 'recordSnap',
+ payload: rootPayload,
+ },
+ '*',
+ );
+ mockPostMessage.mockClear();
+
+ // After modified fiberRoot to functionalComponent, onCommitFiberRoot should send snapSot of functionalPayload
+ fiberRoot = { current: functionalComponent };
+ await new Promise(onCommitFiberRootDelayed);
+ expect(mockPostMessage).toHaveBeenCalledWith(
+ {
+ action: 'recordSnap',
+ payload: functionalPayload,
+ },
+ '*',
+ );
+ });
+
+ it('should send a snapshot when new fiberRoot is commited for mixture of components', async () => {
+ // When first initialize linkFiber, should send snapShot of rootPayload
+ await new Promise(linkFiberDelayed);
+ expect(mockPostMessage).toHaveBeenCalledWith(
+ {
+ action: 'recordSnap',
+ payload: rootPayload,
+ },
+ '*',
+ );
+ mockPostMessage.mockClear();
+
+ // After modified fiberRoot to mixComponents, onCommitFiberRoot should send snapSot of mixPayload
+ fiberRoot = { current: mixComponents };
+ await new Promise(onCommitFiberRootDelayed);
+ expect(mockPostMessage).toHaveBeenCalledWith(
+ {
+ action: 'recordSnap',
+ payload: mixPayload,
+ },
+ '*',
+ );
+ });
+ });
+
+ describe('mode unit tests', () => {
+ it('should not send snapshot if mode is jumping & not navigating', async () => {
+ // When first initialize linkFiber, should not have the update class payload
+ fiberRoot = { current: classComponent };
+ await new Promise(linkFiberDelayed);
+ mockPostMessage.mockClear();
+
+ // Simulate jumping and navigating
+ mode.jumping = true;
+ await new Promise(onCommitFiberRootDelayed);
+ // During jumping &/or navigating, should not post any message/snapshot to front end
+ expect(mockPostMessage).not.toHaveBeenCalled();
+ });
+
+ it('should update react fiber tree based on the payload from frontend when mode is navigating', async () => {
+ // When first initialize linkFiber, should not have the update class payload
+ fiberRoot = { current: classComponent };
+ await new Promise(linkFiberDelayed);
+ expect(mockPostMessage).not.toHaveBeenCalledWith(
+ {
+ action: 'recordSnap',
+ payload: updateClassPayload,
+ },
+ '*',
+ );
+ mockPostMessage.mockClear();
+
+ // Simulate jumping and navigating
+ mode.jumping = true;
+ mode.navigating = () => timeJump(updateClassPayload);
+ await new Promise(onCommitFiberRootDelayed);
+ // During jumping &/or navigating, should not post any message/snapshot to front end
+ expect(mockPostMessage).not.toHaveBeenCalled();
+
+ // After navigate, react application should have updateClassPayload
+ mode.jumping = false;
+ await new Promise(onCommitFiberRootDelayed);
+ expect(mockPostMessage).toHaveBeenCalledTimes(1);
+ expect(mockPostMessage).toHaveBeenCalledWith(
+ {
+ action: 'recordSnap',
+ payload: updateClassPayload,
+ },
+ '*',
+ );
+ });
+ });
+});
diff --git a/src/backend/__tests__/masterState.test.ts b/src/backend/__tests__/masterState.test.ts
new file mode 100644
index 000000000..2bc1c7a59
--- /dev/null
+++ b/src/backend/__tests__/masterState.test.ts
@@ -0,0 +1,84 @@
+import componentActionRecord from '../models/masterState';
+
+describe('Master State unit tests', () => {
+ describe('componentActionRecord unit tests', () => {
+ const component1 = { state: 'dummy state', props: {} };
+ const component2 = { state: 'dummy state2', props: {} };
+ const component3 = { state: 'dummy state3', props: {} };
+ let index1, index2, index3;
+ beforeEach(() => {
+ componentActionRecord.clear();
+ index1 = componentActionRecord.saveNew(component1);
+ index2 = componentActionRecord.saveNew(component2);
+ index3 = componentActionRecord.saveNew(component3);
+ });
+
+ describe('clear', () => {
+ it('should clear componentActionsRecord', () => {
+ componentActionRecord.saveNew(component1);
+ componentActionRecord.clear();
+ expect(componentActionRecord.getAllComponents()).toEqual([]);
+
+ componentActionRecord.saveNew(component2);
+ componentActionRecord.saveNew(component3);
+ componentActionRecord.clear();
+ expect(componentActionRecord.getAllComponents()).toEqual([]);
+ });
+ });
+
+ describe('saveNew', () => {
+ it('should add a new component to componentActionRecord and return its index', () => {
+ expect(index1).toEqual(0);
+ expect(index2).toEqual(1);
+ expect(index3).toEqual(2);
+
+ expect(componentActionRecord.getAllComponents()).toHaveLength(3);
+
+ expect(componentActionRecord.getAllComponents()[index1]).toBe(component1);
+ expect(componentActionRecord.getAllComponents()[index2]).toBe(component2);
+ expect(componentActionRecord.getAllComponents()[index3]).toBe(component3);
+ });
+ });
+
+ describe('getComponentByIndex', () => {
+ it('should return the component at the specified index', () => {
+ expect(componentActionRecord.getComponentByIndex(index1)).toBe(component1);
+ expect(componentActionRecord.getComponentByIndex(index2)).toBe(component2);
+ expect(componentActionRecord.getComponentByIndex(index3)).toBe(component3);
+ });
+
+ it('should return undefined when passed an index that does not exist', () => {
+ expect(componentActionRecord.getComponentByIndex(3)).toBeUndefined();
+ });
+ });
+
+ describe('getComponentByIndexHooks', () => {
+ it('should return the components at the specified indices', () => {
+ expect(componentActionRecord.getComponentByIndexHooks([index1])).toContain(component1);
+ expect(componentActionRecord.getComponentByIndexHooks([0, 1, 2])).toEqual([
+ component1,
+ component2,
+ component3,
+ ]);
+ });
+
+ it('should return undefined when passed an empty array', () => {
+ expect(componentActionRecord.getComponentByIndexHooks([])).toEqual([]);
+ });
+
+ it('should return undefined when passed an index that does not exist', () => {
+ expect(componentActionRecord.getComponentByIndexHooks([3])).toEqual([]);
+ });
+ });
+
+ describe('getAllComponents', () => {
+ it('should return all components in componentActionRecord', () => {
+ expect(componentActionRecord.getAllComponents()).toEqual([
+ component1,
+ component2,
+ component3,
+ ]);
+ });
+ });
+ });
+});
diff --git a/src/backend/__tests__/masterTree.test.tsx b/src/backend/__tests__/masterTree.test.tsx
new file mode 100644
index 000000000..5b3d404f1
--- /dev/null
+++ b/src/backend/__tests__/masterTree.test.tsx
@@ -0,0 +1,608 @@
+import createTree from '../controllers/createTree';
+import componentActionsRecord from '../models/masterState';
+import createComponentActionsRecord from '../controllers/createComponentActionsRecord';
+import {
+ Fiber,
+ FunctionComponent,
+ ClassComponent,
+ IndeterminateComponent,
+ ComponentData,
+ WorkTag,
+} from '../types/backendTypes';
+import { IncrementFuncMultiStates } from './ignore/IncrementFunc';
+import Tree from '../models/tree';
+import {
+ root,
+ functionalComponent,
+ functionalPayload,
+ classComponent,
+ classPayload,
+} from './ignore/stateComponents-testcases';
+
+import { serializeState } from '../models/tree';
+import {
+ allowedComponentTypes,
+ nextJSDefaultComponent,
+ remixDefaultComponents,
+ exclude,
+} from '../models/filterConditions';
+import deepCopy from './ignore/deepCopy';
+import { Children } from 'react';
+import _ from 'lodash';
+
+describe('master tree tests', () => {
+ let treeRoot: Tree;
+ let mockFiberNode: Fiber;
+ const mockComponentData: ComponentData = {
+ actualDuration: 1,
+ actualStartTime: 2,
+ selfBaseDuration: 3,
+ treeBaseDuration: 4,
+ key: null,
+ context: {},
+ hooksIndex: null,
+ hooksState: null,
+ index: null,
+ props: {},
+ state: null,
+ };
+ let mockFiberTree: Tree;
+ /** `mockChildNode` is a CLASS COMPONENT*/
+ let mockChildNode: Fiber;
+ let mockChildTree: Tree;
+
+ /** `mockSibilingNode` is a FUNCTIONAL COMPONENT*/
+ let mockSiblingNode: Fiber;
+ let mockSiblingTree: Tree;
+
+ beforeEach(() => {
+ // create tree root:
+ treeRoot = new Tree('root', 'root');
+ // create a mock Fiber node with relevant properties
+ mockFiberNode = { ...root, tag: IndeterminateComponent };
+ mockFiberTree = new Tree('root', 'root');
+ mockFiberTree.addChild('stateless', 'nameless', mockComponentData, null);
+
+ // create a mock child Fiber node with relevant properties for class component
+ mockChildNode = deepCopy(classComponent);
+ // Since payload will have a root then the component, need to extract the child component
+ mockChildTree = deepCopy(classPayload.children[0]);
+
+ // create a mock sibling Fiber node with relevant properties for class component
+ mockSiblingNode = deepCopy(functionalComponent);
+ // Since payload will have a root then the component, need to extract the child component
+ mockSiblingTree = deepCopy(functionalPayload.children[0]);
+
+ // clear the saved component actions record
+ componentActionsRecord.clear();
+ });
+
+ describe('createTree Unit test', () => {
+ describe('Filter components that are from NextJS, Remix or not from allowed component types', () => {
+ it('should return a Tree if we pass in a empty fiber node', () => {
+ const tree = createTree(mockFiberNode);
+ expect(tree).toEqual(mockFiberTree);
+ });
+
+ it('should filter out NextJS default components with no children or siblings', () => {
+ for (let name of nextJSDefaultComponent) {
+ mockFiberNode.elementType = { name };
+ const tree = createTree(mockFiberNode);
+ expect(tree).toEqual(treeRoot);
+ expect(tree).not.toEqual(mockFiberTree);
+ }
+ });
+ it('should filter out remix default components with no children or siblings', () => {
+ for (let name of remixDefaultComponents) {
+ mockFiberNode.elementType = { name };
+ const tree = createTree(mockFiberNode);
+ expect(tree).toEqual(treeRoot);
+ expect(tree).not.toEqual(mockFiberTree);
+ }
+ });
+
+ it('should only traverse allowed components', () => {
+ for (let tag: any = 1; tag <= 24; tag++) {
+ mockFiberNode.tag = tag;
+ const tree = createTree(mockFiberNode);
+ if (allowedComponentTypes.has(tag)) {
+ expect(tree).toEqual(mockFiberTree);
+ } else {
+ expect(tree).toEqual(treeRoot);
+ }
+ }
+ });
+ it('should filter out NextJS & Remix default components with children and/or siblings', () => {
+ (mockChildTree.componentData as ComponentData).index = 0;
+ (mockSiblingTree.componentData as ComponentData).hooksIndex = [1];
+ treeRoot.children.push(mockChildTree);
+ treeRoot.children.push(mockSiblingTree);
+ for (let name of nextJSDefaultComponent) {
+ componentActionsRecord.clear(); // reset index counts for component actions
+ mockFiberNode.elementType = { name };
+ mockFiberNode.child = mockChildNode;
+ mockFiberNode.sibling = mockSiblingNode;
+ const tree = createTree(mockFiberNode);
+ const children = tree.children;
+ expect(children.length).toEqual(2);
+ expect(children[0]).toEqual(mockChildTree);
+ expect(children[1]).toEqual(mockSiblingTree);
+ expect(tree).toEqual(treeRoot);
+ }
+ for (let name of remixDefaultComponents) {
+ componentActionsRecord.clear(); // reset index counts for component actions
+ mockFiberNode.elementType = { name };
+ mockFiberNode.child = mockChildNode;
+ mockFiberNode.sibling = mockSiblingNode;
+ const tree = createTree(mockFiberNode);
+ const children = tree.children;
+ expect(children.length).toEqual(2);
+ expect(children[0]).toEqual(mockChildTree);
+ expect(children[1]).toEqual(mockSiblingTree);
+ expect(tree).toEqual(treeRoot);
+ }
+ });
+ });
+ describe('Display component props information', () => {
+ const memoizedProps = {
+ propVal: 0,
+ propFunc: jest.fn,
+ propObj: { dummy: 'dummy' },
+ };
+ const props = {
+ propVal: 0,
+ propFunc: 'function',
+ propObj: JSON.stringify({ dummy: 'dummy' }),
+ };
+ it('should display functional props information', () => {
+ mockChildNode.memoizedProps = memoizedProps;
+ (mockChildTree.componentData as ComponentData).props = props;
+ treeRoot.children.push(mockChildTree);
+
+ const tree = createTree(mockChildNode);
+ expect(tree).toEqual(treeRoot);
+ });
+ it('should display class props information', () => {
+ // Assign mock properties to the child node
+ mockChildNode.memoizedProps = memoizedProps;
+ (mockChildTree.componentData as ComponentData).props = props;
+
+ // Set up an isolated copy of the root tree
+ const isolatedTreeRoot = deepCopy(treeRoot);
+ isolatedTreeRoot.children.push(mockChildTree);
+
+ // Generate the tree
+ const tree = createTree(mockChildNode);
+
+ // Debugging: Log actual and expected tree for comparison
+ console.log('Generated Tree:', JSON.stringify(tree, null, 2));
+ console.log('Expected Tree:', JSON.stringify(isolatedTreeRoot, null, 2));
+
+ // Perform the assertion
+ expect(tree).toEqual(isolatedTreeRoot);
+ });
+
+ it('should display React router props information', () => {
+ (mockSiblingTree.componentData as ComponentData) = {
+ ...(mockSiblingTree.componentData as ComponentData),
+ props: { pathname: '/tictactoe' },
+ hooksIndex: null,
+ hooksState: null,
+ };
+ mockSiblingTree.state = 'stateless';
+
+ // For components in react router, there are different way to extract pathname prop
+ const reactRouterProp = {
+ Router: { location: { pathname: '/tictactoe' } },
+ RenderedRoute: { match: { pathname: '/tictactoe' } },
+ };
+
+ for (const componentName in reactRouterProp) {
+ // Router Component
+ mockSiblingNode.memoizedProps = {
+ ...memoizedProps,
+ ...reactRouterProp[componentName],
+ };
+ mockSiblingNode.elementType = { name: componentName };
+ mockSiblingTree.name = componentName;
+ treeRoot.children = [mockSiblingTree];
+
+ const routerTree = createTree(mockSiblingNode);
+ expect(routerTree).toEqual(treeRoot);
+ }
+ });
+ it('should exclude reserved props name', () => {
+ (mockChildTree.componentData as ComponentData).props = props;
+ treeRoot.children.push(mockChildTree);
+
+ for (let propName of exclude) {
+ componentActionsRecord.clear(); // reset index counts for component actions
+ mockChildNode.memoizedProps = { ...memoizedProps, [propName]: 'anything' };
+ const tree = createTree(mockChildNode);
+ expect(tree).toEqual(treeRoot);
+ }
+ });
+ it('should skip circular props', () => {
+ mockChildNode.memoizedProps = {
+ ...memoizedProps,
+ cir: mockChildNode,
+ noCir: 'Not a circular props',
+ };
+ (mockChildTree.componentData as ComponentData).props = {
+ ...props,
+ noCir: 'Not a circular props',
+ };
+ treeRoot.children.push(mockChildTree);
+
+ const tree = createTree(mockChildNode);
+ expect(tree).toEqual(treeRoot);
+ });
+
+ it('should display props information of multiple components', () => {
+ // Set up Fiber Node tree (root => child1 => child2 & sibling1)
+ mockChildNode.memoizedProps = memoizedProps;
+ const child1 = deepCopy(mockChildNode);
+ child1.memoizedProps.name = 'child1';
+ const child2 = deepCopy(mockChildNode);
+ child2.memoizedProps.name = 'child2';
+ mockSiblingNode.memoizedProps = memoizedProps;
+ const sibling1 = deepCopy(mockSiblingNode);
+ sibling1.memoizedProps.name = 'sibling1';
+
+ // Link nodes
+ mockFiberNode.child = child1;
+ child1.child = child2;
+ child2.sibling = sibling1;
+
+ // Set up expected tree structure
+ const isolatedMockFiberTree = deepCopy(mockFiberTree);
+ (mockChildTree.componentData as ComponentData).props = props;
+
+ const childTree1 = deepCopy(mockChildTree);
+ childTree1.name = 'IncrementClass1';
+ (childTree1.componentData as ComponentData).props.name = 'child1';
+ (childTree1.componentData as ComponentData).index = 0;
+
+ const childTree2 = deepCopy(mockChildTree);
+ childTree2.name = 'IncrementClass2';
+ (childTree2.componentData as ComponentData).props.name = 'child2';
+ (childTree2.componentData as ComponentData).index = 1;
+
+ const siblingTree1 = deepCopy(mockSiblingTree);
+ siblingTree1.name = 'IncrementFunc';
+ (siblingTree1.componentData as ComponentData).props.name = 'sibling1';
+ (siblingTree1.componentData as ComponentData).hooksIndex = [2];
+
+ isolatedMockFiberTree.children[0].children = [childTree1];
+ childTree1.children.push(childTree2, siblingTree1);
+
+ // Generate the actual tree
+ const tree = createTree(mockFiberNode);
+
+ // Assertions
+ expect(tree).toEqual(isolatedMockFiberTree);
+ });
+ });
+ describe('Display component states information', () => {
+ const stateNode = {
+ state: {
+ propVal: 0,
+ propObj: { dummy: 'dummy' },
+ },
+ setState: function (cb: Function) {
+ this.state = cb();
+ }.bind(this),
+ };
+ const classState = stateNode.state;
+
+ const memoizedState = {
+ memoizedState: { dummy: 'dummy' },
+ next: null,
+ queue: {
+ dispatch: function (newState) {
+ this.memoizedState = newState;
+ }.bind(this),
+ },
+ };
+ const memoizedState2 = {
+ memoizedState: { dummy2: 'dummy2' },
+ next: null,
+ queue: {
+ dispatch: function (newState) {
+ this.memoizedState = newState;
+ }.bind(this),
+ },
+ };
+ // Note: the key count is the variable state name within the incrementFunction that is assigned to mockSiblingTree
+ const functionalState = { count: memoizedState.memoizedState };
+ const functionalState2 = { count1: memoizedState2.memoizedState };
+ it('should display stateless if functional state empty', () => {
+ // Construct Fiber Node (root => childNode)
+ mockChildNode.stateNode = null;
+ const tree = createTree(mockChildNode);
+
+ // Construct Result Tree (root => childTree)
+ mockChildTree.state = 'stateless';
+ (mockChildTree.componentData as ComponentData).state = null;
+ (mockChildTree.componentData as ComponentData).index = null;
+ treeRoot.children.push(mockChildTree);
+
+ // Compare the two trees:
+ expect(tree).toEqual(treeRoot);
+ });
+
+ it('should display functional state information', () => {
+ // Set up mock Fiber node for functional state
+ mockSiblingNode.memoizedState = {
+ memoizedState: { dummy: 'dummy' },
+ queue: {}, // Required for useState hooks
+ next: null,
+ };
+
+ // Generate the tree
+ const tree = createTree(mockSiblingNode);
+
+ // Set up expected tree structure
+ mockSiblingTree.state = functionalState;
+ (mockSiblingTree.componentData as ComponentData).hooksState = functionalState;
+ (mockSiblingTree.componentData as ComponentData).hooksIndex = [0];
+ treeRoot.children.push(mockSiblingTree);
+
+ // Assertions
+ expect(tree).toBe(treeRoot);
+ });
+
+ it('should keep track of class state index', () => {
+ // Construct Fiber Node (root => FiberNode => child1 => child 2 & 3)
+ mockChildNode.stateNode = stateNode;
+ const child1 = deepCopy(mockChildNode);
+ const child2 = deepCopy(mockChildNode);
+ const child3 = deepCopy(mockChildNode);
+ mockFiberNode.child = child1;
+ child1.child = child2;
+ child2.sibling = child3;
+ const tree = createTree(mockFiberNode);
+
+ // Construct result tree (root => FiberTree => childTree1 => childTree2 & childTree3)
+ (mockChildTree.componentData as ComponentData).state = classState;
+ mockChildTree.state = classState;
+ const childTree1 = deepCopy(mockChildTree);
+ childTree1.name = 'IncrementClass1';
+ (childTree1.componentData as ComponentData).index = 0;
+ const childTree2 = deepCopy(mockChildTree);
+ childTree2.name = 'IncrementClass2';
+ (childTree2.componentData as ComponentData).index = 1;
+ const childTree3 = deepCopy(mockChildTree);
+ childTree3.name = 'IncrementClass3';
+ (childTree3.componentData as ComponentData).index = 2;
+ mockFiberTree.children[0].children = [childTree1];
+ childTree1.children.push(childTree2, childTree3);
+
+ // Compare the two trees:
+ expect(tree).toEqual(mockFiberTree);
+ });
+
+ it('should display stateless if functional state empty', () => {
+ // Construct Fiber Node (root => siblingNode)
+ mockSiblingNode.memoizedState = null;
+ const tree = createTree(mockSiblingNode);
+ // Construct Result Tree (root => siblingTree)
+
+ mockSiblingTree.state = 'stateless';
+ (mockSiblingTree.componentData as ComponentData).hooksState = null;
+ (mockSiblingTree.componentData as ComponentData).hooksIndex = null;
+ treeRoot.children.push(mockSiblingTree);
+
+ // Compare the two trees:
+ expect(tree).toEqual(treeRoot);
+ });
+
+ it('should display functional state information', () => {
+ // Set up mock Fiber node for functional state
+ mockSiblingNode.memoizedState = {
+ memoizedState: { dummy: 'dummy' },
+ queue: {}, // Required for useState hooks
+ next: null,
+ };
+
+ // Create tree
+ const tree = createTree(mockSiblingNode);
+
+ // Set up expected tree structure
+ mockSiblingTree.state = functionalState;
+ (mockSiblingTree.componentData as ComponentData).hooksState = functionalState;
+ (mockSiblingTree.componentData as ComponentData).hooksIndex = [0]; // Single hook index
+ mockSiblingTree.name = 'IncrementFunc'; // Ensure name matches
+ treeRoot.children.push(mockSiblingTree);
+
+ // Compare the actual tree with the expected tree
+ expect(tree).toEqual(treeRoot);
+ });
+
+ it('should keep track of functional state index', () => {
+ // Construct Fiber Node (root => FiberNode => sibling1 => sibling 2 & 3)
+ // sibling 3 will have 2 states
+ mockSiblingNode.memoizedState = memoizedState;
+ const sibling1 = deepCopy(mockSiblingNode);
+ const sibling2 = deepCopy(mockSiblingNode);
+ const sibling3 = deepCopy(mockSiblingNode);
+ sibling3.memoizedState.next = memoizedState2;
+ sibling3.elementType = IncrementFuncMultiStates;
+ mockFiberNode.child = sibling1;
+ sibling1.child = sibling2;
+ sibling2.sibling = sibling3;
+ const tree = createTree(mockFiberNode);
+
+ // Construct result tree (root => FiberTree => siblingTree1 => siblingTree2 & siblingTree3)
+ // sibling 3 will have 2 states
+ mockSiblingTree.state = functionalState;
+ (mockSiblingTree.componentData as ComponentData).hooksState = functionalState;
+ const siblingTree1 = deepCopy(mockSiblingTree);
+ siblingTree1.name = 'IncrementFunc1';
+ (siblingTree1.componentData as ComponentData).hooksIndex = [0];
+ const siblingTree2 = deepCopy(mockSiblingTree);
+ siblingTree2.name = 'IncrementFunc2';
+ (siblingTree2.componentData as ComponentData).hooksIndex = [1];
+ const siblingTree3 = deepCopy(mockSiblingTree);
+ siblingTree3.name = 'IncrementFuncMultiStates';
+ siblingTree3.state = { ...functionalState, ...functionalState2 };
+ Object.assign((siblingTree3.componentData as ComponentData).hooksState!, functionalState2);
+ (siblingTree3.componentData as ComponentData).hooksIndex = [2, 3];
+ mockFiberTree.children[0].children = [siblingTree1];
+ siblingTree1.children.push(siblingTree2, siblingTree3);
+
+ // Compare the two trees:
+ expect(tree).toEqual(mockFiberTree);
+ });
+ });
+ });
+
+ describe('Tree unit test', () => {
+ describe('Serialize state unit test', () => {
+ it('should create a deep copy of state', () => {
+ const dummyState = {
+ counter: 1,
+ playerOne: 'X',
+ board: [
+ ['', 'O', 'X'],
+ ['', 'O', 'X'],
+ ['O', 'X', ''],
+ ],
+ };
+ const serializedState = serializeState(dummyState);
+
+ expect(dummyState).toEqual(serializedState);
+ expect(dummyState).not.toBe(serializedState);
+ });
+
+ it('should detect circular state', () => {
+ const circularState: { [key: string]: any } = {};
+ circularState.circ = circularState;
+ const serializedCircularState = serializeState(circularState);
+
+ expect(serializedCircularState).toEqual('circularState');
+ });
+ });
+
+ describe('Constructing a default tree', () => {
+ const newTree: Tree = new Tree({});
+ it('should be able to create a newTree', () => {
+ expect(newTree).toBeInstanceOf(Tree);
+ expect(newTree.state).toEqual({});
+ });
+
+ it('should have 6 properties', () => {
+ expect(newTree).toHaveProperty('state');
+ expect(newTree).toHaveProperty('name');
+ expect(newTree).toHaveProperty('componentData');
+ expect(newTree).toHaveProperty('children');
+ expect(newTree).toHaveProperty('isExpanded');
+ expect(newTree).toHaveProperty('rtid');
+ });
+
+ it('should have default name, componentData, isExpanded and rtid', () => {
+ expect(newTree.name).toBe('nameless');
+ expect(newTree.componentData).toEqual({});
+ expect(newTree.isExpanded).toBe(true);
+ expect(newTree.rtid).toBe(null);
+ });
+
+ it('should have no children', () => {
+ expect(newTree.children).toHaveLength(0);
+ });
+ });
+
+ describe('Constructing a tree root', () => {
+ const root: Tree = new Tree('root', 'root');
+ it("should have name as 'root' and state as 'root'", () => {
+ expect(root).toBeInstanceOf(Tree);
+ expect(root.state).toBe('root');
+ expect(root.name).toBe('root');
+ expect(root.componentData).toEqual({});
+ expect(root.isExpanded).toBe(true);
+ expect(root.rtid).toBe(null);
+ expect(root.children).toHaveLength(0);
+ });
+ });
+
+ describe('Adding children', () => {
+ let newTree: Tree;
+ let child: Tree;
+ beforeEach(() => {
+ newTree = new Tree('root', 'root');
+ child = newTree.addChild('stateful', 'child', {}, null);
+ });
+
+ it('should return a new tree child', () => {
+ expect(child).toBeInstanceOf(Tree);
+ expect(child).toBeInstanceOf(Tree);
+ expect(child.state).toBe('stateful');
+ expect(child.name).toBe('child');
+ expect(child.componentData).toEqual({});
+ expect(child.isExpanded).toBe(true);
+ expect(child.rtid).toBe(null);
+ expect(child.children).toHaveLength(0);
+ });
+
+ it("should have the child be in the children's array property", () => {
+ expect(newTree.children).toHaveLength(1);
+ expect(newTree.children).toContain(child);
+ expect(newTree.children[0]).toBe(child);
+ });
+
+ it('should have unique name', () => {
+ const nextChild1: Tree = child.addChild('stateful', 'child', {}, null);
+ const nextChild2: Tree = child.addChild('stateful', 'child', {}, null);
+ expect(child.children).toHaveLength(2);
+ expect(child.children[0]).toBe(nextChild1);
+ expect(child.children[1]).toBe(nextChild2);
+ expect(nextChild1.name).toBe('child2');
+ expect(nextChild2.name).toBe('child3');
+ });
+ });
+
+ describe('createComponentActionsRecord unit test', () => {
+ it('should save a new component action record if the Fiber node is a stateful class component', () => {
+ mockFiberNode.tag = ClassComponent;
+ mockFiberNode.stateNode = {
+ state: { counter: 0 }, // a mock state object
+ setState: jest.fn(), // a mock setState method
+ };
+ createComponentActionsRecord(mockFiberNode);
+ expect(componentActionsRecord.getComponentByIndex(0)).toBe(mockFiberNode.stateNode);
+ });
+
+ it('should save a new component action record if the Fiber node is a stateful class component with props', () => {
+ mockFiberNode.tag = ClassComponent;
+ // a mock state object
+ mockFiberNode.stateNode = {
+ state: { counter: 0 },
+ props: { start: 0 },
+ setState: jest.fn(), // a mock setState method
+ };
+ createComponentActionsRecord(mockFiberNode);
+ expect(componentActionsRecord.getComponentByIndex(0)).toMatchObject({
+ props: mockFiberNode.stateNode.props,
+ state: mockFiberNode.stateNode.state,
+ });
+ });
+
+ it('should save a new component action record if the Fiber node is a functional component with state', () => {
+ mockFiberNode.tag = FunctionComponent;
+ mockFiberNode.memoizedState = {
+ queue: [{}, { state: { value: 'test' } }], // a mock memoizedState object
+ };
+ createComponentActionsRecord(mockFiberNode);
+ expect(componentActionsRecord.getComponentByIndex(0)).toBe(
+ mockFiberNode.memoizedState.queue,
+ );
+ });
+
+ it('should not save a new component action record if the Fiber node is not a relevant component type', () => {
+ mockFiberNode.tag = 4; // HostRoot
+ createComponentActionsRecord(mockFiberNode);
+ expect(componentActionsRecord.getAllComponents()).toHaveLength(0);
+ });
+ });
+ });
+});
diff --git a/src/backend/__tests__/routes.test.ts b/src/backend/__tests__/routes.test.ts
new file mode 100644
index 000000000..cacde2df9
--- /dev/null
+++ b/src/backend/__tests__/routes.test.ts
@@ -0,0 +1,106 @@
+/**
+ * @jest-environment node
+ */
+import { JSDOM } from 'jsdom';
+
+import { Routes, Route } from '../models/routes';
+
+describe('Route class testing', () => {
+ let routes: Routes;
+ let dom: JSDOM;
+ beforeAll(() => {
+ // Set up a fake DOM environment with JSDOM
+ dom = new JSDOM('', { url: 'http://localhost' });
+ global.window = dom.window as unknown as Window & typeof globalThis;
+ global.document = dom.window._document;
+ });
+
+ afterAll(() => {
+ // Clean up the fake DOM environment
+ dom.window.close();
+ });
+ beforeEach(() => {
+ routes = new Routes();
+ window.history.replaceState = jest.fn();
+ window.history.pushState = jest.fn();
+ });
+
+ describe('Route initialization', () => {
+ it('should create a new instance of Route class', () => {
+ const route = new Route('/home', 1);
+ expect(route.url).toBe('/home');
+ expect(route.id).toBe(1);
+ });
+ });
+
+ describe('Routes class addRoute and navigate methods', () => {
+ it('should add a new route to the route history array', () => {
+ const route = routes.addRoute('/home');
+ expect(route.url).toBe('/home');
+ expect(route.id).toBe(1);
+ expect(routes.routeHistory).toHaveLength(2);
+ });
+
+ it('should add multiple routes to the route history array', () => {
+ const route = routes.addRoute('/home');
+ const route2 = routes.addRoute('/about');
+ expect(route.url).toBe('/home');
+ expect(route.id).toBe(1);
+ expect(route2.url).toBe('/about');
+ expect(route2.id).toBe(2);
+ expect(routes.routeHistory).toHaveLength(3);
+ });
+
+ it('should return the current route if the user has not navigated away from it', () => {
+ const route = routes.addRoute('/home');
+ const currentRoute = routes.addRoute('/home');
+ expect(currentRoute).toEqual(route);
+ expect(routes.routeHistory).toHaveLength(2);
+ });
+
+ it('should navigate to the target route in the route history stack', () => {
+ const homeRoute = routes.addRoute('/home');
+ const aboutRoute = routes.addRoute('/about');
+ const result = routes.navigate(homeRoute);
+ expect(result).toBe(true);
+ expect(routes.current).toBe(1);
+ const result2 = routes.navigate(aboutRoute);
+ expect(result2).toBe(true);
+ expect(routes.current).toBe(2);
+ });
+
+ it('should not navigate to the target route if it is the current route', () => {
+ const homeRoute = routes.addRoute('/home');
+ const result = routes.navigate(homeRoute);
+ expect(result).toBe(false);
+ expect(routes.current).toBe(1);
+ });
+
+ it('should throw an error if target route is not found', () => {
+ const dummyRoute: Route = new Route('/error', 0);
+ expect(() => routes.navigate(dummyRoute)).toThrowError();
+ });
+ });
+
+ describe('Routes rebuildHistory method', () => {
+ describe('rebuildHistory', () => {
+ it('should replace the current URL in the history stack with the given URL', () => {
+ // Mock the `routeHistory` array with three routes
+ const route1 = new Route('/home', 0);
+ const route2 = new Route('/about', 1);
+ const route3 = new Route('/contact', 2);
+ const route4 = new Route('/portfolio', 3);
+ routes.routeHistory = [route1, route2, route3, route4];
+ // Set current route to 2nd page and try to add the home page
+ routes.current = 1;
+ // Add home page
+ routes.addRoute(route1.url);
+ // Expect the `replaceState` method to have been called with the URL of the third route
+ expect(window.history.replaceState).toHaveBeenCalledWith('', '', route3.url);
+ // Expect the `pushState` method to have been called with the URL of the third route
+ expect(window.history.pushState).toHaveBeenCalledWith('', '', route4.url);
+ expect(window.history.pushState).toHaveBeenCalledWith('', '', route1.url);
+ });
+ });
+ });
+});
diff --git a/src/backend/__tests__/throttle.test.ts b/src/backend/__tests__/throttle.test.ts
new file mode 100644
index 000000000..277d211b4
--- /dev/null
+++ b/src/backend/__tests__/throttle.test.ts
@@ -0,0 +1,64 @@
+import throttle from '../controllers/throttle';
+
+describe('throttle unit tests', () => {
+ const mockCallback = jest.fn();
+
+ beforeEach(() => {
+ jest.useFakeTimers();
+ mockCallback.mockClear();
+ });
+
+ afterEach(() => {
+ jest.runOnlyPendingTimers();
+ jest.useRealTimers();
+ });
+
+ it('should return a function', () => {
+ const result = throttle(mockCallback, 1000);
+ expect(typeof result).toBe('function');
+ });
+
+ it('throttled function should be called with the correct arguments', () => {
+ const throttledFunc = throttle(mockCallback, 1000);
+
+ throttledFunc(1, 2, 3);
+
+ expect(mockCallback).toHaveBeenCalledWith(1, 2, 3);
+ });
+
+ it('should invoke the callback immediately on the first call', () => {
+ const throttledFunc = throttle(mockCallback, 1000);
+
+ throttledFunc();
+ expect(mockCallback).toHaveBeenCalled();
+ });
+
+ it('should invoke the callback only once if called multiple times within the throttling interval', () => {
+ const throttledFunc = throttle(mockCallback, 1000);
+
+ throttledFunc();
+ throttledFunc();
+ throttledFunc();
+ jest.advanceTimersByTime(500);
+
+ expect(mockCallback).toHaveBeenCalledTimes(1);
+ });
+
+ it('should invoke the callback multiple times if called outside of the throttling interval', () => {
+ const throttledFunc = throttle(mockCallback, 1000);
+
+ throttledFunc();
+ jest.advanceTimersByTime(500);
+ expect(mockCallback).toHaveBeenCalledTimes(1);
+
+ throttledFunc();
+ jest.advanceTimersByTime(500);
+ expect(mockCallback).toHaveBeenCalledTimes(2);
+
+ throttledFunc();
+ jest.advanceTimersByTime(100);
+ throttledFunc();
+ jest.advanceTimersByTime(901);
+ expect(mockCallback).toHaveBeenCalledTimes(3);
+ });
+});
diff --git a/src/backend/controllers/createComponentActionsRecord.ts b/src/backend/controllers/createComponentActionsRecord.ts
new file mode 100644
index 000000000..3c40fec4b
--- /dev/null
+++ b/src/backend/controllers/createComponentActionsRecord.ts
@@ -0,0 +1,108 @@
+import { Fiber } from '../types/backendTypes';
+import {
+ FunctionComponent,
+ ClassComponent,
+ IndeterminateComponent,
+ ContextProvider,
+} from '../types/backendTypes';
+import componentActionsRecord from '../models/masterState';
+import { getHooksStateAndUpdateMethod } from './statePropExtractors';
+import {
+ nextJSDefaultComponent,
+ remixDefaultComponents,
+ allowedComponentTypes,
+} from '../models/filterConditions';
+
+// ------------------------CREATE COMPONENT ACTIONS RECORD----------------------
+/**
+ * This is a recursive function that runs after Fiber commit, if user is navigating to a new route during jumping. This function performs the following logic:
+ * 1. Traverse from FiberRootNode
+ * 2. If the component is stateful, extract its update methods & push to the `componentActionRecord` array
+ * @param currentFiberNode A Fiber object
+ */
+export default function createComponentActionsRecord(currentFiberNode: Fiber): void {
+ // ------------------OBTAIN DATA FROM THE CURRENT FIBER NODE----------------
+ // Destructure the current fiber node
+ const {
+ sibling,
+ stateNode,
+ child,
+ // with memoizedState we can grab the root type and construct an Abstract Syntax Tree from the hooks structure using Acorn in order to extract the hook getters and match them with their corresponding setters in an object
+ memoizedState,
+ elementType,
+ tag,
+ } = currentFiberNode;
+
+ // Obtain component name:
+ const componentName =
+ elementType?._context?.displayName || //For ContextProvider
+ elementType?._result?.name || //For lazy Component
+ elementType?.render?.name ||
+ elementType?.name ||
+ 'nameless';
+
+ // --------------------FILTER COMPONENTS/FIBER NODE-------------------------
+ /**
+ * For the snapshot tree,
+ * 1. We are only interested in components that are one of these types: Function Component, Class Component, Indeterminate Component or Context Provider.
+ * NOTE: this list of components may change depending on future use
+ * 2. If user is using Next JS, filter out default NextJS components
+ * 3. If user is using Remix JS, filter out default Remix components
+ */
+
+ if (
+ !allowedComponentTypes.has(tag) ||
+ nextJSDefaultComponent.has(componentName) ||
+ remixDefaultComponents.has(componentName)
+ ) {
+ // -------------------TRAVERSE TO NEXT FIBERNODE------------------------
+ // If currentFiberNode has children, recurse on children
+ if (child) createComponentActionsRecord(child);
+
+ // If currentFiberNode has siblings, recurse on siblings
+ if (sibling) {
+ createComponentActionsRecord(sibling);
+ }
+ // ---------RETURN THE TREE OUTPUT & PASS TO FRONTEND FOR RENDERING-------
+ return;
+ }
+
+ // ---------OBTAIN STATE & SET STATE METHODS FROM CLASS COMPONENT-----------
+ // Check if node is a stateful class component when user use setState.
+ // If user use setState to define/manage state, the state object will be stored in stateNode.state => grab the state object stored in the stateNode.state
+ if ((tag === ClassComponent || tag === IndeterminateComponent) && stateNode?.state) {
+ // Save component setState() method to our componentActionsRecord for use during timeJump
+ componentActionsRecord.saveNew(stateNode);
+ }
+ // --------OBTAIN STATE & DISPATCH METHODS FROM FUNCTIONAL COMPONENT--------
+ // Check if node is a stateful class component when user use setState.
+ // If user use useState to define/manage state, the state object will be stored in memoizedState.queue => grab the state object stored in the memoizedState.queue
+ if (
+ (tag === FunctionComponent || tag === IndeterminateComponent || tag === ContextProvider) &&
+ memoizedState
+ ) {
+ if (memoizedState.queue) {
+ try {
+ // Hooks states are stored as a linked list using memoizedState.next,
+ // so we must traverse through the list and get the states.
+ // We then store them along with the corresponding memoizedState.queue,
+ // which includes the dispatch() function we use to change their state.
+ const hooksStates = getHooksStateAndUpdateMethod(memoizedState);
+ hooksStates.forEach(({ component }) => {
+ // Save component's state and dispatch() function to our record for future time-travel state changing. Add record index to snapshot so we can retrieve later
+ componentActionsRecord.saveNew(component);
+ });
+ } catch (err) {
+ console.log('ERROR: Failed Element during JSX parsing', {
+ componentName,
+ });
+ }
+ }
+ }
+ // ---------------------TRAVERSE TO NEXT FIBERNODE--------------------------
+ // If currentFiberNode has children, recurse on children
+ if (child) createComponentActionsRecord(child);
+
+ // If currentFiberNode has siblings, recurse on siblings
+ if (sibling) createComponentActionsRecord(sibling);
+}
diff --git a/src/backend/controllers/createTree.ts b/src/backend/controllers/createTree.ts
new file mode 100644
index 000000000..d49d134fe
--- /dev/null
+++ b/src/backend/controllers/createTree.ts
@@ -0,0 +1,298 @@
+// --------------------------START OF IMPORT------------------------------------
+import {
+ getHooksNames,
+ getHooksStateAndUpdateMethod,
+ // getStateAndContextData, //COMMENT OUT SINCE EXTRACTING CONTEXT IS STILL IN EXPERIMENT
+ filterAndFormatData,
+} from './statePropExtractors';
+
+import { Fiber, ComponentData } from '../types/backendTypes';
+import {
+ FunctionComponent,
+ ClassComponent,
+ IndeterminateComponent,
+ ContextProvider,
+} from '../types/backendTypes';
+
+import Tree from '../models/tree';
+import componentActionsRecord from '../models/masterState';
+import {
+ nextJSDefaultComponent,
+ remixDefaultComponents,
+ allowedComponentTypes,
+} from '../models/filterConditions';
+
+// -------------------------CREATE TREE TO SEND TO FRONT END--------------------
+/**
+ * This is a function that runs after every Fiber commit using the following logic:
+ * 1. Traverse from FiberRootNode
+ * 2. Create an instance of custom Tree class
+ * 3. Build a new state snapshot
+ * Every time a state change is made in the accompanying app, the extension creates a Tree “snapshot” of the current state, and adds it to the current “cache” of snapshots in the extension
+ * @param currentFiberNode A Fiber object
+ * @return An instance of a Tree object
+ */
+// TODO: Not sure why the ritd need to be outside of the _createTree function. Want to put inside, but in case this need to be keep track for front end.
+export default function createTree(currentFiberNode: Fiber): Tree {
+ let rtidCounter: number = 0;
+ return _createTree(currentFiberNode, new Tree('root', 'root'));
+
+ /**
+ * This is a helper function to recursively traverse the React Fiber Tree and craft the snapshot tree to send to front end
+ * @param currentFiberNode A Fiber object
+ * @param tree A Tree object, default initialized to an instance given 'root' and 'root'
+ * @returns An instance of a Tree Object
+ */
+ function _createTree(currentFiberNode: Fiber, tree: Tree): Tree {
+ // ------------------OBTAIN DATA FROM THE CURRENT FIBER NODE----------------
+ // Destructure the current fiber node:
+ const {
+ sibling,
+ stateNode,
+ child,
+ memoizedState,
+ memoizedProps,
+ elementType,
+ key,
+ tag,
+ actualDuration,
+ actualStartTime,
+ selfBaseDuration,
+ treeBaseDuration,
+ // _debugHookTypes, //COMMENT OUT SINCE EXTRACTING CONTEXT IS STILL IN EXPERIMENT
+ } = currentFiberNode;
+
+ // Obtain component name:
+ /** Name of the current component */
+ let componentName: string =
+ elementType?._context?.displayName || //For ContextProviders like Route, Navigation, Location
+ (elementType?._context && 'ContextProvider') || //Mark's note: useContext providers weren't showing up the way listed in the line above, I actually couldn't find the name of the context provider on the react dev tools fiber tree.
+ elementType?._result?.name || //For lazy Component
+ elementType?.render?.name ||
+ elementType?.name || //For Functional/Class Component
+ 'nameless';
+
+ // --------------------FILTER COMPONENTS/FIBER NODE-------------------------
+ /**
+ * For the snapshot tree,
+ * 1. We will only interested in components that are one of these types: Function Component, Class Component, Indeterminate Component or Context Provider.
+ * NOTE: this list of components may change depending on future use
+ * 2. If user use Next JS, filter out default NextJS components
+ * 3. If user use Remix JS, filter out default Remix components
+ */
+
+ if (
+ !allowedComponentTypes.has(tag) ||
+ nextJSDefaultComponent.has(componentName) ||
+ remixDefaultComponents.has(componentName)
+ ) {
+ // -------------------TRAVERSE TO NEXT FIBERNODE------------------------
+ // If currentFiberNode has children, recurse on children
+ if (child) _createTree(child, tree);
+
+ // If currentFiberNode has siblings, recurse on siblings
+ if (sibling) {
+ _createTree(sibling, tree);
+ }
+ // ---------RETURN THE TREE OUTPUT & PASS TO FRONTEND FOR RENDERING-------
+ return tree;
+ }
+
+ // --------------INITIALIZE OBJECT TO CONTAIN COMPONENT DATA---------------
+ let newState: 'stateless' | object = 'stateless';
+ let componentData: ComponentData = {
+ actualDuration,
+ actualStartTime,
+ selfBaseDuration,
+ treeBaseDuration,
+ key,
+ props: {},
+ context: {},
+ state: null,
+ index: null,
+ hooksState: null,
+ hooksIndex: null,
+ };
+
+ // ---------------APPEND PROP DATA FROM REACT DEV TOOL----------------------
+ // Check to see if the currentFiberNode has any props
+ if (memoizedProps) {
+ switch (elementType.name) {
+ // If component comes from React Router, extract only pathname:
+ case 'Router': {
+ componentData.props = { pathname: memoizedProps?.location?.pathname };
+ break;
+ }
+ case 'RenderedRoute': {
+ componentData.props = { pathname: memoizedProps?.match?.pathname };
+ break;
+ }
+ // For react-router components Route, Navigation, or Location, the elementType won't have a name property, so elementType.name will be undefined.
+ // The following switch case will be entered and will pass limited info to these element's props, but if none of the
+ // "if" statements are entered and a break statements isn't executed, the default case will still be entered
+ case undefined: {
+ if (elementType._context?.displayName === 'Route') {
+ componentData.props = { pathname: memoizedProps?.value?.matches?.[0]?.pathname };
+ break;
+ }
+ if (elementType._context?.displayName === 'Navigation') {
+ componentData.props = { basename: memoizedProps?.value?.basename };
+ break;
+ }
+ if (elementType._context?.displayName === 'Location') {
+ componentData.props = { pathname: memoizedProps?.value?.location?.pathname };
+ break;
+ }
+ }
+ // Else filter & format props data to ensure they are JSON stringify-able, before sending to front end
+ default: {
+ componentData.props = filterAndFormatData(memoizedProps);
+ }
+ }
+ }
+
+ // COMMENT OUT SINCE EXTRACTING CONTEXT IS STILL IN EXPERIMENT
+ // // ------------APPEND CONTEXT DATA FROM REACT DEV TOOL----------------
+ // // memoizedState
+ // // Note: if user use ReactHook, memoizedState.memoizedState can be a falsy value such as null, false, ... => need to specify this data is not undefined
+ // if (
+ // (tag === FunctionComponent || tag === ClassComponent || tag === IndeterminateComponent) &&
+ // memoizedState?.memoizedState !== undefined
+ // ) {
+ // // If user uses Redux, context data will be stored in memoizedState of the Provider component => grab context object stored in the memoizedState
+ // if (elementType.name === 'Provider') {
+ // Object.assign(
+ // componentData.context,
+ // getStateAndContextData(memoizedState, elementType.name, _debugHookTypes),
+ // );
+ // }
+ // // Else if user use ReactHook to define state => all states will be stored in memoizedState => grab all states stored in the memoizedState
+ // // else {
+ // // Object.assign(
+ // // componentData.state,
+ // // getStateAndContextData(memoizedState, elementType.name, _debugHookTypes),
+ // // );
+ // // }
+ // }
+ // // if user uses useContext hook, context data will be stored in memoizedProps.value of the Context.Provider component => grab context object stored in memoizedprops
+ // // Different from other provider, such as Routes, BrowserRouter, ReactRedux, ..., Context.Provider does not have a displayName
+ // // TODO: need to render this context provider when user use useContext hook.
+ //
+ //
+ // if (tag === ContextProvider && !elementType._context.displayName) {
+ // let stateData = memoizedProps.value;
+ // if (stateData === null || typeof stateData !== 'object') {
+ // stateData = { CONTEXT: stateData };
+ // }
+ // componentData.context = filterAndFormatData(stateData);
+ // componentName = 'Context';
+ // }
+
+ // ---------OBTAIN STATE & SET STATE METHODS FROM CLASS COMPONENT-----------
+ // Check if currentFiberNode is a stateful class component when user use setState.
+ // If user use setState to define/manage state, the state object will be stored in stateNode.state => grab the state object stored in the stateNode.state
+ // Example: for tic-tac-toe demo-app: Board is a stateful component that use setState to store state data.
+ if ((tag === ClassComponent || tag === IndeterminateComponent) && stateNode?.state) {
+ componentData.index = componentActionsRecord.saveNew(stateNode);
+ componentData.state = stateNode.state;
+ newState = componentData.state;
+ }
+
+ // --------OBTAIN STATE & DISPATCH METHODS FROM FUNCTIONAL COMPONENT--------
+ // Check if currentFiberNode is a stateful functional component when user use useState hook.
+ // If user use useState to define/manage state, the state object will be stored in memoizedState => grab the state object & its update method (dispatch) from memoizedState
+ // Example: for Stateful buttons demo-app: Increment is a stateful component that use useState hook to store state data.
+ // Inside the _createTree function where we handle functional components
+ if ((tag === FunctionComponent || tag === IndeterminateComponent) && memoizedState) {
+ if (memoizedState.queue) {
+ try {
+ const hooksStates = getHooksStateAndUpdateMethod(memoizedState);
+ const hooksNames = getHooksNames(elementType.toString());
+
+ componentData.hooksState = {};
+ componentData.reducerStates = []; // New array to store reducer states
+ componentData.hooksIndex = [];
+
+ hooksStates.forEach(({ state, component, isReducer, lastAction, reducer }, i) => {
+ componentData.hooksIndex.push(componentActionsRecord.saveNew(component));
+
+ if (isReducer) {
+ // Store reducer-specific information
+ componentData.reducerStates.push({
+ state,
+ lastAction,
+ reducerIndex: i,
+ hookName: hooksNames[i]?.hookName || `Reducer ${i}`,
+ });
+ } else {
+ // Regular useState hook
+ componentData.hooksState[hooksNames[i]?.varName || `State: ${i}`] = state;
+ }
+ });
+ newState = {
+ ...componentData.hooksState,
+ reducers: componentData.reducerStates,
+ };
+ } catch (err) {
+ console.log('Error extracting component state:', {
+ componentName,
+ memoizedState,
+ error: err,
+ });
+ }
+ }
+ }
+ if (tag === ContextProvider && !elementType._context.displayName) {
+ let stateData = memoizedProps.value;
+ if (stateData === null || typeof stateData !== 'object') {
+ stateData = { CONTEXT: stateData };
+ }
+ componentData.context = filterAndFormatData(stateData);
+ componentName = 'Context';
+ }
+
+ // -----------------ADD COMPONENT DATA TO THE OUTPUT TREE-------------------
+
+ /**
+ * `rtid` - The `Root ID` is a unique identifier that is assigned to each React root instance in a React application.
+ */
+ let rtid: string | null = null;
+
+ // Grab JSX Component & replace the 'fromLinkFiber' class value
+ if (currentFiberNode.child?.stateNode?.setAttribute) {
+ rtid = `fromLinkFiber${rtidCounter}`;
+ // rtid = rtidCounter;
+ // check if rtid is already present
+ // remove existing rtid before adding a new one
+ if (currentFiberNode.child.stateNode.classList.length > 0) {
+ const lastClass =
+ currentFiberNode.child.stateNode.classList[
+ currentFiberNode.child.stateNode.classList.length - 1
+ ];
+ if (lastClass.includes('fromLinkFiber')) {
+ currentFiberNode.child.stateNode.classList.remove(lastClass);
+ }
+ }
+ currentFiberNode.child.stateNode.classList.add(rtid);
+ }
+ rtidCounter += 1;
+
+ /**
+ * The updated tree after adding the `componentData` obtained from `currentFiberNode`
+ */
+ const childNode = tree.addChild(newState, componentName, componentData, rtid);
+
+ // ---------------------TRAVERSE TO NEXT FIBERNODE--------------------------
+ // If currentFiberNode has children, recurse on children
+ if (child) _createTree(child, childNode);
+
+ // If currentFiberNode has siblings, recurse on siblings
+ if (sibling) {
+ _createTree(sibling, tree);
+ }
+
+ // ------------RETURN THE TREE OUTPUT & PASS TO FRONTEND FOR RENDERING------
+
+ return tree;
+ }
+}
diff --git a/src/backend/controllers/statePropExtractors.ts b/src/backend/controllers/statePropExtractors.ts
new file mode 100644
index 000000000..0f1a3980f
--- /dev/null
+++ b/src/backend/controllers/statePropExtractors.ts
@@ -0,0 +1,230 @@
+import { parse } from '@babel/parser';
+import { Node, CallExpression, MemberExpression, Identifier } from '@babel/types';
+import { HookStateItem, Fiber } from '../types/backendTypes';
+import { exclude } from '../models/filterConditions';
+
+type ReactimeData = {
+ [key: string]: any;
+};
+// ------------FILTER DATA FROM REACT DEV TOOL && CONVERT TO STRING-------------
+/**
+ * This function receives raw Data from REACT DEV TOOL and filter the Data based on the exclude list. The filterd data is then converted to string (if applicable) before being sent to reacTime front end.
+ * NOTE: the formating is important since Chrome will only accept JSON.stringfiable object. Circular object & function are not JSON stringifiable.
+ *
+ * @param reactDevData - The data object obtained from React Devtool. Ex: memoizedProps, memoizedState
+ * @param reactimeData - The cached data from the current component. This can be data about states, context and/or props of the component.
+ * @returns The update component data object to send to front end of ReactTime
+ */
+export function filterAndFormatData(
+ reactDevData: { [key: string]: any },
+ reactimeData: ReactimeData = {},
+): ReactimeData {
+ for (const key in reactDevData) {
+ try {
+ // If key is in exclude set or if there is no value at key, skip
+ if (exclude.has(key) || reactDevData[key] === undefined) {
+ continue;
+ }
+ // If value at key is a function, assign key with value 'function' to reactimeData object
+ else if (typeof reactDevData[key] === 'function') {
+ reactimeData[key] = 'function';
+ }
+ // If value at key is an object/array and not null => JSON stringify the value
+ else if (typeof reactDevData[key] === 'object' && reactDevData[key] !== null) {
+ reactimeData[key] = JSON.stringify(reactDevData[key]);
+ }
+ // Else assign the primitive value
+ else {
+ reactimeData[key] = reactDevData[key];
+ }
+ } catch (err) {
+ // COMMENT OUT TO AVOID PRINTTING ON THE CONSOLE OF USER - KEEP IT FOR DEBUGGING PURPOSE
+ // console.log({
+ // Message: 'Error in createTree during obtaining props information',
+ // potentialRootCause: 'circular component/failed during JSON stringify',
+ // reactDevData,
+ // key,
+ // err,
+ // });
+ // we will skip any props that cause an error
+ continue;
+ }
+ }
+ return reactimeData;
+}
+
+// ----------------------GET HOOK STATE AND DISPATCH METHOD---------------------
+/**
+ * Helper function to:
+ * - traverse through memoizedState
+ * - extract the state data & the dispatch method, which is stored in memoizedState.queue.
+ *
+ * During time jump, dispatch method will be used to re-render historical state.
+ * @param memoizedState - The current state of the component associated with the current Fiber node.
+ * @return An array of array of HookStateItem objects
+ *
+ */
+export function getHooksStateAndUpdateMethod(
+ memoizedState: Fiber['memoizedState'],
+): Array {
+ const hooksStates: Array = [];
+ while (memoizedState) {
+ if (memoizedState.queue) {
+ // Check if this is a reducer hook by looking at the lastRenderedReducer
+ const isReducer = memoizedState.queue.lastRenderedReducer?.name !== 'basicStateReducer';
+
+ if (isReducer) {
+ // For useReducer hooks, we want to store:
+ // 1. The current state
+ // 2. The last action that was dispatched (if available)
+ // 3. The reducer function itself
+ hooksStates.push({
+ component: memoizedState.queue,
+ state: memoizedState.memoizedState,
+ isReducer: true,
+ lastAction: memoizedState.queue.lastRenderedAction || null,
+ reducer: memoizedState.queue.lastRenderedReducer || null,
+ });
+ } else {
+ // Regular useState hook
+ hooksStates.push({
+ component: memoizedState.queue,
+ state: memoizedState.memoizedState,
+ isReducer: false,
+ });
+ }
+ }
+ memoizedState = memoizedState.next;
+ }
+ return hooksStates;
+}
+
+// ---------------------GET STATE VAR NAME & HOOK NAME--------------------------
+/**
+ * This function receive a string representation of a functional component. This function then use JSX parser to traverse through the function string, and extract the state variable name and its corresponding setState method.
+ * @param elementType - The string representation of a functional component
+ * @returns - An array of objects with key: hookName (the name of setState method) | value: varName (the state variable name)
+ */
+export function getHooksNames(elementType: string): { hookName: string; varName: string }[] {
+ try {
+ const AST = parse(elementType, {
+ sourceType: 'module',
+ plugins: ['jsx', 'typescript'],
+ });
+
+ const statements: { hookName: string; varName: string }[] = [];
+
+ const isIdentifierWithName = (node: any, name: string): boolean => {
+ return node?.type === 'Identifier' && node.name === name;
+ };
+
+ const processArrayPattern = (pattern: any): { setter: string; getter: string } | null => {
+ if (pattern.type === 'ArrayPattern' && pattern.elements.length === 2) {
+ const result = {
+ getter: pattern.elements[0].name,
+ setter: pattern.elements[1].name,
+ };
+ return result;
+ }
+ return null;
+ };
+
+ const extractReducerName = (node: any): string | null => {
+ if (node.type === 'Identifier') {
+ return node.name;
+ }
+
+ if (node.type === 'ArrowFunctionExpression' && node.body.type === 'CallExpression') {
+ if (node.body.callee.type === 'Identifier') {
+ return node.body.callee.name;
+ }
+ }
+
+ return null;
+ };
+
+ function traverse(node: Node) {
+ if (!node) return;
+
+ if (node.type === 'VariableDeclaration') {
+ node.declarations.forEach((declaration) => {
+ if (declaration.init?.type === 'CallExpression') {
+ // Check for Webpack transformed pattern
+ const isWebpackPattern =
+ declaration.init.callee?.type === 'SequenceExpression' &&
+ declaration.init.callee.expressions?.[1]?.type === 'MemberExpression';
+
+ // Get the hook name for Webpack pattern
+ let webpackHookName: string | null = null;
+ if (isWebpackPattern && declaration.init.callee?.type === 'SequenceExpression') {
+ const memberExpr = declaration.init.callee.expressions[1] as any;
+ if (memberExpr?.property?.type === 'Identifier') {
+ webpackHookName = memberExpr.property.name;
+ }
+ }
+
+ // Check for direct pattern
+ const directCallee = declaration.init.callee as any;
+ const isDirectPattern =
+ directCallee?.type === 'Identifier' &&
+ (directCallee.name === 'useState' || directCallee.name === 'useReducer');
+
+ // Check for namespaced pattern
+ const isNamespacedPattern =
+ declaration.init.callee?.type === 'MemberExpression' &&
+ (declaration.init.callee as any).property?.type === 'Identifier' &&
+ ((declaration.init.callee as any).property.name === 'useState' ||
+ (declaration.init.callee as any).property.name === 'useReducer');
+
+ if (isWebpackPattern || isDirectPattern || isNamespacedPattern) {
+ const arrayPattern = processArrayPattern(declaration.id);
+ if (arrayPattern) {
+ const isReducer =
+ webpackHookName === 'useReducer' ||
+ (isDirectPattern && directCallee?.name === 'useReducer') ||
+ (isNamespacedPattern &&
+ (declaration.init.callee as any).property?.name === 'useReducer');
+
+ if (isReducer) {
+ // Handle useReducer
+ if (declaration.init.arguments?.length > 0) {
+ const reducerName = extractReducerName(declaration.init.arguments[0]);
+ if (reducerName) {
+ statements.push({
+ hookName: reducerName,
+ varName: arrayPattern.getter,
+ });
+ }
+ }
+ } else {
+ // Handle useState
+ statements.push({
+ hookName: arrayPattern.setter,
+ varName: arrayPattern.getter,
+ });
+ }
+ }
+ }
+ }
+ });
+ }
+
+ // Recursively traverse
+ for (const key in node) {
+ if (node[key] && typeof node[key] === 'object') {
+ if (Array.isArray(node[key])) {
+ node[key].forEach((child: Node) => traverse(child));
+ } else {
+ traverse(node[key] as Node);
+ }
+ }
+ }
+ }
+
+ traverse(AST);
+ return statements;
+ } catch (err) {
+ console.error('AST Parsing Error:', err);
+ throw new Error('getHooksNameError: ' + err.message);
+ }
+}
diff --git a/src/backend/controllers/throttle.ts b/src/backend/controllers/throttle.ts
new file mode 100644
index 000000000..d4c9c9709
--- /dev/null
+++ b/src/backend/controllers/throttle.ts
@@ -0,0 +1,80 @@
+/**
+ * @method throttle
+ * @param callback A function to throttle
+ * @param MIN_TIME_BETWEEN_UPDATE A number of milliseconds to use as throttling interval
+ * @returns A function that limits input function, `callback`, from being called more than once every `MIN_TIME_BETWEEN_UPDATE` milliseconds
+ *
+ */
+export default function throttle any>(
+ callback: T,
+ MIN_TIME_BETWEEN_UPDATE: number,
+): (...arg: Parameters) => ReturnType {
+ // Initialize boolean flags for callback, throttledFunc
+ /**
+ * A boolean variable tracking if MIN_TIME_BETWEEN_UPDATE has passed
+ *
+ * This value turns TRUE after a callback function is invoked. While this value is true, no additional callback function can be invoked.
+ *
+ * This value turns FALSE after MIN_TIME_BETWEEN_UPDATE has passed => additional callback can now be invoked.
+ *
+ * @default false
+ */
+ let isOnCooldown: boolean = false;
+ /**
+ * A boolean variable tracking if there is a request to invoke the callback in the queue.
+ *
+ * This value turns TRUE if a request to invoke the callback is sent before the MIN_TIME_BETWEEN_UPDATE passes.
+ *
+ * This value turns FALSE after the callback is invoked.
+ *
+ * @default false
+ *
+ */
+ let isCallQueued: boolean = false;
+
+ let timeout: NodeJS.Timeout;
+ // Wrap the passed-in function callback in a callback function that "throttles" (puts a limit on) the number of calls that can be made to function in a given period of time (ms)
+ return function throttledFunc(...args: Parameters): ReturnType {
+ // CASE 1: In cooldown mode and we have a function waiting to be executed, so do nothing
+ if (isOnCooldown && isCallQueued) return;
+
+ // CASE 2: In cooldown mode, but we have no functions waiting to be executed, so just make note that we now have a callback waiting to be executed and then return
+ if (isOnCooldown) {
+ isCallQueued = true;
+ return;
+ }
+
+ // CASE 3: If we are ready to "fire":
+ // Execute the function callback immediately
+ callback(...args);
+ // Initiate a new cooldown period and reset the "call queue"
+ isOnCooldown = true;
+ isCallQueued = false;
+ // Set timeout to end the cooldown period after MIN_TIME_BETWEEN_UPDATE has passed
+ clearTimeout(timeout);
+ timeout = setTimeout(runAfterTimeout, MIN_TIME_BETWEEN_UPDATE);
+
+ /**
+ * @function runAfterTimeout - a function that end the cooldown mode & checks whether we have another function to be executed right after.
+ * @returns void
+ */
+ function runAfterTimeout() {
+ // If there is callback in the queue, execute callback immediately
+ if (isCallQueued) {
+ // Execute callback
+ callback(...args);
+ // Initiate a new cooldown period and reset the "call queue"
+ isOnCooldown = true;
+ isCallQueued = false;
+ // End the cooldown period after MIN_TIME_BETWEEN_UPDATE
+ clearTimeout(timeout);
+ setTimeout(runAfterTimeout, MIN_TIME_BETWEEN_UPDATE);
+ }
+ // If no callback in queue, end the cooldown period
+ else {
+ // End cooldown period after MIN_TIME_BETWEEN_UPDATE has passed
+ isOnCooldown = false;
+ }
+ }
+ };
+}
diff --git a/src/backend/controllers/timeJump.ts b/src/backend/controllers/timeJump.ts
new file mode 100644
index 000000000..bf7a1e85c
--- /dev/null
+++ b/src/backend/controllers/timeJump.ts
@@ -0,0 +1,91 @@
+import componentActionsRecord from '../models/masterState';
+import { Status } from '../types/backendTypes';
+import Tree from '../models/tree';
+import { update } from 'lodash';
+
+export default function timeJumpInitiation(mode: Status) {
+ const resetJumpingMode = (): void => {
+ mode.jumping = false;
+ };
+
+ return async function timeJump(targetSnapshot: Tree): Promise {
+ mode.jumping = true;
+ delete mode.navigating;
+ updateReactFiberTree(targetSnapshot).then(() => {
+ document.removeEventListener('mouseover', resetJumpingMode);
+ document.addEventListener('mouseover', resetJumpingMode, { once: true });
+ });
+ };
+}
+
+async function updateReactFiberTree(
+ targetSnapshot,
+ circularComponentTable: Set = new Set(),
+): Promise {
+ if (!targetSnapshot) return;
+ if (circularComponentTable.has(targetSnapshot)) return;
+
+ circularComponentTable.add(targetSnapshot);
+
+ if (targetSnapshot.state === 'stateless' || targetSnapshot.state === 'root') {
+ targetSnapshot.children.forEach((child) => updateReactFiberTree(child, circularComponentTable));
+ return;
+ }
+
+ const { index, state, hooksIndex, hooksState, reducerStates } = targetSnapshot.componentData;
+
+ // Handle class components
+ if (index !== null) {
+ const classComponent = componentActionsRecord.getComponentByIndex(index);
+ if (classComponent !== undefined) {
+ await classComponent.setState(() => state);
+ }
+ targetSnapshot.children.forEach((child) => updateReactFiberTree(child, circularComponentTable));
+ return;
+ }
+
+ // Handle hooks
+ if (hooksIndex !== null) {
+ const functionalComponent = componentActionsRecord.getComponentByIndexHooks(hooksIndex);
+
+ // Handle regular useState hooks
+ if (hooksState) {
+ const stateEntries = Object.entries(hooksState);
+ for (let i = 0; i < stateEntries.length; i++) {
+ const [key, value] = stateEntries[i];
+ if (functionalComponent[i]?.dispatch) {
+ await functionalComponent[i].dispatch(value);
+ }
+ }
+ }
+
+ // Handle reducer hooks
+ if (reducerStates && reducerStates.length > 0) {
+ for (const reducerState of reducerStates) {
+ const { state: targetState, reducerIndex, hookName } = reducerState;
+ const reducer = functionalComponent[reducerIndex];
+
+ if (reducer?.dispatch) {
+ try {
+ // Use SET_STATE action to update the state
+ const setStateAction = {
+ type: 'SET_STATE',
+ payload: targetState,
+ };
+
+ await reducer.dispatch(setStateAction);
+ } catch (error) {
+ console.error('Error updating reducer state:', {
+ hookName,
+ error,
+ componentName: targetSnapshot.name,
+ });
+ }
+ }
+ }
+ }
+
+ targetSnapshot.children.forEach((child) => updateReactFiberTree(child, circularComponentTable));
+ return;
+ }
+}
diff --git a/src/backend/index.d.ts b/src/backend/index.d.ts
new file mode 100644
index 000000000..44b187e1e
--- /dev/null
+++ b/src/backend/index.d.ts
@@ -0,0 +1,48 @@
+/* eslint-disable quotes */
+// Type definitions for reactime v3.1
+// Project:
+// Definitions by: Abaas Khorrami
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+/**
+ * The 'reactime' module has one export:
+ * --> the async @function returned from linkFiber.js
+ * @param {container} --> the div element corresponding to your root container
+ * @returns {void} --> no return value
+ *
+ * Reactime contributors:
+ "Abaas Khorrami",
+ "Andy Wong",
+ "Brian Yang",
+ "Bryan Lee",
+ "Carlos Perez",
+ "Chris Flannery",
+ "David Chai",
+ "Edwin Menendez",
+ "Emin Tahirov",
+ "Ergi Shehu",
+ "Gabriela Jardim Aquino",
+ "Gregory Panciera",
+ "Josh Kim",
+ "Joshua Howard",
+ "Louis Lam",
+ "Nathanael Wa Mwenze",
+ "Prasanna Malla",
+ "Rajeeb Banstola",
+ "Raymond Kwan",
+ "Rocky Lin",
+ "Ruth Anam",
+ "Ryan Dang",
+ "Samuel Tran",
+ "Sierra Swaby",
+ "Yujin Kang"
+ *
+ *
+ * NOTE: TypeScript support is in beta and still experimental.
+ *
+ */
+
+declare module 'reactime' {
+ function linkFiber(container: HTMLElement): void;
+ export = linkFiber;
+}
diff --git a/src/backend/index.ts b/src/backend/index.ts
new file mode 100644
index 000000000..6f68440e8
--- /dev/null
+++ b/src/backend/index.ts
@@ -0,0 +1,102 @@
+/**
+ * 'reactime' module has a single export
+ * @function linkFiber
+ */
+// --------------------------START OF IMPORT------------------------------------
+// regenerator runtime supports async functionality : This package implements a fully-functional source transformation that takes the syntax for generators/yield from ECMAScript 2015 or ES2015 and Asynchronous Iteration proposal and spits out efficient JS-of-today (ES5) that behaves the same way.
+import 'regenerator-runtime/runtime';
+// linkFiberInitialization (actually uses the function linkFiber but is labeled here as linkFiberInitialization, returns a function). When this returned function is invoked, it checks if devTools is installed, checks if the website is a reactApp, adds event listeners for timetravel, and allows us to obtain the initial FiberRoot Node from react dev tool
+import linkFiber from './routers/linkFiber';
+// timeJumpInitialization (actually uses the function timeJumpInitiation but is labeled here as linkFiberInitialization, returns a function) returns a function that sets jumping to false and handles timetravel feature
+import timeJumpInitialization from './controllers/timeJump';
+import { Snapshot, Status, MsgData } from './types/backendTypes';
+import routes from './models/routes';
+
+// -------------------------INITIALIZE MODE--------------------------
+/** Indicate if mode is jumping/not jumping or navigating during jumping */
+const mode: Status = {
+ jumping: false,
+};
+
+// ---------------------INITIALIZE LINKFIBER & TIMEJUMP-------------------------
+// linkFiber is now assigned the default ASYNC function exported from the file linkFiber.ts
+const linkFiberInit = linkFiber(mode);
+// timeJump is now assigned the default ASYNC function exported from the file timeJump.ts
+const timeJump = timeJumpInitialization(mode);
+
+/**
+ * Invoke linkFiber to perform the following:
+ * 1. Check for ReactDev installation, valid target React App
+ * 2. Obtain the initial ReactFiber Tree from target React App
+ * 3. Send a snapshot of ReactFiber Tree to frontend/Chrome Extension
+ */
+linkFiberInit();
+
+// --------------INITIALIZE EVENT LISTENER FOR TIME TRAVEL----------------------
+/**
+ * On the chrome extension, if user click left/right arrow or the play button (a.k.a time travel functionality), frontend will send a message `jumpToSnap` with payload of the cached snapShot tree at the current step
+ * 1. Set jumping mode to true => dictate we are jumping => no new snapshot will be sent to frontend
+ * 2. If navigate to a new route during jumping => cache timeJump in navigate.
+ * 3. If not navigate during jumping => invoke timeJump to update ReactFiber tree with cached data from the snapshot payload
+ */
+window.addEventListener('message', async ({ data: { action, payload } }: MsgData) => {
+ switch (action) {
+ case 'jumpToSnap':
+ // Set mode to jumping to prevent snapShot being sent to frontEnd
+ // NOTE: mode.jumping is set to false inside the timeJump.ts
+ mode.jumping = true;
+ // Check if we are navigating to another route
+ const navigating: boolean = routes.navigate(payload.route);
+ // If need to navigate
+ if (navigating) {
+ // Cache timeJump function in mode.navigating => which will be invoked during onCommitFiberRoot:
+ mode.navigating = () => timeJump(payload);
+ }
+ // If not navitating, invoke timeJump immediately to update React Application FiberTree based on the snapshotTree payload
+ else {
+ await timeJump(payload); // * This sets state with given payload
+ }
+ break;
+ // case 'reinitialize':
+ // console.log('backend reinitialize received, performing checks again');
+ // let devTools;
+ // while (!devTools) {
+ // devTools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
+ // }
+ // console.log('backend react devtools: ', devTools);
+ // // If React Devtools is not installed, object will be undefined.
+ // if (!devTools) return;
+ // // If React Devtools is installed, send a message to front end.
+
+ // console.log(
+ // 'backend react devtools check passed, sending devToolsInstalled to contentScript',
+ // );
+ // window.postMessage(
+ // {
+ // action: 'devToolsInstalled',
+ // payload: 'devToolsInstalled',
+ // },
+ // '*',
+ // );
+
+ // const reactInstance = devTools.renderers.get(1);
+ // // If target application is not a React App, this will return undefined.
+ // if (!reactInstance) {
+ // return;
+ // }
+ // console.log('backend react instance check passed, sending aReactApp to contentScript');
+ // // If target application is a React App, send a message to front end.
+ // window.postMessage(
+ // {
+ // action: 'aReactApp',
+ // payload: 'aReactApp',
+ // },
+ // '*',
+ // );
+
+ linkFiberInit();
+ break;
+ default:
+ break;
+ }
+});
diff --git a/src/backend/models/filterConditions.ts b/src/backend/models/filterConditions.ts
new file mode 100644
index 000000000..cf043a2b7
--- /dev/null
+++ b/src/backend/models/filterConditions.ts
@@ -0,0 +1,125 @@
+import {
+ FunctionComponent,
+ ClassComponent,
+ IndeterminateComponent, // Before we know whether it is function or class
+ ContextProvider,
+ WorkTag,
+} from '../types/backendTypes';
+
+export const allowedComponentTypes: Set = new Set([
+ FunctionComponent,
+ ClassComponent,
+ ContextProvider,
+ IndeterminateComponent,
+]);
+export const nextJSDefaultComponent = new Set([
+ 'Root',
+ 'Head',
+ 'AppContainer',
+ 'Container',
+ 'ReactDevOverlay',
+ 'ErrorBoundary',
+ 'AppRouterContext',
+ 'SearchParamsContext',
+ 'PathnameContextProviderAdapter',
+ 'PathnameContext',
+ 'RouterContext',
+ 'HeadManagerContext',
+ 'ImageConfigContext',
+ 'RouteAnnouncer',
+ 'Portal',
+]);
+
+export const remixDefaultComponents = new Set([
+ 'RemixBrowser',
+ 'Remix',
+ 'RemixErrorBoundary',
+ 'RouterProvider',
+ 'DataRouter',
+ 'DataRouterState',
+ 'RenderErrorBoundary',
+ 'Meta',
+ 'V1Meta',
+ 'Links',
+ 'RemixRoute',
+ 'Outlet',
+ 'ScrollRestoration2',
+ 'script',
+ 'Scripts',
+ 'LiveReload2',
+]);
+/**
+ * A set of excluded props and variable name
+ */
+export const exclude = new Set([
+ 'alternate',
+ 'basename',
+ 'baseQueue',
+ 'baseState',
+ 'child',
+ 'childLanes',
+ 'children',
+ 'Consumer',
+ 'context',
+ 'create',
+ 'deps',
+ 'dependencies',
+ 'destroy',
+ 'dispatch',
+ 'location',
+ 'effects',
+ 'element',
+ 'elementType',
+ 'firstBaseUpdate',
+ 'firstEffect',
+ 'flags',
+ 'get key',
+ 'getState',
+ 'hash',
+ 'key',
+ 'lanes',
+ 'lastBaseUpdate',
+ 'lastEffect',
+ 'liftedStore',
+ 'navigator',
+ 'memoizedState',
+ 'mode',
+ 'navigationType',
+ 'next',
+ 'nextEffect',
+ 'pending',
+ 'parentSub',
+ 'pathnameBase',
+ 'pendingProps',
+ 'Provider',
+ 'updateQueue',
+ 'ref',
+ 'replaceReducer',
+ 'responders',
+ 'return',
+ 'route',
+ 'routeContext',
+ 'search',
+ 'shared',
+ 'sibling',
+ 'state',
+ 'store',
+ 'subscribe',
+ 'subscription',
+ 'stateNode',
+ 'tag',
+ 'type',
+ '_calculateChangedBits',
+ '_context',
+ '_currentRenderer',
+ '_currentRenderer2',
+ '_currentValue',
+ '_currentValue2',
+ '_owner',
+ '_self',
+ '_source',
+ '_store',
+ '_threadCount',
+ '$$typeof',
+ '@@observable',
+]);
diff --git a/src/backend/models/masterState.ts b/src/backend/models/masterState.ts
new file mode 100644
index 000000000..ec5789186
--- /dev/null
+++ b/src/backend/models/masterState.ts
@@ -0,0 +1,53 @@
+// The HookState data structure is an array that holds the current value of a hook's state, as well as a dispatch function that is used to update that state.
+// Information on these components include ComponentData as well as state
+// For class components, there will be one "component" for each snapshot
+// For functional components that utilize Hooks, there will be one "component"
+
+// for each setter/getter every time we have a new snapshot
+let componentActionsRecord = [];
+
+export default {
+ /**
+ * @function clear - Clears componentActionsRecord
+ */
+ clear: (): void => {
+ // console.log(componentActionsRecord);
+ componentActionsRecord = [];
+ },
+
+ /**
+ * @function saveNew - Adds a new component to the componentActionsRecord array and returns its index.
+ * @param component - An object that contains bound update method. For class component, the udpate method is `setState`. For functional component, the update method is `dispatch`.
+ * @returns - the index of the newly added component
+ */
+ saveNew: (component): number => {
+ return componentActionsRecord.push(component) - 1;
+ },
+ // ----------------------------CLASS COMPONENT--------------------------------
+ /**
+ * @function getComponentByIndex - This function is used for stateful Class Component to retrieve an object that has the bound setState method
+ * @param inputIndex - index of component inside `componentActionsRecord` coming from `timeJump.ts`
+ * @returns - an object containing the bound setState method
+ */
+ getComponentByIndex: (inputIndex: number): any | undefined => componentActionsRecord[inputIndex],
+
+ //---------------------------FUNCTIONAL COMPONENT-----------------------------
+ /**
+ * @function getComponentByIndexHooks - This function is used for Functional Component to retrieve an array of objects that have the bound dispatch methods.
+ * @param inputIndex - index of component inside `componentActionsRecord` coming from `timeJump.ts`
+ * @returns - an array of objects containing the bound dispatch methods
+ */
+ getComponentByIndexHooks: (inputIndex: Array): any[] =>
+ inputIndex
+ // filter for only index exists in componentActionsRecord
+ .filter((index) => index in componentActionsRecord)
+ // get the corresponding dispath method at position index
+ .map((index) => componentActionsRecord[index]),
+
+ // ----------------------------------DEBUGGING--------------------------------
+ /**
+ * @function getAllComponents - This method is used for debugging purpose to access the array of setState/dispatch methods
+ * @returns - an array of objects containing the bound methods for updating state
+ */
+ getAllComponents: (): any[] => componentActionsRecord,
+};
diff --git a/src/backend/models/routes.ts b/src/backend/models/routes.ts
new file mode 100644
index 000000000..60f57d98e
--- /dev/null
+++ b/src/backend/models/routes.ts
@@ -0,0 +1,123 @@
+/**
+ * @class Route instances are created by the addRoute method on Routes. A Route instance has two properties: the url of the route and a unique id.
+ */
+
+export class Route {
+ constructor(
+ public url: string,
+ public id: number,
+ ) {
+ this.url = url;
+ this.id = id;
+ }
+}
+
+/**
+ * @class An instance of this class is the default export from routes.ts. It includes the logic that allows Reactime to work with target applications built with React Router. The addRoute method is invoked in linkFiber.ts within the sendSnapshot function. The navigate method is invoked in timeJump.ts immediately before invoking jump.
+ */
+
+export class Routes {
+ /**
+ * @property A stack of visited routes that matches the browser history stack.
+ * The dummyURL is used to initialize the routeHistory array with a default route at index 0, which serves as
+ a placeholder. This is necessary because the routeHistory array needs to mirror the browser's history
+ stack, and the browser's history stack always has at least one entry when the page is loaded. Once the user
+ navigates to a new route, the dummyURL will be replaced with the first real route.
+ */
+
+ routeHistory: Route[] = [new Route('dummyURL', 0)];
+
+ /**
+ * @property When a new route is added to the history, a new Route object is created with a unique id value, which is incremented for each new route. This is useful because it allows us to compare routes in a more robust way, using both the URL and the ID to ensure that we're looking at the correct route in the history array. In the navigate method, for example, the targetIndex is found by searching for the index of the route with the matching url and id
+ */
+ id = 0;
+
+ /**
+ * @property Tracks the index of the current route in the routeHistory stack. This property is used to ensure that the routeHistory stack always matches the browser history stack. When a user navigates to a new route, the current property is updated to reflect the new index of the current route in the routeHistory stack. When a user performs a time jump, the current property is updated to reflect the new index of the current route in the routeHistory stack as well.
+ */
+ current = 0;
+
+ /**
+ * @method addRoute - Method to add a new route to the route history array. Also ensures that the `routeHistory` stack always matches the browser history stack.
+ * @param url - A url string.
+ * @returns Either the current route if the user has not navigated away from it or a new instance of a route constructed from the url.
+ *
+ *
+ */
+
+ addRoute(url: string): Route {
+ // Get the current route
+ const currentRoute: Route = this.routeHistory[this.current];
+ // Check if the new url is different from the current url
+ const isNavigating = currentRoute.url !== url;
+ if (isNavigating) {
+ // Check if current is not equal to routeHistory.length - 1 becuase if it doesnt, we need to rebuild history
+ if (this.current !== this.routeHistory.length - 1) {
+ // Rebuild the browser history with the new url
+ this.rebuildHistory(url);
+ }
+ // Create a new Route object with the new url and id
+ const newRoute = new Route(url, ++this.id);
+ // Add the new Route object to the route history array
+ this.routeHistory.push(newRoute);
+ // Update the current index to pointer to the new Route object
+ this.current = this.routeHistory.length - 1;
+ // Return the new Route object
+ return newRoute;
+ }
+ // If the new url is the same as the current url, return the current route
+ return currentRoute;
+ }
+
+ /**
+ * @method rebuildHistory
+ * @param url - A url string.
+ *
+ * Rebuilds the browser history stack using the copy of the stack maintained in the `routeHistory` stack. https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState, https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
+ */
+ private rebuildHistory(url: string): void {
+ // Replace window history with the next route
+ window.history.replaceState('', '', this.routeHistory[this.current + 1].url);
+ // For each route in routeHistory after the next route, add to window history
+ for (let i = this.current + 2; i < this.routeHistory.length; i += 1) {
+ window.history.pushState('', '', this.routeHistory[i].url);
+ }
+ // Add the new url to window history
+ window.history.pushState('', '', url);
+ }
+
+ /**
+ * This method will perform the following:
+ * 1. Evaluate if user need to navigate to another route
+ * 2. If navigation is needed, perform navigation and return true
+ * 3. Else return false
+ * @param route - The target route in the `routeHistory` stack that is being navigated to.
+ * @returns A boolean indicating whether or not a new route was navigated to.
+ *
+ * Invokes history.go passing in the delta between the current route and the target route. https://developer.mozilla.org/en-US/docs/Web/API/History/go
+ */
+ navigate(targetRoute: Route): boolean {
+ // Find the index of the target route in the route history array
+ const targetIndex: number = this.routeHistory.findIndex(
+ (route) => route.url === targetRoute.url && route.id === targetRoute.id,
+ );
+ // If the target route is not found, throw an error
+ if (targetIndex === -1) {
+ throw new Error('Error at Routes.navigate: targetIndex is undefined');
+ }
+ // Calculate the difference in index between the current route and the target route
+ const delta: number = targetIndex - this.current;
+ // Update the current route index to the index of the target route
+ this.current += delta;
+ // If the difference is not 0, navigate to the target route using window.history.go() method
+ if (delta !== 0) {
+ window.history.go(delta);
+ // Return true to indicate that the navigation was successful
+ return true;
+ }
+ // If the difference is 0, return false to indicate that no navigation occurred
+ return false;
+ }
+}
+
+export default new Routes();
diff --git a/src/backend/models/tree.ts b/src/backend/models/tree.ts
new file mode 100644
index 000000000..c17397d6c
--- /dev/null
+++ b/src/backend/models/tree.ts
@@ -0,0 +1,145 @@
+import { Route } from './routes';
+import { ComponentData } from '../types/backendTypes';
+
+/** ComponentNames is used to store a mapping between a component's unique identifier and its name. This mapping is used to reconstruct the component instances during deserialization.*/
+let componentNames = {};
+let rootTree;
+
+// Making a deep clone of state becuase we want to make a copy
+/**
+ * @function serializeState - In the context of React, state is often used to store data that determines the behavior and appearance of a component. By serializing the state, we can preserve the component's data across page refreshes, server-side rendering, and other transitions. Additionally, by serializing the state and passing it to a child component, we can create a deep clone of the state, which allows the child component to manipulate the state without affecting the original component. This is useful in situations where we want to keep the state of the parent component immutable, but still allow child components to modify a copy of the state.
+ * @param state - Object that contains the current state of the application or system that needs to be serialized.
+ * @returns - Depclone of the passed in state. If there is any circulate state, return 'circularState'
+ */
+export function serializeState(state) {
+ try {
+ // makes a deep clone
+ return JSON.parse(JSON.stringify(state));
+ } catch (e) {
+ // if there is an error, that means there is circular state i.e state that depends on itself
+ return 'circularState';
+ }
+}
+
+/**
+ * Tree.name's come from the name of the component, but these names should be unique. When a second component
+ * is used in a single tree, this function is called in checkForDuplicates to the change the Tree.name of the
+ * first component to have a "1" at the end.
+ *
+ * @param tree A tree class instance
+ * @param name The name being checked
+ */
+function changeFirstInstanceName(tree: Tree, name: string): void {
+ for (const child of tree.children) {
+ if (child.name === name) {
+ child.name += 1;
+ return;
+ } else {
+ changeFirstInstanceName(child, name);
+ }
+ }
+}
+
+/**
+ * This is the current snapshot that is being sent to the snapshots array.
+ * Creates a Tree
+ * @param state - the current state of the component represented by this node.
+ * @param name - the name of the component represented by this node.
+ * @param componentData - an object containing the props of the component represented by this node.
+ * @param chilren - an array of child nodes.
+ * @param parent - a reference to the parent node.
+ * @param isExpanded - a boolean value indicating whether the node is expanded in the UI.
+ * @param rtid - a unique identifier for the node.
+ * @param route - an object representing the route associated with the node.
+ */
+class Tree {
+ state: string | {}; // TODO: should change this to stateless | statefull | root
+
+ name: string;
+
+ componentData: ComponentData | {};
+
+ children: Tree[];
+
+ isExpanded: boolean = true;
+
+ rtid: string | null;
+
+ route?: Route;
+
+ // Duplicate names: add a unique number ID
+ // Create an object 'componentNames' to store each component name as a key and it's frequency of use as its value
+ // When a new component is made on the tree, check if the new component's name already exists in 'componentNames' (possibly with the .hasOwnProperty method)
+ // If the name already exists, add its value (an integer) to the name
+ // Also, increment the value after
+ // If not, create the new component and also a new key: value pair in 'componentNames' with the component's name as the key and 0 as its value
+ // EXAMPLE OF COMPONENTNAMES OBJECT: {editableInput: 1, Provider: 0, etc}
+
+ constructor(
+ state: string | {},
+ name = 'nameless',
+ componentData: ComponentData | {} = {},
+ rtid: string | null = null,
+ ) {
+ this.children = [];
+ this.componentData = componentData;
+ this.state = state === 'root' ? 'root' : serializeState(state);
+ this.name = name;
+ this.rtid = rtid;
+ }
+
+ // Returns a unique name ready to be used for when new components gets added to the tree
+ /**
+ * @function checkForDuplicates - Generates a unique name for a component that is being added to the component tree
+ * @param name Component name
+ * @returns Unique name for Tree.name
+ */
+ checkForDuplicates(name: string): string {
+ if (this.name === 'root') {
+ componentNames = {};
+ rootTree = this;
+ }
+
+ /**
+ * If a duplicate name is found, adds a number to the end of the name so it'll show up uniquely
+ * in the component map. For example, if only one "Box" component is found, it's name will be "Box".
+ * However, after a second "Box" component is found, the first box will be renamed "Box1" and the
+ * second box will be named "Box2". When the third box is found it'll be named "Box3", and so on.
+ */
+ componentNames[name] = componentNames[name] + 1 || 1;
+
+ if (componentNames[name] === 2) {
+ changeFirstInstanceName(rootTree, name);
+ }
+
+ if (componentNames[name] > 1) name += componentNames[name];
+
+ return name;
+ }
+
+ /**
+ *
+ * @param state - string if root, serialized state otherwise
+ * @param name - name of child
+ * @param componentData - props
+ * @param rtid - ??
+ * @returns - return new tree instance that is child
+ */
+ addChild(
+ state: Tree['state'],
+ name: Tree['name'],
+ componentData: Tree['componentData'],
+ rtid: Tree['rtid'],
+ ): Tree {
+ // Get unique name by invoking checkForDuplicates method
+ const uniqueName = this.checkForDuplicates(name);
+ // Instantiates new child Tree with state, uniqueName, componentData and rtid
+ const newChild: Tree = new Tree(state, uniqueName, componentData, rtid);
+ // adds newChild to children array
+ this.children.push(newChild);
+ // return newChild
+ return newChild;
+ }
+}
+
+export default Tree;
diff --git a/src/backend/module.d.ts b/src/backend/module.d.ts
new file mode 100644
index 000000000..5f8abd83e
--- /dev/null
+++ b/src/backend/module.d.ts
@@ -0,0 +1,12 @@
+// core-js is a library that includes polyfill for many types of native js
+// features such as promise, symbols, collections, typed arrays, etc.
+// Used in reactime to make sure our newest code
+// can run on outdated platforms and with outdated applications
+declare module 'core-js';
+// Regenerator runtime provides runtime support for compiled/transpiled async functions.
+// Like babel that compiles modern js into older js,
+// async functions are also compiled to run on engines that don't support async.
+// After babel does the syntax transformation or transpiles the async functions,
+// the resulting code uses regen runtime to run
+// https://stackoverflow.com/questions/65378542/what-is-regenerator-runtime-npm-package-used-for
+declare module 'regenerator-runtime/runtime';
diff --git a/src/backend/puppeteerServer.ts b/src/backend/puppeteerServer.ts
new file mode 100644
index 000000000..342c22347
--- /dev/null
+++ b/src/backend/puppeteerServer.ts
@@ -0,0 +1,14 @@
+/* eslint-disable linebreak-style */
+/* eslint-disable import/no-extraneous-dependencies */
+/* eslint-disable @typescript-eslint/no-var-requires */
+import express from 'express';
+import path from 'path';
+
+const app = express();
+
+app.use(express.static(path.resolve(__dirname)));
+
+// Apple uses port 5000 for Air Play.
+const server = app.listen(5001);
+
+module.exports = server;
diff --git a/src/backend/routers/linkFiber.ts b/src/backend/routers/linkFiber.ts
new file mode 100644
index 000000000..edf594308
--- /dev/null
+++ b/src/backend/routers/linkFiber.ts
@@ -0,0 +1,145 @@
+import { Snapshot, Status, FiberRoot } from '../types/backendTypes';
+import { DevTools } from '../types/linkFiberTypes';
+import updateAndSendSnapShotTree from './snapShot';
+import throttle from '../controllers/throttle';
+import componentActionsRecord from '../models/masterState';
+import createComponentActionsRecord from '../controllers/createComponentActionsRecord';
+
+// Set global variables to use in exported module and helper functions
+declare global {
+ interface Window {
+ __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevTools;
+ __REDUX_DEVTOOLS_EXTENSION__?: any;
+ }
+}
+
+/**
+ * @constant MIN_TIME_BETWEEN_UPDATE - The minimum time (ms) between each re-render/update of the snapShot tree being displayed on the Chrome Extension.
+ */
+const MIN_TIME_BETWEEN_UPDATE = 70;
+/**
+ * @function throttledUpdateSnapshot - a function that will wait for at least MIN_TIME_BETWEEN_UPDATE ms, before updating the tree snapShot being displayed on the Chrome Extension.
+ * @param fiberRoot - the root of ReactFiber Tree
+ * @param mode - mode is jumping/not jumping or navigating during jumping
+ * @param snapShot - the tree snapshot to send to Front End or obtained from Front End during timeJump
+ */
+const throttledUpdateSnapshot = throttle(async (fiberRoot: FiberRoot, mode: Status) => {
+ // If not jumping
+ if (!mode.jumping) {
+ // Update and Send SnapShot tree to front end
+ updateAndSendSnapShotTree(fiberRoot);
+ }
+
+ // If navigating to another route during jumping:
+ else if (mode.navigating) {
+ // Reset the array containing update methods:
+ componentActionsRecord.clear();
+ // Obtain new update methods for the current route:
+ const { current } = fiberRoot;
+ createComponentActionsRecord(current);
+ // Invoke timeJump, which is stored in mode.navigating, to update React Application FiberTree based on the snapshotTree
+ await mode.navigating();
+ }
+ // NOTE: if not navigating during jumping, timeJump is invoked in index.ts file.
+}, MIN_TIME_BETWEEN_UPDATE);
+
+/**
+ * @function linkFiber - linkFiber contains core module functionality, exported as an anonymous function, perform the following logic:
+ * 1. Check if React Dev Tool is installed.
+ * 2. Check if the target application (on the browser) is a valid react application.
+ * 3. Initiate a event listener for visibility update of the target React Application.
+ * 4. Obtain the initial fiberRootNode, which is the root node of the fiber tree
+ * 5. Initialize the fiber tree snapShot to send to Front End, later rendered on Chrome Extension.
+ * 6. Monkey patching the onCommitFiberRoot from REACT DEV TOOL to obtain updated data after React Applicaiton is re-rendered.
+ * @param snapShot The current snapshot (i.e fiber tree)
+ * @param mode The current mode (i.e. jumping, time-traveling, or paused)
+ * @return a function to be invoked by index.js that initiates snapshot monitoring
+ */
+export default function linkFiber(mode: Status): () => Promise {
+ /** A boolean value indicate if the target React Application is visible */
+ let isVisible: boolean = true;
+ /**
+ * Every React application has one or more DOM elements that act as containers. React creates a fiber root object for each of those containers.
+ * This fiber root is where React holds reference to a fiber tree
+ * The `fiberRootNode`, which is the root node of the fiber tree is stored in the current property of the fiber root object
+ */
+ let fiberRoot: FiberRoot;
+
+ // Return a function to be invoked by index.js that initiates snapshot monitoring
+ return async function linkFiberInitialization() {
+ // -------------------CHECK REACT DEVTOOL INSTALLATION----------------------
+ // react devtools global hook is a global object that was injected by the React Devtools content script, allows access to fiber nodes and react version
+ // Obtain React Devtools Object:
+ let devTools; // changed to a different version of getting hook (08/04/2023)
+ while (!devTools) {
+ devTools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
+ }
+ // If React Devtools is not installed, object will be undefined.
+ if (!devTools) return;
+ // If React Devtools is installed, send a message to front end.
+ window.postMessage(
+ {
+ action: 'devToolsInstalled',
+ payload: 'devToolsInstalled',
+ },
+ '*',
+ );
+
+ // --------------------CHECK VALID REACT APPLICATION------------------------
+ // Obtain React Application information:
+ const reactInstance = devTools.renderers.get(1);
+ // If target application is not a React App, this will return undefined.
+ if (!reactInstance) {
+ return;
+ }
+ // If target application is a React App, send a message to front end.
+ window.postMessage(
+ {
+ action: 'aReactApp',
+ payload: 'aReactApp',
+ },
+ '*',
+ );
+ // --------------INITIATE EVENT LISTENER FOR VISIBILITY CHANGE--------------
+ /**
+ * Initiate an event listener for when there is a change to the visibility of the react target application (the browser tab)
+ * @example If tic-tac-toe demo app is loaded on a tab with localhost:8080, whenever user switch tab or switch to another software => 'visibilityChange' => invoke the callback to update doWork boolean value
+ */
+ document.addEventListener('visibilitychange', () => {
+ // Hidden property = background tab/minimized window
+ isVisible = !document.hidden;
+ });
+
+ // ---------OBTAIN THE INITIAL FIBEROOTNODE FROM REACT DEV TOOL-------------
+ // Obtain the FiberRootNode, which is the first value in the FiberRoot Set:
+ fiberRoot = devTools.getFiberRoots(1).values().next().value;
+
+ // console.log('Initial fiber root', fiberRoot);
+
+ // ----------INITIALIZE THE TREE SNAP SHOT ON CHROME EXTENSION--------------
+ await throttledUpdateSnapshot(fiberRoot, mode); // only runs on start up
+
+ // --------MONKEY PATCHING THE onCommitFiberRoot FROM REACT DEV TOOL--------
+ // React has inherent methods that are called with react fiber
+ // One of which is the onCommitFiberRoot method, which is invoked after the React application re-render its component(s).
+ // we attach new functionality without compromising the original work that onCommitFiberRoot does
+ // Money Patching the onCommitFiberRoot method from REACT DEV TOOL
+ /**
+ * @param onCommitFiberRoot - is a callback provided by React that is automatically invoked by React Fiber after the target React application re-renders its components. This callback is used by REACT DEV TOOL to receive updated data about the component tree and its state. See {@link https://medium.com/@aquinojardim/react-fiber-reactime-4-0-f200f02e7fa8}
+ * @returns an anonymous function, which will have the same parameters as onCommitFiberRoot and when invoked will update the fiberRoot value & post a request to update the snapShot tree on Chrome Extension
+ */
+ function addOneMoreStep(onCommitFiberRoot: DevTools['onCommitFiberRoot']) {
+ return function (...args: Parameters) {
+ // Obtain the updated FiberRootNode, after the target React application re-renders
+ const fiberRoot = args[1];
+ // If the target React application is visible, send a request to update the snapShot tree displayed on Chrome Extension
+ if (isVisible) {
+ throttledUpdateSnapshot(fiberRoot, mode);
+ }
+ // After our added work is completed we invoke the original onComitFiberRoot function
+ return onCommitFiberRoot(...args);
+ };
+ }
+ devTools.onCommitFiberRoot = addOneMoreStep(devTools.onCommitFiberRoot);
+ };
+}
diff --git a/src/backend/routers/snapShot.ts b/src/backend/routers/snapShot.ts
new file mode 100644
index 000000000..41a5cb7f7
--- /dev/null
+++ b/src/backend/routers/snapShot.ts
@@ -0,0 +1,40 @@
+import { Snapshot, FiberRoot } from '../types/backendTypes';
+import componentActionsRecord from '../models/masterState';
+import routes from '../models/routes';
+import createTree from '../controllers/createTree';
+
+// -------------------------UPDATE & SEND TREE SNAP SHOT------------------------
+/**
+ * This function creates a new `snapShot` fiber tree with the provided `fiberRoot`, then send the updated snapshot to front end.
+ * This runs after every Fiber commit if mode is not jumping.
+ * This
+ * @param snapshot The current snapshot of the fiber tree
+ * @param fiberRoot The `fiberRootNode`, which is the root node of the fiber tree is stored in the current property of the fiber root object which we can use to traverse the tree
+ */
+// updating tree depending on current mode on the panel (pause, etc)
+export default function updateAndSendSnapShotTree(fiberRoot: FiberRoot): void {
+ // This is the currently active root fiber(the mutable root of the tree)
+ const { current } = fiberRoot;
+ // Clear all of the legacy actions from old fiber tree because we are about to create a new one
+ componentActionsRecord.clear();
+ // Calls the createTree function which creates the new snapshot tree and store new state update method to compoenActionsRecord
+ /** The snapshot of the current ReactFiber tree */
+ const payload = createTree(current);
+ // Save the current window url to route
+ payload.route = routes.addRoute(window.location.href);
+ // method safely enables cross-origin communication between Window objects;
+ // e.g., between a page and a pop-up that it spawned, or between a page
+ // and an iframe embedded within it.
+ // this postMessage will be sending the most up-to-date snapshot of the current React Fiber Tree
+ // the postMessage action will be received on the content script to later update the tabsObj
+ // this will fire off everytime there is a change in test application
+ // convert the payload from a fiber tree to an object to avoid a data clone error when postMessage processes the argument
+ const obj = JSON.parse(JSON.stringify(payload));
+ window.postMessage(
+ {
+ action: 'recordSnap',
+ payload: obj,
+ },
+ '*',
+ );
+}
diff --git a/src/backend/types/backendTypes.ts b/src/backend/types/backendTypes.ts
new file mode 100644
index 000000000..863a4d38d
--- /dev/null
+++ b/src/backend/types/backendTypes.ts
@@ -0,0 +1,254 @@
+import Tree from '../models/tree';
+
+/**
+ * Contain snapshot of the current ReactFiber tree
+ * @member tree - A snapshot of ReactFiber Tree to send to front end
+ */
+export interface Snapshot {
+ /** A snapshot of ReactFiber Tree to send to front end */
+ tree: Tree;
+}
+
+/**
+ * Indicate if mode is jumping/not jumping or navigating during jumping
+ * @member jumping - Describe whether we are jumping
+ *
+ * When `jumping = true`, no new snapShot will be sent to front end.
+ * @member navigating - Cache timeJump function to be invoked after ReactFibe tree update with new states from new route
+ * @example if user uses click left/right arrow or play button, front end will post a message `jumpToSnap` and a payload of the cached snapShot tree, we will set `jumping = true`
+ * @example if during jumping, we navigate to another route, such as from buttons to tictactoe, backend will set `navigating = cache of timeJump function`
+ */
+export interface Status {
+ /**
+ * Describe whether we are jumping
+ *
+ * When `jumping = true`, no new snapShot will be sent to front end.
+ */
+ jumping: boolean;
+ /** Cache timeJump function to be invoked after ReactFibe tree update with new states from new route*/
+ navigating?: Function;
+}
+
+/**
+ * @type MsgData - obj with data object that will be sent to window?
+ * @member data - an object with action & payload properties
+ */
+export interface MsgData {
+ data: {
+ action: string;
+ payload: any;
+ };
+}
+
+/**
+ * @type ComponentData -
+ * @member actualDuration - The time taken to render the current Fiber node and its descendants during the previous render cycle. This value is used to optimize the rendering of components and to provide performance metrics to developers.
+ * @member actualStartTime - The time at which the rendering of the current Fiber node started during the previous render cycle.
+ * @member key - The key a user assigned to the component or null if they didn't assign one
+ * @member context - {in experiment} - An object contains all context information of the current component
+ * @member index - {class component only} - The index of the bound setState method stored in `componentActionsRecord`
+ * @member hooksState - {functional component only} - An object contains all states of the current functional component
+ * @member hooksIndex - {functional component only} - An array of index of the bound dispatch method stored in `componentActionsRecord`
+ * @member props - An object contains all props of the current component
+ * @member selfBaseDuration - The base duration of the current Fiber node's render phase (excluding the time taken to render its children). This field is only set when the enableProfilerTimer flag is enabled.
+ * @member state - {class component only} - An object contains all states of the current class component
+ * @member treeBaseDuration - The total base duration of the current Fiber node's subtree. This field is only set when the enableProfilerTimer flag is enabled.
+ */
+export interface ComponentData {
+ /** The time taken to render the current Fiber node and its descendants during the previous render cycle. */
+ actualDuration?: number;
+ /** The time at which the rendering of the current Fiber node started during the previous render cycle. */
+ actualStartTime?: number;
+ /**The key a user assigned to the component or null if they didn't assign one */
+ key: string | null;
+ /** {in experiment} - An object contains all context information of the current component */
+ context: {};
+ /** {class component only} - The index of the bound setState method stored in `componentActionsRecord` */
+ index: number | null;
+ /** {functional component only} - An object contains all states of the current functional component */
+ hooksState: {} | null;
+ reducerStates?: Array<{
+ state: any;
+ lastAction: any;
+ reducerIndex: number;
+ hookName: string;
+ }> /** {functional component only} - An array of index of the bound dispatch method stored in `componentActionsRecord` */;
+ hooksIndex: number[] | null;
+ /** An object contains all props of the current component */
+ props: { [key: string]: any };
+ /** The base duration of the current Fiber node's render phase (excluding the time taken to render its children). */
+ selfBaseDuration?: number;
+ /** An object contains all states of the current class component */
+ state: { [key: string]: any } | null;
+ /** The total base duration of the current Fiber node's subtree. */
+ treeBaseDuration?: number;
+}
+
+/**
+ * @member state - states within the current functional component
+ * @member component - contains bound dispatch method to update state of the current functional component
+ */
+export interface HookStateItem {
+ component: any;
+ state: any;
+ isReducer: boolean;
+ lastAction?: any;
+ reducer?: Function;
+}
+
+export type WorkTag =
+ | 0
+ | 1
+ | 2
+ | 3
+ | 4
+ | 5
+ | 6
+ | 7
+ | 8
+ | 9
+ | 10
+ | 11
+ | 12
+ | 13
+ | 14
+ | 15
+ | 16
+ | 17
+ | 18
+ | 19
+ | 20
+ | 21
+ | 22
+ | 23
+ | 24;
+
+export const FunctionComponent = 0;
+export const ClassComponent = 1;
+/** Before we know whether it is function or class */
+export const IndeterminateComponent = 2;
+/** Root of a host tree. Could be nested inside another node. */
+export const HostRoot = 3;
+/** A subtree. Could be an entry point to a different renderer. */
+export const HostPortal = 4;
+/**
+ * Host Component: a type of component that represents a native DOM element in the browser environment, such as div, span, input, h1 etc.
+ */
+export const HostComponent = 5; // has stateNode of html elements
+export const HostText = 6;
+export const Fragment = 7;
+export const Mode = 8;
+export const ContextConsumer = 9;
+export const ContextProvider = 10;
+export const ForwardRef = 11;
+export const Profiler = 12;
+export const SuspenseComponent = 13;
+export const MemoComponent = 14;
+export const SimpleMemoComponent = 15; // A higher order component where if the component renders the same result given the same props, react skips rendering the component and uses last rendered result. Has memoizedProps/memoizedState but no stateNode
+export const LazyComponent = 16;
+export const IncompleteClassComponent = 17;
+export const DehydratedFragment = 18;
+export const SuspenseListComponent = 19;
+export const FundamentalComponent = 20;
+export const ScopeComponent = 21;
+export const Block = 22;
+export const OffscreenComponent = 23;
+export const LegacyHiddenComponent = 24;
+
+/**
+ * @type Fiber - The internal data structure that represents a `fiberNode` or a component in the React component tree
+ *
+ * {@link https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react}
+ * @member actualDuration - The time taken to render the current Fiber node and its descendants during the previous render cycle. This value is used to optimize the rendering of components and to provide performance metrics to developers.
+ * @member actualStartTime - The time at which the rendering of the current Fiber node started during the previous render cycle.
+ * @member child - Pointer to the first child.
+ * @member elementType - The type of the current Fiber node's element (e.g. the component function or class, or the DOM element type). For class/functional component, elmementType stores the function definition.
+ * @member key - The key a user assigned to the component or null if they didn't assign one
+ * @member memoizedProps - The current props of the component associated with the current Fiber node.
+ * @member memoizedState - The current state of the component associated with the current Fiber node.
+ * @member selfBaseDuration - The base duration of the current Fiber node's render phase (excluding the time taken to render its children). This field is only set when the enableProfilerTimer flag is enabled.
+ * @member sibling - Pointer to next sibling
+ * @member stateNode - The local state associated with this fiber. For classComponent, stateNode contains current state and the bound update methods of the component
+ * @member tag - The type of the current Fiber node, such as FunctionComponent, ClassComponent, or HostComponent (for DOM elements).
+ * @member treeBaseDuration - The total base duration of the current Fiber node's subtree. This field is only set when the enableProfilerTimer flag is enabled.
+ * @member _debugHookTypes - An array of hooks used for debugging purposes.
+ */
+export type Fiber = {
+ /**
+ * Time spent rendering this Fiber and its descendants for the current update.
+ *
+ * This tells us how well the tree makes use of sCU for memoization. It is reset to 0 each time we render and only updated when we don't bailout.
+ *
+ * This field is only set when the enableProfilerTimer flag is enabled.
+ */
+ actualDuration?: number;
+
+ /**
+ * If the Fiber is currently active in the "render" phase, this marks the time at which the work began.
+ *
+ * This field is only set when the enableProfilerTimer flag is enabled.
+ */
+ actualStartTime?: number;
+
+ // Singly Linked List Tree Structure.
+ /** Pointer to the first child. */
+ child: Fiber | null;
+
+ /**
+ * The type of the current Fiber node's element (e.g. the component function or class, or the DOM element type).
+ *
+ * For class/functional component, elmementType stores the function definition.
+ */
+ elementType: any;
+
+ /**
+ * Unique key string assigned by the user when making component on null if they didn't assign one
+ */
+ key: string | null;
+
+ /** The current state for a functional component associated with the current Fiber node. */
+ memoizedState: any;
+
+ /** The current props of the component associated with the current Fiber node. */
+ memoizedProps: any;
+
+ /**
+ * Duration of the most recent render time for this Fiber. This value is not updated when we bailout for memoization purposes.
+ *
+ * This field is only set when the enableProfilerTimer flag is enabled.
+ */
+ selfBaseDuration?: number;
+
+ // Singly Linked List Tree Structure.
+ /** Pointer to next sibling */
+ sibling: Fiber | null;
+
+ /**
+ * The local state associated with this fiber.
+ *
+ * For classComponent, stateNode contains current state and the bound update methods of the component.
+ */
+ stateNode: any;
+
+ /** The type of the current Fiber node, such as FunctionComponent, ClassComponent, or HostComponent (for DOM elements). */
+ tag: WorkTag;
+
+ /**
+ * Sum of base times for all descendants of this Fiber. This value bubbles up during the "complete" phase.
+ *
+ * This field is only set when the enableProfilerTimer flag is enabled.
+ */
+ treeBaseDuration?: number;
+
+ /** An array of hooks used for debugging purposes. */
+ _debugHookTypes: string[] | null;
+};
+
+/**
+ * @type FiberRoot - The internal data structure that represents a fiberRootNode or the top-level node of a single component tree
+ *
+ * FiberRoot data structure has several properties. For Reactime, we only access the `current` property which contains the tree structure made of `fiberNode`. Each `fiberNode` contains a component data in the React component tree.
+ */
+export type FiberRoot = {
+ current: Fiber;
+};
diff --git a/src/backend/types/linkFiberTypes.ts b/src/backend/types/linkFiberTypes.ts
new file mode 100644
index 000000000..d06ee0e9e
--- /dev/null
+++ b/src/backend/types/linkFiberTypes.ts
@@ -0,0 +1,26 @@
+import { FiberRoot } from './backendTypes';
+
+/**
+ * @interface DevTools - A global object provided by the React Developer Tools extension. It provides a set of methods that allow developers to inspect and manipulate React components in the browser.
+ */
+export interface DevTools {
+ /**
+ * @property renderers - an Map object containing information about the React renders that are currently active on the page. The react version being used can be obtained at key = 1.
+ */
+ renderers: Map<1, undefined | { version: string }>;
+ /**
+ * @method getFiberRoots - get the Set of fiber roots that are currently mounted for the given rendererID. If not found, initalize a new empty Set at renderID key.
+ * @param renderID - a unique identifier for a specific instance of a React renderer. When a React application is first mounted, it will receive a rendererID. This rendererID will remain the same for the entire lifecycle of the application, even if the state is updated and the components are re-rendered/unmounted/added. However, if the application is unmounted and re-mounted again, it will receive a new rendererID.
+ * @return A set of fiberRoot.
+ */
+ getFiberRoots: (rendererID: number) => Set;
+
+ /**
+ * @method onCommitFiberRoot - After the state of a component in a React Application is updated, the virtual DOM will be updated. When a render has been commited for a root, onCommitFiberRoot will be invoked to determine if the component is being mounted, updated, or unmounted. After that, this method will send update information to the React DevTools to update its UI to reflect the change.
+ * @param rendererID - a unique identifier for a specific instance of a React renderer
+ * @param root - root of the rendered tree (a.k.a the root of the React Application)
+ * @param priorityLevel
+ * @return void
+ */
+ onCommitFiberRoot: (rendererID: number, root: FiberRoot, priorityLevel: any) => void;
+}
diff --git a/src/extension/background.js b/src/extension/background.js
index 404930445..42bba411b 100644
--- a/src/extension/background.js
+++ b/src/extension/background.js
@@ -1,75 +1,445 @@
-/* eslint-disable max-len */
-/* eslint-disable no-param-reassign */
-// store ports in an array
+// Store ports in an array.
const portsArr = [];
const reloaded = {};
const firstSnapshotReceived = {};
-// there will be the same number of objects in here as there are reactime tabs open for each user application being worked on
+
+// Toggle for recording accessibility snapshots
+let toggleAxRecord = false;
+
+// There will be the same number of objects in here as there are
+// Reactime tabs open for each user application being worked on.
+let activeTab;
const tabsObj = {};
+// Will store Chrome web vital metrics and their corresponding values.
+const metrics = {};
+
+// Helper function to check if a URL is localhost
+function isLocalhost(url) {
+ return url?.startsWith('http://localhost:') || url?.startsWith('https://localhost:');
+}
+
+// Helper function to find localhost tab
+async function findLocalhostTab() {
+ try {
+ // First check current window
+ const currentWindowTabs = await chrome.tabs.query({ currentWindow: true });
+ const localhostTab = currentWindowTabs.find((tab) => tab.url && isLocalhost(tab.url));
+
+ if (localhostTab) {
+ return localhostTab;
+ }
+
+ // If not found in current window, check all windows
+ const allTabs = await chrome.tabs.query({});
+ const localhostTabAnyWindow = allTabs.find((tab) => tab.url && isLocalhost(tab.url));
+
+ if (localhostTabAnyWindow) {
+ // Focus the window containing the localhost tab
+ await chrome.windows.update(localhostTabAnyWindow.windowId, { focused: true });
+ return localhostTabAnyWindow;
+ }
+
+ return null;
+ } catch (error) {
+ console.error('Error finding localhost tab:', error);
+ return null;
+ }
+}
+
+//keep alive functionality to address port disconnection issues
+function setupKeepAlive() {
+ // Clear any existing keep-alive alarms to prevent duplicates
+ chrome.alarms.clear('keepAlive', (wasCleared) => {
+ if (wasCleared) {
+ console.log('Cleared existing keep-alive alarm.');
+ }
+ });
+
+ // Create a new keep-alive alarm, we found .5 min to resolve the idle time port disconnection
+ chrome.alarms.create('keepAlive', { periodInMinutes: 0.5 });
+
+ // Log active alarms for debugging
+ chrome.alarms.getAll((alarms) => {
+ console.log(
+ 'Active alarms:',
+ alarms.map((alarm) => alarm.name),
+ );
+ });
+
+ // Listen for the keep-alive alarm
+ chrome.alarms.onAlarm.addListener((alarm) => {
+ if (alarm.name === 'keepAlive') {
+ console.log('Keep-alive alarm triggered.');
+ pingServiceWorker();
+ }
+ });
+}
+
+// Ping the service worker to keep it alive
+function pingServiceWorker() {
+ try {
+ chrome.runtime.getPlatformInfo(() => {
+ console.log('Service worker pinged successfully.');
+ });
+ } catch (error) {
+ console.error('Failed to ping service worker:', error);
+
+ // Fallback: Trigger an empty event to wake up the service worker
+ chrome.runtime.sendMessage({ type: 'ping' }, (response) => {
+ if (chrome.runtime.lastError) {
+ console.error('Fallback message failed:', chrome.runtime.lastError.message);
+ } else {
+ console.log('Fallback message sent successfully:', response);
+ }
+ });
+ }
+}
+
+// function pruning the chrome ax tree and pulling the relevant properties
+const pruneAxTree = (axTree) => {
+ const axArr = [];
+ let orderCounter = 0;
+
+ for (const node of axTree) {
+ let {
+ backendDOMNodeId,
+ childIds,
+ ignored,
+ name,
+ nodeId,
+ ignoredReasons,
+ parentId,
+ properties,
+ role,
+ } = node;
+
+ if (!name) {
+ if (ignored) {
+ name = { value: 'ignored node' };
+ } else {
+ name = { value: 'no name' };
+ }
+ }
+ if (!name.value) {
+ name.value = 'no name';
+ }
+ //if the node is ignored, it should be given an order number as it won't be read at all
+ if (role.type === 'role') {
+ const axNode = {
+ backendDOMNodeId: backendDOMNodeId,
+ childIds: childIds,
+ ignored: ignored,
+ ignoredReasons: ignoredReasons,
+ name: name,
+ nodeId: nodeId,
+ ignoredReasons: ignoredReasons,
+ parentId: parentId,
+ properties: properties,
+ };
+ axArr.push(axNode);
+ }
+ }
+
+ // Sort nodes by backendDOMNodeId in ascending order
+
+ // Assign order based on sorted position
+ for (const axNode of axArr) {
+ if (!axNode.ignored) {
+ // Assuming you only want to assign order to non-ignored nodes
+ axNode.order = orderCounter++;
+ } else {
+ axNode.order = null; // Or keep it undefined, based on your requirement
+ }
+ }
+ return axArr;
+};
+
+// attaches Chrome Debugger API to tab for running future commands
+function attachDebugger(tabId, version) {
+ return new Promise((resolve, reject) => {
+ chrome.debugger.attach({ tabId: tabId }, version, () => {
+ if (chrome.runtime.lastError) {
+ reject(chrome.runtime.lastError);
+ } else {
+ resolve();
+ }
+ });
+ });
+}
+
+// sends commands with Chrome Debugger API
+function sendDebuggerCommand(tabId, command, params = {}) {
+ return new Promise((resolve, reject) => {
+ chrome.debugger.sendCommand({ tabId: tabId }, command, params, (response) => {
+ if (chrome.runtime.lastError) {
+ reject(chrome.runtime.lastError);
+ } else {
+ resolve(response);
+ }
+ });
+ });
+}
+
+// detaches Chrome Debugger API from tab
+function detachDebugger(tabId) {
+ return new Promise((resolve, reject) => {
+ chrome.debugger.detach({ tabId: tabId }, () => {
+ if (chrome.runtime.lastError) {
+ reject(chrome.runtime.lastError);
+ } else {
+ resolve();
+ }
+ });
+ });
+}
+
+// returns a pruned accessibility tree obtained using the Chrome Debugger API
+async function axRecord(tabId) {
+ try {
+ await attachDebugger(tabId, '1.3');
+ await sendDebuggerCommand(tabId, 'Accessibility.enable');
+ const response = await sendDebuggerCommand(tabId, 'Accessibility.getFullAXTree');
+ const pruned = pruneAxTree(response.nodes);
+ await detachDebugger(tabId);
+ return pruned;
+ } catch (error) {
+ console.error('axRecord debugger command failed:', error);
+ }
+}
+
+// Chrome Debugger API is unused unless accessibility features are toggled on with UI.
+// This function will replace the current empty snapshot if accessibility features are toggled on and the current location's accessibility snapshot has not yet been recorded.
+async function replaceEmptySnap(tabsObj, tabId, toggleAxRecord) {
+ if (tabsObj[tabId].currLocation.axSnapshot === 'emptyAxSnap' && toggleAxRecord === true) {
+ // add new ax snapshot to currlocation
+ const addedAxSnap = await axRecord(tabId);
+ tabsObj[tabId].currLocation.axSnapshot = addedAxSnap;
+ // modify array to include the new recorded ax snapshot
+ tabsObj[tabId].axSnapshots[tabsObj[tabId].currLocation.index] = addedAxSnap;
+ }
+}
+// This function will create the first instance of the test app's tabs object
+// which will hold test app's snapshots, link fiber tree info, chrome tab info, etc.
function createTabObj(title) {
- // updating tabsObj
+ // TO-DO for save button
+ // Save State
+ // Knowledge of the comparison snapshot
+ // Check for it in the reducer
+ // update tabsObj
return {
title,
- // snapshots is an array of ALL state snapshots for the reactime tab working on a specific user application
+ // snapshots is an array of ALL state snapshots for stateful and stateless
+ // components the Reactime tab working on a specific user application
snapshots: [],
+ // axSnapshots is an array of the chrome accessibility tree at different points for state and stateless applications
+ axSnapshots: [],
+ // index here is the tab index that shows total amount of state changes
index: 0,
- //* this is our pointer so we know what the current state the user is checking (this accounts for time travel aka when user clicks jump on the UI)
+ //* currLocation points to the current state the user is checking
+ // (this accounts for time travel aka when user clicks jump on the UI)
currLocation: null,
- //* inserting a new property to build out our hierarchy dataset for d3
+ // points to the node that will generate the next child set by newest node or jump
+ currParent: 0,
+ // points to the current branch
+ currBranch: 0,
+ // inserting a new property to build out our hierarchy dataset for d3
hierarchy: null,
+ // status checks: Content Script launched, React Dev Tools installed, target is react app
+ status: {
+ contentScriptLaunched: true,
+ reactDevToolsInstalled: false,
+ targetPageisaReactApp: false,
+ },
+ // Note: Paused = Locked
mode: {
- persist: false,
- locked: false,
- paused: false,
+ paused: true,
},
+ // stores web metrics calculated by the content script file
+ webMetrics: {},
};
}
-class Node {
- constructor(obj, tabObj) {
- // eslint-disable-next-line no-param-reassign
- // eslint-disable-next-line no-multi-assign
- // eslint-disable-next-line no-plusplus
- this.index = tabObj.index++;
- this.stateSnapshot = obj;
+// Each node stores a history of the link fiber tree.
+// In practice, new Nodes are passed the following arguments:
+// 1. param 'obj' : arg request.payload, which is an object containing a tree from snapShot.ts and a route property
+// 2. param tabObj: arg tabsObj[tabId], which is an object that holds info about a specific tab. Should change the name of tabObj to tabCollection or something
+class HistoryNode {
+ constructor(tabObj, newStateSnapshot, newAxSnapshot) {
+ // continues the order of number of total state changes
+ this.index = tabObj.index;
+ tabObj.index += 1;
+ // continues the order of number of states changed from that parent
+ tabObj.currParent += 1;
+ this.name = tabObj.currParent;
+ // marks from what branch this node is originated
+ this.branch = tabObj.currBranch;
+ this.stateSnapshot = newStateSnapshot;
+ this.axSnapshot = newAxSnapshot;
this.children = [];
}
}
+function countCurrName(rootNode, name) {
+ if (rootNode.name > name) {
+ return 0;
+ }
+ if (rootNode.name === name) {
+ return 1;
+ }
+ let branch = 0;
+ rootNode.children.forEach((child) => {
+ branch += countCurrName(child, name);
+ });
+ return branch;
+}
+
+// Adds a new node to the current location.
+// Invoked in the case 'recordSnap'.
+// In practice, sendToHierarchy is passed the following arguments:
+// 1. param tabObj : arg tabObj[tabId]
+// 2. param newNode : arg an instance of the Node class
function sendToHierarchy(tabObj, newNode) {
+ // newNode.axSnapshot = tabObj.axSnapshots[tabObj.axSnapshots.length - 1];
if (!tabObj.currLocation) {
tabObj.currLocation = newNode;
tabObj.hierarchy = newNode;
} else {
+ const currNameCount = countCurrName(tabObj.hierarchy, newNode.name);
+ newNode.branch = currNameCount;
+ tabObj.currBranch = newNode.branch;
tabObj.currLocation.children.push(newNode);
tabObj.currLocation = newNode;
}
}
-function changeCurrLocation(tabObj, rootNode, index) {
+// This function is used when time jumping to a previous state,
+// so that it runs recursively until it finds the correct index,
+// and updates the tabsObject to the node at that index.
+/* eslint no-param-reassign: ["error", { "props": false }] */
+
+function changeCurrLocation(tabObj, rootNode, index, name) {
+ // index comes from the app's main reducer to locate the correct current location on tabObj
// check if current node has the index wanted
if (rootNode.index === index) {
tabObj.currLocation = rootNode;
+ // Count number of nodes in the tree with name = next name.
+ const currNameCount = countCurrName(tabObj.hierarchy, name + 1);
+ tabObj.currBranch = currNameCount === 0 ? 0 : currNameCount - 1;
+ // index of current location from where the next node will be a child
+ tabObj.currParent = name;
return;
}
// base case if no children
- if (!rootNode.children.length) {
+ if (!rootNode || !rootNode.children.length) {
return;
// if not, recurse on each one of the children
}
- rootNode.children.forEach(child => {
- changeCurrLocation(tabObj, child, index);
- });
+
+ if (rootNode.children) {
+ rootNode.children.forEach((child) => {
+ changeCurrLocation(tabObj, child, index, name);
+ });
+ }
}
+async function getActiveTab() {
+ try {
+ // First try to find a localhost tab
+ const localhostTab = await findLocalhostTab();
+ if (localhostTab) {
+ return localhostTab.id;
+ }
+
+ // If no localhost tab is found, provide a more informative error
+ const errorMessage =
+ 'No localhost tab found. Please ensure:\n' +
+ '1. A React development server is running\n' +
+ '2. The server is using localhost\n' +
+ '3. The development page is open in Chrome';
-// establishing connection with devtools
-chrome.runtime.onConnect.addListener(port => {
- // push every port connected to the ports array
- portsArr.push(port);
+ console.warn(errorMessage);
+
+ // Fallback to current active tab
+ const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
+ if (tabs.length > 0) {
+ return tabs[0].id;
+ }
+
+ throw new Error(errorMessage);
+ } catch (error) {
+ console.error('Error in getActiveTab:', error);
+ throw error;
+ }
+}
+
+/*
+ The 'chrome.runtime' API allows a connection to the background service worker (background.js).
+ This allows us to set up listener's for when we connect, message, and disconnect the script.
+*/
+
+// INCOMING CONNECTION FROM FRONTEND (MainContainer) TO BACKGROUND.JS
+// Establishing incoming connection with Reactime.
+chrome.runtime.onConnect.addListener(async (port) => {
+ /*
+ On initial connection, there is an onConnect event emitted. The 'addlistener' method provides a communication channel object ('port') when we connect to the service worker ('background.js') and applies it as the argument to it's 1st callback parameter.
+
+ the 'port' (type: communication channel object) is the communication channel between different components within our Chrome extension, not to a port on the Chrome browser tab or the extension's port on the Chrome browser.
+
+ The port object facilitates communication between the Reactime front-end and this 'background.js' script. This allows you to:
+ 1. send messages and data
+ (look for 'onMessage'/'postMessage' methods within this page)
+ 2. receive messages and data
+ (look for 'addListener' methods within this page)
+ between the front-end and the background.
+
+ To establish communication between different parts of your extension:
+ for the connecting end: use chrome.runtime.connect()
+ for the listening end: use chrome.runtime.onConnect.
+ Once the connection is established, a port object is passed to the addListener callback function, allowing you to start exchanging data.
+
+ Again, this port object is used for communication within your extension, not for communication with external ports or tabs in the Chrome browser. If you need to interact with specific tabs or external ports, you would use other APIs or methods, such as chrome.tabs or other Chrome Extension APIs.
+ */
+ portsArr.push(port); // push each Reactime communication channel object to the portsArr
+ // sets the current Title of the Reactime panel
+
+ /**
+ * Sends messages to ports in the portsArr array, triggering a tab change action.
+ */
+ function sendMessagesToPorts() {
+ portsArr.forEach((bg, index) => {
+ bg.postMessage({
+ action: 'changeTab',
+ payload: { tabId: activeTab.id, title: activeTab.title },
+ });
+ });
+ }
+ if (port.name === 'keepAlivePort') {
+ console.log('Keep-alive port connected:', port);
+
+ // Keep the port open by responding to any message
+ port.onMessage.addListener((msg) => {
+ console.log('Received message from content script:', msg);
+ });
+
+ port.onDisconnect.addListener(() => {
+ console.warn('Keep-alive port disconnected.');
+ });
+ }
+ if (portsArr.length > 0 && Object.keys(tabsObj).length > 0) {
+ //if the activeTab is not set during the onActivate API, run a query to get the tabId and set activeTab
+ if (!activeTab) {
+ const tabId = await getActiveTab();
+ chrome.tabs.get(tabId, (tab) => {
+ // never set a reactime instance to the active tab
+ if (!tab.pendingUrl?.match('^chrome-extension')) {
+ activeTab = tab;
+ sendMessagesToPorts();
+ }
+ });
+ }
+ }
- // send tabs obj to the connected devtools as soon as connection to devtools is made
if (Object.keys(tabsObj).length > 0) {
port.postMessage({
action: 'initialConnectSnapshots',
@@ -77,159 +447,375 @@ chrome.runtime.onConnect.addListener(port => {
});
}
- // every time devtool is closed, remove the port from portsArr
- port.onDisconnect.addListener(e => {
- for (let i = 0; i < portsArr.length; i += 1) {
- if (portsArr[i] === e) {
- portsArr.splice(i, 1);
- break;
- }
+ // Handles port disconnection by removing the disconnected port
+ port.onDisconnect.addListener(() => {
+ const index = portsArr.indexOf(port);
+ if (index !== -1) {
+ console.warn(`Port at index ${index} disconnected. Removing it.`);
+ portsArr.splice(index, 1);
+
+ // Notify remaining ports about the disconnection
+ portsArr.forEach((remainingPort) => {
+ try {
+ remainingPort.postMessage({
+ action: 'portDisconnect',
+ });
+ } catch (error) {
+ console.warn('Failed to notify port of disconnection:', error);
+ }
+ });
}
});
- // receive snapshot from devtools and send it to contentScript
- port.onMessage.addListener(msg => {
- // ---------------------------------------------------------------
- // message incoming from devTools should look like this:
- // {
- // action: 'emptySnap',
- // payload: tabsObj,
- // tabId: 101
- // }
- // ---------------------------------------------------------------
+ // INCOMING MESSAGE FROM FRONTEND (MainContainer) TO BACKGROUND.js
+ // listen for message containing a snapshot from devtools and send it to contentScript -
+ // (i.e. they're all related to the button actions on Reactime)
+ port.onMessage.addListener(async (msg) => {
const { action, payload, tabId } = msg;
+ console.log(`Received message - Action: ${action}, Payload:`, payload);
+ if (!payload && ['import', 'setPause', 'jumpToSnap'].includes(action)) {
+ console.error(`Invalid payload received for action: ${action}`, new Error().stack);
+ }
+
switch (action) {
- case 'import':
- tabsObj[tabId].snapshots = payload;
- return;
+ // import action comes through when the user uses the "upload" button on the front end to import an existing snapshot tree
+ case 'import': // create a snapshot property on tabId and set equal to tabs object
+ // may need do something like filter payload from stateless
+ tabsObj[tabId].snapshots = payload.snapshots; // reset snapshots to page last state recorded
+ tabsObj[tabId].axSnapshots = payload.axSnapshots; // TRYING to import axsnapshots
+ // tabsObj[tabId].hierarchy = savedSnapshot.hierarchy; // why don't we just use hierarchy? Because it breaks everything...
+ tabsObj[tabId].hierarchy.children = payload.hierarchy.children; // resets hierarchy to last state recorded
+ tabsObj[tabId].hierarchy.stateSnapshot = payload.hierarchy.stateSnapshot; // resets hierarchy to last state recorded
+ tabsObj[tabId].hierarchy.axSnapshot = payload.hierarchy.axSnapshot; // TRYING to import hierarchy axsnapshot
+ tabsObj[tabId].currLocation = payload.currLocation; // resets currLocation to last state recorded
+ tabsObj[tabId].index = payload.index; //reset index to last state recorded
+ tabsObj[tabId].currParent = payload.currParent; // reset currParent to last state recorded
+ tabsObj[tabId].currBranch = payload.currBranch; // reset currBranch to last state recorded
+
+ return true; // return true so that port remains open
+
+ // emptySnap actions comes through when the user uses the 'clear' button on the front end to clear the snapshot history and move slider back to 0 position
case 'emptySnap':
- tabsObj[tabId].snapshots.splice(1);
- // reset children in root node to reset graph
- tabsObj[tabId].hierarchy.children = [];
- // reassigning pointer to the appropriate node to branch off of
- tabsObj[tabId].currLocation = tabsObj[tabId].hierarchy;
- // reset index
- tabsObj[tabId].index = 1;
- return;
- case 'setLock':
- tabsObj[tabId].mode.locked = payload;
- break;
- case 'setPause':
+ tabsObj[tabId].snapshots = [tabsObj[tabId].snapshots[tabsObj[tabId].currLocation.index]]; // reset snapshots to current page state
+ tabsObj[tabId].hierarchy.children = []; // resets hierarchy
+ tabsObj[tabId].hierarchy.stateSnapshot = {
+ // resets hierarchy to current page state
+ // not sure why they are doing a "shallow" deep copy
+ ...tabsObj[tabId].snapshots[0],
+ };
+ tabsObj[tabId].axSnapshots = [
+ tabsObj[tabId].axSnapshots[tabsObj[tabId].currLocation.index],
+ ]; // resets axSnapshots to current page state
+ tabsObj[tabId].hierarchy.axSnapshot = tabsObj[tabId].axSnapshots[0]; // resets hierarchy to accessibility tree of current page state
+ tabsObj[tabId].index = 1; //reset index
+ tabsObj[tabId].currParent = 0; // reset currParent
+ tabsObj[tabId].currBranch = 1; // reset currBranch
+ tabsObj[tabId].currLocation = tabsObj[tabId].hierarchy; // reset currLocation
+
+ return true; // return true so that port remains open
+
+ case 'setPause': // Pause = lock on tab
tabsObj[tabId].mode.paused = payload;
- break;
- case 'setPersist':
- tabsObj[tabId].mode.persist = payload;
- break;
+ return true; // return true so that port remains open
+
+ // Handling the launchContentScript case with proper validation
+ case 'launchContentScript': {
+ if (!tabId) {
+ console.error('No tabId provided for content script injection');
+ return false;
+ }
+
+ try {
+ // Validate the tab exists before injecting
+ chrome.tabs.get(tabId, async (tab) => {
+ if (chrome.runtime.lastError) {
+ console.error('Error getting tab:', chrome.runtime.lastError);
+ return;
+ }
+
+ // Skip injection for chrome:// URLs
+ if (tab.url?.startsWith('chrome://')) {
+ console.warn('Cannot inject scripts into chrome:// URLs');
+ return;
+ }
+
+ try {
+ await chrome.scripting.executeScript({
+ target: { tabId: tab.id },
+ files: ['bundles/content.bundle.js'],
+ });
+ console.log('Content script injected successfully');
+ } catch (err) {
+ console.error('Error injecting content script:', err);
+ }
+ });
+ } catch (err) {
+ console.error('Error in launchContentScript:', err);
+ }
+ return true;
+ }
+
+ case 'jumpToSnap':
+ chrome.tabs.sendMessage(tabId, msg);
+ return true; // attempt to fix message port closing error, consider return Promise
+
+ case 'toggleRecord':
+ chrome.tabs.sendMessage(tabId, msg);
+ return true;
+
+ case 'toggleAxRecord':
+ toggleAxRecord = !toggleAxRecord;
+
+ await replaceEmptySnap(tabsObj, tabId, toggleAxRecord);
+ // sends new tabs obj to devtools
+ if (portsArr.length > 0) {
+ portsArr.forEach((bg) =>
+ bg.postMessage({
+ action: 'sendSnapshots',
+ payload: tabsObj,
+ tabId,
+ }),
+ );
+ }
+ return true; // return true so that port remains open
+
+ case 'reinitialize':
+ chrome.tabs.sendMessage(tabId, msg);
+ return true;
+
default:
+ return true;
}
-
- chrome.tabs.sendMessage(tabId, msg);
});
});
-// background.js recieves message from contentScript.js
-chrome.runtime.onMessage.addListener((request, sender) => {
- // IGNORE THE AUTOMATIC MESSAGE SENT BY CHROME WHEN CONTENT SCRIPT IS FIRST LOADED
- if (request.type === 'SIGN_CONNECT') return;
+// INCOMING MESSAGE FROM CONTENT SCRIPT TO BACKGROUND.JS
+// background.js listening for a message from contentScript.js
+chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
+ // AUTOMATIC MESSAGE SENT BY CHROME WHEN CONTENT SCRIPT IS FIRST LOADED: set Content
+ if (request.type === 'SIGN_CONNECT') {
+ return true;
+ }
const tabTitle = sender.tab.title;
const tabId = sender.tab.id;
- const { action, index } = request;
+ const { action, index, name, value } = request;
let isReactTimeTravel = false;
- // Filter out tabs that don't have reactime
- if (action === 'tabReload' || action === 'recordSnap' || action === 'jumpToSnap') {
- isReactTimeTravel = true;
- } else return;
+ if (name) {
+ metrics[name] = value;
+ }
- // everytime we get a new tabid, add it to the object
+ // Filter out tabs that don't have reactime, tabs that dont use react?
+ if (
+ action === 'tabReload' ||
+ action === 'recordSnap' ||
+ action === 'jumpToSnap' ||
+ action === 'injectScript' ||
+ action === 'devToolsInstalled' ||
+ action === 'aReactApp'
+ ) {
+ isReactTimeTravel = true;
+ } else {
+ return true;
+ }
+ // everytime we get a new tabId, add it to the object
if (isReactTimeTravel && !(tabId in tabsObj)) {
tabsObj[tabId] = createTabObj(tabTitle);
}
-
- const { persist } = tabsObj[tabId].mode;
-
switch (action) {
- case 'jumpToSnap': {
- changeCurrLocation(tabsObj[tabId], tabsObj[tabId].hierarchy, index);
+ case 'attemptReconnect': {
+ const success = 'portSuccessfullyConnected';
+ sendResponse({ success });
break;
}
- // this case causes d3 graph to display 1 instead of 0
- case 'tabReload': {
- tabsObj[tabId].mode.locked = false;
- tabsObj[tabId].mode.paused = false;
- // dont remove snapshots if persisting
- if (!persist) {
- tabsObj[tabId].snapshots.splice(1);
- // reset children in root node to reset graph
- // if (tabsObj[tabId].hierarchy)
- tabsObj[tabId].hierarchy.children = [];
- // reassigning pointer to the appropriate node to branch off of
- tabsObj[tabId].currLocation = tabsObj[tabId].hierarchy;
- // reset index
- tabsObj[tabId].index = 1;
-
- // send a message to devtools
- portsArr.forEach(bg => bg.postMessage({
- action: 'initialConnectSnapshots',
- payload: tabsObj,
- }));
+ case 'jumpToSnap': {
+ changeCurrLocation(tabsObj[tabId], tabsObj[tabId].hierarchy, index, name);
+ // hack to test without message from mainSlice
+ // toggleAxRecord = true;
+ // record ax tree snapshot of the state that has now been jumped to if user did not toggle button on
+ await replaceEmptySnap(tabsObj, tabId, toggleAxRecord);
+
+ // sends new tabs obj to devtools
+ if (portsArr.length > 0) {
+ portsArr.forEach((bg) =>
+ bg.postMessage({
+ action: 'sendSnapshots',
+ payload: tabsObj,
+ tabId,
+ }),
+ );
+ }
+
+ if (portsArr.length > 0) {
+ portsArr.forEach((bg) =>
+ bg.postMessage({
+ action: 'setCurrentLocation',
+ payload: tabsObj,
+ }),
+ );
}
+ break;
+ }
- reloaded[tabId] = true;
+ // Confirmed React Dev Tools installed, send this info to frontend
+ case 'devToolsInstalled': {
+ tabsObj[tabId].status.reactDevToolsInstalled = true;
+ portsArr.forEach((bg) =>
+ bg.postMessage({
+ action: 'devTools',
+ payload: tabsObj,
+ }),
+ );
+ break;
+ }
+ case 'aReactApp': {
+ tabsObj[tabId].status.targetPageisaReactApp = true;
+ // JR 12.20.23 9.53pm added a message action to send to frontend
+ portsArr.forEach((bg) =>
+ bg.postMessage({
+ action: 'aReactApp',
+ payload: tabsObj,
+ }),
+ );
+ break;
+ }
+ // This injects a script into the app that you're testing Reactime on,
+ // so that Reactime's backend files can communicate with the app's DOM.
+ case 'injectScript': {
+ const injectScript = (file, tab) => {
+ const htmlBody = document.getElementsByTagName('body')[0];
+ const script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script.setAttribute('src', file);
+ // eslint-disable-next-line prefer-template
+ htmlBody.appendChild(script);
+ };
+
+ chrome.scripting.executeScript({
+ target: { tabId },
+ func: injectScript,
+ args: [chrome.runtime.getURL('bundles/backend.bundle.js'), tabId],
+ });
break;
}
case 'recordSnap': {
const sourceTab = tabId;
+ tabsObj[tabId].webMetrics = metrics;
- // first snapshot received from tab
if (!firstSnapshotReceived[tabId]) {
firstSnapshotReceived[tabId] = true;
reloaded[tabId] = false;
-
+ tabsObj[tabId].webMetrics = metrics;
tabsObj[tabId].snapshots.push(request.payload);
- sendToHierarchy(tabsObj[tabId], new Node(request.payload, tabsObj[tabId]));
+
+ // check if accessibility recording has been toggled on
+ let addedAxSnap;
+ if (toggleAxRecord === true) {
+ addedAxSnap = await axRecord(tabId);
+ tabsObj[tabId].axSnapshots.push(addedAxSnap);
+ } else {
+ addedAxSnap = 'emptyAxSnap';
+ tabsObj[tabId].axSnapshots.push(addedAxSnap);
+ }
+ sendToHierarchy(
+ tabsObj[tabId],
+ new HistoryNode(tabsObj[tabId], request.payload, addedAxSnap),
+ );
+
if (portsArr.length > 0) {
- portsArr.forEach(bg => bg.postMessage({
- action: 'initialConnectSnapshots',
- payload: tabsObj,
- }));
+ portsArr.forEach((bg) =>
+ bg.postMessage({
+ action: 'initialConnectSnapshots',
+ payload: tabsObj,
+ }),
+ );
}
+
break;
}
- // don't add anything to snapshot storage if tab is reloaded for the initial snapshot
+ // DUPLICATE SNAPSHOT CHECK
+ const isDuplicateSnapshot = (previous, incoming) => {
+ if (!previous || !incoming) return false;
+ const prevData = previous?.componentData;
+ const incomingData = incoming?.componentData;
+
+ // Check if both snapshots have required data
+ if (!prevData || !incomingData) return false;
+
+ const timeDiff = Math.abs(
+ (incomingData.timestamp || Date.now()) - (prevData.timestamp || Date.now()),
+ );
+ return prevData.actualDuration === incomingData.actualDuration && timeDiff < 1000;
+ };
+
+ const previousSnap = tabsObj[tabId]?.currLocation?.stateSnapshot?.children[0];
+ const incomingSnap = request.payload.children[0];
+
+ if (isDuplicateSnapshot(previousSnap, incomingSnap)) {
+ console.warn('Duplicate snapshot detected, skipping');
+ break;
+ }
+
+ // Or if it is a snapShot after a jump, we don't record it.
if (reloaded[tabId]) {
+ // don't add anything to snapshot storage if tab is reloaded for the initial snapshot
reloaded[tabId] = false;
} else {
tabsObj[tabId].snapshots.push(request.payload);
- //! INVOKING buildHierarchy FIGURE OUT WHAT TO PASS IN!!!!
- sendToHierarchy(tabsObj[tabId], new Node(request.payload, tabsObj[tabId]));
+ // INVOKING buildHierarchy FIGURE OUT WHAT TO PASS IN
+ if (!tabsObj[tabId][index]) {
+ // check if accessibility recording has been toggled on
+ let addedAxSnap;
+ if (toggleAxRecord === true) {
+ addedAxSnap = await axRecord(tabId);
+ tabsObj[tabId].axSnapshots.push(addedAxSnap);
+ } else {
+ addedAxSnap = 'emptyAxSnap';
+ tabsObj[tabId].axSnapshots.push(addedAxSnap);
+ }
+ sendToHierarchy(
+ tabsObj[tabId],
+ new HistoryNode(tabsObj[tabId], request.payload, addedAxSnap),
+ );
+ }
}
- // send message to devtools
+
+ // sends new tabs obj to devtools
if (portsArr.length > 0) {
- portsArr.forEach(bg => bg.postMessage({
- action: 'sendSnapshots',
- payload: tabsObj,
- sourceTab,
- }));
+ portsArr.forEach((bg, index) => {
+ try {
+ bg.postMessage({
+ action: 'sendSnapshots',
+ payload: tabsObj,
+ sourceTab,
+ });
+ } catch (error) {
+ console.warn(`Failed to send snapshots to port at index ${index}:`, error);
+ }
+ });
+ } else {
+ console.warn('No active ports to send snapshots to.');
}
- break;
}
default:
break;
}
+ return true; // attempt to fix close port error
});
-// when tab is closed, remove the tabid from the tabsObj
-chrome.tabs.onRemoved.addListener(tabId => {
+// when tab is closed, remove the tabId from the tabsObj
+chrome.tabs.onRemoved.addListener((tabId) => {
// tell devtools which tab to delete
if (portsArr.length > 0) {
- portsArr.forEach(bg => bg.postMessage({
- action: 'deleteTab',
- payload: tabId,
- }));
+ portsArr.forEach((bg) =>
+ bg.postMessage({
+ action: 'deleteTab',
+ payload: tabId,
+ }),
+ );
}
// delete the tab from the tabsObj
@@ -238,6 +824,94 @@ chrome.tabs.onRemoved.addListener(tabId => {
delete firstSnapshotReceived[tabId];
});
+// when a new url is loaded on the same tab,
+// this remove the tabid from the tabsObj, recreate the tab and inject the script
+chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
+ // check if the tab title changed to see if tab need to restart
+ if (changeInfo && tabsObj[tabId]) {
+ if (changeInfo.title && changeInfo.title !== tabsObj[tabId].title) {
+ // tell devtools which tab to delete
+ if (portsArr.length > 0) {
+ portsArr.forEach((bg) =>
+ bg.postMessage({
+ action: 'deleteTab',
+ payload: tabId,
+ }),
+ );
+ }
+
+ // delete the tab from the tabsObj
+ delete tabsObj[tabId];
+ delete reloaded[tabId];
+ delete firstSnapshotReceived[tabId];
+
+ // recreate the tab on the tabsObj
+ tabsObj[tabId] = createTabObj(changeInfo.title);
+ }
+ }
+});
+
+// When tab view is changed, put the tabId as the current tab
+chrome.tabs.onActivated.addListener(async (info) => {
+ // Get info about the tab information from tabId
+ try {
+ const tab = await chrome.tabs.get(info.tabId);
+
+ // Only update activeTab if:
+ // 1. It's not a Reactime extension tab
+ // 2. We don't already have a localhost tab being tracked
+ // 3. Or if it is a localhost tab (prioritize localhost)
+ if (!tab.url?.match('^chrome-extension')) {
+ if (isLocalhost(tab.url)) {
+ // Always prioritize localhost tabs
+ activeTab = tab;
+ if (portsArr.length > 0) {
+ portsArr.forEach((bg) =>
+ bg.postMessage({
+ action: 'changeTab',
+ payload: { tabId: tab.id, title: tab.title },
+ }),
+ );
+ }
+ } else if (!activeTab || !isLocalhost(activeTab.url)) {
+ // Only set non-localhost tab as active if we don't have a localhost tab
+ activeTab = tab;
+ if (portsArr.length > 0) {
+ portsArr.forEach((bg) =>
+ bg.postMessage({
+ action: 'changeTab',
+ payload: { tabId: tab.id, title: tab.title },
+ }),
+ );
+ }
+ }
+ }
+ } catch (error) {
+ console.error('Error in tab activation handler:', error);
+ }
+});
+
+// Ensure keep-alive is set up when the extension is installed
+chrome.runtime.onInstalled.addListener(() => {
+ console.log('Extension installed. Setting up keep-alive...');
+ setupKeepAlive();
+});
+
+// Ensure keep-alive is set up when the browser starts
+chrome.runtime.onStartup.addListener(() => {
+ console.log('Browser started. Setting up keep-alive...');
+ setupKeepAlive();
+});
+
+// Optional: Reset keep-alive when a message is received (to cover edge cases)
+chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
+ if (message === 'resetKeepAlive') {
+ console.log('Resetting keep-alive as requested.');
+ setupKeepAlive();
+ sendResponse({ success: true });
+ }
+});
+
// when reactime is installed
// create a context menu that will open our devtools in a new window
chrome.runtime.onInstalled.addListener(() => {
@@ -248,16 +922,34 @@ chrome.runtime.onInstalled.addListener(() => {
});
});
-// when context menu is clicked, listen for the menuItemId,
-// if user clicked on reactime, open the devtools window
-chrome.contextMenus.onClicked.addListener(({ menuItemId }) => {
- const options = {
- type: 'panel',
- left: 0,
- top: 0,
- width: 380,
- height: window.screen.availHeight,
- url: chrome.runtime.getURL('panel.html'),
- };
- if (menuItemId === 'reactime') chrome.windows.create(options);
+chrome.contextMenus.onClicked.addListener((info, tab) => {
+ if (info.menuItemId === 'reactime') {
+ chrome.windows
+ .create({
+ url: chrome.runtime.getURL('panel.html'),
+ type: 'popup',
+ width: 1200,
+ height: 800,
+ })
+ .then((window) => {
+ // Listen for window close
+ chrome.windows.onRemoved.addListener(function windowClosedListener(windowId) {
+ if (windowId === window.id) {
+ // Cleanup when window is closed
+ portsArr.forEach((port) => {
+ try {
+ port.disconnect();
+ } catch (error) {
+ console.warn('Error disconnecting port:', error);
+ }
+ });
+ // Clear the ports array
+ portsArr.length = 0;
+
+ // Remove this specific listener
+ chrome.windows.onRemoved.removeListener(windowClosedListener);
+ }
+ });
+ });
+ }
});
diff --git a/src/extension/build/assets/blackWhiteSquareIcon128.png b/src/extension/build/assets/blackWhiteSquareIcon128.png
new file mode 100644
index 000000000..9ff5c33e0
Binary files /dev/null and b/src/extension/build/assets/blackWhiteSquareIcon128.png differ
diff --git a/src/extension/build/assets/blackWhiteSquareIcon48.png b/src/extension/build/assets/blackWhiteSquareIcon48.png
new file mode 100644
index 000000000..d054e2b6c
Binary files /dev/null and b/src/extension/build/assets/blackWhiteSquareIcon48.png differ
diff --git a/src/extension/build/assets/icon128.png b/src/extension/build/assets/icon128.png
deleted file mode 100644
index 5010da137..000000000
Binary files a/src/extension/build/assets/icon128.png and /dev/null differ
diff --git a/src/extension/build/assets/icon48.png b/src/extension/build/assets/icon48.png
deleted file mode 100644
index bcc344401..000000000
Binary files a/src/extension/build/assets/icon48.png and /dev/null differ
diff --git a/src/extension/build/assets/whiteBlackSquareIcon128.png b/src/extension/build/assets/whiteBlackSquareIcon128.png
new file mode 100644
index 000000000..cb89df108
Binary files /dev/null and b/src/extension/build/assets/whiteBlackSquareIcon128.png differ
diff --git a/src/extension/build/assets/whiteBlackSquareIcon48.png b/src/extension/build/assets/whiteBlackSquareIcon48.png
new file mode 100644
index 000000000..01e46463c
Binary files /dev/null and b/src/extension/build/assets/whiteBlackSquareIcon48.png differ
diff --git a/src/extension/build/assets/whiteBlackSquareLogo.png b/src/extension/build/assets/whiteBlackSquareLogo.png
new file mode 100644
index 000000000..dbd50002c
Binary files /dev/null and b/src/extension/build/assets/whiteBlackSquareLogo.png differ
diff --git a/src/extension/build/devtools.html b/src/extension/build/devtools.html
index 2fd128795..de554fb40 100644
--- a/src/extension/build/devtools.html
+++ b/src/extension/build/devtools.html
@@ -1,15 +1,13 @@
-
+
+
+
+
+
+ Reactime v26.0
+
-
-
-
-
- Reactime
-
-
-
-
-
-
+
+
+
diff --git a/src/extension/build/devtools.js b/src/extension/build/devtools.js
index 25605ed25..02c6d8f96 100644
--- a/src/extension/build/devtools.js
+++ b/src/extension/build/devtools.js
@@ -1 +1,6 @@
-chrome.devtools.panels.create('Reactime', null, 'panel.html', () => {});
+chrome.devtools.panels.create(
+ 'Reactime',
+ 'assets/whiteBlackSquareIcon48.png',
+ 'panel.html',
+ () => {},
+);
diff --git a/src/extension/build/manifest.json b/src/extension/build/manifest.json
index 704f6879d..5d99bd806 100644
--- a/src/extension/build/manifest.json
+++ b/src/extension/build/manifest.json
@@ -1,20 +1,29 @@
{
"name": "Reactime",
- "version": "3.1",
+ "version": "26.1",
"devtools_page": "devtools.html",
- "description": "A Chrome extension that helps debug state in React applications by memorizing the state of components with every render.",
- "manifest_version": 2,
- "content_security_policy": "script-src 'self' 'unsafe-eval' ; object-src 'self'",
+ "description": "A Chrome extension for time travel debugging and performance monitoring in React applications.",
+ "manifest_version": 3,
"background": {
- "scripts": ["bundles/background.bundle.js"],
- "persistent": false
+ "service_worker": "bundles/background.bundle.js"
+ },
+
+ "icons": {
+ "48": "assets/whiteBlackSquareIcon48.png",
+ "128": "assets/whiteBlackSquareIcon128.png"
},
- "icons": { "48": "assets/icon48.png", "128": "assets/icon128.png" },
"content_scripts": [
{
- "matches": [""],
+ "matches": ["http://localhost/*"],
"js": ["bundles/content.bundle.js"]
}
],
- "permissions": ["contextMenus", "tabs", ""]
+ "web_accessible_resources": [
+ {
+ "resources": ["bundles/backend.bundle.js"],
+ "matches": [""]
+ }
+ ],
+ "permissions": ["contextMenus", "tabs", "activeTab", "scripting", "debugger", "alarms"],
+ "host_permissions": [""]
}
diff --git a/src/extension/build/panel.html b/src/extension/build/panel.html
index 5bf78743c..9c475132a 100644
--- a/src/extension/build/panel.html
+++ b/src/extension/build/panel.html
@@ -1,10 +1,10 @@
-
+
- Document
+ Reactime v26.0
diff --git a/src/extension/contentScript.js b/src/extension/contentScript.js
deleted file mode 100644
index 7e7263ab3..000000000
--- a/src/extension/contentScript.js
+++ /dev/null
@@ -1,37 +0,0 @@
-let firstMessage = true;
-
-// listening for messages from npm package
-window.addEventListener('message', msg => {
- // window listener picks up the message it sends, so we should filter
- // messages sent by contentscript
- if (msg.data.action !== 'contentScriptStarted' && firstMessage) {
- // since contentScript is run everytime a page is refreshed
- // tell the background script that the tab has reloaded
- chrome.runtime.sendMessage({ action: 'tabReload' });
- firstMessage = false;
- }
-
- // post initial Message to background.js
- const { action } = msg.data;
- if (action === 'recordSnap') chrome.runtime.sendMessage(msg.data);
-});
-
-// listening for messages from the UI
-chrome.runtime.onMessage.addListener(request => {
- // send the message to npm package
- const { action } = request;
- switch (action) {
- case 'jumpToSnap':
- chrome.runtime.sendMessage(request);
- window.postMessage(request);
- break;
- case 'setLock':
- case 'setPause':
- window.postMessage(request);
- break;
- default:
- break;
- }
-});
-
-window.postMessage({ action: 'contentScriptStarted' });
diff --git a/src/extension/contentScript.ts b/src/extension/contentScript.ts
new file mode 100644
index 000000000..76a992750
--- /dev/null
+++ b/src/extension/contentScript.ts
@@ -0,0 +1,204 @@
+// Web vital metrics calculated by 'web-vitals' npm package to be displayed
+// in Web Metrics tab of Reactime app.
+import { current } from '@reduxjs/toolkit';
+import { onTTFB, onLCP, onFID, onFCP, onCLS, onINP } from 'web-vitals';
+
+const MAX_RECONNECT_ATTEMPTS = 5;
+const INITIAL_RECONNECT_DELAY = 1000;
+const MAX_RECONNECT_DELAY = 16000;
+
+let currentPort = null;
+let isAttemptingReconnect = false;
+
+function establishConnection(attemptNumber = 1) {
+ console.log(`Establishing connection, attempt ${attemptNumber}`);
+
+ try {
+ currentPort = chrome.runtime.connect({ name: 'keepAlivePort' });
+
+ console.log('Port created, setting up listeners');
+
+ currentPort.onMessage.addListener((msg) => {
+ console.log('Port received message:', msg);
+ });
+
+ currentPort.onDisconnect.addListener(() => {
+ const error = chrome.runtime.lastError;
+ console.log('Port disconnect triggered', error);
+
+ // Clear current port
+ currentPort = null;
+
+ // Prevent multiple simultaneous reconnection attempts
+ if (isAttemptingReconnect) {
+ console.log('Already attempting to reconnect, skipping');
+ return;
+ }
+
+ isAttemptingReconnect = true;
+
+ // Calculate delay with exponential backoff
+ const delay = Math.min(
+ INITIAL_RECONNECT_DELAY * Math.pow(2, attemptNumber - 1),
+ MAX_RECONNECT_DELAY,
+ );
+
+ if (attemptNumber <= MAX_RECONNECT_ATTEMPTS) {
+ console.log(
+ `Will attempt reconnection ${attemptNumber}/${MAX_RECONNECT_ATTEMPTS} in ${delay}ms`,
+ );
+
+ window.postMessage(
+ {
+ action: 'portDisconnect',
+ payload: {
+ attemptNumber,
+ maxAttempts: MAX_RECONNECT_ATTEMPTS,
+ nextRetryDelay: delay,
+ },
+ },
+ '*',
+ );
+
+ setTimeout(() => {
+ isAttemptingReconnect = false;
+ establishConnection(attemptNumber + 1);
+ }, delay);
+ } else {
+ console.log('Max reconnection attempts reached');
+ isAttemptingReconnect = false;
+
+ window.postMessage(
+ {
+ action: 'portDisconnect',
+ payload: {
+ autoReconnectFailed: true,
+ message: 'Automatic reconnection failed. Please use the reconnect button.',
+ },
+ },
+ '*',
+ );
+ }
+ });
+
+ // Send initial test message
+ currentPort.postMessage({ type: 'connectionTest' });
+ console.log('Test message sent');
+ } catch (error) {
+ console.error('Error establishing connection:', error);
+ isAttemptingReconnect = false;
+
+ // If immediate connection fails, try again
+ if (attemptNumber <= MAX_RECONNECT_ATTEMPTS) {
+ const delay = INITIAL_RECONNECT_DELAY;
+ console.log(`Connection failed immediately, retrying in ${delay}ms`);
+ setTimeout(() => establishConnection(attemptNumber + 1), delay);
+ }
+ }
+}
+
+// Initial connection
+console.log('Starting initial connection');
+establishConnection();
+
+// Reactime application starts off with this file, and will send
+// first message to background.js for initial tabs object set up.
+// A "tabs object" holds the information of the current tab,
+// such as snapshots, performance metrics, title of app, and so on.
+let firstMessage = true;
+// Listens for window messages (from the injected script on the DOM)
+let isRecording = true;
+
+// INCOMING MESSAGE FROM BACKEND (index.ts) TO CONTENT SCRIPT
+window.addEventListener('message', (msg) => {
+ // Event listener runs constantly based on actions
+ // recorded on the test application from backend files (linkFiber.ts).
+ // Background.js has a listener that includes switch cases, depending on
+ // the name of the action (e.g. 'tabReload').
+ if (firstMessage) {
+ // One-time request tells the background script that the tab has reloaded.
+ chrome.runtime.sendMessage({ action: 'tabReload' });
+ firstMessage = false;
+ }
+
+ // After tabs object has been created from firstMessage, backend (linkFiber.ts)
+ // will send snapshots of the test app's link fiber tree.
+ const { action }: { action: string } = msg.data;
+ if (action === 'recordSnap') {
+ if (isRecording) {
+ // add timestamp to payload for the purposes of duplicate screenshot check in backgroundscript -ellie
+ msg.data.payload.children[0].componentData.timestamp = Date.now();
+ chrome.runtime.sendMessage(msg.data);
+ }
+ }
+ if (action === 'devToolsInstalled') {
+ chrome.runtime.sendMessage(msg.data);
+ }
+ if (action === 'aReactApp') {
+ chrome.runtime.sendMessage(msg.data);
+ }
+});
+
+// FROM BACKGROUND TO CONTENT SCRIPT
+// Listening for messages from the UI of the Reactime extension.
+chrome.runtime.onMessage.addListener((request) => {
+ const { action, port }: { action: string; port?: string } = request;
+ if (action) {
+ // Message being sent from background.js
+ // This is toggling the record button on Reactime when clicked
+ if (action === 'toggleRecord') {
+ isRecording = !isRecording;
+ }
+ // this is only listening for Jump toSnap
+ if (action === 'jumpToSnap') {
+ chrome.runtime.sendMessage(request);
+ // After the jumpToSnap action has been sent back to background js,
+ // it will send the same action to backend files (index.ts) for it execute the jump feature
+ // '*' == target window origin required for event to be dispatched, '*' = no preference
+ window.postMessage(request, '*');
+ }
+ if (action === 'portDisconnect' && !currentPort && !isAttemptingReconnect) {
+ console.log('Received disconnect message, initiating reconnection');
+ // When we receive a port disconnection message, relay it to the window
+ window.postMessage(
+ {
+ action: 'portDisconnect',
+ },
+ '*',
+ );
+
+ // Attempt to re-establish connection
+ establishConnection();
+ }
+
+ if (action === 'reinitialize') {
+ window.postMessage(request, '*');
+ }
+ return true;
+ }
+});
+
+// Performance metrics being calculated by the 'web-vitals' api and
+// sent as an object to background.js.
+// To learn more about Chrome web vitals, see https://web.dev/vitals/.
+const metrics = {};
+const gatherMetrics = ({ name, value }) => {
+ metrics[name] = value;
+ chrome.runtime.sendMessage({
+ type: 'performance:metric',
+ name,
+ value,
+ });
+};
+
+// Functions that calculate web metric values.
+onTTFB(gatherMetrics);
+onLCP(gatherMetrics);
+onFID(gatherMetrics);
+onFCP(gatherMetrics);
+onCLS(gatherMetrics);
+onINP(gatherMetrics);
+
+// Send message to background.js for injecting the initial script
+// into the app's DOM.
+chrome.runtime.sendMessage({ action: 'injectScript' });
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 000000000..e2e7ac64f
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,128 @@
+{
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig to read more about this file */
+
+ /* Projects */
+ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+
+ /* Language and Environment */
+ "target": "ES6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ "jsx": "react" /* Specify what JSX code is generated. */,
+ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
+ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
+
+ /* Modules */
+ "module": "ES6" /* Specify what module code is generated. */,
+ // "rootDir": "./", /* Specify the root folder within your source files. */
+ "moduleResolution": "node10" /* Specify how TypeScript looks up a file from a given module specifier. */,
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ "typeRoots": [
+ "./node_modules/@types"
+ ] /* Specify multiple folders that act like './node_modules/@types'. */,
+ "types": [
+ "chrome",
+ "jest",
+ "node"
+ ] /* Specify type package names to be included without being referenced in a source file. */,
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
+ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
+ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
+ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
+ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
+ "resolveJsonModule": true /* Enable importing .json files. */,
+ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
+ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
+
+ /* JavaScript Support */
+ "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */,
+ // "checkJs": true /* Enable error reporting in type-checked JavaScript files. */,
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
+
+ /* Emit */
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ "sourceMap": true /* Create source map files for emitted JavaScript files. */,
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
+ "outDir": "./src/extension/build/bundles/" /* Specify an output folder for all emitted files. */,
+ "removeComments": true /* Disable emitting comments. */,
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+
+ /* Interop Constraints */
+ "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */,
+ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
+ "allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */,
+ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
+
+ /* Type Checking */
+ // "strict": true /* Enable all strict type-checking options. */,
+ // "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
+ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
+ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
+ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
+ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ },
+ // Specifies which files should be included in the compilation.
+ "include": ["./src/**/*", "./src/backend", "./src/extension", "./global.d.ts"],
+ "exclude": [
+ "./src/app/__tests__",
+ "./src/backend/__tests__",
+ "node_modules",
+ "./src/extension/build/bundles"
+ ],
+ "typedocOptions": {
+ "entryPoints": ["src"],
+ "entryPointStrategy": "expand",
+ "out": "docs"
+ }
+}
diff --git a/webpack.config.js b/webpack.config.js
index 635c4ea78..b64c21032 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,62 +1,65 @@
+require('dotenv').config();
+/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
-const ChromeExtensionReloader = require('webpack-chrome-extension-reloader');
-const config = {
+/** ChromeExtensionReloader plugin is a tool for hot-reloading code in a Chrome extension during development.
+ * It works by injecting a script into the extension that listens for file changes and automatically reloads the extension when a file is modified.
+ */
+
+// const TypedocWebpackPlugin = require('typedoc-webpack-plugin');
+
+module.exports = {
+ mode: process.env.NODE_ENV || 'production',
+ // use a "multi-main entry" to inject multiple dependent files together
+ // and graph their dependencies into one "chunk"
entry: {
- app: './src/app/index.js',
+ app: './src/app/index.tsx',
background: './src/extension/background.js',
- content: './src/extension/contentScript.js',
+ content: './src/extension/contentScript.ts',
+ backend: './src/backend/index.ts',
+ },
+ watchOptions: {
+ aggregateTimeout: 1000,
+ ignored: /node_modules/,
},
output: {
path: path.resolve(__dirname, 'src/extension/build/bundles'),
filename: '[name].bundle.js',
},
+ devtool: 'cheap-module-source-map',
module: {
rules: [
+ /**
+ * For all files ending in .ts or .tsx, except those in node_modules
+ * => transpile typescript files into javascript file.
+ */
{
- test: /\.jsx?/,
- exclude: /(node_modules)/,
+ test: /\.tsx?$/,
+ use: 'ts-loader',
+ exclude: /node_modules/,
resolve: {
- extensions: ['.js', '.jsx'],
+ extensions: ['.tsx', '.ts', '.js'],
},
- use: {
- loader: 'babel-loader',
- options: {
- presets: [
- '@babel/preset-env',
- '@babel/preset-react',
- {
- plugins: [
- '@babel/plugin-proposal-class-properties',
- ],
- },
- ],
- },
- },
- },
- {
- test: /\.scss$/,
- use: ['style-loader', 'css-loader', 'sass-loader'],
},
+ /**
+ * For all files ending in .scss or .css files
+ * Since sass-loader will only works with .scss & .sass files, for any .css file, webpack will skip sass-loader and use css-loader, then style-loader.
+ */
{
- test: /\.css$/,
- use: ['style-loader', 'css-loader'],
+ test: /\.s?css$/,
+ use: [
+ // Creates `style` nodes from JS strings
+ 'style-loader',
+ // Translates CSS into CommonJS
+ 'css-loader',
+ // Compiles Sass to CSS
+ 'sass-loader',
+ ],
},
],
},
- plugins: [],
-};
-
-module.exports = (env, argv) => {
- if (argv.mode === 'development') {
- config.plugins.push(
- new ChromeExtensionReloader({
- entries: {
- contentScript: ['app', 'content'],
- background: ['background'],
- },
- }),
- );
- }
- return config;
+ // Add `.ts` and `.tsx` as a resolvable extension.
+ resolve: {
+ extensions: ['.ts', '.tsx', '.js', '.jsx'],
+ },
};