From fb761b1c5a2d465518d0fcf81102f6fbd471dd2d Mon Sep 17 00:00:00 2001 From: Michael Dirolf Date: Thu, 25 Jun 2020 13:58:49 -0400 Subject: [PATCH] Remove old crossword/ create-react-app --- app/firestore.rules | 86 +- .../src => app/scripts}/makeAdmin.ts | 0 .../src => app/scripts}/migrate.ts | 0 crossword/.env.development | 1 - crossword/.firebaserc | 5 - crossword/.gitignore | 26 - crossword/README.md | 36 - crossword/__mocks__/workerMock.ts | 1 - crossword/admin_scripts/package-lock.json | 1488 -- crossword/admin_scripts/package.json | 14 - crossword/admin_scripts/sentryRelease.js | 24 - crossword/admin_scripts/tsconfig.json | 19 - crossword/admin_scripts/tslint.json | 18 - crossword/firestore.indexes.json | 192 - crossword/firestore.rules | 85 - crossword/package.json | 79 - crossword/public/apple-icon-120.png | Bin 537 -> 0 bytes crossword/public/apple-icon-152.png | Bin 584 -> 0 bytes crossword/public/apple-icon-167.png | Bin 289 -> 0 bytes crossword/public/apple-icon-180.png | Bin 600 -> 0 bytes crossword/public/apple-splash-1125-2436.png | Bin 3172 -> 0 bytes crossword/public/apple-splash-1136-640.png | Bin 1370 -> 0 bytes crossword/public/apple-splash-1242-2208.png | Bin 3421 -> 0 bytes crossword/public/apple-splash-1242-2688.png | Bin 4001 -> 0 bytes crossword/public/apple-splash-1334-750.png | Bin 1633 -> 0 bytes crossword/public/apple-splash-1536-2048.png | Bin 3451 -> 0 bytes crossword/public/apple-splash-1668-2224.png | Bin 3783 -> 0 bytes crossword/public/apple-splash-1668-2388.png | Bin 4007 -> 0 bytes crossword/public/apple-splash-1792-828.png | Bin 1894 -> 0 bytes crossword/public/apple-splash-2048-1536.png | Bin 3240 -> 0 bytes crossword/public/apple-splash-2048-2732.png | Bin 5185 -> 0 bytes crossword/public/apple-splash-2208-1242.png | Bin 2765 -> 0 bytes crossword/public/apple-splash-2224-1668.png | Bin 3514 -> 0 bytes crossword/public/apple-splash-2388-1668.png | Bin 3596 -> 0 bytes crossword/public/apple-splash-2436-1125.png | Bin 2364 -> 0 bytes crossword/public/apple-splash-2688-1242.png | Bin 2832 -> 0 bytes crossword/public/apple-splash-2732-2048.png | Bin 4423 -> 0 bytes crossword/public/apple-splash-640-1136.png | Bin 1240 -> 0 bytes crossword/public/apple-splash-750-1334.png | Bin 1588 -> 0 bytes crossword/public/apple-splash-828-1792.png | Bin 2048 -> 0 bytes crossword/public/demos/mini.xw | 15 - crossword/public/demos/mini2.xw | 13 - crossword/public/demos/presidential_appts.xw | 15 - crossword/public/favicon-32x32.png | Bin 382 -> 0 bytes crossword/public/favicon.ico | Bin 15406 -> 0 bytes crossword/public/index.html | 48 - crossword/public/keypress.mp3 | Bin 2304 -> 0 bytes crossword/public/manifest-icon-192.png | Bin 616 -> 0 bytes crossword/public/manifest-icon-512.png | Bin 872 -> 0 bytes crossword/public/manifest.json | 27 - crossword/public/robots.txt | 2 - crossword/public/staging.robots.txt | 3 - crossword/public/success.mp3 | Bin 56841 -> 0 bytes crossword/src/AccountPage.tsx | 149 - crossword/src/Admin.tsx | 209 - crossword/src/App.test.tsx | 12 - crossword/src/App.tsx | 369 - crossword/src/AutoSizer.tsx | 139 - crossword/src/Autofiller.ts | 309 - crossword/src/Builder.tsx | 567 - crossword/src/Calendar.tsx | 110 - crossword/src/Category.test.tsx | 22 - crossword/src/Category.tsx | 100 - crossword/src/Cell.tsx | 130 - crossword/src/Comments.test.tsx | 265 - crossword/src/Comments.tsx | 300 - crossword/src/DBTest.tsx | 25 - crossword/src/Emoji.tsx | 5 - crossword/src/ErrorBoundary.tsx | 37 - crossword/src/Grid.tsx | 67 - crossword/src/Home.tsx | 59 - crossword/src/Icons.test.tsx | 212 - crossword/src/Icons.tsx | 246 - crossword/src/Keyboard.tsx | 213 - crossword/src/Overlay.tsx | 45 - crossword/src/Page.tsx | 279 - crossword/src/Puzzle.test.tsx | 80 - crossword/src/Puzzle.tsx | 882 -- crossword/src/PuzzleStats.tsx | 59 - crossword/src/Square.tsx | 60 - crossword/src/TopBar.tsx | 136 - crossword/src/UpcomingMinisCalendar.tsx | 46 - crossword/src/Uploader.tsx | 66 - crossword/src/WordDB.ts | 192 - crossword/src/autofill.worker.ts | 41 - crossword/src/autofillGrid.ts | 170 - crossword/src/bitArray.test.ts | 195 - crossword/src/bitArray.ts | 124 - crossword/src/common/dbtypes.ts | 221 - crossword/src/crosshare.png | Bin 1771 -> 0 bytes crossword/src/dbUtils.ts | 171 - crossword/src/firebase.ts | 57 - crossword/src/googlesignin.png | Bin 5088 -> 0 bytes crossword/src/gridBase.ts | 227 - crossword/src/hooks.ts | 17 - crossword/src/index.tsx | 43 - crossword/src/react-app-env.d.ts | 1 - crossword/src/reducer.ts | 575 - crossword/src/serviceWorker.ts | 145 - crossword/src/setupTests.ts | 5 - crossword/src/style.css | 89 - crossword/src/style.ts | 41 - crossword/src/testUtils.tsx | 69 - crossword/src/timestamp.ts | 26 - crossword/src/types.ts | 138 - crossword/src/utils.test.ts | 34 - crossword/src/utils.ts | 82 - crossword/src/vendor/detectElementResize.d.ts | 8 - crossword/src/vendor/detectElementResize.js | 235 - crossword/src/viewableGrid.ts | 329 - crossword/tsconfig.json | 32 - crossword/typings/custom/index.d.ts | 7 - crossword/yarn.lock | 12552 ---------------- crossword/firebase.json => firebase.json | 0 {crossword/functions => functions}/.gitignore | 0 .../functions => functions}/package-lock.json | 0 .../functions => functions}/package.json | 0 {crossword/functions => functions}/src/common | 0 .../functions => functions}/src/index.ts | 0 .../functions => functions}/src/timestamp.ts | 0 .../functions => functions}/tsconfig.json | 0 .../functions => functions}/tslint.json | 0 crossword/logo.svg => logo.svg | 0 123 files changed, 85 insertions(+), 23136 deletions(-) mode change 120000 => 100644 app/firestore.rules rename {crossword/admin_scripts/src => app/scripts}/makeAdmin.ts (100%) rename {crossword/admin_scripts/src => app/scripts}/migrate.ts (100%) delete mode 100644 crossword/.env.development delete mode 100644 crossword/.firebaserc delete mode 100644 crossword/.gitignore delete mode 100644 crossword/README.md delete mode 100644 crossword/__mocks__/workerMock.ts delete mode 100644 crossword/admin_scripts/package-lock.json delete mode 100644 crossword/admin_scripts/package.json delete mode 100644 crossword/admin_scripts/sentryRelease.js delete mode 100644 crossword/admin_scripts/tsconfig.json delete mode 100644 crossword/admin_scripts/tslint.json delete mode 100644 crossword/firestore.indexes.json delete mode 100644 crossword/firestore.rules delete mode 100644 crossword/package.json delete mode 100644 crossword/public/apple-icon-120.png delete mode 100644 crossword/public/apple-icon-152.png delete mode 100644 crossword/public/apple-icon-167.png delete mode 100644 crossword/public/apple-icon-180.png delete mode 100644 crossword/public/apple-splash-1125-2436.png delete mode 100644 crossword/public/apple-splash-1136-640.png delete mode 100644 crossword/public/apple-splash-1242-2208.png delete mode 100644 crossword/public/apple-splash-1242-2688.png delete mode 100644 crossword/public/apple-splash-1334-750.png delete mode 100644 crossword/public/apple-splash-1536-2048.png delete mode 100644 crossword/public/apple-splash-1668-2224.png delete mode 100644 crossword/public/apple-splash-1668-2388.png delete mode 100644 crossword/public/apple-splash-1792-828.png delete mode 100644 crossword/public/apple-splash-2048-1536.png delete mode 100644 crossword/public/apple-splash-2048-2732.png delete mode 100644 crossword/public/apple-splash-2208-1242.png delete mode 100644 crossword/public/apple-splash-2224-1668.png delete mode 100644 crossword/public/apple-splash-2388-1668.png delete mode 100644 crossword/public/apple-splash-2436-1125.png delete mode 100644 crossword/public/apple-splash-2688-1242.png delete mode 100644 crossword/public/apple-splash-2732-2048.png delete mode 100644 crossword/public/apple-splash-640-1136.png delete mode 100644 crossword/public/apple-splash-750-1334.png delete mode 100644 crossword/public/apple-splash-828-1792.png delete mode 100644 crossword/public/demos/mini.xw delete mode 100644 crossword/public/demos/mini2.xw delete mode 100644 crossword/public/demos/presidential_appts.xw delete mode 100644 crossword/public/favicon-32x32.png delete mode 100644 crossword/public/favicon.ico delete mode 100644 crossword/public/index.html delete mode 100644 crossword/public/keypress.mp3 delete mode 100644 crossword/public/manifest-icon-192.png delete mode 100644 crossword/public/manifest-icon-512.png delete mode 100644 crossword/public/manifest.json delete mode 100644 crossword/public/robots.txt delete mode 100644 crossword/public/staging.robots.txt delete mode 100644 crossword/public/success.mp3 delete mode 100644 crossword/src/AccountPage.tsx delete mode 100644 crossword/src/Admin.tsx delete mode 100644 crossword/src/App.test.tsx delete mode 100644 crossword/src/App.tsx delete mode 100644 crossword/src/AutoSizer.tsx delete mode 100644 crossword/src/Autofiller.ts delete mode 100644 crossword/src/Builder.tsx delete mode 100644 crossword/src/Calendar.tsx delete mode 100644 crossword/src/Category.test.tsx delete mode 100644 crossword/src/Category.tsx delete mode 100644 crossword/src/Cell.tsx delete mode 100644 crossword/src/Comments.test.tsx delete mode 100644 crossword/src/Comments.tsx delete mode 100644 crossword/src/DBTest.tsx delete mode 100644 crossword/src/Emoji.tsx delete mode 100644 crossword/src/ErrorBoundary.tsx delete mode 100644 crossword/src/Grid.tsx delete mode 100644 crossword/src/Home.tsx delete mode 100644 crossword/src/Icons.test.tsx delete mode 100644 crossword/src/Icons.tsx delete mode 100644 crossword/src/Keyboard.tsx delete mode 100644 crossword/src/Overlay.tsx delete mode 100644 crossword/src/Page.tsx delete mode 100644 crossword/src/Puzzle.test.tsx delete mode 100644 crossword/src/Puzzle.tsx delete mode 100644 crossword/src/PuzzleStats.tsx delete mode 100644 crossword/src/Square.tsx delete mode 100644 crossword/src/TopBar.tsx delete mode 100644 crossword/src/UpcomingMinisCalendar.tsx delete mode 100644 crossword/src/Uploader.tsx delete mode 100644 crossword/src/WordDB.ts delete mode 100644 crossword/src/autofill.worker.ts delete mode 100644 crossword/src/autofillGrid.ts delete mode 100644 crossword/src/bitArray.test.ts delete mode 100644 crossword/src/bitArray.ts delete mode 100644 crossword/src/common/dbtypes.ts delete mode 100644 crossword/src/crosshare.png delete mode 100644 crossword/src/dbUtils.ts delete mode 100644 crossword/src/firebase.ts delete mode 100644 crossword/src/googlesignin.png delete mode 100644 crossword/src/gridBase.ts delete mode 100644 crossword/src/hooks.ts delete mode 100644 crossword/src/index.tsx delete mode 100644 crossword/src/react-app-env.d.ts delete mode 100644 crossword/src/reducer.ts delete mode 100644 crossword/src/serviceWorker.ts delete mode 100644 crossword/src/setupTests.ts delete mode 100644 crossword/src/style.css delete mode 100644 crossword/src/style.ts delete mode 100644 crossword/src/testUtils.tsx delete mode 100644 crossword/src/timestamp.ts delete mode 100644 crossword/src/types.ts delete mode 100644 crossword/src/utils.test.ts delete mode 100644 crossword/src/utils.ts delete mode 100644 crossword/src/vendor/detectElementResize.d.ts delete mode 100644 crossword/src/vendor/detectElementResize.js delete mode 100644 crossword/src/viewableGrid.ts delete mode 100644 crossword/tsconfig.json delete mode 100644 crossword/typings/custom/index.d.ts delete mode 100644 crossword/yarn.lock rename crossword/firebase.json => firebase.json (100%) rename {crossword/functions => functions}/.gitignore (100%) rename {crossword/functions => functions}/package-lock.json (100%) rename {crossword/functions => functions}/package.json (100%) rename {crossword/functions => functions}/src/common (100%) rename {crossword/functions => functions}/src/index.ts (100%) rename {crossword/functions => functions}/src/timestamp.ts (100%) rename {crossword/functions => functions}/tsconfig.json (100%) rename {crossword/functions => functions}/tslint.json (100%) rename crossword/logo.svg => logo.svg (100%) diff --git a/app/firestore.rules b/app/firestore.rules deleted file mode 120000 index b10e7e2d..00000000 --- a/app/firestore.rules +++ /dev/null @@ -1 +0,0 @@ -../crossword/firestore.rules \ No newline at end of file diff --git a/app/firestore.rules b/app/firestore.rules new file mode 100644 index 00000000..d227de13 --- /dev/null +++ b/app/firestore.rules @@ -0,0 +1,85 @@ +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + function isAdmin() { + return request.auth.token.admin; + } + function isNonAnonymous() { + return request.auth.token.firebase.sign_in_provider != 'anonymous'; + } + match /{document=**} { + allow read, write: if false; + } + function isAuthor() { + return request.auth.uid != null && request.auth.uid == resource.data.a; + } + function authorSetCorrectly() { + return request.auth.uid != null && request.auth.uid == request.resource.data.a; + } + function isPublished() { + return resource.data.p == null || request.time > resource.data.p; + } + function validCrossword() { + return (request.resource.data.g.size() == (request.resource.data.w * request.resource.data.h)) + && (request.resource.data.ac.size() == request.resource.data.an.size()) + && (request.resource.data.dc.size() == request.resource.data.dn.size()); + } + match /c/{crosswordId} { + allow get: if isAdmin() || isAuthor() + || (resource.data.m == true && isPublished()); + + // TODO figure out exactly what is using this query + allow list: if isAdmin() || isAuthor() + || (resource.data.c != null && request.time + duration.time(1, 0, 0, 0) >= resource.data.p); + + // This supports the homepage 'recent puzzles' list + allow list: if resource.data.m + && request.query.limit <= 30 + && request.time + duration.time(1, 0, 0, 0) >= resource.data.p; + + allow update: if isAdmin(); + + allow create: if isAdmin() && authorSetCorrectly() + && validCrossword() + && request.resource.data.c == null + && request.resource.data.p == null + && request.resource.data.m == false; + } + match /uc/{userId} { + allow read: if request.auth.uid == userId; + allow write: if request.auth.uid == userId; + } + match /up/{userId} { + allow read: if request.auth.uid == userId; + allow write: if request.auth.uid == userId; + } + match /p/{playId} { + allow read: if request.auth.uid != null + && (resource == null || request.auth.uid == resource.data.u); + + allow write: if request.auth.uid != null + && request.auth.uid == request.resource.data.u + && playId == (request.resource.data.c + '-' + request.resource.data.u); + + allow delete: if request.auth.uid != null + && request.auth.uid == resource.data.u; + } + match /s/{crosswordId} { + allow read: if isAdmin() || request.auth.uid == resource.data.a; + } + match /ds/{dateString} { + allow read: if isAdmin(); + } + match /categories/{category} { + allow get: if true; + allow write: if isAdmin(); + } + match /cfm/{commentId} { + allow write: if request.auth.uid != null + && isNonAnonymous() + && request.auth.uid == request.resource.data.a; + allow read: if isAdmin(); + allow delete: if isAdmin(); + } + } +} diff --git a/crossword/admin_scripts/src/makeAdmin.ts b/app/scripts/makeAdmin.ts similarity index 100% rename from crossword/admin_scripts/src/makeAdmin.ts rename to app/scripts/makeAdmin.ts diff --git a/crossword/admin_scripts/src/migrate.ts b/app/scripts/migrate.ts similarity index 100% rename from crossword/admin_scripts/src/migrate.ts rename to app/scripts/migrate.ts diff --git a/crossword/.env.development b/crossword/.env.development deleted file mode 100644 index 0e798443..00000000 --- a/crossword/.env.development +++ /dev/null @@ -1 +0,0 @@ -HOST=md.local diff --git a/crossword/.firebaserc b/crossword/.firebaserc deleted file mode 100644 index af04a056..00000000 --- a/crossword/.firebaserc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "projects": { - "default": "mdcrosshare" - } -} diff --git a/crossword/.gitignore b/crossword/.gitignore deleted file mode 100644 index ad121706..00000000 --- a/crossword/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* -dist -.sentryclirc -firestore-debug.log \ No newline at end of file diff --git a/crossword/README.md b/crossword/README.md deleted file mode 100644 index a70bc254..00000000 --- a/crossword/README.md +++ /dev/null @@ -1,36 +0,0 @@ -## Available Scripts - -In the project directory, you can run: - -### `yarn start` - -Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.
-You will also see any lint errors in the console. - -### `yarn test` - -Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `yarn build` - -Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `yarn eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. diff --git a/crossword/__mocks__/workerMock.ts b/crossword/__mocks__/workerMock.ts deleted file mode 100644 index 0210a729..00000000 --- a/crossword/__mocks__/workerMock.ts +++ /dev/null @@ -1 +0,0 @@ -module.exports = Object.create(null); diff --git a/crossword/admin_scripts/package-lock.json b/crossword/admin_scripts/package-lock.json deleted file mode 100644 index a1be2014..00000000 --- a/crossword/admin_scripts/package-lock.json +++ /dev/null @@ -1,1488 +0,0 @@ -{ - "name": "crosshare-scripts", - "version": "0.1.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@firebase/app-types": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.0.tgz", - "integrity": "sha512-ld6rzjXk/SUauHiQZJkeuSJpxIZ5wdnWuF5fWBFQNPaxsaJ9kyYg9GqEvwZ1z2e6JP5cU9gwRBlfW1WkGtGDYA==" - }, - "@firebase/auth-interop-types": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.4.tgz", - "integrity": "sha512-CLKNS84KGAv5lRnHTQZFWoR11Ti7gIPFirDDXWek/fSU+TdYdnxJFR5XSD4OuGyzUYQ3Dq7aVj5teiRdyBl9hA==" - }, - "@firebase/component": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.8.tgz", - "integrity": "sha512-kzuCF+NVympQk3gcsHldOmDRVPVndECi6O9Wvd47HTEQYO9HsZWfOM1fHUvvHAijSzNi16p4NSM7UziuBQBL4w==", - "requires": { - "@firebase/util": "0.2.43", - "tslib": "1.11.1" - } - }, - "@firebase/database": { - "version": "0.5.24", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.5.24.tgz", - "integrity": "sha512-9whAQzU8cxDUKGBWCT/aHVmqfyzCP2RkGhbZi2oHpMrmvht7cuBtXtUbDD5R8WomniCOUP8rtQfmCFI7V9ehYw==", - "requires": { - "@firebase/auth-interop-types": "0.1.4", - "@firebase/component": "0.1.8", - "@firebase/database-types": "0.4.14", - "@firebase/logger": "0.2.0", - "@firebase/util": "0.2.43", - "faye-websocket": "0.11.3", - "tslib": "1.11.1" - } - }, - "@firebase/database-types": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.4.14.tgz", - "integrity": "sha512-+D41HWac0HcvwMi+0dezEdSOZHpVjPKPNmpQiW2GDuS5kk27/v1jxc9v7F4ALLtpxbVcn16UZl5PqEkcS9H2Xg==", - "requires": { - "@firebase/app-types": "0.6.0" - } - }, - "@firebase/logger": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.0.tgz", - "integrity": "sha512-qOMnAh1JY9NkYUEy3iFviiFq0dCvk6qN2DsRy2Y7eAhHR6RqwA47l1kI+0MIXmSzlJ9akXjWAXxV5ijzr68Big==" - }, - "@firebase/util": { - "version": "0.2.43", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.43.tgz", - "integrity": "sha512-4gGlvcoOJ48xO6PH59UOHLjvImdYXANF/1d0ao60fbiJDIKxJqMksXw3UF2zsUrRkyCOqIDLeiVuF18vffXP+g==", - "requires": { - "tslib": "1.11.1" - } - }, - "@google-cloud/common": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", - "integrity": "sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg==", - "optional": true, - "requires": { - "@google-cloud/projectify": "^1.0.0", - "@google-cloud/promisify": "^1.0.0", - "arrify": "^2.0.0", - "duplexify": "^3.6.0", - "ent": "^2.2.0", - "extend": "^3.0.2", - "google-auth-library": "^5.5.0", - "retry-request": "^4.0.0", - "teeny-request": "^6.0.0" - } - }, - "@google-cloud/firestore": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-3.7.1.tgz", - "integrity": "sha512-2zDGr3wnzgMf/sn+wgqLJoakKbchqrn1F05O0CrXdr3pmOpRCTDWD+ua/k73JG/fqWGkoLw+uuDQew980ZHlvw==", - "optional": true, - "requires": { - "deep-equal": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "google-gax": "^1.13.0", - "readable-stream": "^3.4.0", - "through2": "^3.0.0" - } - }, - "@google-cloud/paginator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz", - "integrity": "sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg==", - "optional": true, - "requires": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - } - }, - "@google-cloud/projectify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz", - "integrity": "sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg==", - "optional": true - }, - "@google-cloud/promisify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz", - "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ==", - "optional": true - }, - "@google-cloud/storage": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-4.6.0.tgz", - "integrity": "sha512-ubhbLAnj+hrp32x5gI+JajKU0kvhApA6PsLOLkuOj4Cz4b6MNsyhSWZ5rq2W7TylqfNNW8M9QxPCKWg3Sb0IbA==", - "optional": true, - "requires": { - "@google-cloud/common": "^2.1.1", - "@google-cloud/paginator": "^2.0.0", - "@google-cloud/promisify": "^1.0.0", - "arrify": "^2.0.0", - "compressible": "^2.0.12", - "concat-stream": "^2.0.0", - "date-and-time": "^0.12.0", - "duplexify": "^3.5.0", - "extend": "^3.0.2", - "gaxios": "^2.0.1", - "gcs-resumable-upload": "^2.2.4", - "hash-stream-validation": "^0.2.2", - "mime": "^2.2.0", - "mime-types": "^2.0.8", - "onetime": "^5.1.0", - "p-limit": "^2.2.0", - "pumpify": "^2.0.0", - "readable-stream": "^3.4.0", - "snakeize": "^0.1.0", - "stream-events": "^1.0.1", - "through2": "^3.0.0", - "xdg-basedir": "^4.0.0" - } - }, - "@grpc/grpc-js": { - "version": "0.6.18", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-0.6.18.tgz", - "integrity": "sha512-uAzv/tM8qpbf1vpx1xPMfcUMzbfdqJtdCYAqY/LsLeQQlnTb4vApylojr+wlCyr7bZeg3AFfHvtihnNOQQt/nA==", - "optional": true, - "requires": { - "semver": "^6.2.0" - } - }, - "@grpc/proto-loader": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.3.tgz", - "integrity": "sha512-8qvUtGg77G2ZT2HqdqYoM/OY97gQd/0crSG34xNmZ4ZOsv3aQT/FQV9QfZPazTGna6MIoyUd+u6AxsoZjJ/VMQ==", - "optional": true, - "requires": { - "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" - } - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", - "optional": true - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "optional": true - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "optional": true - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", - "optional": true - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "optional": true, - "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": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", - "optional": true - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", - "optional": true - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", - "optional": true - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", - "optional": true - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", - "optional": true - }, - "@tootallnate/once": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", - "integrity": "sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA==", - "optional": true - }, - "@types/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-UoOfVEzAUpeSPmjm7h1uk5MH6KZma2z2O7a75onTGjnNvAvMVrPzPL/vBbT65iIGHWj6rokwfmYcmxmlSf2uwg==", - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", - "optional": true - }, - "@types/node": { - "version": "8.10.59", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.59.tgz", - "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==" - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "optional": true, - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "agent-base": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", - "optional": true, - "requires": { - "debug": "4" - } - }, - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "optional": true - }, - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", - "optional": true - }, - "bignumber.js": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", - "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==", - "optional": true - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "optional": true - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "optional": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "optional": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "optional": true, - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "optional": true - }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "optional": true - }, - "date-and-time": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.12.0.tgz", - "integrity": "sha512-n2RJIAp93AucgF/U/Rz5WRS2Hjg5Z+QxscaaMCi6pVZT1JpJKRH+C08vyH/lRR1kxNXnPxgo3lWfd+jCb/UcuQ==", - "optional": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.1.tgz", - "integrity": "sha512-7Et6r6XfNW61CPPCIYfm1YPGSmh6+CliYeL4km7GWJcpX5LTAflGF8drLLR+MZX+2P3NZfAfSduutBbSWqER4g==", - "optional": true, - "requires": { - "es-abstract": "^1.16.3", - "es-get-iterator": "^1.0.1", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "isarray": "^2.0.5", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0", - "side-channel": "^1.0.1", - "which-boxed-primitive": "^1.0.1", - "which-collection": "^1.0.0" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "optional": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "dicer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", - "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", - "requires": { - "streamsearch": "0.1.2" - } - }, - "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", - "optional": true, - "requires": { - "is-obj": "^2.0.0" - } - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "optional": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "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==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - } - } - }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "optional": true, - "requires": { - "once": "^1.4.0" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "optional": true - }, - "es-abstract": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", - "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", - "optional": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-get-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", - "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", - "optional": true, - "requires": { - "es-abstract": "^1.17.4", - "has-symbols": "^1.0.1", - "is-arguments": "^1.0.4", - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "optional": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "optional": true - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "optional": true - }, - "fast-text-encoding": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.1.tgz", - "integrity": "sha512-x4FEgaz3zNRtJfLFqJmHWxkMDDvXVtaznj2V9jiP8ACUJrUgist4bP9FmDL2Vew2Y9mEQI/tG4GqabaitYp9CQ==", - "optional": true - }, - "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "firebase-admin": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-8.10.0.tgz", - "integrity": "sha512-QzJZ1sBh9xzKjb44aP6m1duy0Xe1ixexwh0eaOt1CkJYCOq2b6bievK4GNWMl5yGQ7FFBEbZO6hyDi+5wrctcg==", - "requires": { - "@firebase/database": "^0.5.17", - "@google-cloud/firestore": "^3.0.0", - "@google-cloud/storage": "^4.1.2", - "@types/node": "^8.10.59", - "dicer": "^0.3.0", - "jsonwebtoken": "8.1.0", - "node-forge": "0.7.4" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "optional": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "optional": true - }, - "gaxios": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.2.tgz", - "integrity": "sha512-K/+py7UvKRDaEwEKlLiRKrFr+wjGjsMz5qH7Vs549QJS7cpSCOT/BbWL7pzqECflc46FcNPipjSfB+V1m8PAhw==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" - } - }, - "gcp-metadata": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz", - "integrity": "sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA==", - "optional": true, - "requires": { - "gaxios": "^2.1.0", - "json-bigint": "^0.3.0" - } - }, - "gcs-resumable-upload": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-2.3.3.tgz", - "integrity": "sha512-sf896I5CC/1AxeaGfSFg3vKMjUq/r+A3bscmVzZm10CElyRanN0XwPu/MxeIO4LSP+9uF6yKzXvNsaTsMXUG6Q==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "configstore": "^5.0.0", - "gaxios": "^2.0.0", - "google-auth-library": "^5.0.0", - "pumpify": "^2.0.0", - "stream-events": "^1.0.4" - } - }, - "google-auth-library": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", - "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", - "optional": true, - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^2.1.0", - "gcp-metadata": "^3.4.0", - "gtoken": "^4.1.0", - "jws": "^4.0.0", - "lru-cache": "^5.0.0" - } - }, - "google-gax": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-1.15.1.tgz", - "integrity": "sha512-1T1PwSZWnbdRusA+NCZMSe56iU6swGvuZuy54eYl9vEHiRXTLYbQmUkWY2CqgYD9Fd/T4WBkUl22+rZG80unyw==", - "optional": true, - "requires": { - "@grpc/grpc-js": "^0.6.18", - "@grpc/proto-loader": "^0.5.1", - "@types/fs-extra": "^8.0.1", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^3.6.0", - "google-auth-library": "^5.0.0", - "is-stream-ended": "^0.1.4", - "lodash.at": "^4.6.0", - "lodash.has": "^4.5.2", - "node-fetch": "^2.6.0", - "protobufjs": "^6.8.9", - "retry-request": "^4.0.0", - "semver": "^6.0.0", - "walkdir": "^0.4.0" - } - }, - "google-p12-pem": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz", - "integrity": "sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg==", - "optional": true, - "requires": { - "node-forge": "^0.9.0" - }, - "dependencies": { - "node-forge": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", - "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==", - "optional": true - } - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "optional": true - }, - "gtoken": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz", - "integrity": "sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA==", - "optional": true, - "requires": { - "gaxios": "^2.1.0", - "google-p12-pem": "^2.0.0", - "jws": "^4.0.0", - "mime": "^2.2.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "optional": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "optional": true - }, - "hash-stream-validation": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.2.tgz", - "integrity": "sha512-cMlva5CxWZOrlS/cY0C+9qAzesn5srhFA8IT1VPiHc9bWWBLkJfEUIZr7MWoi89oOOGmpg8ymchaOjiArsGu5A==", - "optional": true, - "requires": { - "through2": "^2.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "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==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "optional": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "http-parser-js": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=" - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "optional": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "optional": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "optional": true - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "optional": true - }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "optional": true - }, - "is-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", - "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==", - "optional": true - }, - "is-boolean-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", - "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", - "optional": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "optional": true - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "optional": true - }, - "is-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", - "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", - "optional": true - }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", - "optional": true - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "optional": true - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "optional": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-set": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", - "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", - "optional": true - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "optional": true - }, - "is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", - "optional": true - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "optional": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "optional": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "optional": true - }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "optional": true - }, - "is-weakset": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", - "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", - "optional": true - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "optional": true - }, - "json-bigint": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", - "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", - "optional": true, - "requires": { - "bignumber.js": "^7.0.0" - } - }, - "jsonwebtoken": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.1.0.tgz", - "integrity": "sha1-xjl80uX9WD1lwAeoPce7eOaYK4M=", - "requires": { - "jws": "^3.1.4", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.0.0", - "xtend": "^4.0.1" - }, - "dependencies": { - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - } - } - }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "optional": true, - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "optional": true, - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "lodash.at": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz", - "integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g=", - "optional": true - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", - "optional": true - }, - "lodash.has": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=", - "optional": true - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "optional": true - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "optional": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", - "optional": true, - "requires": { - "semver": "^6.0.0" - } - }, - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", - "optional": true - }, - "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", - "optional": true - }, - "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", - "optional": true, - "requires": { - "mime-db": "1.43.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "optional": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", - "optional": true - }, - "node-forge": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.4.tgz", - "integrity": "sha512-8Df0906+tq/omxuCZD6PqhPaQDYuyJ1d+VITgxoIA8zvQd1ru+nMJcDChHH324MWitIgbVkAkQoGEEVJNpn/PA==" - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "optional": true - }, - "object-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", - "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", - "optional": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "optional": true - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "optional": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "optional": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "optional": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "optional": true - }, - "protobufjs": { - "version": "6.8.9", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.9.tgz", - "integrity": "sha512-j2JlRdUeL/f4Z6x4aU4gj9I2LECglC+5qR2TrWb193Tla1qfdaNQTZ8I27Pt7K0Ajmvjjpft7O3KWTGciz4gpw==", - "optional": true, - "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.0", - "@types/node": "^10.1.0", - "long": "^4.0.0" - }, - "dependencies": { - "@types/node": { - "version": "10.17.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.17.tgz", - "integrity": "sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q==", - "optional": true - } - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "optional": true, - "requires": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - }, - "dependencies": { - "duplexify": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", - "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", - "optional": true, - "requires": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - } - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "optional": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", - "optional": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "retry-request": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.1.tgz", - "integrity": "sha512-BINDzVtLI2BDukjWmjAIRZ0oglnCAkpP2vQjM3jdLhmT62h0xnQgciPwBRDAvHqpkPT2Wo1XuUyLyn6nbGrZQQ==", - "optional": true, - "requires": { - "debug": "^4.1.1", - "through2": "^3.0.1" - } - }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "optional": true - }, - "side-channel": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", - "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", - "optional": true, - "requires": { - "es-abstract": "^1.17.0-next.1", - "object-inspect": "^1.7.0" - } - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "optional": true - }, - "snakeize": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", - "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", - "optional": true - }, - "stream-events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", - "optional": true, - "requires": { - "stubs": "^3.0.0" - } - }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "optional": true - }, - "streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" - }, - "string.prototype.trimleft": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", - "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", - "optional": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string.prototype.trimright": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", - "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", - "optional": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - } - } - }, - "stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", - "optional": true - }, - "teeny-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.3.tgz", - "integrity": "sha512-TZG/dfd2r6yeji19es1cUIwAlVD8y+/svB1kAC2Y0bjEyysrfbO8EZvJBRwIE6WkwmUoB7uvWLwTIhJbMXZ1Dw==", - "optional": true, - "requires": { - "http-proxy-agent": "^4.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.2.0", - "stream-events": "^1.0.5", - "uuid": "^7.0.0" - } - }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "optional": true, - "requires": { - "readable-stream": "2 || 3" - } - }, - "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "optional": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "optional": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "optional": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "optional": true - }, - "uuid": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz", - "integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==", - "optional": true - }, - "walkdir": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", - "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", - "optional": true - }, - "websocket-driver": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", - "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", - "requires": { - "http-parser-js": ">=0.4.0 <0.4.11", - "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==" - }, - "which-boxed-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", - "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", - "optional": true, - "requires": { - "is-bigint": "^1.0.0", - "is-boolean-object": "^1.0.0", - "is-number-object": "^1.0.3", - "is-string": "^1.0.4", - "is-symbol": "^1.0.2" - } - }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "optional": true, - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "optional": true - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "optional": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "optional": true - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "optional": true - } - } -} diff --git a/crossword/admin_scripts/package.json b/crossword/admin_scripts/package.json deleted file mode 100644 index 3b028cc4..00000000 --- a/crossword/admin_scripts/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "crosshare-scripts", - "version": "0.1.0", - "private": true, - "scripts": { - "makeAdmin": "node dist/scripts/src/makeAdmin.js", - "build": "npm run build-ts && npm run tslint", - "build-ts": "tsc", - "tslint": "tslint -c tslint.json -p tsconfig.json" - }, - "dependencies": { - "firebase-admin": "^8.10.0" - } -} diff --git a/crossword/admin_scripts/sentryRelease.js b/crossword/admin_scripts/sentryRelease.js deleted file mode 100644 index ee64ac88..00000000 --- a/crossword/admin_scripts/sentryRelease.js +++ /dev/null @@ -1,24 +0,0 @@ -const SentryCli = require('@sentry/cli'); -async function createReleaseAndUpload() { - const release = process.env.REACT_APP_SENTRY_RELEASE; - if (!release) { - console.warn('REACT_APP_SENTRY_RELEASE is not set'); - return; - } - const cli = new SentryCli(); - try { - console.log('Creating sentry release ' + release); - await cli.releases.new(release); - console.log('Uploading source maps'); - await cli.releases.uploadSourceMaps(release, { - include: ['build/static/js'], - urlPrefix: '~/static/js', - rewrite: false, - }); - console.log('Finalizing release'); - await cli.releases.finalize(release); - } catch (e) { - console.error('Source maps uploading failed:', e); - } -} -createReleaseAndUpload(); diff --git a/crossword/admin_scripts/tsconfig.json b/crossword/admin_scripts/tsconfig.json deleted file mode 100644 index aac0444b..00000000 --- a/crossword/admin_scripts/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "sourceMap": true, - "outDir": "dist", - "strict": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "baseUrl": "." - }, - "include": [ - "src/**/*" - ] -} diff --git a/crossword/admin_scripts/tslint.json b/crossword/admin_scripts/tslint.json deleted file mode 100644 index 85598d7b..00000000 --- a/crossword/admin_scripts/tslint.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "tslint:latest", - "rules": { - "interface-name": false, - "max-line-length": [true, 120], - "member-ordering": [true, - "public-before-private", - "static-before-instance", - "variables-before-functions" - ], - "no-console": false, - "no-implicit-dependencies": [true, "dev"], - "no-var-requires": false, - "object-literal-sort-keys": false, - "quotemark": [true, "single", "avoid-escape"], - "variable-name": [true, "ban-keywords", "check-format"] - } -} diff --git a/crossword/firestore.indexes.json b/crossword/firestore.indexes.json deleted file mode 100644 index d9142a12..00000000 --- a/crossword/firestore.indexes.json +++ /dev/null @@ -1,192 +0,0 @@ -{ - "indexes": [ - { - "collectionGroup": "c", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "c", - "order": "ASCENDING" - }, - { - "fieldPath": "p", - "order": "ASCENDING" - } - ] - }, - { - "collectionGroup": "c", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "c", - "order": "ASCENDING" - }, - { - "fieldPath": "p", - "order": "DESCENDING" - } - ] - }, - { - "collectionGroup": "p", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "f", - "order": "ASCENDING" - }, - { - "fieldPath": "ua", - "order": "ASCENDING" - } - ] - }, - { - "collectionGroup": "p", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "u", - "order": "ASCENDING" - }, - { - "fieldPath": "ua", - "order": "DESCENDING" - } - ] - } - ], - "fieldOverrides": [ - { - "collectionGroup": "c", - "fieldPath": "ac", - "indexes": [] - }, - { - "collectionGroup": "c", - "fieldPath": "an", - "indexes": [] - }, - { - "collectionGroup": "c", - "fieldPath": "dc", - "indexes": [] - }, - { - "collectionGroup": "c", - "fieldPath": "dn", - "indexes": [] - }, - { - "collectionGroup": "c", - "fieldPath": "g", - "indexes": [] - }, - { - "collectionGroup": "c", - "fieldPath": "h", - "indexes": [] - }, - { - "collectionGroup": "c", - "fieldPath": "hs", - "indexes": [] - }, - { - "collectionGroup": "c", - "fieldPath": "n", - "indexes": [] - }, - { - "collectionGroup": "c", - "fieldPath": "s", - "indexes": [] - }, - { - "collectionGroup": "c", - "fieldPath": "t", - "indexes": [] - }, - { - "collectionGroup": "c", - "fieldPath": "w", - "indexes": [] - }, - { - "collectionGroup": "p", - "fieldPath": "ch", - "indexes": [] - }, - { - "collectionGroup": "p", - "fieldPath": "ct", - "indexes": [] - }, - { - "collectionGroup": "p", - "fieldPath": "f", - "indexes": [] - }, - { - "collectionGroup": "p", - "fieldPath": "g", - "indexes": [] - }, - { - "collectionGroup": "p", - "fieldPath": "n", - "indexes": [] - }, - { - "collectionGroup": "p", - "fieldPath": "rc", - "indexes": [] - }, - { - "collectionGroup": "p", - "fieldPath": "t", - "indexes": [] - }, - { - "collectionGroup": "p", - "fieldPath": "uc", - "indexes": [] - }, - { - "collectionGroup": "p", - "fieldPath": "vc", - "indexes": [] - }, - { - "collectionGroup": "p", - "fieldPath": "wc", - "indexes": [] - }, - { - "collectionGroup": "p", - "fieldPath": "we", - "indexes": [] - }, - { - "collectionGroup": "s", - "fieldPath": "a", - "indexes": [] - }, - { - "collectionGroup": "s", - "fieldPath": "ua", - "indexes": [] - }, - { - "collectionGroup": "s", - "fieldPath": "ct", - "indexes": [] - }, - { - "collectionGroup": "s", - "fieldPath": "uc", - "indexes": [] - } - ] -} diff --git a/crossword/firestore.rules b/crossword/firestore.rules deleted file mode 100644 index d227de13..00000000 --- a/crossword/firestore.rules +++ /dev/null @@ -1,85 +0,0 @@ -rules_version = '2'; -service cloud.firestore { - match /databases/{database}/documents { - function isAdmin() { - return request.auth.token.admin; - } - function isNonAnonymous() { - return request.auth.token.firebase.sign_in_provider != 'anonymous'; - } - match /{document=**} { - allow read, write: if false; - } - function isAuthor() { - return request.auth.uid != null && request.auth.uid == resource.data.a; - } - function authorSetCorrectly() { - return request.auth.uid != null && request.auth.uid == request.resource.data.a; - } - function isPublished() { - return resource.data.p == null || request.time > resource.data.p; - } - function validCrossword() { - return (request.resource.data.g.size() == (request.resource.data.w * request.resource.data.h)) - && (request.resource.data.ac.size() == request.resource.data.an.size()) - && (request.resource.data.dc.size() == request.resource.data.dn.size()); - } - match /c/{crosswordId} { - allow get: if isAdmin() || isAuthor() - || (resource.data.m == true && isPublished()); - - // TODO figure out exactly what is using this query - allow list: if isAdmin() || isAuthor() - || (resource.data.c != null && request.time + duration.time(1, 0, 0, 0) >= resource.data.p); - - // This supports the homepage 'recent puzzles' list - allow list: if resource.data.m - && request.query.limit <= 30 - && request.time + duration.time(1, 0, 0, 0) >= resource.data.p; - - allow update: if isAdmin(); - - allow create: if isAdmin() && authorSetCorrectly() - && validCrossword() - && request.resource.data.c == null - && request.resource.data.p == null - && request.resource.data.m == false; - } - match /uc/{userId} { - allow read: if request.auth.uid == userId; - allow write: if request.auth.uid == userId; - } - match /up/{userId} { - allow read: if request.auth.uid == userId; - allow write: if request.auth.uid == userId; - } - match /p/{playId} { - allow read: if request.auth.uid != null - && (resource == null || request.auth.uid == resource.data.u); - - allow write: if request.auth.uid != null - && request.auth.uid == request.resource.data.u - && playId == (request.resource.data.c + '-' + request.resource.data.u); - - allow delete: if request.auth.uid != null - && request.auth.uid == resource.data.u; - } - match /s/{crosswordId} { - allow read: if isAdmin() || request.auth.uid == resource.data.a; - } - match /ds/{dateString} { - allow read: if isAdmin(); - } - match /categories/{category} { - allow get: if true; - allow write: if isAdmin(); - } - match /cfm/{commentId} { - allow write: if request.auth.uid != null - && isNonAnonymous() - && request.auth.uid == request.resource.data.a; - allow read: if isAdmin(); - allow delete: if isAdmin(); - } - } -} diff --git a/crossword/package.json b/crossword/package.json deleted file mode 100644 index 1e52fbfb..00000000 --- a/crossword/package.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "name": "crosshare", - "version": "0.1.1", - "private": true, - "dependencies": { - "@emotion/core": "^10.0.28", - "@modern-dev/jsbn": "^1.2.5", - "@reach/router": "^1.3.3", - "@sentry/browser": "^5.15.4", - "@sentry/cli": "^1.52.1", - "@testing-library/jest-dom": "^4.2.4", - "@testing-library/react": "^9.5.0", - "@testing-library/user-event": "^7.1.2", - "@types/jest": "^25.2.1", - "@types/lz-string": "^1.3.34", - "@types/node": "^13.11.1", - "@types/reach__router": "^1.3.4", - "@types/react": "^16.9.34", - "@types/react-dom": "^16.9.6", - "@types/react-window": "^1.8.1", - "@use-it/event-listener": "^0.1.3", - "eslint-plugin-flowtype": "^4.7.0", - "firebase": "^7.13.2", - "fp-ts": "^2.5.3", - "io-ts": "^2.1.3", - "localforage": "^1.7.3", - "lz-string": "^1.4.4", - "react": "^16.13.1", - "react-device-detect": "^1.11.14", - "react-dom": "^16.13.1", - "react-firebase-hooks": "^2.1.1", - "react-helmet-async": "^1.0.4", - "react-icons": "^3.9.0", - "react-scripts": "^3.4.1", - "react-toastify": "^5.5.0", - "react-window": "^1.8.5", - "source-map-explorer": "^2.4.2", - "typescript": "^3.8.3", - "worker-loader": "^2.0.0" - }, - "scripts": { - "analyze": "source-map-explorer 'build/static/js/*.js'", - "dev:firebase": "firebase serve -p 4000", - "dev:react": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject", - "release": "(export REACT_APP_SENTRY_RELEASE=$(git rev-parse --short HEAD); react-scripts build && node admin_scripts/sentryRelease.js)" - }, - "eslintConfig": { - "extends": "react-app" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "proxy": "http://localhost:4000", - "devDependencies": { - "@firebase/testing": "^0.19.2", - "@types/jest-in-case": "^1.0.2", - "jest-emotion": "^10.0.32", - "jest-in-case": "^1.0.2", - "prettier": "^2.0.5", - "react-devtools": "^4.6.0" - }, - "jest": { - "moduleNameMapper": { - "\\.worker.ts":"/__mocks__/workerMock.ts" - } - } -} diff --git a/crossword/public/apple-icon-120.png b/crossword/public/apple-icon-120.png deleted file mode 100644 index 212aad1d6b3b5042d109e2b84630df0ab758157b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 537 zcmV+!0_OdRP)3 z2f@)%5S(0f7KP6K&kiX_e2=^Pz5jd3y}N_V60*!JbbE1BuT`w(;gQ9dxn4gc-6ml8 zYG!y@{iiO?rrLhr)B33qN7`J!Y>VfE5nmW?jriJdv|q1^Z^bLY7`MiF5Qnk&$<%vc zWcW?I-R+!vN#D%)y@Q6L=l?}d5Jt#MY}lU*ivK^n}XaC z7sQV8o0JvhH1A+1_j&dR6lgj z|NpNu7TEv*0G3HaK~zY`?bk65#2^er(ch%W>D+KX>N({W*aSt*x-8AE7) zi^JR40ID#+75y#sB5X(+VZ6Uu2_8v2Or)y9fI4fc8Vt>XJMROWn0X9Ukep#A%d4!qZAM>J0&k(_&7W*W{JeA<_{L)hMiIbh@gDmQMGiBxT bU$TpTC5y0mig`8200000NkvXXu0mjfF&qC+ diff --git a/crossword/public/apple-icon-152.png b/crossword/public/apple-icon-152.png deleted file mode 100644 index 1e33eea21e093cd591a0eeed956701bec20c02da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 584 zcmV-O0=NB%P)3 z2f@)%5S(0f7KP6K&kiX_e2=^Pz5jd3y}N_V60*!JbbE1BuT`w(;gQ9dxn4gc-6ml8 zYG!y@{iiO?rrLhr)B33qN7`J!Y>VfE5nmW?jriJdv|q1^Z^bLY7`MiF5Qnk&$<%vc zWcW?I-R+!vN#D%)y@Q6L=l?}d5Jt#MY}lU*ivK^n}XaC z7sQV8o0JvhH1A+1_j&dR6lgj z|NpNu7TEv*0L4i}K~z}7?by8ygD?<=;WtP$=~*IEWCRM>hfyfeH)RT69E^yCqICYQ z^XaK9?#D1+3e3!YXCnZES)HY9K2F>vEzPnqvH>SLNh`zQdowH88$AR&)F0~9?1Zfl zj84*)QJW0<`T1cRx08l%b~*vg%>KZ}eVv+JWU1v#0MeKK)(V>-Prx8)0#a-aW(7-Y zLW%`RDiVWPouvgMu9rx*%+lf>wc1s#*KAj(M1RY%<(G4NZ2xx7Q5%$I{{Lq75B37^ WDpO_mJJ9|B0000ruC&qtSAEvBX?qBj6a(>=5S;F7{x(qQ^0+E7Uvf$0!xNj>H2FEGuf5f&N) z1xf0;uBMCWhjf5Qm(cVd6w0nmDG4dAgYXgodkgiX_tOsco7~Z!iav zsp5UeBIucB)A0V#)PUjvL!}QB(_?7}dULm!k4<;cFj3 z2f@)%5S(0f7KP6K&kiX_e2=^Pz5jd3y}N_V60*!JbbE1BuT`w(;gQ9dxn4gc-6ml8 zYG!y@{iiO?rrLhr)B33qN7`J!Y>VfE5nmW?jriJdv|q1^Z^bLY7`MiF5Qnk&$<%vc zWcW?I-R+!vN#D%)y@Q6L=l?}d5Jt#MY}lU*ivK^n}XaC z7sQV8o0JvhH1A+1_j&dR6lgj z|NpNu7TEv*0M$uEK~!ko?b$IBgCG0A$;D?xrHej*8 zV!qFc0anC(r64UWeOh|}0AA9)wcDPIw(}&(Al+NPF_2`QInx=xSDJK74NN3Iz&>-S zbFdFex7Q1hvfV~yiypZ#yIF+b+2T8tc~IFu7Id)CoRuO8cq+c()(v9nRZc|5+8k6Uz| zT+i_88e8(}BMwNhPOb|xGy5K#vGxBP-Tg63h$>DD{wC{Vw&-PJBO*zX={TTso-JAF z>O#3Uso*hST&I=IdLdncRB)pdQlxnIvRv~1O>;AkY>*2eH>o10y|kEF?i<`%3a{wN ztern!XEznn?%5M2s>jY>U1ZE#`podt-pkZ*r?KE!!8;4gts2*-I#V8}vs5#0In2#9 zAUhI}oN$)QL@?w^UG0u9r=57H5W$i?YWItO7uZgtMi%YNn1)>GnIIL?xGKgWsfGtB zCXw4sNd865V>sGP$a-LMp0Ty3u5s>T2*8{*6|$OSQS@&G13 cU;bJ4dG~^tVpMHb0=o+gp00i_>zopr0I#_|D*ylh diff --git a/crossword/public/apple-splash-1136-640.png b/crossword/public/apple-splash-1136-640.png deleted file mode 100644 index 385f600279cae820aaa1c11f94ac1b6e20cf3415..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1370 zcmeAS@N?(olHy`uVBq!ia0y~yU@2f=U}|7u28wh>rDriPFluHxI|l@3XC^W*XiTh~ zXy|>|LF9P7nD|AY_UeGUmrPhTt~Knva@SYp568JWX7+;@ z0|n;9l!~>U`*)sC)1#(;_wT*`t55FT-F!x9(;1zT>cWuqp=+K!oOpD`&NzFOyJ`tP zveGId|1Q@%yX;oo{mZ{r3cBBl*`L$>yjd__BCq$yt%5D_>$~!gY9Ex?eyl>EedCXl zSE>_Je)#UW`*u#X=lwH3_BOX&TXxLI)bA;yY|XXIf%|An0E$W;1+Peo17r$_Ox85XE z?&SId-9@kTmYwgpw-dNmzeAbDu>y=&hNAG!gEWGc;4h%ca z0G|+7ApLSi=>Px!*Bgu7WME*v?&;zfQgQ3;UBkRX1|kiK%|{O1^DgL;X=Hpid2x!% zoK4PFH$H8ivgN4f%kS$8o9=Iq;(WO-jgwKvqTs=SMrL+CnS)>w%vFX9S~P*AAY6Bt zN{a#(xFCBUR69@&2TUcZY0V69aU{hD1YuU6d&ax+6_SZ?)yNEM`HyD$yKOcjY=gKO z;Zm^cmZx9rNY1-5#pc;|n_1ltv8sW&{qx;*X9d$&YR=fMS@~j1CYnhwRWSdrEbJ0~ zz>pp{hv{U0^?3r$P@iuu|8X17^7otuFkd2j1`&`CbhvOx;B`98QH{*cuS;L6xhh7$ zkZ+B_a~Q;ZcV6GQ@87lmk2%pjhlqNh$#d>LdJ-((T*4LG?rrj4r91Jmwrss4FuyZ+y85}Sb4q9e0R3MmGXMYp diff --git a/crossword/public/apple-splash-1242-2208.png b/crossword/public/apple-splash-1242-2208.png deleted file mode 100644 index 04f6e51dbbf65def118b6c3e38847365212cc11c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3421 zcmeAS@N?(olHy`uVBq!ia0y~yV7bM>z_Ead87MO4&vKvyqh_YFb3kx*W+DTF#>Co* zhTexAM2^>siC=UKxOhsaWf|f9Lr$J!<-Q|K9t*`sCi-&1Zx*ozW?&E(}>8y5`x#iAQJbjI&p{tCsL1 zE3G2(?{dAf%Wl=(zx->Zp!=9-pWp!P}kiSPi_RI zh|F%fBPf2P@6+rlqEAiUC#Zj~{KoNAJ6Bd^*6h~b{4=E0qP}^ry?fwr@e8+i>rFD{ zPOd-jy{V{tRek$Q@0HzZb8BYraXl5e);@jfjm7Q9XPpSUUfE@T^q!Z;!uw9_z_8;C z@Ck7R(l2L({{R1fy|LI$1_th1o-U3d6}R5rIhfn*AmDJZS5`oy0?|lJnznqPHC*>B2ZwC1|2xr&^Iys{p_`>51hn7NtSAi?RR-sM(&^WuX@eCeMhNkGAK6|KE76S z&2C<4pX~nN|8*hT-+$ac{Z&O-AXtiuz5v>_Ow;_e)aG@T+aG>^8GX6^6gY~g7EBP& zluf%GbEqh<|3}%~Aldm*)b#+^!MBCCZ_56>_2t_5c3$(zjn~dvUaRzzu#+S< ziNK8{HE>{HkT^V22o@jpP98?QKt!;|i?lgNe2B)*?c=-tGIX`ZuR0`nM+n&DXYh3O Kb6Mw<&;$U8bUvm4 diff --git a/crossword/public/apple-splash-1242-2688.png b/crossword/public/apple-splash-1242-2688.png deleted file mode 100644 index 4ddebf1828009d14312000f4132774c7d724090f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4001 zcmeAS@N?(olHy`uVBq!ia0y~yV7bM>z}3LS3>0BGzgd)lfl)Kl**PFMJ2R1iL1SX= zL__bx4kE|v#l$Z<23$O-c;TpP?&4hC9ZN*JvsVY)y=20&ajjwRmAk$&e>l$7F|!}M z7$`6&rc|u;+`sdDnjSU%yMOQfUwv}#?&dQ>o6hKzR2PP<4_))@;l!gecE;JO+*M2X zk(E{v`FFYA*=4ut?qB}3QqcWY%>JD2=gorg5_!EpZWU~aU*DB~RQsUB_G1+S?Hhlb zyi%Q*^22w}-M4e9J@23SvA4PH+OlItrhZQuWoxcw7928Mx57nmHg9F8O{nW`izhdN zQbcAq-4PT&()Vfh6w#+9?-SI&SAOGos+}vVGHZ70Z~ht5YEj?3*WNvFxcG(JyY(iS zawpdx_})}hzN)_crT5BiwYfF3_qd*lTx*}c^~U1%$jRbuKgFqI z#ivJ~7e<~(A!dZo?t9v#C<7Cr`vW0Gulb`(BV%fm_vn}ftL z$>dRsK!G_LbfDxhni#-2YY-)d%e{BKR`mgk0#K#~mj={fY(LDqGIIZ{f7NUD?K?_U zlOayMR&&j6UTL4~{^0+0q1)$w+&}$QMOh%VeF3y~4_k{3t3$11w%|_eEQ7`FZP0nfY7^m>3l!{U zuzCLKe{EZ-Srov1;F?$b=ULSKpuIBEQ>h+4(%U!RH;jJlE1!Pv8TDiME%SE!=fPiZ zm+I~e+{{Pam<0vJyBE`PG_GHryOS2Vvm8>scY?zlgFFIGNEjrJrXi%sh}kp*iOdF< z0ys!taG+t3I9ijCRwia^5-ft@g&jyD7#_I;78&&pj(CB@8b-Vv1B;Bt4sz1MWDokp dcDe61f55~4mO)o-TY=q122WQ%mvv4FO#n|mN00yj diff --git a/crossword/public/apple-splash-1334-750.png b/crossword/public/apple-splash-1334-750.png deleted file mode 100644 index b643fd4957603108bdbcbdfd43fb106ab05cc6bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1633 zcmeAS@N?(olHy`uVBq!ia0y~yU^Qc4V0y>I3>10Uw=kW7fl)Kl**PFMJ2R1iL1SX= zL__bx4kE|v#l$Z<23$O-c;TpP?&4hC9ZN*JvsVY)y=20&ajjwRmAk$&e>l$7F|!}M z7$`6&rc|u;+`sdDnjSU%yMOQfUwv}#?&dQ>o6hKzR2PP<4_))@;l!gecE;JO+*M2X zk(E{v`FFYA*=4ut?qB}3QqcWY%>JD2=gorg5_!EpZWU~aU*DB~RQsUB_G1+S?Hhlb zyi%Q*^22w}-M4e9J@23SvA4PH+OlItrhZQuWoxcw7928Mx57nmHg9F8O{nW`izhdN zQbcAq-4PT&()Vfh6w#+9?-SI&SAOGos+}vVGHZ70Z~ht5YEj?3*WNvFxcG(JyY(iS zawpdx_})}hzN)_crT5BiwYfF3_qd*lTx*}c^~U1%Px!*Bgu7WME*q=jq}YQgQ3;UBkR)2LXq`9L?V9c%|hZ1Y=r$7l?Re z`@c*J)`@ZxP^#vAKb=EhPiFY6b=UuwzL$7#q%+u}@R5k$9E(COb-y`>I)#@PJVFSz z3L^wHfoi}iL4psDKvaSRK^iUa34*LR0#gaJ0<4`_dt?5aUtI?>0HhD(1pJKj-E(GZ zd^$TlzD+I*LmlZ zI^k=T)eGh_xfpE?_GjO;Q4nKqzA_5zz`1kWY z|5J|}5T_XA;Ga9^L_V3cig)R!^1tg&CdHEAr04f6>%A7N(<}B%{3_Y=bhbb70g%o; zXS-{_PWQhoj{@d9{^`>u-oZEZo=4m_l#%y%<3xgEKQmjTFG%B4IX1h5P?ALs^4(9* z_?GkNPx4%yR`6u%S)x6-Ir7|1{hI;S_Kn1+BsoJ+*n{#u$=Mudj)r7Tii;71`Omv8 a=KDSMU)eLu`7*HBWAJqKb6Mw<&;$S+ACM+A*8unhf>nrnz<6Ipx`@xHW z0&`+Y#ahq(JI|--QPaQs_ul{2C-?4dJ|nd0j7~{)VaWQ>HP0SSJUU}%oW06jwS*s8 zX%&%wm+PHfcB}6G-UHM0~4@zu5Rw2;7@yE$4 z)rl!TeD~aaJEz+7{+SH;$*;xw0y=X1D(4pCPRl_04f2v>uk2QvTQhr)>#4}K_UT)1EN(wO>qOY~$}an(_q;q7-gjaLh8<^s zPlzj!emNub|NsB%jm2&oLwbBfUcYgd1Dndo!hr;BZSX!q{c^)1G{H)pFdFgSpshk=nnfq{XAp@D%x zfPsODGC^vYgW?QoI}&U-6+DaLNEToOc{5Na6j2POyYFjFwzKRnU$gh)-^+XjESxwJ z2&E|n?njA8XP(b{Vzu&f_Wk;sbUt`bnaADAyEhs86o^Tf6b1O>_1_cc9k0loDmCF| z?xCgW4-<3WidWBhynka=(eq7PnPnW|uEdi^a0C!Bu6O8+v-aL(@Y>LP-p75t^}QA8 ziF;Bu9eQxW_1)~XkC*;BII*lj*#nVF86uddmQ@j6V{m9TlYcPxdhGu5`(qC+z4PSU zr41LZ;fZ@oi9hY-a@5zK!$5~{(| zIO{W;w=O6n7=3u6p4>8%VH&WsASM(Y5OGhagx-9!rlI_v-nF}Y5C>D;0p-sJ@{ngw@$D^zd2&zz$IEf0AhLcx9_*d7=3z9$0`)*JEzopr09+>%9RL6T diff --git a/crossword/public/apple-splash-1668-2224.png b/crossword/public/apple-splash-1668-2224.png deleted file mode 100644 index d4e28b3d1084ae1437ba0f8a569049820ffef655..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3783 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 diff --git a/crossword/public/apple-splash-1792-828.png b/crossword/public/apple-splash-1792-828.png deleted file mode 100644 index 215965c5ea4d84f4656ff86debbc479903c17039..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1894 zcmeAS@N?(olHy`uVBq!ia0y~yUCgk|Ge!`>@*eP#Y|oU3DIKX@@v zU`|Y_SnIie=lL`}YWjEo-uu7$mM$Io;2j1>+_1dVkz1*b=|KEB~nWL5c0hDg@d${y2H1 zIx*#k@1DDF=Tv*%Kl5X6bKA9L$Baz z%x=0PD1M~x)9fjtPfgw@sDH2g#_?1;S5{@#?AG7>Go;m`zIm^`d*E>K3%7UcO)}+9 zu0Qa-si=HaefvxAmECG{Yi93pJr%juK7H$r#qGyuod~;L*=2w9o|nhM`%a)BwMgx;Z0|RRS1FOIS z2CfAROh7>q2aq5W2QI+?21cMtE(MTEun8cQ7=jqa4i>vCCaCRey13%@)Uer0WugAW za6PJ*uADb(csMP;XW5)@ANBG!UwwRw4_Dx!`!X;5bIGOK*?ZVO-&}O~S<#DaA5%5r zX8p4`tsi^zu*r=^T)~eTCx5*{`L-{)IHj`CM0M@F`Cj31@|P#aSK6B6HUKUDLjJ3L zt#A1xbE*AQ|4Ykt`t522v_gUem>z5w&1#S4`m^f8`h6z6lCJuwQYQ22R)t4p$@3 z9@foY|4}Z--a1p7|MQCo50Z6^jOB$vaeSQ_{cPd3@)v+P#_jwqd73zdFcSU+`anoS%?vM)olb{)YdJW`q zNOs4f0THgR_^-5d-FuT6zlS}3-yi<|*Oy+EB(h@`7w|NL63g3HXIZU`+5Y>}($KSt zHyi7mP>UP%90MtX4m>n%cqj()8oqdi1P^u-5TzJ)XVInA diff --git a/crossword/public/apple-splash-2048-1536.png b/crossword/public/apple-splash-2048-1536.png deleted file mode 100644 index 84fa1f8db767eb45bdfd3e8d8b8fd635e8fb4b1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3240 zcmeAS@N?(olHy`uVBq!ia0y~y-~ck&7?_xW)ckJ_{}>n;H8Y)^1A?+ACM+A*8unhf>nrnz<6Ipx`@xHW z0&`+Y#ahq(JI|--QPaQs_ul{2C-?4dJ|nd0j7~{)VaWQ>HP0SSJUU}%oW06jwS*s8 zX%&%wm+PHfcB}6G-UHM0~4@zu5Rw2;7@yE$4 z)rl!TeD~aaJEz+7{+SH;$*;xw0y=X1D(4pCPRl_04f2v>uk2QvTQhr)>#4}K_UT)1EN(wO>qOY~$}an(_q;q7-gjaLh8<^s zPlzj!emNub|NsB%jm2&(`XF{HM_u0U z^qk*7N6GsZ1;-!PKFCeGe8;$hi4ij)SH!34HOoD2RXU=5{{|iF#Ow zGcZvjAu%u_6*CFWz^Dmj{Vzp!Dy5Q}FoTEmfFNK-1%K+c$^C2p&l4vmKuNbAXZrc) zHuLm??Vh{N8a;n^>A9c%-RmE{J}?n*E&2B2O8*CDYaQZhleqmQf7TcOUBxlqzq%ir zUHZ=O@8rMKO8f#1+C;khM6h7p|Jmo=V;+{y1xtgd1OIdg!IaPtdY%PbjL{iT4CDpMPO}~BZ@%`OQB`1(lqZ@^^#Hh>P z7Ud1Dt)hFe*!6&q>J<|)#SYe3L`ouLn?Z7Zz+ov9Ru>X5J>lv;M%g1FPcOOZY-a!h MPgg&ebxsLQ0C*WbWB>pF diff --git a/crossword/public/apple-splash-2048-2732.png b/crossword/public/apple-splash-2048-2732.png deleted file mode 100644 index 4727f5366238ef02dfd3f150d29ac8d446424118..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5185 zcmeAS@N?(olHy`uVBq!ia0y~y-~ckY)-W*xDYK5>@(c`&nwied0m0dsi3|)H6Kf|L zdLMQWIbJU&e$g@D;z7j=M_qFl=j!fQBHEq3I^gal6PAr@4STQL^_BU&R*rNTEdU4 zw2H{T%k|DKyH$7p@~@SG?zdw0=X5`B7L1q3>-}-7U`zb^uKc6g2PL*2s}N}4_~Yc2 z>co^EzI*Pzom1_3|IClQ&286~9WyfZd&($Vb1k#rkm0%&E`qapD?4pMU3XhNxe=5i zGP~)Hp!ku#PqU|pJ~ertp#Ht`8^=@aTv?S_vs-`j&yZG&`sTg%?t#O_FWla(H_4Pc zx&FZSrlRsy_3bacS9Yt-t(m>Y^;G0q`}D0h7PlXtbt3F~WtaWYdtM$3?>n&r!;Uk+ zC&U##$q=a7=-qDx;TbZ+lH6!kv$6&z3fLoT1G8gv0 zx_U3?6<6N2pHbhE=WWkediMSr`${}cM|B(n0~6Meqeq#ucZC;s`~#^=ds5z;%6&es z|MPFK_}ZNFH)r$uZrOAjU+@nWKfEaYYIEEs|DxCI{gL%uChb6%AMCHZv-y0#wF~#` zC#>WZ;tUdu^o*4kYe7lKMz8wcXR)<4+rYs;X%AIgz(CX5D`a2QOKzD(ty-?y{lC51 zE#($Z_5H*|^*WQGL6FM+SRlFjxnLmiBqKmykkiQUr_+p%G)tU0M3_#%N>gTe~DWM4f?^NS& diff --git a/crossword/public/apple-splash-2208-1242.png b/crossword/public/apple-splash-2208-1242.png deleted file mode 100644 index 1dd90509add2a3ef3377f76ca53f61bc29ae606a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2765 zcmeAS@N?(olHy`uVBq!ia0y~y;8?)Gz;cU;87Lw&CB>hCfl)Kl**PFMJ2R1iL1SX= zL__bx4kE|v#l$Z<23$O-c;TpP?&4hC9ZN*JvsVY)y=20&ajjwRmAk$&e>l$7F|!}M z7$`6&rc|u;+`sdDnjSU%yMOQfUwv}#?&dQ>o6hKzR2PP<4_))@;l!gecE;JO+*M2X zk(E{v`FFYA*=4ut?qB}3QqcWY%>JD2=gorg5_!EpZWU~aU*DB~RQsUB_G1+S?Hhlb zyi%Q*^22w}-M4e9J@23SvA4PH+OlItrhZQuWoxcw7928Mx57nmHg9F8O{nW`izhdN zQbcAq-4PT&()Vfh6w#+9?-SI&SAOGos+}vVGHZ70Z~ht5YEj?3*WNvFxcG(JyY(iS zawpdx_})}hzN)_crT5BiwYfF3_qd*lTx*}c^~U1%#y30{ha(iZuh%41PE|BIcjjQE<~^tL||eHA}vf1Ss<$^zyYKV z%o5;2u$&lSVnC%(S+FLc4wylsh64lRhsIj>g6+8{Ewv&7KfI!DSUJtym%wymPI18Z zJ#VG{PO-?4U8_;?mAb*cu*P@A$93COZ+Y-Osp45F`>=jpVd1G6qTn<^#dPvedCS#1 zCuM5i1q<)IW_W8xkagZ3H&fI)o1SKO>T==(*1OSO)Lga LS3j3^P62B7IG2-wfl)Kl**PFMJ2R1iL1SX= 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 U`CrEJjsXZfUHx3vIVCg!03;z25dZ)H diff --git a/crossword/public/apple-splash-2688-1242.png b/crossword/public/apple-splash-2688-1242.png deleted file mode 100644 index ecd0c88472bbf233ad1702eaa0ee1579c57ec01f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2832 zcmeAS@N?(olHy`uVBq!ia0y~y;A&uCV7bM_3>0BqdM}QFfl)Kl**PFMJ2R1iL1SX= zL__bx4kE|v#l$Z<23$O-c;TpP?&4hC9ZN*JvsVY)y=20&ajjwRmAk$&e>l$7F|!}M z7$`6&rc|u;+`sdDnjSU%yMOQfUwv}#?&dQ>o6hKzR2PP<4_))@;l!gecE;JO+*M2X zk(E{v`FFYA*=4ut?qB}3QqcWY%>JD2=gorg5_!EpZWU~aU*DB~RQsUB_G1+S?Hhlb zyi%Q*^22w}-M4e9J@23SvA4PH+OlItrhZQuWoxcw7928Mx57nmHg9F8O{nW`izhdN zQbcAq-4PT&()Vfh6w#+9?-SI&SAOGos+}vVGHZ70Z~ht5YEj?3*WNvFxcG(JyY(iS zawpdx_})}hzN)_crT5BiwYfF3_qd*lTx*}c^~U1%ikM1c4MwmxJR1AjQSxxupKA1)8~CfI&F^6+2)+mERqE)Nf;feS|0 z1#S&!A>Vo-ktKg&DtnhSEINty2a_oiGzDO4F)AyKE4Z$7_TAp}iUqUwN)wxM8l?y) zwpPY!!{!@Ll+J!S_5GJc9w_}$DSlc7+XcV4iDk}vxc>F~tqU&mQYoExIrK6#9#YwD zrN5%MKk~v$Hhk{A!U4@`L?+RP`8TiroLw<7ZTHK)I~+mj79;tiWls!wB|Fn7OY4uz&d!p(@l39Jdo8sS zas2Mpo1-sOp4*$4^8eYd_?p+{Ma`fffx8^xGR&|?P5?Nx7(6g&is7yI{hmMXbp8QC znFMATuF3)?56Wu>ht{3rezbJ9sm+E~_HA!;aAa;Ga|TcuN@lwD_9#zO*@JaoXPH_* zc#I>jU?xLiY;(JDwbBuo0n^^2CKjU0dh7+?!=-Ot9pNRed}GOCf!53>LoBotenMpfRy_ zqM`R;2a)6TV&WGa11=s^yl~VtcX6)njwPbq*{cKYUNT|XxYn@u%3WWXKOE=knAs0r z3>26XQ!3Va?%#PnO^=%X-M{z#uRghVck>ycO=omUstZHbhpu_{aN^M!JLBwC?y4pH z$V#h-{JUK5?6O;R_b>liDd>JHW`9oi^Jc+#iM-w)w+gnzukXq~s(nym`>_gv_KiPI zUa3w@`Qf|g?%O%lp7+oE*xTH8ZP_s+Q@^K-vNhK-3l15sTj3%&o42ylCe(Ge#giLB zDI&9*?g)w>>H9Q$is)05_X+CXE5C6()y|bwnKirhH~$Q2wWx32YwsR7T>QfA-FlNu zxs&S;d~Yf$Usd1!(tBmM+T5Didt6UNuC-6!dSh|>@mVLru2**1AHC=0vGBeVJ231x z1AIbUf%MB6q5uE?UvDgSlYxQ%h^LEVNX4zUS2h+db`WVuEN&JmGGC|_)5NUn?4haU zx{zIP2TSNfwJDP;*w!^JmwwMNpQR@LM_c6EL+f=faqAqCU(c|%DeR(FhUk{AFvryk zLbq^5v94VddI2Z`lyF=Pl;DbT#V2t=3#bEP#@Z@Y!UloWW3vItAh2UHu4+3FYcYyb z(G3Fo%5`-dBMz^jtB3m;T>>e1qP}urh7nfvpuoc_ffUXcSA{nq2R0t{@W93$VO?Q+ znV_+TZqNt+rQsXIg6&TmtfOrusWZQH z?5(dqcHM9K)>Aijgd-(QJO+YOF}0F6>E3u3yzXzqb?-m&Z#RWW-m0kUJHGpSuBXLvkARMg|m#7Kst=58bjM9{-i?xEUKTV+ySJ^s1xbkwrG9oQc;4G zFL0LSL|Kbg9}toFFe{2f#v0d)CMf-!0V->-dY+g>04=|XC`O40lz~Wkl%@dmsAy%? z0F{Z~-`*v%ek8r*qDI<+7ct*8FP-AA*n8yv#s0{!^`GV5r~SORu-E?SLDZ6CP$gZY z7R85n(acQieQ}%Q?#pkMTmQJQ^w(Ks-dvmaz(RfY9{u`)^~5!y1~}>6S?+Ou*XEV| z&woE=HZ$JxanI>!U}1jsC8N94?23QCp2cA=MUV;$c!Yvmgh&#I)B>+F2n7jRnkKq% z_%X9+@pHM^YmTqZn-;%!xzC;eolXt=psiS>sK-&}kmJiuc~8UsFOIcY|M+7; z^1Y{jv*v!USnmHFsr5`7dvTVqI3>`7(slKRGhJJ@$j0k${^M>x?f2QXvS~vp)!(-H zenr3h{qFK1Q)x~rZveJT$ju_?853)(0-NE)G~EWiQGqL)5K%foN;o2#GT1@~sd_>R jVIpkBQ(A0dt7o)I3Ypz{ogo3(xo7Zn^>bP0l+XkK!l8SP diff --git a/crossword/public/apple-splash-640-1136.png b/crossword/public/apple-splash-640-1136.png deleted file mode 100644 index 991deef2e8a74bcfcb33b10883ba7acbc5195f4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1240 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sU@2f?28ysh>LoBotenMpfRy_ zqM`R;2a)6TV&WGa11=s^yl~VtcX6)njwPbq*{cKYUNT|XxYn@u%3WWXKOE=knAs0r z3>26XQ!3Va?%#PnO^=%X-M{z#uRghVck>ycO=omUstZHbhpu_{aN^M!JLBwC?y4pH z$V#h-{JUK5?6O;R_b>liDd>JHW`9oi^Jc+#iM-w)w+gnzukXq~s(nym`>_gv_KiPI zUa3w@`Qf|g?%O%lp7+oE*xTH8ZP_s+Q@^K-vNhK-3l15sTj3%&o42ylCe(Ge#giLB zDI&9*?g)w>>H9Q$is)05_X+CXE5C6()y|bwnKirhH~$Q2wWx32YwsR7T>QfA-FlNu zxs&S;d~Yf$Usd1!(tBmM+T5Didt6UNuC-6!dSh|>@mVLru2**1AHC=0vGBeVJ231x z1AIbUf%MB6q5uE?UvDgSlYxOb)YHW=q~g}wJC1xzh8zqB>Mr^J@_H{Vy&$Z6_QPGV zGvYiGZDt*3U>WKlhec*G7GJ#KKBLz6uRe}R00V5$H}H~iTy5Zh0S^DjQy z86Q6Zi$ObNB}97^F7CYf$Q8po4hM=4Dmg4N_6E@vUzHj# z!SjaAj4xg_AANIln{Ng)MhG$GF^4_8sd1edGfCwz|0!j_)G)+E_H~NCnsIi8@XWcZ QfVqvq)78&qol`;+04OLWvH$=8 diff --git a/crossword/public/apple-splash-750-1334.png b/crossword/public/apple-splash-750-1334.png deleted file mode 100644 index 226a13901348c249a7a439c9178d2b1d1aab7c69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1588 zcmeAS@N?(olHy`uVBq!ia0y~yV0y>Ez-q?C3>4YE^Y&B*21dw7Zbne7;y2R;)SEGxr=jkcPtU@&R!jG_mTG*3Z!4o2>t*6|9WGwn+yyr8$DedLn>~)y|X*_uz?6`fNF#E*SJH$6)fVP?4(?m zF4PQeJ-azx<0rF_<@Ti?Dpq`Jl3DlnnbCp6qW~5H-<#*R9sk+XjuZSB_%|n~`-+f%{r@eRt#A z^uPZ$h&WU_T{)5nbKCA{&+7mpH+?=@bCYF^ZxeRIB_|YpZ~td zzBd(7^68!Ir|srHj>Hv44GPR&u1U{wZGix$nW}%>twc{ zjhQq{7^mC!#r}!8EV3;3!M5zS54eH+dZYI-FQo4mgy9UzA8sFBCeN;Z&%5q+*U?GS z_la=f_H_K@Hwn_kcRU+#hIqdCeFxkr26re8SDF6@{xdAy@%p8v^<`6FNygym>gTe~ HDWM4f_l2bk diff --git a/crossword/public/apple-splash-828-1792.png b/crossword/public/apple-splash-828-1792.png deleted file mode 100644 index 96c9ff8eab65cba3ab5ca2415470deaa169127f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2048 zcmeAS@N?(olHy`uVBq!ia0y~yV76gkU}s=r28v|luiL@Ez^Iw&>>LoBotenMpfRy_ zqM`R;2a)6TV&WGa11=s^yl~VtcX6)njwPbq*{cKYUNT|XxYn@u%3WWXKOE=knAs0r z3>26XQ!3Va?%#PnO^=%X-M{z#uRghVck>ycO=omUstZHbhpu_{aN^M!JLBwC?y4pH z$V#h-{JUK5?6O;R_b>liDd>JHW`9oi^Jc+#iM-w)w+gnzukXq~s(nym`>_gv_KiPI zUa3w@`Qf|g?%O%lp7+oE*xTH8ZP_s+Q@^K-vNhK-3l15sTj3%&o42ylCe(Ge#giLB zDI&9*?g)w>>H9Q$is)05_X+CXE5C6()y|bwnKirhH~$Q2wWx32YwsR7T>QfA-FlNu zxs&S;d~Yf$Usd1!(tBmM+T5Didt6UNuC-6!dSh|>@mVLru2**1AHC=0vGBeVJ231x z1AIbUf%MB6q5uE?UvDgSlYxP)*3-o?q~g}wJ9~2v8wjukC^<-dk2@5c!6JUnPRe!Z z0?pu{qm4_x3SU0=JF06}^&0-^b#~gF?W15AguwF)JS%5t^Wz8p?elb1wNGx(_ZE;N z0Onn`?JqiMIjMGfedYOhw+>DD>v7El!s+>Wo1IU>cRqPlp;P^-{*-gyQN=Eh49B0|9Nej)r=FK{yg*ana*S0IfHSXo(tTp-EC#r5^; z*I&PW{r~^Jvv0#mpeS2OkY6wZD;H4oW z@)BpzDTb$dk`3J45+|4}BNTHIDw#LO%x^hs`ZxIRhBM2LZ*I}-icj44uKcdR#AlzZ zG!34g@1FW+<1a(eU0$Dbf0&6YIQE^7nPI*9=j~Mrdiv@7jhRjJZ`{;jc(60wJ(A%= z7ORP8!?(6z<%V*`Te~js&Cv~d{7iPmd;0}{n9uC2XMg(j;JcF3$4?o^9Iv_?l-#0d zagJZ-eAkf$$F?*|cISVLOb~dqDQ|YX)13BIE5h%|vl!VW%ByHQsP-{E&{%)qJHy_F zj|{u+Yclw3<$mkpQ|P^^+4x6Ga$&#Hqz`N3GzDFxoTuu={$uL&OZ&~p{BP~oM~j?p PfkM&K)z4*}Q$iB})MuL1 diff --git a/crossword/public/favicon.ico b/crossword/public/favicon.ico deleted file mode 100644 index bc6e13d81d19a1cb0d91e8d30e30de67cc344cd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15406 zcmeI0!EWp}3`O0vL4vHNi|(^3-tw)w?x%Fwuj=RYbCT6yfMAd*k49Ie6-k!taq&_- z0*q{lyu74n*_mnjVft}893~r|r$2w1rr)M%dVap%fBAWu{<68RU*r0()AaW*)AYq` z%*8ye^ER~P&KA-8ySuxy>7}A)Y@#jp2tIH4-QVAz-`?IX()0OzL3PiF4Yv41-{9kI z&F1p(@DO~N=bAbEyH@lwxts96qvv3%<&q zyu=TCUE{;G@VLg-d_(>e&?MB}=56^}Km6eAaig#NRR1f&^>5e<{IHGu9)!VYX6#=} zLJv6q{Js)%l|oOFuQ-j4rCQaf5G&Uw_QO=q+~`RBrvh&NtZ{!d#Xc#qkH=$pX4R)7 zBfemG%y>;KabPUwsq>1nEsq-xJol%x&Kwwp`uVz0-)f$U?QuM>rS+T__gP&6Kjur~ z>_hL7ODn$BGT>?7YM9_)EBjJ>=9IVWYPdZ-#Rc2Z_{w`JzWB<%mRC=F_~I`mHu$yr zGcSJfQO!=ubM$q{;}hF#&{_XzU$A$1 zm-4NIPSbFhoy*D->=<8p&FaNEw(tkT6s$E8>R^uGgqW&>i;oQ&-jkJeTV5?-lS2!C z&-f5u!e3ekV~Z@|FRhQn2FokHOZZFcz45_G=TN*Bm;Yk_Lmn14KPQL#Z{_os>d4vQ z&X1wzPiL|7?>&D~d_Bi8kJ)z$YlY{3c3z6ZU>#=hg%R^;oj>#!zO!>M^QVSObpWH9 z2f4U?31@$dYeSHxf9HAJ%){@8MIfP#gFrFEaku)zSVxF=J4K2z$QkEL*(fBbB!+c#^G>C{NLZ- zt9!s&EY^kB=0FKo2>FIxLe7?eA+_V2{bs;V_b4a;`d@}{g?3JEVWqqH%9aQyQUwO_>AVO=Od2!``5N12zjtJ{64DnOlkj_^F2`W z^)?wNIKnj-=01FX8vg9NLbU~7XNcb;-&fjmjU&E=J_&suIhOd75A(UE@46Px61TYD zAKuG(juqd$PdqQU_6mNXuWEbk`=qw-D|~Bx(#uzUgBDF!(#+PlLmkKjc4zy0)O#HzHr4Z zKZ^|WRy|Y88xH=AqCY+pfAS&+l>gzV*JtBzl!J`+!>GOD>@|+^8MWVww{S=KjM^*C zUgIdAQTwgDiC0qLj`A6`SDZB?vHUs7IfMFl;wYa{`!&4flVaIKdQaK4f4?M~&iB!% zy|}CyQ!M!FJHbZy>$kg`o&P(0KP3Ke*Y8W&_Z7Kpe*f7g zM|-Hs-|h7p{^3lZ57|rVg6dnEYlrW96+RJ@sy?@J@@5I+58SUUIc?GfzIp1t;co1P2vZPjfA z-~VQ{YcCn+UjAkpggnS|_J2gJSEDUHy@GAAmvnw)J(K=tkA6$Bs`xiNmxzI;UgDYc zYHOlt6yJPg9lJG>@co(A=p|y02>79wY=mb?N1rbrKjCxc_;*~T oS4a61ix^AlIgMTmyQf#z@E_%~)}B0;di6X1wblLZ_rIvXKNs9mh5!Hn diff --git a/crossword/public/index.html b/crossword/public/index.html deleted file mode 100644 index 14969a5b..00000000 --- a/crossword/public/index.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - Crosshare - crossword community - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - diff --git a/crossword/public/keypress.mp3 b/crossword/public/keypress.mp3 deleted file mode 100644 index 502890c71e5404edc65f6ccdca53cb5c6a19a52f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2304 zcmciDYf#czAHeZ{0;Q&irjLlGpjd)NSYFCo;G*jV@sfBerbN_e)w~p@^%y8FWLhX{ zYPyQI@U~KU%k_e3re%d~>bBWxu9vhm?Y3%bve`4tJoCJLX7)34-kkH|_nk9m=E(o@ z?*smqdrZWsFMAqa)^-3;0RUYEoPvu2P2se{C53W@c7{s2mI#2f02Lfhw!)9f6xTB{8Cwe0nY}UO#=7?L@^m- zAo)wm(^igRp*hy3-3ark1-kbBmiOINioeLZMJ}=I~FoZY;}x9nD$sZ1!F%s2Ul%Q&VUo zQz5H6`xCVH9|*FC&~q@MhM=$}8_VWyBfL{+`Z+9`7!Y(c*S0G^&HCp$+xz$BA8HZw zv@zp=C0br?eOOi8;Xcb+x5RH}BK^OoGQ}(s5$p%-=At(HA#9b^kD;^RKw?Z0cD+|! zupUwdEr=a>njB)KQ{-fueyP%F{Nrz9(MBwXCP>r*vYc(>g#=TWH(M?-e#_ruIRCm^ z^R2#hbPrsu3r~H#D8J|1Z)%t)7$PW<02(aD-2i<2cn49Pdd0=L?ZYajf}v+JJReuD z?PyHb@rWx^ITYA2Ftpbnzp?hD0fM+*TWIk^u&cwW$4iR1Ur-Z=;d0WNALtWieE1m? zBeWb3W^FYIkCuTk(y#r1YP~OY7EbB$<1cjdqmh=pEM@kZ^0!;3`%<&DCC;{vhh_wJ zb+BTNJB4~U$ftlWhF7(7P1LvlpmFeF(V0_-Irx(n$+c0CC4wCs4|i~^GFk~=VqLU7 zxJZS*5r^+|ftdM`HNi3`hQuB%kb$a+NVBByvL`An&oR+{u{2&sEMXkJ4{x9yRPG8& ztpK%JNknfSf%aAH&LX+4sN;ODJpde?vMUlrNt)eny% z#^}$t^{NlRIY1`M?Bd+K#~f*@U8zd zvaBd2EN&_jseAT(A^!$(Sd0hDCKfCgnYD@eB=Z{2oxp*3X%U(;>c!QEep9wg@t|6~ z{6I3Q@aQ3wkQ0X-9_gY{8KtFL7kGn*l26j0g{@ZY=k#0Kk7g~Pl&X=?^fpmzg#jzQ zSknkEKeIjQYD!V^?GtB7d!OlhetrLJRoIHxy-Z)CXVLB(T>kQoyNKesZwB;@b7i24#fU3wpKDr!7?kD~% znh+;w%H5B3whW|7)yxIccX5B_4GS`L;Ak7*_^fh>Ykon>pliIhv7WZ4`H8-Cz2chE zT+O@(R@4w)MP9Bn%X{TLk$<|m&plgDS3~bnng+Bjjvf)#NliS}(AUYX{nBpls%rR$>h8&&IB5(re@RQ z%NPV>n(OGJGYI3e_m|<2AxP$8m|F69?$TH^Bf2tI`PQBwfx1$i4hjcSePbJWwP9?$*qTaL)1=tvx@S>F=((Pojs?2F(Z+Gl)gT`qhXx4SJMHJP(J?w3*I&3 zl|O8XB}-5l(?kj)@x?xZD4WDV~vvkh)_ZuV;dD(DsS3Ou<-S@`>Og1W#% z4f}vO9S{BVqQuuCsbsArvE~$|iFNMe)xuq2q|4qlv*z-T8OB3yz~q56lR5N#9Mtfo z^-V4Q$5=0vz}Ewtn2vwXn}h%P)`D^++2tNxYs(p5d(5B{<#j9s)yd}N3pj})}ct+OhW(mFmY@q)*b6}#%_pRK0i@8_Uwpi^Yt`I zw?5rrIL2Xq@ru!g{Au?)Q9(wK-BZJqdBb+4W6{@Wbqr;-7g;tpP6xacz318PHOp0o zYKvi(?_ROammyV3Vdp|57liUD4f(`+;l9t^KSeW;H?%|}?CkF@Q61b!+FIhX*p;5c zwFd8Qw$v;O7aU9>xe)vEGKSKmjOl%(+-LOq+LC-VA3(6B0OtxZf%5o0t!w{gzY}|_ z706r!CLovD;Y|P^EVoz`H)t++B_$bli}bR84K-$#@7w=5zM31k%zwy&^$__xiP^q{ ux3%dmWch02)Ks(L>%>`8|52O$G|+&>yBQO3f)oI(*Z=_jM+^SHVSfXxUh_Wy diff --git a/crossword/public/manifest-icon-192.png b/crossword/public/manifest-icon-192.png deleted file mode 100644 index 572c145b91b35a7fcda4ccfb221918ac78006ccf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 616 zcmV-u0+;=XP)3 z2f@)%5S(0f7KP6K&kiX_e2=^Pz5jd3y}N_V60*!JbbE1BuT`w(;gQ9dxn4gc-6ml8 zYG!y@{iiO?rrLhr)B33qN7`J!Y>VfE5nmW?jriJdv|q1^Z^bLY7`MiF5Qnk&$<%vc zWcW?I-R+!vN#D%)y@Q6L=l?}d5Jt#MY}lU*ivK^n}XaC z7sQV8o0JvhH1A+1_j&dR6lgj z|NpNu7TEv*0Od(UK~!ko?b)#n!ypg^(4Rk;SngDeLM=K^`}g67;s`sNtFYA zDhuvaKDa~R`Qw0qfPgmu8hVP@Ezlm&bF+YeFYp5Wbi!N0KWWzh0000Cgk|Ge!`>@*eP#Y|oU3DIKX@@v zU`|Y_SnIie=lL`}YWjEo-uu7$mM$Io;2j1>+_1dVkz1*b=|KEB~nWL5c0hDg@d${y2H1 zIx*#k@1DDF=Tv*%Kl5X6bKA9L$Baz z%x=0PD1M~x)9fjtPfgw@sDH2g#_?1;S5{@#?AG7>Go;m`zIm^`d*E>K3%7UcO)}+9 zu0Qa-si=HaefvxAmECG{Yi93pJr%juK7H$r#qGyuod~;L*=2w9o|nhM`%dh@u;UEy z32_C|FK2}Q|NnozvDi&uNIvy+aSW-r_4ck|-ysJc28ZSn-oLd?;U8Lb57k$2r5@2< zzu;!@o?F}+UPV2(kKD^#TQPUXIvGZwF<8NdAB>lOupvp^P+s73x~4R!?Rj>;mK%~p zLpa0vIl-~d!>lJw@pf^Mc|D~XNN)Otq=ex%>y)(Y(sfyGmwM;Eo)aez@&ycR5I?Z@ z-7z7DnPthzn@d4y0|sWWudtr|Fux)CGqWH}3`if?$KZ*?MP;tx&?sK^5?Sn@q(UU` z^cY*OdHZY6b0B0MOlF+EtEAZY_TtL4O*3`V-mXFRZUnzU3Ez*e=JPLa@wYA$+Wp^> z6`_^khJixlth;s!QBp{P4co03Z?tqU=SH%GVMC&WneX*I?pL}Cqn<7;v_q(G=x0$0 pOe)jaA;pIfWJusWP=qs5J}@6XzS;lPi_ID!9iFa!F6*2UngD$~DNq0a diff --git a/crossword/public/manifest.json b/crossword/public/manifest.json deleted file mode 100644 index ca8d09e0..00000000 --- a/crossword/public/manifest.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "short_name": "Crosshare", - "name": "Crosshare", - "icons": [ - { - "src": "manifest-icon-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable any" - }, - { - "src": "manifest-icon-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable any" - }, - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - } - ], - "start_url": "https://crosshare.org", - "display": "standalone", - "theme_color": "#EB984E", - "background_color": "#ffffff" -} diff --git a/crossword/public/robots.txt b/crossword/public/robots.txt deleted file mode 100644 index 01b0f9a1..00000000 --- a/crossword/public/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * diff --git a/crossword/public/staging.robots.txt b/crossword/public/staging.robots.txt deleted file mode 100644 index b21f0887..00000000 --- a/crossword/public/staging.robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: / diff --git a/crossword/public/success.mp3 b/crossword/public/success.mp3 deleted file mode 100644 index aa24d6e57f4eaf747ea0b90c952f3d591f53d5ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56841 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 jAX3I+ { - return ( -
  • {props.title} {props.didComplete ? "completed " + (props.didCheat ? "with helpers" : "without helpers") : "unfinished"} {timeString(props.playTime, false)}
  • - ); -} - -export const AuthoredListItem = (props: AuthoredPuzzle) => { - return ( -
  • {props.title}
  • - ); -} - -export const getDisplayName = (user: firebase.User) => { - return user.displayName || "Anonymous Crossharer"; -} - -export const DisplayNameForm = ({ user, onChange, onCancel }: { user: firebase.User, onChange: (newName: string) => void, onCancel?: () => void }) => { - function sanitize(input: string) { - return input.replace(/[^0-9a-zA-Z ]/g, ''); - } - const [newDisplayName, setNewDisplayName] = React.useState(sanitize(getDisplayName(user))); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (newDisplayName.trim()) { - user.updateProfile({ displayName: newDisplayName.trim() }).then(() => { - if (!user.displayName) { - throw new Error("something went wrong"); - } - onChange(user.displayName); - }); - } - } - - return ( -
    - - - {onCancel ? - - : ''} -
    - ); -}; - -interface AuthoredPuzzle { - id: string, - createdAt: firebase.firestore.Timestamp, - title: string, -} - -interface UserPlay { - id: string, - updatedAt: firebase.firestore.Timestamp, - playTime: number, - didCheat: boolean, - didComplete: boolean, - title: string, -} - -export const AccountPage = requiresAuth(({ user }: RouteComponentProps & AuthProps) => { - const [authoredPuzzles, setAuthoredPuzzles] = React.useState | null>(null); - const [plays, setPlays] = React.useState | null>(null); - const [error, setError] = React.useState(false); - const [displayName, setDisplayName] = React.useState(getDisplayName(user)); - - React.useEffect(() => { - console.log("loading authored puzzles and plays"); - // TODO pagination on both of these - Promise.all([ - getFromSessionOrDB('uc', user.uid, AuthoredPuzzlesV, -1), - getFromSessionOrDB('up', user.uid, UserPlaysV, -1) - ]) - .then(([authoredResult, playsResult]) => { - if (authoredResult === null) { - setAuthoredPuzzles([]); - } else { - const authored = Object.entries(authoredResult).map(([id, val]) => { - const [createdAt, title] = val; - return { id, createdAt, title }; - }) - // Sort in reverse order by createdAt - authored.sort((a, b) => b.createdAt.toMillis() - a.createdAt.toMillis()); - setAuthoredPuzzles(authored); - } - if (playsResult === null) { - setPlays([]); - } else { - const plays = Object.entries(playsResult).map(([id, val]) => { - const [updatedAt, playTime, didCheat, didComplete, title] = val; - return { id, updatedAt, playTime, didCheat, didComplete, title }; - }) - // Sort in reverse order by updatedAt - plays.sort((a, b) => b.updatedAt.toMillis() - a.updatedAt.toMillis()); - setPlays(plays); - } - }).catch(reason => { - console.error(reason); - setError(true); - }); - }, [user]); - - if (error) { - return Error loading plays / authored puzzles; - } - return ( - -
    -

    Account

    -

    You're logged in as {user.email}.

    -

    Your display name - {displayName} - is displayed next to any comments you make or puzzles you create.

    - - {plays && plays.length ? - -

    Recent Plays

    -
      {plays.map(PlayListItem)}
    -
    - : - "" - } - {authoredPuzzles && authoredPuzzles.length ? - -

    Authored Puzzles

    -
      {authoredPuzzles.map(AuthoredListItem)}
    -
    - : - "" - } -
    -
    - ); -}); diff --git a/crossword/src/Admin.tsx b/crossword/src/Admin.tsx deleted file mode 100644 index 642d8d60..00000000 --- a/crossword/src/Admin.tsx +++ /dev/null @@ -1,209 +0,0 @@ -/** @jsx jsx */ -import { jsx } from '@emotion/core'; - -import * as React from 'react'; - -import { navigate, Link, RouteComponentProps } from "@reach/router"; - -import { requiresAdmin, AuthProps } from './App'; -import { Page } from './Page'; -import { PuzzleResult, puzzleFromDB, puzzleTitle } from './types'; -import { - TimestampedPuzzleT, DailyStatsT, DailyStatsV, DBPuzzleV, getDateString, - CategoryIndexT, CategoryIndexV, prettifyDateString, DBPuzzleT, - CommentForModerationWithIdT, CommentForModerationV, CommentWithRepliesT -} from './common/dbtypes'; -import { getFromDB, getFromSessionOrDB, mapEachResult } from './dbUtils'; -import { getFirebaseApp, getTimestampClass } from './firebase'; -import { UpcomingMinisCalendar } from './UpcomingMinisCalendar'; - -const PuzzleListItem = (props: PuzzleResult) => { - return ( -
  • {puzzleTitle(props)} by {props.authorName}
  • - ); -} - -export const Admin = requiresAdmin((_: RouteComponentProps & AuthProps) => { - const [unmoderated, setUnmoderated] = React.useState | null>(null); - const [commentsForModeration, setCommentsForModeration] = React.useState | null>(null); - const [minis, setMinis] = React.useState(null); - const [stats, setStats] = React.useState(null); - const [error, setError] = React.useState(false); - const [commentIdsForDeletion, setCommentIdsForDeletion] = React.useState>(new Set()); - - React.useEffect(() => { - console.log("loading admin content"); - const db = getFirebaseApp().firestore(); - const now = new Date(); - const dateString = getDateString(now); - Promise.all([ - getFromSessionOrDB('ds', dateString, DailyStatsV, 1000 * 60 * 30), - getFromSessionOrDB('categories', 'dailymini', CategoryIndexV, 24 * 60 * 60 * 1000), - mapEachResult(db.collection('c').where("m", "==", false), DBPuzzleV, (dbpuzz, docId) => { - const forStorage: TimestampedPuzzleT = { downloadedAt: getTimestampClass().now(), data: dbpuzz } - sessionStorage.setItem('c/' + docId, JSON.stringify(forStorage)); - return { ...puzzleFromDB(dbpuzz), id: docId }; - }), - mapEachResult(db.collection('cfm'), CommentForModerationV, (cfm, docId) => { - return { ...cfm, i: docId }; - }) - ]) - .then(([stats, minis, unmoderated, cfm]) => { - setStats(stats); - setMinis(minis); - setUnmoderated(unmoderated); - setCommentsForModeration(cfm); - }) - .catch(reason => { - console.error(reason); - setError(true); - }); - }, []); - - const goToPuzzle = React.useCallback((_date: Date, puzzle: string | null) => { - if (puzzle) { - navigate("/crosswords/" + puzzle); - } - }, []); - - if (error) { - return Error loading admin content; - } - if (unmoderated === null || commentsForModeration === null) { - return Loading admin content...; - } - - function titleForId(crosswordId: string): string { - if (minis) { - const dateString = Object.keys(minis).find(key => minis[key] === crosswordId); - if (dateString) { - return "Daily mini for " + prettifyDateString(dateString); - } - } - return crosswordId; - } - - function findCommentById(comments: Array, id: string): CommentWithRepliesT | null { - for (const comment of comments) { - if (comment.i === id) { - return comment; - } - if (comment.r !== undefined) { - const res = findCommentById(comment.r, id); - if (res !== null) { - return res; - } - } - } - return null; - } - - async function moderateComments(e: React.FormEvent) { - e.preventDefault(); - const db = getFirebaseApp().firestore(); - const puzzles: Record = {}; - if (commentsForModeration) { - for (const comment of commentsForModeration) { - // Don't need to do anything for any comment that has been marked for deletion - if (!commentIdsForDeletion.has(comment.i)) { - let puzzle: DBPuzzleT; - if (puzzles.hasOwnProperty(comment.pid)) { - puzzle = puzzles[comment.pid]; - } else { - puzzle = await getFromDB('c', comment.pid, DBPuzzleV); - puzzles[comment.pid] = puzzle; - } - if (puzzle.cs === undefined) { - puzzle.cs = []; - } - if (comment.rt === null) { - puzzle.cs.push(comment); - } else { - const parent = findCommentById(puzzle.cs, comment.rt); - if (parent === null) { - throw new Error('parent comment not found'); - } - if (parent.r) { - parent.r.push(comment); - } else { - parent.r = [comment]; - } - } - } - await db.collection('cfm').doc(comment.i).delete(); - } - } - - // Now we've merged in all the comments, so update the puzzles: - for (const [puzzleId, dbPuzzle] of Object.entries(puzzles)) { - console.log("updating", puzzleId, dbPuzzle.cs); - await db.collection('c').doc(puzzleId).update({ cs: dbPuzzle.cs }); - } - - setCommentsForModeration([]); - } - - function setCommentForDeletion(commentId: string, checked: boolean) { - if (!checked) { - commentIdsForDeletion.delete(commentId); - } else { - commentIdsForDeletion.add(commentId); - } - setCommentIdsForDeletion(new Set(commentIdsForDeletion)); - } - - return ( - -
    -

    Comment Moderation

    - {commentsForModeration.length === 0 ? -
    No comments are currently awaiting moderation.
    - : -
    -

    Check comments to disallow them

    -
      - {commentsForModeration.map((cfm) => -
    • - -
    • - )} -
    - -
    - } -

    Unmoderated

    - {unmoderated.length === 0 ? -
    No puzzles are currently awaiting moderation.
    - : -
      {unmoderated.map(PuzzleListItem)}
    - } - {stats ? - -

    Today's Stats

    -
    Total completions: {stats.n}
    -
    Users w/ completions: {stats.u.length}
    -
    Top Puzzles
    -
      - {Object.entries(stats.c).map(([crosswordId, count]) => { - return ( -
    • - {titleForId(crosswordId)}: {count} - (stats) -
    • - ); - })} -
    -
    - : ""} -

    Upcoming Minis

    - - -
    -
    - ); -}); diff --git a/crossword/src/App.test.tsx b/crossword/src/App.test.tsx deleted file mode 100644 index a2f686a3..00000000 --- a/crossword/src/App.test.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import App from './App'; -import { initFirebaseForTesting } from './testUtils'; - -initFirebaseForTesting(); - -test('renders todays mini link', () => { - const { getByText } = render(); - const linkElement = getByText(/today's daily mini crossword/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/crossword/src/App.tsx b/crossword/src/App.tsx deleted file mode 100644 index b9d927f2..00000000 --- a/crossword/src/App.tsx +++ /dev/null @@ -1,369 +0,0 @@ -/** @jsx jsx */ -import { jsx } from '@emotion/core'; -import * as React from 'react'; - -import { WindowLocation, Location, Router, RouteComponentProps } from "@reach/router"; -import { Helmet, HelmetProvider } from 'react-helmet-async'; -import { ToastContainer } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.min.css'; - -import { ErrorBoundary } from './ErrorBoundary'; -import { PuzzleLoader } from './Puzzle'; -import { PuzzleStats } from './PuzzleStats'; -import { Page, SquareTest, TwoColTest } from './Page'; -import { AccountPage } from './AccountPage'; -import { IconTestPage } from './Icons'; -import { Admin } from './Admin'; -import { Home } from './Home'; -import { Category } from './Category'; -import { PlayV, UserPlayT, UserPlaysV } from './common/dbtypes'; -import { getValidatedAndDelete, setInCache, updateInCache } from './dbUtils'; -import { useAuthState, getFirebaseApp, getGoogleAuthProvider } from './firebase'; -import { BuilderDBLoader } from './Builder'; - -import googlesignin from './googlesignin.png'; - -interface AuthContextValue { - user: firebase.User | undefined, - isAdmin: boolean, - loadingUser: boolean, - error: string | undefined -} -export const AuthContext = React.createContext({ user: undefined, loadingUser: false, error: "using default context" } as AuthContextValue); - -type CrosshareAudioContextValue = [AudioContext | null, () => void]; -export const CrosshareAudioContext = React.createContext([null, () => { }]); - -type Optionalize = Omit; - -export interface AuthProps { - isAdmin: boolean, - user: firebase.User, -}; - -export const GoogleSignInButton = () => { - function signin() { - getFirebaseApp().auth().signInWithPopup(getGoogleAuthProvider()) - .then(() => { - getFirebaseApp().analytics().logEvent("login", { method: 'google' }); - }) - } - return ( - - ); -} - -export const GoogleLinkButton = ({ user }: { user: firebase.User }) => { - function signin() { - user.linkWithPopup(getGoogleAuthProvider()) - .then(() => { - console.log("linked w/o needing a merge") - getFirebaseApp().analytics().logEvent("login", { method: 'google' }); - }) - .catch(async (error: any) => { - if (error.code !== 'auth/credential-already-in-use') { - console.log(error); - return; - } - // Get anonymous user plays - const db = getFirebaseApp().firestore(); - await db.collection('up').doc(user.uid).delete(); - const plays = await getValidatedAndDelete(db.collection('p').where('u', '==', user.uid), PlayV); - return getFirebaseApp().auth().signInWithCredential(error.credential).then(async (value: firebase.auth.UserCredential) => { - console.log("signed in as new user " + value.user ?.uid); - const newUser = value.user; - if (!newUser) { - throw new Error('missing new user after link'); - } - await Promise.all(plays.map((play) => { - play.u = newUser.uid; - console.log("Updating play " + play.c + '-' + play.u) - const userPlay: UserPlayT = [play.ua, play.t, play.ch, play.f, 'Crossword']; - return Promise.all([ - setInCache('p', play.c + "-" + newUser.uid, play, PlayV, true), - updateInCache('up', newUser.uid, { [play.c]: userPlay }, UserPlaysV, true) - ]); - })); - user.delete(); - console.log("linked and merged plays"); - }); - }); - } - return ( - - ); -} - -/* Ensure we have at least an anonymous user */ -export function ensureUser(WrappedComponent: React.ComponentType) { - return (props: Optionalize) => { - const { user, isAdmin, loadingUser, error } = React.useContext(AuthContext); - if (loadingUser) { - return Loading user...; - } - if (error) { - return Error loading user: {error}; - } - if (!user) { - getFirebaseApp().auth().signInAnonymously().then(() => { - getFirebaseApp().analytics().logEvent("login", { method: 'anonymous' }); - }) - return Loading user...; - }; - return - } -} - -/* Ensure we have a non-anonymous user, upgrading an anonymous user if we have one. */ -export function requiresAuth(WrappedComponent: React.ComponentType) { - return (props: Optionalize) => { - const { user, isAdmin, loadingUser, error } = React.useContext(AuthContext); - if (loadingUser) { - return Loading user...; - } - if (error) { - return Error loading user: {error}; - } - if (!user) { - return ( - -
    -

    Please sign-in with your Google account to continue. We use your account to keep track of the puzzles you've played and your solve streaks.

    - -
    -
    - ); - } - if (user.isAnonymous) { - return ( - -
    -

    Please sign-in with your Google account to continue. We use your account to keep track of the puzzles you've played and your solve streaks.

    - -
    -
    - ); - } - return - } -} - -/* Ensure we have an admin user, upgrading an anonymous user if we have one. */ -export function requiresAdmin(WrappedComponent: React.ComponentType) { - return (props: Optionalize) => { - const { user, isAdmin, loadingUser, error } = React.useContext(AuthContext); - if (loadingUser) { - return Loading user...; - } - if (error) { - return Error loading user: {error}; - } - if (user && user.email) { - if (!isAdmin) { - return Must be an admin to view this page.; - } - return - } - return ( - -
    -

    Please sign-in to continue. You must be an admin to view this page.

    - -
    -
    - ); - } -} - -const ErrorTest = requiresAdmin((_: RouteComponentProps & AuthProps) => { - if (1) - throw new Error("testing"); - return
    error
    ; -}); - -const NotFound = (_: RouteComponentProps) => { - return not found :(; -} - -const TermsOfService = (_: RouteComponentProps) => { - return ( - -

    Crosshare Terms of Service

    -

    1. Terms

    -

    By accessing the website at https://crosshare.org, you are agreeing to be bound by these terms of service, all applicable laws and regulations, and agree that you are responsible for compliance with any applicable local laws. If you do not agree with any of these terms, you are prohibited from using or accessing this site. The materials contained in this website are protected by applicable copyright and trademark law.

    -

    2. Use License

    -

    Permission is granted to temporarily download one copy of the materials (information or software) on Crosshare's website for personal, non-commercial transitory viewing only. This is the grant of a license, not a transfer of title, and under this license you may not:

    -

    - modify or copy the materials;

    -

    - use the materials for any commercial purpose, or for any public display (commercial or non-commercial);

    -

    - attempt to decompile or reverse engineer any software contained on Crosshare's website;

    -

    - remove any copyright or other proprietary notations from the materials; or

    -

    - transfer the materials to another person or "mirror" the materials on any other server.

    -

    This license shall automatically terminate if you violate any of these restrictions and may be terminated by Crosshare at any time. Upon terminating your viewing of these materials or upon the termination of this license, you must destroy any downloaded materials in your possession whether in electronic or printed format.

    -

    3. Disclaimer

    -

    The materials on Crosshare's website are provided on an 'as is' basis. Crosshare makes no warranties, expressed or implied, and hereby disclaims and negates all other warranties including, without limitation, implied warranties or conditions of merchantability, fitness for a particular purpose, or non-infringement of intellectual property or other violation of rights.

    -

    Further, Crosshare does not warrant or make any representations concerning the accuracy, likely results, or reliability of the use of the materials on its website or otherwise relating to such materials or on any sites linked to this site.

    -

    4. Limitations

    -

    In no event shall Crosshare or its suppliers be liable for any damages (including, without limitation, damages for loss of data or profit, or due to business interruption) arising out of the use or inability to use the materials on Crosshare's website, even if Crosshare or a Crosshare authorized representative has been notified orally or in writing of the possibility of such damage. Because some jurisdictions do not allow limitations on implied warranties, or limitations of liability for consequential or incidental damages, these limitations may not apply to you.

    -

    5. Accuracy of materials

    -

    The materials appearing on Crosshare's website could include technical, typographical, or photographic errors. Crosshare does not warrant that any of the materials on its website are accurate, complete or current. Crosshare may make changes to the materials contained on its website at any time without notice. However Crosshare does not make any commitment to update the materials.

    -

    6. Links

    -

    Crosshare has not reviewed all of the sites linked to its website and is not responsible for the contents of any such linked site. The inclusion of any link does not imply endorsement by Crosshare of the site. Use of any such linked website is at the user's own risk.

    -

    7. Modifications

    -

    Crosshare may revise these terms of service for its website at any time without notice. By using this website you are agreeing to be bound by the then current version of these terms of service.

    -

    8. Governing Law

    -

    These terms and conditions are governed by and construed in accordance with the laws of New York and you irrevocably submit to the exclusive jurisdiction of the courts in that State or location.

    -
    - ); -} - -const PrivacyPolicy = (_: RouteComponentProps) => { - return ( - -

    Privacy Policy

    -

    Your privacy is important to us. It is Crosshare's policy to respect your privacy regarding any information we may collect from you across our website, https://crosshare.org, and other sites we own and operate.

    -

    We only ask for personal information when we truly need it to provide a service to you. We collect it by fair and lawful means, with your knowledge and consent. We also let you know why we’re collecting it and how it will be used.

    -

    We only retain collected information for as long as necessary to provide you with your requested service. What data we store, we’ll protect within commercially acceptable means to prevent loss and theft, as well as unauthorized access, disclosure, copying, use or modification.

    -

    We don’t share any personally identifying information publicly or with third-parties, except when required to by law.

    -

    Our website may link to external sites that are not operated by us. Please be aware that we have no control over the content and practices of these sites, and cannot accept responsibility or liability for their respective privacy policies.

    -

    You are free to refuse our request for your personal information, with the understanding that we may be unable to provide you with some of your desired services.

    -

    Your continued use of our website will be regarded as acceptance of our practices around privacy and personal information. If you have any questions about how we handle user data and personal information, feel free to contact us.

    -

    This policy is effective as of 10 March 2020.

    -
    - ); -} - -const Construct = (_: RouteComponentProps) => { - let size = 5; - let grid = [ - " ", " ", " ", " ", " ", - " ", " ", " ", " ", " ", - " ", " ", " ", " ", " ", - " ", " ", " ", " ", " ", - " ", " ", " ", " ", " ", - ]; - // size = 15; - // grid = [ - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", - // ]; - // size = 15; - // grid = [ - // " . . ", - // " . . ", - // " . . ", - // "VANBURENZOPIANO", - // "... .. ....", - // "WASHINGTONYHAWK", - // " .. . ", - // " . . ", - // " . .. ", - // "ROOSEVELTONJOHN", - // ".... .. ...", - // "JEFFERSONNYBONO", - // " . . ", - // " . . ", - // " . . " - // ]; - // grid = grid.map(s => s.split("")).flat(); - const props = { - "size": { - "rows": size, - "cols": size - }, - "grid": grid - } - - return ; -}; - -const PageViewTracker = ({ location }: { location: WindowLocation }) => { - React.useEffect(() => { - try { - getFirebaseApp().analytics().logEvent("screen_view", { app_name: 'react', screen_name: location.pathname }); - } catch (e) { - if (!(e instanceof TypeError)) { - throw e; - } - } - }, [location]); - return ; -} - -const App = () => { - const [user, loadingUser, error] = useAuthState(); - const [isAdmin, setIsAdmin] = React.useState(false); - React.useEffect(() => { - if (user && user.email) { - user.getIdTokenResult() - .then((idTokenResult) => { - if (!!idTokenResult.claims.admin) { - setIsAdmin(true); - } else { - setIsAdmin(false); - } - }) - .catch((error) => { - setIsAdmin(false); - console.error(error); - }); - } - }, [user]); - - const [audioContext, setAudioContext] = React.useState(null); - const initAudioContext = React.useCallback(() => { - if (!audioContext) { - const constructor = window.AudioContext || (window as any).webkitAudioContext; - setAudioContext(new constructor()); - } - }, [audioContext, setAudioContext]); - return ( - - - - - - - {({ location }) => ( - - )} - - Loading...}> - - - - - - - - - - - - - - - - - - - - - - - ); -} - -export default App; diff --git a/crossword/src/AutoSizer.tsx b/crossword/src/AutoSizer.tsx deleted file mode 100644 index b45872b1..00000000 --- a/crossword/src/AutoSizer.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import * as React from 'react'; -import createDetectElementResize from './vendor/detectElementResize'; - -declare global { - interface Window { - HTMLElement: typeof HTMLElement; - } -} - -interface Size { - height: number, - width: number, -}; - -interface Props { - /** Function responsible for rendering children. */ - children: (size: Size) => React.ReactNode; -} - -interface State { - height: number, - width: number, -}; - -type ResizeHandler = (element: HTMLElement, onResize: () => void) => void; - -interface DetectElementResize { - addResizeListener: ResizeHandler, - removeResizeListener: ResizeHandler, -}; - -export default class AutoSizer extends React.PureComponent { - static defaultProps = { - onResize: () => { }, - disableHeight: false, - disableWidth: false, - style: {}, - }; - - state = { - height: 0, - width: 0, - }; - - _parentNode?: HTMLElement; - _autoSizer?: HTMLElement; - _detectElementResize?: DetectElementResize; - - componentDidMount() { - if ( - this._autoSizer && - this._autoSizer.parentNode && - this._autoSizer.parentNode.ownerDocument && - this._autoSizer.parentNode.ownerDocument.defaultView && - this._autoSizer.parentNode instanceof - this._autoSizer.parentNode.ownerDocument.defaultView.HTMLElement - ) { - // Delay access of parentNode until mount. - // This handles edge-cases where the component has already been unmounted before its ref has been set, - // As well as libraries like react-lite which have a slightly different lifecycle. - this._parentNode = this._autoSizer.parentNode; - - // Defer requiring resize handler in order to support server-side rendering. - // See issue #41 - this._detectElementResize = createDetectElementResize(null); - if (this._parentNode) { - this._detectElementResize.addResizeListener( - this._parentNode, - this._onResize, - ); - } - - this._onResize(); - } - } - - componentWillUnmount() { - if (this._detectElementResize && this._parentNode) { - this._detectElementResize.removeResizeListener( - this._parentNode, - this._onResize, - ); - } - } - - render() { - const { height, width } = this.state; - - // Outer div should not force width/height since that may prevent containers from shrinking. - // Inner component should overflow and use calculated width/height. - // See issue #68 for more information. - const outerStyle: React.CSSProperties = { width: 0, height: 0, overflow: 'visible' }; - const childParams: Size = {width: width, height: height}; - - return ( -
    - {width !== 0 && height !== 0 && this.props.children(childParams)} -
    - ); - } - - _onResize = () => { - if (this._parentNode) { - // Guard against AutoSizer component being removed from the DOM immediately after being added. - // This can result in invalid style values which can result in NaN values if we don't handle them. - // See issue #150 for more context. - - const height = this._parentNode.offsetHeight || 0; - const width = this._parentNode.offsetWidth || 0; - - const style = window.getComputedStyle(this._parentNode) || {}; - const paddingLeft = parseInt(style.paddingLeft, 10) || 0; - const paddingRight = parseInt(style.paddingRight, 10) || 0; - const paddingTop = parseInt(style.paddingTop, 10) || 0; - const paddingBottom = parseInt(style.paddingBottom, 10) || 0; - - let newHeight = height - paddingTop - paddingBottom; - let newWidth = width - paddingLeft - paddingRight; - - if ( - (this.state.height !== newHeight) || - (this.state.width !== newWidth) - ) { - this.setState({ - height: newHeight, - width: newWidth, - }); - } - } - }; - - _setRef = (autoSizer: HTMLDivElement) => { - this._autoSizer = autoSizer; - }; -} diff --git a/crossword/src/Autofiller.ts b/crossword/src/Autofiller.ts deleted file mode 100644 index f8849580..00000000 --- a/crossword/src/Autofiller.ts +++ /dev/null @@ -1,309 +0,0 @@ -import { valAt, getCrosses } from './gridBase'; -import { - AutofillGrid, fromTemplate, minGridCost, stableSubsets, - numMatchesForEntry, gridWithEntryDecided -} from './autofillGrid'; -import * as WordDB from './WordDB'; - -enum ResultTag { - Recur, - Value -} -interface Result { - type: ResultTag -} -interface Recur extends Result { - type: ResultTag.Recur, - grid: AutofillGrid, - discrep: number, - pitched: Set, - subset: Set | null, - cont: Cont -} -function isRecur(result: Result): result is Recur { - return result.type === ResultTag.Recur; -} -function recur(grid: AutofillGrid, discrep: number, pitched: Set, subset: Set | null, cont: Cont): Recur { - return { type: ResultTag.Recur, grid, discrep, pitched, subset, cont }; -} -interface Value extends Result { - type: ResultTag.Value, - result: AutofillGrid | null -} -function isValue(result: Result): result is Value { - return result.type === ResultTag.Value; -} -function value(x: AutofillGrid | null) { - return { type: ResultTag.Value, result: x } -} - -type Cont = (x: AutofillGrid | null) => Result; - -export class Autofiller { - public initialGrid: AutofillGrid; - public completed: boolean; - public solnGrid: AutofillGrid | null; - public solnCost: number | null; - public nextStep: Result; - public postedSoln: boolean; - public startTime: number; - - constructor( - public readonly grid: string[], - public readonly width: number, - public readonly height: number, - public onResult: (input: string[], result: string[]) => void, - public onComplete: () => void - ) { - this.initialGrid = fromTemplate(this.grid, this.width, this.height); - this.completed = false; - this.solnCost = null; - this.solnGrid = null; - this.postedSoln = false; - this.startTime = new Date().getTime(); - - // Our initial step is a call to recur - this.nextStep = recur(this.initialGrid, 5, new Set(), null, (result) => { return value(result) }); - }; - - step() { - if (!WordDB.dbTransformed) { - console.error("Worker has no db but attempting autofill"); - this.completed = true; - return; - } - if (this.completed) { - console.log("Calling step but already completed"); - return; - } - - /* Take as many steps as we can w/in 50ms. After that we need to take a - * break so that we have a chance to get any new input from the user. */ - const start = new Date().getTime(); - while (isRecur(this.nextStep) && new Date().getTime() - start < 50) { - this.nextStep = this._solve( - this.nextStep.grid, - this.nextStep.discrep, - this.nextStep.pitched, - this.nextStep.subset, - this.nextStep.cont); - } - - // If it's a "value" (not a "recur") we're done. - if (isValue(this.nextStep)) { - console.log("Finished: " + ((new Date().getTime() - this.startTime) / 1000).toPrecision(4) + "s"); - this.completed = true; - this.onComplete(); - } - - // If we have a solution that hasn't been posted yet, post it. - if (this.solnGrid && !this.postedSoln) { - this.postedSoln = true; - this.onResult(this.grid, this.solnGrid.cells); - } - }; - - /* Fill out a grid or a subset of a grid */ - _solve(grid: AutofillGrid, discrep: number, pitched: Set, subset: Set | null, cont: Cont): Result { - const baseCost = minGridCost(grid); - - // We already have a solution that's better than this grid could possibly get - if (this.solnCost && baseCost > this.solnCost) { - return cont(null); - } - - let entriesToConsider = grid.entries.filter((e) => !e.completedWord); - // There are no entries left to consider, this grid must be a new best solution - if (entriesToConsider.length === 0) { - this.solnGrid = grid - this.solnCost = baseCost - this.postedSoln = false; - return cont(grid) - } - - if (subset !== null) { - entriesToConsider = entriesToConsider.filter((e) => subset.has(e.index)); - } - // There are no entries left in this subset, so we're done with this subsection - if (entriesToConsider.length === 0) { - return cont(grid); - } - - // See if there are any stable subsets out of the entries we're considering - const subsets = stableSubsets(grid, subset); - if (subsets.length > 1) { - subsets.sort((a, b) => a.size - b.size); - // Attempt to solve the smallest subset - return recur(grid, discrep, pitched, subsets[0], subsolved => { - if (subsolved === null) { - // The subset couldn't be solved, so this grid is a failure - return cont(null); - } else { - // Solve the rest of the grid - return recur(subsolved, discrep, pitched, subset, cont); - } - }); - } - - // Consider entries in order of possible matches - entriesToConsider.sort((e1, e2) => numMatchesForEntry(e1) - numMatchesForEntry(e2)); - let successor: [AutofillGrid, number, string] | null = null; - let successorDiff: number | null = null; - - for (const entry of entriesToConsider) { - const crosses = getCrosses(grid, entry) - let bestGrid: [AutofillGrid, number, string] | null = null; - let bestCost: number | null = null; - let secondBestCost: number | null = null; - - let skipEntry = false; - const failingLetters: Array = []; - entry.cells.forEach(() => { failingLetters.push("") }); - const succeedingLetters: Array = []; - entry.cells.forEach(() => { succeedingLetters.push("") }); - for (const [word, score] of WordDB.matchingWords(entry.length, entry.bitmap)) { - if (pitched.has(entry.index + ":" + word)) { - continue; - } - if (grid.usedWords.has(word)) { - continue; - } - - // If we have a secondBestCost for this entry we know it's lower than existing soln cost - const costToBeat = secondBestCost !== null ? secondBestCost : this.solnCost; - - // Fail fast based on score change due to this entry alone - if (costToBeat !== null && ((baseCost - entry.minCost + 1 / score) > costToBeat)) { - continue; - } - - // Fail fast based on score change due to any crosses - let failFast = false; - let j = -1; - for (let i = 0; i < entry.cells.length; i += 1) { - j += 1; - const cell = valAt(grid, entry.cells[i]); - if (cell !== ' ') { // Don't need to check cross - j += cell.length - 1; - continue; - } - const crossIndex = crosses[i].entryIndex; - if (crossIndex === null) { - continue; - } - if (failingLetters[i].indexOf(word[j]) !== -1) { - failFast = true; - break; - } - } - if (!failFast) { - let j = -1; - for (let i = 0; i < entry.cells.length; i += 1) { - j += 1; - const cell = valAt(grid, entry.cells[i]); - if (cell !== ' ') { // Don't need to check cross - j += cell.length - 1; - continue; - } - const crossIndex = crosses[i].entryIndex; - if (crossIndex === null) { - continue; - } - if (succeedingLetters[i].indexOf(word[j]) !== -1) { - continue; - } - const cross = grid.entries[crossIndex]; - const crossLength = cross.length; - const newBitmap = WordDB.updateBitmap(crossLength, cross.bitmap, crosses[i].wordIndex, word[j]); - if (newBitmap.isZero()) { - failingLetters[i] += word[j]; - failFast = true; - break; - } - const newCost = baseCost - cross.minCost + WordDB.minCost(crossLength, newBitmap); - if (costToBeat !== null && newCost > costToBeat) { - failingLetters[i] += word[j]; - failFast = true; - break; - } - succeedingLetters[i] += word[j]; - } - } - if (failFast) { - continue; - } - - const newgrid = gridWithEntryDecided(grid, entry.index, word, score); - if (newgrid === null) { - continue; - } - - const newCost = minGridCost(newgrid); - - // Check overall score - if (costToBeat && newCost > costToBeat) { - continue; - } - - if (bestGrid === null || bestCost === null) { - bestGrid = [newgrid, entry.index, word]; - bestCost = newCost; - } else if (newCost < bestCost) { - bestGrid = [newgrid, entry.index, word]; - secondBestCost = bestCost; - bestCost = newCost; - } else if (secondBestCost === null || newCost < secondBestCost) { - secondBestCost = newCost; - if (successorDiff && secondBestCost - baseCost < successorDiff) { - skipEntry = true; - break; - } - } - } - - if (skipEntry) { - break; - } - - if (bestGrid === null || bestCost === null) { // No valid option for this entry, bad grid - return cont(null); - } - - if (secondBestCost === null) { // No backup option, so this entry is forced - successor = bestGrid; - break; - } - - const costDiff = secondBestCost - bestCost; - if (successorDiff === null || costDiff > successorDiff) { // No successor or this one has higher cost differential - successor = bestGrid; - successorDiff = costDiff; - } - } - - if (successor === null) { - throw new Error("successor was null"); - } - const suc = successor; // weird hack around type system not realizing successor isn't null - let nextSubset = null; - if (subset !== null) { - nextSubset = new Set(Array.from(subset).filter(e => e !== suc[1])) - } - - if (!discrep || pitched.size >= discrep) { - return recur(successor[0], discrep, pitched, nextSubset, cont); - } - - return recur(successor[0], discrep, pitched, nextSubset, - result => { - const newPitched = new Set(pitched.values()); - newPitched.add(suc[1] + ":" + suc[2]); - return recur(grid, discrep, newPitched, subset, - res2 => { - return cont((res2 !== null && (result === null || minGridCost(res2) < minGridCost(result))) ? res2 : result); - } - ); - } - ) - }; -} diff --git a/crossword/src/Builder.tsx b/crossword/src/Builder.tsx deleted file mode 100644 index 1e30c318..00000000 --- a/crossword/src/Builder.tsx +++ /dev/null @@ -1,567 +0,0 @@ -/** @jsx jsx */ -import { jsx } from '@emotion/core'; - -import * as React from 'react'; - -import { isMobile, isTablet, isIPad13 } from "react-device-detect"; -import { - FaRegNewspaper, FaUser, FaListOl, FaRegCircle, FaRegCheckCircle, FaTabletAlt, - FaKeyboard, FaEllipsisH, FaVolumeUp, FaVolumeMute, FaFillDrip, FaUserLock -} from 'react-icons/fa'; -import { IoMdStats } from 'react-icons/io'; -import useEventListener from '@use-it/event-listener'; -import { Helmet } from "react-helmet-async"; -import { FixedSizeList as List } from "react-window"; -import { navigate } from '@reach/router'; - -import AutoSizer from "./AutoSizer"; -import { - Rebus, SpinnerWorking, SpinnerFinished, SpinnerFailed, SpinnerDisabled, - SymmetryIcon, SymmetryRotational, SymmetryVertical, SymmetryHorizontal, SymmetryNone, - EscapeKey, BacktickKey -} from './Icons'; -import { requiresAdmin, AuthProps } from './App'; -import { getFirebaseApp, getTimestampClass } from './firebase'; -import { GridView } from './Grid'; -import { getCrosses, valAt, entryAndCrossAtPosition } from './gridBase'; -import { fromCells, getClueMap } from './viewableGrid'; -import { TimestampedPuzzleT, AuthoredPuzzleT, AuthoredPuzzlesV } from './common/dbtypes' -import { updateInCache } from './dbUtils'; -import { Direction, PuzzleT } from './types'; -import { - Symmetry, BuilderState, BuilderEntry, builderReducer, validateGrid, - KeypressAction, SetClueAction, SymmetryAction, ClickedFillAction, PuzzleAction, - SetTitleAction, SetHighlightAction, PublishAction -} from './reducer'; -import { TopBarLink, TopBar, TopBarDropDownLink, TopBarDropDown } from './TopBar'; -import { SquareAndCols, TinyNav, Page } from './Page'; -import { RebusOverlay } from './Puzzle'; -import AutofillWorker from 'worker-loader!./autofill.worker.ts'; // eslint-disable-line import/no-webpack-loader-syntax -import { isAutofillCompleteMessage, isAutofillResultMessage, WorkerMessage, LoadDBMessage, AutofillMessage } from './types'; -import * as WordDB from './WordDB'; -import { Overlay } from './Overlay'; -import { usePersistedBoolean } from './hooks'; -import { buttonAsLink } from './style'; - -let worker: Worker; - -type WithOptional = Omit & Partial>; -type BuilderProps = WithOptional, "clues" | "title" | "highlighted" | "highlight"> - -export const BuilderDBLoader = requiresAdmin((props: BuilderProps & AuthProps) => { - const [ready, setReady] = React.useState(false); - WordDB.initializeOrBuild(setReady); - if (ready) { - return ; - } - return Loading word database... -}); - -interface PotentialFillItemProps { - isGoodSuggestion: (entryIndex: number, word: string) => boolean, - entryIndex: number, - value: [string, number], - dispatch: React.Dispatch, -} -const PotentialFillItem = (props: PotentialFillItemProps) => { - function click(e: React.MouseEvent) { - e.preventDefault(); - props.dispatch({ type: 'CLICKEDFILL', entryIndex: props.entryIndex, value: props.value[0] }); - } - return ( -
    - {props.value[0]} -
    - ); -} - -interface PotentialFillListProps { - isGoodSuggestion: (entryIndex: number, word: string) => boolean, - header?: string, - entryIndex: number, - values: Array<[string, number]>, - dispatch: React.Dispatch, -} -const PotentialFillList = (props: PotentialFillListProps) => { - const listRef = React.useRef(null); - React.useEffect(() => { - if (listRef.current !== null) { - listRef.current.scrollToItem(0); - } - }, [props.entryIndex, props.values]); - return ( -
    {props.header ? -
    {props.header}
    : ""} -
    - - {({ height, width }) => { - return ( - {({ index, style }) => ( -
    - -
    - )}
    ) - }} -
    -
    -
    - ); -} - -const ClueRow = (props: { dispatch: React.Dispatch, entry: BuilderEntry, clues: Map }) => { - if (props.entry.completedWord === null) { - throw new Error("shouldn't ever get here"); - } - return ( - - {props.entry.completedWord} - { - const sca: SetClueAction = { type: "SETCLUE", word: props.entry.completedWord || "", clue: e.target.value }; - props.dispatch(sca); - }} /> - - ); -} - -interface ClueModeProps { - title: string | null, - exitClueMode: () => void, - completedEntries: Array, - clues: Map, - dispatch: React.Dispatch, -} -const ClueMode = (props: ClueModeProps) => { - const topbar = ( - - } text="Back to Grid" onClick={props.exitClueMode} /> - - ); - const clueRows = props.completedEntries.map(e => ); - return ( - -
    -

    Title

    - { - const sta: SetTitleAction = { type: "SETTITLE", value: e.target.value }; - props.dispatch(sta); - }} /> -

    Clues

    - {props.completedEntries.length ? - - - {clueRows} - -
    - : - -

    This where you come to set clues for your puzzle, but you don't have any completed fill words yet!

    -

    Go back to and fill in one or more words completely. Then come back here and make some clues.

    -
    - } -
    -
    - ) -} - -export const Builder = (props: BuilderProps & AuthProps) => { - const initialGrid = fromCells({ - mapper: (e) => e, - width: props.size.cols, - height: props.size.rows, - cells: props.grid, - allowBlockEditing: true, - highlighted: new Set(props.highlighted), - highlight: props.highlight || "circle", - }); - const [state, dispatch] = React.useReducer(builderReducer, { - type: 'builder', - title: props.title || null, - active: { col: 0, row: 0, dir: Direction.Across }, - grid: initialGrid, - showKeyboard: isMobile || isIPad13, - isTablet: isTablet || isIPad13, - showExtraKeyLayout: false, - isEnteringRebus: false, - rebusValue: '', - gridIsComplete: false, - repeats: new Set(), - hasNoShortWords: false, - isEditable: () => true, - symmetry: Symmetry.Rotational, - clues: getClueMap(initialGrid, props.clues || []), - publishErrors: [], - toPublish: null, - authorId: props.user.uid, - authorName: props.user.displayName || "Anonymous", - postEdit(_cellIndex) { - return validateGrid(this); - } - }, validateGrid); - - const [autofilledGrid, setAutofilledGrid] = React.useState([]); - const [autofillInProgress, setAutofillInProgress] = React.useState(false); - - // TODO should we actually disable autofill? Right now it just turns off display - const [autofillEnabled, setAutofillEnabled] = React.useState(true); - - // We need a ref to the current grid so we can verify it in worker.onmessage - const currentCells = React.useRef(state.grid.cells); - React.useEffect(() => { - if (!WordDB.dbEncoded) { - throw new Error("missing db!"); - } - currentCells.current = state.grid.cells; - setAutofilledGrid([]); - if (!worker) { - console.log("initializing worker"); - worker = new AutofillWorker(); - worker.onmessage = e => { - const data = e.data as WorkerMessage; - if (isAutofillResultMessage(data)) { - if (currentCells.current.every((c, i) => c === data.input[i])) { - setAutofilledGrid(data.result); - } - } else if (isAutofillCompleteMessage(data)) { - setAutofillInProgress(false); - } else { - console.error("unhandled msg in builder: ", e.data); - } - }; - let loaddb: LoadDBMessage = { type: 'loaddb', db: WordDB.dbEncoded }; - worker.postMessage(loaddb); - } - let autofill: AutofillMessage = { - type: 'autofill', - grid: state.grid.cells, - width: state.grid.width, - height: state.grid.height - }; - setAutofillInProgress(true); - worker.postMessage(autofill); - }, [state.grid.cells, state.grid.width, state.grid.height]); - - const [clueMode, setClueMode] = React.useState(false); - if (clueMode) { - return e.completedWord)} exitClueMode={() => setClueMode(false)} /> - } - return -} - -interface GridModeProps { - user: firebase.User, - isAdmin: boolean, - autofillEnabled: boolean, - setAutofillEnabled: (val: boolean) => void, - autofilledGrid: string[], - autofillInProgress: boolean, - state: BuilderState, - dispatch: React.Dispatch, - setClueMode: (val: boolean) => void, -} -const GridMode = ({ state, dispatch, setClueMode, ...props }: GridModeProps) => { - const [muted, setMuted] = usePersistedBoolean("muted", false); - - const physicalKeyboardHandler = React.useCallback((e: React.KeyboardEvent) => { - if (e.metaKey || e.altKey || e.ctrlKey) { - return; // This way you can still do apple-R and such - } - const kpa: KeypressAction = { type: "KEYPRESS", key: e.key, shift: e.shiftKey }; - dispatch(kpa); - e.preventDefault(); - }, [dispatch]); - useEventListener('keydown', physicalKeyboardHandler); - - let left = ; - let right = ; - let tiny = ; - const doCrossesWork = React.useCallback((entryIndex: number, word: string) => { - let successFailureEntries = new Map>(); - const entry = state.grid.entries[entryIndex]; - let successFailure = successFailureEntries.get(entryIndex); - if (successFailure === undefined) { - successFailure = new Map(); - successFailureEntries.set(entryIndex, successFailure); - } - const crosses = getCrosses(state.grid, entry) - let j = -1; - for (let i = 0; i < entry.cells.length; i += 1) { - j += 1; - const cell = valAt(state.grid, entry.cells[i]); - if (cell.length > 1) { - // Handle rebuses - j += cell.length - 1; - continue; - } - if (!entry.completedWord && cell !== ' ') { - continue; - } - const crossIndex = crosses[i].entryIndex; - if (crossIndex === null) { - continue; - } - const cross = state.grid.entries[crossIndex]; - if (cross.completedWord) { - continue; - } - let crossSuccessFailure = successFailure.get(i); - if (crossSuccessFailure === undefined) { - crossSuccessFailure = ["", ""]; - } - const [succeeding, failing] = crossSuccessFailure; - if (failing.indexOf(word[j]) !== -1) { - return false; - } - if (succeeding.indexOf(word[j]) !== -1) { - continue; - } - let crossPattern = ''; - for (let crossCell of cross.cells) { - if (crossCell.row === entry.cells[i].row && crossCell.col === entry.cells[i].col) { - crossPattern += word[j]; - } else { - crossPattern += valAt(state.grid, crossCell); - } - } - const newBitmap = WordDB.matchingBitmap(crossPattern); - if (newBitmap ?.isZero()) { - successFailure.set(i, [succeeding, failing + word[j]]); - return false; - } else { - successFailure.set(i, [succeeding + word[j], failing]); - } - } - return true; - }, [state.grid]); - for (let entry of entryAndCrossAtPosition(state.grid, state.active)) { - if (entry !== null) { - let matches: Array<[string, number]>; - let pattern = ""; - let crosses = getCrosses(state.grid, entry); - for (let index = 0; index < entry.cells.length; index += 1) { - const cell = entry.cells[index]; - const val = valAt(state.grid, cell); - const cross = crosses[index]; - // If complete, remove any cells whose crosses aren't complete and show that - if (entry.completedWord && - val.length === 1 && - cross.entryIndex !== null && - !state.grid.entries[cross.entryIndex].completedWord) { - pattern += " "; - } else { - pattern += val; - } - } - matches = WordDB.matchingWords(pattern.length, WordDB.matchingBitmap(pattern)); - if (entry.direction === state.active.dir) { - tiny = ; - } - if (entry.direction === Direction.Across) { - left = ; - } else { - right = ; - } - } - } - - const { autofillEnabled, setAutofillEnabled } = props; - const toggleAutofillEnabled = React.useCallback(() => { - setAutofillEnabled(!autofillEnabled); - }, [autofillEnabled, setAutofillEnabled]); - - - let totalLength = 0; - state.grid.entries.forEach((e) => totalLength += e.cells.length); - const numEntries = state.grid.entries.length; - const averageLength = totalLength / numEntries; - - const publishInProgress = React.useRef(false); - React.useEffect(() => { - if (state.toPublish === null || publishInProgress.current) { - return; - } - const dbpuzzle = state.toPublish; - publishInProgress.current = true; - console.log("Uploading"); - console.log(dbpuzzle); - const db = getFirebaseApp().firestore(); - db.collection("c").add(dbpuzzle).then(async (ref) => { - console.log("Uploaded", ref.id); - - const authoredPuzzle: AuthoredPuzzleT = [dbpuzzle.ca, dbpuzzle.t]; - await updateInCache('uc', props.user.uid, { [ref.id]: authoredPuzzle }, AuthoredPuzzlesV, true); - - const forStorage: TimestampedPuzzleT = { downloadedAt: getTimestampClass().now(), data: dbpuzzle } - sessionStorage.setItem('c/' + ref.id, JSON.stringify(forStorage)); - navigate("/crosswords/" + ref.id); - }); - }, [state.toPublish, props.user.uid]); - - const keyboardHandler = React.useCallback((key: string) => { - const kpa: KeypressAction = { type: "KEYPRESS", key: key, shift: false }; - dispatch(kpa); - }, [dispatch]); - - const topBar = React.useMemo(() => { - let autofillIcon = ; - let autofillText = "Autofill disabled"; - if (props.autofillEnabled) { - if (props.autofillInProgress) { - autofillIcon = ; - autofillText = "Autofill in progress"; - } else if (props.autofilledGrid.length) { - autofillIcon = ; - autofillText = "Autofill complete"; - } else { - autofillIcon = ; - autofillText = "Couldn't autofill this grid"; - } - } - return - - } text="Clues" onClick={() => setClueMode(true)} /> - } text="Stats"> -

    Grid status

    -
    {state.gridIsComplete ? : } All cells should be filled
    -
    {state.hasNoShortWords ? : } All words should be at least three letters
    -
    {state.repeats.size > 0 ? ({Array.from(state.repeats).sort().join(", ")}) : } No words should be repeated
    -

    Fill

    -
    Number of words: {numEntries}
    -
    Mean word length: {averageLength.toPrecision(3)}
    -
    - } text="Symmetry"> - } text="Rotational Symmetry" onClick={() => { - const a: SymmetryAction = { type: "CHANGESYMMETRY", symmetry: Symmetry.Rotational }; - dispatch(a); - }} /> - } text="Horizontal Symmetry" onClick={() => { - const a: SymmetryAction = { type: "CHANGESYMMETRY", symmetry: Symmetry.Horizontal }; - dispatch(a); - }} /> - } text="Vertical Symmetry" onClick={() => { - const a: SymmetryAction = { type: "CHANGESYMMETRY", symmetry: Symmetry.Vertical }; - dispatch(a); - }} /> - } text="No Symmetry" onClick={() => { - const a: SymmetryAction = { type: "CHANGESYMMETRY", symmetry: Symmetry.None }; - dispatch(a); - }} /> - - } text="More"> - } text="Publish Puzzle" onClick={() => { - const a: PublishAction = { type: 'PUBLISH', publishTimestamp: getTimestampClass().now() }; - dispatch(a); - }} /> - } text="Enter Rebus" shortcutHint={} onClick={() => { - const a: KeypressAction = { type: "KEYPRESS", key: 'Escape', shift: false }; - dispatch(a); - }} /> - : } text="Toggle Square Highlight" shortcutHint={} onClick={() => { - const a: KeypressAction = { type: "KEYPRESS", key: '`', shift: false }; - dispatch(a); - }} /> - : } text={state.grid.highlight === "circle" ? "Use Shade for Highlights" : "Use Circle for Highlights"} onClick={() => { - const a: SetHighlightAction = { type: "SETHIGHLIGHT", highlight: state.grid.highlight === "circle" ? "shade" : "circle" }; - dispatch(a); - }} /> - { - muted ? - } text="Unmute" onClick={() => setMuted(false)} /> - : - } text="Mute" onClick={() => setMuted(true)} /> - } - { - props.isAdmin ? - - } text="Toggle Keyboard" onClick={() => dispatch({ type: "TOGGLEKEYBOARD" })} /> - } text="Toggle Tablet" onClick={() => dispatch({ type: "TOGGLETABLET" })} /> - } text="Admin" onClick={() => navigate('/admin')} /> - - : - "" - } - } text="Account" onClick={() => navigate('/account')} /> - -
    - }, [props.autofillEnabled, props.autofillInProgress, props.autofilledGrid.length, averageLength, dispatch, muted, numEntries, props.isAdmin, setClueMode, setMuted, state.grid.highlight, state.gridIsComplete, state.hasNoShortWords, state.repeats, state.symmetry, toggleAutofillEnabled]); - - return ( - - - Constructor - - - {topBar} - - {state.isEnteringRebus ? - : ""} - {state.publishErrors.length ? - dispatch({ type: "CLEARPUBLISHERRORS" })}> - -
    Please fix the following errors and try publishing again:
    -
      - {state.publishErrors.map((s, i) =>
    • {s}
    • )} -
    -
    -
    : ""} - { - return - } - } - left={left} - right={right} - tinyColumn={{tiny}} - /> -
    - ) -} diff --git a/crossword/src/Calendar.tsx b/crossword/src/Calendar.tsx deleted file mode 100644 index c780150f..00000000 --- a/crossword/src/Calendar.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/** @jsx jsx */ -import { jsx } from '@emotion/core'; - -import * as React from 'react'; - -const daysToDisplay = 42; -const dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat']; -const monthLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - -interface CalendarProps { - selected?: Date, - onClick: (d: Date) => void, - dateIsDisabled: (d: Date) => boolean -} - -export function Calendar(props: CalendarProps) { - const today = new Date(); - const selected = props.selected || today; - const [monthToShow, setMonthToShow] = React.useState(selected); - const year = monthToShow.getUTCFullYear(); - const month = monthToShow.getUTCMonth(); - const firstMonthDay = new Date(year, month, 0); - const firstMonthDayNumber = firstMonthDay.getDay(); - const firstDayToDisplay = new Date(year, month, -firstMonthDayNumber); - - function changeMonth(incr: number) { - const changed = new Date(monthToShow); - changed.setUTCMonth(changed.getUTCMonth() + incr) - setMonthToShow(changed); - } - - function sameDate(d1: Date, d2: Date) { - return d1.getUTCFullYear() === d2.getUTCFullYear() && - d1.getUTCMonth() === d2.getUTCMonth() && - d1.getUTCDate() === d2.getUTCDate(); - } - - return ( -
    -
    -
    changeMonth(-1)}> - {'<'} -
    -
    {monthLabels[month]} {year}
    -
    changeMonth(1)}> - {'>'} -
    -
    -
    - {dayLabels.map(label =>
    {label}
    )} - {Array(daysToDisplay).fill(0).map((_, i) => { - const d = new Date(firstDayToDisplay); - d.setDate(d.getDate() + i); - const isDisabled = props.dateIsDisabled(d); - const isToday = sameDate(d, today); - const isSelected = sameDate(d, selected); - return
    { - if (!isDisabled) { - props.onClick(d); - } - }} - > - {d.getDate()} -
    - } - )} -
    -
    - ) -} diff --git a/crossword/src/Category.test.tsx b/crossword/src/Category.test.tsx deleted file mode 100644 index ed737645..00000000 --- a/crossword/src/Category.test.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { render, initFirebaseForTesting } from './testUtils'; -import { Category } from './Category'; -import * as firebase from '@firebase/testing'; - -var adminApp = firebase.initializeAdminApp({ - projectId: "mdcrosshare", -}); - -initFirebaseForTesting(); - -test('basic category page test', async () => { - window.HTMLElement.prototype.scrollIntoView = function() { }; - - await adminApp.firestore().collection('categories').doc('dailymini').set({ '2020-4-10': 'foobar' }); - - const { findByText } = render(); - - const link = await findByText('Daily Mini for 5/10/2020'); - expect(link).toBeInTheDocument(); - expect(link).toHaveAttribute('href', '/crosswords/foobar'); -}); diff --git a/crossword/src/Category.tsx b/crossword/src/Category.tsx deleted file mode 100644 index d089c969..00000000 --- a/crossword/src/Category.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/** @jsx jsx */ -import { jsx } from '@emotion/core'; -import * as React from 'react'; - -import { Link, RouteComponentProps } from "@reach/router"; - -import { ensureUser, AuthProps } from './App'; -import { Page } from './Page'; -import { - CategoryIndexT, CategoryIndexV, UserPlaysT, UserPlaysV, - getDateString, prettifyDateString -} from './common/dbtypes'; -import { getFromSessionOrDB } from './dbUtils'; - -interface CategoryProps extends RouteComponentProps, AuthProps { - categoryId?: string -} - -const CategoryNames: { [key: string]: string } = { - dailymini: "Daily Mini" -} - -export const Category = ensureUser(({ user, categoryId }: CategoryProps) => { - const [plays, setPlays] = React.useState(null); - const [puzzles, setPuzzles] = React.useState(null); - const [error, setError] = React.useState(false); - - React.useEffect(() => { - if (!categoryId || !CategoryNames.hasOwnProperty(categoryId)) { - console.log("Missing category id") - setError(true); - return; - } - Promise.all([ - getFromSessionOrDB('categories', categoryId, CategoryIndexV, 24 * 60 * 60 * 1000), - getFromSessionOrDB('up', user.uid, UserPlaysV, -1) - ]) - .then(([puzzles, plays]) => { - setPuzzles(puzzles); - setPlays(plays); - }).catch(reason => { - console.error(reason); - setError(true); - }); - }, [categoryId, user.uid]); - - if (!puzzles) { - return Loading...; - } - - if (!categoryId || !CategoryNames.hasOwnProperty(categoryId) || !puzzles || error) { - return Error loading category page; - } - - const today = new Date() - today.setHours(12); - const ds = addZeros(getDateString(today)); - - function addZeros(dateString: string) { - const groups = dateString.match(/^(\d+)-(\d+)-(\d+)$/); - if (!groups) { - throw new Error("Bad date string: " + dateString); - } - const year = groups[1]; - let month = groups[2]; - if (month.length === 1) { - month = '0' + month; - } - let date = groups[3]; - if (date.length === 1) { - date = '0' + date; - } - return year + '-' + month + '-' + date; - } - - return ( - -
      - {Object.entries(puzzles) - .map(([k, v]) => [addZeros(k), v]) - .filter(([k, _v]) => k <= ds) - .sort((a, b) => a[0] > b[0] ? -1 : 1) - .map(([dateString, puzzleId]) => { - const play = plays && plays[puzzleId]; - return (
    • - {CategoryNames[categoryId] + ' for ' + prettifyDateString(dateString)} -
      {play ?.[3] ? 'completed ' + (play ?.[2] ? 'with helpers' : 'without helpers') : (play ? 'unfinished' : '')}
      -
    • ); - })} -
    -
    - ); -}); diff --git a/crossword/src/Cell.tsx b/crossword/src/Cell.tsx deleted file mode 100644 index 54ebefad..00000000 --- a/crossword/src/Cell.tsx +++ /dev/null @@ -1,130 +0,0 @@ -/** @jsx jsx */ -import { jsx } from '@emotion/core'; - -import * as React from 'react'; - -import { FaSlash, FaEye } from 'react-icons/fa'; - -import { - notSelectable, PRIMARY, SECONDARY, ERROR_COLOR, -} from './style'; - -type CellProps = { - autofill: string, - showingKeyboard: boolean, - gridWidth: number, - gridSize: number, - isBlock: boolean, - active: boolean, - entryCell: boolean, - highlight: "circle" | "shade" | undefined, - value: string, - number: string, - row: number, - column: number, - onClick: (pos: { row: number, col: number }) => void, - isVerified: boolean | undefined, - isWrong: boolean | undefined, - wasRevealed: boolean | undefined, -} - -export const Cell = React.memo(function Cell(props: CellProps) { - let bg = "var(--white)"; - if (props.isBlock && props.active) { - bg = "repeating-linear-gradient(-45deg,var(--cell-wall),var(--cell-wall) 10px," + PRIMARY + " 10px," + PRIMARY + " 20px);" - } else if (props.isBlock) { - bg = "var(--cell-wall)"; - } else if (props.active) { - bg = PRIMARY; - } else if (props.entryCell) { - bg = SECONDARY; - } - - const cellSize = props.gridSize / props.gridWidth; - const value = props.value.trim() ? props.value : props.autofill; - - return ( -
    -
    props.onClick({ row: props.row, col: props.column })} css={[notSelectable, { - position: 'absolute', - width: '100%', - height: '100%', - borderRight: '1px solid var(--cell-wall)', - borderBottom: '1px solid var(--cell-wall)', - borderTop: (props.row === 0) ? '1px solid var(--cell-wall)' : 0, - borderLeft: (props.column === 0) ? '1px solid var(--cell-wall)' : 0, - background: bg, - - }]}> - {!props.isBlock ? - -
    - {props.wasRevealed ? -
    : ""} - {props.number}
    -
    - {props.isWrong ? -
    : ""} - {props.highlight === 'circle' ? -
    : ""} - {props.highlight === 'shade' ? -
    : ""} -
    {value}
    -
    -
    - : ""} -
    -
    - ); -}); diff --git a/crossword/src/Comments.test.tsx b/crossword/src/Comments.test.tsx deleted file mode 100644 index afc1d040..00000000 --- a/crossword/src/Comments.test.tsx +++ /dev/null @@ -1,265 +0,0 @@ -import React from "react"; -import { Comments } from "./Comments"; -import { anonymousUser, render, initFirebaseForTesting } from "./testUtils"; -import { getTimestampClass } from "./firebase"; -import * as firebase from "@firebase/testing"; - -initFirebaseForTesting(); - -const testComments = [ - { - i: "comment-id", - c: "my first comment", - a: "comment-author-id", - n: "Mike D", - t: 55.4, - ch: false, - p: getTimestampClass().now(), - }, -]; - -test("basic comment display", () => { - const { getByText, container } = render( - - ); - expect(getByText(/my first comment/i)).toBeVisible(); - expect(container.firstChild).toMatchInlineSnapshot(` - .emotion-5 { - margin-top: 1em; - } - - .emotion-0 { - border-bottom: 1px solid var(--black); - } - - .emotion-4 { - list-style-type: none; - margin: 2em 0 0 0; - padding: 0; - } - - .emotion-3 { - margin-top: 1em; - } - - .emotion-1 { - vertical-align: text-bottom; - } - - .emotion-2 { - font-size: 0.75em; - background-color: var(--caption); - color: white; - border-radius: 5px; - padding: 0.1em 0.2em; - } - -
    -

    - Comments -

    -
    - Sign in with google (above) to leave a comment of your own -
    -
      -
    • -
      -
      - - - - - - - - - - - - - - - - - - - - - - - Mike D - - - - 🤓 - - - 55s - -
      -
      - my first comment -
      - - -
      -
    • -
    -
    - `); -}); - -test("security rules should only allow commenting as onesself", async () => { - var app = firebase.initializeTestApp({ - projectId: "mdcrosshare", - auth: { - uid: "mike", - admin: false, - firebase: { - sign_in_provider: "google.com", - }, - }, - }); - - await firebase.assertFails( - app.firestore().collection("cfm").add({ c: "comment text" }) - ); - await firebase.assertFails( - app.firestore().collection("cfm").add({ c: "comment text", a: "jared" }) - ); - await firebase.assertSucceeds( - app.firestore().collection("cfm").add({ c: "comment text", a: "mike" }) - ); -}); - -test("security rules should only allow commenting if non-anonymous", async () => { - var app = firebase.initializeTestApp({ - projectId: "mdcrosshare", - auth: { - uid: "mike", - admin: false, - firebase: { - sign_in_provider: "anonymous", - }, - }, - }); - - await firebase.assertFails( - app.firestore().collection("cfm").add({ c: "comment text" }) - ); - await firebase.assertFails( - app.firestore().collection("cfm").add({ c: "comment text", a: "jared" }) - ); - await firebase.assertFails( - app.firestore().collection("cfm").add({ c: "comment text", a: "mike" }) - ); -}); diff --git a/crossword/src/Comments.tsx b/crossword/src/Comments.tsx deleted file mode 100644 index 5e739a89..00000000 --- a/crossword/src/Comments.tsx +++ /dev/null @@ -1,300 +0,0 @@ -/** @jsx jsx */ -import { jsx } from '@emotion/core'; -import * as React from 'react'; -import * as t from "io-ts"; -import { isRight } from 'fp-ts/lib/Either'; -import { PathReporter } from "io-ts/lib/PathReporter"; - -import { Identicon } from './Icons'; -import { timeString } from './utils'; -import { Emoji } from './Emoji'; -import { buttonAsLink } from './style'; -import { DisplayNameForm, getDisplayName } from './AccountPage'; -import { getFirebaseApp, getTimestampClass } from './firebase'; -import { - CommentWithOrWithoutRepliesT, CommentForModerationT, CommentForModerationWithIdT, CommentWithRepliesT, CommentT, - CommentForModerationWithIdV, -} from './common/dbtypes'; - -const COMMENT_LENGTH_LIMIT = 140; - -interface CommentProps { - puzzleAuthorId: string, - comment: CommentWithOrWithoutRepliesT, - children?: React.ReactNode -} -const Comment = (props: CommentProps) => { - return ( -
    -
    -
    {props.comment.c}
    - {props.children} -
    - ); -} - -const CommentWithReplies = (props: CommentFormProps & { comment: CommentWithOrWithoutRepliesT }) => { - const [showingForm, setShowingForm] = React.useState(false); - return ( - - {(props.user.isAnonymous || props.comment.i === undefined) ? - '' - : - (showingForm ? -
    - setShowingForm(false)} replyToId={props.comment.i} {...props} /> -
    - : -
    - ) - } - {props.comment.r ? -
      - {props.comment.r.map((a, i) =>
    • )} -
    - : - '' - } -
    - ) -} - -function commentsKey(puzzleId: string) { - return 'comments/' + puzzleId; -} - -function commentsFromStorage(puzzleId: string): Array { - const inSession = localStorage.getItem(commentsKey(puzzleId)); - if (inSession) { - const res = t.array(CommentForModerationWithIdV).decode(JSON.parse(inSession)); - if (isRight(res)) { - return res.right; - } else { - console.error("Couldn't parse object in local storage"); - console.error(PathReporter.report(res).join(",")); - } - } - return []; -} - -interface CommentFlairProps { - solveTime: number, - didCheat: boolean, - displayName: string, - userId: string, - puzzleAuthorId: string, -} -const CommentFlair = (props: CommentFlairProps) => { - return ( - - - {props.displayName} - {(props.userId === props.puzzleAuthorId) ? - constructor - : - - {props.didCheat ? : } - < span css={{ - fontSize: '0.75em', - backgroundColor: 'var(--caption)', - color: 'white', - borderRadius: 5, - padding: '0.1em 0.2em', - }}>{timeString(props.solveTime, false)} - - } - - ); -} - -interface CommentFormProps { - displayName: string, - setDisplayName: (name: string) => void, - puzzleAuthorId: string, - user: firebase.User, - solveTime: number, - didCheat: boolean, - puzzleId: string, - replyToId?: string, -} - -const CommentForm = ({ onCancel, ...props }: CommentFormProps & { onCancel?: () => void }) => { - const [commentText, setCommentText] = React.useState(''); - const [editingDisplayName, setEditingDisplayName] = React.useState(false); - const [submittedComment, setSubmittedComment] = React.useState(null); - - function sanitize(input: string) { - return input.substring(0, COMMENT_LENGTH_LIMIT); - } - - function submitComment(event: React.FormEvent) { - event.preventDefault(); - const textToSubmit = commentText.trim(); - if (!textToSubmit) { - return; - } - const comment: CommentForModerationT = { - c: textToSubmit, - a: props.user.uid, - n: props.displayName, - t: props.solveTime, - ch: props.didCheat, - p: getTimestampClass().now(), - pid: props.puzzleId, - rt: props.replyToId !== undefined ? props.replyToId : null - } - console.log("Submitting comment", comment); - const db = getFirebaseApp().firestore(); - // Add to moderation queue for long term - db.collection('cfm').add(comment).then((ref) => { - console.log("Uploaded", ref.id); - - // Replace this form w/ the comment for the short term - setSubmittedComment(comment); - // Add the comment to localStorage for the medium term - const forSession = commentsFromStorage(props.puzzleId); - forSession.push({ i: ref.id, ...comment }); - localStorage.setItem(commentsKey(props.puzzleId), JSON.stringify(forSession)); - }); - } - - if (submittedComment) { - return - } - - return ( - -
    -