diff --git a/README.md b/README.md index a6eae44..15aa001 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,126 @@ -# Voter Canvassing App +# **Canvassers** -Team members: -* ... -* ... \ No newline at end of file +## Visit the app [here](https://leejere.github.io/js-voter-canvassing/site/) +[![face](mockups/saved_iphone13blue_landscape.png)](https://leejere.github.io/js-voter-canvassing/site/) + +by **Jie Li ([@Leejere](https://github.com/Leejere))** and **Myron Bañez ([@myronbanez](https://github.com/myronbanez))** + + +This is a mobile-friendly web-app used for voter canvassing. With this app, the canvasser can select the voters from a list and a map, view their status and other information, and record additional information regarding each canvassed voter. The more specific [**PRD for this product**](https://github.com/Leejere/js-voter-canvassing/blob/dev/PRD.md) can be found in the same repository or via [**this link**](https://github.com/Leejere/js-voter-canvassing/blob/dev/PRD.md). The PRD was produced by Mjumbe Poe ([@mjumbewu](https://github.com/mjumbewu)) + +## **Data source** + +The voter data comes from [Pennsylvania Department of State](https://www.dos.pa.gov/VotingElections/OtherServicesEvents/VotingElectionStatistics/Pages/VotingElectionStatistics.aspx), who provides voter registration data that includes the name, address, and other voting-related information. The data is available for purchase through [this link](https://www.pavoterservices.pa.gov/Pages/PurchasePAFULLVoterExport.aspx). For this application, only data from Philadelphia gets extracted, preprocessed, and geocoded by Mjumbe Poe ([@mjumbewu](https://github.com/mjumbewu)). + +## **App Modules** + +### **Module 0: Database APIs** + +Data from the [Pennsylvania Department of State](https://www.dos.pa.gov/VotingElections/OtherServicesEvents/VotingElectionStatistics/Pages/VotingElectionStatistics.aspx) is stored in the same repo under the directory of `\data\voter-lists\`. Due to its large amount, the data is split into more 1,000 separate lists by neighborhood, which also acts as task lists for the canvassing team. One single list will be loaded at a time on demand in the next module. + +At the same, the requirement document asks for the function of recording and saving additional information. This app uses [**Firebase**](https://firebase.google.com/docs/firestore) to store additional information recorded on canvassing visits. The saving and loading API calls are in the script of `main.js`. + +### **Module 1: `list-loader`** + +Each voter list can get loaded into the app asynchronously on demand via the `list-loader` widget. `list-loader.js` primarily deals with loading data. The loaded data gets stored in a global object, which is the data source for all other operations. The `list-loader` module is rerequisit for all other functions of the app. + +- **Default list on page load.** By default, the same list that the user last used on their device is loaded. When the user last loaded the list, the list number was stored in `localStorage` and also on Firebase. On page load, the app first seeks `localStorage` for the previous list number. If such `localStorage` does not exist, the app makes an API call to Firebase and pulls the list number there. If both do not exist, **the first list (0101)** is loaded by default. + +- **Custom list input widget** An input box exists on the top right of the viewport that allows the user to input a number, and then imports the corresponding list. The list loader is triggered either on **button click** or on **Enter keypress**. + +- **Two API calls to the repo and Firebase.** When the loader is triggered, an API call is made to fetch voter data. On success, another API call is made to Firebase to pull down additional information recorded on previous canvassing visits. The Firebase database is organized by list number, so only data regarding this list is loaded. + +- **Data update and display.** The data pulled from Firebase is used to update the data pulled from the repo. Then, the updated data is passed to **Module 2**. + +Showcase: + +| Number input | Data loaded | +|--|--| +| ![loader-1](mockups/list-loader-before_iphone13blue_portrait.png)| ![loader-2](mockups/list-loader-after_iphone13blue_portrait.png)| + + +### **Module 2: `list-filters`** + +This filter filters voters after a particular voter list is loaded by name/address, registered party, voter status, and visit status. The kernel script for the function resides in `list-filters.js`. The filters work together in the following way: + +1. **Data import.** +2. **Event listeners.** Event listeners get added to all filter widgets. +3. **Filter application.** Every time a filter event is recorded, whether a new filter is applied, or a filter is modified or canceled, the `allFilters` functions takes the imported data and applies **all the filters** to produce to filtered version of the data. +4. **Display update.** Use the filtered data to updated the voter list and map, which gets implemented in the **Module 3**. + +Showcase: + +| Filter window | Data filtered | +|--|--| +|![filter-1](mockups/filter-1_iphone13blue_portrait.png)|![filter-2](mockups/filter-2_iphone13blue_portrait.png)| + + +### **Module 3: Voters display** + +This model displayed either comprehensive or filtered voter data in a particular list in the voter list and on the map. + +- `voter-list.js` takes the data, group them by address, and display them in a list. The list includes the voter's address, full name, and some icons that shows the voters canvassing status (pending visit, visited, awaiting followup, etc.), voter status (active or inactive), party registration, etc. + +- `map.js` takes the data, makes an geometry object (`FeatureCollection`), and display it on the map. Note that **every marker stands for one address** (house number + street name), rather than one voter. + +Each displayed voter either on the map and on the list is attached with an event listener, ready to be selected in **Module 4**. + +### **Module 4: Voter selection** + +This module highlights a **maximum of one voter** as the "selected" voter. The module resides in `selected-voter.js` and contains the following components: + +- A global variable to store the ID of the currently selected voter. If no voter is currently under selection, the variable remains `undefined`. +- Event listeners added on each voter list item and map marker. Note that as each map marker stands for an address rather than a voter, **the first voter by this address** is selected when the marker is clicked on. +- Whenever the list item or marker get clicked, it either + - **highlights a new voter**, + - **updats the highlighted voter**, or + - **unhighlights the currently highlighted voter**. +- The highlighted voter ID is recorded and passed into **Module 5**. + +### **Module 5: Voter information display** + +When a voter is currently highlighted, the most basic basic information regarding this voter is displayed on the bottom panel: name, address, visit status, and other information that was recorded on the previous visit (if any). + +- **Basic information**: name and address. +- **Visit status**: either `pending`, `awaits followup`, or `completed`. This information is pulled from Firebase, and every voter is `pending` by default. +- **Other overview information**, e.g., registered party, whether received mail ballot, etc. Some of this information was recorded on the last visit and pulled from Firebase. + +An Edit button exist on top of the basic-information panel. On click, the filters and voter list is hidden, and more information panels emerge. + +- **Record panel**: displays voter information recorded on the last visit, if any. The user may record, edit, and save information on this panel via **Module 6**. +- **Voting history panel**: displays the past voting records of the selected voter. The list may expand on demand. + +Showcase: + +|Basic panel|Record panel|Voting history| +|-|-|-| +|![display-1](mockups/display-1_iphone13blue_portrait.png)|![display-2](mockups/display-2_iphone13blue_portrait.png)|![display-3](mockups/display-3_iphone13blue_portrait.png)| + +### **Module 6: Data editing and saving** + +The user may edit information via the "Update Status" button on the basic-information panel and the "Save" button on the record panel. + +- The "Update Status" button only saves visit statuses, whereas the "Save" button saves all information. +- Whenever the user clicks a button or inputs some text, the unsaved edits will be temporarily recorded in the corresponding DOM object. When the save buttons are clicked, the unsaved chagnes get consolidated and uploaded to Firebase. +- A "toast" appears on the button to give feedback to the user that the information has been saved. + +Showcase: + +| Update Status | Final Save | +|-|-| +|![save-1](mockups/status-saved_iphone13blue_portrait.png)|![save-2](mockups/saved_iphone13blue_portrait.png)| + +## Collaboration + +|Item|Contributor| +|--|--| +|Wireframing| Myron & Jie| +|Module 0: Firebase| Jie | +|Module 1: List loader| Jie| +|Module 1: Parsing CSVs| Myron| +|Module 2: List filters | Jie & Myron| +|Module 3: Voters display | Jie & Myron| +|Module 4: Voter selection| Jie| +|Module 5: Voter information display| Jie| +|Module 6: Data editing and saving| Jie| +|CSS|Jie & Myron| diff --git a/mockups/display-1_iphone13blue_portrait.png b/mockups/display-1_iphone13blue_portrait.png new file mode 100644 index 0000000..f0e8962 Binary files /dev/null and b/mockups/display-1_iphone13blue_portrait.png differ diff --git a/mockups/display-2_iphone13blue_portrait.png b/mockups/display-2_iphone13blue_portrait.png new file mode 100644 index 0000000..fd29805 Binary files /dev/null and b/mockups/display-2_iphone13blue_portrait.png differ diff --git a/mockups/display-3_iphone13blue_portrait.png b/mockups/display-3_iphone13blue_portrait.png new file mode 100644 index 0000000..878a5b1 Binary files /dev/null and b/mockups/display-3_iphone13blue_portrait.png differ diff --git a/mockups/filter-1_iphone13blue_portrait.png b/mockups/filter-1_iphone13blue_portrait.png new file mode 100644 index 0000000..168b4a2 Binary files /dev/null and b/mockups/filter-1_iphone13blue_portrait.png differ diff --git a/mockups/filter-2_iphone13blue_portrait.png b/mockups/filter-2_iphone13blue_portrait.png new file mode 100644 index 0000000..51dac1f Binary files /dev/null and b/mockups/filter-2_iphone13blue_portrait.png differ diff --git a/mockups/list-loader-after_iphone13blue_portrait.png b/mockups/list-loader-after_iphone13blue_portrait.png new file mode 100644 index 0000000..ca81846 Binary files /dev/null and b/mockups/list-loader-after_iphone13blue_portrait.png differ diff --git a/mockups/list-loader-before_iphone13blue_portrait.png b/mockups/list-loader-before_iphone13blue_portrait.png new file mode 100644 index 0000000..4742cf2 Binary files /dev/null and b/mockups/list-loader-before_iphone13blue_portrait.png differ diff --git a/mockups/raw/display-1.png b/mockups/raw/display-1.png new file mode 100644 index 0000000..d4d243b Binary files /dev/null and b/mockups/raw/display-1.png differ diff --git a/mockups/raw/display-2.png b/mockups/raw/display-2.png new file mode 100644 index 0000000..1acaf37 Binary files /dev/null and b/mockups/raw/display-2.png differ diff --git a/mockups/raw/display-3.png b/mockups/raw/display-3.png new file mode 100644 index 0000000..053d0d1 Binary files /dev/null and b/mockups/raw/display-3.png differ diff --git a/mockups/raw/filter-1.png b/mockups/raw/filter-1.png new file mode 100644 index 0000000..646513a Binary files /dev/null and b/mockups/raw/filter-1.png differ diff --git a/mockups/raw/filter-2.png b/mockups/raw/filter-2.png new file mode 100644 index 0000000..eba1eb9 Binary files /dev/null and b/mockups/raw/filter-2.png differ diff --git a/mockups/raw/list-loader-after.png b/mockups/raw/list-loader-after.png new file mode 100644 index 0000000..7aaa48d Binary files /dev/null and b/mockups/raw/list-loader-after.png differ diff --git a/mockups/raw/list-loader-before.png b/mockups/raw/list-loader-before.png new file mode 100644 index 0000000..26ad3b2 Binary files /dev/null and b/mockups/raw/list-loader-before.png differ diff --git a/mockups/raw/saved.png b/mockups/raw/saved.png new file mode 100644 index 0000000..423cbf6 Binary files /dev/null and b/mockups/raw/saved.png differ diff --git a/mockups/raw/status-saved.png b/mockups/raw/status-saved.png new file mode 100644 index 0000000..0e2df0e Binary files /dev/null and b/mockups/raw/status-saved.png differ diff --git a/mockups/saved_iphone13blue_landscape.png b/mockups/saved_iphone13blue_landscape.png new file mode 100644 index 0000000..e7e3172 Binary files /dev/null and b/mockups/saved_iphone13blue_landscape.png differ diff --git a/mockups/saved_iphone13blue_portrait.png b/mockups/saved_iphone13blue_portrait.png new file mode 100644 index 0000000..387bad7 Binary files /dev/null and b/mockups/saved_iphone13blue_portrait.png differ diff --git a/mockups/status-saved_iphone13blue_portrait.png b/mockups/status-saved_iphone13blue_portrait.png new file mode 100644 index 0000000..55cd41d Binary files /dev/null and b/mockups/status-saved_iphone13blue_portrait.png differ diff --git a/package-lock.json b/package-lock.json index 02ef655..51d8419 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,10 @@ "name": "school-explorer", "version": "1.0.0", "license": "ISC", + "dependencies": { + "firebase": "^9.14.0", + "papaparse": "^5.3.2" + }, "devDependencies": { "eslint": "^8.22.0", "http-server": "^14.1.1", @@ -644,6 +648,620 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@firebase/analytics": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.8.4.tgz", + "integrity": "sha512-Bgr2tMexv0YrL6kjrOF1xVRts8PM6WWmROpfRQjh0xFU4QSoofBJhkVn2NXDXkHWrr5slFfqB5yOnmgAIsHiMw==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/installations": "0.5.16", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.1.17.tgz", + "integrity": "sha512-36ByEDsH6/3YNuD6yig30s2A/+E1pt333r8SJirUE8+aHYl/DGX0PXplKvJWDGamYYjMwet3Kt4XRrB1NY8mLg==", + "dependencies": { + "@firebase/analytics": "0.8.4", + "@firebase/analytics-types": "0.7.1", + "@firebase/component": "0.5.21", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.7.1.tgz", + "integrity": "sha512-a1INLjelc1Mqrt2CbGmGdlNBj0zsvwBv0K5q5C6Fje8GSXBMc3+iQQQjzYe/4KkK6nL54UP7ZMeI/Q3VEW72FA==" + }, + "node_modules/@firebase/app": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.8.4.tgz", + "integrity": "sha512-gQntijd+sLaGWjcBQpk33giCEXNzGLB6489NMpypVgEXJwQXYQPSrtb9vUHXot1w1iy/j6xlNl4K8wwwNdRgDg==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.5.17.tgz", + "integrity": "sha512-P4bm0lbs+VgS7pns322GC0hyKuTDCqYk2X4FGBf133LZaw1NXJpzOteqPdCT0hBCaR0QSHk49gxx+bdnSdd5Fg==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.2.17.tgz", + "integrity": "sha512-yhiAy6U4MuhbY+DCgvG5FcrXkAL+7YohRzqywycQKr31k/ftelbR5l9Zmo2WJMxdLxfubnnqeG/BYCRHlSvk7A==", + "dependencies": { + "@firebase/app-check": "0.5.17", + "@firebase/app-check-types": "0.4.1", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.1.1.tgz", + "integrity": "sha512-QpYh5GmiLA9ob8NWAZpHbNNl9TzxxZI4NLevT6MYPRDXKG9BSmBI7FATRfm5uv2QQUVSQrESKog5CCmU16v+7Q==" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.4.1.tgz", + "integrity": "sha512-4X79w2X0H5i5qvaho3qkjZg5qdERnKR4gCfy/fxDmdMMP4QgNJHJ9IBk1E+c4cm5HlaZVcLq9K6z8xaRqjZhyw==" + }, + "node_modules/@firebase/app-compat": { + "version": "0.1.39", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.39.tgz", + "integrity": "sha512-F5O/N38dVGFzpe6zM//MslYT80rpX0V+MQNMvONPUlXhvDqS5T+8NMSCWOcZ++Z4Hkj8EvgTJk59AMnD8SdyFw==", + "dependencies": { + "@firebase/app": "0.8.4", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", + "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==" + }, + "node_modules/@firebase/auth": { + "version": "0.20.11", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.20.11.tgz", + "integrity": "sha512-cKy91l4URDG3yWfPK7tjUySh2wCLxtTilsR59jiqQJLReBrQsKP79eFDJ6jqWwbEh3+f1lmoH1nKswwbo9XdmA==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "node-fetch": "2.6.7", + "selenium-webdriver": "4.5.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.2.24", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.2.24.tgz", + "integrity": "sha512-IuZQScjtoOLkUHtmIUJ2F3E2OpDOyap6L/9HL/DX3nzEA1LrX7wlpeU6OF2jS9E0KLueWKIrSkIQOOsKoQj/sA==", + "dependencies": { + "@firebase/auth": "0.20.11", + "@firebase/auth-types": "0.11.1", + "@firebase/component": "0.5.21", + "@firebase/util": "1.7.3", + "node-fetch": "2.6.7", + "selenium-webdriver": "4.5.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", + "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/auth-types": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.11.1.tgz", + "integrity": "sha512-ud7T39VG9ptTrC2fOy/XlU+ubC+BVuBJPteuzsPZSa9l7gkntvWgVb3Z/3FxqqRPlkVUYiyvmsbRN3DE1He2ow==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", + "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", + "dependencies": { + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", + "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", + "dependencies": { + "@firebase/auth-interop-types": "0.1.7", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", + "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/database": "0.13.10", + "@firebase/database-types": "0.9.17", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", + "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", + "dependencies": { + "@firebase/app-types": "0.8.1", + "@firebase/util": "1.7.3" + } + }, + "node_modules/@firebase/firestore": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-3.7.3.tgz", + "integrity": "sha512-hnA8hljwJBpejv0SPlt0yiej1wz3VRcLzoNAZujTCI1wLoADkRNsqic5uN/Ge0M0vbmHliLXtet/PDqvEbB9Ww==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "@firebase/webchannel-wrapper": "0.8.1", + "@grpc/grpc-js": "^1.3.2", + "@grpc/proto-loader": "^0.6.13", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10.10.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.2.3.tgz", + "integrity": "sha512-FgJwGCA2K+lsGk6gbJo57qn4iocQSGfOlNi2s4QsEO/WOVIU00yYGm408fN7iAGpr9d5VKyulO4sYcic7cS51g==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/firestore": "3.7.3", + "@firebase/firestore-types": "2.5.1", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.5.1.tgz", + "integrity": "sha512-xG0CA6EMfYo8YeUxC8FeDzf6W3FX1cLlcAGBYV6Cku12sZRI81oWcu61RSKM66K6kUENP+78Qm8mvroBcm1whw==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.8.8.tgz", + "integrity": "sha512-weNcDQJcH3/2YFaXd5dF5pUk3IQdZY60QNuWpq7yS+uaPlCRHjT0K989Q3ZcmYwXz7mHTfhlQamXdA4Yobgt+Q==", + "dependencies": { + "@firebase/app-check-interop-types": "0.1.1", + "@firebase/auth-interop-types": "0.1.7", + "@firebase/component": "0.5.21", + "@firebase/messaging-interop-types": "0.1.1", + "@firebase/util": "1.7.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.2.8.tgz", + "integrity": "sha512-5w668whT+bm6oVcFqIxfFbn9N77WycpNCfZNg1l0iC+5RLSt53RTVu43pqi43vh23Vp4ad+SRBgZiQGAMen5wA==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/functions": "0.8.8", + "@firebase/functions-types": "0.5.1", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.5.1.tgz", + "integrity": "sha512-olEJnTuULM/ws0pwhHA0Ze5oIdpFbZsdBGCaBhyL4pm1NUR4Moh0cyAsqr+VtqHCNMGquHU1GJ77qITkoonp0w==" + }, + "node_modules/@firebase/installations": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.5.16.tgz", + "integrity": "sha512-k3iyjr+yZnDOcJbP+CCZW3/zQJf9gYL2CNBJs9QbmFJoLz7cgIcnAT/XNDMudxcggF1goLfq4+MygpzHD0NzLA==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/util": "1.7.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.1.16.tgz", + "integrity": "sha512-Xp7s3iUMZ6/TN0a+g1kpHNEn7h59kSxi44/2I7bd3X6xwHnxMu0TqYB7U9WfqEhqiI9iKulL3g06wIZqaklElw==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/installations": "0.5.16", + "@firebase/installations-types": "0.4.1", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.4.1.tgz", + "integrity": "sha512-ac906QcmipomZjSasGDYNS1LDy4JNGzQ4VXHpFtoOrI6U2QGFkRezZpI+5bzfU062JOD+doO6irYC6Uwnv/GnA==", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/logger": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", + "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.11.0.tgz", + "integrity": "sha512-V7+Xw4QlB8PgINY7Wml+Uj8A3S2nR0ooVoaqfRJ8ZN3W7A4aO/DCkjPsf6DXehwfqRLA7PGB9Boe8l9Idy7icA==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/installations": "0.5.16", + "@firebase/messaging-interop-types": "0.1.1", + "@firebase/util": "1.7.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.1.21.tgz", + "integrity": "sha512-oxQCQ8EXqpSaTybryokbEM/LAqkG0L7OJuucllCg5roqRGIHE437Abus0Bn67P8TKJaYjyKxomg8wCvfmInjlg==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/messaging": "0.11.0", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.1.1.tgz", + "integrity": "sha512-7XuY87zPh01EBaeS3s6co31Il5oGbPl5MxAg6Uj3fPv7PqJQlbwQ+B5k7CKSF/Y26tRxp+u+usxIvIWCSEA8CQ==" + }, + "node_modules/@firebase/performance": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.5.17.tgz", + "integrity": "sha512-NDgzI5JYo6Itnj1FWhMkK3LtwKhtOnhC+WBkxezjzFVuCOornQjvu7ucAU1o2dHXh7MFruhHGFPsHyfkkMCljA==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/installations": "0.5.16", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.1.17.tgz", + "integrity": "sha512-Hci5MrDlRuqwVozq7LaSAufXXElz+AtmEQArix64kLRJqHhOu5K/8TpuZXM/klR6gnLyIrk+01CrAemH3zHpDw==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/performance": "0.5.17", + "@firebase/performance-types": "0.1.1", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.1.1.tgz", + "integrity": "sha512-wiJRLBg8EPaYSGJqx7aqkZ3L5fULfZa9zOTs4C06K020g0zzJh9kUUO/0U3wvHz7zRQjJxTO8Jw4SDjxs3EZrA==" + }, + "node_modules/@firebase/remote-config": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.3.15.tgz", + "integrity": "sha512-ZCyqoCaftoNvc2r4zPaqNV4OgC4sRHjcQI+agzXESnhDLnTY8DpCaQ0m9j6deHuxxDOgu8QPDb8psLbjR+9CgQ==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/installations": "0.5.16", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.1.16.tgz", + "integrity": "sha512-BWonzeqODnGki/fZ17zOnjJFR5CWbIOU0PmYGjWBnbkWxpFDdE3zNsz8JTVd/Mkt7y2PHFMYpLsyZ473E/62FQ==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/remote-config": "0.3.15", + "@firebase/remote-config-types": "0.2.1", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.2.1.tgz", + "integrity": "sha512-1PGx4vKtMMd5uB6G1Nj2b8fOnJx7mIJGzkdyfhIM1oQx9k3dJ+pVu4StrNm46vHaD8ZlOQLr91YfUE43xSXwSg==" + }, + "node_modules/@firebase/storage": { + "version": "0.9.14", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.9.14.tgz", + "integrity": "sha512-he8VAJ4BLkQdebnna15TI1/ymkwQTeKnjA/psKMAJ2+/UswD/68bCMKOlTrMvw6Flv3zc5YZk1xdL9DHR0i6wg==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/util": "1.7.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.1.22.tgz", + "integrity": "sha512-uv33WnAEcxf2983Z03uhJmKc91LKSsRijFwut8xeoJamJoGAVj1Tc9Mio491aI1KZ+RMkNFghHL2FpxjuvxpPg==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/storage": "0.9.14", + "@firebase/storage-types": "0.6.1", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.6.1.tgz", + "integrity": "sha512-/pkNzKiGCSjdBBZHPvWL1kkPZfM3pFJ38HPJE1xTHwLBwdrFb4JrmY+5/E4ma5ePsbejecIOD1SZhEKDB/JwUQ==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", + "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.8.1.tgz", + "integrity": "sha512-CJW8vxt6bJaBeco2VnlJjmCmAkrrtIdf0GGKvpAB4J5gw8Gi0rHb+qsgKp6LsyS5W6ALPLawLs7phZmw02dvLw==" + }, + "node_modules/@grpc/grpc-js": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.3.tgz", + "integrity": "sha512-5dAvoZwna2Py3Ef96Ux9jIkp3iZ62TUsV00p3wVBPNX5K178UbNi8Q7gQVqwXT1Yq9RejIGG9G2IPEo93T6RcA==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/grpc-js/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + }, + "node_modules/@grpc/grpc-js/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -1160,6 +1778,60 @@ "node": ">= 8" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -1279,6 +1951,11 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "node_modules/@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -1288,8 +1965,7 @@ "node_modules/@types/node": { "version": "18.11.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.2.tgz", - "integrity": "sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw==", - "dev": true + "integrity": "sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -1422,7 +2098,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1431,7 +2106,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1609,8 +2283,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -1661,7 +2334,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1915,7 +2587,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1926,8 +2597,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/colord": { "version": "2.9.3", @@ -1947,8 +2617,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/convert-source-map": { "version": "1.9.0", @@ -1956,6 +2625,11 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/corser": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", @@ -2178,8 +2852,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/end-of-stream": { "version": "1.4.4", @@ -2204,7 +2877,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -2570,6 +3242,17 @@ "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -2668,6 +3351,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.14.0.tgz", + "integrity": "sha512-wePrsf7W33mhT7RVXQavragoAgXb/NDm22vuhwJXkprrQ2Y9alrEKC5LTAtLJL3P2dHdDmeylS6PLZwWPEE79A==", + "dependencies": { + "@firebase/analytics": "0.8.4", + "@firebase/analytics-compat": "0.1.17", + "@firebase/app": "0.8.4", + "@firebase/app-check": "0.5.17", + "@firebase/app-check-compat": "0.2.17", + "@firebase/app-compat": "0.1.39", + "@firebase/app-types": "0.8.1", + "@firebase/auth": "0.20.11", + "@firebase/auth-compat": "0.2.24", + "@firebase/database": "0.13.10", + "@firebase/database-compat": "0.2.10", + "@firebase/firestore": "3.7.3", + "@firebase/firestore-compat": "0.2.3", + "@firebase/functions": "0.8.8", + "@firebase/functions-compat": "0.2.8", + "@firebase/installations": "0.5.16", + "@firebase/installations-compat": "0.1.16", + "@firebase/messaging": "0.11.0", + "@firebase/messaging-compat": "0.1.21", + "@firebase/performance": "0.5.17", + "@firebase/performance-compat": "0.1.17", + "@firebase/remote-config": "0.3.15", + "@firebase/remote-config-compat": "0.1.16", + "@firebase/storage": "0.9.14", + "@firebase/storage-compat": "0.1.22", + "@firebase/util": "1.7.3" + } + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -2747,8 +3463,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.2", @@ -2783,7 +3498,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -2827,7 +3541,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3053,6 +3766,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, "node_modules/http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", @@ -3129,6 +3847,11 @@ "node": ">=0.10.0" } }, + "node_modules/idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -3159,6 +3882,11 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -3225,7 +3953,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3234,8 +3961,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "1.3.8", @@ -3289,7 +4015,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -3366,6 +4091,11 @@ "node": ">=0.10.0" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4305,6 +5035,39 @@ "node": ">=6" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "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" + } + }, + "node_modules/jszip/node_modules/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==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -4363,6 +5126,14 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4390,6 +5161,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4402,6 +5178,11 @@ "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -4574,7 +5355,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4683,8 +5463,6 @@ "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -4776,7 +5554,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -4870,6 +5647,16 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/papaparse": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.2.tgz", + "integrity": "sha512-6dNZu0Ki+gyV0eBsFKJhYr+MdQYAzFUGlBMNj3GNrmHxmz1lfRa24CjFObPXtjcetlOv5Ad299MhIK0znp3afw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4922,7 +5709,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5178,6 +5964,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/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==" + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -5201,6 +5992,31 @@ "node": ">= 6" } }, + "node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -5490,7 +6306,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5593,7 +6408,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -5639,8 +6453,7 @@ "node_modules/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==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -5654,6 +6467,19 @@ "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", "dev": true }, + "node_modules/selenium-webdriver": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.5.0.tgz", + "integrity": "sha512-9mSFii+lRwcnT2KUAB1kqvx6+mMiiQHH60Y0VUtr3kxxi3oZ3CV3B8e2nuJ7T4SPb+Q6VA0swswe7rYpez07Bg==", + "dependencies": { + "jszip": "^3.10.0", + "tmp": "^0.2.1", + "ws": ">=8.7.0" + }, + "engines": { + "node": ">= 14.20.0" + } + }, "node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -5663,6 +6489,11 @@ "semver": "bin/semver.js" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "node_modules/shallow-clone": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", @@ -5918,7 +6749,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5932,7 +6762,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -6274,6 +7103,17 @@ "dev": true, "peer": true }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -6304,9 +7144,7 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "peer": true + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/tree-kill": { "version": "1.2.2", @@ -6329,8 +7167,7 @@ "node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "node_modules/type-check": { "version": "0.4.0", @@ -6432,8 +7269,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/v8-compile-cache": { "version": "2.3.0", @@ -6496,9 +7332,28 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "peer": true + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } }, "node_modules/whatwg-encoding": { "version": "2.0.0", @@ -6516,8 +7371,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -6551,7 +7404,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6567,8 +7419,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", @@ -6587,8 +7438,6 @@ "version": "8.9.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", - "dev": true, - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -6609,7 +7458,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -6651,7 +7499,6 @@ "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, "engines": { "node": ">=10" } @@ -7033,127 +7880,645 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", + "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + } + }, + "@babel/traverse": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.4.tgz", + "integrity": "sha512-w3K1i+V5u2aJUOXBFFC5pveFLmtq1s3qcdDNC2qRI6WPBQIDaKFqXxDEqDO/h1dQ3HjsZoZMyIy6jGLq0xtw+g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.4", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.19.4", + "@babel/types": "^7.19.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@csstools/selector-specificity": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", + "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "dev": true, + "requires": {} + }, + "@eslint/eslintrc": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", + "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@firebase/analytics": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.8.4.tgz", + "integrity": "sha512-Bgr2tMexv0YrL6kjrOF1xVRts8PM6WWmROpfRQjh0xFU4QSoofBJhkVn2NXDXkHWrr5slFfqB5yOnmgAIsHiMw==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/installations": "0.5.16", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "@firebase/analytics-compat": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.1.17.tgz", + "integrity": "sha512-36ByEDsH6/3YNuD6yig30s2A/+E1pt333r8SJirUE8+aHYl/DGX0PXplKvJWDGamYYjMwet3Kt4XRrB1NY8mLg==", + "requires": { + "@firebase/analytics": "0.8.4", + "@firebase/analytics-types": "0.7.1", + "@firebase/component": "0.5.21", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "@firebase/analytics-types": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.7.1.tgz", + "integrity": "sha512-a1INLjelc1Mqrt2CbGmGdlNBj0zsvwBv0K5q5C6Fje8GSXBMc3+iQQQjzYe/4KkK6nL54UP7ZMeI/Q3VEW72FA==" + }, + "@firebase/app": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.8.4.tgz", + "integrity": "sha512-gQntijd+sLaGWjcBQpk33giCEXNzGLB6489NMpypVgEXJwQXYQPSrtb9vUHXot1w1iy/j6xlNl4K8wwwNdRgDg==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + } + }, + "@firebase/app-check": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.5.17.tgz", + "integrity": "sha512-P4bm0lbs+VgS7pns322GC0hyKuTDCqYk2X4FGBf133LZaw1NXJpzOteqPdCT0hBCaR0QSHk49gxx+bdnSdd5Fg==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "@firebase/app-check-compat": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.2.17.tgz", + "integrity": "sha512-yhiAy6U4MuhbY+DCgvG5FcrXkAL+7YohRzqywycQKr31k/ftelbR5l9Zmo2WJMxdLxfubnnqeG/BYCRHlSvk7A==", + "requires": { + "@firebase/app-check": "0.5.17", + "@firebase/app-check-types": "0.4.1", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "@firebase/app-check-interop-types": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.1.1.tgz", + "integrity": "sha512-QpYh5GmiLA9ob8NWAZpHbNNl9TzxxZI4NLevT6MYPRDXKG9BSmBI7FATRfm5uv2QQUVSQrESKog5CCmU16v+7Q==" + }, + "@firebase/app-check-types": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.4.1.tgz", + "integrity": "sha512-4X79w2X0H5i5qvaho3qkjZg5qdERnKR4gCfy/fxDmdMMP4QgNJHJ9IBk1E+c4cm5HlaZVcLq9K6z8xaRqjZhyw==" + }, + "@firebase/app-compat": { + "version": "0.1.39", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.39.tgz", + "integrity": "sha512-F5O/N38dVGFzpe6zM//MslYT80rpX0V+MQNMvONPUlXhvDqS5T+8NMSCWOcZ++Z4Hkj8EvgTJk59AMnD8SdyFw==", + "requires": { + "@firebase/app": "0.8.4", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "@firebase/app-types": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", + "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==" + }, + "@firebase/auth": { + "version": "0.20.11", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.20.11.tgz", + "integrity": "sha512-cKy91l4URDG3yWfPK7tjUySh2wCLxtTilsR59jiqQJLReBrQsKP79eFDJ6jqWwbEh3+f1lmoH1nKswwbo9XdmA==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "node-fetch": "2.6.7", + "selenium-webdriver": "4.5.0", + "tslib": "^2.1.0" + } + }, + "@firebase/auth-compat": { + "version": "0.2.24", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.2.24.tgz", + "integrity": "sha512-IuZQScjtoOLkUHtmIUJ2F3E2OpDOyap6L/9HL/DX3nzEA1LrX7wlpeU6OF2jS9E0KLueWKIrSkIQOOsKoQj/sA==", + "requires": { + "@firebase/auth": "0.20.11", + "@firebase/auth-types": "0.11.1", + "@firebase/component": "0.5.21", + "@firebase/util": "1.7.3", + "node-fetch": "2.6.7", + "selenium-webdriver": "4.5.0", + "tslib": "^2.1.0" + } + }, + "@firebase/auth-interop-types": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", + "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", + "requires": {} + }, + "@firebase/auth-types": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.11.1.tgz", + "integrity": "sha512-ud7T39VG9ptTrC2fOy/XlU+ubC+BVuBJPteuzsPZSa9l7gkntvWgVb3Z/3FxqqRPlkVUYiyvmsbRN3DE1He2ow==", + "requires": {} + }, + "@firebase/component": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", + "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", + "requires": { + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "@firebase/database": { + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", + "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", + "requires": { + "@firebase/auth-interop-types": "0.1.7", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "@firebase/database-compat": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", + "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/database": "0.13.10", + "@firebase/database-types": "0.9.17", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "@firebase/database-types": { + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", + "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", + "requires": { + "@firebase/app-types": "0.8.1", + "@firebase/util": "1.7.3" + } + }, + "@firebase/firestore": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-3.7.3.tgz", + "integrity": "sha512-hnA8hljwJBpejv0SPlt0yiej1wz3VRcLzoNAZujTCI1wLoADkRNsqic5uN/Ge0M0vbmHliLXtet/PDqvEbB9Ww==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "@firebase/webchannel-wrapper": "0.8.1", + "@grpc/grpc-js": "^1.3.2", + "@grpc/proto-loader": "^0.6.13", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + } + }, + "@firebase/firestore-compat": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.2.3.tgz", + "integrity": "sha512-FgJwGCA2K+lsGk6gbJo57qn4iocQSGfOlNi2s4QsEO/WOVIU00yYGm408fN7iAGpr9d5VKyulO4sYcic7cS51g==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/firestore": "3.7.3", + "@firebase/firestore-types": "2.5.1", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "@firebase/firestore-types": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.5.1.tgz", + "integrity": "sha512-xG0CA6EMfYo8YeUxC8FeDzf6W3FX1cLlcAGBYV6Cku12sZRI81oWcu61RSKM66K6kUENP+78Qm8mvroBcm1whw==", + "requires": {} + }, + "@firebase/functions": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.8.8.tgz", + "integrity": "sha512-weNcDQJcH3/2YFaXd5dF5pUk3IQdZY60QNuWpq7yS+uaPlCRHjT0K989Q3ZcmYwXz7mHTfhlQamXdA4Yobgt+Q==", + "requires": { + "@firebase/app-check-interop-types": "0.1.1", + "@firebase/auth-interop-types": "0.1.7", + "@firebase/component": "0.5.21", + "@firebase/messaging-interop-types": "0.1.1", + "@firebase/util": "1.7.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + } + }, + "@firebase/functions-compat": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.2.8.tgz", + "integrity": "sha512-5w668whT+bm6oVcFqIxfFbn9N77WycpNCfZNg1l0iC+5RLSt53RTVu43pqi43vh23Vp4ad+SRBgZiQGAMen5wA==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/functions": "0.8.8", + "@firebase/functions-types": "0.5.1", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "@firebase/functions-types": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.5.1.tgz", + "integrity": "sha512-olEJnTuULM/ws0pwhHA0Ze5oIdpFbZsdBGCaBhyL4pm1NUR4Moh0cyAsqr+VtqHCNMGquHU1GJ77qITkoonp0w==" + }, + "@firebase/installations": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.5.16.tgz", + "integrity": "sha512-k3iyjr+yZnDOcJbP+CCZW3/zQJf9gYL2CNBJs9QbmFJoLz7cgIcnAT/XNDMudxcggF1goLfq4+MygpzHD0NzLA==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/util": "1.7.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + } + }, + "@firebase/installations-compat": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.1.16.tgz", + "integrity": "sha512-Xp7s3iUMZ6/TN0a+g1kpHNEn7h59kSxi44/2I7bd3X6xwHnxMu0TqYB7U9WfqEhqiI9iKulL3g06wIZqaklElw==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/installations": "0.5.16", + "@firebase/installations-types": "0.4.1", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "@firebase/installations-types": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.4.1.tgz", + "integrity": "sha512-ac906QcmipomZjSasGDYNS1LDy4JNGzQ4VXHpFtoOrI6U2QGFkRezZpI+5bzfU062JOD+doO6irYC6Uwnv/GnA==", + "requires": {} + }, + "@firebase/logger": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", + "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@firebase/messaging": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.11.0.tgz", + "integrity": "sha512-V7+Xw4QlB8PgINY7Wml+Uj8A3S2nR0ooVoaqfRJ8ZN3W7A4aO/DCkjPsf6DXehwfqRLA7PGB9Boe8l9Idy7icA==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/installations": "0.5.16", + "@firebase/messaging-interop-types": "0.1.1", + "@firebase/util": "1.7.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + } + }, + "@firebase/messaging-compat": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.1.21.tgz", + "integrity": "sha512-oxQCQ8EXqpSaTybryokbEM/LAqkG0L7OJuucllCg5roqRGIHE437Abus0Bn67P8TKJaYjyKxomg8wCvfmInjlg==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/messaging": "0.11.0", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "@firebase/messaging-interop-types": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.1.1.tgz", + "integrity": "sha512-7XuY87zPh01EBaeS3s6co31Il5oGbPl5MxAg6Uj3fPv7PqJQlbwQ+B5k7CKSF/Y26tRxp+u+usxIvIWCSEA8CQ==" + }, + "@firebase/performance": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.5.17.tgz", + "integrity": "sha512-NDgzI5JYo6Itnj1FWhMkK3LtwKhtOnhC+WBkxezjzFVuCOornQjvu7ucAU1o2dHXh7MFruhHGFPsHyfkkMCljA==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/installations": "0.5.16", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "@firebase/performance-compat": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.1.17.tgz", + "integrity": "sha512-Hci5MrDlRuqwVozq7LaSAufXXElz+AtmEQArix64kLRJqHhOu5K/8TpuZXM/klR6gnLyIrk+01CrAemH3zHpDw==", "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/performance": "0.5.17", + "@firebase/performance-types": "0.1.1", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" } }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@firebase/performance-types": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.1.1.tgz", + "integrity": "sha512-wiJRLBg8EPaYSGJqx7aqkZ3L5fULfZa9zOTs4C06K020g0zzJh9kUUO/0U3wvHz7zRQjJxTO8Jw4SDjxs3EZrA==" + }, + "@firebase/remote-config": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.3.15.tgz", + "integrity": "sha512-ZCyqoCaftoNvc2r4zPaqNV4OgC4sRHjcQI+agzXESnhDLnTY8DpCaQ0m9j6deHuxxDOgu8QPDb8psLbjR+9CgQ==", + "requires": { + "@firebase/component": "0.5.21", + "@firebase/installations": "0.5.16", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" } }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, + "@firebase/remote-config-compat": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.1.16.tgz", + "integrity": "sha512-BWonzeqODnGki/fZ17zOnjJFR5CWbIOU0PmYGjWBnbkWxpFDdE3zNsz8JTVd/Mkt7y2PHFMYpLsyZ473E/62FQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/remote-config": "0.3.15", + "@firebase/remote-config-types": "0.2.1", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" } }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, + "@firebase/remote-config-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.2.1.tgz", + "integrity": "sha512-1PGx4vKtMMd5uB6G1Nj2b8fOnJx7mIJGzkdyfhIM1oQx9k3dJ+pVu4StrNm46vHaD8ZlOQLr91YfUE43xSXwSg==" + }, + "@firebase/storage": { + "version": "0.9.14", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.9.14.tgz", + "integrity": "sha512-he8VAJ4BLkQdebnna15TI1/ymkwQTeKnjA/psKMAJ2+/UswD/68bCMKOlTrMvw6Flv3zc5YZk1xdL9DHR0i6wg==", "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@firebase/component": "0.5.21", + "@firebase/util": "1.7.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" } }, - "@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", - "dev": true, + "@firebase/storage-compat": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.1.22.tgz", + "integrity": "sha512-uv33WnAEcxf2983Z03uhJmKc91LKSsRijFwut8xeoJamJoGAVj1Tc9Mio491aI1KZ+RMkNFghHL2FpxjuvxpPg==", "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@firebase/component": "0.5.21", + "@firebase/storage": "0.9.14", + "@firebase/storage-types": "0.6.1", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" } }, - "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "dev": true, + "@firebase/storage-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.6.1.tgz", + "integrity": "sha512-/pkNzKiGCSjdBBZHPvWL1kkPZfM3pFJ38HPJE1xTHwLBwdrFb4JrmY+5/E4ma5ePsbejecIOD1SZhEKDB/JwUQ==", + "requires": {} + }, + "@firebase/util": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", + "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "tslib": "^2.1.0" } }, - "@babel/traverse": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.4.tgz", - "integrity": "sha512-w3K1i+V5u2aJUOXBFFC5pveFLmtq1s3qcdDNC2qRI6WPBQIDaKFqXxDEqDO/h1dQ3HjsZoZMyIy6jGLq0xtw+g==", - "dev": true, + "@firebase/webchannel-wrapper": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.8.1.tgz", + "integrity": "sha512-CJW8vxt6bJaBeco2VnlJjmCmAkrrtIdf0GGKvpAB4J5gw8Gi0rHb+qsgKp6LsyS5W6ALPLawLs7phZmw02dvLw==" + }, + "@grpc/grpc-js": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.4", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.4", - "@babel/types": "^7.19.4", - "debug": "^4.1.0", - "globals": "^11.1.0" + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" }, "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "@grpc/proto-loader": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.3.tgz", + "integrity": "sha512-5dAvoZwna2Py3Ef96Ux9jIkp3iZ62TUsV00p3wVBPNX5K178UbNi8Q7gQVqwXT1Yq9RejIGG9G2IPEo93T6RcA==", + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "dependencies": { + "long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + } + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } } } }, - "@babel/types": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", - "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@csstools/selector-specificity": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", - "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", - "dev": true, - "requires": {} - }, - "@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", - "dev": true, + "@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + }, + "dependencies": { + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + } } }, "@hapi/hoek": { @@ -7561,6 +8926,60 @@ "fastq": "^1.6.0" } }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -7680,6 +9099,11 @@ "@types/istanbul-lib-report": "*" } }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -7689,8 +9113,7 @@ "@types/node": { "version": "18.11.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.2.tgz", - "integrity": "sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw==", - "dev": true + "integrity": "sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw==" }, "@types/normalize-package-data": { "version": "2.4.1", @@ -7797,14 +9220,12 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -7940,8 +9361,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { "version": "1.5.1", @@ -7975,7 +9395,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8149,7 +9568,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -8157,8 +9575,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "colord": { "version": "2.9.3", @@ -8175,8 +9592,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "convert-source-map": { "version": "1.9.0", @@ -8184,6 +9600,11 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "corser": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", @@ -8349,8 +9770,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "end-of-stream": { "version": "1.4.4", @@ -8374,8 +9794,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-string-regexp": { "version": "4.0.0", @@ -8648,6 +10067,14 @@ "reusify": "^1.0.4" } }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, "fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -8725,6 +10152,39 @@ "path-exists": "^4.0.0" } }, + "firebase": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.14.0.tgz", + "integrity": "sha512-wePrsf7W33mhT7RVXQavragoAgXb/NDm22vuhwJXkprrQ2Y9alrEKC5LTAtLJL3P2dHdDmeylS6PLZwWPEE79A==", + "requires": { + "@firebase/analytics": "0.8.4", + "@firebase/analytics-compat": "0.1.17", + "@firebase/app": "0.8.4", + "@firebase/app-check": "0.5.17", + "@firebase/app-check-compat": "0.2.17", + "@firebase/app-compat": "0.1.39", + "@firebase/app-types": "0.8.1", + "@firebase/auth": "0.20.11", + "@firebase/auth-compat": "0.2.24", + "@firebase/database": "0.13.10", + "@firebase/database-compat": "0.2.10", + "@firebase/firestore": "3.7.3", + "@firebase/firestore-compat": "0.2.3", + "@firebase/functions": "0.8.8", + "@firebase/functions-compat": "0.2.8", + "@firebase/installations": "0.5.16", + "@firebase/installations-compat": "0.1.16", + "@firebase/messaging": "0.11.0", + "@firebase/messaging-compat": "0.1.21", + "@firebase/performance": "0.5.17", + "@firebase/performance-compat": "0.1.17", + "@firebase/remote-config": "0.3.15", + "@firebase/remote-config-compat": "0.1.16", + "@firebase/storage": "0.9.14", + "@firebase/storage-compat": "0.1.22", + "@firebase/util": "1.7.3" + } + }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -8778,8 +10238,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fsevents": { "version": "2.3.2", @@ -8803,8 +10262,7 @@ "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==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { "version": "1.1.3", @@ -8833,7 +10291,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -8998,6 +10455,11 @@ "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", "dev": true }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, "http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", @@ -9056,6 +10518,11 @@ "safer-buffer": ">= 2.1.2 < 3.0.0" } }, + "idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -9069,6 +10536,11 @@ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -9111,7 +10583,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -9120,8 +10591,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.8", @@ -9165,8 +10635,7 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-generator-fn": { "version": "2.1.0", @@ -9216,6 +10685,11 @@ "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", "dev": true }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -9958,6 +11432,41 @@ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true }, + "jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "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" + } + }, + "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" + } + } + } + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -10001,6 +11510,14 @@ "type-check": "~0.4.0" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -10022,6 +11539,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -10034,6 +11556,11 @@ "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -10156,7 +11683,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -10242,8 +11768,6 @@ "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "peer": true, "requires": { "whatwg-url": "^5.0.0" } @@ -10308,7 +11832,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -10372,6 +11895,16 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "papaparse": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.2.tgz", + "integrity": "sha512-6dNZu0Ki+gyV0eBsFKJhYr+MdQYAzFUGlBMNj3GNrmHxmz1lfRa24CjFObPXtjcetlOv5Ad299MhIK0znp3afw==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -10408,8 +11941,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-key": { "version": "3.1.1", @@ -10595,6 +12127,11 @@ } } }, + "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", @@ -10612,6 +12149,26 @@ "sisteransi": "^1.0.5" } }, + "protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -10826,8 +12383,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" }, "require-from-string": { "version": "2.0.2", @@ -10901,7 +12457,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -10927,8 +12482,7 @@ "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==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", @@ -10942,12 +12496,27 @@ "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", "dev": true }, + "selenium-webdriver": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.5.0.tgz", + "integrity": "sha512-9mSFii+lRwcnT2KUAB1kqvx6+mMiiQHH60Y0VUtr3kxxi3oZ3CV3B8e2nuJ7T4SPb+Q6VA0swswe7rYpez07Bg==", + "requires": { + "jszip": "^3.10.0", + "tmp": "^0.2.1", + "ws": ">=8.7.0" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "shallow-clone": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", @@ -11153,7 +12722,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -11164,7 +12732,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -11434,6 +13001,14 @@ "dev": true, "peer": true }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "requires": { + "rimraf": "^3.0.0" + } + }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -11458,9 +13033,7 @@ "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "peer": true + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "tree-kill": { "version": "1.2.2", @@ -11477,8 +13050,7 @@ "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "type-check": { "version": "0.4.0", @@ -11549,8 +13121,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "v8-compile-cache": { "version": "2.3.0", @@ -11604,9 +13175,22 @@ "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "peer": true + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" }, "whatwg-encoding": { "version": "2.0.0", @@ -11621,8 +13205,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "peer": true, "requires": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -11647,7 +13229,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -11657,8 +13238,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "write-file-atomic": { "version": "4.0.2", @@ -11674,15 +13254,12 @@ "version": "8.9.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", - "dev": true, - "peer": true, "requires": {} }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yallist": { "version": "4.0.0", @@ -11722,8 +13299,7 @@ "yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" }, "yauzl": { "version": "2.10.0", diff --git a/package.json b/package.json index 3a52e5f..f584bdd 100644 --- a/package.json +++ b/package.json @@ -27,5 +27,9 @@ "jest-puppeteer": "^6.1.1", "stylelint": "^14.11.0", "stylelint-config-standard": "^28.0.0" + }, + "dependencies": { + "firebase": "^9.14.0", + "papaparse": "^5.3.2" } } diff --git a/site/css/banner.css b/site/css/banner.css new file mode 100644 index 0000000..efb419d --- /dev/null +++ b/site/css/banner.css @@ -0,0 +1,20 @@ +/* ---------- Top banner ------------- */ + +#banner-component { + position: fixed; + background-color: #fff; + height: 5vh; + width: 100vw; + border-bottom: 3px solid #0d59a9; + z-index: 99; +} + +#banner-component p { + color: #0d59a9; + font-size: 1.3em; + margin: 0; + line-height: 5vh; + font-weight: 800; + text-align: center; + vertical-align: middle; +} \ No newline at end of file diff --git a/site/css/edit.css b/site/css/edit.css new file mode 100644 index 0000000..7695164 --- /dev/null +++ b/site/css/edit.css @@ -0,0 +1,400 @@ +#basic-panel-component { + position: relative; +} + +/* +The bottom component is originally not showing until you select a voter +*/ + +/* Some overall setting */ + +.edit-button { + cursor: pointer; +} + +.edit-button .material-symbols-outlined { + font-size: 1.2rem; + line-height: 1.2; + cursor: pointer; +} + +.panel-title { + padding: 0.3rem 0.5rem 0; + font-weight: 800; + margin-bottom: 1.5rem; +} + +#bottom-component .edit-button { + background-color: var(--main-color); + float: left; + color: #fff; + padding: 0.2rem 0.5rem; + border-radius: 0.5rem 0.5rem 0 0; +} + +.edit-container { + max-height: 55vh; + overflow-y: auto; +} + +.info-panel { + clear: left; + background-color: white; + padding: 0.5rem 0.5rem 0; + display: block; + margin-bottom: 0.5rem; +} + +.basic-info-panel { + height: auto; +} + +/* Icon set */ + +.info-panel-canvass-status { + width: 50%; + float: left; + display: flex; + flex-direction: column; + align-items: center; +} + +.icon-group { + display: flex; + justify-content: center; +} + +.icon-set { + width: 3rem; + float: left; + display: flex; + flex-direction: column; + align-items: center; +} + +.icon-set .list-icon { + border-color: #bbb; + color: #bbb; +} + +.icon-set .material-symbols-outlined { + font-size: 1.2rem; +} + +.icon-subtitle { + font-size: 0.65rem; + text-align: center; + color: #bbb; +} + +.canvass-status-save { + clear: left; + width: 8rem; + border: 1px solid var(--main-color); + margin-top: 0.25rem; + padding: 0.1rem; + text-align: center; + font-size: 0.9rem; + border-radius: 0 0 0.4rem 0.4rem; + border-bottom-width: 2.5px; + cursor: pointer; +} + +.canvass-status-save-toast { + background-color: var(--main-color); + color: #fff; +} + +/* Dealing with highlighted items */ + +.highlighted .material-symbols-outlined { + font-size: 1.2rem; + color: #fff; +} + +.highlighted p { + font-size: 1.2rem; + color: #fff; +} + +.highlighted .list-icon { + background-color: var(--main-color); + border-color: var(--main-color); +} + +#icon-who-r.highlighted .list-icon { + background-color: var(--red-color); + border-color: var(--red-color); +} + +#icon-plan-yes.highlighted .list-icon { + background-color: var(--ok-color); + border-color: var(--ok-color); +} + +#icon-canvass-pending.highlighted .list-icon { + background-color: #999; + border-color: #999; +} + +#icon-canvass-pending.highlighted .icon-subtitle { + color: #999; + font-weight: 800; +} + +#icon-canvass-awaits.highlighted .list-icon { + background-color: var(--red-color); + border-color: var(--red-color); +} + +#icon-canvass-awaits.highlighted .icon-subtitle { + color: var(--red-color); + font-weight: 800; +} + +#icon-canvass-completed.highlighted .list-icon { + background-color: var(--ok-color); + border-color: var(--ok-color); +} + +#icon-canvass-completed.highlighted .icon-subtitle { + color: var(--ok-color); + font-weight: 800; +} + +/* Other overview icons */ + +.overview-display { + padding: 0.4rem; + clear: left; + margin-top: 0.5rem; + display: flex; +} + +#overview-display .icon-set { + margin-right: 0.8rem; + width: 4.3rem; +} + +#overview-display .icon-set .icon-subtitle { + color: var(--main-color); +} + +#overview-display .icon-set .list-icon { + border-color: var(--main-color); +} + +#overview-display .icon-set .material-symbols-outlined { + font-size: 1.2rem; +} + +/* When screen too small, don't display subtitles */ + +@media only screen and (max-width: 340px) { + .icon-set { + width: 2rem; + } + + .icon-subtitle { + display: none; + } +} + +/* Basic info panel name and address */ + +.info-panel-name-address { + float: left; + width: 50%; +} + +.info-panel-name { + padding: 0.5rem; + font-size: 1.4rem; + font-weight: 800; +} + +.info-panel-address { + font-size: 0.85rem; + padding: 0 0.5rem; +} + +@media only screen and (max-width: 340px) { + .info-panel-name { + font-size: 1rem; + } + + .info-panel-address { + font-size: 0.75rem; + } +} + +#edit-component { + display: none; + transform: translateX(-40em); + transition: transform 0.5s ease-in-out; +} + +#edit-component .scroll-container li { + width: 100%; +} + +/* Voting History */ + +.voter-history-panel { + min-height: 10vh; +} + +.voting-history-title { + width: 100%; + padding: 0 0.5rem; + margin-bottom: -0.3rem; +} + +#edit-component .list-expand-button { + margin-top: -0.5rem; + margin-bottom: 1rem; + height: 1.4rem; + width: 2rem; + line-height: 0.8; + margin-left: calc(50% - 1rem); +} + +.voting-history-title > div { + font-size: 0.7rem; + float: left; +} + +.voting-history-title-date { + width: 20%; +} + +.voting-history-title-name { + width: 48%; +} + +.voting-history-title-party { + width: 15%; +} + +.voting-history-title-method { + width: 12%; +} + +#edit-component .scroll-container { + max-height: 12vh; + clear: left; +} + +#edit-component .scroll-container-expanded { + max-height: 25vh; + transition: max-height 0.5s; +} + +.voting-history-item { + float: left; + font-size: 0.8rem; +} + +.voting-history-date { + width: 22%; + font-size: 0.7rem; +} + +.voting-history-name { + width: 53%; + font-weight: 800; +} + +.voting-history-party { + width: 18%; +} + +.voting-history-method { + width: 12%; +} + +.this-visit-panel { + height: auto; + padding-bottom: 1rem; + transition: height 1s ease-in-out; +} + +.last-visit-panel { + min-height: 15vh; +} + +.record-item { + padding-left: 0.5rem; + margin-top: 0.5rem; +} + +.record-item-name { + float: left; + font-size: 0.85rem; + width: 30%; + position: relative; + top: 0.75rem; +} + +.record-item .icon-group { + justify-content: flex-start; + width: 70%; +} + +.record-item .input-container { + margin-top: 0.3rem; + margin-left: 0.5rem; + width: 3rem; +} + +.record-item input { + border-color: #bbb; + border-width: 1px 1px 2.5px; + font-size: 0.8rem; + height: 1.2rem; + color: #bbb; +} + +.record-item input:focus { + border-width: 2.5px; + border-color: var(--aux-color); + border-radius: 0 0 0.5em 0.5em; +} + +.record-item input::placeholder { + color: #bbb; +} + +#other-notes-input { + margin: 1rem 0.5rem 0.5rem; + width: calc(100% - 2rem); + height: 3rem; +} + +#other-notes-input input { + height: 100%; +} + +.final-save { + height: 2rem; + background-color: var(--main-color); + color: #fff; + border: 1.5px solid var(--main-color); + border-bottom-width: 3px; + border-radius: 0 0 0.6rem 0.6rem; + display: flex; + align-items: center; + justify-content: center; + margin-top: -1rem; + margin-bottom: 0.5rem; + font-size: 1.2rem; + font-weight: 800; + transform: translateX(-40em); + transition: transform 0.5s ease-in-out; + cursor: pointer; +} + +.final-save-toast { + background-color: var(--aux-color); + border-color: var(--aux-color); +} \ No newline at end of file diff --git a/site/css/framework.css b/site/css/framework.css new file mode 100644 index 0000000..f8deebe --- /dev/null +++ b/site/css/framework.css @@ -0,0 +1,54 @@ +html { + font-family: Roboto, sans-serif; +} + +body { + min-height: 100vh; + width: 100vw; + padding: 0; + margin: 0; + position: relative; +} + +#banner-component { + position: fixed; + background-color: #fff; + height: 5vh; + width: 100vw; + border-bottom: 3px solid #0d59a9; + z-index: 99; +} + +#banner-component p { + color: #0d59a9; + font-size: 1.3em; + margin: 0; + line-height: 5vh; + font-weight: 800; + text-align: center; + vertical-align: middle; +} + +/* ---------- List loader and filter button ------------- */ + +#nav-component { + height: 6vh; + position: fixed; + top: 7vh; + margin-bottom: 0.5rem; +} + +#top-component { + position: fixed; + top: 14vh; + height: 25vh; + z-index: 99999; +} + +#bottom-component { + top: 60vh; + position: fixed; + transform: translateX(-40em); + transition: all 0.5s ease-in-out; + max-height: 70vh; +} \ No newline at end of file diff --git a/site/css/list-filters.css b/site/css/list-filters.css new file mode 100644 index 0000000..89925bb --- /dev/null +++ b/site/css/list-filters.css @@ -0,0 +1,202 @@ +/* ---------- +Top right button to initiate filter window +------------ */ + +#list-filter-component { + position: relative; + float: right; + right: 0; + height: 2.5em; + width: 2.1rem; + z-index: 99999; +} + +#list-filter-component .button { + width: 1.7rem; + margin: 0 0.1rem; + border-bottom-width: 3px; + border-radius: 0 0 0.5rem 0.5rem; + background-color: var(--main-color); + border-color: var(--main-color); +} + +/* ------------ +Filter window +-------------- */ + +/* ------- Half-transparent overlay ---------- */ + +.filter-overlay { + align-items: center; + justify-content: center; + position: fixed; + left: 0; + top: 0; + bottom: 0; + right: 0; + background-color: rgb(0 0 0 / 60%); + color: white; + font-weight: bold; + z-index: 9999999; + animation: pulse-black 1s infinite; + display: none; + opacity: 0; + transition: opacity 0.5s ease-in-out; +} + +/* ------- Filter window ---------- */ + +.filter-window { + position: fixed; + top: 30%; + left: 50%; + width: 16rem; + height: 28rem; + margin-top: -10rem; + margin-left: -8rem; + background-color: white; + border-radius: 1rem; + z-index: 2000; + z-index: 9999999999; + filter: drop-shadow(10px 10px 12px #333); + display: none; /* But will turn into flex with js */ + flex-direction: column; + opacity: 0; + transition: opacity 0.5s ease-in-out; + overflow-y: scroll; +} + +/* --------- Filters --------- */ + +.filter-group-title { + margin: 2rem 1.5rem 0; + color: var(--main-color); + font-weight: 500; + font-size: 0.95rem; + padding: 0; + text-align: center; +} + +.filter-group { + margin: 1rem 1.5rem 0; + display: flex; + justify-content: center; +} + +.filter-group .icon-set { + width: 4rem; +} + +.filter-checkbox:checked + .icon-set .list-icon { + background-color: var(--main-color); + border-color: var(--main-color); +} + +.filter-checkbox:checked + .icon-set .list-icon .material-symbols-outlined { + color: #fff; +} + +.filter-checkbox:checked + .icon-set .list-icon p { + color: #fff; +} + +.filter-checkbox:checked + .icon-set .icon-subtitle { + color: var(--main-color); + font-weight: 800; +} + +.filter-checkbox { + opacity: 0; + position: absolute; + pointer-events: none; +} + +.filter-checkbox:checked + #filter-pending .list-icon { + background-color: #bbb; + border-color: #bbb; +} + +.filter-checkbox:checked + #filter-pending .icon-subtitle { + color: #bbb; +} + +.filter-checkbox:checked + #filter-noparty .list-icon { + background-color: #bbb; + border-color: #bbb; +} + +.filter-checkbox:checked + #filter-noparty .icon-subtitle { + color: #bbb; +} + +.filter-checkbox:checked + #filter-rep .list-icon { + background-color: var(--red-color); + border-color: var(--red-color); +} + +.filter-checkbox:checked + #filter-rep .icon-subtitle { + color: var(--red-color); +} + +.filter-checkbox:checked + #filter-ind .list-icon { + background-color: var(--aux-color); + border-color: var(--aux-color); +} + +.filter-checkbox:checked + #filter-ind .icon-subtitle { + color: var(--aux-color); +} + +.filter-checkbox:checked + #filter-lib .list-icon { + background-color: var(--ok-color); + border-color: var(--ok-color); +} + +.filter-checkbox:checked + #filter-lib .icon-subtitle { + color: var(--ok-color); +} + +.filter-checkbox:checked + #filter-oth .list-icon { + background-color: #bbb; + border-color: #bbb; +} + +.filter-checkbox:checked + #filter-oth .icon-subtitle { + color: #bbb; +} + +.filter-checkbox:checked + #filter-active .list-icon { + background-color: var(--ok-color); + border-color: var(--ok-color); +} + +.filter-checkbox:checked + #filter-active .icon-subtitle { + color: var(--ok-color); +} + +.filter-checkbox:checked + #filter-inactive .list-icon { + background-color: #bbb; + border-color: #bbb; +} + +.filter-checkbox:checked + #filter-inactive .icon-subtitle { + color: #bbb; +} + +.filter-checkbox:checked + #filter-completed .list-icon { + background-color: var(--ok-color); + border-color: var(--ok-color); +} + +.filter-checkbox:checked + #filter-completed .icon-subtitle { + color: var(--ok-color); +} + +.filter-checkbox:checked + #filter-awaits .list-icon { + background-color: var(--red-color); + border-color: var(--red-color); +} + +.filter-checkbox:checked + #filter-awaits .icon-subtitle { + color: var(--red-color); +} diff --git a/site/css/list-loader.css b/site/css/list-loader.css new file mode 100644 index 0000000..4d86f12 --- /dev/null +++ b/site/css/list-loader.css @@ -0,0 +1,70 @@ +#list-loader-component { + position: relative; + height: 6.2vh; + width: 13rem; + float: left; + z-index: 99999; + margin: 0; +} + +#list-loader-hidable-chunk { + transition: transform 0.5s ease-in-out; + z-index: 1; + height: 100%; +} + +#list-loader-component .hide-show { + font-size: 3rem; + width: 1.5rem; + line-height: 0.5; + border-bottom-width: 3px; + border-radius: 0 0 0.5rem 0.5rem; +} + +#list-loader-component .input-container { + height: 2rem; + float: left; + margin-left: 0.3rem; +} + +#list-loader-component .input-container input { + height: 100%; + width: 4.6rem; + font-size: 0.9rem; +} + +#list-loader-load .material-symbols-outlined { + font-size: 1.2rem; + line-height: 0.9; +} + +#list-loader-load { + border-right-width: 3px; + border-radius: 0 0.5rem 0.5rem 0; + width: 2rem; + float: left; + margin-left: 0.2em; + font-size: 0.8rem; + line-height: 0.8; +} + +#list-loader-load .tooltiptext { + color: #0d59a9; + text-align: center; + width: 8em; + height: 1rem; + padding: 0.2rem 0.5rem; + position: absolute; + bottom: 120%; + left: 60%; + font-weight: 500; + font-size: 0.8rem; +} + +#list-loader-load:hover .tooltiptext { + visibility: visible; +} + +.tooltip-content { + z-index: 9999; +} \ No newline at end of file diff --git a/site/css/main-box.css b/site/css/main-box.css new file mode 100644 index 0000000..04ac9ff --- /dev/null +++ b/site/css/main-box.css @@ -0,0 +1,24 @@ +.main-box { + z-index: 9999; + padding: 0 1rem; + margin-right: 0.5rem; + width: calc(100vw - 2rem); +} + +@media only screen and (min-width: 600px) { + .main-box { + width: 60vw; + } +} + +@media only screen and (min-width: 768px) { + .main-box { + width: 40vw; + } +} + +@media only screen and (min-width: 1200px) { + .main-box { + width: 25vw; + } +} diff --git a/site/css/main.css b/site/css/main.css new file mode 100644 index 0000000..26e0eb3 --- /dev/null +++ b/site/css/main.css @@ -0,0 +1,121 @@ +/* -------- +Overall colors +-------- */ + +:root { + --main-color: #0d59a9; + --aux-color: #fad03c; + --ok-color: #15b88a; + --red-color: #e96a46; +} + +/* -------- +Overall framework setting +-------- */ + +.component-margin-left { + margin-left: 1rem; +} + +.component-margin-right { + margin-right: 1rem; +} + +/* -------- +Responsive design +-------- */ + +@media only screen and (max-width: 400px) { + .component-margin-left { + margin-left: 0.5rem; + } + + .component-margin-right { + margin-right: 0.5rem; + } +} + +@media only screen and (min-width: 768px) { + .component-margin-left { + margin-left: 2rem; + } + + .component-margin-right { + margin-right: 2rem; + } +} + +/* -------- +Defining some universal classes +-------- */ + +.button { + background-color: var(--main-color); + width: 2rem; + height: 2rem; + font-size: 1.8rem; + float: left; + color: #fff; + border: 1.5px solid var(--main-color); + cursor: pointer; + text-align: center; + vertical-align: middle; + border-bottom-width: 3px; + border-radius: 0 0 0.5rem 0.5rem; + box-shadow: 10px 10px 10px var(-main-color); +} + +.hide-show { + width: 2.2rem; + height: 2rem; +} + +input[type="text"] { + width: 100%; + border: 1.5px solid var(--main-color); + padding-left: 0.5rem; + color: var(--main-color); + border-bottom-width: 3px; + border-radius: 0 0 0.5em 0.5em; + font-size: 1rem; +} + +input:focus { + background-color: #fff; + border: 3px solid var(--aux-color); + outline: none; + border-bottom-width: 3px; + border-radius: 0 0 0.5em 0.5em; +} + +.hidable-container { + overflow-x: hidden; + height: 100%; +} + +.fix-height { + height: 2em; +} + +.tooltiptext { + visibility: hidden; + background-color: #fff; + text-align: center; + border-radius: 6px; + padding: 0.2rem 0.5rem; + position: absolute; + z-index: 999999; + bottom: 120%; + font-weight: 500; + font-size: 0.8rem; +} + +.panel { + height: 100%; + border: 1.5px solid var(--main-color); + padding-left: 0.5rem; + color: var(--main-color); + border-bottom-width: 3px; + border-radius: 0 0 0.5em 0.5em; + font-size: 1rem; +} \ No newline at end of file diff --git a/site/css/map.css b/site/css/map.css new file mode 100644 index 0000000..9ef51fd --- /dev/null +++ b/site/css/map.css @@ -0,0 +1,11 @@ +#map-component { + position: fixed; + width: 100vw; + height: 100vh; + z-index: 0; +} + +#leaflet-control-attribution { + z-index: 999; + position: relative; +} \ No newline at end of file diff --git a/site/css/scroll-list.css b/site/css/scroll-list.css new file mode 100644 index 0000000..b507656 --- /dev/null +++ b/site/css/scroll-list.css @@ -0,0 +1,30 @@ +/* ------- +Default scroll container height +-------- */ + +.scroll-container { + max-height: 16vh; + overflow: auto; + transition: max-height 0.75s; +} + +/* ---------- +Expand-list button +---------- */ + +.list-expand-button { + margin-left: auto; + margin-right: auto; +} + +/* ---- Each component: overall setting ------ */ + +.scroll-container li { + padding: 0.3rem; + border-bottom: 1px solid #bbb; + height: 1.5rem; + display: flex; + justify-content: flex-start; + align-items: center; + width: 28rem; +} \ No newline at end of file diff --git a/site/css/search-box.css b/site/css/search-box.css new file mode 100644 index 0000000..30b0744 --- /dev/null +++ b/site/css/search-box.css @@ -0,0 +1,17 @@ +#search-box-component { + position: relative; + height: 2.5rem; + z-index: 999; + transition: transform 0.5s; + margin-bottom: 0.6rem; +} + +#search-box-component .input-container { + height: 2.1rem; + margin-right: 0.8rem; +} + +#search-box-component .input-container input { + height: 100%; + font-size: 0.9rem; +} \ No newline at end of file diff --git a/site/css/voter-list.css b/site/css/voter-list.css new file mode 100644 index 0000000..b3ec03f --- /dev/null +++ b/site/css/voter-list.css @@ -0,0 +1,176 @@ +/* ------- +Voter list component overall setting +-------- */ + +#voter-list-component { + position: relative; + display: flex; + flex-direction: column; + transition: transform 0.5s; +} + +#voter-list-component .panel { + background-color: white; + padding: 0 0.5rem 0.05rem; +} + +/* ---------- +Change scroll container height based when clicked +---------- */ + +/* ---- Normally, expand to 42vh ------ */ + +.scroll-container-expanded { + max-height: 35vh; + transition: max-height 0.5s; +} + +/* ---- With wide screen, when basic info panel is on the other side, use 75vh ------ */ + +.scroll-container-expanded-long { + max-height: 35vh; + transition: max-height 1s; +} + +/* ------- +Voter list items +-------- */ + +/* ---- Overall adjustment; overwrite list defaults ------ */ + +.scroll-container ul { + display: block; + margin-right: 0.5rem; + padding: 0.1rem 0.4rem 0 0.2rem; +} + +/* ---- Overwrite some settings for address items ------ */ + +#voter-list-component .scroll-container .list-address { + margin-right: 1rem; + font-size: 0.8rem; + padding-top: 1rem; + height: 1rem; + vertical-align: baseline; + border-bottom: 0 solid #bbb; +} + +/* ---- Hover ------ */ + +#voter-list-component .scroll-container .list-voter:hover { + background-color: #eee; +} + +/* ---- Highlight voter ------ */ + +#voter-list-component .scroll-container .selected { + background-color: #0d59a9; +} + +#voter-list-component .scroll-container .selected:hover { + background-color: #0d59a9; +} + +/* ---------- +Text and icons inside voter list item +---------- */ + +#voter-list-component .selected .list-name { + color: #fff; +} + +#voter-list-component .scroll-container li .list-name { + position: relative; + font-weight: 600; + float: left; + margin-right: 1rem; + font-size: 1rem; + vertical-align: center; +} + +#voter-list-component .button { + font-size: 0.8rem; + height: 1.3rem; +} + +.list-icon { + background-color: #fff; + float: left; + height: 1.1rem; + margin: 0.3rem; + min-width: 1.5rem; + font-size: 0.9rem; + font-weight: 800; + padding: 0.15rem 0.05rem; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + line-height: 0.8rem; + border-radius: 0 0 0.3rem 0.3rem; + border: 1.25px solid #0d59a9; + border-bottom-width: 2.5px; + cursor: pointer; +} + +#voter-list-component .material-symbols-outlined { + font-size: 1.2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +/* ---------- +COLOR +---------- */ + +/* ---- Some colors ------ */ + +.icon-ok-color { + color: #15b88a; +} + +.icon-notify-color { + color: #e96a46; +} + +.icon-gray-color { + color: #bbb; +} + +.icon-main-color { + color: #0d59a9; +} + +/* ---- Registered party ------ */ + +.icon-democrat-color { + color: #0d59a9; +} + +#overview-display .icon-set .icon-democrat-color { + color: #0d59a9; +} + +.icon-republican-color { + color: #e96a46; +} + +.icon-set .icon-republican-color { + color: #e96a46; +} + +#overview-display .icon-republican-color { + color: #e96a46; +} + +.icon-oth-party-color { + color: #c1c1c1; +} + +#voter-list-component .panel .button { + height: 1.1rem; + width: 1.1rem; + border-radius: 0.1rem; + margin-right: 0.4rem; +} diff --git a/site/data/election_lookup.js b/site/data/election_lookup.js new file mode 100644 index 0000000..f2d1fb0 --- /dev/null +++ b/site/data/election_lookup.js @@ -0,0 +1,162 @@ +export default[{ + "Election 1": { + "description": "2020 GENERAL PRIMARY", + "date": "06/02/2020" + }, + "Election 2": { + "description": "2021 MUNICIPAL ELECTION", + "date": "11/02/2021" + }, + "Election 3": { + "description": "2020 GENERAL ELECTION", + "date": "11/03/2020" + }, + "Election 4": { + "description": "2017 MUNICIPAL PRIMARY", + "date": "05/16/2017" + }, + "Election 5": { + "description": "2017 MUNICIPAL ELECTION", + "date": "11/07/2017" + }, + "Election 6": { + "description": "2018 GENERAL ELECTION", + "date": "11/06/2018" + }, + "Election 7": { + "description": "2018 GENERAL PRIMARY", + "date": "05/15/2018" + }, + "Election 8": { + "description": "2019 MUNICIPAL PRIMARY", + "date": "05/21/2019" + }, + "Election 9": { + "description": "2019 Special Election 190TH Leg", + "date": "03/12/2019" + }, + "Election 10": { + "description": "2020 SPECIAL ELECTION 190TH DIST", + "date": "02/25/2020" + }, + "Election 11": { + "description": "2021 MUNICIPAL PRIMARY", + "date": "05/18/2021" + }, + "Election 12": { + "description": "2019 MUNICIPAL ELECTION", + "date": "11/05/2019" + }, + "Election 13": { + "description": "2022 GENERAL PRIMARY", + "date": "05/17/2022" + }, + "Election 14": { + "description": "2006 GENERAL PRIMARY", + "date": "05/16/2006" + }, + "Election 15": { + "description": "2006 GENERAL ELECTION", + "date": "11/07/2006" + }, + "Election 16": { + "description": "2007 GENERAL PRIMARY", + "date": "05/15/2007" + }, + "Election 17": { + "description": "2007 GENERAL ELECTION", + "date": "11/06/2007" + }, + "Election 18": { + "description": "2008 GENERAL PRIMARY", + "date": "04/22/2008" + }, + "Election 19": { + "description": "2008 GENERAL ELECTION", + "date": "11/04/2008" + }, + "Election 20": { + "description": "2009 MUNICIPAL PRIMARY ELECTION", + "date": "05/19/2009" + }, + "Election 21": { + "description": "2009 MUNICIPAL GENERAL ELECTION", + "date": "11/03/2009" + }, + "Election 22": { + "description": "2010 GENERAL PRIMARY", + "date": "05/18/2010" + }, + "Election 23": { + "description": "2010 GENERAL ELECTION", + "date": "11/02/2010" + }, + "Election 24": { + "description": "2011 SPECIAL ELECTION", + "date": "02/01/2011" + }, + "Election 25": { + "description": "2011 MUNICIPAL PRIMARY", + "date": "05/17/2011" + }, + "Election 26": { + "description": "2011 MUNICIPAL ELECTION", + "date": "11/08/2011" + }, + "Election 27": { + "description": "2012 GENERAL PRIMARY", + "date": "04/24/2012" + }, + "Election 28": { + "description": "2012 GENERAL ELECTION", + "date": "11/06/2012" + }, + "Election 29": { + "description": "2013 MUNICIPAL PRIMARY", + "date": "05/21/2013" + }, + "Election 30": { + "description": "2013 MUNICIPAL ELECTION", + "date": "11/05/2013" + }, + "Election 31": { + "description": "2014 GENERAL PRIMARY", + "date": "05/20/2014" + }, + "Election 32": { + "description": "2014 GENERAL ELECTION", + "date": "11/04/2014" + }, + "Election 33": { + "description": "Special Election for the170thLegislative District", + "date": "03/24/2015" + }, + "Election 34": { + "description": "2015 MUNICIPAL PRIMARY", + "date": "05/19/2015" + }, + "Election 35": { + "description": "2015 SPECIAL ELECTION FOR THE 174TH,191ST,195TH, L", + "date": "08/11/2015" + }, + "Election 36": { + "description": "2015 MUNICIPAL ELECTION", + "date": "11/03/2015" + }, + "Election 37": { + "description": "2016 SPECIAL ELECTION (03/15/2016)", + "date": "03/15/2016" + }, + "Election 38": { + "description": "2016 GENERAL PRIMARY", + "date": "04/26/2016" + }, + "Election 39": { + "description": "2016 GENERAL ELECTION", + "date": "11/08/2016" + }, + "Election 40": { + "description": "2017 SPECIAL ELECTION LEGISLATIVE DISTRICT 197", + "date": "03/21/2017" + } +}]; \ No newline at end of file diff --git a/site/data/election_lookup.json b/site/data/election_lookup.json index 6185502..f2d1fb0 100644 --- a/site/data/election_lookup.json +++ b/site/data/election_lookup.json @@ -1,4 +1,4 @@ -{ +export default[{ "Election 1": { "description": "2020 GENERAL PRIMARY", "date": "06/02/2020" @@ -159,4 +159,4 @@ "description": "2017 SPECIAL ELECTION LEGISLATIVE DISTRICT 197", "date": "03/21/2017" } -} \ No newline at end of file +}]; \ No newline at end of file diff --git a/site/data/political_party_lookup.js b/site/data/political_party_lookup.js new file mode 100644 index 0000000..9c245f0 --- /dev/null +++ b/site/data/political_party_lookup.js @@ -0,0 +1,535 @@ +export default[{ + "3RD": "3rd", + "A": "ALL", + "ABO": "ABOLITIONIST", + "AC": "ANARCHIST", + "ACO": "ANARCHO-COMMUNIST", + "ACP": "ANARCHO CAPITALIST", + "ACR": "ALL CROOKS", + "AD": "ADARIAN", + "AE": "AMERICAN EAGLE", + "AF": "AMERICAN FIRST PARTY", + "AFP": "AMERICAN FREEDOM PARTY", + "AG": "ALMIGHTY GOD'S", + "AH": "AMERICAN HERITAGE", + "AI": "AMERICAN INDEPENDENT", + "AIP": "AIP", + "AL": "AMERICAN LABOR", + "AMA": "AMERICA", + "ALL": "ALLAH", + "AM": "AMERICAN", + "AMD": "AMERICAN DELTA", + "AME": "AMERICA'S", + "AMF": "AMERICA FIRST", + "AMI": "AMERICAN INDIAN", + "AMRE": "American Reform", + "AMRL": "AMERICAL LIBERTY", + "AN": "AMERICAN NAZI", + "ANA": "ANARCHY", + "ANL": "ANIMAL LIFE", + "ANO": "ANONYMOUS", + "ANP": "AIN'T NO PARTY LIKE A POLITICAL PARTY", + "ANY": "ANY", + "AO": "ANTI OBAMA", + "AP": "AMERICAN PATRIOT", + "APJ": "APPLE JUICE", + "APP": "AMERICA'S PARTY PENNSYLVANIA", + "APR": "AMERICAN FIRST PROGRESSIVE", + "APTY": "AMERICA's PARTY ", + "ARI": "ARISTOCRAT", + "ARP": "AMERICAN REVOLUTIONARY PARTY", + "ASF": "AMERICA - SEMPER FI", + "ASI": "ASIAN", + "ASM": "ASSEMBLY", + "ASP": "American Solidarity Party", + "AT": "ATHEIST", + "ATP": "AMERICAN TEA PARTY", + "AU": "AMERICAN UNITY", + "AW": "AWAKE", + "AZ": "AQUAZIAN ZIONIST", + "AZPA": "AQUARIAN ZIONIST PARTY OF AMERICA", + "AYE": "Aseyev", + "B": "BIRTHDAY", + "BA": "BLUE ARMY", + "BH": "BOTH", + "BL": "BLISS", + "BLP": "BLACK PANTHER", + "BM": "BULL MOOSE", + "BMP": "BOTH MAJOR PARTIES", + "BO": "BARACK OBAMA", + "BP": "BI-PARTISAN", + "BPAT": "BEST PERSON AT THE TIME", + "BPFJ": "BEST PERSON FOR THE JOB", + "BS": "BERNIE SANDERS", + "BSRR": "BEER/SEX/ROCK ROLL", + "BSTP": "BEST PERSON", + "BTH": "BOTH 1/2", + "BTM": "BETWEEN MIDDLE", + "C": "CONSERVATIVE", + "CA": "CAPITALIST", + "CB": "CAMPBELL PARTY", + "CC": "CHRISTIAN CONSERVATIVE", + "CCO": "CHRISTIAN COALITION", + "CCUS": "CATHOLIC CENTER PARTY USA", + "CD": "CHRISTIAN DEMOCRAT", + "CDU": "CHRISTIAN DEMOCRATIC PARTY U.S.A.", + "CDUN": "CHRISTIAN DEMOCRAT UNION", + "CE": "CENTRALIST", + "CF": "CONFEDERATE", + "CFED": "Constitutional Federalist", + "CH": "CHRISTIAN", + "CHA": "Chablais", + "CHG": "CHANGE", + "CHY": "Christianity", + "CL": "CONSTITUTIONAL", + "CLI": "CHARLIE", + "CLL": "CHRISTIANS FOR LIFE AND LIBERTY", + "CM": "COMMUNIST", + "CMR": "COMMERCIAL", + "CN": "CONFIDENSIAL", + "CNC": "CONSTITUTIONAL CONSERVATIVE", + "CNT": "CENTRIST", + "CNLB": "CENTRIST LIBERTARIAN", + "CO": "CONSUMER'S", + "COD": "CONSERVATIVE DEMOCRAT", + "COI": "CONSERVATIVE INDEPENDENT", + "COL": "COLL", + "COM": "COMMUNIST PARTY USA", + "CON": "CONSUMER", + "CP": "CITIZEN'S", + "CPU": "CPUSA", + "CR": "Conservative Republican", + "CRE": "CARE", + "CRT": "CIVIL RIGHTS", + "CS": "Common Sense", + "CSI": "COMMON SENSE INDEPENDENT", + "CSIP": "CSI PARTY", + "CT": "CONSTITUTION", + "CTL": "Cocktail Party", + "CTT": "CONSTITUTIONALIST", + "D": "DEMOCRATIC", + "DEAB": "Democrats Abroad", + "DEC": "DECEPTICON", + "DEM": "_DEMOCRACY", + "DEP": "DEPPERT", + "DIE": "DIETZIST", + "DIS": "DISABILITY", + "DKY": "DONT KNOW YET", + "DL": "DEMOCRATIC LIBERAL", + "DNA": "Democratic-Non Affiliation", + "DNV": "DO NOT VOTE", + "DPD": "DEPENDED", + "DPN": "DEPENDS", + "DPP": "DEMOCRATIC PROGRESSIVE PARTY", + "DR": "Democratic Republican", + "DS": "DEMOCRATIC SOCIALIST", + "DSN": "DEAD SOON", + "DSNY": "Disney Party", + "DST": "DMCRT SOCIALIST", + "DT": "DON\u2019T KNOW", + "DTP": "DIESEL TRUCK PARTY", + "DTS": "DECLINE TO STATE", + "E": "EVERYDAY", + "ECO": "Ecomagen", + "ECS": "Economic Statism", + "EGA": "Egalitare", + "EMET": "EMET", + "ENP": "EGYPTIAN", + "EPTY": "Ethan's Party", + "EQU": "EQuAliST", + "EX": "EXPERT", + "FA": "FASCIST", + "FAC": "FACIST", + "FAS": "FASCIM", + "FD": "FREEDOM", + "FE": "FEDERALIST", + "FLT": "Free\\Libert\u00e8", + "FM": "FEMINIST MAJORITY", + "FO": "FUGIFINO", + "FP": "FREAK POWER", + "FR": "FREE", + "FRA": "FREE AGENT", + "FRUSA": "FREEDOM USA", + "FS": "FREEDOM SELF", + "FT": "FREETHINKER", + "FTP": "FOR THE PEOPLE", + "FUT": "FUCK TRUP", + "G": "GOD", + "GAD": "GUNS AND DOPE", + "GD": "GREEN DEMOCRAT", + "GDI": "GDI", + "GE": "GALACTIC EMPIRE", + "GG": "GOLD GOOSE", + "GIM": "GIANT METEOR", + "GJ": "GARY JOHNSON", + "GM": "GRAND METACOSMOS", + "GN": "GOOD NEIGHBOR", + "GO": "GOOD ONE", + "GOP": "GOP", + "GP": "GREEN PARTY", + "GPUS": "GREEN PARTY OF THE US", + "GR": "GREEN", + "GRD": "GREEN DEMOCRATIC", + "GRNI": "GREEN INDEPENDENT", + "GRR": "GRASS ROOT", + "GS": "GO STEELERS", + "GT": "Go Trump", + "GTP": "GREEN TEA PARTY", + "GW": "George Washington", + "H": "HALLOWEEN", + "HAB": "HABIBI", + "HAP": "HAPPY", + "HC": "HILLERY CLINTON", + "HE": "HELLO", + "HI": "HUMAN INTEREST", + "HIL": "HILLARY", + "HILA": "HILARY", + "HO": "HORDE", + "HOG": "HOG4BOSS", + "HP": "Hoffman Party", + "HR": "HUMAN RACE", + "HSP": "HOLY SPIRIT", + "HTL": "HITLER", + "HUM": "HUMANITARIAN", + "HUP": "HUMANE", + "I": "INDEPENDENT", + "IA": "INDEPENDENCE PARTY OF AMERICA", + "IAPP": "INDEPENDENT AMERICAN PARTY OF PA", + "IC": "INDEPENDENT-COMPLETION", + "ICC": "Independent Constitutional Conservative", + "ICL": "INDEPENDENT CONSERVATIVE LIBERTARIAN", + "ICO": "INDEPENDENT CONSERVATIVE", + "ICON": "INDEPENDENT CONSTITUTIONALIST", + "ID": "INDEPENDENT DEMOCRAT", + "INDd": "IND.", + "INT": "INTRA", + "INTE": "INTELLIGENT", + "IDK": "I DON'T KNOW", + "IDL": "IDOL", + "IDR": "I DONT REGISTER ", + "IG": "INDEPENDENT GREEN", + "IITM": "I'M IN THE MIDDLE", + "IK": "IDK", + "IL": "INDEPENDENT/LIBERTARIAN", + "ILM": "ILLUMINATI", + "IM": "INDEPENDENT MODERATE", + "IN": "INTERESTING", + "IND": "INDEPENDENCE", + "INDE": "INDEPENDANT", + "INDY": "INDY", + "INRE": "INDEPENDANT/REPUBLIC", + "INNA": "Independent No Affiliation ", + "INNP": "INDEPENDENT/NO PARTY", + "IP": "INDEPENDENT POPULIST", + "IPPA": "IPPA", + "IR": "INDEPENDENT REPUBLICAN", + "IS": "INTERNATIONAL SOCIALIST", + "IT": "INDEPENT", + "ITENT": "INDEPENTENT", + "ITM": "Intermediated", + "IV": "INDIVIDUAL", + "IVFABP": "I VOTE FOR A BETTER PERSON NOT", + "JBN": "JACOBIN", + "J": "JUBILEEIAN", + "JD": "JEDI", + "JO": "JACKASS-ONE", + "JON": "JOHNSON'S", + "JP": "JUSTICE PARTY", + "JS": "JESUS", + "KEG": "KEG", + "KN": "KNOW NOTHING", + "KNP": "KUNNERMAN", + "KOG": "KINGDOM OF GOD", + "KQ": "KINGS & QUEENS", + "KWAD": "KWAD", + "L": "LABOR", + "LA": "LABORER", + "LAN": "LANNISTER", + "LAT": "LATINA", + "LD": "Liberal Democrat", + "LEG": "LEGALIST", + "LF": "LAISSE-FAIRE CAPITALIST", + "LG": "LIBERAL GREENE", + "LI": "LIBERAL", + "LIB": "Liberalist", + "LIBIN": "LIBERAL/INDEPENDENT", + "LIUP": "Liberty Union Party", + "LN": "LIBERTARIAN", + "LNS": "LIBERTARIAN SOCIALIST", + "LNSG": "LIBERTARIAN NATIONAL SOCIALIST GREEN", + "LR": "LIBERTARIAN REPUBLICAN", + "LREP": "LIBERAL REPUBLICAN", + "LT": "LIBERTARIAN TRANSHUMANIST", + "LUK": "LUKAWANDA", + "M": "MODERATE", + "MA": "MAITER", + "MAJA": "MARIJUANA", + "MAN": "MAN", + "MARR": "Marriage", + "MAY": "MAYBE", + "MAS": "MIGHT AS WELL", + "MC": "McCain", + "MCHAR": "MORAL CHARACTER", + "MDWP": "MODERATE WARD PARTY", + "MG": "MIDDLE GROUND", + "MI": "MANAYUNK INDEPENDENT", + "MID": "MIDDLE", + "MIG": "MARK IS GOD", + "MIN": "MODERATE \\ INDEPENDENT", + "ML": "MODERATE-LIBERAL", + "MM": "MICKEY MOUSE", + "MMI": "ME MYSELF AND I", + "MNT": "MONARCHIST", + "MODR": "MODERN REPUBLICAN", + "MP": "Moderate Republican", + "MOP": "Movement for a People's Party Or None", + "MRP": "Marijuana Reform Party", + "MRT": "MR TRUMP", + "MSE": "MEASE", + "MU": "MUTUAL", + "MUS": "MUSLIM", + "MW": "MODERN WHIG", + "MWP": "MODERN WHIG PARTY", + "MX": "MARXIST PARTY", + "MYMED": "MEN YOGA MEDITATION", + "MYP": "My Party", + "MZ": "MADI Z", + "N": "N/A", + "NA": "NEW ALLIANCE", + "NAI": "NEW AFRIKAN INDEPENDENCE", + "NAT": "NAT'L TAXPAYER", + "NC": "NON-COMMITTAL", + "NCP": "NON-CONFORMIST PARTY", + "ND": "NOT DEMO-REPO", + "NE": "NEUTRAL", + "NEI": "Not Either", + "NEO": "NEO-AGRARIAN", + "NF": "NO AFFILIATION", + "NI": "NOT INTERESTED", + "NINC": "NON-INCRIMINATING", + "NL": "NATURAL LAW", + "NO": "NO PARTY", + "NODE": "NON DENOMINATIONAL", + "NON": "NONE", + "NOYO": "None-Your", + "NOO": "NO", + "NOP": "NON-PARTISAN", + "NOPA": "NO PARTY AFFILIATION", + "NOS": "Non Specific", + "NOT": "NOTALITARIAN", + "NOV": "NOVELIST", + "NOW": "NO WAY", + "NP": "NEW PATRIOT", + "NPA": "Non Party", + "NPF": "No Preference", + "NPP": "NEW PATRIOT PARTY", + "NPRO": "NEW PROGRESSIVE", + "NPS": "NPS", + "NRA": "NRA", + "NS": "NATIONAL SOCIALIST", + "NSAL": "NSALP", + "NSSLP": "THE NATIONAL SOCIALIST SONS OF LIBERTY PARTY", + "NST": "NATIONALIST", + "NT": "NATIONAL TAXPAYERS", + "NTRP": "NOT TRUMP", + "NTS": "NOT SURE", + "NU": "NO LONGER IN USE", + "NVTR": "#NeverTrump", + "NW": "NEOWHIG", + "NZ": "NAZI", + "NYP": "NOT YOU PROBLEM", + "O": "OPRAH", + "OBA": "OBAMA", + "OLAP": "Ol' American Party", + "OP": "OPEN", + "OPV": "OTHER POLITICAL VIEWS", + "OR": "ORANGE", + "OTH": "OTHER", + "OW": "OLD WHIG", + "OWN": "OWN", + "OWW": "Old wagon Wheel", + "P": "PUMPKIN", + "P2": "PP", + "PA": "PA FOR PEROT", + "PA2": "PA", + "PAL": "Pacifist/Libertarian", + "PAP": "PEACE & PROSPERITY", + "PAR": "PARTY FOR SOCIALISM AND LIBERIA", + "PART": "PARTY", + "PAS": "PENNSYLVANIA SOVEREIGNS", + "PAST": "PASTAFARIEN", + "PE": "PEDESTRIAN", + "PENN": "PENNSYLVANIA", + "PEPA": "PEACE PARTY", + "PER": "PERSONAL", + "PF": "PEACE AND FREEDOM", + "PFP": "#66 PA FOR PEROT", + "PG": "PACIFIC GREEN", + "PH": "PROHIBITION", + "PHA": "PHILOSOPHICAL ANARCHIST", + "PI": "PENNSYLVANIA INDEPENDENT PARTY", + "PIR": "PIRATE", + "PIZ": "PIZZA PARTY", + "PJ": "THE PARTY OF JAY", + "PL": "PRO-LIFE", + "PLL": "Pleasant Living", + "PNP": "Pennsylvania Nationalist Party ", + "PNT": "PANTS PARTY", + "PNTS": "PREFER NOT TO SAY", + "PO": "POPULIST", + "POL": "POLITICAL", + "POLA": "POLITICAL ATHEIST", + "POO": "PARTY OF ONE", + "PP": "PATRIOT", + "PPD": "PPD", + "PPOA": "Progressive Party of America", + "PPP": "PANSEXUAL PEACE PARTY", + "PPUS": "PIRATE PARTY OF THE UNITED STATES", + "PR": "PROGRESSIVE", + "PRE": "PRESIDENT", + "PRI": "PRIVIT", + "PRK": "PHREAK THE 4TH", + "PRM": "PRAGMATIST", + "PRO": "PRO-FOR THE CULTURE", + "PRT": "PEROT", + "PS": "PACIFIST SOCIALIST", + "PSL": "PSL", + "PSS": "PIRATE STARSHIP", + "PUPA": "PURGE PARTY", + "PT": "PROUT", + "PU": "PURITAN", + "PUE": "Puerto Rico", + "PUR": "PURPLE PARTY", + "PVT": "Private", + "PZ": "PRINCIPALITY OF ZION", + "R": "REPUBLICAN", + "RA": "RAINBOW", + "RAM": "Rambler", + "RC": "RATIONAL CITIZEN", + "RD": "REFORMED", + "RDNA": "REPUBLICAN DEMOCRATIC NO AFFILIATION", + "RE": "REAL", + "REP": "REPRESENTATIVE", + "RES": "RESTRICTED", + "RF": "REFORM", + "RGN": "REAGANITE", + "RL": "RIGHT TO LIFE", + "RO": "ROYALIST", + "RON": "RON PAUL", + "ROY": "ROYAL", + "RP": "REPRESENTITIVENESS", + "RPDM": "REPUBLICAN DEMOCRATIC", + "RTV": "RIGHT TO VOTE", + "RVA": "REVOLUTIONARY ANARCHIST", + "RW": "RIGHT WING", + "S": "SOCIALIST", + "SA": "SOCIAL ANARCHIST", + "SAME": "SAME", + "SCIW": "SHARE COMMON INTERESTS WITHIN", + "SD": "SOCIALIST DEMOCRAT", + "SE": "Self", + "SST": "SECESSIONIST", + "SEC": "SECREST PARTY", + "SECR": "SECRETO", + "SEQ": "Socialist Equality", + "SFC": "SIR FRED THE CAT", + "SH": "SKYHOOK", + "SI": "SLOTH AND INDOLENCE", + "SIN": "SINGLE", + "SIS": "SISTER PARTY", + "SL": "SOCIALIST LABOR", + "SM": "SMUCK", + "SOA": "SOCIALIST ALTERNATIVE", + "SOD": "SOCIAL DEMOCRAT", + "SP": "SOCIALIST PROGRESSIVE", + "SPA": "spaghetti", + "SPI": "SPIRITUALISM", + "SPL": "SPLIT PARTY", + "SPP": "SILLY PARTY", + "SPS": "SPUSA", + "SPU": "SOCIALIST PARTY USA", + "SQ": "S.Q.U.I.D.", + "SR": "SQUIRREL REFORM", + "SSU": "SOVIET SOCIALIST UNION", + "SUP": "THE SURPRISE PARTY", + "SV": "SPLIT VOTE", + "SW": "SOCIALIST WORKERS", + "SWP": "SWP", + "T": "TAXPAYERS", + "TB": "TRUTH BASED", + "TFP": "THE FUN PARTY", + "TH ": "THE HUMANITY PARTY", + "TGPU": "THE GREEN PARTY OF THE UNITED", + "THP": "TRANSHUMANIST PARTY", + "THPY": "Third Party", + "THR": "THREE PERCENTER PARTY", + "TNP": "The New President", + "TO": "TOGA", + "TP": "Tea Party", + "TPC": "Tea Party Conservative", + "TPP": "Tea Party Patriot", + "TRP": "TRUMP", + "TVP": "TVP", + "TWSP": "TAX WALL STREET PARTY", + "TWVP": "TWELVE VISIONS PARTY", + "U": "UNITARIAN", + "UAT": "UNITED AMERICAN TAXPAYERS", + "UC": "UNITED CONSTITUTION", + "UDC": "UNDECLARED", + "UDCL": "UNDISCLOSED", + "UK": "UNITED KNOWLEDGE", + "UL": "US LABOR", + "UA": "UNITED AMERICA", + "UNA": "UNAFFILIATED", + "UNC": "UNCERTAIN", + "UND": "UNDECIDED", + "UNDT": "UNDETERMINED", + "UNER": "UNENROLLED", + "UNK": "UNKNOWN", + "UNPP": "United Progressive Party", + "UNS": "UNSURE", + "UP": "UNITED PEOPLE'S", + "UPA": "United Party of America", + "UPP": "U.S. Pirate Party", + "US": "U.S. SOCIALIST", + "USA": "USA", + "USC": "US CONSTITUTION", + "USM": "USA MINUTEMEN", + "USMJ": "USMJ PARTY", + "USP": "UNITED STATES PIRATE", + "UT": "US TAXPAYERS", + "UTP": "Utopian", + "UU": "U", + "UW": "UWSA", + "UWS": "UNITED WE STAND", + "V": "VOTE", + "VFI": "VOTE FOR WHO I LIKE @ THE TIME", + "VIG": "VIGILANTE", + "VP": "VETERAN'S PARTY OF AMERICA", + "WFP": "WORKING FAMILIES PARTY", + "WH": "WHIG", + "WHO": "whoever", + "WR": "WIG-REPUBLICAN", + "WI": "WILD", + "WIC": "WHOMEVER I CHOOSE", + "WIG": "WIG", + "WIL": "WHATEVER I LIKE", + "WIO": "WHEN ITS OBAMA", + "WIS": "WILL SEE", + "WITB": "WHO I THINK IS BEST", + "WIW": "WHOEVER I WANT", + "WJW": "WHAT JESUS WANTS", + "WL": "WHITE LOTUS", + "WN": "White Nationalist", + "WOR": "WORKERS WORLD", + "WP": "WORKERS PARTY", + "WPP": "WORKING PEOPLE'S", + "WWP": "WORLD WORKERS PARTY", + "WS": "Wynd Sok", + "WTBC": "WHOEVER WILL BE THE BEST CANID", + "WTH": "WILLING TO HELP", + "X": "X", + "YI": "YOUTH INTERNATIONAL", + "YO": "YOUNG OUTSIDER", + "YVIS": "YOUR VOTE IS SECRET" +}] \ No newline at end of file diff --git a/site/data/political_party_lookup.json b/site/data/political_party_lookup.json index bf65a0b..9c245f0 100644 --- a/site/data/political_party_lookup.json +++ b/site/data/political_party_lookup.json @@ -1,4 +1,4 @@ -{ +export default[{ "3RD": "3rd", "A": "ALL", "ABO": "ABOLITIONIST", @@ -532,4 +532,4 @@ "YI": "YOUTH INTERNATIONAL", "YO": "YOUNG OUTSIDER", "YVIS": "YOUR VOTE IS SECRET" -} \ No newline at end of file +}] \ No newline at end of file diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..996edd8 --- /dev/null +++ b/site/index.html @@ -0,0 +1,463 @@ + + + + Canvasser + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+ + +
+ Filter by registered party +
+
+
+ +
+ Filter by voter status +
+ +
+ + +
+ +
+ Filter by visit status +
+ +
+ + + +
+
+ + + +
+ + +
+
+ +
+
+ + +
+
+
+
    +
