diff --git a/Dockerfile b/Dockerfile index 3ef213587d0dd9a7c3f63877f7eb5450c96b889e..88b5046798182ce34716adc7cd5cedc75075984c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,5 +10,6 @@ RUN npm run build # production environment FROM nginx:1.16.0-alpine COPY --from=build /usr/src/app/build /usr/share/nginx/html +COPY default.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] diff --git a/default.conf b/default.conf new file mode 100644 index 0000000000000000000000000000000000000000..6f9b9322e0b2a4e07d2887abaf8ff13f4ae16c8c --- /dev/null +++ b/default.conf @@ -0,0 +1,10 @@ +server { + listen 80; + server_name localhost; + location / { + try_files $uri /index.html; + root /usr/share/nginx/html; + index index.html index.htm; + } + error_page 500 502 503 504 /50x.html; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a5d09a513ce24f8fbb0dba8bc7923b341a5fc223..6463ada39fd86f4a088ae8726c205a054b7c90cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1624,6 +1624,12 @@ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==" }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -1740,6 +1746,16 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -2756,6 +2772,17 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000971.tgz", "integrity": "sha512-TQFYFhRS0O5rdsmSbF1Wn+16latXYsQJat66f7S7lizXW1PVpWJeZw9wqqVLIjuxDRz7s7xRUj13QCfd8hKn6g==" }, + "canvas": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.0.tgz", + "integrity": "sha512-bEO9f1ThmbknLPxCa8Es7obPlN9W3stB1bo7njlhOFKIdUTldeTqXCh9YclCPAi2pSQs84XA0jq/QEZXSzgyMw==", + "optional": true, + "requires": { + "nan": "^2.14.0", + "node-pre-gyp": "^0.11.0", + "simple-get": "^3.0.3" + } + }, "capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", @@ -3573,6 +3600,12 @@ "date-now": "^0.1.4" } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "optional": true + }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -4048,11 +4081,26 @@ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "optional": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "optional": true + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -4156,6 +4204,12 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "optional": true + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -4175,6 +4229,12 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "optional": true + }, "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", @@ -5292,6 +5352,82 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, + "fabric": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fabric/-/fabric-3.3.2.tgz", + "integrity": "sha512-xUbSmv3KzmmXgSTqHZbg33YMiIm3ZGlWvDgqEqdTBMSrrQd8V9t596ra7lIgr8rNh3HhxBDu7rZJ+R0t6FNFAg==", + "requires": { + "canvas": "^2.6.0", + "jsdom": "^15.1.0" + }, + "dependencies": { + "jsdom": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.1.1.tgz", + "integrity": "sha512-cQZRBB33arrDAeCrAEWn1U3SvrvC8XysBua9Oqg1yWrsY/gYcusloJC3RZJXuY5eehSCmws8f2YeliCqGSkrtQ==", + "optional": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^6.1.1", + "acorn-globals": "^4.3.2", + "array-equal": "^1.0.0", + "cssom": "^0.3.6", + "cssstyle": "^1.2.2", + "data-urls": "^1.1.0", + "domexception": "^1.0.1", + "escodegen": "^1.11.1", + "html-encoding-sniffer": "^1.0.2", + "nwsapi": "^2.1.4", + "parse5": "5.1.0", + "pn": "^1.1.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.7", + "saxes": "^3.1.9", + "symbol-tree": "^3.2.2", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.1", + "w3c-xmlserializer": "^1.1.2", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^7.0.0", + "ws": "^7.0.0", + "xml-name-validator": "^3.0.0" + } + }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "optional": true, + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "optional": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "ws": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.0.tgz", + "integrity": "sha512-Swie2C4fs7CkwlHu1glMePLYJJsWjzhl1vm3ZaLplD0h7OMkZyZ6kLTB/OagiU923bZrPFXuDTeEqaEN4NWG4g==", + "optional": true, + "requires": { + "async-limiter": "^1.0.0" + } + } + } + }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", @@ -5573,6 +5709,15 @@ "universalify": "^0.1.0" } }, + "fs-minipass": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", + "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, "fs-write-stream-atomic": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", @@ -5605,6 +5750,59 @@ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", @@ -5848,6 +6046,12 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "optional": true + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -6174,6 +6378,15 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, "immer": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", @@ -8275,6 +8488,12 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "optional": true + }, "mini-create-react-context": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz", @@ -8318,6 +8537,25 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -8458,6 +8696,28 @@ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" }, + "needle": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", + "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "optional": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "optional": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -8552,6 +8812,32 @@ } } }, + "node-pre-gyp": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "optional": true + } + } + }, "node-releases": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.21.tgz", @@ -8567,6 +8853,16 @@ } } }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -8603,6 +8899,22 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" }, + "npm-bundled": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", + "optional": true + }, + "npm-packlist": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.4.tgz", + "integrity": "sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==", + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -8611,6 +8923,18 @@ "path-key": "^2.0.0" } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", @@ -8835,6 +9159,12 @@ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "optional": true + }, "os-locale": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", @@ -8850,6 +9180,16 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -10257,6 +10597,18 @@ } } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, "react": { "version": "16.8.6", "resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz", @@ -11299,6 +11651,23 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, + "simple-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=", + "optional": true + }, + "simple-get": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.0.3.tgz", + "integrity": "sha512-Wvre/Jq5vgoz31Z9stYWPLn0PqRqmBDpFSdypAnHu5AvRVCYPRYGnvryNLiXu8GOBNDH82J2FRHUGMjjHUpXFw==", + "optional": true, + "requires": { + "decompress-response": "^3.3.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -11973,6 +12342,21 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" }, + "tar": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, "terser": { "version": "3.17.0", "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", @@ -12895,6 +13279,15 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/package.json b/package.json index 91b670f85cb70e95340396820573962e7fb8e5fe..1dc26e15271ed4fb049478589a5b9e8bbf7a4a01 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "react-leaflet-draw": "0.19.0", "react-router-dom": "^5.0.1", "react-scripts": "3.0.1", - "universal-cookie": "^4.0.0", "socket.io": "^2.2.0", "socket.io-client": "^2.2.0" }, diff --git a/public/infantry.svg b/public/infantry.svg new file mode 100644 index 0000000000000000000000000000000000000000..ac9e8bcfed3b9100dcf635fd161aa2cb5a0e612a --- /dev/null +++ b/public/infantry.svg @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="591.0625" + height="372.0625" + id="svg2"> + <defs + id="defs4"> + <linearGradient + id="linearGradient3777"> + <stop + id="stop3779" + style="stop-color:#b35800;stop-opacity:1" + offset="0" /> + </linearGradient> + </defs> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + transform="translate(-72.28125,-210.75)" + id="layer1"> + <path + d="M 585.90976,366.37406 4.2663332,3.7357361" + transform="translate(72.28125,210.75)" + id="path3881" + style="fill:none;stroke:#000000;stroke-width:24;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + <path + d="M 584.7946,6.0174311 5.6411055,365.7946" + transform="translate(72.28125,210.75)" + id="path3883" + style="fill:none;stroke:#000000;stroke-width:24;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + <rect + width="583.06555" + height="364.06042" + x="76.266335" + y="214.73877" + id="rect3859" + style="fill:none;stroke:#ff0000;stroke-width:24;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + </g> +</svg> diff --git a/public/light-infantry.svg b/public/light-infantry.svg new file mode 100644 index 0000000000000000000000000000000000000000..4e7d1c7347c4c0b6aa04d0250fdec374a9252ba7 --- /dev/null +++ b/public/light-infantry.svg @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="591.0625" height="372.0625" id="svg2"> + <defs id="defs4"> + <linearGradient id="linearGradient3777"> + <stop id="stop3779" style="stop-color:#b35800;stop-opacity:1" offset="0"/> + </linearGradient> + </defs> + <metadata id="metadata7"> + <rdf:RDF> + <cc:Work rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> + <dc:title/> + </cc:Work> + </rdf:RDF> + </metadata> + <g transform="translate(-72.28125,-210.75)" id="layer1"> + <path d="M 585.90976,366.37406 4.2663332,3.7357361" transform="translate(72.28125,210.75)" id="path3881" style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/> + <path d="M 584.7946,6.0174311 5.6411055,365.7946" transform="translate(72.28125,210.75)" id="path3883" style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/> + <rect width="583.06555" height="364.06042" x="76.266335" y="214.73877" id="rect3859" style="fill:none;stroke:#0b04fb;stroke-width:8;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/> + </g> + <text x="293.67578" y="344.20807" id="text2988" xml:space="preserve" style="font-size:50px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan x="293.67578" y="344.20807" id="tspan2990" style="font-size:100px">L</tspan></text> +</svg> \ No newline at end of file diff --git a/public/mechanized.svg b/public/mechanized.svg new file mode 100644 index 0000000000000000000000000000000000000000..b3857efd4debbee06f8e812ba6e3db13c359b3fa --- /dev/null +++ b/public/mechanized.svg @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="591.0625" + height="372.0625" + id="svg2" + version="1.1" + inkscape:version="0.48.4 r9939" + sodipodi:docname="NATO Map Symbol - Infantry.svg"> + <defs + id="defs4"> + <linearGradient + id="linearGradient3777" + osb:paint="solid"> + <stop + style="stop-color:#b35800;stop-opacity:1;" + offset="0" + id="stop3779" /> + </linearGradient> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.595432" + inkscape:cx="295.53125" + inkscape:cy="186.03125" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="1401" + inkscape:window-height="960" + inkscape:window-x="22" + inkscape:window-y="22" + inkscape:window-maximized="0" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-72.28125,-210.75)"> + <path + sodipodi:type="arc" + style="fill:none;stroke:#000000;stroke-width:12;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + id="path3879" + sodipodi:cx="294.37698" + sodipodi:cy="184.34384" + sodipodi:rx="160.69855" + sodipodi:ry="76.793999" + d="m 455.07553,184.34384 a 160.69855,76.793999 0 1 1 -321.39709,0 160.69855,76.793999 0 1 1 321.39709,0 z" + transform="matrix(1.4300046,0,0,1.5757504,-53.16133,106.2891)" /> + <path + style="fill:none;stroke:#000000;stroke-width:24;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none" + d="M 585.90976,366.37406 4.2663332,3.7357361" + id="path3881" + inkscape:connector-curvature="0" + transform="translate(72.28125,210.75)" /> + <path + style="fill:none;stroke:#000000;stroke-width:24;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none" + d="M 584.7946,6.0174311 5.6411055,365.7946" + id="path3883" + inkscape:connector-curvature="0" + transform="translate(72.28125,210.75)" /> + <rect + style="fill:none;stroke:#ff0000;stroke-width:24;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + id="rect3859" + width="583.06555" + height="364.06042" + x="76.266335" + y="214.73877" /> + </g> +</svg> diff --git a/public/recon.svg b/public/recon.svg new file mode 100644 index 0000000000000000000000000000000000000000..4e89e6116e69adc6ec87efe92c20630cb8d907f9 --- /dev/null +++ b/public/recon.svg @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + width="591.0625" + height="372.0625" + id="svg2" + inkscape:version="0.48.4 r9939" + sodipodi:docname="NATO Map Symbol - Infantry.svg"> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="640" + inkscape:window-height="480" + id="namedview11" + showgrid="false" + inkscape:zoom="0.53801417" + inkscape:cx="295.53125" + inkscape:cy="186.03125" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <defs + id="defs4"> + <linearGradient + id="linearGradient3777"> + <stop + id="stop3779" + style="stop-color:#b35800;stop-opacity:1" + offset="0" /> + </linearGradient> + </defs> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <path + inkscape:connector-curvature="0" + style="fill:none;stroke:#000000;stroke-width:24;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + id="path3883" + d="M 584.7946,6.0174311 5.6411055,365.7946" /> + <rect + style="fill:none;stroke:#ff0000;stroke-width:24;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + id="rect3859" + y="3.9887695" + x="3.9850845" + height="364.06042" + width="583.06555" /> +</svg> diff --git a/src/App.css b/src/App.css index 4b1a814b09da13bed06a89bfedef6cfa3f883355..5896a306cc8fa6929f964ef665b62003a1c1266b 100644 --- a/src/App.css +++ b/src/App.css @@ -212,8 +212,12 @@ div.login button:hover { } .gamelist { + border: 2px solid white; + max-width: 800px; +} + +.gamelist-item { background-color: #1d1d1b; - max-width: 500px; padding: 10px; } @@ -247,3 +251,50 @@ div.login button:hover { .notification-popup.alert { background-color: red; } + +.leaflet-control-playback { + position: relative; + background-color: #7cbdf5; + padding: 10px; +} +.leaflet-control-playback .optionsContainer { + position: relative; +} +.leaflet-control-playback .optionsContainer > div { + display: inline-block; +} +.leaflet-control-playback .buttonContainer { +} +.leaflet-control-playback .buttonContainer a { + display: inline-block; + width: 32px; + height: 32px; + text-decoration: none; +} +.leaflet-control-playback .buttonContainer .btn-stop { + background: url(icons/icon-play.png) no-repeat center; +} +.leaflet-control-playback .buttonContainer .btn-start { + background: url(icons/icon-stop.png) no-repeat center; +} +.leaflet-control-playback .buttonContainer .btn-restart { + background: url(icons/icon-restart.png) no-repeat center; +} +.leaflet-control-playback .buttonContainer .btn-slow { + background: url(icons/icon-slow.png) no-repeat center; +} +.leaflet-control-playback .buttonContainer .btn-quick { + background: url(icons/icon-quick.png) no-repeat center; +} +.leaflet-control-playback .buttonContainer .btn-close { + background: url(icons/icon-close.png) no-repeat center; +} +.leaflet-control-playback .infoContainer { +} +.leaflet-control-playback .sliderContainer { +} + +.leaflet-tooltip { + background-color: rgba(0, 0, 0, 0.5); + color: white; +} diff --git a/src/App.js b/src/App.js index 3b57bad75dba81a4592757b60669e01685834f28..b1752f70d4e09e5afc57d112005bc247f8d70d8e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,8 @@ import React, { Component } from "react"; import "../node_modules/leaflet-draw/dist/leaflet.draw.css"; import "./App.css"; + +import ClientSocket from "./components/Socket"; import { BrowserRouter as Router, Route, @@ -11,6 +13,7 @@ import LoginForm from "./components/LoginForm"; import RegisterForm from "./components/RegisterForm"; import GameSelection from "./components/GameSelection"; import GameView from "./components/GameView"; +import ReplayMap from "./components/ReplayMap"; export default class App extends Component { constructor() { @@ -124,6 +127,11 @@ export default class App extends Component { /> ); }; + + replay = () => { + return <ReplayMap />; + }; + render() { // TODO: think better solution to wait for authenticator if (!this.state.authenticateComplete) { @@ -159,9 +167,9 @@ export default class App extends Component { <Redirect from="*" to="/" /> </Switch> )} - {this.state.logged && ( <Switch> + <Route exact path="/replay" component={this.replay} /> <Route path="/game" component={() => { diff --git a/src/components/DrawGeoJSON.js b/src/components/DrawGeoJSON.js new file mode 100644 index 0000000000000000000000000000000000000000..fd7f4f3b464f6c3c4814217db17fe1056a3fcb25 --- /dev/null +++ b/src/components/DrawGeoJSON.js @@ -0,0 +1,134 @@ +import React from "react"; +import L from "leaflet"; +import "leaflet-draw"; +import { + Circle, + Marker, + Polygon, + Polyline, + Rectangle, + Tooltip +} from "react-leaflet"; + +// an empty icon for textboxes +let noIcon = L.divIcon({ + className: "", + iconSize: [20, 20], + iconAnchor: [10, 20] +}); + +class DrawGeoJSON extends React.Component { + constructor(props) { + super(props); + } + + render() { + return ( + <React.Fragment> + {/* iterate through every element fetched from back-end */} + {this.props.geoJSONLayer.features.map(feature => { + let id = feature.mapDrawingHistoryId; + let coords = feature.data.geometry.coordinates; + let type = feature.data.geometry.type; + let color = feature.data.properties.color; + let radius = feature.data.properties.radius; + let text = feature.data.properties.text; + let rectangle = feature.data.properties.rectangle; + if (type === "Point") { + // GeoJSON saves latitude first, not longitude like usual. swapping + let position = [coords[1], coords[0]]; + if (radius) { + return ( + // keys are required to be able to edit + <Circle + key={Math.random()} + center={position} + id={id} + radius={radius} + color={color} + /> + ); + } else if (text) { + return ( + <Marker + key={Math.random()} + position={position} + id={id} + color={color} + icon={noIcon} + > + <Tooltip + direction="bottom" + permanent + className="editable" + interactive={true} + > + <div class="editable"> + <div + contenteditable="true" + placeholder="Click out to save" + > + {text} + </div> + </div> + </Tooltip> + </Marker> + ); + } else { + // unknown if color changes anything. need to test + return ( + <Marker + key={Math.random()} + position={position} + id={id} + color={color} + /> + ); + } + } else if (rectangle) { + // instead of an array of four coordinates, rectangles only have two corners + let bounds = coords[0].map(coord => { + return [coord[1], coord[0]]; + }); + return ( + <Rectangle + key={Math.random()} + bounds={bounds} + id={id} + color={color} + /> + ); + } else if (type === "Polygon") { + // Polygon coordinates are wrapped under a one element array, for some reason + let positions = coords[0].map(coord => { + return [coord[1], coord[0]]; + }); + return ( + <Polygon + key={Math.random()} + positions={positions} + id={id} + color={color} + /> + ); + } else if (type === "LineString") { + // Polyline coordinates are a normal array, unlike Polygon + let positions = coords.map(coord => { + return [coord[1], coord[0]]; + }); + return ( + <Polyline + key={Math.random()} + positions={positions} + id={id} + color={color} + /> + ); + } + })} + </React.Fragment> + ); + } +} + +export default DrawGeoJSON; diff --git a/src/components/GameList.js b/src/components/GameList.js index fcb3091c15867045b985157192b864789dabc204..4796e8a1c3dfe575f9efded6b02c2017ece1e8eb 100644 --- a/src/components/GameList.js +++ b/src/components/GameList.js @@ -80,20 +80,21 @@ class GameList extends React.Component { }; render() { - let gamelistItems = this.props.games.map(game => { - return ( - <GameCard - key={game.id} - gameId={game.id} - onEditSave={this.props.onEditSave} - /> - ); - }); + let gamelistItems = this.props.games.map(game => ( + <GameCard + key={game.id} + gameId={game.id} + onEditSave={this.props.onEditSave} + /> + )); return ( - <Fragment> - <div className="gamelist">{gamelistItems}</div> - </Fragment> + <div + className="gamelist" + style={{ maxHeight: "500px", overflow: "scroll" }} + > + <div className="gamelist-item">{gamelistItems}</div> + </div> ); } } diff --git a/src/components/ReplayMap.js b/src/components/ReplayMap.js new file mode 100644 index 0000000000000000000000000000000000000000..f173ffd93b7bccb67fd61e031800ccb6ac857dbe --- /dev/null +++ b/src/components/ReplayMap.js @@ -0,0 +1,177 @@ +// https://github.com/linghuam/Leaflet.TrackPlayBack + +import React from "react"; +import L from "leaflet"; +import { Map, TileLayer, ZoomControl, Marker, Popup } from "react-leaflet"; +import "../track-playback/src/leaflet.trackplayback/clock"; +import "../track-playback/src/leaflet.trackplayback/index"; +import "../track-playback/src/control.trackplayback/control.playback"; +import "../track-playback/src/control.trackplayback/index"; +import DrawGeoJSON from "./DrawGeoJSON"; + +export default class ReplayMap extends React.Component { + constructor(props) { + super(props); + this.state = { + // stores the playback object + playback: null, + // stores player locations from backend + data: null, + // stores all drawings from backend + allGeoJSON: [], + // stores all active drawings on the map + activeGeoJSON: [] + }; + this.map = React.createRef(); + } + + async componentDidMount() { + await this.fetchPlayerData(); + //await this.fetchDrawingData(); + //this.tickDrawings(); + this.replay(); + } + + componentWillReceiveProps(nextProps) {} + + // cloud game a1231e2b-aa29-494d-b687-ea2d48cc23df + // local game wimma 314338f9-b0bb-4bf7-9554-769c7b409bce + // local game vbox 16977b13-c419-48b4-b7d6-e7620f27bf39 + // fetch player locations from the game + fetchPlayerData = async () => { + await fetch( + `${ + process.env.REACT_APP_API_URL + }/replay/players/314338f9-b0bb-4bf7-9554-769c7b409bce`, + { + method: "GET" + } + ) + .then(async res => await res.json()) + .then( + async res => { + await this.setState({ data: res }); + }, + // Note: it's important to handle errors here + // instead of a catch() block so that we don't swallow + // exceptions from actual bugs in components. + error => { + console.log(error); + } + ); + }; + + fetchDrawingData = async () => { + await fetch( + `${process.env.REACT_APP_API_URL}/replay/{ + "lng": 25.72588, + "lat": 62.23147 +}`, + { + method: "GET" + } + ) + .then(async res => await res.json()) + .then( + async res => { + await this.setState({ allGeoJSON: res }); + }, + error => { + console.log(error); + } + ); + }; + + tickDrawings = () => { + let activeDrawings = []; + this.state.allGeoJSON.map(drawing => { + activeDrawings.push(drawing[0]); + this.setState({ + activeGeoJSON: { + type: "FeatureCollection", + features: [...activeDrawings] + } + }); + }); + }; + + replay = () => { + this.map = L.map(this.refs.map).setView([62.3, 25.7], 15); + L.tileLayer("https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg", { + attribution: + '© <a href="https://www.maanmittauslaitos.fi/">Maanmittauslaitos</a>' + }).addTo(this.map); + this.trackplayback = new L.TrackPlayBack(this.state.data, this.map, { + trackPointOptions: { + // whether to draw track point + isDraw: true, + // whether to use canvas to draw it, if false, use leaflet api `L.circleMarker` + useCanvas: false, + stroke: true, + color: "#000000", + fill: true, + fillColor: "rgba(0,0,0,0)", + opacity: 0, + radius: 12 + }, + targetOptions: { + // whether to use an image to display target, if false, the program provides a default + useImg: true, + // if useImg is true, provide the imgUrl + imgUrl: "../light-infantry.svg", + // the width of target, unit: px + width: 60, + // the height of target, unit: px + height: 40, + // the stroke color of target, effective when useImg set false + color: "#00f", + // the fill color of target, effective when useImg set false + fillColor: "#9FD12D" + }, + clockOptions: { + // the default speed + // caculate method: fpstime * Math.pow(2, speed - 1) + // fpstime is the two frame time difference + speed: 10, + // the max speed + maxSpeed: 100 + }, + toolTipOptions: { + offset: [0, 0], + direction: "top", + permanent: false + } + }); + this.setState({ + playback: this.trackplayback + }); + this.trackplaybackControl = L.trackplaybackcontrol(this.trackplayback); + this.trackplaybackControl.addTo(this.map); + }; + + render() { + return ( + /* <Map + className="map" + ref={this.map} + center={[62.3, 25.7]} + zoom={15} + minZoom="7" + maxZoom="17" + zoomControl={false} + > + <TileLayer + attribution='© <a href="https://www.maanmittauslaitos.fi/">Maanmittauslaitos</a>' + url={"https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg"} + /> + <ZoomControl position="topright" /> + {this.state.activeGeoJSON.features && ( + <DrawGeoJSON geoJSONLayer={this.state.activeGeoJSON} /> + )} + </Map> */ + <React.Fragment> + <div className="map" ref="map" /> + </React.Fragment> + ); + } +} diff --git a/src/icons/icon-close.png b/src/icons/icon-close.png new file mode 100644 index 0000000000000000000000000000000000000000..81bef1ad48176c82033dcb527940cff736a53818 Binary files /dev/null and b/src/icons/icon-close.png differ diff --git a/src/icons/icon-play.png b/src/icons/icon-play.png new file mode 100644 index 0000000000000000000000000000000000000000..3af78948ec238cc2fc13e5a2e16569c516b1cd22 Binary files /dev/null and b/src/icons/icon-play.png differ diff --git a/src/icons/icon-quick.png b/src/icons/icon-quick.png new file mode 100644 index 0000000000000000000000000000000000000000..3dc45fd114d7f5813be62cebde2bd95ccedcdceb Binary files /dev/null and b/src/icons/icon-quick.png differ diff --git a/src/icons/icon-restart.png b/src/icons/icon-restart.png new file mode 100644 index 0000000000000000000000000000000000000000..022c4e60fdec44ee1a2d605c85b1f156c16c7cb6 Binary files /dev/null and b/src/icons/icon-restart.png differ diff --git a/src/icons/icon-slow.png b/src/icons/icon-slow.png new file mode 100644 index 0000000000000000000000000000000000000000..eaa1fbdaf382287502240e0987e1df0b8840ee8e Binary files /dev/null and b/src/icons/icon-slow.png differ diff --git a/src/icons/icon-stop.png b/src/icons/icon-stop.png new file mode 100644 index 0000000000000000000000000000000000000000..3ee3137966264afc7119579bf52a7a1bb457e753 Binary files /dev/null and b/src/icons/icon-stop.png differ diff --git a/src/track-playback/src/control.trackplayback/control.playback.js b/src/track-playback/src/control.trackplayback/control.playback.js new file mode 100644 index 0000000000000000000000000000000000000000..6c6f101980579abf0abab21b9d32c2d4626abca4 --- /dev/null +++ b/src/track-playback/src/control.trackplayback/control.playback.js @@ -0,0 +1,319 @@ +import L from "leaflet"; + +export const TrackPlayBackControl = L.Control.extend({ + options: { + position: "topright", + showOptions: true, + showInfo: true, + showSlider: true, + autoPlay: false + }, + + initialize: function(trackplayback, options) { + L.Control.prototype.initialize.call(this, options); + this.trackPlayBack = trackplayback; + this.trackPlayBack.on("tick", this._tickCallback, this); + }, + + onAdd: function(map) { + this._initContainer(); + return this._container; + }, + + onRemove: function(map) { + this.trackPlayBack.dispose(); + this.trackPlayBack.off("tick", this._tickCallback, this); + }, + + /** + * æ ¹æ®unix时间戳(å•ä½:秒)获å–时间å—符串 + * @param {[int]} time [时间戳(精确到秒)] + * @param {[string]} accuracy [精度,日:d, å°æ—¶ï¼šh,分钟:m,秒:s] + * @return {[string]} [yy:mm:dd hh:mm:ss] + */ + getTimeStrFromUnix: function(time, accuracy = "s") { + return ` + ${new Date(time).toLocaleDateString("en-US")} + ${new Date(time * 1e3).toISOString().slice(-13, -5)} + `; + /* time = parseInt(time * 1000); + let newDate = new Date(time); + let year = newDate.getFullYear(); + let month = + newDate.getMonth() + 1 < 10 + ? "0" + (newDate.getMonth() + 1) + : newDate.getMonth() + 1; + let day = + newDate.getDate() < 10 ? "0" + newDate.getDate() : newDate.getDate(); + let hours = + newDate.getHours() < 10 ? "0" + newDate.getHours() : newDate.getHours(); + let minuts = + newDate.getMinutes() < 10 + ? "0" + newDate.getMinutes() + : newDate.getMinutes(); + let seconds = + newDate.getSeconds() < 10 + ? "0" + newDate.getSeconds() + : newDate.getSeconds(); + let ret; + if (accuracy === "d") { + ret = year + "-" + month + "-" + day; + } else if (accuracy === "h") { + ret = year + "-" + month + "-" + day + " " + hours; + } else if (accuracy === "m") { + ret = year + "-" + month + "-" + day + " " + hours + ":" + minuts; + } else { + ret = + year + + "-" + + month + + "-" + + day + + " " + + hours + + ":" + + minuts + + ":" + + seconds; + } + return ret; */ + }, + + _initContainer: function() { + var className = "leaflet-control-playback"; + this._container = L.DomUtil.create("div", className); + L.DomEvent.disableClickPropagation(this._container); + + this._optionsContainer = this._createContainer( + "optionsContainer", + this._container + ); + this._buttonContainer = this._createContainer( + "buttonContainer", + this._container + ); + this._infoContainer = this._createContainer( + "infoContainer", + this._container + ); + this._sliderContainer = this._createContainer( + "sliderContainer", + this._container + ); + this._lineCbx = this._createCheckbox( + "show trackLine", + "show-trackLine", + this._optionsContainer, + this._showTrackLine + ); + + this._playBtn = this._createButton( + "play", + "btn-stop", + this._buttonContainer, + this._play + ); + this._restartBtn = this._createButton( + "replay", + "btn-restart", + this._buttonContainer, + this._restart + ); + this._slowSpeedBtn = this._createButton( + "slow", + "btn-slow", + this._buttonContainer, + this._slow + ); + this._quickSpeedBtn = this._createButton( + "quick", + "btn-quick", + this._buttonContainer, + this._quick + ); + /* this._closeBtn = this._createButton( + "close", + "btn-close", + this._buttonContainer, + this._close + ); */ + + this._infoStartTime = this._createInfo( + "Game started: ", + this.getTimeStrFromUnix(this.trackPlayBack.getStartTime()), + "info-start-time", + this._infoContainer + ); + this._infoEndTime = this._createInfo( + "Game ended: ", + this.getTimeStrFromUnix(this.trackPlayBack.getEndTime()), + "info-end-time", + this._infoContainer + ); + this._infoCurTime = this._createInfo( + "Current time: ", + this.getTimeStrFromUnix(this.trackPlayBack.getCurTime()), + "info-cur-time", + this._infoContainer + ); + this._infoSpeedRatio = this._createInfo( + "speed: ", + `X${this.trackPlayBack.getSpeed()}`, + "info-speed-ratio", + this._infoContainer + ); + + this._slider = this._createSlider( + "time-slider", + this._sliderContainer, + this._scrollchange + ); + + return this._container; + }, + + _createContainer: function(className, container) { + return L.DomUtil.create("div", className, container); + }, + + _createCheckbox: function(labelName, className, container, fn) { + let divEle = L.DomUtil.create( + "div", + className + " trackplayback-checkbox", + container + ); + + let inputEle = L.DomUtil.create("input", "trackplayback-input", divEle); + let inputId = `trackplayback-input-${L.Util.stamp(inputEle)}`; + inputEle.setAttribute("type", "checkbox"); + inputEle.setAttribute("id", inputId); + + let labelEle = L.DomUtil.create("label", "trackplayback-label", divEle); + labelEle.setAttribute("for", inputId); + labelEle.innerHTML = labelName; + + L.DomEvent.on(inputEle, "change", fn, this); + + return divEle; + }, + + _createButton: function(title, className, container, fn) { + let link = L.DomUtil.create("a", className, container); + link.href = "#"; + link.title = title; + + /* + * Will force screen readers like VoiceOver to read this as "Zoom in - button" + */ + link.setAttribute("role", "button"); + link.setAttribute("aria-label", title); + + L.DomEvent.disableClickPropagation(link); + L.DomEvent.on(link, "click", fn, this); + + return link; + }, + + _createInfo: function(title, info, className, container) { + let infoContainer = L.DomUtil.create("div", "info-container", container); + let infoTitle = L.DomUtil.create("span", "info-title", infoContainer); + infoTitle.innerHTML = title; + let infoEle = L.DomUtil.create("span", className, infoContainer); + infoEle.innerHTML = info; + return infoEle; + }, + + _createSlider: function(className, container, fn) { + let sliderEle = L.DomUtil.create("input", className, container); + sliderEle.setAttribute("type", "range"); + sliderEle.setAttribute("min", this.trackPlayBack.getStartTime()); + sliderEle.setAttribute("max", this.trackPlayBack.getEndTime()); + sliderEle.setAttribute("value", this.trackPlayBack.getCurTime()); + + L.DomEvent.on( + sliderEle, + "click mousedown dbclick", + L.DomEvent.stopPropagation + ) + .on(sliderEle, "click", L.DomEvent.preventDefault) + .on(sliderEle, "change", fn, this) + .on(sliderEle, "mousemove", fn, this); + + return sliderEle; + }, + + _showTrackLine(e) { + if (e.target.checked) { + this.trackPlayBack.showTrackLine(); + } else { + this.trackPlayBack.hideTrackLine(); + } + }, + + _play: function() { + let hasClass = L.DomUtil.hasClass(this._playBtn, "btn-stop"); + if (hasClass) { + L.DomUtil.removeClass(this._playBtn, "btn-stop"); + L.DomUtil.addClass(this._playBtn, "btn-start"); + this._playBtn.setAttribute("title", "stop"); + this.trackPlayBack.start(); + } else { + L.DomUtil.removeClass(this._playBtn, "btn-start"); + L.DomUtil.addClass(this._playBtn, "btn-stop"); + this._playBtn.setAttribute("title", "play"); + this.trackPlayBack.stop(); + } + }, + + _restart: function() { + // æ’放开始改å˜æ’æ”¾æŒ‰é’®æ ·å¼ + L.DomUtil.removeClass(this._playBtn, "btn-stop"); + L.DomUtil.addClass(this._playBtn, "btn-start"); + this._playBtn.setAttribute("title", "stop"); + this.trackPlayBack.rePlaying(); + }, + + _slow: function() { + this.trackPlayBack.slowSpeed(); + let sp = this.trackPlayBack.getSpeed(); + this._infoSpeedRatio.innerHTML = `X${sp}`; + }, + + _quick: function() { + this.trackPlayBack.quickSpeed(); + let sp = this.trackPlayBack.getSpeed(); + this._infoSpeedRatio.innerHTML = `X${sp}`; + }, + + _close: function() { + L.DomUtil.remove(this._container); + if (this.onRemove) { + this.onRemove(this._map); + } + return this; + }, + + _scrollchange: function(e) { + let val = Number(e.target.value); + this.trackPlayBack.setCursor(val); + }, + + _tickCallback: function(e) { + // 更新时间 + let time = this.getTimeStrFromUnix(e.time); + this._infoCurTime.innerHTML = time; + // 更新时间轴 + this._slider.value = e.time; + // æ’放结æŸåŽæ”¹å˜æ’æ”¾æŒ‰é’®æ ·å¼ + if (e.time >= this.trackPlayBack.getEndTime()) { + L.DomUtil.removeClass(this._playBtn, "btn-start"); + L.DomUtil.addClass(this._playBtn, "btn-stop"); + this._playBtn.setAttribute("title", "play"); + this.trackPlayBack.stop(); + } + } +}); + +export const trackplaybackcontrol = function(trackplayback, options) { + return new TrackPlayBackControl(trackplayback, options); +}; diff --git a/src/track-playback/src/control.trackplayback/index.js b/src/track-playback/src/control.trackplayback/index.js new file mode 100644 index 0000000000000000000000000000000000000000..599a2df5932224ccd4af7d3146dfa8d078d9c6ab --- /dev/null +++ b/src/track-playback/src/control.trackplayback/index.js @@ -0,0 +1,5 @@ +import L from "leaflet"; +import { TrackPlayBackControl, trackplaybackcontrol } from "./control.playback"; + +L.TrackPlayBackControl = TrackPlayBackControl; +L.trackplaybackcontrol = trackplaybackcontrol; diff --git a/src/track-playback/src/leaflet.trackplayback/clock.js b/src/track-playback/src/leaflet.trackplayback/clock.js new file mode 100644 index 0000000000000000000000000000000000000000..83ff85bdcfeb6f7e85707282e250052deb37c622 --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/clock.js @@ -0,0 +1,132 @@ +import L from "leaflet"; +/** + * 时钟类,控制轨迹æ’放动画 + */ +export const Clock = L.Class.extend({ + includes: L.Mixin.Events, + + options: { + // æ’放速度 + // 计算方法 fpstime * Math.pow(2, this._speed - 1) + speed: 12, + // 最大æ’放速度 + maxSpeed: 65 + }, + + initialize: function(trackController, options) { + L.setOptions(this, options); + + this._trackController = trackController; + this._endTime = this._trackController.getMaxTime(); + this._curTime = this._trackController.getMinTime(); + this._speed = this.options.speed; + this._maxSpeed = this.options.maxSpeed; + this._intervalID = null; + this._lastFpsUpdateTime = 0; + }, + + start: function() { + if (this._intervalID) return; + this._intervalID = L.Util.requestAnimFrame(this._tick, this); + }, + + stop: function() { + if (!this._intervalID) return; + L.Util.cancelAnimFrame(this._intervalID); + this._intervalID = null; + this._lastFpsUpdateTime = 0; + }, + + rePlaying: function() { + this.stop(); + this._curTime = this._trackController.getMinTime(); + this.start(); + }, + + slowSpeed: function() { + this._speed = this._speed <= 1 ? this._speed : this._speed - 1; + if (this._intervalID) { + this.stop(); + this.start(); + } + }, + + quickSpeed: function() { + this._speed = this._speed >= this._maxSpeed ? this._speed : this._speed + 1; + if (this._intervalID) { + this.stop(); + this.start(); + } + }, + + getSpeed: function() { + return this._speed; + }, + + getCurTime: function() { + return this._curTime; + }, + + getStartTime: function() { + return this._trackController.getMinTime(); + }, + + getEndTime: function() { + return this._trackController.getMaxTime(); + }, + + isPlaying: function() { + return !!this._intervalID; + }, + + setCursor: function(time) { + this._curTime = time; + this._trackController.drawTracksByTime(this._curTime); + this.fire("tick", { + time: this._curTime + }); + }, + + setSpeed: function(speed) { + this._speed = speed; + if (this._intervalID) { + this.stop(); + this.start(); + } + }, + + // 计算两帧时间间隔,å•ä½ï¼šç§’ + _caculatefpsTime: function(now) { + let time; + if (this._lastFpsUpdateTime === 0) { + time = 0; + } else { + time = now - this._lastFpsUpdateTime; + } + this._lastFpsUpdateTime = now; + // 将毫秒转æ¢æˆç§’ + time = time / 1000; + return time; + }, + + _tick: function() { + let now = +new Date(); + let fpstime = this._caculatefpsTime(now); + let isPause = false; + let stepTime = fpstime * Math.pow(2, this._speed - 1); + this._curTime += stepTime; + if (this._curTime >= this._endTime) { + this._curTime = this._endTime; + isPause = true; + } + this._trackController.drawTracksByTime(this._curTime); + this.fire("tick", { + time: this._curTime + }); + if (!isPause) this._intervalID = L.Util.requestAnimFrame(this._tick, this); + } +}); + +export const clock = function(trackController, options) { + return new Clock(trackController, options); +}; diff --git a/src/track-playback/src/leaflet.trackplayback/draw.js b/src/track-playback/src/leaflet.trackplayback/draw.js new file mode 100644 index 0000000000000000000000000000000000000000..ba05a447d31c1f556076c475519b2401a460d501 --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/draw.js @@ -0,0 +1,380 @@ +import L from "leaflet"; + +import { TrackLayer } from "./tracklayer"; + +/** + * 绘制类 + * 完æˆè½¨è¿¹çº¿ã€è½¨è¿¹ç‚¹ã€ç›®æ ‡ç‰©çš„绘制工作 + */ +export const Draw = L.Class.extend({ + trackPointOptions: { + isDraw: false, + useCanvas: true, + stroke: false, + color: "#ef0300", + fill: true, + fillColor: "#ef0300", + opacity: 0.3, + radius: 4 + }, + trackLineOptions: { + isDraw: false, + stroke: true, + color: "#1C54E2", // stroke color + weight: 2, + fill: false, + fillColor: "#000", + opacity: 0.3 + }, + targetOptions: { + useImg: false, + imgUrl: "../../static/images/ship.png", + showText: false, + width: 8, + height: 18, + color: "#00f", // stroke color + fillColor: "#9FD12D" + }, + toolTipOptions: { + offset: [0, 0], + direction: "top", + permanent: false + }, + + initialize: function(map, options) { + L.extend(this.trackPointOptions, options.trackPointOptions); + L.extend(this.trackLineOptions, options.trackLineOptions); + L.extend(this.targetOptions, options.targetOptions); + L.extend(this.toolTipOptions, options.toolTipOptions); + + this._showTrackPoint = this.trackPointOptions.isDraw; + this._showTrackLine = this.trackLineOptions.isDraw; + + this._map = map; + this._map.on("click", this._onmouseclickEvt, this); + this._map.on("mousemove", this._onmousemoveEvt, this); + + this._trackLayer = new TrackLayer().addTo(map); + this._trackLayer.on("update", this._trackLayerUpdate, this); + + this._canvas = this._trackLayer.getContainer(); + this._ctx = this._canvas.getContext("2d"); + + this._bufferTracks = []; + + if (!this.trackPointOptions.useCanvas) { + this._trackPointFeatureGroup = L.featureGroup([]).addTo(map); + } + + // setup array for images + this._targetImg = []; + }, + + update: function() { + this._trackLayerUpdate(); + }, + + drawTrack: function(trackpoints) { + this._bufferTracks.push(trackpoints); + this._drawTrack(trackpoints); + }, + + showTrackPoint: function() { + this._showTrackPoint = true; + this.update(); + }, + + hideTrackPoint: function() { + this._showTrackPoint = false; + this.update(); + }, + + showTrackLine: function() { + this._showTrackLine = true; + this.update(); + }, + + hideTrackLine: function() { + this._showTrackLine = false; + this.update(); + }, + + remove: function() { + this._bufferTracks = []; + this._trackLayer.off("update", this._trackLayerUpdate, this); + this._map.off("click", this._onmouseclickEvt, this); + this._map.off("mousemove", this._onmousemoveEvt, this); + if (this._map.hasLayer(this._trackLayer)) { + this._map.removeLayer(this._trackLayer); + } + if (this._map.hasLayer(this._trackPointFeatureGroup)) { + this._map.removeLayer(this._trackPointFeatureGroup); + } + }, + + clear: function() { + this._clearLayer(); + this._bufferTracks = []; + }, + + _trackLayerUpdate: function() { + if (this._bufferTracks.length) { + this._clearLayer(); + this._bufferTracks.forEach( + function(element, index) { + this._drawTrack(element); + }.bind(this) + ); + } + }, + + // changes cursor icon to pointer and shows information about tracked player + _onmousemoveEvt: function(e) { + if (!this._showTrackPoint) { + return; + } + let point = e.layerPoint; + if (this._bufferTracks.length) { + for (let i = 0, leni = this._bufferTracks.length; i < leni; i++) { + for (let j = 0, len = this._bufferTracks[i].length; j < len; j++) { + let tpoint = this._getLayerPoint(this._bufferTracks[i][j]); + if (point.distanceTo(tpoint) <= this.trackPointOptions.radius) { + this._canvas.style.cursor = "pointer"; + return; + } + } + } + } + this._canvas.style.cursor = "grab"; + }, + + // on click event that shows popup about tracked player + _onmouseclickEvt: function(e) { + if (!this._showTrackPoint) { + return; + } + let point = e.layerPoint; + if (this._bufferTracks.length) { + for (let i = 0, leni = this._bufferTracks.length; i < leni; i++) { + for (let j = 0, len = this._bufferTracks[i].length; j < len; j++) { + let tpoint = this._getLayerPoint(this._bufferTracks[i][j]); + if (point.distanceTo(tpoint) <= this.trackPointOptions.radius) { + this._opentoolTip(this._bufferTracks[i][j]); + return; + } + } + } + } + if (this._map.hasLayer(this._tooltip)) { + this._map.removeLayer(this._tooltip); + } + this._canvas.style.cursor = "pointer"; + }, + + _opentoolTip: function(trackpoint) { + if (this._map.hasLayer(this._tooltip)) { + this._map.removeLayer(this._tooltip); + } + let latlng = L.latLng(trackpoint.lat, trackpoint.lng); + let tooltip = (this._tooltip = L.tooltip(this.toolTipOptions)); + tooltip.setLatLng(latlng); + tooltip.addTo(this._map); + tooltip.setContent(this._getTooltipText(trackpoint)); + }, + + _drawTrack: function(trackpoints) { + // 画轨迹线 + if (this._showTrackLine) { + this._drawTrackLine(trackpoints); + } + // 画船 + let targetPoint = trackpoints[trackpoints.length - 1]; + let info = trackpoints[0].info; + if (this.targetOptions.useImg && this._targetImg) { + this._drawShipImage(targetPoint, info); + } else { + this._drawShipCanvas(targetPoint); + } + // ç”»æ ‡æ³¨ä¿¡æ¯ + if (this.targetOptions.showText) { + this._drawtxt(`航å‘:${parseInt(targetPoint.dir)}度`, targetPoint); + } + // ç”»ç»è¿‡çš„轨迹点 + if (this._showTrackPoint) { + if (this.trackPointOptions.useCanvas) { + this._drawTrackPointsCanvas(trackpoints); + } else { + this._drawTrackPointsSvg(trackpoints); + } + } + }, + + _drawTrackLine: function(trackpoints) { + let options = this.trackLineOptions; + let tp0 = this._getLayerPoint(trackpoints[0]); + this._ctx.save(); + this._ctx.beginPath(); + // 画轨迹线 + this._ctx.moveTo(tp0.x, tp0.y); + for (let i = 1, len = trackpoints.length; i < len; i++) { + let tpi = this._getLayerPoint(trackpoints[i]); + this._ctx.lineTo(tpi.x, tpi.y); + } + this._ctx.globalAlpha = options.opacity; + if (options.stroke) { + this._ctx.strokeStyle = options.color; + this._ctx.lineWidth = options.weight; + this._ctx.stroke(); + } + if (options.fill) { + this._ctx.fillStyle = options.fillColor; + this._ctx.fill(); + } + this._ctx.restore(); + }, + + _drawTrackPointsCanvas: function(trackpoints) { + let options = this.trackPointOptions; + let i = trackpoints.length - 1; + this._ctx.save(); + let latLng = L.latLng(trackpoints[i].lat, trackpoints[i].lng); + let radius = options.radius; + let point = this._map.latLngToLayerPoint(latLng); + this._ctx.beginPath(); + this._ctx.arc(point.x, point.y, radius, 0, Math.PI * 2, false); + this._ctx.globalAlpha = options.opacity; + if (options.stroke) { + this._ctx.strokeStyle = options.color; + this._ctx.stroke(); + } + if (options.fill) { + this._ctx.fillStyle = options.fillColor; + this._ctx.fill(); + } + this._ctx.restore(); + }, + + _drawTrackPointsSvg: function(trackpoints) { + let i = trackpoints.length - 1; + let latLng = L.latLng(trackpoints[i].lat, trackpoints[i].lng); + let cricleMarker = L.circleMarker(latLng, this.trackPointOptions); + cricleMarker.bindTooltip( + this._getTooltipText(trackpoints[0]), + this.toolTipOptions + ); + this._trackPointFeatureGroup.addLayer(cricleMarker); + }, + + _drawtxt: function(text, trackpoint) { + let point = this._getLayerPoint(trackpoint); + this._ctx.save(); + this._ctx.font = "12px Verdana"; + this._ctx.fillStyle = "#000"; + this._ctx.textAlign = "center"; + this._ctx.textBaseline = "bottom"; + this._ctx.fillText(text, point.x, point.y - 12, 200); + this._ctx.restore(); + }, + + _drawShipCanvas: function(trackpoint) { + let point = this._getLayerPoint(trackpoint); + let rotate = trackpoint.dir || 0; + let w = this.targetOptions.width; + let h = this.targetOptions.height; + let dh = h / 3; + + this._ctx.save(); + this._ctx.fillStyle = this.targetOptions.fillColor; + this._ctx.strokeStyle = this.targetOptions.color; + this._ctx.translate(point.x, point.y); + this._ctx.rotate((Math.PI / 180) * rotate); + this._ctx.beginPath(); + this._ctx.moveTo(0, 0 - h / 2); + this._ctx.lineTo(0 - w / 2, 0 - h / 2 + dh); + this._ctx.lineTo(0 - w / 2, 0 + h / 2); + this._ctx.lineTo(0 + w / 2, 0 + h / 2); + this._ctx.lineTo(0 + w / 2, 0 - h / 2 + dh); + this._ctx.closePath(); + this._ctx.fill(); + this._ctx.stroke(); + this._ctx.restore(); + }, + + // used to draw image for tracking data + _drawShipImage: function(trackpoint, info) { + let point = this._getLayerPoint(trackpoint); + let width = this.targetOptions.width; + let height = this.targetOptions.height; + let offset = { + x: width / 2, + y: height / 2 + }; + this._ctx.save(); + this._ctx.translate(point.x, point.y); + let image; + // use an existing image if it has the same icon as the new data + this._targetImg.map(img => { + if (img.icon == info[0]["value"]) { + image = img; + } + }); + // else create a new global image with new icon + if (!image) { + let img = new Image(); + img.onload = () => { + this._targetImg.push(img); + }; + img.onerror = () => { + throw new Error("img load error!"); + }; + img.src = info[0]["value"]; + img.icon = info[0]["value"]; + image = img; + } + this._ctx.drawImage(image, 0 - offset.x, 0 - offset.y, width, height); + // draw rect based on faction colour + this._ctx.strokeStyle = info[1]["value"]; + this._ctx.lineWidth = 3; + this._ctx.strokeRect(0 - offset.x, 0 - offset.y, width, height); + this._ctx.restore(); + }, + + _getTooltipText: function(targetobj) { + let content = []; + content.push("<table>"); + if (targetobj.info && targetobj.info.length) { + // skip first two as they're icon and faction colour + for (let i = 2, len = targetobj.info.length; i < len; i++) { + content.push("<tr>"); + content.push("<td>" + targetobj.info[i].value + "</td>"); + content.push("</tr>"); + } + } + content.push("</table>"); + content = content.join(""); + return content; + }, + + _clearLayer: function() { + let bounds = this._trackLayer.getBounds(); + if (bounds) { + let size = bounds.getSize(); + this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y); + } else { + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + } + if (this._map.hasLayer(this._trackPointFeatureGroup)) { + this._trackPointFeatureGroup.clearLayers(); + } + }, + + _getLayerPoint(trackpoint) { + return this._map.latLngToLayerPoint( + L.latLng(trackpoint.lat, trackpoint.lng) + ); + } +}); + +export const draw = function(map, options) { + return new Draw(map, options); +}; diff --git a/src/track-playback/src/leaflet.trackplayback/index.js b/src/track-playback/src/leaflet.trackplayback/index.js new file mode 100644 index 0000000000000000000000000000000000000000..defba9838814140a2047a331ff2825ddd0e22e1e --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/index.js @@ -0,0 +1,9 @@ +import L from 'leaflet' + +import { + TrackPlayBack, + trackplayback +} from './trackplayback' + +L.TrackPlayBack = TrackPlayBack +L.trackplayback = trackplayback diff --git a/src/track-playback/src/leaflet.trackplayback/track.js b/src/track-playback/src/leaflet.trackplayback/track.js new file mode 100644 index 0000000000000000000000000000000000000000..c770c3f3e61a3b70b964be786f9cd77dcd39467f --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/track.js @@ -0,0 +1,166 @@ +import L from "leaflet"; + +import { isArray } from "./util"; + +/** + * 轨迹类 + */ +export const Track = L.Class.extend({ + initialize: function(trackData = [], options) { + L.setOptions(this, options); + + trackData.forEach(item => { + // æ·»åŠ isOrigin å—段用æ¥æ ‡è¯†æ˜¯å¦æ˜¯åŽŸå§‹é‡‡æ ·ç‚¹ï¼Œä¸Žæ’值点区分开 + item.isOrigin = true; + }); + this._trackPoints = trackData; + this._timeTick = {}; + this._update(); + }, + + addTrackPoint: function(trackPoint) { + if (isArray(trackPoint)) { + for (let i = 0, len = trackPoint.length; i < len; i++) { + this.addTrackPoint(trackPoint[i]); + } + } + this._addTrackPoint(trackPoint); + }, + + getTimes: function() { + let times = []; + for (let i = 0, len = this._trackPoints.length; i < len; i++) { + times.push(this._trackPoints[i].time); + } + return times; + }, + + getStartTrackPoint: function() { + return this._trackPoints[0]; + }, + + getEndTrackPoint: function() { + return this._trackPoints[this._trackPoints.length - 1]; + }, + + getTrackPointByTime: function(time) { + return this._trackPoints[this._timeTick[time]]; + }, + + _getCalculateTrackPointByTime: function(time) { + // 先判æ–最åŽä¸€ä¸ªç‚¹æ˜¯å¦ä¸ºåŽŸå§‹ç‚¹ + let endpoint = this.getTrackPointByTime(time); + let startPt = this.getStartTrackPoint(); + let endPt = this.getEndTrackPoint(); + let times = this.getTimes(); + if (time < startPt.time || time > endPt.time) return; + let left = 0; + let right = times.length - 1; + let n; + // 处ç†åªæœ‰ä¸€ä¸ªç‚¹æƒ…况 + if (left === right) { + return endpoint; + } + // 通过ã€äºŒåˆ†æŸ¥æ‰¾ã€‘法查出当å‰æ—¶é—´æ‰€åœ¨çš„时间区间 + while (right - left !== 1) { + n = parseInt((left + right) / 2); + if (time > times[n]) left = n; + else right = n; + } + + let t0 = times[left]; + let t1 = times[right]; + let t = time; + let p0 = this.getTrackPointByTime(t0); + let p1 = this.getTrackPointByTime(t1); + startPt = L.point(p0.lng, p0.lat); + endPt = L.point(p1.lng, p1.lat); + let s = startPt.distanceTo(endPt); + // ä¸åŒæ—¶é—´åœ¨åŒä¸€ä¸ªç‚¹æƒ…å½¢ + if (s <= 0) { + endpoint = p1; + return endpoint; + } + // å‡è®¾ç›®æ ‡åœ¨ä¸¤ç‚¹é—´åšåŒ€é€Ÿç›´çº¿è¿åŠ¨ + // 求解速度å‘é‡ï¼Œå¹¶è®¡ç®—时间 t ç›®æ ‡æ‰€åœ¨ä½ç½® + let v = s / (t1 - t0); + let sinx = (endPt.y - startPt.y) / s; + let cosx = (endPt.x - startPt.x) / s; + let step = v * (t - t0); + let x = startPt.x + step * cosx; + let y = startPt.y + step * sinx; + // æ±‚ç›®æ ‡çš„è¿åŠ¨æ–¹å‘,0-360度 + let dir = + endPt.x >= startPt.x + ? ((Math.PI * 0.5 - Math.asin(sinx)) * 180) / Math.PI + : ((Math.PI * 1.5 + Math.asin(sinx)) * 180) / Math.PI; + + if (endpoint) { + if (endpoint.dir === undefined) { + endpoint.dir = dir; + } + } else { + endpoint = { + lng: x, + lat: y, + dir: endPt.dir || dir, + isOrigin: false, + time: time + }; + } + return endpoint; + }, + + // 获å–æŸä¸ªæ—¶é—´ç‚¹ä¹‹å‰èµ°è¿‡çš„轨迹 + getTrackPointsBeforeTime: function(time) { + let tpoints = []; + for (let i = 0, len = this._trackPoints.length; i < len; i++) { + if (this._trackPoints[i].time < time) { + tpoints.push(this._trackPoints[i]); + } + } + // 获å–最åŽä¸€ä¸ªç‚¹ï¼Œæ ¹æ®æ—¶é—´çº¿æ€§æ’å€¼è€Œæ¥ + let endPt = this._getCalculateTrackPointByTime(time); + if (endPt) { + tpoints.push(endPt); + } + return tpoints; + }, + + _addTrackPoint: function(trackPoint) { + trackPoint.isOrigin = true; + this._trackPoints.push(trackPoint); + this._update(); + }, + + _update: function() { + this._sortTrackPointsByTime(); + this._updatetimeTick(); + }, + + // è½¨è¿¹ç‚¹æŒ‰æ—¶é—´æŽ’åº ã€å†’泡排åºã€‘ + _sortTrackPointsByTime: function() { + let len = this._trackPoints.length; + for (let i = 0; i < len; i++) { + for (let j = 0; j < len - 1 - i; j++) { + if (this._trackPoints[j].time > this._trackPoints[j + 1].time) { + let tmp = this._trackPoints[j + 1]; + this._trackPoints[j + 1] = this._trackPoints[j]; + this._trackPoints[j] = tmp; + } + } + } + }, + + // 为轨迹点建立时间索引,优化查找性能 + _updatetimeTick: function() { + this._timeTick = {}; + for (let i = 0, len = this._trackPoints.length; i < len; i++) { + this._timeTick[this._trackPoints[i].time] = i; + } + } +}); + +export const track = function(trackData, options) { + return new Track(trackData, options); +}; diff --git a/src/track-playback/src/leaflet.trackplayback/trackcontroller.js b/src/track-playback/src/leaflet.trackplayback/trackcontroller.js new file mode 100644 index 0000000000000000000000000000000000000000..2e8157437f541500e2aa0774329d2c9969a7d6ce --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/trackcontroller.js @@ -0,0 +1,72 @@ +import L from "leaflet"; + +import { isArray } from "./util"; +import { Track } from "./track"; + +/** + * 控制器类 + * 控制轨迹和绘制 + */ +export const TrackController = L.Class.extend({ + initialize: function(tracks = [], draw, options) { + L.setOptions(this, options); + + this._tracks = []; + this.addTrack(tracks); + + this._draw = draw; + + this._updateTime(); + }, + + getMinTime: function() { + return this._minTime; + }, + + getMaxTime: function() { + return this._maxTime; + }, + + addTrack: function(track) { + if (isArray(track)) { + for (let i = 0, len = track.length; i < len; i++) { + this.addTrack(track[i]); + } + } else if (track instanceof Track) { + this._tracks.push(track); + this._updateTime(); + } else { + throw new Error( + "tracks must be an instance of `Track` or an array of `Track` instance!" + ); + } + }, + + drawTracksByTime: function(time) { + this._draw.clear(); + for (let i = 0, len = this._tracks.length; i < len; i++) { + let track = this._tracks[i]; + let tps = track.getTrackPointsBeforeTime(time); + if (tps && tps.length) this._draw.drawTrack(tps); + } + }, + + _updateTime: function() { + this._minTime = this._tracks[0].getStartTrackPoint().time; + this._maxTime = this._tracks[0].getEndTrackPoint().time; + for (let i = 0, len = this._tracks.length; i < len; i++) { + let stime = this._tracks[i].getStartTrackPoint().time; + let etime = this._tracks[i].getEndTrackPoint().time; + if (stime < this._minTime) { + this._minTime = stime; + } + if (etime > this._maxTime) { + this._maxTime = etime; + } + } + } +}); + +export const trackController = function(tracks, draw, options) { + return new TrackController(tracks, draw, options); +}; diff --git a/src/track-playback/src/leaflet.trackplayback/tracklayer.js b/src/track-playback/src/leaflet.trackplayback/tracklayer.js new file mode 100644 index 0000000000000000000000000000000000000000..08a21644a7ac2576a25a6c6fcd4650b61b8e576a --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/tracklayer.js @@ -0,0 +1,67 @@ +import L from "leaflet"; + +/** + * 轨迹图层 + */ +export const TrackLayer = L.Renderer.extend({ + initialize: function(options) { + L.Renderer.prototype.initialize.call(this, options); + this.options.padding = 0.1; + }, + + onAdd: function(map) { + this._container = L.DomUtil.create("canvas", "leaflet-zoom-animated"); + + var pane = map.getPane(this.options.pane); + pane.appendChild(this._container); + + this._ctx = this._container.getContext("2d"); + + this._update(); + }, + + onRemove: function(map) { + L.DomUtil.remove(this._container); + }, + + getContainer: function() { + return this._container; + }, + + getBounds: function() { + return this._bounds; + }, + + _update: function() { + if (this._map._animatingZoom && this._bounds) { + return; + } + L.Renderer.prototype._update.call(this); + + var b = this._bounds; + + var container = this._container; + + var size = b.getSize(); + + var m = L.Browser.retina ? 2 : 1; + + L.DomUtil.setPosition(container, b.min); + + // set canvas size (also clearing it); use double size on retina + container.width = m * size.x; + container.height = m * size.y; + container.style.width = size.x + "px"; + container.style.height = size.y + "px"; + + if (L.Browser.retina) { + this._ctx.scale(2, 2); + } + + // translate so we use the same path coordinates after canvas element moves + this._ctx.translate(-b.min.x, -b.min.y); + + // Tell paths to redraw themselves + this.fire("update"); + } +}); diff --git a/src/track-playback/src/leaflet.trackplayback/trackplayback.js b/src/track-playback/src/leaflet.trackplayback/trackplayback.js new file mode 100644 index 0000000000000000000000000000000000000000..460102ff1b594c01fad7396cb9517366a04242f8 --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/trackplayback.js @@ -0,0 +1,131 @@ +import L from 'leaflet' + +import { + Track +} from './track' +import { + TrackController +} from './trackcontroller' +import { + Clock +} from './clock' +import { + Draw +} from './draw' +import * as Util from './util' + +/** + * single track data + * [{lat: 30, lng: 116, time: 1502529980, heading: 300, info:[]},{},....] + * + * mutiple track data + * [single track data, single track data, single track data] + */ +export const TrackPlayBack = L.Class.extend({ + + includes: L.Mixin.Events, + + initialize: function (data, map, options = {}) { + let drawOptions = { + trackPointOptions: options.trackPointOptions, + trackLineOptions: options.trackLineOptions, + targetOptions: options.targetOptions, + toolTipOptions: options.toolTipOptions + } + this.tracks = this._initTracks(data) + this.draw = new Draw(map, drawOptions) + this.trackController = new TrackController(this.tracks, this.draw) + this.clock = new Clock(this.trackController, options.clockOptions) + + this.clock.on('tick', this._tick, this) + }, + start: function () { + this.clock.start() + return this + }, + stop: function () { + this.clock.stop() + return this + }, + rePlaying: function () { + this.clock.rePlaying() + return this + }, + slowSpeed: function () { + this.clock.slowSpeed() + return this + }, + quickSpeed: function () { + this.clock.quickSpeed() + return this + }, + getSpeed: function () { + return this.clock.getSpeed() + }, + getCurTime: function () { + return this.clock.getCurTime() + }, + getStartTime: function () { + return this.clock.getStartTime() + }, + getEndTime: function () { + return this.clock.getEndTime() + }, + isPlaying: function () { + return this.clock.isPlaying() + }, + setCursor: function (time) { + this.clock.setCursor(time) + return this + }, + setSpeed: function (speed) { + this.clock.setSpeed(speed) + return this + }, + showTrackPoint: function () { + this.draw.showTrackPoint() + return this + }, + hideTrackPoint: function () { + this.draw.hideTrackPoint() + return this + }, + showTrackLine: function () { + this.draw.showTrackLine() + return this + }, + hideTrackLine: function () { + this.draw.hideTrackLine() + return this + }, + dispose: function () { + this.clock.off('tick', this._tick) + this.draw.remove() + this.tracks = null + this.draw = null + this.trackController = null + this.clock = null + }, + _tick: function (e) { + this.fire('tick', e) + }, + _initTracks: function (data) { + let tracks = [] + if (Util.isArray(data)) { + if (Util.isArray(data[0])) { + // 多æ¡è½¨è¿¹ + for (let i = 0, len = data.length; i < len; i++) { + tracks.push(new Track(data[i])) + } + } else { + // å•æ¡è½¨è¿¹ + tracks.push(new Track(data)) + } + } + return tracks + } +}) + +export const trackplayback = function (data, map, options) { + return new TrackPlayBack(data, map, options) +} diff --git a/src/track-playback/src/leaflet.trackplayback/util.js b/src/track-playback/src/leaflet.trackplayback/util.js new file mode 100644 index 0000000000000000000000000000000000000000..b40984d26d198f2dcb8dade0f18eba8153d44d87 --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/util.js @@ -0,0 +1,3 @@ +export function isArray (arr) { + return Array.isArray ? Array.isArray(arr) : Object.prototype.toString.call(arr) === '[object Array]' +}