aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/PingManager.ts37
-rw-r--r--src/client/views/Main.tsx2
-rw-r--r--src/client/views/MainView.tsx3
-rw-r--r--src/client/views/topbar/TopBar.scss437
-rw-r--r--src/client/views/topbar/TopBar.tsx30
-rw-r--r--src/server/ApiManagers/UploadManager.ts8
6 files changed, 300 insertions, 217 deletions
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
new file mode 100644
index 000000000..0c41a1ea7
--- /dev/null
+++ b/src/client/util/PingManager.ts
@@ -0,0 +1,37 @@
+import { action, observable } from 'mobx';
+import { Networking } from '../Network';
+export class PingManager {
+ // create static instance and getter for global use
+ @observable static _instance: PingManager;
+ static get Instance(): PingManager {
+ return PingManager._instance;
+ }
+
+ @observable IsBeating = true;
+ private setIsBeating = action((status: boolean) => {
+ this.IsBeating = status;
+ setTimeout(this.showAlert, 100);
+ });
+
+ showAlert = () => {
+ alert(PingManager.Instance.IsBeating ? 'The server connection is active' : 'The server connection has been interrupted.NOTE: Any changes made will appear to persist but will be lost after a browser refreshes.');
+ };
+ sendPing = async (): Promise<void> => {
+ try {
+ await Networking.PostToServer('/ping', { date: new Date() });
+ !this.IsBeating && this.setIsBeating(true);
+ } catch {
+ if (this.IsBeating) {
+ this.setIsBeating(false);
+ }
+ }
+ };
+
+ // not used now, but may need to clear interval
+ private _interval: NodeJS.Timeout | null = null;
+ INTERVAL_SECONDS = 1;
+ constructor() {
+ PingManager._instance = this;
+ this._interval = setInterval(this.sendPing, this.INTERVAL_SECONDS * 1000);
+ }
+}
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 6b18caed0..b0b757388 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -15,6 +15,7 @@ import { TrackMovements } from '../util/TrackMovements';
import { CollectionView } from './collections/CollectionView';
import { MainView } from './MainView';
import * as dotenv from 'dotenv'; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import
+import { PingManager } from '../util/PingManager';
dotenv.config();
AssignAllExtensions();
@@ -48,6 +49,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0 }; // bcz: not sure
new LinkManager();
new TrackMovements();
new ReplayMovements();
+ new PingManager();
root.render(<MainView />);
}, 0);
})();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index dbe8fb608..ab2e0f7c5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -30,7 +30,6 @@ import { ColorScheme, SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { SnappingManager } from '../util/SnappingManager';
import { Transform } from '../util/Transform';
-import { UndoManager } from '../util/UndoManager';
import { TimelineMenu } from './animationtimeline/TimelineMenu';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
@@ -489,6 +488,8 @@ export class MainView extends React.Component {
fa.faSquareRootAlt,
fa.faVolumeMute,
fa.faUserCircle,
+ fa.faHeart,
+ fa.faHeartBroken,
fa.faHighlighter,
fa.faRemoveFormat,
fa.faHandPointUp,
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
index a1131b92e..ede59a910 100644
--- a/src/client/views/topbar/TopBar.scss
+++ b/src/client/views/topbar/TopBar.scss
@@ -1,243 +1,256 @@
-@import "../global/globalCssVariables";
-
-
+@import '../global/globalCssVariables';
+
+.iconButton-container.primary {
+ color: white;
+ .iconButton-background {
+ filter: unset;
+ background: transparent;
+ }
+}
+.topbarHeart-red {
+ .iconButton-container.primary {
+ .iconButton-content {
+ color: red;
+ }
+ .iconButton-background {
+ background: black;
+ }
+ }
+}
.topbar-container {
- flex-direction: column;
- font-size: 10px;
- line-height: 1;
- overflow-y: auto;
- overflow-x: visible;
- background: $dark-gray;
- overflow: visible;
- z-index: 1000;
- align-items: center;
- height: $topbar-height;
- background-color: $dark-gray;
- border-bottom: $standard-border;
- padding: 0px 10px;
- cursor: default;
- display: flex;
- justify-content: center;
+ flex-direction: column;
+ font-size: 10px;
+ line-height: 1;
+ overflow-y: auto;
+ overflow-x: visible;
+ background: $dark-gray;
+ overflow: visible;
+ z-index: 1000;
+ align-items: center;
+ height: $topbar-height;
+ background-color: $dark-gray;
+ border-bottom: $standard-border;
+ padding: 0px 10px;
+ cursor: default;
+ display: flex;
+ justify-content: center;
+ width: 100%;
+
+ .topbar-inner-container {
+ display: flex;
+ flex-direction: row;
+ position: relative;
+ display: grid;
+ grid-auto-columns: 33.3% 33.3% 33.3%;
width: 100%;
+ align-items: center;
- .topbar-inner-container {
- display: flex;
- flex-direction: row;
- position: relative;
- display: grid;
- grid-auto-columns: 33.3% 33.3% 33.3%;
- width: 100%;
- align-items: center;
-
- // &:first-child {
- // height: 20px;
- // }
- }
-
- .topbar-button-text {
- color: $white;
- padding: 10px;
- size: 15;
+ // &:first-child {
+ // height: 20px;
+ // }
+ }
- &:hover {
- font-weight: 500;
- }
- }
+ .topbar-button-text {
+ color: $white;
+ padding: 10px;
+ size: 15;
- .topbar-button-icon {
- cursor: pointer;
- width: fit-content;
- display: flex;
- justify-content: center;
- gap: 4px;
- align-items: center;
- justify-self: center;
- align-self: center;
- padding: 5px;
- transition: linear 0.2s;
- color: $white;
-
- &:hover {
- background-color: darken($color: $light-gray, $amount: 20);
- font-weight: 500;
- }
- }
-
- .topbar-title {
- color: $white;
- font-size: 17;
+ &:hover {
font-weight: 500;
- }
+ }
+ }
- .topbar-center {
- grid-column: 2;
- display: inline-flex;
+ .topbar-button-icon {
+ cursor: pointer;
+ width: fit-content;
+ display: flex;
+ justify-content: center;
+ gap: 4px;
+ align-items: center;
+ justify-self: center;
+ align-self: center;
+ padding: 5px;
+ transition: linear 0.2s;
+ color: $white;
+
+ &:hover {
+ background-color: darken($color: $light-gray, $amount: 20);
+ font-weight: 500;
+ }
+ }
+
+ .topbar-title {
+ color: $white;
+ font-size: 17;
+ font-weight: 500;
+ }
+
+ .topbar-center {
+ grid-column: 2;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ gap: 5px;
+
+ .topbar-dashboard-header {
+ font-weight: 600;
+ }
+ }
+
+ .topbar-right {
+ grid-column: 3;
+ position: relative;
+ display: flex;
+ justify-content: flex-end;
+ gap: 5px;
+ margin-right: 5px;
+ }
+
+ .topbar-left {
+ grid-column: 1;
+ color: black;
+ font-family: 'Roboto';
+ position: relative;
+ display: flex;
+ width: fit-content;
+ gap: 5px;
+
+ .logo-container {
+ font-size: 15;
+ display: flex;
+ flex-direction: row;
justify-content: center;
align-items: center;
- gap: 5px;
-
- .topbar-dashboard-header {
- font-weight: 600;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+
+ .logo {
+ background-color: transparent;
+ width: 25px;
+ height: 25px;
+ margin-right: 5px;
}
- }
+ }
+ .topBar-icon:hover {
+ background-color: $close-red;
+ }
- .topbar-right {
- grid-column: 3;
- position: relative;
- display: flex;
- justify-content: flex-end;
- gap: 5px;
- margin-right: 5px;
- }
-
- .topbar-left {
- grid-column: 1;
- color: black;
+ .topbar-lozenge-user,
+ .topbar-lozenge {
+ height: 23;
+ font-size: 12;
+ color: white;
font-family: 'Roboto';
- position: relative;
+ font-weight: 400;
+ padding: 4px;
+ align-self: center;
+ margin-left: 7px;
display: flex;
- width: fit-content;
- gap: 5px;
-
- .logo-container {
- font-size: 15;
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -o-user-select: none;
- user-select: none;
-
- .logo {
- background-color: transparent;
- width: 25px;
- height: 25px;
- margin-right: 5px;
-
- }
- }
+ align-items: center;
- .topBar-icon:hover {
- background-color: $close-red;
- }
+ .topbar-dashSelect {
+ border: none;
+ background-color: transparent;
+ color: black;
+ font-family: 'Roboto';
+ font-size: 17;
+ font-weight: 500;
- .topbar-lozenge-user,
- .topbar-lozenge {
- height: 23;
- font-size: 12;
- color: white;
- font-family: 'Roboto';
- font-weight: 400;
- padding: 4px;
- align-self: center;
- margin-left: 7px;
- display: flex;
- align-items: center;
-
- .topbar-dashSelect {
- border: none;
- background-color: transparent;
- color: black;
- font-family: 'Roboto';
- font-size: 17;
- font-weight: 500;
-
- &:hover {
- cursor: pointer;
- }
- }
+ &:hover {
+ cursor: pointer;
+ }
}
+ }
+
+ .topbar-logoff {
+ border-radius: 3px;
+ background: olivedrab;
+ color: white;
+ display: none;
+ margin-left: 5px;
+ padding: 1px 2px 1px 2px;
+ cursor: pointer;
+ }
- .topbar-logoff {
- border-radius: 3px;
- background: olivedrab;
- color: white;
- display: none;
- margin-left: 5px;
- padding: 1px 2px 1px 2px;
- cursor: pointer;
- }
+ .topbar-logoff {
+ background: red;
+ }
+ .topbar-lozenge-user:hover {
.topbar-logoff {
- background: red;
- }
-
- .topbar-lozenge-user:hover {
- .topbar-logoff {
- display: inline-block;
- }
- }
- }
-
- .topbar-barChild {
-
- &.topbar-collection {
- flex: 0 1 auto;
- margin-left: 2px;
- margin-right: 2px
+ display: inline-block;
}
-
- &.topbar-input {
- margin: 5px;
- border-radius: 20px;
- border: $dark-gray;
- display: block;
- width: 130px;
- -webkit-transition: width 0.4s;
- transition: width 0.4s;
- /* align-self: stretch; */
- outline: none;
-
- &:focus {
- width: 500px;
- outline: none;
- }
+ }
+ }
+
+ .topbar-barChild {
+ &.topbar-collection {
+ flex: 0 1 auto;
+ margin-left: 2px;
+ margin-right: 2px;
+ }
+
+ &.topbar-input {
+ margin: 5px;
+ border-radius: 20px;
+ border: $dark-gray;
+ display: block;
+ width: 130px;
+ -webkit-transition: width 0.4s;
+ transition: width 0.4s;
+ /* align-self: stretch; */
+ outline: none;
+
+ &:focus {
+ width: 500px;
+ outline: none;
}
+ }
- &.topbar-filter {
- align-self: stretch;
+ &.topbar-filter {
+ align-self: stretch;
- button {
- transform: none;
+ button {
+ transform: none;
- &:hover {
- transform: none;
- }
- }
+ &:hover {
+ transform: none;
+ }
}
+ }
- &.topbar-submit {
- margin-left: 2px;
- margin-right: 2px
- }
+ &.topbar-submit {
+ margin-left: 2px;
+ margin-right: 2px;
+ }
- &.topbar-close {
- color: $white;
- max-height: $topbar-height;
- }
- }
+ &.topbar-close {
+ color: $white;
+ max-height: $topbar-height;
+ }
+ }
}
.topbar-results {
- display: flex;
- flex-direction: column;
- top: 300px;
- display: flex;
- flex-direction: column;
- height: 100%;
- overflow: visible;
-
- .no-result {
- width: 500px;
- background: $light-gray;
- padding: 10px;
- height: 50px;
- text-transform: uppercase;
- text-align: left;
- font-weight: bold;
- }
-} \ No newline at end of file
+ display: flex;
+ flex-direction: column;
+ top: 300px;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ overflow: visible;
+
+ .no-result {
+ width: 500px;
+ background: $light-gray;
+ padding: 10px;
+ height: 50px;
+ text-transform: uppercase;
+ text-align: left;
+ font-weight: bold;
+ }
+}
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 71daad1a9..20cf563c1 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -1,14 +1,16 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, IconButton, Size } from 'browndash-components';
-import { action, computed, observable } from 'mobx';
+import { Tooltip } from '@mui/material';
+import { Button, FontSize, IconButton, Size } from 'browndash-components';
+import { action, computed, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaBug, FaCamera, FaStamp } from 'react-icons/fa';
-import { Doc } from '../../../fields/Doc';
+import { Doc } from '../../../fields/Doc';
import { AclAdmin } from '../../../fields/DocSymbols';
import { StrCast } from '../../../fields/Types';
import { GetEffectiveAcl } from '../../../fields/util';
import { DocumentManager } from '../../util/DocumentManager';
+import { PingManager } from '../../util/PingManager';
import { ReportManager } from '../../util/ReportManager';
import { ServerStats } from '../../util/ServerStats';
import { SettingsManager } from '../../util/SettingsManager';
@@ -35,7 +37,16 @@ export class TopBar extends React.Component {
};
@observable textColor: string = Colors.LIGHT_GRAY;
- @observable backgroundColor: string = Colors.DARK_GRAY;
+ @computed get backgroundColor() {
+ return PingManager.Instance.IsBeating ? Colors.DARK_GRAY : Colors.MEDIUM_GRAY;
+ }
+
+ @observable happyHeart: boolean = PingManager.Instance.IsBeating;
+ setHappyHeart = action((status: boolean) => (this.happyHeart = status));
+ dispose = reaction(
+ () => PingManager.Instance.IsBeating,
+ isBeating => this.setHappyHeart(isBeating)
+ );
/**
* Returns the left hand side of the topbar.
@@ -138,6 +149,17 @@ export class TopBar extends React.Component {
<IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={ReportManager.Instance.open} icon={<FaBug />} />
<IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
<IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={SettingsManager.Instance.open} icon={<FontAwesomeIcon icon="cog" />} />
+ <Tooltip title={<div className="dash-tooltip">{'Server connection ' + (PingManager.Instance.IsBeating ? 'active' : 'broken')}</div>}>
+ <div className={'topbarHeart' + (this.happyHeart ? '' : '-red')}>
+ <IconButton
+ size={Size.SMALL}
+ onClick={PingManager.Instance.showAlert}
+ tooltip={'Server is ' + (PingManager.Instance.IsBeating ? '' : 'NOT ') + 'running'}
+ color={this.happyHeart ? Colors.LIGHT_BLUE : Colors.ERROR_RED}
+ icon={<FontAwesomeIcon icon={this.happyHeart ? 'heart' : 'heart-broken'} />}
+ />
+ </div>
+ </Tooltip>
{/* <Button text={'Logout'} borderRadius={5} hoverStyle={'gray'} backgroundColor={Colors.DARK_GRAY} color={this.textColor} fontSize={FontSize.SECONDARY} onClick={() => window.location.assign(Utils.prepend('/logout'))} /> */}
</div>
);
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index ba6d7acfe..820e815d8 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -43,6 +43,14 @@ export default class UploadManager extends ApiManager {
protected initialize(register: Registration): void {
register({
method: Method.POST,
+ subscription: '/ping',
+ secureHandler: async ({ req, res }) => {
+ _success(res, { message: 'pong', date: new Date() });
+ },
+ });
+
+ register({
+ method: Method.POST,
subscription: '/concatVideos',
secureHandler: async ({ req, res }) => {
// req.body contains the array of server paths to the videos