+
+
+ + +
+ expand_more +
+ +
+ +
+ +
+
+ + edit + +
+ +
+ +
+
+
+
+ +
+
+
+
+ hourglass_top +
+
Pending
+
+
+
+ timeline +
+
Awaits followup
+
+
+
+ task_alt +
+
Completed
+
+
+ +
+ Update status +
+ +
+ + +
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ +
+
+
+ + +
+
+ + +
+
+
Language
+
+
+
+

EN

+
+
English
+
+
+
+

ES

+
+
Needs Spanish help
+
+
+
+

ZH

+
+
Needs Chinise help
+
+
+
+ +
+
+
+
+ +
+
Planned to vote
+
+
+
+ task_alt +
+
Yes
+
+
+
+ unpublished +
+
No
+
+
+
+
+
Mail ballot
+
+
+
+ mark_email_read +
+
Received
+
+
+
+ unsubscribe +
+
Not received
+
+
+
+ footprint +
+
Vote in person
+
+
+
+
+
Who for
+
+
+
+

D

+
+
Dem.
+
+
+
+

R

+
+
Rep.
+
+
+
+ +
+
+
+
+
+ +
+
+
Save
+ +
+
VOTING HISTORY
+
+
Date
+
Election
+
Party
+
Method
+
+
+
    +
