From 3788a132c44671bbf9bd7c76e6e77528f04456ec Mon Sep 17 00:00:00 2001
From: David Tsay <david.e.tsay@nasa.gov>
Date: Mon, 13 May 2024 18:06:58 -0700
Subject: [PATCH] create useClock composable code clean up

---
 src/plugins/timeConductor/ConductorClock.vue  |  78 +---------
 .../timeConductor/ConductorComponent.vue      |  31 +---
 .../timeConductor/ConductorHistory.vue        |  12 +-
 src/plugins/timeConductor/ConductorPopUp.vue  |  19 +--
 .../timeConductor/ConductorTimeSystem.vue     |  10 +-
 src/plugins/timeConductor/clock-mixin.js      |   9 --
 .../independent/IndependentClock.vue          |   2 +-
 .../independent/IndependentMode.vue           |   2 +-
 src/plugins/timeConductor/mode-mixin.js       |   9 --
 src/plugins/timeConductor/useClock.js         | 141 ++++++++++++++++++
 10 files changed, 161 insertions(+), 152 deletions(-)
 create mode 100644 src/plugins/timeConductor/useClock.js

diff --git a/src/plugins/timeConductor/ConductorClock.vue b/src/plugins/timeConductor/ConductorClock.vue
index 23f29d4895..db162167c6 100644
--- a/src/plugins/timeConductor/ConductorClock.vue
+++ b/src/plugins/timeConductor/ConductorClock.vue
@@ -23,8 +23,8 @@
   <div v-if="readOnly === false" ref="clockButton" class="c-tc-input-popup__options">
     <div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
       <button
-        class="c-button--menu js-clock-button"
-        :class="[buttonCssClass, selectedClock.cssClass]"
+        class="c-button--menu js-clock-button c-icon-button"
+        :class="selectedClock.cssClass"
         aria-label="Time Conductor Clock Menu"
         @click.prevent.stop="showClocksMenu"
       >
@@ -38,18 +38,8 @@
 </template>
 
 <script>
