aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FrontEnd/package-lock.json159
-rw-r--r--FrontEnd/package.json1
-rw-r--r--FrontEnd/src/app/http/common.ts9
-rw-r--r--FrontEnd/src/app/services/timeline.ts51
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx12
5 files changed, 206 insertions, 26 deletions
diff --git a/FrontEnd/package-lock.json b/FrontEnd/package-lock.json
index 39b8743c..5d675fb8 100644
--- a/FrontEnd/package-lock.json
+++ b/FrontEnd/package-lock.json
@@ -8,6 +8,7 @@
"name": "timeline",
"version": "0.3.0",
"dependencies": {
+ "@microsoft/signalr": "^5.0.6",
"axios": "^0.21.1",
"bootstrap": "^5.0.1",
"bootstrap-icons": "^1.5.0",
@@ -1664,6 +1665,18 @@
"react": "*"
}
},
+ "node_modules/@microsoft/signalr": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-5.0.6.tgz",
+ "integrity": "sha512-ExwKc9L/Dia7gachfekMYNqalH1qAnluNmh1mhTNGqIp0s3SH9DJMi5FSVcmgx8R1K6Sn86Z67lYGBfOlvifLg==",
+ "dependencies": {
+ "abort-controller": "^3.0.0",
+ "eventsource": "^1.0.7",
+ "fetch-cookie": "^0.7.3",
+ "node-fetch": "^2.6.0",
+ "ws": "^6.0.0"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
@@ -3001,6 +3014,17 @@
"node": ">=10.19.0"
}
},
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
"node_modules/accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@@ -3294,8 +3318,7 @@
"node_modules/async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
- "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
- "dev": true
+ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"node_modules/at-least-node": {
"version": "1.0.0",
@@ -5734,6 +5757,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es6-denodeify": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/es6-denodeify/-/es6-denodeify-0.1.5.tgz",
+ "integrity": "sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8="
+ },
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -6300,6 +6328,14 @@
"node": ">= 0.6"
}
},
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@@ -6319,7 +6355,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
- "dev": true,
"dependencies": {
"original": "^1.0.0"
},
@@ -6714,6 +6749,15 @@
"node": ">=0.8.0"
}
},
+ "node_modules/fetch-cookie": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.7.3.tgz",
+ "integrity": "sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA==",
+ "dependencies": {
+ "es6-denodeify": "^0.1.1",
+ "tough-cookie": "^2.3.3"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -9211,6 +9255,14 @@
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
"dev": true
},
+ "node_modules/node-fetch": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
+ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ }
+ },
"node_modules/node-forge": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
@@ -9627,7 +9679,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
"integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
- "dev": true,
"dependencies": {
"url-parse": "^1.4.3"
}
@@ -11920,6 +11971,11 @@
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
"dev": true
},
+ "node_modules/psl": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+ "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
+ },
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -11934,7 +11990,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true,
"engines": {
"node": ">=6"
}
@@ -11975,8 +12030,7 @@
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
- "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
- "dev": true
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"node_modules/queue-microtask": {
"version": "1.2.3",
@@ -12595,8 +12649,7 @@
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
- "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
- "dev": true
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"node_modules/resolve": {
"version": "1.20.0",
@@ -14275,6 +14328,18 @@
"node": ">=0.6"
}
},
+ "node_modules/tough-cookie": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+ "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+ "dependencies": {
+ "psl": "^1.1.28",
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/tr46": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
@@ -14770,7 +14835,6 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
- "dev": true,
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
@@ -15983,7 +16047,6 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
- "dev": true,
"dependencies": {
"async-limiter": "~1.0.0"
}
@@ -17432,6 +17495,18 @@
"integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==",
"requires": {}
},
+ "@microsoft/signalr": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-5.0.6.tgz",
+ "integrity": "sha512-ExwKc9L/Dia7gachfekMYNqalH1qAnluNmh1mhTNGqIp0s3SH9DJMi5FSVcmgx8R1K6Sn86Z67lYGBfOlvifLg==",
+ "requires": {
+ "abort-controller": "^3.0.0",
+ "eventsource": "^1.0.7",
+ "fetch-cookie": "^0.7.3",
+ "node-fetch": "^2.6.0",
+ "ws": "^6.0.0"
+ }
+ },
"@nodelib/fs.scandir": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
@@ -18504,6 +18579,14 @@
"tslib": "^1.13.0"
}
},
+ "abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "requires": {
+ "event-target-shim": "^5.0.0"
+ }
+ },
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@@ -18718,8 +18801,7 @@
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
- "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
- "dev": true
+ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"at-least-node": {
"version": "1.0.0",
@@ -20631,6 +20713,11 @@
"is-symbol": "^1.0.2"
}
},
+ "es6-denodeify": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/es6-denodeify/-/es6-denodeify-0.1.5.tgz",
+ "integrity": "sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8="
+ },
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -21066,6 +21153,11 @@
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
"dev": true
},
+ "event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
+ },
"eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@@ -21082,7 +21174,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
- "dev": true,
"requires": {
"original": "^1.0.0"
}
@@ -21417,6 +21508,15 @@
"websocket-driver": ">=0.5.1"
}
},
+ "fetch-cookie": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.7.3.tgz",
+ "integrity": "sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA==",
+ "requires": {
+ "es6-denodeify": "^0.1.1",
+ "tough-cookie": "^2.3.3"
+ }
+ },
"file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -23361,6 +23461,11 @@
}
}
},
+ "node-fetch": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
+ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
+ },
"node-forge": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
@@ -23678,7 +23783,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
"integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
- "dev": true,
"requires": {
"url-parse": "^1.4.3"
}
@@ -25378,6 +25482,11 @@
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
"dev": true
},
+ "psl": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+ "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
+ },
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -25391,8 +25500,7 @@
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"qs": {
"version": "6.10.1",
@@ -25418,8 +25526,7 @@
"querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
- "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
- "dev": true
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"queue-microtask": {
"version": "1.2.3",
@@ -25893,8 +26000,7 @@
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
- "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
- "dev": true
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"resolve": {
"version": "1.20.0",
@@ -27245,6 +27351,15 @@
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
"dev": true
},
+ "tough-cookie": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+ "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+ "requires": {
+ "psl": "^1.1.28",
+ "punycode": "^2.1.1"
+ }
+ },
"tr46": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
@@ -27627,7 +27742,6 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
- "dev": true,
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
@@ -28605,7 +28719,6 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
- "dev": true,
"requires": {
"async-limiter": "~1.0.0"
}
diff --git a/FrontEnd/package.json b/FrontEnd/package.json
index 8a15812f..28aa7022 100644
--- a/FrontEnd/package.json
+++ b/FrontEnd/package.json
@@ -6,6 +6,7 @@
"keywords": [],
"description": "Timeline app.",
"dependencies": {
+ "@microsoft/signalr": "^5.0.6",
"axios": "^0.21.1",
"bootstrap": "^5.0.1",
"bootstrap-icons": "^1.5.0",
diff --git a/FrontEnd/src/app/http/common.ts b/FrontEnd/src/app/http/common.ts
index 78ba3cda..e1672985 100644
--- a/FrontEnd/src/app/http/common.ts
+++ b/FrontEnd/src/app/http/common.ts
@@ -1,5 +1,6 @@
import rawAxios, { AxiosError, AxiosResponse } from "axios";
import { Base64 } from "js-base64";
+import { BehaviorSubject, Observable } from "rxjs";
export const apiBaseUrl = "/api";
@@ -44,14 +45,14 @@ axios.interceptors.response.use(undefined, convertToNetworkError);
axios.interceptors.response.use(undefined, convertToForbiddenError);
axios.interceptors.response.use(undefined, convertToNotFoundError);
-let _token: string | null = null;
+const tokenSubject = new BehaviorSubject<string | null>(null);
export function getHttpToken(): string | null {
- return _token;
+ return tokenSubject.value;
}
export function setHttpToken(token: string | null): void {
- _token = token;
+ tokenSubject.next(token);
if (token == null) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
@@ -62,6 +63,8 @@ export function setHttpToken(token: string | null): void {
}
}
+export const token$: Observable<string | null> = tokenSubject.asObservable();
+
export function base64(blob: Blob | string): Promise<string> {
if (typeof blob === "string") {
return Promise.resolve(Base64.encode(blob));
diff --git a/FrontEnd/src/app/services/timeline.ts b/FrontEnd/src/app/services/timeline.ts
index a24ec8eb..c49ba654 100644
--- a/FrontEnd/src/app/services/timeline.ts
+++ b/FrontEnd/src/app/services/timeline.ts
@@ -1,5 +1,11 @@
import { TimelineVisibility } from "@/http/timeline";
import XRegExp from "xregexp";
+import { Observable } from "rxjs";
+import { HubConnection, HubConnectionBuilder } from "@microsoft/signalr";
+
+import { UiLogicError } from "@/common";
+
+import { token$ } from "@/http/common";
const timelineNameReg = XRegExp("^[-_\\p{L}]*$", "u");
@@ -15,3 +21,48 @@ export const timelineVisibilityTooltipTranslationMap: Record<
Register: "timeline.visibilityTooltip.register",
Private: "timeline.visibilityTooltip.private",
};
+
+function generateTimelineHubUrl(token: string | null): string {
+ return `/api/hub/timeline${token == null ? "" : "?token=" + token}`;
+}
+
+function createTimelineHubConnection(token: string | null): HubConnection {
+ return new HubConnectionBuilder()
+ .withUrl(generateTimelineHubUrl(token))
+ .withAutomaticReconnect()
+ .build();
+}
+
+let timelineHubConnection: HubConnection | null = null;
+
+token$.subscribe((token) => {
+ if (timelineHubConnection != null) {
+ void timelineHubConnection.stop();
+ }
+ timelineHubConnection = createTimelineHubConnection(token);
+ void timelineHubConnection.start();
+});
+
+export function getTimelinePostUpdate(
+ timelineName: string
+): Observable<string> {
+ return new Observable((subscriber) => {
+ if (timelineHubConnection == null)
+ throw new UiLogicError("Connection is null.");
+
+ const connection = timelineHubConnection;
+
+ const handler = (tn: string): void => {
+ if (timelineName === tn) {
+ subscriber.next(tn);
+ }
+ };
+ connection.on("OnTimelinePostChanged", handler);
+ void connection.invoke("SubscribeTimelinePostChange", timelineName);
+
+ return () => {
+ void connection.invoke("UnsubscribeTimelinePostChange", timelineName);
+ connection.off("OnTimelinePostChanged", handler);
+ };
+ });
+}
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
index 81a3c179..4c0cc8e3 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
@@ -12,6 +12,7 @@ import TimelinePostEdit from "./TimelinePostEdit";
import useReverseScrollPositionRemember from "@/utilities/useReverseScrollPositionRemember";
import { generatePalette, setPalette } from "@/palette";
+import { getTimelinePostUpdate as getTimelinePostUpdate$ } from "@/services/timeline";
export interface TimelinePageCardProps {
timeline: HttpTimelineInfo;
@@ -91,6 +92,17 @@ const TimelinePageTemplate: React.FC<TimelinePageTemplateProps> = (props) => {
setTimelineReloadKey((old) => old + 1);
};
+ React.useEffect(() => {
+ const timelinePostUpdate$ = getTimelinePostUpdate$(timelineName);
+ const subscription = timelinePostUpdate$.subscribe(() => {
+ setTimelineReloadKey((old) => old + 1);
+ });
+
+ return () => {
+ subscription.unsubscribe();
+ };
+ }, [timelineName]);
+
const onPostEditHeightChange = React.useCallback((height: number): void => {
setBottomSpaceHeight(height);
if (height === 0) {