+
+
+ +
+ expand_more +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/js/filter-window.js b/site/js/filter-window.js new file mode 100644 index 0000000..119892b --- /dev/null +++ b/site/js/filter-window.js @@ -0,0 +1,47 @@ +/* +MISCELLANEOUS +This script hides or shows the filter window on click +When clicking on filter button, the filter window appears +Then, clicking on the areas outside the filter window, the window closes +*/ + +const filterOverlayEl = document.querySelector("#filter-overlay"); +const filterWindowEl = document.querySelector("#filter-window"); +const filterButtonEl = document.querySelector("#filter-popup-button"); + +// Filter window pops up when clicking on the button +filterButtonEl.addEventListener("click", ( ) => { + filterOverlayEl.style.display = "block"; + filterWindowEl.style.display = "flex"; + setTimeout( ( ) => { + filterOverlayEl.style.opacity = 0.8; + filterWindowEl.style.opacity = 1; + }, 50); + +}); + +// Window disappears when clicking outside of window +filterOverlayEl.addEventListener("click", ( ) => { + filterOverlayEl.style.opacity = 0; + filterWindowEl.style.opacity = 0; + setTimeout( () => { + filterOverlayEl.style.display = "none"; + filterWindowEl.style.display = "none"; + }, 500); + +}); + +// Function to change filter button color when any filter is applied +function changeFilterButtonColor(status) { + if(status === 1) { + filterButtonEl.style.backgroundColor = "#fad03c"; + filterButtonEl.style.borderColor = "#fad03c"; + } else { + filterButtonEl.style.backgroundColor = "#0d59a9"; + filterButtonEl.style.borderColor = "#0d59a9"; + } +} + +export { + changeFilterButtonColor, +}; \ No newline at end of file diff --git a/site/js/hide-show.js b/site/js/hide-show.js new file mode 100644 index 0000000..721e853 --- /dev/null +++ b/site/js/hide-show.js @@ -0,0 +1,154 @@ +/* +MISCELLANEOUS +1. Shows or hide + 1. list loader component + 2. search box component + 3. voter list component + when clicking on the hide-show button aside the list loader +2. Shows or hides components when clicking on the edit or go-back button +*/ + +// FIRST +// Top component show-hide + +import { voterListExpandButtonEl, voterListContainerEl, onExpandButtonClick } from "./list-expand.js"; +import { finalSaveButtonEl } from "./update-info.js"; +import { filterAllAndUpdate } from "./list-filters.js"; +import { selectedVoter } from "./selected-voter.js"; +import { highlightVoter } from "./selected-voter.js"; +import { showHideExpandButton } from "./voter-list.js"; + +// State variable: whether the chunk is hidden at the moment +export let loaderElIsHidden = 0; + +let hideButtonEl = document.querySelector("#list-loader-hide"); +let listLoaderHidableEl = document.querySelector("#list-loader-hidable-chunk"); +let searchBoxHidableEl = document.querySelector("#search-box-component"); +let voterListHidableEl = document.querySelector("#voter-list-component"); +export let bottomComponentEl = document.querySelector("#bottom-component"); +let navComponentEl = document.querySelector("#nav-component"); +let topComponentEl = document.querySelector("#top-component"); +export let editComponentEl = document.querySelector("#edit-component"); + +function hideChunk() { + // Hide chunk by moving + listLoaderHidableEl.style.transform = "translateX(-16em)"; + searchBoxHidableEl.style.transform = "translateX(-42em)"; + voterListHidableEl.style.transform = "translateX(-42em)"; + topComponentEl.style.pointerEvents = "none"; + + // If the list is currently expanded, unexpand + if(voterListExpandButtonEl.expandStatus == 1) { + onExpandButtonClick(voterListExpandButtonEl, voterListContainerEl); + } + + // Update button icon + hideButtonEl.innerHTML = ` + chevron_right + `; + + // Update state to hidden + loaderElIsHidden = 1; +} + +function showChunk() { + // Move back + listLoaderHidableEl.style.transform = "translateX(0em)"; + searchBoxHidableEl.style.transform = "translateX(0em)"; + voterListHidableEl.style.transform = "translateX(0em)"; + topComponentEl.style.pointerEvents = "auto"; + + // Update button icon + hideButtonEl.innerHTML = ` + chevron_left + `; + + // Update state to not hidden + loaderElIsHidden = 0; +} + +function onHideChunkClick() { + if(loaderElIsHidden == 0) { + hideChunk(); + } else { + showChunk(); + } +} + +// The list loader thing can be hidden (not very important) +hideButtonEl.addEventListener("click", onHideChunkClick); + +// THEN +// Edit button + +// State of edit mode +let editMode = 0; + +let editButtonEl = document.querySelector(".edit-button"); + +function switchToEdit() { + // Move the top thing away + topComponentEl.style.transition = 'transform 0.3s ease-in-out'; + navComponentEl.style.transition = 'transform 0.3s ease-in-out'; + topComponentEl.style.transform = "translateX(-42em)"; + navComponentEl.style.transform = "translateX(-42em)"; + + // Move the whole thing up + bottomComponentEl.style.top = "7vh"; + + // Make the other panels show: first show then slide to center + editComponentEl.style.display = "block"; + finalSaveButtonEl.style.display = "flex"; + // Only show button when necessary on the voting history panel + const electionListExpandButtonEl = document.querySelector("#election-list-expand-button"); + showHideExpandButton("#voting-history", "#voting-history-container", electionListExpandButtonEl); + + setTimeout(( ) => { + editComponentEl.style.transform = "translateX(0em)"; + finalSaveButtonEl.style.transform = "translateX(0em)"; + }, 200); + // Change the icon + setTimeout(() => { + editButtonEl.querySelector("span").innerHTML = "chevron_left"; + }, 450); +} + +function switchToNormal() { + // Move the top thing away + topComponentEl.style.transition = 'transform 0.7s ease-in-out'; + navComponentEl.style.transition = 'transform 0.7s ease-in-out'; + topComponentEl.style.transform = "translateX(0em)"; + navComponentEl.style.transform = "translateX(0em)"; + + // Hide the other panels + editComponentEl.style.transform = "translateX(-42em)"; + finalSaveButtonEl.style.transform = "translateX(-42em)"; + setTimeout(( ) => { + editComponentEl.style.display = "none"; + finalSaveButtonEl.style.display = "none"; + }, 200); + + + // Move the whole thing down + bottomComponentEl.style.top = "60vh"; + + // Update list display + filterAllAndUpdate(); + highlightVoter(selectedVoter); + + + // Change the icon + setTimeout(() => { + editButtonEl.querySelector("span").innerHTML = "edit"; + }, 450); +} + +editButtonEl.addEventListener("click", () => { + if(editMode === 0){ + switchToEdit(); + editMode = 1; + } else { + switchToNormal(); + editMode = 0; + } +}); \ No newline at end of file diff --git a/site/js/htmlelement.js b/site/js/htmlelement.js new file mode 100644 index 0000000..821f7c6 --- /dev/null +++ b/site/js/htmlelement.js @@ -0,0 +1,36 @@ +/* ==================== +The following two functions take a string of HTML and create DOM element objects +representing the tags, using the `template` feature of HTML. See the following +for more information: https://stackoverflow.com/a/35385518/123776 +==================== */ + +/* eslint-disable no-unused-vars */ + +/** + * @param {String} HTML representing a single element + * @return {Element} + */ + function htmlToElement(html) { + const template = document.createElement('template'); + const trimmedHtml = html.trim(); // Never return a text node of whitespace as the result + template.innerHTML = trimmedHtml; + return template.content.firstChild; + } + + /** + * @param {String} HTML representing any number of sibling elements + * @return {NodeList} + */ + function htmlToElements(html) { + const template = document.createElement('template'); + template.innerHTML = html; + return template.content.childNodes; + } + + window.htmlToElement = htmlToElement; + window.htmlToElements = htmlToElements; + + export { + htmlToElement, + htmlToElements, + }; \ No newline at end of file diff --git a/site/js/list-expand.js b/site/js/list-expand.js new file mode 100644 index 0000000..d0940fb --- /dev/null +++ b/site/js/list-expand.js @@ -0,0 +1,60 @@ +/* +LIST EXPAND AND UNEXPAND FUNCTION +*/ +export let voterListExpandButtonEl = document.querySelector("#voter-list-expand-button"); +export let voterListContainerEl = document.querySelector("#voter-list-component").querySelector(".scroll-container"); + +export let electionListExpandButtonEl = document.querySelector("#election-list-expand-button"); +export let electionListContainerEl = document.querySelector(".voter-history-panel").querySelector(".scroll-container"); + +// Store state: whether the list is currently expanded or not +voterListExpandButtonEl.expandStatus = 0; +electionListExpandButtonEl.expandStatus = 0; + +function expandList(thisButtonEl, thisContainerEl) { + + thisContainerEl.classList.add("scroll-container-expanded"); // Expand to 42vh max + + // Update button icon + thisButtonEl.innerHTML = `expand_less`; + + // Update state + thisButtonEl.expandStatus = 1; +} + +function unExpandList(thisButtonEl, thisContainerEl) { + // Unexpand + thisContainerEl.className = "scroll-container"; + + // Update button icon + thisButtonEl.innerHTML = `expand_more`; + + // Update state + thisButtonEl.expandStatus = 0; +} + +function onExpandButtonClick(thisButtonEl, thisContainerEl) { + console.log(thisButtonEl.expandStatus); + // If currently not expanded, expand it + if(thisButtonEl.expandStatus == 0) { + expandList(thisButtonEl, thisContainerEl); + } else { + unExpandList(thisButtonEl, thisContainerEl); + } +} + +function addExpandListFunction(thisButtonEl, thisContainerEl) { + thisButtonEl.addEventListener("click", ( ) => { + onExpandButtonClick(thisButtonEl, thisContainerEl); + }); +} + +// Voter list +addExpandListFunction(voterListExpandButtonEl, voterListContainerEl); + +// Voting history list +addExpandListFunction(electionListExpandButtonEl, electionListContainerEl); + +export { + onExpandButtonClick, +}; \ No newline at end of file diff --git a/site/js/list-filters.js b/site/js/list-filters.js new file mode 100644 index 0000000..3cf1a06 --- /dev/null +++ b/site/js/list-filters.js @@ -0,0 +1,91 @@ +/* +MODULE 2: LIST FILTERS (MAIN) +=================================================== +This script contains the links to all the filters. +Every time a filter is applied, changed, or taken off, +we should calculate a new filtered data by applying all the filters. + +Once all the filters are applied, update the voter list and voter map + +Sequence: +eventlistener -> function: 1. apply all filters on by one, 2. update voter list and map +*/ + +import { data } from "./list-loader.js"; +import { filterByNameAddress } from "./search-box.js"; +import { showVotersInList } from "./voter-list.js"; +import { showVotersOnMap } from "./map.js"; +import { filterByOption, uncheckAllOptions, filterByVisitStatus } from "./other-filters.js"; +import { changeFilterButtonColor } from "./filter-window.js"; + +//import { makeVoterFeatureCollection } from "./map.js"; + +// Initialize +export let filteredData = undefined; + +// Store a global status: whether any filter is applied +export let filterApplied = { + status: 0, +}; + +// All the filter elements +import { voterInputBoxEl } from "./search-box.js"; +let partyFiltersEl = document.querySelectorAll('.party-checkbox'); +let statusFiltersEl = document.querySelectorAll('.status-checkbox'); +let visitFiltersEl = document.querySelectorAll('.visit-checkbox'); + +/* +A function that includes all the filters +*/ + +function allFilters() { + filterApplied.status = 0; + filteredData = data; + filteredData = filterByNameAddress(filteredData); + filteredData = filterByOption(filteredData, partyFiltersEl, "Party Code"); + filteredData = filterByOption(filteredData, statusFiltersEl, "Voter Status"); + filteredData = filterByVisitStatus(filteredData, visitFiltersEl); + + changeFilterButtonColor(filterApplied.status); + return(filteredData); +} + +function filterAllAndUpdate() { + filteredData = allFilters(); + // Update display + showVotersInList(filteredData); + showVotersOnMap(filteredData); +} + +// Search box filter +voterInputBoxEl.addEventListener("input", filterAllAndUpdate); + +// The other filters + +function prepareFilterSet(filtersEl) { + for (const cb of filtersEl) { + cb.addEventListener('change', ( ) => { + if(cb.checked) { // Meaning user intends to check this one + + // First uncheck everything in the party group + uncheckAllOptions(filtersEl); + // Recheck self if it should be that way + cb.checked = true; + + } else { // Meaning user intends to uncheck this one + cb.checked = false; + } + // Go through all filters + filterAllAndUpdate(); + }); + } +} + +prepareFilterSet(partyFiltersEl); +prepareFilterSet(statusFiltersEl); +prepareFilterSet(visitFiltersEl); + +export { + filterAllAndUpdate, + allFilters, +}; \ No newline at end of file diff --git a/site/js/list-loader.js b/site/js/list-loader.js new file mode 100644 index 0000000..5be9c45 --- /dev/null +++ b/site/js/list-loader.js @@ -0,0 +1,192 @@ +/* +MODULE 1: LIST LOADER +================================================== +This script has the main functionalities of the list loader module +1. Load list on demand +2. Store loaded data in a global variable +3. Update data by getting additional info from the cloud +4. Export data to be used in other scripts +*/ + +import { showVotersOnMap } from "./map.js"; +import { showVotersInList } from './voter-list.js'; +import { loadListNum, saveListNum, updateAdditionalInfo } from "./main.js"; + +/* +Hide and Show List Loader +*/ + +// DOM elements +let listNumberInputEl = document.querySelector("#list-loader-input"); +let loadButtonEl = document.querySelector("#list-loader-load"); +let toolTipEl = document.querySelector("#list-loader-load").querySelector(".tooltiptext"); + +// Create a global object to store current input number +export let inputNumber; + +// This is a global object to store the current list of voters +export let data; +export let votersFeature; + +// A global object to store additional voter data from firebase +export let additionalData = { + info: null, +}; + +/* +FUNCTIONS +*/ + +// Function to change button tooltip depending on input +function errorTooltip(inputNumber) { + let interruptLoad = false; + if(inputNumber.length == 0) { + toolTipEl.innerHTML = `
Empty input
`; + interruptLoad = true; + } else if(inputNumber.length != 4) { + toolTipEl.innerHTML = `
Wrong digits
`; + interruptLoad = true; + } + return interruptLoad; +} + +/* Function to check if fetch is successful. +If so, do fetch; if not, show error on tooltip */ +function checkFetchStatus(resp) { + if(resp.ok) { + return resp.text(); + } else { + // If the file doesn't exist, then show in tooltip + toolTipEl.innerHTML = `
Wrong number
`; + return false; + } +} + +// Function to add short address to each voter in data +// Function is called after data is fetched +function makeShortAddress(data) { + for(let voter of data) { + voter["short_address"] = `${voter["House Number"]} ${voter["Street Name"]}`; + } + return(data); +} + +/* Function: what happens after successful fetch: +1. Make the data using Papaparse, and store it by updating the global variable "data" +2. When having the data, make a geometry object, which has voter IDs, using makeVoterFeatureCollection(data) +3. Show voters on the map +4. Show voters in the list +*/ +function loadVoterData(text) { + if(text == false) { + return; + } + + // Note skipEmptyLines: true; cleaning up the CSV + /* notes with Mjumbe: + Papa Parse is reading the last line of each csv as a person */ + data = {}; + data = Papa.parse(text, { header: true, skipEmptyLines: true }).data; + + // Create new property: combine house number with street name + data = makeShortAddress(data); + // Load additional voter data from firebase, and update data + // And then display the data on the map and in the list + updateAdditionalInfo(inputNumber, data, showVotersInList, showVotersOnMap); + + // // Show voters on the map and list + // showVotersInList(data); + // showVotersOnMap(data); + + + // Save current list number to be loaded the next time + saveListNum(inputNumber); + window.data = data; +} + +/* Function on what happens when clicking on load button +1. Make a path, and fetch +2. Check fetch status using checkFetchStatus +3. If check success, load data using loadVoterData; otherwise, show error tooltip +*/ +function loadByListNumber(number) { + // Update input number, if loading from cloud and it is undefined + inputNumber = number; + let path = 'https://leejere.github.io/js-voter-canvassing/site/data/voters_lists/' + number + '.csv'; + + // Set the input box placeholder + listNumberInputEl.placeholder = `${number}`; + + // Fetch the particular CSV file + fetch(path) + .then(checkFetchStatus) + .then(loadVoterData); +} + +/* Function on what happens when clicking on load button +1. Read the list number +2. If the number is wrong (wrong digits, empty, etc.), go to errorTooltip +3. If the number is fine, go to loadByListNumber; +*/ +function onLoadButtonClick() { + // Get input list number + inputNumber = listNumberInputEl.value.replace(/\s/g, ''); + + if(errorTooltip(inputNumber)) { return } + loadByListNumber(inputNumber); +} + + +// Automatically load list from local storage + +// First try to load list number from local storage +inputNumber = localStorage.getItem("current-list") || "{}"; + +// When local storage has a valid number, use that +if(inputNumber != undefined & inputNumber.length === 4) { + loadByListNumber(inputNumber); +} else { + // If success, load that list from fire store + // If not success, load list 0101 + loadListNum(loadByListNumber, loadByListNumber); +} + + +/* +MAIN +*/ + +// Add event listener to the load button +loadButtonEl.addEventListener("click", onLoadButtonClick); + +// Simulate button click when press enter after input +listNumberInputEl.addEventListener("keypress", (e) => { + if(e.key === "Enter") { + e.preventDefault(); + loadButtonEl.click(); + } +}); + +// Refresh tooltip text when mouse moves out +loadButtonEl.addEventListener("mouseout", ( ) => { + toolTipEl.innerHTML = `
Load List
`; +}); + +/* +FULFILL TEST REQUIREMENT +*/ + +/* +Requirement: +There should be an input element on the page where you can enter a voter file number. +Save the input DOM element in a variable named voterFileInput attached to the global window object. +*/ + +window.voterFileInput = listNumberInputEl; + +/* +Requirement: +There should be a button that will load the voter file number given in the voterFileInput when clicked. Save the button DOM element in a variable named voterFileLoadButton attached to the global window object. +*/ + +window.voterFileLoadButton = loadButtonEl; \ No newline at end of file diff --git a/site/js/main.js b/site/js/main.js new file mode 100644 index 0000000..aeae9e3 --- /dev/null +++ b/site/js/main.js @@ -0,0 +1,128 @@ +/* +MAIN COORDINATION +This script is used for setting up cloud storage + +This part is inspired by Mjumbe Poe +*/ + +// Import functions used to use Firestore cloud storage +// https://firebase.google.com/docs/web/setup#available-libraries for libraries to use +import { initializeApp } from 'https://www.gstatic.com/firebasejs/9.13.0/firebase-app.js'; +import { getFirestore, doc, setDoc, getDoc } from 'https://www.gstatic.com/firebasejs/9.13.0/firebase-firestore.js'; +// My web app's Firebase config +const firebaseConfig = { + apiKey: "AIzaSyDL11M21sMZ6wJ1SxFvEqEvQkipD7DFKjk", + authDomain: "voter-canvassing.firebaseapp.com", + projectId: "voter-canvassing", + storageBucket: "voter-canvassing.appspot.com", + messagingSenderId: "20100623977", + appId: "1:20100623977:web:2d20af24c659bdda17bcf8", + measurementId: "G-BXCLNBWQH6", +}; + +// Initialize firebase +const firebaseApp = initializeApp(firebaseConfig); +const firestoreDb = getFirestore(firebaseApp); + +/* +Save or Load List Number +*/ + +// LOAD LIST NUMBER FROM LAST TIME FROM CLOUD +// This function is called when starting the page and the local storage has no list number stored +// If success, load that list +// If not success, load list 0101 +async function loadListNum(onSuccess, onFailure) { + try { + const notesDoc = doc(firestoreDb, "voter-canvassing", "current-list"); + const result = await getDoc(notesDoc); + console.log("Loaded list number from Firestore!"); + onSuccess(result.data().inputNumber); + } catch { + if(onFailure) { + onFailure("0101"); + } + } +} + +// SAVE CURRENT LIST NUMBER LOCALLY AND TO CLOUD +// This function is called whenever a voter list gets loaded [list-loader.js] +// Current list number automatically saved + +import { additionalData } from "./list-loader.js"; + +async function saveListNum(inputNumber) { + // Save locally + localStorage.setItem("current-list", inputNumber); + + // Save to the cloud + try { + const notesDoc = doc(firestoreDb, "voter-canvassing", "current-list"); + await setDoc(notesDoc, { inputNumber }); + console.log("Saved current list number to cloud!"); + } catch(error) { + console.log(error); + } +} + +/* +Save or Load Edited Voter Info +*/ + +import { fitMap } from "./map.js"; +import { data } from "./list-loader.js"; + +// Update the data loaded from the csv with additional info after getting additional info +function updateVoters(additionalInfo) { + for(let voter of data) { + let thisId = voter["ID Number"]; + if(additionalInfo[thisId]) { + let thisAdditionalInfo = additionalInfo[thisId]; + let keys = Object.keys(thisAdditionalInfo); + for(let key of keys){ + voter[key] = thisAdditionalInfo[key]; + } + } + } +} + +// Function called to get additional voter data from the cloud +// And then use the updated data to update the map and list +async function updateAdditionalInfo(listNumber, data, showOnMap, showInList) { + try { + const voterNotesDoc = doc(firestoreDb, "voter-info", listNumber); + const result = await getDoc(voterNotesDoc); + additionalData.info = result.data(); + updateVoters(result.data(), data); + + } catch { + additionalData.info = {}; + } + showOnMap(data); + showInList(data); + fitMap(); +} + +// Function to save the updated additional info to Firebase +// The firebase db is categorized based on list number +async function saveAdditionalInfo(listNumber, additionalInfo) { + // First save locally + let localPath = `note${listNumber}`; + localStorage.setItem(localPath, JSON.stringify(additionalInfo)); + + try { + const voterNotesDoc = doc(firestoreDb, "voter-info", listNumber); + await setDoc(voterNotesDoc, additionalInfo); + console.log("Saved edited info to the cloud!"); + } catch(error) { + console.log(error); + } +} + +export { + loadListNum, + saveListNum, + updateAdditionalInfo, + updateVoters, + saveAdditionalInfo, +}; \ No newline at end of file diff --git a/site/js/map.js b/site/js/map.js new file mode 100644 index 0000000..13858f9 --- /dev/null +++ b/site/js/map.js @@ -0,0 +1,172 @@ +/* +MODULE 3: Voters display (on the map) +================================================== +Every time a new list is loaded, or when the filter(s) are changed +voters need to be updated both on the map and in the list +This script deals with the map +1. Create geo features from data coming from MODULE 1: LIST-LOADER + or MODULE 2: LIST FILTERS + The created features are ONE MARKER PER ADDRESS +2. Put the geo features on the map, and prepare them with event listeners + The event listeners are to prepare for MODULE 4: VOTER SELECTION +*/ + +import { onSelectAction } from "./selected-voter.js"; + +/* +INITIALIZE BASE MAP TO SHOW +*/ + +// const mapboxAccount = 'mapbox'; +// const mapboxStyle = 'light-v10'; +// const mapboxToken = `pk.eyJ1IjoibGktamllLWZqIiwiYSI6ImNsYWU2dWtqbzByZHYzb3F5dndrZm9vbXoifQ.RhKDjT-7I5oWlzeDbfrI9g`; + +let baseMapEl = document.querySelector("#map-component"); +const mapboxAccount = 'mapbox'; +const mapboxStyle = 'light-v10'; +const mapboxToken = 'pk.eyJ1IjoibGktamllLWZqIiwiYSI6ImNsYWU2dWtqbzByZHYzb3F5dndrZm9vbXoifQ.RhKDjT-7I5oWlzeDbfrI9g'; +export let baseMap = L.map(baseMapEl, { zoomControl: false }).setView([40, -75.15], 11); +L.tileLayer(`https://api.mapbox.com/styles/v1/${mapboxAccount}/${mapboxStyle}/tiles/256/{z}/{x}/{y}@2x?access_token=${mapboxToken}`, { + maxZoom: 20, + attribution: '© Mapbox © OpenStreetMap Improve this map', +}).addTo(baseMap); + +/* +FUNCTION TO MAKE FEATURE COLLECTION FROM DATA +*/ + +// Util function to make sure coords are valid +function coordsAreValid(lng, lat) { + let result = false; + if(typeof(lng) == "number" && typeof(lat) == "number") { + if(lng < -73 && lng > -77 && lat < 41 && lat > 38) { + result = true; + } + } + return result; +} +// Function to make feature collection out of the imported data +// Record only key information +function makeVoterFeatureCollection(thisData) { + + // Construct a geojson empty frame + const voters = { + type: "FeatureCollection", + features: [], + }; + + // Write into geojson + for(let i = 0; i < thisData.length; i++) { + let thisLngLat = thisData[i]["TIGER/Line Lng/Lat"]; + if(typeof(thisLngLat) == "string"){ + + let thisLng = Number(thisLngLat.split(",")[0]); + let thisLat = Number(thisLngLat.split(",")[1]); + + if(coordsAreValid(thisLng, thisLat)) { + voters.features.push( { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [thisLng, thisLat], + }, + "properties": { + "id": thisData[i]["ID Number"], + "last_name": thisData[i]["Last Name"], + "first_name": thisData[i]["First Name"], + "address": thisData[i]["short_address"], + }, + }); + } + } + } + return voters; +} + +/* +FUNCTION TO SHOW VOTERS ON THE MAP +*/ + +// What happens when voter marker gets clicked +function voterMarkerOnClick(event) { + onSelectAction(event.layer.feature.properties.id); +} + +// When showing on the map, we show only one marker per short_address +// This function slices the data to only one voter per short_address +function sliceByKey(data, key) { + let dataUniqueAddress = data.reduce((result, thisItem) => { + let thisCategory = thisItem[key]; + // Add item if its key is not added yet + if(result.find(item => item[key] === thisCategory) == undefined) { + result.push(thisItem); + } else { + // empty + } + return result; + }, []); + + return dataUniqueAddress; +} + +function showVotersOnMap(thisData) { + let dataUniqueAddress = sliceByKey(thisData, "short_address"); + let voterFeatures = makeVoterFeatureCollection(dataUniqueAddress); + if(baseMap.voterLayers !== undefined) { + baseMap.removeLayer(baseMap.voterLayers); + } + baseMap.voterLayers = L.geoJSON(voterFeatures, { + pointToLayer: (point, latLng) => L.circleMarker(latLng), + style: { + radius: 7, + color: "#999999", + stroke: true, + opacity: 0.5, + weight: 2, + }, + }) + .on("click", voterMarkerOnClick) + .addTo(baseMap); +} + +/* +FUNCTION THAT ZOOM TO CURRENT BOUNDS +*/ + +// Adjust bounds +// We have a voter list which blocks part of the view port +// Therefore we need to move the bounds a bit northward +function getAdjustedBounds(layers) { + let bounds = layers.getBounds(); + let adjustRatio = 0.15; + const northLat = bounds._northEast.lat; + const southLat = bounds._southWest.lat; + const latRange = northLat - southLat; + const adjustAmount = latRange * adjustRatio; + bounds._southWest.lat = southLat + adjustAmount; + bounds._northEast.lat = northLat + adjustAmount; + + return bounds; +} + +// Fit map to bounds +function fitMap() { + // Fit voters on the map + let bounds = getAdjustedBounds(baseMap.voterLayers); + baseMap.fitBounds(bounds); +} + + +export { + showVotersOnMap, + fitMap, + voterMarkerOnClick, + makeVoterFeatureCollection, +}; + +/* Requirement: +A leaflet map to show voter locations; +The leaflet map object should be available on the global window; +*/ + +window.voterMap = baseMap; \ No newline at end of file diff --git a/site/js/other-filters.js b/site/js/other-filters.js new file mode 100644 index 0000000..37bc729 --- /dev/null +++ b/site/js/other-filters.js @@ -0,0 +1,53 @@ +/* +MODULE 2: LIST FILTERS (COMPONENT) +================================================== +This script contains the functions to filter voters by the other filters +Refer to [list-filters.js] to see how all the filters work together +*/ + +import { filterApplied } from "./list-filters.js"; + +// Function to filter data by party +function filterByOption(data, filtersEl, voterProperty) { + let filtered = data; + for (const checkbox of filtersEl) { + if (checkbox.checked) { + filtered = filtered.filter((voter) => voter[voterProperty] === checkbox.value); + + // If anything is checked... + filterApplied.status = 1; + } + } + return filtered; +} + +function filterByVisitStatus(data, filtersEl) { + let filtered = data; + for (const checkbox of filtersEl) { + if (checkbox.checked) { + filtered = filtered.filter((voter) => { + let thisStatus; + if(voter["canvass-status"]) { + thisStatus = voter["canvass-status"]; + } else { + thisStatus = "pending"; + } + return thisStatus === checkbox.value; + }); + } + } + return filtered; +} + +// Function to uncheck all checkboxes in a group +function uncheckAllOptions(optionsEl) { + for(const cb of optionsEl) { + cb.checked = false; + } +} + +export { + filterByOption, + uncheckAllOptions, + filterByVisitStatus, +}; \ No newline at end of file diff --git a/site/js/search-box.js b/site/js/search-box.js new file mode 100644 index 0000000..bfd5f0f --- /dev/null +++ b/site/js/search-box.js @@ -0,0 +1,45 @@ +/* +MODULE 2: LIST FILTERS (COMPONENT) +================================================== +This script contains the functions to filter voters by search box input +Refer to [list-filters.js] to see how all the filters work together +*/ + +export let voterInputBoxEl = document.querySelector('#search-box-input'); + +// Step in filter by search box; match if an individual voter fulfills the filter +function matchInput(voter, inputText) { + + // Split the input text into individual words + const keyWords = inputText.split(" ").map(s => s.replace(/\s/g, "")); + + // Match with first name, last name, and short address + const firstName = voter['First Name'].toLowerCase(); + const lastName = voter['Last Name'].toLowerCase(); + const shortAddress = voter["short_address"].toLowerCase(); + + // Rule 1: each word should be matched + // Rule 2: either match with first name, or last name, or address + let includesKeyword = keyWords.map(word => { + let eachWordMatch = firstName.includes(word) || lastName.includes(word) || shortAddress.includes(word); + return eachWordMatch; + }); + return !includesKeyword.includes(false); +} + +// Filter by the input of search box +// Search by first name or last name or short address +function filterByNameAddress(data) { + let filtered = data; + const inputText = voterInputBoxEl.value.toLowerCase(); + + filtered = filtered.filter((voter) => { + let isMatched = matchInput(voter, inputText); + return isMatched; + }); + return filtered; +} + +export { + filterByNameAddress, +}; \ No newline at end of file diff --git a/site/js/selected-voter.js b/site/js/selected-voter.js new file mode 100644 index 0000000..facf448 --- /dev/null +++ b/site/js/selected-voter.js @@ -0,0 +1,143 @@ +/* +MODULE 4: VOTER SELECTION +================================================== +1. Use global variable selectedVoter to store the currently voter selected. + If no voter is selected at the moment, variable is undefined +2. Whenever a click on the voter list or on the map happens: + 2.1 Check if newly selected is the same as the current. + 2.1.1 If same, update selectedVoter to undefined -> use unhighlightVoter function + 2.1.2 If same, update selectedVoter. unhighlight current voter, highlight new voter + 2.2 Show this voter's information on the edit panels + +Also does miscellaneous work, i.e., pan to voter on selection +*/ + +import { voterListItemsEl } from "./voter-list.js"; +import { baseMap } from "./map.js"; +import { showVotersOnMap } from "./map.js"; +import { data } from "./list-loader.js"; +import { filteredData } from "./list-filters.js"; +import { voterListExpandButtonEl, voterListContainerEl, onExpandButtonClick } from "./list-expand.js"; +import { bottomComponentEl } from "./hide-show.js"; +import { displayInfo } from "./show-info.js"; + + +/* +The unhighlight function is made of two parts: +unhighlight voter in the list, and unhighlight voter on the map +*/ + +// Initialize the variable to store which voter is currently selected +export let selectedVoter = undefined; + +// Unhighlight all the voters +function unhighlightVoterInList() { + for(const listItem of voterListItemsEl) { + listItem.className = "list-voter"; + } +} + +function unhighlightVoterOnMap() { + // Re-run showVotersOnMap + // Either use data or filteredData (if it's defined) + if(filteredData !== undefined) { + showVotersOnMap(filteredData); + } else { + showVotersOnMap(data); + } +} + +function unhighlightVoter() { + unhighlightVoterInList(); + unhighlightVoterOnMap(); +} + +/* +The highlight function is made of two parts: +highlight voter in list, and highlight voter on map +*/ + +function highlightVoterInList(thisId) { + // Initialize: remove all existing highlighted voters, if any + unhighlightVoterInList(); + // Highlight selected voter + let thisListItem = Array.from(voterListItemsEl).find(item => item.title == thisId); + + // Make this voter highlighted in color + thisListItem.classList.add("selected"); + + thisListItem.scrollIntoView({ + behavior: "smooth", + block: "start", + }); +} + +function highlightVoterOnMap(thisId) { + + // First remove the existing highlight voter, if existing + unhighlightVoterOnMap(); + + // As the map doesn't have every voter's ID, we need to find the same address + let thisAddress = data.find(item => item["ID Number"] === thisId).short_address; + + // Then find the marker to be highlighted + // And then highlight it + for(let entry of Object.entries(baseMap.voterLayers._layers)) { + if(entry[1].feature.properties.address == thisAddress) { + entry[1].setStyle({ + radius: 10, + color: "#0d59a9", + fillColor: "#0d59a9", + stroke: true, + opacity: 1, + fillOpacity: 0.7, + weight: 2, + }) + .bringToFront(); + + // Pan to the selected feature marker + baseMap.panTo(entry[1]._latlng); + break; + } + } +} + +function highlightVoter(thisId) { + highlightVoterInList(thisId); + highlightVoterOnMap(thisId); +} + +// Main. This function gets called whenever a "select" action is made +// i.e., clicking on a list item, or on a map marker + +// Either highlight new voter and update selectedVoter, or unhighlight current voter +function onSelectAction(thisId) { + + // If clicking on the same voter, unhighlight them + if(selectedVoter != undefined && thisId == selectedVoter) { + unhighlightVoter(); + selectedVoter = undefined; + // Show info edit panel + bottomComponentEl.style.transform = "translateX(-40em)"; + + } else { + // If the list is currently expanded, unexpand it + if(voterListExpandButtonEl.expandStatus == 1) { + onExpandButtonClick(voterListExpandButtonEl, voterListContainerEl); + } + highlightVoter(thisId); + // Update stored voter ID + selectedVoter = thisId; + + // Show info edit panel (if not already) + bottomComponentEl.style.transform = "translateX(0em)"; + + // Display voter info + displayInfo(thisId); + } +} + +export { + onSelectAction, + highlightVoter, +}; \ No newline at end of file diff --git a/site/js/show-info.js b/site/js/show-info.js new file mode 100644 index 0000000..5a17287 --- /dev/null +++ b/site/js/show-info.js @@ -0,0 +1,353 @@ +/* +SHOW INFO WHEN A VOTER IS SELECTED +*/ + +import { data } from "./list-loader.js"; +import electionLookup from "../data/election_lookup.js"; +import { htmlToElement } from './htmlelement.js'; +import { highlightOption, prepareOption, prepareInput } from "./update-info.js"; +import partyLookup from '../data/political_party_lookup.js'; +import { getPartyColor } from "./voter-list.js"; + +const electionDict = electionLookup[0]; +const partyDict = partyLookup[0]; +window.partyDict = partyDict; + +let basicInfoNameEl = document.querySelector(".info-panel-name"); +let basicInfoAddressEl = document.querySelector(".info-panel-address"); + +let electionListEl = document.querySelector("#voting-history"); + +/* BASIC INFO PANEL */ + +function displayName(thisVoter) { + const name = `${thisVoter["First Name"]} ${thisVoter["Last Name"]}`; + basicInfoNameEl.innerHTML = name; +} + +function displayAddress(thisVoter) { + const address = thisVoter.short_address; + basicInfoAddressEl.innerHTML = address; + +} + +function displayParty(thisVoter) { + const partyCode = thisVoter["Party Code"]; + const party = partyDict[partyCode]; + const partyCodeEl = document.querySelector("#info-panel-party-set").getElementsByClassName("list-icon")[0]; + partyCodeEl.innerHTML = partyCode; + + const partyColor = getPartyColor(partyCode); + + partyCodeEl.classList.remove(partyCodeEl.classList.item(1)); + partyCodeEl.classList.add(partyColor); + + const partyNameEl = document.querySelector("#info-panel-party-set").getElementsByClassName("icon-subtitle")[0]; + partyNameEl.innerHTML = party; +} + +function displayCanvassStatus(thisVoter) { + // Get the canvass state + let thisStatus = "pending"; + if(thisVoter["canvass-status"]) { + thisStatus = thisVoter["canvass-status"]; + } + let optionIdSelector = `#icon-canvass-${thisStatus}`; + + // Store current voter id in the container DOM object + document.querySelector("#icon-canvass").currentVoterId = thisVoter["ID Number"]; + + // Highlight option + highlightOption("#icon-canvass", optionIdSelector); + + // Prepare: add event listeners for them to be clicked on + prepareOption("#icon-canvass"); +} + +function displayActiveness(thisVoter) { + const activeCode = thisVoter["Voter Status"]; + const activeName = activeCode == "A" ? "ACTIVE" : "INACTIVE"; + const activeCodeEl = document.querySelector("#info-panel-active-set").getElementsByClassName("list-icon")[0]; + const activeNameEl = document.querySelector("#info-panel-active-set").getElementsByClassName("icon-subtitle")[0]; + + const activeVoterIcon = `ballot`; + const inactiveVoterIcon = `close`; + const voterStatusIcon = activeCode == "A" ? activeVoterIcon : inactiveVoterIcon; + + + activeCodeEl.innerHTML = voterStatusIcon; + + activeNameEl.innerHTML = activeName; +} + +function displayMailGeneral(thisVoter) { + // Get DOM objects + const mailSet = document.querySelector("#info-panel-mail-set"); + mailSet.style.display = "none"; + + const mailCodeEl = document.querySelector("#info-panel-mail-set").getElementsByClassName("list-icon")[0]; + const mailNameEl = document.querySelector("#info-panel-mail-set").getElementsByClassName("icon-subtitle")[0]; + + + // Construct some HTML elements + const mailReceivedHTML = `mark_email_read`; + const mailNotReceivedHTML = `unsubscribe`; + const voteInPersonHTML = `footprint`; + + // Only display if such info exists + if(thisVoter["mail"]) { + // Only show if there's this thing + mailSet.style.display = "flex"; + + let mailBallotInfo = thisVoter["mail"]; + if(mailBallotInfo === "received") { + mailCodeEl.innerHTML = mailReceivedHTML; + mailNameEl.innerHTML = "Received mail ballot"; + } else if(mailBallotInfo === "awaits") { + mailCodeEl.innerHTML = mailNotReceivedHTML; + mailNameEl.innerHTML = "Awaits mail ballot"; + } else { + mailCodeEl.innerHTML = voteInPersonHTML; + mailNameEl.innerHTML = "Will vote in person"; + } + } +} + +function displayPlanGeneral(thisVoter) { + // Get DOM objects + const planSet = document.querySelector("#info-panel-plan-set"); + planSet.style.display = "none"; + + const planCodeEl = document.querySelector("#info-panel-plan-set").getElementsByClassName("list-icon")[0]; + const planNameEl = document.querySelector("#info-panel-plan-set").getElementsByClassName("icon-subtitle")[0]; + + // Construct some HTML elements + const planYes = `task_alt`; + const planNo = `unpublished`; + + // Only display if such info exists + if(thisVoter["plan"]) { + // Only show if there's this thing + planSet.style.display = "flex"; + + let planInfo = thisVoter["plan"]; + if(planInfo === "yes") { + planCodeEl.innerHTML = planYes; + planNameEl.innerHTML = "Planned"; + } else { + planCodeEl.innerHTML = planNo; + planNameEl.innerHTML = "Not planned"; + } + } +} + +/* VOTING HISTORY */ + +// Function to construct an array sorted by date, with all the election info regarding this voter +function getVotingHistory(thisVoter) { + // Initiate an array to store election info regarding a particular voter + let thisVotingHistory = []; + for(let i=1; i<=40; i++){ + // Get election info from dictionary + let thisElection = electionDict[`Election ${i}`]; + + if(thisElection) { + let thisElectionName = thisElection.description.substring(5); // Name without year + + // Dates + let thisElectionDate = thisElection.date; + let thisElectionYear = thisElectionDate.substring(6); + let thisElectionMonth = thisElectionDate.substring(0, 2); + let thisElectionDay = thisElectionDate.substring(3, 5); + // Concat dates for sorting + let thisElectionDateCompare = thisElectionYear.concat(thisElectionMonth).concat(thisElectionDay); + + let thisElectionParty = thisVoter[`Election ${i} Party`]; + let thisElectionMethod = thisVoter[`Election ${i} Vote Method`]; + + if(thisElectionParty) { + thisVotingHistory.push({ + "name": thisElectionName, + "date": thisElectionDate, + "date-compare": thisElectionDateCompare, + "party": thisElectionParty, + "method": thisElectionMethod, + }); + } + } + } + return thisVotingHistory.sort((a, b) => (a["date-compare"] > b["date-compare"]) ? -1 : 1); +} + +// Voting history on the second panel +function displayVotingHistory(thisVoter) { + let thisVotingHistory = getVotingHistory(thisVoter); + + // Initialize + electionListEl.innerHTML = ""; + + // Display preparation: all the HTML components + for(let election of thisVotingHistory) { + let thisYearMonth = `${election["date"].substring(0, 2)}/${election["date"].substring(6)}`; + + let thisNameHTML = `
${election["name"]}
`; + let thisDateHTML = `
${thisYearMonth}
`; + + let partyColor = getPartyColor(election["party"]); + let thisPartyHTML = `
+
${election["party"]}
+
`; + let thisMethodHTML = `
${election["method"]}
`; + const electionItemEl = htmlToElement(` +
  • + ${thisDateHTML} ${thisNameHTML} ${thisPartyHTML} ${thisMethodHTML} +
  • + `); + electionListEl.append(electionItemEl); + } + +} + +/* PER CANVASSING RECORD */ + +// Display options for Voter Language +function displayLanguageOptions(thisVoter) { + let langInput = document.querySelector("#icon-lang-input").getElementsByTagName("input")[0]; + langInput.placeholder = "Other..."; + // Get the canvass state + let thisStatus = undefined; + // Store current voter id in the container DOM object + // Do this because we have to store the info associated with the voter + document.querySelector("#icon-lang").currentVoterId = thisVoter["ID Number"]; + + thisStatus = thisVoter["language"]; + let optionIdSelector = `#icon-lang-${thisStatus}`; + // Highlight option (thisStatus undefined situation dealt with there) + highlightOption("#icon-lang", optionIdSelector); + + // If not the three languages, show in the input box + if(thisStatus != undefined && !["english", "spanish", "chinese"].includes(thisStatus)) { + langInput.placeholder = thisStatus; + } + + // Prepare: add event listeners for them to be clicked on + prepareOption("#icon-lang"); + prepareInput("#icon-lang"); +} + +// Display options for Voter Plan +function displayPlanOptions(thisVoter) { + + // Get the canvass state + let thisStatus = undefined; + // Store current voter id in the container DOM object + // Do this because we have to store the info associated with the voter + document.querySelector("#icon-plan").currentVoterId = thisVoter["ID Number"]; + + thisStatus = thisVoter["plan"]; + let optionIdSelector = `#icon-plan-${thisStatus}`; + + // Highlight option (thisStatus undefined situation dealt with there) + highlightOption("#icon-plan", optionIdSelector); + + // Prepare: add event listeners for them to be clicked on + prepareOption("#icon-plan"); +} + +// Display options for Voter mailing option +function displayMailOptions(thisVoter) { + + // Get the canvass state + let thisStatus = undefined; + // Store current voter id in the container DOM object + // Do this because we have to store the info associated with the voter + document.querySelector("#icon-mail").currentVoterId = thisVoter["ID Number"]; + + thisStatus = thisVoter["mail"]; + let optionIdSelector = `#icon-mail-${thisStatus}`; + + // Highlight option (thisStatus undefined situation dealt with there) + highlightOption("#icon-mail", optionIdSelector); + + // Prepare: add event listeners for them to be clicked on + prepareOption("#icon-mail"); +} + +// Display options for Voter Language +function displayWhoOptions(thisVoter) { + let whoInput = document.querySelector("#icon-who-input").getElementsByTagName("input")[0]; + whoInput.placeholder = "Other..."; + + // Get the canvass state + let thisStatus = undefined; + // Store current voter id in the container DOM object + // Do this because we have to store the info associated with the voter + document.querySelector("#icon-who").currentVoterId = thisVoter["ID Number"]; + + thisStatus = thisVoter["who"]; + let optionIdSelector = `#icon-who-${thisStatus}`; + // Highlight option (thisStatus undefined situation dealt with there) + highlightOption("#icon-who", optionIdSelector); + + // If not the two parties, show in the input box + if(thisStatus != undefined && !["d", "r"].includes(thisStatus)) { + whoInput.placeholder = thisStatus; + } + + // Prepare: add event listeners for them to be clicked on + prepareOption("#icon-who"); + prepareInput("#icon-who"); +} + +// Display extra notes +function displayExtraNotes(thisVoter) { + let extraNotesEl = document.querySelector("#other-notes-input").getElementsByTagName("input")[0]; + if(thisVoter["notes"]) { + extraNotesEl.placeholder = thisVoter["notes"]; + } +} + +/* UTIL: FIND THE VOTER DATA ENTRY USING VOTER ID */ + +// Find voter: takes voter ID and outputs comprehensive voter data +function findThisVoter(thisId) { + for(let voter of data) { + if(voter["ID Number"] === thisId) { + return(voter); + } + } +} + +function displayInfo(thisId) { + // Find this voter's data + let thisVoter = findThisVoter(thisId); + + // Display in the basic info panel + displayName(thisVoter); + displayAddress(thisVoter); + displayActiveness(thisVoter); + displayParty(thisVoter); + displayCanvassStatus(thisVoter); + displayMailGeneral(thisVoter); + displayPlanGeneral(thisVoter); + + // Voting history part + displayVotingHistory(thisVoter); + + // Canvass recording part + displayLanguageOptions(thisVoter); + displayPlanOptions(thisVoter); + displayMailOptions(thisVoter); + displayWhoOptions(thisVoter); + + // Show notes if any + displayExtraNotes(thisVoter); +} + +export { + displayInfo, + highlightOption, + displayPlanGeneral, + displayMailGeneral, + findThisVoter, +}; \ No newline at end of file diff --git a/site/js/update-info.js b/site/js/update-info.js new file mode 100644 index 0000000..d4fd0bd --- /dev/null +++ b/site/js/update-info.js @@ -0,0 +1,216 @@ +import { additionalData } from "./list-loader.js"; +import { updateVoters, saveAdditionalInfo } from "./main.js"; +import { data } from "./list-loader.js"; +import { showVotersOnMap } from "./map.js"; +import { highlightVoter } from "./selected-voter.js"; +import { allFilters } from "./list-filters.js"; +import { inputNumber } from "./list-loader.js"; +import { displayPlanGeneral, displayMailGeneral, findThisVoter } from "./show-info.js"; +import { selectedVoter } from "./selected-voter.js"; + +// Initiate properties on DOM elements to store unsaved information +// Add names and such +let saveItemsSelectorsList = ["#icon-canvass", "#icon-lang", "#icon-plan", "#icon-mail", "#icon-who"]; +let recordItemsList = ["canvass-status", "language", "plan", "mail", "who"]; + +for(let i = 0; i < saveItemsSelectorsList.length; i++) { + let selector = saveItemsSelectorsList[i]; + let name = recordItemsList[i]; + let itemEl = document.querySelector(selector); + itemEl.recordItemName = name; + itemEl.currentVoterId = undefined; + itemEl.unsavedSelection = undefined; + console.log(name); +} + + +// Function to highlight an option given selectors on click +function highlightOption(groupIdSelector, optionIdSelector) { + // First remove all highlighted classes and reset everything to default gray color + let iconGroupEl = document.querySelector(groupIdSelector).querySelectorAll(".icon-set"); + for(let item of iconGroupEl) { + item.classList.remove("highlighted"); + //item.classList.add("icon-no-color"); + } + + // DOM object of the icon to be highlighted + if(document.querySelector(groupIdSelector).querySelector(optionIdSelector)) { + let highlightIconSet = document.querySelector(groupIdSelector).querySelector(optionIdSelector); + highlightIconSet.classList.add("highlighted"); + } +} + +// Prepare the detailed info display for the selected voter +// 1. highlight options, 2. add event listeners +// Everytime a click happens, store the clicked item in the container DOM +// .. until pushing the save button +function prepareOption(groupIdSelector) { + + // DOM: all the icon sets in a particular group + let iconGroupEl = document.querySelector(groupIdSelector).querySelectorAll(".icon-set"); + // Add event listener + for(let item of iconGroupEl) { + item.addEventListener("click", ( ) => { + + // First, highlight that option + highlightOption(groupIdSelector, `#${item.id}`); + + // If this parent has an input, clear that input box + if(document.querySelector(groupIdSelector).querySelector("input")) { + document.querySelector(groupIdSelector).querySelector("input").value = ""; + } + + // Find substring item.id to get the real term + let lastDashLocation = item.id.lastIndexOf("-"); + + // Then, record the click + let storageEl = document.querySelector(groupIdSelector); + storageEl.unsavedSelection = item.id.substring(lastDashLocation + 1); + }); + } +} + +// Likewise, also prepare the input box to be listened +function prepareInput(groupIdSelector) { + let inputEl = document.querySelector(groupIdSelector).querySelector("input"); + inputEl.addEventListener("input", ( ) => { + + // When input, unhighlight all other options + let iconGroupEl = document.querySelector(groupIdSelector).querySelectorAll(".icon-set"); + for(let item of iconGroupEl) { + item.classList.remove("highlighted"); + item.classList.add("icon-no-color"); + } + + // Record the input + let customInput = inputEl.value; + let storageEl = document.querySelector(groupIdSelector); + storageEl.unsavedSelection = customInput; + + }); +} + +// After saving data, update Filtered data +// At the same time, update display on the map and in the list +function updateDataOnStatusSave(data, currentVoterId, status) { + let filteredData = allFilters(); + // showVotersInList(filteredData); + showVotersOnMap(filteredData); + + // Update the display of this particular voter + let voterListItemsEl = document.querySelectorAll(".list-voter"); + // First find out which icon it should be + let canvassStatusIcon = `hourglass_top`; + if(status) { + if(status === "completed") { + canvassStatusIcon = `task_alt`; + } else if(status === "awaits") { + canvassStatusIcon = `timeline`; + } + } + // Then find the corresponding DOM and update it + for(let thisListItem of voterListItemsEl) { + if(thisListItem.title == currentVoterId) { + thisListItem.getElementsByTagName("div")[1].innerHTML = canvassStatusIcon; + } + } + + // Then rehighlight this voter + highlightVoter(currentVoterId); +} + +// Called on success of updating voter canvass status +function onUpdateStatusSuccess(canvassStatusSaveButtonEl) { + canvassStatusSaveButtonEl.classList.add("canvass-status-save-toast"); + canvassStatusSaveButtonEl.innerHTML = "Updated status!"; + setTimeout(( ) => { + canvassStatusSaveButtonEl.classList.remove("canvass-status-save-toast"); + canvassStatusSaveButtonEl.innerHTML = "Update status"; + }, 1500); +} + +function onFinalSaveSuccess(finalSaveButtonEl) { + finalSaveButtonEl.classList.add("final-save-toast"); + finalSaveButtonEl.innerHTML = "Saved!"; + setTimeout(( ) => { + finalSaveButtonEl.classList.remove("final-save-toast"); + finalSaveButtonEl.innerHTML = "Save"; + }, 1500); +} + +// Save current canvass status on click +const canvassStatusSaveButtonEl = document.querySelector("#canvass-status-save"); +canvassStatusSaveButtonEl.addEventListener("click", ( ) => { + let unsavedSelection = document.querySelector("#icon-canvass").unsavedSelection; + let currentVoterId = document.querySelector("#icon-canvass").currentVoterId; + if(unsavedSelection) { + // Initiate, if no additional info has been recorded for this voter + if(!additionalData.info[currentVoterId]) { + additionalData.info[currentVoterId] = {}; + } + additionalData.info[currentVoterId]["canvass-status"] = unsavedSelection; + + // Then, update Data + updateVoters(additionalData.info); + + // Then, update map and list + updateDataOnStatusSave(data, currentVoterId, unsavedSelection); + + // Then, send the updated info to the cloud + saveAdditionalInfo(inputNumber, additionalData.info); + + // Then, show a toast on the button + onUpdateStatusSuccess(canvassStatusSaveButtonEl); + } +}); + +// Save all on click +export const finalSaveButtonEl = document.querySelector("#final-save"); +finalSaveButtonEl.addEventListener("click", ( ) => { + let currentVoterId; + + // First update the extra notes, if any + let extraNotesEl = document.querySelector("#other-notes-input").getElementsByTagName("input")[0]; + if(extraNotesEl.value) { + // Initiate, if no additional info has been recorded for this voter + if(!additionalData.info[selectedVoter]) { + additionalData.info[selectedVoter] = {}; + } + additionalData.info[selectedVoter]["notes"] = extraNotesEl.value; + } + + for(let selector of saveItemsSelectorsList) { + let itemEl = document.querySelector(selector); + let recordItemName = itemEl.recordItemName; + currentVoterId = itemEl.currentVoterId; + let unsavedSelection = itemEl.unsavedSelection; + + if(currentVoterId && unsavedSelection) { + // Initiate, if no additional info has been recorded for this voter + if(!additionalData.info[currentVoterId]) { + additionalData.info[currentVoterId] = {}; + } + // Add to temp save data + additionalData.info[currentVoterId][recordItemName] = unsavedSelection; + } + } + // Update data + updateVoters(additionalData.info); + + // Update some general display + let thisVoter = findThisVoter(currentVoterId); + // Update display on the top + displayPlanGeneral(thisVoter); + displayMailGeneral(thisVoter); + + // Then, send the updated info to the cloud + saveAdditionalInfo(inputNumber, additionalData.info); + + onFinalSaveSuccess(finalSaveButtonEl); +}); + +export { + highlightOption, + prepareOption, + prepareInput, +}; \ No newline at end of file diff --git a/site/js/voter-list.js b/site/js/voter-list.js new file mode 100644 index 0000000..db5a23b --- /dev/null +++ b/site/js/voter-list.js @@ -0,0 +1,185 @@ +/* +MODULE 4: Voters display (on the map) +================================================== +Every time a new list is loaded, or when the filter(s) are changed +voters need to be updated both on the map and in the list +This script deals with the list +1. Create HTML elements of the voters to be added to the voter list +2. Note that these are group by ADDRESS +3. Add event listener to prepare for MODULE 4: VOTER SELECTION +*/ + +import { htmlToElement } from './htmlelement.js'; +import { onSelectAction } from "./selected-voter.js"; +import { voterListExpandButtonEl } from "./list-expand.js"; + +// DOM obj of voter list +let voterList = document.querySelector('#voter-list'); + +// DOM obj of each voter list item +// Not defined yet; defined in showVotersInList; +export let voterListItemsEl; + +// Function to turn array of voters into a two-level array grouped by address +// Use reduce; Initiate address if not already; append voter to address +function groupByKey(data, key) { + let dataGroupedByAddress = data.reduce((grouped, thisItem) => { + let thisCategory = thisItem[key]; + grouped[thisCategory] = grouped[thisCategory] || []; // Initialize if not existing + grouped[thisCategory].push(thisItem); + return grouped; + }, {}); + + return dataGroupedByAddress; +} + +// The output voters are grouped by their addresses +// Function to show address +function addAddressToList(address) { + const addressEl = htmlToElement(` +
  • + ${address} +
  • + `); + voterList.append(addressEl); +} + +// Function: prepare the voterlist for the next module (voter selection) +function listPrepare(voterListItemsEl) { + for(const thisListItem of voterListItemsEl) { + thisListItem.addEventListener("click", () => { + let thisId = thisListItem.title; + onSelectAction(thisId); + }); + } +} + +// Function to get the canvassing status (visited, pendign, etc.) for each voter +function getCanvassStatusIcon(voter) { + // Default to hourglass (pending) + let canvassStatusIcon = `hourglass_top`; + if(voter["canvass-status"]) { + if(voter["canvass-status"] === "completed") { + canvassStatusIcon = `task_alt`; + } else if(voter["canvass-status"] === "awaits") { + canvassStatusIcon = `timeline`; + } + } + return canvassStatusIcon; +} + +// Function to get the voter status (active or inactive) for each voter +function getVoterStatusIcon(voter) { + const activeVoterIcon = `ballot`; + const inactiveVoterIcon = `close`; + + const voterStatusIcon = voter["Voter Status"] == "A" ? activeVoterIcon : inactiveVoterIcon; + + return voterStatusIcon; +} + +// Function to get the party for each voter +function getPartyColor(party) { + const democratColor = "icon-democrat-color"; + const republicanColor = "icon-republican-color"; + const otherPartyColor = "icon-oth-party-color"; + + let partyColor; + switch(party) { + case "D": + partyColor = democratColor; + break; + case "R": + partyColor = republicanColor; + break; + default: + partyColor = otherPartyColor; + } + + return partyColor; +} + +// Function to show voters by each address +function addVotersByAddress(votersByThisAddress) { + for(const voter of votersByThisAddress) { + + // Get current voter ID + // const voterId = voter["ID Number"]; + + // Get voter status icon + const voterStatusIcon = getVoterStatusIcon(voter); + + // Check canvassing status of this voter and get the icon + const canvassStatusIcon = getCanvassStatusIcon(voter); + + // Check party affiliation + const party = voter["Party Code"]; + const partyColor = getPartyColor(voter["Party Code"]); + + const voterEl = htmlToElement(` +
  • +
    ${voter["First Name"]} ${voter["Last Name"]}
    +
    ${canvassStatusIcon}
    +
    ${voterStatusIcon}
    +
    ${party}
    +
  • + `); + voterList.append(voterEl); + } +} + +// Function to decide whether to show voter list's expand button +function showHideExpandButton(innerSelector, outerSelector, buttonEl) { + let voterListHeight = document.querySelector(innerSelector).offsetHeight; + let voterContainerHeight = document.querySelector(outerSelector).offsetHeight; + if(voterListHeight > voterContainerHeight){ + buttonEl.style.display = "block"; + } else { + buttonEl.style.display = "none"; + } +} + +function showVotersInList(data) { + // First empty out existing voter list + voterList.innerHTML = ''; + + let dataGroupedByAddress = groupByKey(data, "short_address"); + let addressKeys = Object.keys(dataGroupedByAddress); + + for(const address of addressKeys) { + addAddressToList(address); + let votersByThisAddress = dataGroupedByAddress[address]; + addVotersByAddress(votersByThisAddress); + } + voterListItemsEl = document.querySelectorAll(".list-voter"); + listPrepare(voterListItemsEl); + + // Only show expand button when necessary + showHideExpandButton("#voter-list", "#voter-list-container", voterListExpandButtonEl); + + // Scroll back to top + let scrollContainer = document.querySelector("#voter-list-component").querySelector(".scroll-container"); + scrollContainer.scrollTop = 0; +} + +export { + showVotersInList, + voterList, + getPartyColor, + showHideExpandButton, + getVoterStatusIcon, +}; + +/* +Requirement: +The list's DOM element should be available on the global +*/ + +window.voterList = voterList; + +/* +Requirement: +Wrap each voter's name in an element with the class `voter-name`. +Wrap addresses in an element with the address `voter-address`. +Make sure the class names match. +*/ \ No newline at end of file