From e9d5dbeef2bf1dab9dfb863d970b70b3074e3d0a Mon Sep 17 00:00:00 2001
From: Michael Foiani <sotech117@michaels-mbp-21.devices.brown.edu>
Date: Wed, 2 Nov 2022 14:56:20 -0400
Subject: add basic heartbeat functinality througha ping/pong api cycle

---
 src/client/views/topbar/TopBar.tsx | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

(limited to 'src/client/views/topbar/TopBar.tsx')

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>
         );
-- 
cgit v1.2.3-70-g09d2


From 3139bf1474b714a099b2c13de4a78b7c4ded38df Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 24 Jun 2023 10:22:30 -0400
Subject: ping cleanup

---
 src/client/util/PingManager.ts     |  23 +++---
 src/client/views/MainView.tsx      | 156 ++++++++++++++++---------------------
 src/client/views/topbar/TopBar.tsx |   4 +-
 3 files changed, 81 insertions(+), 102 deletions(-)

(limited to 'src/client/views/topbar/TopBar.tsx')

diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
index 7562faf03..9f4bd3642 100644
--- a/src/client/util/PingManager.ts
+++ b/src/client/util/PingManager.ts
@@ -1,5 +1,5 @@
-import { action, IReactionDisposer, observable, observe, reaction } from "mobx";
-import { Networking } from "../Network";
+import { action, observable } from 'mobx';
+import { Networking } from '../Network';
 export class PingManager {
     // create static instance and getter for global use
     @observable static _instance: PingManager;
@@ -7,25 +7,24 @@ export class PingManager {
         return PingManager._instance;
     }
 
-    @observable isBeating: boolean = true;
-    private setIsBeating = action((status: boolean) => this.isBeating = status);
+    @observable IsBeating: boolean = true;
+    private setIsBeating = action((status: boolean) => (this.IsBeating = status));
 
-    ping = async (): Promise<void> => {
+    sendPing = 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);
+            await Networking.PostToServer('/ping', { date: new Date() });
+            !this.IsBeating && this.setIsBeating(true);
         } catch {
             console.error('ping error');
-            this.isBeating && this.setIsBeating(false);
+            this.IsBeating && this.setIsBeating(false);
         }
-    }
+    };
 
     // not used now, but may need to clear interval
-    private interval: NodeJS.Timeout | null = null;
+    private _interval: NodeJS.Timeout | null = null;
     INTERVAL_SECONDS = 1;
     constructor() {
         PingManager._instance = this;
-        this.interval = setInterval(this.ping, this.INTERVAL_SECONDS * 1000);
+        this._interval = setInterval(this.sendPing, this.INTERVAL_SECONDS * 1000);
     }
 }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index fbe44cf7a..6c05db892 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -145,95 +145,75 @@ 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');
-        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.showDocument(doc, { willPan: false }) : 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();
-                    }
-                }
-            );
+        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.showDocument(doc, { willPan: false }) : null));
+        });
+        document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener);
+        this.initEventListeners();
     }
 
     componentWillUnMount() {
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 68d652cc4..157d7c04a 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -38,10 +38,10 @@ export class TopBar extends React.Component {
     @observable textColor: string = Colors.LIGHT_GRAY;
     @observable backgroundColor: string = Colors.DARK_GRAY;
 
-    @observable happyHeart: boolean = PingManager.Instance.isBeating;
+    @observable happyHeart: boolean = PingManager.Instance.IsBeating;
     setHappyHeart = action((status: boolean) => (this.happyHeart = status));
     dispose = reaction(
-        () => PingManager.Instance.isBeating,
+        () => PingManager.Instance.IsBeating,
         isBeating => this.setHappyHeart(isBeating)
     );
 
-- 
cgit v1.2.3-70-g09d2


From 6344439d62e5450f69ca7c0492976e55241d250f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 24 Jun 2023 11:34:15 -0400
Subject: changed server ping to generate client alerts

---
 src/client/util/PingManager.ts          | 15 +++++++++++----
 src/client/views/topbar/TopBar.scss     |  5 ++++-
 src/client/views/topbar/TopBar.tsx      | 19 +++++++++++++++----
 src/server/ApiManagers/UploadManager.ts |  2 --
 4 files changed, 30 insertions(+), 11 deletions(-)

(limited to 'src/client/views/topbar/TopBar.tsx')

diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
index 9f4bd3642..0c41a1ea7 100644
--- a/src/client/util/PingManager.ts
+++ b/src/client/util/PingManager.ts
@@ -7,16 +7,23 @@ export class PingManager {
         return PingManager._instance;
     }
 
-    @observable IsBeating: boolean = true;
-    private setIsBeating = action((status: boolean) => (this.IsBeating = status));
+    @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 {
-            console.error('ping error');
-            this.IsBeating && this.setIsBeating(false);
+            if (this.IsBeating) {
+                this.setIsBeating(false);
+            }
         }
     };
 
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
index a0f803da5..ede59a910 100644
--- a/src/client/views/topbar/TopBar.scss
+++ b/src/client/views/topbar/TopBar.scss
@@ -9,8 +9,11 @@
 }
 .topbarHeart-red {
     .iconButton-container.primary {
+        .iconButton-content {
+            color: red;
+        }
         .iconButton-background {
-            background: red;
+            background: black;
         }
     }
 }
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 157d7c04a..20cf563c1 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -1,4 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+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';
@@ -36,7 +37,9 @@ 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));
@@ -146,9 +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" />} />
-                <div className={'topbarHeart' + (this.happyHeart ? '' : '-red')}>
-                    <IconButton size={Size.SMALL} color={this.happyHeart ? Colors.LIGHT_BLUE : Colors.ERROR_RED} icon={<FontAwesomeIcon icon={this.happyHeart ? 'heart' : 'heart-broken'} />} />
-                </div>
+                <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 de1661ed6..820e815d8 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -45,8 +45,6 @@ export default class UploadManager extends ApiManager {
             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() });
             },
         });
-- 
cgit v1.2.3-70-g09d2