-import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
-import clockMixin from './clock-mixin.js';
-
 export default {
-  mixins: [clockMixin],
-  inject: {
-    openmct: 'openmct',
-    configuration: {
-      from: 'configuration',
-      default: undefined
-    }
-  },
+  inject: ['openmct', 'configuration', 'clock', 'getAllClockMetadata', 'getClockMetadata'],
   props: {
     readOnly: {
       type: Boolean,
@@ -58,21 +48,15 @@ export default {
       }
     }
   },
-  emits: ['clock-updated'],
   data() {
-    const activeClock = this.getActiveClock();
-
     return {
-      selectedClock: activeClock ? this.getClockMetadata(activeClock) : undefined,
-      clocks: []
+      clocks: this.getAllClockMetadata(this.configuration.menuOptions)
     };
   },
-  mounted() {
-    this.loadClocks(this.configuration.menuOptions);
-    this.openmct.time.on(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
-  },
-  unmounted() {
-    this.openmct.time.off(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
+  computed: {
+    selectedClock() {
+      return this.getClockMetadata(this.clock);
+    }
   },
   methods: {
     showClocksMenu() {
@@ -86,52 +70,6 @@ export default {
       };
 
       this.dismiss = this.openmct.menus.showSuperMenu(x, y, this.clocks, menuOptions);
-    },
-    setClock(clockKey) {
-      const option = {
-        clockKey
-      };
-      let configuration = this.getMatchingConfig({
-        clock: clockKey,
-        timeSystem: this.openmct.time.getTimeSystem().key
-      });
-
-      if (configuration === undefined) {
-        configuration = this.getMatchingConfig({
-          clock: clockKey
-        });
-
-        option.timeSystem = configuration.timeSystem;
-        option.bounds = configuration.bounds;
-
-        // this.openmct.time.setTimeSystem(configuration.timeSystem, configuration.bounds);
-      }
-
-      const offsets = this.openmct.time.getClockOffsets() ?? configuration.clockOffsets;
-      option.offsets = offsets;
-
-      this.$emit('clock-updated', option);
-    },
-    getMatchingConfig(options) {
-      const matchers = {
-        clock(config) {
-          return options.clock === config.clock;
-        },
-        timeSystem(config) {
-          return options.timeSystem === config.timeSystem;
-        }
-      };
-
-      function configMatches(config) {
-        return Object.keys(options).reduce((match, option) => {
-          return match && matchers[option](config);
-        }, true);
-      }
-
-      return this.configuration.menuOptions.filter(configMatches)[0];
-    },
-    setViewFromClock(clock) {
-      this.selectedClock = this.getClockMetadata(clock);
     }
   }
 };
diff --git a/src/plugins/timeConductor/ConductorComponent.vue b/src/plugins/timeConductor/ConductorComponent.vue
index df8ec451b7..614438586f 100644
--- a/src/plugins/timeConductor/ConductorComponent.vue
+++ b/src/plugins/timeConductor/ConductorComponent.vue
@@ -61,9 +61,6 @@
       :position-x="positionX"
       :position-y="positionY"
       @popup-loaded="initializePopup"
-      @clock-updated="saveClock"
-      @fixed-bounds-updated="saveFixedBounds"
-      @clock-offsets-updated="saveClockOffsets"
       @dismiss="clearPopup"
     />
   </div>
@@ -72,7 +69,6 @@
 <script>
 import { inject, onMounted, provide } from 'vue';
 
-import { FIXED_MODE_KEY, REALTIME_MODE_KEY } from '../../api/time/constants.js';
 import ConductorAxis from './ConductorAxis.vue';
 import ConductorClock from './ConductorClock.vue';
 import ConductorInputsFixed from './ConductorInputsFixed.vue';
@@ -82,6 +78,7 @@ import ConductorModeIcon from './ConductorModeIcon.vue';
 import ConductorPopUp from './ConductorPopUp.vue';
 import conductorPopUpManager from './conductorPopUpManager.js';
 import ConductorTimeSystem from './ConductorTimeSystem.vue';
+import { useClock } from './useClock.js';
 import { useClockOffsets } from './useClockOffsets.js';
 import { useTimeBounds } from './useTimeBounds.js';
 import { useTimeMode } from './useTimeMode.js';
@@ -117,12 +114,14 @@ export default {
       getModeMetadata
     } = useTimeMode(openmct);
     const { observeTimeBounds, bounds, isTick } = useTimeBounds(openmct);
+    const { observeClock, clock, getAllClockMetadata, getClockMetadata } = useClock(openmct);
     const { observeClockOffsets, offsets } = useClockOffsets(openmct);
 
     onMounted(() => {
       observeTimeSystem();
       observeTimeMode();
       observeTimeBounds();
+      observeClock();
       observeClockOffsets();
     });
 
@@ -137,22 +136,18 @@ export default {
     provide('bounds', bounds);
     provide('isTick', isTick);
     provide('offsets', offsets);
+    provide('clock', clock);
+    provide('getAllClockMetadata', getAllClockMetadata);
+    provide('getClockMetadata', getClockMetadata);
 
     return {
       timeSystemFormatter,
-      timeSystemDurationFormatter,
       isFixedTimeMode,
-      isRealTimeMode,
-      bounds,
-      isTick
+      bounds
     };
   },
   data() {
     return {
-      // offsets: {
-      //   start: offsets && this.durationFormatter.format(Math.abs(offsets.start)),
-      //   end: offsets && this.durationFormatter.format(Math.abs(offsets.end))
-      // },
       viewBounds: {
         start: this.bounds.start,
         end: this.bounds.end
@@ -170,9 +165,6 @@ export default {
         start: this.timeSystemFormatter.format(this.bounds.start),
         end: this.timeSystemFormatter.format(this.bounds.end)
       };
-    },
-    mode() {
-      return this.isFixedTimeMode ? FIXED_MODE_KEY : REALTIME_MODE_KEY;
     }
   },
   watch: {
@@ -234,15 +226,6 @@ export default {
       // this.formattedBounds.end = this.timeFormatter.format(bounds.end);
       this.viewBounds.start = bounds.start;
       this.viewBounds.end = bounds.end;
-    },
-    saveFixedBounds(bounds) {
-      // this.openmct.time.setBounds(bounds);
-    },
-    saveClockOffsets(offsets) {
-      // this.openmct.time.setClockOffsets(offsets);
-    },
-    saveClock(clockOptions) {
-      this.openmct.time.setClock(clockOptions.clockKey);
     }
   }
 };
diff --git a/src/plugins/timeConductor/ConductorHistory.vue b/src/plugins/timeConductor/ConductorHistory.vue
index b7764fb5c0..cb215065c3 100644
--- a/src/plugins/timeConductor/ConductorHistory.vue
+++ b/src/plugins/timeConductor/ConductorHistory.vue
@@ -24,8 +24,7 @@
     <div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
       <button
         aria-label="Time Conductor History"
-        class="c-button--menu c-history-button icon-history"
-        :class="buttonCssClass"
+        class="c-button--menu c-history-button icon-history c-icon-button"
         @click.prevent.stop="showHistoryMenu"
       >
         <span class="c-button__label">History</span>
@@ -47,15 +46,6 @@ import UTCTimeFormat from '../utcTimeSystem/UTCTimeFormat.js';
 
 export default {
   inject: ['openmct', 'configuration'],
-  props: {
-    buttonCssClass: {
-      type: String,
-      required: false,
-      default() {
-        return '';
-      }
-    }
-  },
   data() {
     const mode = this.openmct.time.getMode();
 
diff --git a/src/plugins/timeConductor/ConductorPopUp.vue b/src/plugins/timeConductor/ConductorPopUp.vue
index 062a319b2c..b89bb8c221 100644
--- a/src/plugins/timeConductor/ConductorPopUp.vue
+++ b/src/plugins/timeConductor/ConductorPopUp.vue
@@ -12,35 +12,29 @@
         v-else
         class="c-conductor__mode-select"
         title="Sets the Time Conductor's mode."
-        :button-css-class="'c-icon-button'"
       />
       <IndependentClock
         v-if="isIndependent"
         class="c-conductor__mode-select"
         title="Sets the Time Conductor's clock."
         :clock="timeOptionClock"
-        :button-css-class="'c-icon-button'"
         @independent-clock-updated="saveIndependentClock"
       />
       <ConductorClock
         v-else
         class="c-conductor__mode-select"
         title="Sets the Time Conductor's clock."
-        :button-css-class="'c-icon-button'"
-        @clock-updated="saveClock"
       />
       <!-- TODO: Time system and history must work even with ITC later -->
       <ConductorTimeSystem
         v-if="!isIndependent"
         class="c-conductor__time-system-select"
         title="Sets the Time Conductor's time system."
-        :button-css-class="'c-icon-button'"
       />
       <ConductorHistory
         v-if="!isIndependent"
         class="c-conductor__history-select"
         title="Select and apply previously entered time intervals."
-        :button-css-class="'c-icon-button'"
       />
     </div>
     <conductor-inputs-fixed
@@ -82,14 +76,7 @@ export default {
     ConductorInputsFixed,
     ConductorInputsRealtime
   },
-  inject: {
-    openmct: 'openmct',
-    configuration: {
-      from: 'configuration',
-      default: undefined
-    },
-    isFixedTimeMode: 'isFixedTimeMode'
-  },
+  inject: ['openmct', 'configuration', 'isFixedTimeMode'],
   props: {
     positionX: {
       type: Number,
@@ -130,7 +117,6 @@ export default {
     'independent-clock-updated',
     'fixed-bounds-updated',
     'clock-offsets-updated',
-    'clock-updated',
     'independent-mode-updated'
   ],
   data() {
@@ -225,9 +211,6 @@ export default {
     saveClockOffsets(offsets) {
       this.$emit('clock-offsets-updated', offsets);
     },
-    saveClock(clockOptions) {
-      this.$emit('clock-updated', clockOptions);
-    },
     saveIndependentMode(mode) {
       this.$emit('independent-mode-updated', mode);
     },
diff --git a/src/plugins/timeConductor/ConductorTimeSystem.vue b/src/plugins/timeConductor/ConductorTimeSystem.vue
index 4023249fcb..08225e9051 100644
--- a/src/plugins/timeConductor/ConductorTimeSystem.vue
+++ b/src/plugins/timeConductor/ConductorTimeSystem.vue
@@ -26,8 +26,7 @@
     class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
   >
     <button
-      class="c-button--menu c-time-system-button"
-      :class="[buttonCssClass]"
+      class="c-button--menu c-time-system-button c-icon-button"
       aria-label="Time Conductor Time System"
       @click.prevent.stop="showTimeSystemMenu"
     >
@@ -50,13 +49,6 @@ import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
 export default {
   inject: ['openmct', 'configuration'],
   props: {
-    buttonCssClass: {
-      type: String,
-      required: false,
-      default() {
-        return '';
-      }
-    },
     readOnly: {
       type: Boolean,
       default() {
diff --git a/src/plugins/timeConductor/clock-mixin.js b/src/plugins/timeConductor/clock-mixin.js
index 32da96c7ad..37ba025954 100644
--- a/src/plugins/timeConductor/clock-mixin.js
+++ b/src/plugins/timeConductor/clock-mixin.js
@@ -1,13 +1,4 @@
 export default {
-  props: {
-    buttonCssClass: {
-      type: String,
-      required: false,
-      default() {
-        return '';
-      }
-    }
-  },
   methods: {
     loadClocks(menuOptions) {
       let clocks;
diff --git a/src/plugins/timeConductor/independent/IndependentClock.vue b/src/plugins/timeConductor/independent/IndependentClock.vue
index 79e532c84d..7ec8a04495 100644
--- a/src/plugins/timeConductor/independent/IndependentClock.vue
+++ b/src/plugins/timeConductor/independent/IndependentClock.vue
@@ -25,7 +25,7 @@
       <button
         v-if="selectedClock"
         class="c-icon-button c-button--menu js-clock-button"
-        :class="[buttonCssClass, selectedClock.cssClass]"
+        :class="selectedClock.cssClass"
         aria-label="Independent Time Conductor Clock Menu"
         @click.prevent.stop="showClocksMenu"
       >
diff --git a/src/plugins/timeConductor/independent/IndependentMode.vue b/src/plugins/timeConductor/independent/IndependentMode.vue
index fe599a12e2..c203d848fc 100644
--- a/src/plugins/timeConductor/independent/IndependentMode.vue
+++ b/src/plugins/timeConductor/independent/IndependentMode.vue
@@ -24,7 +24,7 @@
     <div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
       <button
         class="c-icon-button c-button--menu js-mode-button"
-        :class="[buttonCssClass, selectedMode.cssClass]"
+        :class="selectedMode.cssClass"
         aria-label="Independent Time Conductor Mode Menu"
         @click.prevent.stop="showModesMenu"
       >
diff --git a/src/plugins/timeConductor/mode-mixin.js b/src/plugins/timeConductor/mode-mixin.js
index f8dc37db64..110b49f8cc 100644
--- a/src/plugins/timeConductor/mode-mixin.js
+++ b/src/plugins/timeConductor/mode-mixin.js
@@ -1,15 +1,6 @@
 import { FIXED_MODE_KEY, REALTIME_MODE_KEY } from '../../api/time/constants.js';
 
 export default {
-  props: {
-    buttonCssClass: {
-      type: String,
-      required: false,
-      default() {
-        return '';
-      }
-    }
-  },
   methods: {
     loadModes() {
       this.modes = [FIXED_MODE_KEY, REALTIME_MODE_KEY].map(this.getModeMetadata);
diff --git a/src/plugins/timeConductor/useClock.js b/src/plugins/timeConductor/useClock.js
new file mode 100644
index 0000000000..df1dde14ee
--- /dev/null
+++ b/src/plugins/timeConductor/useClock.js
@@ -0,0 +1,141 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2024, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+import { onBeforeUnmount, ref } from 'vue';
+
+/**
+ * Provides reactive TODO,
+ * as well as a function to observe and update the component's clock,
+ * which automatically stops observing when the component is unmounted.
+ *
+ * @param {OpenMCT} openmct the Open MCT API
+ * @returns {{
+ *   observeClock: () => void,
+ *   timeMode: import('vue').Ref<string>,
+ *   isFixedTimeMode: import('vue').Ref<boolean>,
+ *   isRealTimeMode: import('vue').Ref<boolean>
+ * }}
+ */
+export function useClock(openmct, options) {
+  let stopObservingClock;
+
+  const clock = ref(openmct.time.getClock());
+
+  onBeforeUnmount(() => stopObservingClock?.());
+
+  function observeClock() {
+    openmct.time.on(TIME_CONTEXT_EVENTS.clockChanged, updateClock);
+    stopObservingClock = () => openmct.time.off(TIME_CONTEXT_EVENTS.clockChanged, updateClock);
+  }
+
+  function getAllClockMetadata(menuOptions) {
+    const clocks = menuOptions
+      ? menuOptions
+          .map((menuOption) => menuOption.clock)
+          .filter((key, index, array) => key !== undefined && array.indexOf(key) === index)
+          .map((clockKey) => openmct.time.getAllClocks().find((_clock) => _clock.key === clockKey))
+      : openmct.time.getAllClocks();
+
+    const clockMetadata = clocks.map(getClockMetadata);
+
+    return clockMetadata;
+  }
+
+  function getClockMetadata(_clock) {
+    if (_clock === undefined) {
+      return;
+    }
+
+    const clockMetadata = {
+      key: _clock.key,
+      name: _clock.name,
+      description: 'Uses the system clock as the current time basis. ' + _clock.description,
+      cssClass: _clock.cssClass || 'icon-clock',
+      onItemClicked: () => setClock(_clock.key)
+    };
+
+    return clockMetadata;
+  }
+
+  function setClock(key) {
+    openmct.time.setClock(key);
+  }
+
+  function updateClock(_clock) {
+    clock.value = _clock;
+  }
+
+  /**
+   * TODO: bring this back. we lost this in the last refactor.
+   * changing clock requires a timesystem check.
+   * 
+  function setClockWithOptions() {
+    const option = {
+      clockKey
+    };
+    let configuration = this.getMatchingConfig({
+      clock: clockKey,
+      timeSystem: this.openmct.time.getTimeSystem().key
+    });
+
+    if (configuration === undefined) {
+      configuration = this.getMatchingConfig({
+        clock: clockKey
+      });
+
+      option.timeSystem = configuration.timeSystem;
+      option.bounds = configuration.bounds;
+
+      // this.openmct.time.setTimeSystem(configuration.timeSystem, configuration.bounds);
+    }
+
+    const offsets = this.openmct.time.getClockOffsets() ?? configuration.clockOffsets;
+    option.offsets = offsets;
+  }
+
+  function getMatchingConfig(options) {
+    const matchers = {
+      clock(config) {
+        return options.clock === config.clock;
+      },
+      timeSystem(config) {
+        return options.timeSystem === config.timeSystem;
+      }
+    };
+
+    function configMatches(config) {
+      return Object.keys(options).reduce((match, option) => {
+        return match && matchers[option](config);
+      }, true);
+    }
+
+    return this.configuration.menuOptions.filter(configMatches)[0];
+  }
+  */
+
+  return {
+    observeClock,
+    clock,
+    getAllClockMetadata,
+    getClockMetadata
+  };
+}