diff options
-rw-r--r-- | package-lock.json | 262 | ||||
-rw-r--r-- | package.json | 5 | ||||
-rw-r--r-- | src/client/util/CalendarManager.scss | 62 | ||||
-rw-r--r-- | src/client/util/CalendarManager.tsx | 229 | ||||
-rw-r--r-- | src/client/views/DocumentButtonBar.tsx | 21 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/MapBox/MapBox.scss | 3 | ||||
-rw-r--r-- | src/client/views/nodes/MapBox/MapBox.tsx | 851 | ||||
-rw-r--r-- | src/client/views/nodes/MapBox/MapboxApiUtility.ts | 98 |
9 files changed, 1003 insertions, 530 deletions
diff --git a/package-lock.json b/package-lock.json index b0748aae6..9dec742fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3992,6 +3992,234 @@ } } }, + "@mui/x-date-pickers": { + "version": "6.18.5", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.18.5.tgz", + "integrity": "sha512-3jImYIWP2Xgi608yzm/Sz1v0MTjQQYdZSQOEIi3dWBfSAU9B06KXDpqlXfRSpTV+rtsnfYIIyiWlz6Ltk7sUWw==", + "requires": { + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.22", + "@mui/utils": "^5.14.16", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", + "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, + "@floating-ui/react-dom": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", + "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", + "requires": { + "@floating-ui/dom": "^1.5.1" + } + }, + "@mui/base": { + "version": "5.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.27.tgz", + "integrity": "sha512-duL37qxihT1N0pW/gyXVezP7SttLkF+cLAs/y6g6ubEFmVadjbnZ45SeF12/vAiKzqwf5M0uFH1cczIPXFZygA==", + "requires": { + "@babel/runtime": "^7.23.5", + "@floating-ui/react-dom": "^2.0.4", + "@mui/types": "^7.2.11", + "@mui/utils": "^5.15.0", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1" + } + }, + "@mui/types": { + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.11.tgz", + "integrity": "sha512-KWe/QTEsFFlFSH+qRYf3zoFEj3z67s+qAuSnMMg+gFwbxG7P96Hm6g300inQL1Wy///gSRb8juX7Wafvp93m3w==" + }, + "@mui/utils": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.0.tgz", + "integrity": "sha512-XSmTKStpKYamewxyJ256+srwEnsT3/6eNo6G7+WC1tj2Iq9GfUJ/6yUoB7YXjOD2jTZ3XobToZm4pVz1LBt6GA==", + "requires": { + "@babel/runtime": "^7.23.5", + "@types/prop-types": "^15.7.11", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + } + }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" + }, + "@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + }, + "@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "requires": { + "@types/react": "*" + } + }, + "clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==" + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + } + } + }, + "@mui/x-date-pickers-pro": { + "version": "6.18.5", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers-pro/-/x-date-pickers-pro-6.18.5.tgz", + "integrity": "sha512-f1JKEpi0cVmsOTab3rnx1W4aEPuKnF9IU3Ib7an75rW321QdwFYTm9+V4ZwWnN0jwDq8+13jjhdDEzfqcX76RA==", + "requires": { + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.22", + "@mui/utils": "^5.14.16", + "@mui/x-date-pickers": "6.18.5", + "@mui/x-license-pro": "6.10.2", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", + "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, + "@floating-ui/react-dom": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", + "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", + "requires": { + "@floating-ui/dom": "^1.5.1" + } + }, + "@mui/base": { + "version": "5.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.27.tgz", + "integrity": "sha512-duL37qxihT1N0pW/gyXVezP7SttLkF+cLAs/y6g6ubEFmVadjbnZ45SeF12/vAiKzqwf5M0uFH1cczIPXFZygA==", + "requires": { + "@babel/runtime": "^7.23.5", + "@floating-ui/react-dom": "^2.0.4", + "@mui/types": "^7.2.11", + "@mui/utils": "^5.15.0", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1" + } + }, + "@mui/types": { + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.11.tgz", + "integrity": "sha512-KWe/QTEsFFlFSH+qRYf3zoFEj3z67s+qAuSnMMg+gFwbxG7P96Hm6g300inQL1Wy///gSRb8juX7Wafvp93m3w==" + }, + "@mui/utils": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.0.tgz", + "integrity": "sha512-XSmTKStpKYamewxyJ256+srwEnsT3/6eNo6G7+WC1tj2Iq9GfUJ/6yUoB7YXjOD2jTZ3XobToZm4pVz1LBt6GA==", + "requires": { + "@babel/runtime": "^7.23.5", + "@types/prop-types": "^15.7.11", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + } + }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" + }, + "@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + }, + "clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==" + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + } + } + }, + "@mui/x-license-pro": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@mui/x-license-pro/-/x-license-pro-6.10.2.tgz", + "integrity": "sha512-Baw3shilU+eHgU+QYKNPFUKvfS5rSyNJ98pQx02E0gKA22hWp/XAt88K1qUfUMPlkPpvg/uci6gviQSSLZkuKw==", + "requires": { + "@babel/runtime": "^7.22.6", + "@mui/utils": "^5.13.7" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", + "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, + "@mui/utils": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.0.tgz", + "integrity": "sha512-XSmTKStpKYamewxyJ256+srwEnsT3/6eNo6G7+WC1tj2Iq9GfUJ/6yUoB7YXjOD2jTZ3XobToZm4pVz1LBt6GA==", + "requires": { + "@babel/runtime": "^7.23.5", + "@types/prop-types": "^15.7.11", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + } + }, + "@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + } + } + }, "@nicolo-ribaudo/chokidar-2": { "version": "2.1.8-no-fsevents.3", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", @@ -7695,6 +7923,16 @@ "@types/reactcss": "*" } }, + "@types/react-date-range": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@types/react-date-range/-/react-date-range-1.4.9.tgz", + "integrity": "sha512-5oVEDW0ElYmY1+YVSzdMUR8stxSI5QrRJCgCFUvuEAV5197t412vimD9aVTW6g4JTaxCnMmB1BdEOT/odpaBxQ==", + "dev": true, + "requires": { + "@types/react": "*", + "date-fns": "^2.16.1" + } + }, "@types/react-datepicker": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-3.1.8.tgz", @@ -12838,6 +13076,11 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==" }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -27697,6 +27940,17 @@ } } }, + "react-date-range": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/react-date-range/-/react-date-range-1.4.0.tgz", + "integrity": "sha512-+9t0HyClbCqw1IhYbpWecjsiaftCeRN5cdhsi9v06YdimwyMR2yYHWcgVn3URwtN/txhqKpEZB6UX1fHpvK76w==", + "requires": { + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react-list": "^0.8.13", + "shallow-equal": "^1.2.1" + } + }, "react-datepicker": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.8.0.tgz", @@ -27879,6 +28133,14 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-list": { + "version": "0.8.17", + "resolved": "https://registry.npmjs.org/react-list/-/react-list-0.8.17.tgz", + "integrity": "sha512-pgmzGi0G5uGrdHzMhgO7KR1wx5ZXVvI3SsJUmkblSAKtewIhMwbQiMuQiTE83ozo04BQJbe0r3WIWzSO0dR1xg==", + "requires": { + "prop-types": "15" + } + }, "react-loader-spinner": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-5.3.4.tgz", diff --git a/package.json b/package.json index 5d53a6c4e..58a25a68c 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "@types/react": "^18.0.15", "@types/react-autosuggest": "^9.3.14", "@types/react-color": "^2.17.6", + "@types/react-date-range": "^1.4.9", "@types/react-datepicker": "^3.1.8", "@types/react-dom": "^18.0.6", "@types/react-grid-layout": "^1.3.2", @@ -148,6 +149,8 @@ "@material-ui/core": "^4.12.3", "@mui/icons-material": "^5.11.16", "@mui/material": "^5.13.1", + "@mui/x-date-pickers": "^6.18.5", + "@mui/x-date-pickers-pro": "^6.18.5", "@octokit/core": "^4.0.4", "@react-google-maps/api": "^2.7.0", "@react-three/fiber": "^6.2.3", @@ -201,6 +204,7 @@ "csv-parser": "^3.0.0", "csv-stringify": "^6.3.0", "d3": "^7.8.5", + "dayjs": "^1.11.10", "debounce": "^2.0.0", "depcheck": "^0.9.2", "equation-editor-react": "github:bobzel/equation-editor-react#useLocally", @@ -296,6 +300,7 @@ "react-chartjs-2": "^4.3.0", "react-color": "^2.19.3", "react-compound-slider": "^2.5.0", + "react-date-range": "^1.4.0", "react-datepicker": "^3.8.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", diff --git a/src/client/util/CalendarManager.scss b/src/client/util/CalendarManager.scss new file mode 100644 index 000000000..60610f298 --- /dev/null +++ b/src/client/util/CalendarManager.scss @@ -0,0 +1,62 @@ +.calendar-interface{ + width: 600px; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + gap: 7px; + padding: 10px; + + .selected-doc-title{ + font-size: 1.4em; + } + + .creation-type-container{ + display: flex; + justify-content: center; + align-items: center; + align-self: center; + gap: 12px; + + .calendar-creation{ + cursor: pointer; + } + + .calendar-creation-selected{ + border-bottom: 2px solid white; + } + } + + .choose-calendar-container{ + margin-top: 10px; + align-self: center; + width: 60%; + + .MuiFilledInput-input{ + padding: 10px; + } + } + + .date-range-picker-container{ + margin-top: 5px; + align-self:center; + + .react-date-range{ + + } + } + + .create-button-container{ + + margin-top: 5px; + align-self: center; + + .button-content{ + font-size: 1.2em; + padding: 10px; + border-radius: 5px; + background-color: #EFF2F7; + } + + } +} diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx new file mode 100644 index 000000000..39ba41652 --- /dev/null +++ b/src/client/util/CalendarManager.tsx @@ -0,0 +1,229 @@ +import * as React from 'react'; +import './CalendarManager.scss'; +import { observer } from "mobx-react"; +import { action, computed, observable, runInAction } from 'mobx'; +import { Doc } from '../../fields/Doc'; +import { DocumentView } from '../views/nodes/DocumentView'; +import { DictationOverlay } from '../views/DictationOverlay'; +import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; +import { MainViewModal } from '../views/MainViewModal'; +import { TextField } from '@material-ui/core'; +import Select from 'react-select'; +import { SettingsManager } from './SettingsManager'; +import { StrCast } from '../../fields/Types'; +import { SelectionManager } from './SelectionManager'; +import { DocumentManager } from './DocumentManager'; +import { DocData } from '../../fields/DocSymbols'; +import { DateRange, Range, RangeKeyDict } from 'react-date-range'; +import { Button } from 'browndash-components'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons'; +import 'react-date-range/dist/styles.css'; +import 'react-date-range/dist/theme/default.css'; + +type CreationType = 'new-calendar' | 'existing-calendar' | 'manage-calendars'; + +@observer +export class CalendarManager extends React.Component<{}> { + public static Instance: CalendarManager; + @observable private isOpen = false; + @observable private targetDoc: Doc | undefined; // the target document + @observable private targetDocView: DocumentView | undefined; // the DocumentView of the target doc + @observable private dialogueBoxOpacity = 1; // for the modal + @observable private overlayOpacity = 0.4; // for the modal + + @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used + + @observable private creationType: CreationType = 'new-calendar'; + + @observable + calendarName: string = ""; + + @action + setInterationType = (type: CreationType) => { + this.creationType = type; + } + + + public open = (target?: DocumentView, target_doc?: Doc) => { + console.log('hi'); + runInAction(() => { + this.targetDoc = target_doc || target?.props.Document; + this.targetDocView = target; + DictationOverlay.Instance.hasActiveModal = true; + this.isOpen = this.targetDoc !== undefined; + }) + }; + + public close = action(() => { + this.isOpen = false; + TaskCompletionBox.taskCompleted = false; + setTimeout( + action(() => { + DictationOverlay.Instance.hasActiveModal = false; + this.targetDoc = undefined; + }), 500 + ); + this.layoutDocAcls = false; + }); + + constructor(props: {}) { + super(props); + CalendarManager.Instance = this; + } + + componentDidMount(): void { + + } + + private focusOn = (contents: string) => { + const title = this.targetDoc ? StrCast(this.targetDoc.title) : ''; + const docs = SelectionManager.Views().length > 1 ? SelectionManager.Views().map(docView => docView.props.Document) : [this.targetDoc]; + return ( + <span + className="focus-span" + title={title} + onClick={() => { + if (this.targetDoc && this.targetDocView && docs.length === 1) { + DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true }); + } + }} + onPointerEnter={action(() => { + if (docs.length) { + docs.forEach(doc => doc && Doc.BrushDoc(doc)); + this.dialogueBoxOpacity = 0.1; + this.overlayOpacity = 0.1; + } + })} + onPointerLeave={action(() => { + if (docs.length) { + docs.forEach(doc => doc && Doc.UnBrushDoc(doc)); + this.dialogueBoxOpacity = 1; + this.overlayOpacity = 0.4; + } + })}> + {contents} + </span> + ); + }; + + @observable + selectedDateRange: Range[] = [{ + startDate: new Date(), + endDate: undefined, + key: 'selection' + }] + + @action + setSelectedDateRange = (range: Range[]) => { + this.selectedDateRange = range; + } + + @computed + get createButtonActive() { + if (this.calendarName.length === 0) return false // disabled if no calendar name + let startDate: Date | undefined; + let endDate: Date | undefined; + try { + startDate = this.selectedDateRange[0].startDate; + endDate = this.selectedDateRange[0].endDate; + } catch (e: any){ + return false; // disabled + } + if (!startDate || !endDate) return false; // disabled if any is undefined + return true; + } + + @computed + get calendarInterface(){ + let docs = SelectionManager.Views().length < 2 ? [this.targetDoc] : SelectionManager.Views().map(docView => docView.rootDoc); + const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; + + const currentDate = new Date(); + + return ( + <div + className='calendar-interface' + style= {{ + background: SettingsManager.userBackgroundColor, + color: StrCast(Doc.UserDoc().userColor) + }} + > + <p className="selected-doc-title" style={{ color: SettingsManager.userColor }}> + <b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}</b> + </p> + <div className='creation-type-container'> + <div + className={`calendar-creation ${this.creationType === 'new-calendar' ? 'calendar-creation-selected' : ''}`} + onClick={(e) => this.setInterationType('new-calendar')} + > + Add to New Calendar + </div> + <div + className={`calendar-creation ${this.creationType === 'existing-calendar' ? 'calendar-creation-selected' : ''}`} + onClick={(e) => this.setInterationType('existing-calendar')} + > + Add to Existing calendar + </div> + </div> + <div className='choose-calendar-container'> + {this.creationType === 'new-calendar' ? + <TextField + fullWidth + placeholder='Enter calendar name...' + variant='filled' + style={{ + backgroundColor: 'white', + color: 'black', + borderRadius: '5px' + + }} + + /> + : + <Select + className='existing-calendar-search' + placeholder='Search for existing calendar...' + isMulti + isSearchable + > + + </Select> + } + </div> + <div className='date-range-picker-container'> + <DateRange + className='react-date-range' + editableDateInputs={true} + // ranges={[selectionRange]} + onChange={item => this.setSelectedDateRange([item.selection])} + ranges={this.selectedDateRange} + // onChange={this.handleSelect} + /> + </div> + <div className='create-button-container'> + <Button + active={this.createButtonActive} + text="Add to Calendar" + iconPlacement='right' + icon={<FontAwesomeIcon icon={faPlus as IconLookup}/>} + /> + </div> + + </div> + ) + } + + render() { + return ( + <MainViewModal + contents={this.calendarInterface} + isDisplayed={this.isOpen} + interactive={true} + dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} + overlayDisplayedOpacity={this.overlayOpacity} + closeOnExternalClick={this.close} + /> + ) + } +}
\ No newline at end of file diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index bd82f7782..ccd459f0d 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -1,4 +1,4 @@ -import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { IconLookup, IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, observable, runInAction } from 'mobx'; @@ -14,6 +14,7 @@ import { DragManager } from '../util/DragManager'; import { IsFollowLinkScript } from '../util/LinkFollower'; import { SelectionManager } from '../util/SelectionManager'; import { SharingManager } from '../util/SharingManager'; +import { CalendarManager } from '../util/CalendarManager'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { TabDocView } from './collections/TabDocView'; @@ -28,6 +29,8 @@ import { GoogleRef } from './nodes/formattedText/FormattedTextBox'; import { PinProps } from './nodes/trails'; import { TemplateMenu } from './TemplateMenu'; import React = require('react'); +import { faCalendarDays } from '@fortawesome/free-solid-svg-icons'; + const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -443,6 +446,21 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ); } + @computed + get calendarButton(){ + const targetDoc = this.view0?.props.Document; + return !targetDoc ? null : ( + <Tooltip title={<div className='dash-calendar-button'>Open calendar menu</div>}> + <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={e => { + console.log('hi: ', CalendarManager.Instance) + CalendarManager.Instance.open(this.view0, targetDoc)} + }> + <FontAwesomeIcon className="documentdecorations-icon" icon={faCalendarDays as IconLookup}/> + </div> + </Tooltip> + ) + } + @observable _isRecording = false; _stopFunc: () => void = emptyFunction; @computed @@ -606,6 +624,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>} <div className="documentButtonBar-button">{this.pinButton}</div> <div className="documentButtonBar-button">{this.recordButton}</div> + <div className="documentButtonBar-button">{this.calendarButton}</div> {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : <div className="documentButtonBar-button">{this.shareButton}</div>} {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : ( <div className="documentButtonBar-button" style={{ display: !considerPush() ? 'none' : '' }}> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 13f7dc896..d45f24930 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -72,6 +72,7 @@ import { PropertiesView } from './PropertiesView'; import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider'; import { TopBar } from './topbar/TopBar'; import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu'; +import { CalendarManager } from '../util/CalendarManager'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -1038,6 +1039,7 @@ export class MainView extends React.Component { {this.inkResources} <DictationOverlay /> <SharingManager /> + <CalendarManager /> <ServerStats /> <RTFMarkup /> <SettingsManager /> diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss index e25261729..ba1e99f84 100644 --- a/src/client/views/nodes/MapBox/MapBox.scss +++ b/src/client/views/nodes/MapBox/MapBox.scss @@ -88,12 +88,11 @@ flex-direction: column; justify-content: flex-start; align-items: flex-start; - position: absolute; background-color: rgb(187, 187, 187); padding: 10px; border-top-right-radius: 5px; border-bottom-right-radius: 5px; - width: 100%; + position: absolute; #route-to-animate-title { font-size: 1.25em; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 348e52d6c..5c4a6203a 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -3,7 +3,7 @@ import BingMapsReact from 'bingmaps-react'; // import 'mapbox-gl/dist/mapbox-gl.css'; import { Button, EditableText, IconButton, Size, Type } from 'browndash-components'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, flow, toJS, autorun} from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, flow, toJS, autorun } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc'; @@ -28,28 +28,27 @@ import { FieldView, FieldViewProps } from '../FieldView'; import { FormattedTextBox } from '../formattedText/FormattedTextBox'; import { PinProps, PresBox } from '../trails'; import { MapAnchorMenu } from './MapAnchorMenu'; -import * as HME from "h264-mp4-encoder"; -import {simd} from 'wasm-feature-detect'; -import { +import { Map as MapboxMap, MapRef, - Marker, - ControlPosition, - FullscreenControl, - MapProvider, - MarkerProps, - NavigationControl, - ScaleControl, - ViewState, + Marker, + ControlPosition, + FullscreenControl, + MapProvider, + MarkerProps, + NavigationControl, + ScaleControl, + ViewState, ViewStateChangeEvent, useControl, GeolocateControl, Popup, MapEvent, Source, - Layer} from 'react-map-gl'; -import MapboxGeocoder, {GeocoderOptions} from '@mapbox/mapbox-gl-geocoder'; + Layer, +} from 'react-map-gl'; +import MapboxGeocoder, { GeocoderOptions } from '@mapbox/mapbox-gl-geocoder'; import debounce from 'debounce'; import './MapBox.scss'; import { NumberLiteralType } from 'typescript'; @@ -57,7 +56,7 @@ import { NumberLiteralType } from 'typescript'; import mapboxgl, { LngLat, LngLatBoundsLike, LngLatLike, MapLayerMouseEvent, MercatorCoordinate } from 'mapbox-gl'; import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, MultiLineString, Position } from 'geojson'; import { MarkerEvent } from 'react-map-gl/dist/esm/types'; -import { MapboxApiUtility, TransportationType} from './MapboxApiUtility'; +import { MapboxApiUtility, TransportationType } from './MapboxApiUtility'; import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; @@ -65,7 +64,7 @@ import { IconLookup, faCircleXmark, faFileExport, faGear, faMinus, faPause, faPl import { MarkerIcons } from './MarkerIcons'; import { SettingsManager } from '../../../util/SettingsManager'; import * as turf from '@turf/turf'; -import * as d3 from "d3"; +import * as d3 from 'd3'; import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility'; import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons'; import { CirclePicker, ColorState } from 'react-color'; @@ -91,24 +90,24 @@ const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/map const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/'; type PopupInfo = { - longitude: number, - latitude: number, - title: string, - description: string -} + longitude: number; + latitude: number; + title: string; + description: string; +}; export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & { - mapboxAccessToken: string, + mapboxAccessToken: string; marker?: Omit<MarkerProps, 'longitude' | 'latitude'>; position: ControlPosition; onResult: (...args: any[]) => void; -} +}; type MapMarker = { - longitude: number, - latitude: number -} + longitude: number; + latitude: number; +}; /** * Consider integrating later: allows for drawing, circling, making shapes on map @@ -157,7 +156,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const originalCoordinates: Position[] = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates)); // const index = Math.floor(this.animationPhase * originalCoordinates.length); const index = this.animationPhase * (originalCoordinates.length - 1); // Calculate the fractional index - console.log("Animation phase", this.animationPhase); + console.log('Animation phase', this.animationPhase); const startIndex = Math.floor(index); const endIndex = Math.ceil(index); let feature: Feature<Geometry, GeoJsonProperties>; @@ -173,7 +172,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps feature = { type: 'Feature', properties: { - 'routeTitle': StrCast(this.routeToAnimate.title) + routeTitle: StrCast(this.routeToAnimate.title), }, geometry: geometry, }; @@ -186,7 +185,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const interpolator = d3.interpolateArray(startCoord, endCoord); const interpolatedCoord = interpolator(fraction); - + const coordinates = originalCoordinates.slice(0, startIndex + 1).concat([interpolatedCoord]); geometry = { @@ -196,12 +195,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps feature = { type: 'Feature', properties: { - 'routeTitle': StrCast(this.routeToAnimate.title) + routeTitle: StrCast(this.routeToAnimate.title), }, geometry: geometry, }; } - + autorun(() => { const animationUtil = this.animationUtility; const concattedCoordinates = geometry.coordinates.concat(originalCoordinates.slice(endIndex)); @@ -210,28 +209,28 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps properties: {}, geometry: { type: 'LineString', - coordinates: concattedCoordinates - } - } - if (animationUtil){ - animationUtil.setPath(newFeature) + coordinates: concattedCoordinates, + }, + }; + if (animationUtil) { + animationUtil.setPath(newFeature); } - }) + }); return feature; } - console.log("ERROR"); + console.log('ERROR'); return { type: 'Feature', properties: {}, geometry: { type: 'LineString', - coordinates: [] + coordinates: [], }, }; } @computed get selectedRouteCoordinates(): Position[] { let coordinates: Position[] = []; - if (this.routeToAnimate?.routeCoordinates){ + if (this.routeToAnimate?.routeCoordinates) { coordinates = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates)); } return coordinates; @@ -247,7 +246,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return { type: 'Feature', properties: { - 'routeTitle': routeDoc.title}, + routeTitle: routeDoc.title, + }, geometry: geometry, }; }); @@ -274,10 +274,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps componentDidMount() { this._unmounting = false; this.props.setContentView?.(this); - } - _unmounting = false; componentWillUnmount(): void { this._unmounting = true; @@ -509,7 +507,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // The pin that is selected @observable selectedPinOrRoute: Doc | undefined; - @action deselectPinOrRoute = () => { if (this.selectedPinOrRoute) { @@ -517,7 +514,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove'); // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove'); // Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove'); - // const temp = this.selectedPin; // if (!this._unmounting) { // this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp)); @@ -533,7 +529,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } }; - getView = async (doc: Doc) => { if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(); return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); @@ -692,8 +687,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true); }, 'delete pin'); - - tryHideMapAnchorMenu = (e: PointerEvent) => { let target = document.elementFromPoint(e.x, e.y); while (target) { @@ -711,8 +704,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps centerOnSelectedPin = () => { if (this.selectedPinOrRoute) { this._mapRef.current?.flyTo({ - center: [NumCast(this.selectedPinOrRoute.longitude), NumCast(this.selectedPinOrRoute.latitude)] - }) + center: [NumCast(this.selectedPinOrRoute.longitude), NumCast(this.selectedPinOrRoute.latitude)], + }); } // if (this.selectedPin) { // this.dataDoc.latitude = this.selectedPin.latitude; @@ -733,7 +726,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps mapTypeId: 'grayscale', }; - /** * Map options */ @@ -752,8 +744,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }, }; - - recolorPin = (pin: Doc, color?: string) => { // this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin)); // this.map_docToPinMap.delete(pin); @@ -829,22 +819,24 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps setupMoveUpEvents( e, e, - e => { // move event + e => { + // move event if (!dragClone) { - dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement; // copy draggable pin + dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement; // copy draggable pin dragClone.style.position = 'absolute'; dragClone.style.zIndex = '10000'; - DragManager.Root().appendChild(dragClone); // add clone to root + DragManager.Root().appendChild(dragClone); // add clone to root } dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`; return false; }, - e => { // up event + e => { + // up event if (!dragClone) return; DragManager.Root().removeChild(dragClone); let target = document.elementFromPoint(e.x, e.y); // element for specified x and y coordinates while (target) { - if (target === this._ref.current) { + if (target === this._ref.current) { const cpt = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); const x = cpt[0] - (this.props.PanelWidth() - this.sidebarWidth()) / 2; const y = cpt[1] - 20 /* height of search bar */ - this.props.PanelHeight() / 2; @@ -864,7 +856,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ); }; - // incrementer: number = 0; + // incrementer: number = 0; /* * Creates Pushpin doc and adds it to the list of annotations */ @@ -877,13 +869,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps false, [], { - title: location ?? `lat=${NumCast(latitude)},lng=${NumCast(longitude)}`, - map: location, - description: "", + title: location ?? `lat=${NumCast(latitude)},lng=${NumCast(longitude)}`, + map: location, + description: '', wikiData: wikiData, markerType: 'MAP_PIN', - markerColor: '#ff5722' - }, + markerColor: '#ff5722', + } // { title: map ?? `lat=${latitude},lng=${longitude}`, map: map }, // ,'pushpinIDamongus'+ this.incrementer++ ); @@ -895,60 +887,43 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }, 'createpin'); @action - createMapRoute = undoable((coordinates: Position[], origin: string, destination: any, createPinForDestination: boolean) => { - const mapRoute = Docs.Create.MapRouteDocument( - false, - [], - {title: `${origin} --> ${destination.place_name}`, routeCoordinates: JSON.stringify(coordinates)}, - ); - this.addDocument(mapRoute, this.annotationKey); - if (createPinForDestination) { - this.createPushpin(destination.center[1], destination.center[0], destination.place_name); + createMapRoute = undoable((coordinates: Position[], originName: string, destination: any, createPinForDestination: boolean) => { + if (originName !== destination.place_name) { + const mapRoute = Docs.Create.MapRouteDocument(false, [], { title: `${originName} --> ${destination.place_name}`, routeCoordinates: JSON.stringify(coordinates) }); + this.addDocument(mapRoute, this.annotationKey); + if (createPinForDestination) { + this.createPushpin(destination.center[1], destination.center[0], destination.place_name); + } + return mapRoute; } - return mapRoute; - - // mapMarker.infoWindowOpen = true; + // TODO: Display error that can't create route to same location }, 'createmaproute'); searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch(); - - - - - @observable featuresFromGeocodeResults: any[] = []; - @action addMarkerForFeature = (feature: any) => { const location = feature.place_name; - if (feature.center){ + if (feature.center) { const longitude = feature.center[0]; const latitude = feature.center[1]; const wikiData = feature.properties?.wikiData; - - this.createPushpin( - latitude, - longitude, - location, - wikiData - ) - - if (this._mapRef.current){ + + this.createPushpin(latitude, longitude, location, wikiData); + + if (this._mapRef.current) { this._mapRef.current.flyTo({ - center: feature.center + center: feature.center, }); } this.featuresFromGeocodeResults = []; - } else { // TODO: handle error } - } - - + }; /** * Makes a forward geocoding API call to Mapbox to retrieve locations based on the search input @@ -956,12 +931,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps */ handleSearchChange = async (searchText: string) => { const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText); - if (features && !this.isAnimating){ + if (features && !this.isAnimating) { runInAction(() => { - this.settingsOpen= false; + this.settingsOpen = false; this.featuresFromGeocodeResults = features; this.routeToAnimate = undefined; - }) + }); } // try { // const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) +'.json' +`?access_token=${MAPBOX_ACCESS_TOKEN}`; @@ -971,34 +946,32 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // this.featuresFromGeocodeResults = data.features; // }) // } catch (error: any){ - // // TODO: handle error in better way + // // TODO: handle error in better way // console.log(error); // } - } + }; // @action // debouncedCall = React.useCallback(debounce(this.debouncedOnSearchBarChange, 300), []); - @action handleMapClick = (e: MapLayerMouseEvent) => { - if (this._mapRef.current){ - const features = this._mapRef.current.queryRenderedFeatures( - e.point, { - layers: ['map-routes-layer'] - } - ); + this.featuresFromGeocodeResults = []; + if (this._mapRef.current) { + const features = this._mapRef.current.queryRenderedFeatures(e.point, { + layers: ['map-routes-layer'], + }); console.error(features); if (features && features.length > 0 && features[0].properties && features[0].geometry) { const geometry = features[0].geometry as LineString; const routeTitle: string = features[0].properties['routeTitle']; - const routeDoc: Doc | undefined = this.allRoutes.find((routeDoc) => routeDoc.title === routeTitle); + const routeDoc: Doc | undefined = this.allRoutes.find(routeDoc => routeDoc.title === routeTitle); this.deselectPinOrRoute(); // TODO: Also deselect route if selected - if (routeDoc){ + if (routeDoc) { this.selectedPinOrRoute = routeDoc; Doc.setDocFilter(this.rootDoc, LinkedTo, `mapRoute=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); - // TODO: Recolor route + // TODO: Recolor route MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute; MapAnchorMenu.Instance.Center = this.centerOnSelectedPin; @@ -1008,9 +981,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps MapAnchorMenu.Instance.setRouteDoc(routeDoc); // TODO: Subject to change - MapAnchorMenu.Instance.setAllMapboxPins( - this.allAnnotations.filter(anno => !anno.layout_unrendered) - ) + MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered)); MapAnchorMenu.Instance.DisplayRoute = this.displayRoute; MapAnchorMenu.Instance.HideRoute = this.hideRoute; @@ -1025,16 +996,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true); document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true); - } + } } } - } - + }; /** * Makes a reverse geocoding API call to retrieve features corresponding to a map click (based on longitude - * and latitude). Sets the search results accordingly. - * @param e + * and latitude). Sets the search results accordingly. + * @param e */ handleMapDblClick = async (e: MapLayerMouseEvent) => { e.preventDefault(); @@ -1043,13 +1013,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const latitude: number = lngLat.lat; const features = await MapboxApiUtility.reverseGeocodeForFeatures(longitude, latitude); - if (features){ + if (features) { runInAction(() => { this.featuresFromGeocodeResults = features; - }) + }); } - // // REVERSE GEOCODE TO GET LOCATION DETAILS + // // REVERSE GEOCODE TO GET LOCATION DETAILS // try { // const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + "," + latitude.toString()) + '.json' + // `?access_token=${MAPBOX_ACCESS_TOKEN}`; @@ -1063,9 +1033,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // // TODO: handle error in better way // console.log(error); // } - } + }; - @observable + @observable currentPopup: PopupInfo | undefined = undefined; @action @@ -1080,18 +1050,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); this.recolorPin(this.selectedPinOrRoute, 'green'); // TODO: check this method - - + MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute; MapAnchorMenu.Instance.Center = this.centerOnSelectedPin; MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation; MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag; - // pass in the pinDoc + // pass in the pinDoc MapAnchorMenu.Instance.setPinDoc(pinDoc); - MapAnchorMenu.Instance.setAllMapboxPins( - this.allAnnotations.filter(anno => !anno.layout_unrendered) - ) + MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered)); MapAnchorMenu.Instance.DisplayRoute = this.displayRoute; MapAnchorMenu.Instance.HideRoute = this.hideRoute; @@ -1106,22 +1073,20 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true); - - // this._mapRef.current.flyTo({ - // center: [NumCast(pinDoc.longitude), NumCast(pinDoc.latitude)-3] - // }) - + // this._mapRef.current.flyTo({ + // center: [NumCast(pinDoc.longitude), NumCast(pinDoc.latitude)-3] + // }) }; @observable temporaryRouteSource: FeatureCollection = { type: 'FeatureCollection', - features: [] - } + features: [], + }; @action displayRoute = (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => { - if (routeInfoMap){ + if (routeInfoMap) { const newTempRouteSource: FeatureCollection = { type: 'FeatureCollection', features: [ @@ -1130,16 +1095,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps properties: {}, geometry: { type: 'LineString', - coordinates: routeInfoMap[type].coordinates - } - } - ] - } - // TODO: Create pin for destination - // TODO: Fly to point where full route will be shown + coordinates: routeInfoMap[type].coordinates, + }, + }, + ], + }; + // TODO: Create pin for destination + // TODO: Fly to point where full route will be shown this.temporaryRouteSource = newTempRouteSource; } - } + }; @observable isAnimating: boolean = false; @@ -1147,10 +1112,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable routeToAnimate: Doc | undefined = undefined; - @observable + @observable animationPhase: number = 0; - @observable + @observable finishedFlyTo: boolean = false; @action @@ -1158,73 +1123,72 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.animationPhase = newValue; }; - @observable + @observable frameId: number | null = null; @action setFrameId = (frameId: number) => { this.frameId = frameId; - } + }; - @observable + @observable animationUtility: AnimationUtility | null = null; @action setAnimationUtility = (util: AnimationUtility) => { this.animationUtility = util; - } + }; @action openAnimationPanel = (routeDoc: Doc | undefined) => { - if (routeDoc){ + if (routeDoc) { MapAnchorMenu.Instance.fadeOut(true); document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true); + this.featuresFromGeocodeResults = []; this.routeToAnimate = routeDoc; } - } + }; @observable mapboxMapViewState: ViewState = { zoom: this.dataDoc.map_zoom ? NumCast(this.dataDoc.map_zoom) : 8, - longitude: this.dataDoc.longitude ? NumCast(this.dataDoc.longitude) : -71.4128, - latitude: this.dataDoc.latitude ? NumCast(this.dataDoc.latitude) : 41.8240, + longitude: this.dataDoc.longitude ? NumCast(this.dataDoc.longitude) : -71.4128, + latitude: this.dataDoc.latitude ? NumCast(this.dataDoc.latitude) : 41.824, pitch: this.dataDoc.map_pitch ? NumCast(this.dataDoc.map_pitch) : 0, bearing: this.dataDoc.map_bearing ? NumCast(this.dataDoc.map_bearing) : 0, padding: { top: 0, bottom: 0, left: 0, - right: 0 + right: 0, }, - } + }; - @computed + @computed get preAnimationViewState() { - if (!this.isAnimating){ + if (!this.isAnimating) { return this.mapboxMapViewState; } } - - @observable + @observable isStreetViewAnimation: boolean = false; - @observable + @observable animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM; - @observable animationLineColor: string = '#ffff00'; @action setAnimationLineColor = (color: ColorState) => { this.animationLineColor = color.hex; - } + }; @action updateAnimationSpeed = () => { let newAnimationSpeed: AnimationSpeed; - switch (this.animationSpeed){ + switch (this.animationSpeed) { case AnimationSpeed.SLOW: newAnimationSpeed = AnimationSpeed.MEDIUM; break; @@ -1239,63 +1203,62 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps break; } this.animationSpeed = newAnimationSpeed; - if (this.animationUtility){ + if (this.animationUtility) { this.animationUtility.updateAnimationSpeed(newAnimationSpeed); } - } + }; @computed get animationSpeedTooltipText(): string { switch (this.animationSpeed) { - case AnimationSpeed.SLOW: - return '1x speed'; - case AnimationSpeed.MEDIUM: - return '2x speed'; - case AnimationSpeed.FAST: - return '3x speed'; - default: - return '2x speed'; + case AnimationSpeed.SLOW: + return '1x speed'; + case AnimationSpeed.MEDIUM: + return '2x speed'; + case AnimationSpeed.FAST: + return '3x speed'; + default: + return '2x speed'; } } - @computed get animationSpeedIcon(): JSX.Element{ + @computed get animationSpeedIcon(): JSX.Element { switch (this.animationSpeed) { case AnimationSpeed.SLOW: - return slowSpeedIcon; + return slowSpeedIcon; case AnimationSpeed.MEDIUM: - return mediumSpeedIcon; + return mediumSpeedIcon; case AnimationSpeed.FAST: - return fastSpeedIcon; + return fastSpeedIcon; default: - return mediumSpeedIcon; - } + return mediumSpeedIcon; + } } @action toggleIsStreetViewAnimation = () => { const newVal = !this.isStreetViewAnimation; this.isStreetViewAnimation = newVal; - if (this.animationUtility){ - this.animationUtility.updateIsStreetViewAnimation(newVal) + if (this.animationUtility) { + this.animationUtility.updateIsStreetViewAnimation(newVal); } - } + }; - @observable + @observable dynamicRouteFeature: Feature<Geometry, GeoJsonProperties> = { type: 'Feature', properties: {}, geometry: { type: 'LineString', - coordinates: [] - } + coordinates: [], + }, }; - - @observable + @observable path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties> = { type: 'Feature', geometry: { type: 'LineString', - coordinates: [] + coordinates: [], }, - properties: {} + properties: {}, }; getFeatureFromRouteDoc = (routeDoc: Doc): Feature<Geometry, GeoJsonProperties> => { @@ -1306,14 +1269,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return { type: 'Feature', properties: { - 'routeTitle': routeDoc.title}, + routeTitle: routeDoc.title, + }, geometry: geometry, }; - } + }; - @action + @action playAnimation = (status: AnimationStatus) => { - if (!this._mapRef.current || !this.routeToAnimate){ + if (!this._mapRef.current || !this.routeToAnimate) { return; } @@ -1322,43 +1286,32 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.finishedFlyTo = AnimationStatus.RESUME ? this.finishedFlyTo : false; const path = turf.lineString(this.selectedRouteCoordinates); - + this.settingsOpen = false; this.path = path; this.isAnimating = true; runInAction(() => { - return new Promise<void>(async (resolve) => { + return new Promise<void>(async resolve => { const targetLngLat = { lng: this.selectedRouteCoordinates[0][0], lat: this.selectedRouteCoordinates[0][1], }; - const animationUtil = new AnimationUtility( - targetLngLat, - this.selectedRouteCoordinates, - this.isStreetViewAnimation, - this.animationSpeed, - this.showTerrain, - this._mapRef.current - ); + const animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this.isStreetViewAnimation, this.animationSpeed, this.showTerrain, this._mapRef.current); runInAction(() => { this.setAnimationUtility(animationUtil); - }) - + }); const updateFrameId = (newFrameId: number) => { this.setFrameId(newFrameId); - } + }; - const updateAnimationPhase = ( - newAnimationPhase: number, - ) => { + const updateAnimationPhase = (newAnimationPhase: number) => { this.setAnimationPhase(newAnimationPhase); }; - - if (status !== AnimationStatus.RESUME) { + if (status !== AnimationStatus.RESUME) { const result = await animationUtil.flyInAndRotate({ map: this._mapRef.current!, // targetLngLat, @@ -1372,18 +1325,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps updateFrameId, }); - console.log("Bearing: ", result.bearing); - console.log("Altitude: ", result.altitude); - - } + console.log('Bearing: ', result.bearing); + console.log('Altitude: ', result.altitude); + } runInAction(() => { this.finishedFlyTo = true; - }) + }); // follow the path while slowly rotating the camera, passing in the camera bearing and altitude from the previous animation await animationUtil.animatePath({ - map: this._mapRef.current!, + map: this._mapRef.current!, // path: this.path, // startBearing: -20, // startAltitude: this.isStreetViewAnimation ? 80 : 12000, @@ -1392,43 +1344,39 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps updateAnimationPhase, updateFrameId, }); - + // get the bounds of the linestring, use fitBounds() to animate to a final view const bbox3d = turf.bbox(this.path); - + const bbox2d: LngLatBoundsLike = [bbox3d[0], bbox3d[1], bbox3d[2], bbox3d[3]]; - + this._mapRef.current!.fitBounds(bbox2d, { duration: 3000, pitch: 30, bearing: 0, padding: 120, }); - + setTimeout(() => { this.isStreetViewAnimation = false; resolve(); }, 10000); }); - - }) - - - } - + }); + }; - @action + @action pauseAnimation = () => { - if (this.frameId && this.animationPhase > 0){ + if (this.frameId && this.animationPhase > 0) { window.cancelAnimationFrame(this.frameId); this.frameId = null; this.isAnimating = false; } - } + }; @action stopAnimation = (close: boolean) => { - if (this.frameId){ + if (this.frameId) { window.cancelAnimationFrame(this.frameId); } this.animationPhase = 0; @@ -1441,22 +1389,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.routeToAnimate = undefined; this.animationUtility = null; } - if (this.preAnimationViewState){ + if (this.preAnimationViewState) { this.mapboxMapViewState = this.preAnimationViewState; } - - - } + }; @action - exportAnimationToVideo = () => { - - } + exportAnimationToVideo = () => {}; getRouteAnimationOptions = (): JSX.Element => { return ( <> - <IconButton + <IconButton tooltip={this.isAnimating && this.finishedFlyTo ? 'Pause Animation' : 'Play Animation'} onPointerDown={() => { if (this.isAnimating && this.finishedFlyTo) { @@ -1467,87 +1411,57 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.playAnimation(AnimationStatus.START); // Play from the beginning } }} - icon={this.isAnimating && this.finishedFlyTo ? - <FontAwesomeIcon icon={faPause as IconLookup}/> - : - <FontAwesomeIcon icon={faPlay as IconLookup}/> - } - color='black' - size={Size.MEDIUM} - /> - {this.isAnimating && this.finishedFlyTo && - <IconButton - tooltip='Restart animation' - onPointerDown={() => { - this.stopAnimation(false); - this.playAnimation(AnimationStatus.START) - }} - icon={<FontAwesomeIcon icon={faRotate as IconLookup}/>} - color='black' - size={Size.MEDIUM} - /> - - } - <IconButton - style={{marginRight: '10px'}} - tooltip='Stop and close animation' - onPointerDown={() => this.stopAnimation(true)} - icon={<FontAwesomeIcon icon={faCircleXmark as IconLookup}/>} - color='black' + icon={this.isAnimating && this.finishedFlyTo ? <FontAwesomeIcon icon={faPause as IconLookup} /> : <FontAwesomeIcon icon={faPlay as IconLookup} />} + color="black" size={Size.MEDIUM} /> + {this.isAnimating && this.finishedFlyTo && ( + <IconButton + tooltip="Restart animation" + onPointerDown={() => { + this.stopAnimation(false); + this.playAnimation(AnimationStatus.START); + }} + icon={<FontAwesomeIcon icon={faRotate as IconLookup} />} + color="black" + size={Size.MEDIUM} + /> + )} + <IconButton style={{ marginRight: '10px' }} tooltip="Stop and close animation" onPointerDown={() => this.stopAnimation(true)} icon={<FontAwesomeIcon icon={faCircleXmark as IconLookup} />} color="black" size={Size.MEDIUM} /> <> - <div className='animation-suboptions'> + <div className="animation-suboptions"> <div>|</div> <FormControlLabel - className='first-person-label' - style={{width: '130px'}} - label='1st person animation:' - labelPlacement='start' - control={ - <Checkbox - color='success' - checked={this.isStreetViewAnimation} - onChange={this.toggleIsStreetViewAnimation} - /> - } - /> - <div id='divider'>|</div> - <IconButton - tooltip={this.animationSpeedTooltipText} - onPointerDown={this.updateAnimationSpeed} - icon={this.animationSpeedIcon} - size={Size.MEDIUM} + className="first-person-label" + style={{ width: '130px' }} + label="1st person animation:" + labelPlacement="start" + control={<Checkbox color="success" checked={this.isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />} /> - <div id='divider'>|</div> - <div style={{width: '230px'}}>Select Line Color: </div> - <CirclePicker - circleSize={12} - circleSpacing={5} - width='100%' - colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} - onChange={(color) => this.setAnimationLineColor(color)} - /> + <div id="divider">|</div> + <IconButton tooltip={this.animationSpeedTooltipText} onPointerDown={this.updateAnimationSpeed} icon={this.animationSpeedIcon} size={Size.MEDIUM} /> + <div id="divider">|</div> + <div style={{ width: '230px' }}>Select Line Color: </div> + <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={color => this.setAnimationLineColor(color)} /> </div> - </> + </> </> - ) - } + ); + }; @action hideRoute = () => { this.temporaryRouteSource = { type: 'FeatureCollection', - features: [] - } - } - + features: [], + }; + }; - @observable + @observable settingsOpen: boolean = false; - @observable - mapStyle: string = 'mapbox://styles/mapbox/streets-v12' + @observable + mapStyle: string = 'mapbox://styles/mapbox/streets-v12'; @observable showTerrain: boolean = true; @@ -1558,103 +1472,94 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.featuresFromGeocodeResults = []; this.settingsOpen = !this.settingsOpen; } - } + }; @action changeMapStyle = (e: React.ChangeEvent<HTMLSelectElement>) => { this.dataDoc.map_style = `mapbox://styles/mapbox/${e.target.value}`; // this.mapStyle = `mapbox://styles/mapbox/${e.target.value}` - } + }; - @action + @action onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => { const bearing = parseInt(e.target.value); - if (!isNaN(bearing) && this._mapRef.current){ + if (!isNaN(bearing) && this._mapRef.current) { const fixedBearing = Math.max(0, Math.min(360, bearing)); this._mapRef.current.setBearing(fixedBearing); this.dataDoc.map_bearing = fixedBearing; this.mapboxMapViewState = { ...this.mapboxMapViewState, - bearing: fixedBearing - } + bearing: fixedBearing, + }; } - } + }; - @action + @action onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => { const pitch = parseInt(e.target.value); - if (!isNaN(pitch) && this._mapRef.current){ + if (!isNaN(pitch) && this._mapRef.current) { const fixedPitch = Math.max(0, Math.min(85, pitch)); this._mapRef.current.setPitch(fixedPitch); this.dataDoc.map_pitch = fixedPitch; this.mapboxMapViewState = { ...this.mapboxMapViewState, - pitch: fixedPitch - } + pitch: fixedPitch, + }; } - } + }; - @action + @action onZoomChange = (e: React.ChangeEvent<HTMLInputElement>) => { const zoom = parseInt(e.target.value); - if (!isNaN(zoom) && this._mapRef.current){ + if (!isNaN(zoom) && this._mapRef.current) { const fixedZoom = Math.max(0, Math.min(16, zoom)); this._mapRef.current.setZoom(fixedZoom); this.dataDoc.map_zoom = fixedZoom; this.mapboxMapViewState = { ...this.mapboxMapViewState, - zoom: fixedZoom - } + zoom: fixedZoom, + }; } - } + }; @action onStepZoomChange = (increment: boolean) => { if (this._mapRef.current) { let newZoom: number; if (increment) { - console.log('inc') + console.log('inc'); newZoom = this.mapboxMapViewState.zoom + 1; - } else { - console.log('dec') + console.log('dec'); newZoom = this.mapboxMapViewState.zoom - 1; } this._mapRef.current.setZoom(newZoom); this.dataDoc.map_zoom = newZoom; this.mapboxMapViewState = { ...this.mapboxMapViewState, - zoom: increment ? Math.min(16, newZoom) : Math.max(0, newZoom) - } + zoom: increment ? Math.min(16, newZoom) : Math.max(0, newZoom), + }; } - - } - + }; @action onMapMove = (e: ViewStateChangeEvent) => { this.mapboxMapViewState = e.viewState; this.dataDoc.longitude = e.viewState.longitude; this.dataDoc.latitude = e.viewState.latitude; - } + }; @action toggleShowTerrain = () => { this.showTerrain = !this.showTerrain; - } + }; getMarkerIcon = (pinDoc: Doc): JSX.Element | null => { const markerType = StrCast(pinDoc.markerType); const markerColor = StrCast(pinDoc.markerColor); return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor) ?? null; - - } - - - - - + }; static _firstRender = true; static _rerenderDelay = 500; @@ -1675,7 +1580,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }), MapBox._rerenderDelay); return null; } - const scale = this.props.NativeDimScaling?.() || 1; const renderAnnotations = (childFilters?: () => string[]) => null; @@ -1688,107 +1592,76 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps e.button === 0 && !e.ctrlKey && e.stopPropagation(); }} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}> + {/* style={{ transformOrigin: "top left", transform: `scale(${scale})`, width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}> */} <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div> {renderAnnotations(this.opaqueFilter)} {SnappingManager.GetIsDragging() ? null : renderAnnotations()} - {!this.routeToAnimate && + {!this.routeToAnimate && ( <div className="mapBox-searchbar" style={{ zIndex: 1, position: 'relative', background: 'lightGray' }}> - <TextField - fullWidth - placeholder='Enter a location' - onChange={(e) => this.handleSearchChange(e.target.value)} - /> - <IconButton - icon={<FontAwesomeIcon icon={faGear as IconLookup} size='1x'/>} - type={Type.TERT} - onClick={(e) => this.toggleSettings()} - - /> - </div> - } - {this.settingsOpen && !this.routeToAnimate && - <div className='mapbox-settings-panel' style={{right: `${0+ this.sidebarWidth()}px`}}> - <div className='mapbox-style-select'> - <div> - Map Style: - </div> + <TextField fullWidth placeholder="Enter a location" onChange={e => this.handleSearchChange(e.target.value)} /> + <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} /> + </div> + )} + {this.settingsOpen && !this.routeToAnimate && ( + <div className="mapbox-settings-panel" style={{ right: `${0 + this.sidebarWidth()}px` }}> + <div className="mapbox-style-select"> + <div>Map Style:</div> <div> <select onChange={this.changeMapStyle}> - <option value='streets-v11'>Streets</option> - <option value='outdoors-v12'>Outdoors</option> - <option value='light-v11'>Light</option> - <option value='dark-v11'>Dark</option> - <option value='satellite-v9'>Satellite</option> - <option value='satellite-streets-v12'>Satellite Streets</option> - <option value='navigation-day-v1'>Navigation Day</option> - <option value='navigation-night-v1'>Navigation Night</option> + <option value="streets-v11">Streets</option> + <option value="outdoors-v12">Outdoors</option> + <option value="light-v11">Light</option> + <option value="dark-v11">Dark</option> + <option value="satellite-v9">Satellite</option> + <option value="satellite-streets-v12">Satellite Streets</option> + <option value="navigation-day-v1">Navigation Day</option> + <option value="navigation-night-v1">Navigation Night</option> </select> </div> </div> - <div className='mapbox-bearing-selection'> + <div className="mapbox-bearing-selection"> <div>Bearing: </div> - <input - value={NumCast(this.mapboxMapViewState.bearing).toFixed(2)} - type='number' - onChange={this.onBearingChange}/> + <input value={NumCast(this.mapboxMapViewState.bearing).toFixed(2)} type="number" onChange={this.onBearingChange} /> </div> - <div className='mapbox-pitch-selection'> + <div className="mapbox-pitch-selection"> <div>Pitch: </div> - <input - value={NumCast(this.mapboxMapViewState.pitch).toFixed(2)} - type='number' - onChange={this.onPitchChange}/> + <input value={NumCast(this.mapboxMapViewState.pitch).toFixed(2)} type="number" onChange={this.onPitchChange} /> </div> - <div className='mapbox-pitch-selection'> + <div className="mapbox-pitch-selection"> <div>Zoom: </div> - <input - value={NumCast(this.mapboxMapViewState.zoom).toFixed(2)} - type='number' - onChange={this.onZoomChange}/> + <input value={NumCast(this.mapboxMapViewState.zoom).toFixed(2)} type="number" onChange={this.onZoomChange} /> </div> - <div className='mapbox-terrain-selection'> + <div className="mapbox-terrain-selection"> <div>Show terrain: </div> - <input - type='checkbox' - checked={this.showTerrain} - onChange={this.toggleShowTerrain} - /> + <input type="checkbox" checked={this.showTerrain} onChange={this.toggleShowTerrain} /> </div> </div> - } - {this.routeToAnimate && - <div className='animation-panel'> - <div id='route-to-animate-title'> - {StrCast(this.routeToAnimate.title)} - </div> - <div className='route-animation-options'> - {this.getRouteAnimationOptions()} - </div> + )} + {this.routeToAnimate && ( + <div className="animation-panel" style={{ width: this.sidebarWidth() === 0 ? '100%' : `calc(100% - ${this.sidebarWidth()}px)` }}> + <div id="route-to-animate-title">{StrCast(this.routeToAnimate.title)}</div> + <div className="route-animation-options">{this.getRouteAnimationOptions()}</div> </div> - } + )} {this.featuresFromGeocodeResults.length > 0 && ( - <div className='mapbox-geocoding-search-results'> + <div className="mapbox-geocoding-search-results"> <React.Fragment> <h4>Choose a location for your pin: </h4> {this.featuresFromGeocodeResults .filter(feature => feature.place_name) .map((feature, idx) => ( - <div - key={idx} - className='search-result-container' - onClick={() => { - this.handleSearchChange(""); - this.addMarkerForFeature(feature); - }} - > - <div className='search-result-place-name'> - {feature.place_name} + <div + key={idx} + className="search-result-container" + onClick={() => { + this.handleSearchChange(''); + this.addMarkerForFeature(feature); + }}> + <div className="search-result-place-name">{feature.place_name}</div> </div> - </div> - ))} + ))} </React.Fragment> - - </div> + </div> )} {/* <div className='zoom-box'> <IconButton // increment @@ -1812,7 +1685,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps id="mapbox-map" viewState={{ ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width) / scale, height: NumCast(this.layoutDoc._height) / scale }} mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'} - style={{ + style={{ transformOrigin: 'center', transform: `scale(${scale < 1 ? 1 : scale})`, zIndex: '0', @@ -1823,106 +1696,88 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps onMove={this.onMapMove} onClick={this.handleMapClick} onDblClick={this.handleMapDblClick} - terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined} - - - > - <Source - id="mapbox-dem" - type="raster-dem" - url="mapbox://mapbox.mapbox-terrain-dem-v1" - tileSize={512} - maxzoom={14} - /> - <Source id='temporary-route' type='geojson' data={this.temporaryRouteSource}/> - <Source id='map-routes' type='geojson' data={this.allRoutesGeoJson}/> - <Layer - id='temporary-route-layer' - type='line' - source='temporary-route' - layout={{"line-join": "round", "line-cap": "round"}} - paint={{"line-color": "#36454F", "line-width": 4, "line-dasharray": [1,1]}} - /> - {!this.isAnimating && this.animationPhase == 0 && - <Layer - id='map-routes-layer' - type='line' - source='map-routes' - layout={{"line-join": "round", "line-cap": "round"}} - paint={{"line-color": "#FF0000", "line-width": 4}} - /> - } - {this.routeToAnimate && (this.isAnimating || this.animationPhase > 0) && + terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}> + <Source id="mapbox-dem" type="raster-dem" url="mapbox://mapbox.mapbox-terrain-dem-v1" tileSize={512} maxzoom={14} /> + <Source id="temporary-route" type="geojson" data={this.temporaryRouteSource} /> + <Source id="map-routes" type="geojson" data={this.allRoutesGeoJson} /> + <Layer id="temporary-route-layer" type="line" source="temporary-route" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#36454F', 'line-width': 4, 'line-dasharray': [1, 1] }} /> + {!this.isAnimating && this.animationPhase == 0 && <Layer id="map-routes-layer" type="line" source="map-routes" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#FF0000', 'line-width': 4 }} />} + {this.routeToAnimate && (this.isAnimating || this.animationPhase > 0) && ( <> - {!this.isStreetViewAnimation && + {!this.isStreetViewAnimation && ( <> - <Source id='animated-route' type='geojson' data={this.updatedRouteCoordinates}/> - <Layer - id='dynamic-animation-line' - type='line' - source='animated-route' - paint={{ - 'line-color': this.animationLineColor, - 'line-width': 5, - }} + <Source id="animated-route" type="geojson" data={this.updatedRouteCoordinates} /> + <Layer + id="dynamic-animation-line" + type="line" + source="animated-route" + paint={{ + 'line-color': this.animationLineColor, + 'line-width': 5, + }} /> </> - } - <Source id='start-pin-base' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.04)}/> - <Source id='start-pin-top' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.25)}/> - <Source id='end-pin-base' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.04)}/> - <Source id='end-pin-top' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.25)}/> - <Layer id='start-fill-pin-base' type='fill-extrusion' source='start-pin-base' + )} + <Source id="start-pin-base" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.04)} /> + <Source id="start-pin-top" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.25)} /> + <Source id="end-pin-base" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.04)} /> + <Source id="end-pin-top" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.25)} /> + <Layer + id="start-fill-pin-base" + type="fill-extrusion" + source="start-pin-base" paint={{ 'fill-extrusion-color': '#0bfc03', - 'fill-extrusion-height': 1000 + 'fill-extrusion-height': 1000, }} /> - <Layer id='start-fill-pin-top' type='fill-extrusion' source='start-pin-top' + <Layer + id="start-fill-pin-top" + type="fill-extrusion" + source="start-pin-top" paint={{ 'fill-extrusion-color': '#0bfc03', 'fill-extrusion-base': 1000, - 'fill-extrusion-height': 1200 + 'fill-extrusion-height': 1200, }} /> - <Layer id='end-fill-pin-base' type='fill-extrusion' source='end-pin-base' + <Layer + id="end-fill-pin-base" + type="fill-extrusion" + source="end-pin-base" paint={{ 'fill-extrusion-color': '#eb1c1c', - 'fill-extrusion-height': 1000 + 'fill-extrusion-height': 1000, }} /> - <Layer id='end-fill-pin-top' type='fill-extrusion' source='end-pin-top' + <Layer + id="end-fill-pin-top" + type="fill-extrusion" + source="end-pin-top" paint={{ 'fill-extrusion-color': '#eb1c1c', 'fill-extrusion-base': 1000, - 'fill-extrusion-height': 1200 + 'fill-extrusion-height': 1200, }} /> - </> - } - + )} <> - {!this.isAnimating && this.animationPhase == 0 && this.allPushpins - // .filter(anno => !anno.layout_unrendered) - .map((pushpin, idx) => ( - <Marker - key={idx} - longitude={NumCast(pushpin.longitude)} - latitude={NumCast(pushpin.latitude)} - anchor='bottom' - onClick={(e: MarkerEvent<mapboxgl.Marker, MouseEvent>) => this.handleMarkerClick(e, pushpin)} - > - {this.getMarkerIcon(pushpin)} - </Marker> - ))} + {!this.isAnimating && + this.animationPhase == 0 && + this.allPushpins + // .filter(anno => !anno.layout_unrendered) + .map((pushpin, idx) => ( + <Marker key={idx} longitude={NumCast(pushpin.longitude)} latitude={NumCast(pushpin.latitude)} anchor="bottom" onClick={(e: MarkerEvent<mapboxgl.Marker, MouseEvent>) => this.handleMarkerClick(e, pushpin)}> + {this.getMarkerIcon(pushpin)} + </Marker> + ))} </> - + {/* {this.mapMarkers.length > 0 && this.mapMarkers.map((marker, idx) => ( <Marker key={idx} longitude={marker.longitude} latitude={marker.latitude}/> ))} */} - </MapboxMap> </MapProvider> @@ -2002,7 +1857,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } } -{/* <Autocomplete +{ + /* <Autocomplete fullWidth id="map-location-searcher" freeSolo @@ -2021,8 +1877,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps placeholder='Enter a location' /> )} - /> */} - {/* <EditableText + /> */ +} +{ + /* <EditableText // editing setVal={(newText: string | number) => typeof newText === 'string' && this.handleSearchChange(newText)} // onEnter={e => this.bingSearch()} @@ -2031,8 +1889,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // placeholder={this.bingSearchBarContents || 'Enter a location'} placeholder='Enter a location' textAlign="center" - /> */} - {/* <IconButton + /> */ +} +{ + /* <IconButton icon={ <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF"> <path @@ -2045,4 +1905,5 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps /> <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}> <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} /> - </div> */} + </div> */ +} diff --git a/src/client/views/nodes/MapBox/MapboxApiUtility.ts b/src/client/views/nodes/MapBox/MapboxApiUtility.ts index 011b6f72a..592330ac2 100644 --- a/src/client/views/nodes/MapBox/MapboxApiUtility.ts +++ b/src/client/views/nodes/MapBox/MapboxApiUtility.ts @@ -34,48 +34,39 @@ export class MapboxApiUtility { static getDirections = async (origin: number[], destination: number[]): Promise<Record<TransportationType, any> | undefined> => { try { - const drivingQuery = await fetch( - `${MAPBOX_DIRECTIONS_BASE_URL}/driving/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`); - const cyclingQuery = await fetch( - `${MAPBOX_DIRECTIONS_BASE_URL}/cycling/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`); + const directionsPromises: Promise<any>[] = []; + const transportationTypes: TransportationType[] = ['driving', 'cycling', 'walking']; - const walkingQuery = await fetch( - `${MAPBOX_DIRECTIONS_BASE_URL}/walking/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`); + transportationTypes.forEach((type) => { + directionsPromises.push( + fetch( + `${MAPBOX_DIRECTIONS_BASE_URL}/${type}/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}` + ).then((response) => response.json()) + ); + }); - const drivingJson = await drivingQuery.json(); - const cyclingJson = await cyclingQuery.json(); - const walkingJson = await walkingQuery.json(); - - console.log("Driving: ", drivingJson); - console.log("Cycling: ", cyclingJson); - console.log("Waling: ", walkingJson); - - const routeMap = { - 'driving': drivingJson.routes[0], - 'cycling': cyclingJson.routes[0], - 'walking': walkingJson.routes[0] - } + const results = await Promise.all(directionsPromises); const routeInfoMap: Record<TransportationType, any> = { 'driving': {}, 'cycling': {}, 'walking': {}, - }; - - Object.entries(routeMap).forEach(([key, routeData]) => { - const transportationTypeKey = key as TransportationType; - const geometry = routeData.geometry; - const coordinates = geometry.coordinates; - - console.log(coordinates); - - routeInfoMap[transportationTypeKey] = { + }; + + transportationTypes.forEach((type, index) => { + const routeData = results[index].routes[0]; + if (routeData) { + const geometry = routeData.geometry; + const coordinates = geometry.coordinates; + + routeInfoMap[type] = { duration: this.secondsToMinutesHours(routeData.duration), distance: this.metersToMiles(routeData.distance), - coordinates: coordinates + coordinates: coordinates, + }; } - }) + }); return routeInfoMap; @@ -102,4 +93,47 @@ export class MapboxApiUtility { return `${parseFloat((meters/1609.34).toFixed(2))} mi`; } -}
\ No newline at end of file +} + +// const drivingQuery = await fetch( +// `${MAPBOX_DIRECTIONS_BASE_URL}/driving/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`); + +// const cyclingQuery = await fetch( +// `${MAPBOX_DIRECTIONS_BASE_URL}/cycling/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`); + +// const walkingQuery = await fetch( +// `${MAPBOX_DIRECTIONS_BASE_URL}/walking/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`); + +// const drivingJson = await drivingQuery.json(); +// const cyclingJson = await cyclingQuery.json(); +// const walkingJson = await walkingQuery.json(); + +// console.log("Driving: ", drivingJson); +// console.log("Cycling: ", cyclingJson); +// console.log("Waling: ", walkingJson); + +// const routeMap = { +// 'driving': drivingJson.routes[0], +// 'cycling': cyclingJson.routes[0], +// 'walking': walkingJson.routes[0] +// } + +// const routeInfoMap: Record<TransportationType, any> = { +// 'driving': {}, +// 'cycling': {}, +// 'walking': {}, +// }; + +// Object.entries(routeMap).forEach(([key, routeData]) => { +// const transportationTypeKey = key as TransportationType; +// const geometry = routeData.geometry; +// const coordinates = geometry.coordinates; + +// console.log(coordinates); + +// routeInfoMap[transportationTypeKey] = { +// duration: this.secondsToMinutesHours(routeData.duration), +// distance: this.metersToMiles(routeData.distance), +// coordinates: coordinates +// } +// })
\ No newline at end of file |