aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Foiani <sotech117@michaels-mbp-21.devices.brown.edu>2022-11-02 14:56:20 -0400
committerMichael Foiani <sotech117@michaels-mbp-21.devices.brown.edu>2022-11-02 14:56:20 -0400
commite9d5dbeef2bf1dab9dfb863d970b70b3074e3d0a (patch)
treee88de9ff3a05d094667a991cebf27006a0566d63
parent770f546a38d7ec827bff55fd0dd64b873a112180 (diff)
add basic heartbeat functinality througha ping/pong api cycle
-rw-r--r--src/client/util/PingManager.ts31
-rw-r--r--src/client/views/Main.tsx2
-rw-r--r--src/client/views/MainView.tsx158
-rw-r--r--src/client/views/topbar/TopBar.tsx19
-rw-r--r--src/server/ApiManagers/UploadManager.ts10
5 files changed, 152 insertions, 68 deletions
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
new file mode 100644
index 000000000..7562faf03
--- /dev/null
+++ b/src/client/util/PingManager.ts
@@ -0,0 +1,31 @@
+import { action, IReactionDisposer, observable, observe, reaction } 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: boolean = true;
+ private setIsBeating = action((status: boolean) => this.isBeating = status);
+
+ ping = async (): Promise<void> => {
+ try {
+ const response = await Networking.PostToServer('/ping', { date: new Date() });
+ // console.log('ping response', response, this.interval);
+ !this.isBeating && this.setIsBeating(true);
+ } catch {
+ console.error('ping error');
+ 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.ping, this.INTERVAL_SECONDS * 1000);
+ }
+}
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index f327f3184..a1d221af2 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -14,6 +14,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();
@@ -44,6 +45,7 @@ AssignAllExtensions();
new LinkManager();
new TrackMovements();
new ReplayMovements();
+ new PingManager();
ReactDOM.createRoot(document.getElementById('root')!).render(<MainView />);
}, 0);
})();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 9648a7807..5be5c3588 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -66,6 +66,7 @@ import { PropertiesView } from './PropertiesView';
import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
import { TopBar } from './topbar/TopBar';
import 'browndash-components/dist/styles/global.min.css';
+import { PingManager } from '../util/PingManager';
const _global = (window /* browser */ || global) /* node */ as any;
@observer
@@ -143,75 +144,96 @@ export class MainView extends React.Component {
componentDidMount() {
document.getElementById('root')?.addEventListener('scroll', e => (ele => (ele.scrollLeft = ele.scrollTop = 0))(document.getElementById('root')!));
const ele = document.getElementById('loader');
- const prog = document.getElementById('dash-progress');
- if (ele && prog) {
- // remove from DOM
- setTimeout(() => {
- clearTimeout();
- prog.style.transition = '1s';
- prog.style.width = '100%';
- }, 0);
- setTimeout(() => (ele.outerHTML = ''), 1000);
- }
- this._sidebarContent.proto = undefined;
- if (!MainView.Live) {
- DocServer.setPlaygroundFields([
- 'dataTransition',
- 'viewTransition',
- 'treeViewOpen',
- 'showSidebar',
- 'itemIndex', // for changing slides in presentations
- 'sidebarWidthPercent',
- 'currentTimecode',
- 'timelineHeightPercent',
- 'presStatus',
- 'panX',
- 'panY',
- 'overlayX',
- 'overlayY',
- 'fitWidth',
- 'nativeWidth',
- 'nativeHeight',
- 'text-scrollHeight',
- 'text-height',
- 'hideMinimap',
- 'viewScale',
- 'scrollTop',
- 'hidden',
- 'curPage',
- 'viewType',
- 'chromeHidden',
- 'currentFrame',
- 'width',
- 'height',
- 'nativeWidth',
- ]); // can play with these fields on someone else's
+ console.log(PingManager.Instance);
+
+ const wrapper = () => {
+ const prog = document.getElementById('dash-progress');
+ if (ele && prog) {
+ // remove from DOM
+ setTimeout(() => {
+ clearTimeout();
+ prog.style.transition = '1s';
+ prog.style.width = '100%';
+ }, 0);
+ setTimeout(() => (ele.outerHTML = ''), 1000);
+ }
+ this._sidebarContent.proto = undefined;
+ if (!MainView.Live) {
+ DocServer.setPlaygroundFields([
+ 'dataTransition',
+ 'viewTransition',
+ 'treeViewOpen',
+ 'showSidebar',
+ 'itemIndex', // for changing slides in presentations
+ 'sidebarWidthPercent',
+ 'currentTimecode',
+ 'timelineHeightPercent',
+ 'presStatus',
+ 'panX',
+ 'panY',
+ 'overlayX',
+ 'overlayY',
+ 'fitWidth',
+ 'nativeWidth',
+ 'nativeHeight',
+ 'text-scrollHeight',
+ 'text-height',
+ 'hideMinimap',
+ 'viewScale',
+ 'scrollTop',
+ 'hidden',
+ 'curPage',
+ 'viewType',
+ 'chromeHidden',
+ 'currentFrame',
+ 'width',
+ 'height',
+ 'nativeWidth',
+ ]); // can play with these fields on someone else's
+ }
+ DocServer.GetRefField('rtfProto').then(
+ proto =>
+ proto instanceof Doc &&
+ reaction(
+ () => StrCast(proto.BROADCAST_MESSAGE),
+ msg => msg && alert(msg)
+ )
+ );
+
+ const tag = document.createElement('script');
+ tag.src = 'https://www.youtube.com/iframe_api';
+ const firstScriptTag = document.getElementsByTagName('script')[0];
+ firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
+ window.removeEventListener('keydown', KeyManager.Instance.handle);
+ window.addEventListener('keydown', KeyManager.Instance.handle);
+ window.removeEventListener('keyup', KeyManager.Instance.unhandle);
+ window.addEventListener('keyup', KeyManager.Instance.unhandle);
+ window.addEventListener('paste', KeyManager.Instance.paste as any);
+ document.addEventListener('dash', (e: any) => {
+ // event used by chrome plugin to tell Dash which document to focus on
+ const id = FormattedTextBox.GetDocFromUrl(e.detail);
+ DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.jumpToDocument(doc, false, undefined, []) : null));
+ });
+ document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener);
+ this.initEventListeners();
}
- DocServer.GetRefField('rtfProto').then(
- proto =>
- proto instanceof Doc &&
- reaction(
- () => StrCast(proto.BROADCAST_MESSAGE),
- msg => msg && alert(msg)
- )
- );
- const tag = document.createElement('script');
- tag.src = 'https://www.youtube.com/iframe_api';
- const firstScriptTag = document.getElementsByTagName('script')[0];
- firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
- window.removeEventListener('keydown', KeyManager.Instance.handle);
- window.addEventListener('keydown', KeyManager.Instance.handle);
- window.removeEventListener('keyup', KeyManager.Instance.unhandle);
- window.addEventListener('keyup', KeyManager.Instance.unhandle);
- window.addEventListener('paste', KeyManager.Instance.paste as any);
- document.addEventListener('dash', (e: any) => {
- // event used by chrome plugin to tell Dash which document to focus on
- const id = FormattedTextBox.GetDocFromUrl(e.detail);
- DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.jumpToDocument(doc, false, undefined, []) : null));
- });
- document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener);
- this.initEventListeners();
+ if (PingManager.Instance.isBeating) {
+ wrapper();
+ } else {
+ console.error('PingManager is not beating', new Date());
+ const dispose = reaction(
+ () => PingManager.Instance.isBeating,
+ isBeating => {
+ if (isBeating) {
+ console.log('PingManager is beating', new Date());
+ wrapper();
+ dispose();
+ }
+ }
+ );
+ }
+
}
componentWillUnMount() {
@@ -478,6 +500,8 @@ export class MainView extends React.Component {
fa.faSquareRootAlt,
fa.faVolumeMute,
fa.faUserCircle,
+ fa.faHeart,
+ fa.faHeartBroken,
]
);
this.initAuthenticationRouters();
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 7e728306c..50a93ee92 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, FontSize, IconButton, Size } from 'browndash-components';
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaBug, FaCamera } from 'react-icons/fa';
@@ -8,6 +8,7 @@ import { AclAdmin, Doc, DocListCast } from '../../../fields/Doc';
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 { SettingsManager } from '../../util/SettingsManager';
import { SharingManager } from '../../util/SharingManager';
@@ -35,6 +36,12 @@ export class TopBar extends React.Component {
@observable textColor: string = Colors.LIGHT_GRAY;
@observable backgroundColor: string = Colors.DARK_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.
* This side of the topbar contains the different modes.
@@ -176,6 +183,16 @@ export class TopBar extends React.Component {
onClick={() => SettingsManager.Instance.open()}
icon={<FontAwesomeIcon icon="cog" />}
/>
+ <IconButton
+ size={Size.SMALL}
+ isCircle={true}
+ color={this.happyHeart ? Colors.LIGHT_BLUE : Colors.ERROR_RED}
+ backgroundColor={Colors.DARK_GRAY}
+ hoverStyle="gray"
+ isActive={SettingsManager.Instance.isOpen}
+ onClick={() => SettingsManager.Instance.open()}
+ icon={<FontAwesomeIcon icon={this.happyHeart ? "heart" : "heart-broken"} />}
+ />
{/* <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 fe4c475c9..0a16bd8ec 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -43,6 +43,16 @@ export default class UploadManager extends ApiManager {
protected initialize(register: Registration): void {
register({
method: Method.POST,
+ subscription: '/ping',
+ secureHandler: async ({ req, res }) => {
+ // req.body contains the array of server paths to the videos
+ // console.log('ping', req.body);
+ _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