Compare commits
	
		
			11 Commits
		
	
	
		
			timelist-e
			...
			activity-s
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					9e880a5020 | ||
| 
						 | 
					50de0f6f27 | ||
| 
						 | 
					a0ccc4e300 | ||
| 
						 | 
					c6a6a18eba | ||
| 
						 | 
					2e41753f41 | ||
| 
						 | 
					3c14025501 | ||
| 
						 | 
					e47bfedaf7 | ||
| 
						 | 
					02edb9924b | ||
| 
						 | 
					4d4f83ee95 | ||
| 
						 | 
					92a30a3485 | ||
| 
						 | 
					7003f00707 | 
@@ -108,6 +108,7 @@
 | 
			
		||||
 | 
			
		||||
      openmct.install(openmct.plugins.Espresso());
 | 
			
		||||
      openmct.install(openmct.plugins.MyItems());
 | 
			
		||||
      openmct.install(openmct.plugins.ActivityStates());
 | 
			
		||||
      openmct.install(
 | 
			
		||||
        openmct.plugins.PlanLayout({
 | 
			
		||||
          creatable: true
 | 
			
		||||
 
 | 
			
		||||
@@ -99,7 +99,13 @@ export default class ObjectAPI {
 | 
			
		||||
    this.cache = {};
 | 
			
		||||
    this.interceptorRegistry = new InterceptorRegistry();
 | 
			
		||||
 | 
			
		||||
    this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'restricted-notebook', 'plan', 'annotation'];
 | 
			
		||||
    this.SYNCHRONIZED_OBJECT_TYPES = [
 | 
			
		||||
      'notebook',
 | 
			
		||||
      'restricted-notebook',
 | 
			
		||||
      'plan',
 | 
			
		||||
      'annotation',
 | 
			
		||||
      'activity-states'
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    this.errors = {
 | 
			
		||||
      Conflict: ConflictError
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										51
									
								
								src/plugins/activityStates/activityStatesInterceptor.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/plugins/activityStates/activityStatesInterceptor.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2023, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT 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 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 { ACTIVITYSTATES_KEY, ACTIVITYSTATES_TYPE } from './createActivityStatesIdentifier.js';
 | 
			
		||||
 | 
			
		||||
function activityStatesInterceptor(openmct, identifierObject, name) {
 | 
			
		||||
  const activityStatesModel = {
 | 
			
		||||
    identifier: identifierObject,
 | 
			
		||||
    name,
 | 
			
		||||
    type: ACTIVITYSTATES_TYPE,
 | 
			
		||||
    activities: {},
 | 
			
		||||
    location: null
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    appliesTo: (identifier) => {
 | 
			
		||||
      return identifier.key === ACTIVITYSTATES_KEY;
 | 
			
		||||
    },
 | 
			
		||||
    invoke: (identifier, object) => {
 | 
			
		||||
      if (!object || openmct.objects.isMissing(object)) {
 | 
			
		||||
        openmct.objects.save(activityStatesModel);
 | 
			
		||||
 | 
			
		||||
        return activityStatesModel;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return object;
 | 
			
		||||
    },
 | 
			
		||||
    priority: openmct.priority.HIGH
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default activityStatesInterceptor;
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
export const ACTIVITYSTATES_KEY = 'activity-states';
 | 
			
		||||
export const ACTIVITYSTATES_TYPE = 'activity-states';
 | 
			
		||||
 | 
			
		||||
export function createActivityStatesIdentifier(namespace = '') {
 | 
			
		||||
  return {
 | 
			
		||||
    key: ACTIVITYSTATES_KEY,
 | 
			
		||||
    namespace
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								src/plugins/activityStates/plugin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/plugins/activityStates/plugin.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2023, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT 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 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 activityStatesInterceptor from './activityStatesInterceptor.js';
 | 
			
		||||
import { createActivityStatesIdentifier } from './createActivityStatesIdentifier.js';
 | 
			
		||||
 | 
			
		||||
const ACTIVITYSTATES_DEFAULT_NAME = 'Activity States';
 | 
			
		||||
 | 
			
		||||
export default function ActivityStatesPlugin(
 | 
			
		||||
  name = ACTIVITYSTATES_DEFAULT_NAME,
 | 
			
		||||
  namespace = '',
 | 
			
		||||
  priority = undefined
 | 
			
		||||
) {
 | 
			
		||||
  return function install(openmct) {
 | 
			
		||||
    const identifier = createActivityStatesIdentifier(namespace);
 | 
			
		||||
 | 
			
		||||
    if (priority === undefined) {
 | 
			
		||||
      priority = openmct.priority.LOW;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    openmct.objects.addGetInterceptor(activityStatesInterceptor(openmct, identifier, name));
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										89
									
								
								src/plugins/activityStates/pluginSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/plugins/activityStates/pluginSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2023, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT 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 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 { createOpenMct, resetApplicationState } from 'utils/testing';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  ACTIVITYSTATES_KEY,
 | 
			
		||||
  createActivityStatesIdentifier
 | 
			
		||||
} from './createActivityStatesIdentifier.js';
 | 
			
		||||
 | 
			
		||||
const MISSING_NAME = `Missing: ${ACTIVITYSTATES_KEY}`;
 | 
			
		||||
const DEFAULT_NAME = 'Activity States';
 | 
			
		||||
const activityStatesIdentifier = createActivityStatesIdentifier();
 | 
			
		||||
 | 
			
		||||
describe('the plugin', () => {
 | 
			
		||||
  let openmct;
 | 
			
		||||
  let missingObj = {
 | 
			
		||||
    identifier: activityStatesIdentifier,
 | 
			
		||||
    type: 'unknown',
 | 
			
		||||
    name: MISSING_NAME
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  describe('with no arguments passed in', () => {
 | 
			
		||||
    beforeEach((done) => {
 | 
			
		||||
      openmct = createOpenMct();
 | 
			
		||||
      openmct.install(openmct.plugins.ActivityStates());
 | 
			
		||||
 | 
			
		||||
      openmct.on('start', done);
 | 
			
		||||
      openmct.startHeadless();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
      return resetApplicationState(openmct);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('when installed, adds "Activity States"', async () => {
 | 
			
		||||
      const activityStatesObject = await openmct.objects.get(activityStatesIdentifier);
 | 
			
		||||
      expect(activityStatesObject.name).toBe(DEFAULT_NAME);
 | 
			
		||||
      expect(activityStatesObject).toBeDefined();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('adds an interceptor that returns a "Activity States" model for', () => {
 | 
			
		||||
      let activityStatesObject;
 | 
			
		||||
      let mockNotFoundProvider;
 | 
			
		||||
      let activeProvider;
 | 
			
		||||
 | 
			
		||||
      beforeEach(async () => {
 | 
			
		||||
        mockNotFoundProvider = {
 | 
			
		||||
          get: () => Promise.reject(new Error('Not found')),
 | 
			
		||||
          create: () => Promise.resolve(missingObj),
 | 
			
		||||
          update: () => Promise.resolve(missingObj)
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        activeProvider = mockNotFoundProvider;
 | 
			
		||||
        spyOn(openmct.objects, 'getProvider').and.returnValue(activeProvider);
 | 
			
		||||
        activityStatesObject = await openmct.objects.get(activityStatesIdentifier);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('missing objects', () => {
 | 
			
		||||
        let idsMatch = openmct.objects.areIdsEqual(
 | 
			
		||||
          activityStatesObject.identifier,
 | 
			
		||||
          activityStatesIdentifier
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        expect(activityStatesObject).toBeDefined();
 | 
			
		||||
        expect(idsMatch).toBeTrue();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -488,6 +488,7 @@ export default {
 | 
			
		||||
            },
 | 
			
		||||
            start: rawActivity.start,
 | 
			
		||||
            end: rawActivity.end,
 | 
			
		||||
            description: rawActivity.description,
 | 
			
		||||
            row: currentRow,
 | 
			
		||||
            textLines: textLines,
 | 
			
		||||
            textStart: textStart,
 | 
			
		||||
@@ -496,7 +497,8 @@ export default {
 | 
			
		||||
            rectStart: rectX1,
 | 
			
		||||
            rectEnd: showTextInsideRect ? rectX2 : textStart + textWidth,
 | 
			
		||||
            rectWidth: rectWidth,
 | 
			
		||||
            clipPathId: this.getClipPathId(groupName, rawActivity, currentRow)
 | 
			
		||||
            clipPathId: this.getClipPathId(groupName, rawActivity, currentRow),
 | 
			
		||||
            id: rawActivity.id
 | 
			
		||||
          };
 | 
			
		||||
          activitiesByRow[currentRow].push(activity);
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -20,21 +20,39 @@
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="c-inspector__properties c-inspect-properties">
 | 
			
		||||
    <plan-activity-view
 | 
			
		||||
  <div>
 | 
			
		||||
    <plan-activity-time-view
 | 
			
		||||
      v-for="activity in activities"
 | 
			
		||||
      :key="activity.id"
 | 
			
		||||
      :key="activity.key"
 | 
			
		||||
      class="c-inspector__properties c-inspect-properties"
 | 
			
		||||
      :activity="activity"
 | 
			
		||||
      :heading="heading"
 | 
			
		||||
    />
 | 
			
		||||
    <plan-activity-properties-view
 | 
			
		||||
      v-for="activity in activities"
 | 
			
		||||
      :key="activity.key"
 | 
			
		||||
      :heading="'Properties'"
 | 
			
		||||
      class="c-inspector__properties c-inspect-properties"
 | 
			
		||||
      :activity="activity"
 | 
			
		||||
    ></plan-activity-properties-view>
 | 
			
		||||
    <plan-activity-status-view
 | 
			
		||||
      v-if="canPersistState"
 | 
			
		||||
      :key="activities[0].key"
 | 
			
		||||
      class="c-inspector__properties c-inspect-properties"
 | 
			
		||||
      :activity="activities[0]"
 | 
			
		||||
      :execution-state="persistedActivityStates[activities[0].id]"
 | 
			
		||||
      :heading="'Activity Status'"
 | 
			
		||||
      @update-activity-state="persistedActivityState"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { getPreciseDuration } from 'utils/duration';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
 | 
			
		||||
import PlanActivityView from './PlanActivityView.vue';
 | 
			
		||||
import PlanActivityPropertiesView from './PlanActivityPropertiesView.vue';
 | 
			
		||||
import PlanActivityStatusView from './PlanActivityStatusView.vue';
 | 
			
		||||
import PlanActivityTimeView from './PlanActivityTimeView.vue';
 | 
			
		||||
 | 
			
		||||
const propertyLabels = {
 | 
			
		||||
  start: 'Start DateTime',
 | 
			
		||||
@@ -44,23 +62,34 @@ const propertyLabels = {
 | 
			
		||||
  latestEnd: 'Latest End',
 | 
			
		||||
  gap: 'Gap',
 | 
			
		||||
  overlap: 'Overlap',
 | 
			
		||||
  totalTime: 'Total Time'
 | 
			
		||||
  totalTime: 'Total Time',
 | 
			
		||||
  description: 'Description'
 | 
			
		||||
};
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
    PlanActivityView
 | 
			
		||||
    PlanActivityTimeView,
 | 
			
		||||
    PlanActivityPropertiesView,
 | 
			
		||||
    PlanActivityStatusView
 | 
			
		||||
  },
 | 
			
		||||
  inject: ['openmct', 'selection'],
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      name: '',
 | 
			
		||||
      activities: [],
 | 
			
		||||
      selectedActivities: [],
 | 
			
		||||
      persistedActivityStates: {},
 | 
			
		||||
      heading: ''
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    canPersistState() {
 | 
			
		||||
      return this.selectedActivities.length === 1 && this.activities[0].id;
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.setFormatters();
 | 
			
		||||
    this.getPlanData(this.selection);
 | 
			
		||||
    this.getActivityStates();
 | 
			
		||||
    this.getActivities();
 | 
			
		||||
    this.openmct.selection.on('change', this.updateSelection);
 | 
			
		||||
    this.openmct.time.on('timeSystem', this.setFormatters);
 | 
			
		||||
@@ -68,8 +97,23 @@ export default {
 | 
			
		||||
  beforeUnmount() {
 | 
			
		||||
    this.openmct.selection.off('change', this.updateSelection);
 | 
			
		||||
    this.openmct.time.off('timeSystem', this.setFormatters);
 | 
			
		||||
    if (this.stopObservingActivityStatesObject) {
 | 
			
		||||
      this.stopObservingActivityStatesObject();
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    async getActivityStates() {
 | 
			
		||||
      this.activityStatesObject = await this.openmct.objects.get('activity-states');
 | 
			
		||||
      this.setActivityStates();
 | 
			
		||||
      this.stopObservingActivityStatesObject = this.openmct.objects.observe(
 | 
			
		||||
        this.activityStatesObject,
 | 
			
		||||
        '*',
 | 
			
		||||
        this.setActivityStates
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
    setActivityStates() {
 | 
			
		||||
      this.persistedActivityStates = { ...this.activityStatesObject.activities };
 | 
			
		||||
    },
 | 
			
		||||
    setFormatters() {
 | 
			
		||||
      let timeSystem = this.openmct.time.timeSystem();
 | 
			
		||||
      this.timeFormatter = this.openmct.telemetry.getValueFormatter({
 | 
			
		||||
@@ -86,6 +130,7 @@ export default {
 | 
			
		||||
        if (selectionItem[0].context.type === 'activity') {
 | 
			
		||||
          const activity = selectionItem[0].context.activity;
 | 
			
		||||
          if (activity) {
 | 
			
		||||
            activity.key = activity.id ?? activity.name;
 | 
			
		||||
            this.selectedActivities.push(activity);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
@@ -104,20 +149,31 @@ export default {
 | 
			
		||||
      this.activities.splice(0);
 | 
			
		||||
      this.selectedActivities.forEach((selectedActivity, index) => {
 | 
			
		||||
        const activity = {
 | 
			
		||||
          id: uuid(),
 | 
			
		||||
          start: {
 | 
			
		||||
            label: propertyLabels.start,
 | 
			
		||||
            value: this.formatTime(selectedActivity.start)
 | 
			
		||||
          },
 | 
			
		||||
          end: {
 | 
			
		||||
            label: propertyLabels.end,
 | 
			
		||||
            value: this.formatTime(selectedActivity.end)
 | 
			
		||||
          },
 | 
			
		||||
          duration: {
 | 
			
		||||
            label: propertyLabels.duration,
 | 
			
		||||
            value: this.formatDuration(selectedActivity.end - selectedActivity.start)
 | 
			
		||||
          id: selectedActivity.id,
 | 
			
		||||
          key: selectedActivity.key,
 | 
			
		||||
          timeProperties: {
 | 
			
		||||
            start: {
 | 
			
		||||
              label: propertyLabels.start,
 | 
			
		||||
              value: this.formatTime(selectedActivity.start)
 | 
			
		||||
            },
 | 
			
		||||
            end: {
 | 
			
		||||
              label: propertyLabels.end,
 | 
			
		||||
              value: this.formatTime(selectedActivity.end)
 | 
			
		||||
            },
 | 
			
		||||
            duration: {
 | 
			
		||||
              label: propertyLabels.duration,
 | 
			
		||||
              value: this.formatDuration(selectedActivity.end - selectedActivity.start)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
        if (selectedActivity.description) {
 | 
			
		||||
          activity.metadata = {
 | 
			
		||||
            description: {
 | 
			
		||||
              label: propertyLabels.description,
 | 
			
		||||
              value: selectedActivity.description
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
        this.activities[index] = activity;
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
@@ -141,6 +197,8 @@ export default {
 | 
			
		||||
      let latestEnd;
 | 
			
		||||
      let gap;
 | 
			
		||||
      let overlap;
 | 
			
		||||
      let id;
 | 
			
		||||
      let key;
 | 
			
		||||
 | 
			
		||||
      //Sort by start time
 | 
			
		||||
      let selectedActivities = this.selectedActivities.sort(this.sortFn);
 | 
			
		||||
@@ -159,6 +217,8 @@ export default {
 | 
			
		||||
          earliestStart = Math.min(earliestStart, selectedActivity.start);
 | 
			
		||||
          latestEnd = Math.max(latestEnd, selectedActivity.end);
 | 
			
		||||
        } else {
 | 
			
		||||
          id = selectedActivity.id;
 | 
			
		||||
          key = selectedActivity.id ?? selectedActivity.name;
 | 
			
		||||
          earliestStart = selectedActivity.start;
 | 
			
		||||
          latestEnd = selectedActivity.end;
 | 
			
		||||
        }
 | 
			
		||||
@@ -166,30 +226,33 @@ export default {
 | 
			
		||||
      let totalTime = latestEnd - earliestStart;
 | 
			
		||||
 | 
			
		||||
      const activity = {
 | 
			
		||||
        id: uuid(),
 | 
			
		||||
        earliestStart: {
 | 
			
		||||
          label: propertyLabels.earliestStart,
 | 
			
		||||
          value: this.formatTime(earliestStart)
 | 
			
		||||
        },
 | 
			
		||||
        latestEnd: {
 | 
			
		||||
          label: propertyLabels.latestEnd,
 | 
			
		||||
          value: this.formatTime(latestEnd)
 | 
			
		||||
        id,
 | 
			
		||||
        key,
 | 
			
		||||
        timeProperties: {
 | 
			
		||||
          earliestStart: {
 | 
			
		||||
            label: propertyLabels.earliestStart,
 | 
			
		||||
            value: this.formatTime(earliestStart)
 | 
			
		||||
          },
 | 
			
		||||
          latestEnd: {
 | 
			
		||||
            label: propertyLabels.latestEnd,
 | 
			
		||||
            value: this.formatTime(latestEnd)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      if (gap) {
 | 
			
		||||
        activity.gap = {
 | 
			
		||||
        activity.timeProperties.gap = {
 | 
			
		||||
          label: propertyLabels.gap,
 | 
			
		||||
          value: this.formatDuration(gap)
 | 
			
		||||
        };
 | 
			
		||||
      } else if (overlap) {
 | 
			
		||||
        activity.overlap = {
 | 
			
		||||
        activity.timeProperties.overlap = {
 | 
			
		||||
          label: propertyLabels.overlap,
 | 
			
		||||
          value: this.formatDuration(overlap)
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      activity.totalTime = {
 | 
			
		||||
      activity.timeProperties.totalTime = {
 | 
			
		||||
        label: propertyLabels.totalTime,
 | 
			
		||||
        value: this.formatDuration(totalTime)
 | 
			
		||||
      };
 | 
			
		||||
@@ -201,6 +264,11 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    formatTime(time) {
 | 
			
		||||
      return this.timeFormatter.format(time);
 | 
			
		||||
    },
 | 
			
		||||
    persistedActivityState(data) {
 | 
			
		||||
      const { key, executionState } = data;
 | 
			
		||||
      const activitiesPath = `activities.${key}`;
 | 
			
		||||
      this.openmct.objects.mutate(this.activityStatesObject, activitiesPath, executionState);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,82 @@
 | 
			
		||||
<!--
 | 
			
		||||
 Open MCT, Copyright (c) 2014-2023, United States Government
 | 
			
		||||
 as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 Administration. All rights reserved.
 | 
			
		||||
 | 
			
		||||
 Open MCT 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 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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <div v-if="properties.length" class="u-contents">
 | 
			
		||||
      <div class="c-inspect-properties__header">{{ heading }}</div>
 | 
			
		||||
      <ul v-for="property in properties" :key="property.id" class="c-inspect-properties__section">
 | 
			
		||||
        <activity-property :label="property.label" :value="property.value" />
 | 
			
		||||
      </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import ActivityProperty from './ActivityProperty.vue';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
    ActivityProperty
 | 
			
		||||
  },
 | 
			
		||||
  props: {
 | 
			
		||||
    activity: {
 | 
			
		||||
      type: Object,
 | 
			
		||||
      required: true
 | 
			
		||||
    },
 | 
			
		||||
    heading: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      required: true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      properties: []
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.setProperties();
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    setProperties() {
 | 
			
		||||
      if (!this.activity.metadata) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      Object.keys(this.activity.metadata).forEach((key) => {
 | 
			
		||||
        if (this.activity.metadata[key].label) {
 | 
			
		||||
          const label = this.activity.metadata[key].label;
 | 
			
		||||
          const value = String(this.activity.metadata[key].value);
 | 
			
		||||
          const id = this.activity.id;
 | 
			
		||||
 | 
			
		||||
          this.properties[this.properties.length] = {
 | 
			
		||||
            id,
 | 
			
		||||
            label,
 | 
			
		||||
            value
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      console.log(this.activity.metadata, this.properties);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										114
									
								
								src/plugins/plan/inspector/components/PlanActivityStatusView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/plugins/plan/inspector/components/PlanActivityStatusView.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
<!--
 | 
			
		||||
 Open MCT, Copyright (c) 2014-2023, United States Government
 | 
			
		||||
 as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 Administration. All rights reserved.
 | 
			
		||||
 | 
			
		||||
 Open MCT 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 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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <div class="u-contents">
 | 
			
		||||
      <div class="c-inspect-properties__header">{{ heading }}</div>
 | 
			
		||||
      <form name="activityStatus">
 | 
			
		||||
        <select v-model="currentStatusKey" name="setActivityStatus" @change="changeActivityStatus">
 | 
			
		||||
          <option v-for="status in activityStates" :key="status.key" :value="status.label">
 | 
			
		||||
            {{ status.label }}
 | 
			
		||||
          </option>
 | 
			
		||||
        </select>
 | 
			
		||||
      </form>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
const activityStates = [
 | 
			
		||||
  {
 | 
			
		||||
    key: '',
 | 
			
		||||
    label: '- Select Activity Status -'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: 'active',
 | 
			
		||||
    label: 'In progress'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: 'completed',
 | 
			
		||||
    label: 'Completed'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: 'aborted',
 | 
			
		||||
    label: 'Aborted'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: 'cancelled',
 | 
			
		||||
    label: 'Cancelled'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: 'notStarted',
 | 
			
		||||
    label: 'Not started'
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    activity: {
 | 
			
		||||
      type: Object,
 | 
			
		||||
      required: true
 | 
			
		||||
    },
 | 
			
		||||
    executionState: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default() {
 | 
			
		||||
        return '';
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    heading: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      required: true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  emits: ['updateActivityState'],
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      activityStates: activityStates,
 | 
			
		||||
      currentStatusKey: activityStates[0].key
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    executionState() {
 | 
			
		||||
      this.setActivityStatus();
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.setActivityStatus();
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    setActivityStatus() {
 | 
			
		||||
      this.currentStatusKey = this.executionState;
 | 
			
		||||
    },
 | 
			
		||||
    changeActivityStatus() {
 | 
			
		||||
      if (this.currentStatusKey === '') {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      this.activity.executionState = this.currentStatusKey;
 | 
			
		||||
      this.$emit('updateActivityState', {
 | 
			
		||||
        key: this.activity.id,
 | 
			
		||||
        executionState: this.currentStatusKey
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
@@ -21,23 +21,23 @@
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div v-if="timeProperties.length" class="u-contents">
 | 
			
		||||
    <div class="c-inspect-properties__header">
 | 
			
		||||
      {{ heading }}
 | 
			
		||||
  <div>
 | 
			
		||||
    <div v-if="timeProperties.length" class="u-contents">
 | 
			
		||||
      <div class="c-inspect-properties__header">
 | 
			
		||||
        {{ heading }}
 | 
			
		||||
      </div>
 | 
			
		||||
      <ul
 | 
			
		||||
        v-for="timeProperty in timeProperties"
 | 
			
		||||
        :key="timeProperty.id"
 | 
			
		||||
        class="c-inspect-properties__section"
 | 
			
		||||
      >
 | 
			
		||||
        <activity-property :label="timeProperty.label" :value="timeProperty.value" />
 | 
			
		||||
      </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
    <ul
 | 
			
		||||
      v-for="timeProperty in timeProperties"
 | 
			
		||||
      :key="timeProperty.id"
 | 
			
		||||
      class="c-inspect-properties__section"
 | 
			
		||||
    >
 | 
			
		||||
      <activity-property :label="timeProperty.label" :value="timeProperty.value" />
 | 
			
		||||
    </ul>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
 | 
			
		||||
import ActivityProperty from './ActivityProperty.vue';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
@@ -64,13 +64,14 @@ export default {
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    setProperties() {
 | 
			
		||||
      Object.keys(this.activity).forEach((key) => {
 | 
			
		||||
        if (this.activity[key].label) {
 | 
			
		||||
          const label = this.activity[key].label;
 | 
			
		||||
          const value = String(this.activity[key].value);
 | 
			
		||||
      Object.keys(this.activity.timeProperties).forEach((key) => {
 | 
			
		||||
        if (this.activity.timeProperties[key].label) {
 | 
			
		||||
          const label = this.activity.timeProperties[key].label;
 | 
			
		||||
          const value = String(this.activity.timeProperties[key].value);
 | 
			
		||||
          const id = this.activity.id;
 | 
			
		||||
 | 
			
		||||
          this.timeProperties[this.timeProperties.length] = {
 | 
			
		||||
            id: uuid(),
 | 
			
		||||
            id,
 | 
			
		||||
            label,
 | 
			
		||||
            value
 | 
			
		||||
          };
 | 
			
		||||
@@ -45,6 +45,10 @@ export function getValidatedData(domainObject) {
 | 
			
		||||
          groupActivity.end = activity[sourceMap.end];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (sourceMap.id) {
 | 
			
		||||
          groupActivity.id = activity[sourceMap.id];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!mappedJson[groupIdKey]) {
 | 
			
		||||
          mappedJson[groupIdKey] = [];
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ import ExampleUser from '../../example/exampleUser/plugin.js';
 | 
			
		||||
import ExampleFaultSource from '../../example/faultManagement/exampleFaultSource.js';
 | 
			
		||||
import GeneratorPlugin from '../../example/generator/plugin.js';
 | 
			
		||||
import ExampleImagery from '../../example/imagery/plugin.js';
 | 
			
		||||
import ActivityStatesPlugin from './activityStates/plugin.js';
 | 
			
		||||
import AutoflowPlugin from './autoflow/AutoflowTabularPlugin.js';
 | 
			
		||||
import BarChartPlugin from './charts/bar/plugin.js';
 | 
			
		||||
import ScatterPlotPlugin from './charts/scatter/plugin.js';
 | 
			
		||||
@@ -101,6 +102,7 @@ plugins.LocalTimeSystem = LocalTimeSystem;
 | 
			
		||||
plugins.RemoteClock = RemoteClock;
 | 
			
		||||
 | 
			
		||||
plugins.MyItems = MyItems;
 | 
			
		||||
plugins.ActivityStates = ActivityStatesPlugin;
 | 
			
		||||
 | 
			
		||||
plugins.StaticRootPlugin = StaticRootPlugin;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										112
									
								
								src/plugins/timelist/CompactView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/plugins/timelist/CompactView.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
			
		||||
<!--
 | 
			
		||||
 Open MCT, Copyright (c) 2014-2024, United States Government
 | 
			
		||||
 as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 Administration. All rights reserved.
 | 
			
		||||
 | 
			
		||||
 Open MCT 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 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.
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <compact-view-item
 | 
			
		||||
    v-for="item in sortedItems"
 | 
			
		||||
    :key="item.key"
 | 
			
		||||
    :item="item"
 | 
			
		||||
    :item-properties="itemProperties"
 | 
			
		||||
  >
 | 
			
		||||
  </compact-view-item>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import CompactViewItem from './CompactViewItem.vue';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: { CompactViewItem },
 | 
			
		||||
  inject: ['domainObject', 'openmct'],
 | 
			
		||||
  props: {
 | 
			
		||||
    headerItems: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
      required: true
 | 
			
		||||
    },
 | 
			
		||||
    items: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
      required: true
 | 
			
		||||
    },
 | 
			
		||||
    defaultSort: {
 | 
			
		||||
      type: Object,
 | 
			
		||||
      default() {
 | 
			
		||||
        return {
 | 
			
		||||
          property: '',
 | 
			
		||||
          defaultDirection: true
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    let sortBy = this.defaultSort.property;
 | 
			
		||||
    let ascending = this.defaultSort.defaultDirection;
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      sortBy,
 | 
			
		||||
      ascending
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    sortedItems() {
 | 
			
		||||
      let sortedItems = _.sortBy(this.items, this.sortBy);
 | 
			
		||||
      if (!this.ascending) {
 | 
			
		||||
        sortedItems = sortedItems.reverse();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return sortedItems;
 | 
			
		||||
    },
 | 
			
		||||
    itemProperties() {
 | 
			
		||||
      return this.headerItems.map((headerItem) => {
 | 
			
		||||
        return {
 | 
			
		||||
          property: headerItem.property,
 | 
			
		||||
          format: headerItem.format
 | 
			
		||||
        };
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    defaultSort: {
 | 
			
		||||
      handler() {
 | 
			
		||||
        this.setSort();
 | 
			
		||||
      },
 | 
			
		||||
      deep: true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    setSort() {
 | 
			
		||||
      this.sortBy = this.defaultSort.property;
 | 
			
		||||
      this.ascending = this.defaultSort.defaultDirection;
 | 
			
		||||
    },
 | 
			
		||||
    sort(data) {
 | 
			
		||||
      const property = data.property;
 | 
			
		||||
      const direction = data.direction;
 | 
			
		||||
 | 
			
		||||
      if (this.sortBy === property) {
 | 
			
		||||
        this.ascending = !this.ascending;
 | 
			
		||||
      } else {
 | 
			
		||||
        this.sortBy = property;
 | 
			
		||||
        this.ascending = direction;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										134
									
								
								src/plugins/timelist/CompactViewItem.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								src/plugins/timelist/CompactViewItem.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,134 @@
 | 
			
		||||
<!--
 | 
			
		||||
 Open MCT, Copyright (c) 2014-2024, United States Government
 | 
			
		||||
 as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 Administration. All rights reserved.
 | 
			
		||||
 | 
			
		||||
 Open MCT 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 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.
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div :class="listItemClass">
 | 
			
		||||
    <div class="c-tli__activity-color">
 | 
			
		||||
      <div class="c-tli__activity-color-swatch" :style="styleClass"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="c-tli__title-and-bounds">
 | 
			
		||||
      <div class="c-tli__title">{{ formattedItemValue.title }}</div>
 | 
			
		||||
      <div class="c-tli__bounds">
 | 
			
		||||
        <span class="c-tli__duration">{{ formattedItemValue.duration }}</span>
 | 
			
		||||
        <span class="c-tli__start-time">{{ formattedItemValue.start }}</span>
 | 
			
		||||
        <span class="c-tli__end-time">{{ formattedItemValue.end }}</span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="c-tli__progress-pie">
 | 
			
		||||
      <svg viewBox="0 0 100 100">
 | 
			
		||||
        <circle class="c-svg-progress__bg" r="50" cx="50" cy="50"></circle>
 | 
			
		||||
        <path id="svg-progress-path" class="c-svg-progress__progress"></path>
 | 
			
		||||
        <circle
 | 
			
		||||
          class="c-svg-progress__ticks"
 | 
			
		||||
          r="40"
 | 
			
		||||
          cx="50"
 | 
			
		||||
          cy="50"
 | 
			
		||||
          stroke-dasharray="3 7.472"
 | 
			
		||||
        ></circle>
 | 
			
		||||
      </svg>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="c-tli__time-hero">
 | 
			
		||||
      <div class="c-tli__time-hero-context">{{ formattedItemValue.label }}</div>
 | 
			
		||||
      <div class="c-tli__time-hero-time --is-countdown">{{ formattedItemValue.countdown }}</div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
const CURRENT_CSS_SUFFIX = '--is-current';
 | 
			
		||||
const PAST_CSS_SUFFIX = '--is-past';
 | 
			
		||||
const FUTURE_CSS_SUFFIX = '--is-future';
 | 
			
		||||
 | 
			
		||||
const ITEM_COLORS = {
 | 
			
		||||
  [CURRENT_CSS_SUFFIX]: '#ffcc00',
 | 
			
		||||
  [PAST_CSS_SUFFIX]: '#0088ff',
 | 
			
		||||
  [FUTURE_CSS_SUFFIX]: '#7300ff'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ITEM_STATES = {
 | 
			
		||||
  notStarted: 'not-started',
 | 
			
		||||
  inProgress: 'in-progress',
 | 
			
		||||
  completed: 'completed',
 | 
			
		||||
  aborted: 'aborted',
 | 
			
		||||
  skipped: 'skipped',
 | 
			
		||||
  incomplete: 'incomplete',
 | 
			
		||||
  overdue: 'overdue',
 | 
			
		||||
  runningLong: 'running-long'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  inject: ['openmct'],
 | 
			
		||||
  props: {
 | 
			
		||||
    item: {
 | 
			
		||||
      type: Object,
 | 
			
		||||
      required: true
 | 
			
		||||
    },
 | 
			
		||||
    itemProperties: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
      required: true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      itemState: ITEM_STATES.notStarted
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    styleClass() {
 | 
			
		||||
      return { backgroundColor: ITEM_COLORS[this.item.cssClass] };
 | 
			
		||||
    },
 | 
			
		||||
    listItemClass() {
 | 
			
		||||
      const timeRelationClass = this.item.cssClass;
 | 
			
		||||
      const itemStateClass = `--is-${this.itemState}`;
 | 
			
		||||
      return `c-tli ${timeRelationClass} ${itemStateClass}`;
 | 
			
		||||
    },
 | 
			
		||||
    formattedItemValue() {
 | 
			
		||||
      let itemValue = {
 | 
			
		||||
        title: this.item.name
 | 
			
		||||
      };
 | 
			
		||||
      this.itemProperties.forEach((itemProperty) => {
 | 
			
		||||
        let value = this.item[itemProperty.property];
 | 
			
		||||
        let formattedValue;
 | 
			
		||||
        if (itemProperty.format) {
 | 
			
		||||
          formattedValue = itemProperty.format(
 | 
			
		||||
            value,
 | 
			
		||||
            this.item,
 | 
			
		||||
            itemProperty.property,
 | 
			
		||||
            this.openmct,
 | 
			
		||||
            {
 | 
			
		||||
              skipPrefix: true
 | 
			
		||||
            }
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
        itemValue[itemProperty.property] = formattedValue;
 | 
			
		||||
 | 
			
		||||
        let label;
 | 
			
		||||
        if (itemProperty.property === 'countdown') {
 | 
			
		||||
          label = value > 0 ? 'Starts' : 'Ended';
 | 
			
		||||
        }
 | 
			
		||||
        itemValue.label = itemValue.label ?? label;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return itemValue;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
@@ -21,12 +21,20 @@
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div ref="timelistHolder" class="c-timelist">
 | 
			
		||||
  <div ref="timelistHolder" :class="listTypeClass">
 | 
			
		||||
    <compact-view
 | 
			
		||||
      v-if="isCompact"
 | 
			
		||||
      :items="planActivities"
 | 
			
		||||
      :header-items="headerItems"
 | 
			
		||||
      :default-sort="defaultSort"
 | 
			
		||||
    ></compact-view>
 | 
			
		||||
    <list-view
 | 
			
		||||
      v-else
 | 
			
		||||
      :items="planActivities"
 | 
			
		||||
      :header-items="headerItems"
 | 
			
		||||
      :default-sort="defaultSort"
 | 
			
		||||
      class="sticky"
 | 
			
		||||
      @item-selection-changed="setSelectionForActivity"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -39,6 +47,7 @@ import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
 | 
			
		||||
import ListView from '../../ui/components/List/ListView.vue';
 | 
			
		||||
import { getPreciseDuration } from '../../utils/duration.js';
 | 
			
		||||
import { getValidatedData, getValidatedGroups } from '../plan/util.js';
 | 
			
		||||
import CompactView from './CompactView.vue';
 | 
			
		||||
import { SORT_ORDER_OPTIONS } from './constants.js';
 | 
			
		||||
 | 
			
		||||
const SCROLL_TIMEOUT = 10000;
 | 
			
		||||
@@ -72,17 +81,22 @@ const headerItems = [
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    defaultDirection: false,
 | 
			
		||||
    property: 'duration',
 | 
			
		||||
    property: 'countdown',
 | 
			
		||||
    name: 'Time To/From',
 | 
			
		||||
    format: function (value) {
 | 
			
		||||
    format: function (value, object, key, openmct, options = {}) {
 | 
			
		||||
      let result;
 | 
			
		||||
      if (value < 0) {
 | 
			
		||||
        result = `+${getPreciseDuration(Math.abs(value), {
 | 
			
		||||
        const prefix = options.skipPrefix ? '' : '+';
 | 
			
		||||
        result = `${prefix}${getPreciseDuration(Math.abs(value), {
 | 
			
		||||
          excludeMilliSeconds: true,
 | 
			
		||||
          useDayFormat: true
 | 
			
		||||
        })}`;
 | 
			
		||||
      } else if (value > 0) {
 | 
			
		||||
        result = `-${getPreciseDuration(value, { excludeMilliSeconds: true, useDayFormat: true })}`;
 | 
			
		||||
        const prefix = options.skipPrefix ? '' : '+';
 | 
			
		||||
        result = `${prefix}${getPreciseDuration(value, {
 | 
			
		||||
          excludeMilliSeconds: true,
 | 
			
		||||
          useDayFormat: true
 | 
			
		||||
        })}`;
 | 
			
		||||
      } else {
 | 
			
		||||
        result = 'Now';
 | 
			
		||||
      }
 | 
			
		||||
@@ -90,6 +104,14 @@ const headerItems = [
 | 
			
		||||
      return result;
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    defaultDirection: false,
 | 
			
		||||
    property: 'duration',
 | 
			
		||||
    name: 'Duration',
 | 
			
		||||
    format: function (value, object, key, openmct) {
 | 
			
		||||
      return `${getPreciseDuration(value, { excludeMilliSeconds: true, useDayFormat: true })}`;
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    defaultDirection: true,
 | 
			
		||||
    property: 'name',
 | 
			
		||||
@@ -104,6 +126,7 @@ const defaultSort = {
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
    CompactView,
 | 
			
		||||
    ListView
 | 
			
		||||
  },
 | 
			
		||||
  inject: ['openmct', 'domainObject', 'path', 'composition'],
 | 
			
		||||
@@ -114,9 +137,18 @@ export default {
 | 
			
		||||
      height: 0,
 | 
			
		||||
      planActivities: [],
 | 
			
		||||
      headerItems: headerItems,
 | 
			
		||||
      defaultSort: defaultSort
 | 
			
		||||
      defaultSort: defaultSort,
 | 
			
		||||
      isCompact: false
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    listTypeClass() {
 | 
			
		||||
      if (this.isCompact) {
 | 
			
		||||
        return 'c-timelist c-timelist--large';
 | 
			
		||||
      }
 | 
			
		||||
      return 'c-timelist';
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.isEditing = this.openmct.editor.isEditing();
 | 
			
		||||
    this.updateTimestamp = _.throttle(this.updateTimestamp, 1000);
 | 
			
		||||
@@ -208,15 +240,14 @@ export default {
 | 
			
		||||
      this.setViewFromConfig(mutatedObject.configuration);
 | 
			
		||||
    },
 | 
			
		||||
    setViewFromConfig(configuration) {
 | 
			
		||||
      this.filterValue = configuration.filter;
 | 
			
		||||
      if (this.isEditing) {
 | 
			
		||||
        this.filterValue = configuration.filter;
 | 
			
		||||
        this.hideAll = false;
 | 
			
		||||
        this.listActivities();
 | 
			
		||||
      } else {
 | 
			
		||||
        this.filterValue = configuration.filter;
 | 
			
		||||
        this.setSort();
 | 
			
		||||
        this.listActivities();
 | 
			
		||||
        this.isCompact = configuration.isCompact;
 | 
			
		||||
      }
 | 
			
		||||
      this.listActivities();
 | 
			
		||||
    },
 | 
			
		||||
    updateTimestamp(timestamp) {
 | 
			
		||||
      //The clock never stops ticking
 | 
			
		||||
@@ -379,11 +410,13 @@ export default {
 | 
			
		||||
        activity.key = uuid();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      activity.duration = activity.end - activity.start;
 | 
			
		||||
 | 
			
		||||
      if (activity.start < this.timestamp) {
 | 
			
		||||
        //if the activity start time has passed, display the time to the end of the activity
 | 
			
		||||
        activity.duration = activity.end - this.timestamp;
 | 
			
		||||
        activity.countdown = activity.end - this.timestamp;
 | 
			
		||||
      } else {
 | 
			
		||||
        activity.duration = activity.start - this.timestamp;
 | 
			
		||||
        activity.countdown = activity.start - this.timestamp;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return activity;
 | 
			
		||||
@@ -426,7 +459,7 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    setScrollTop() {
 | 
			
		||||
      //The view isn't ready yet
 | 
			
		||||
      if (!this.$el.parentElement) {
 | 
			
		||||
      if (!this.$el.parentElement || this.isCompact) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@@ -516,6 +549,29 @@ export default {
 | 
			
		||||
    setEditState(isEditing) {
 | 
			
		||||
      this.isEditing = isEditing;
 | 
			
		||||
      this.setViewFromConfig(this.domainObject.configuration);
 | 
			
		||||
    },
 | 
			
		||||
    setSelectionForActivity(activity, element) {
 | 
			
		||||
      const multiSelect = false;
 | 
			
		||||
 | 
			
		||||
      this.openmct.selection.select(
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
            element: element,
 | 
			
		||||
            context: {
 | 
			
		||||
              type: 'activity',
 | 
			
		||||
              activity: activity
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            element: this.openmct.layout.$refs.browseObject.$el,
 | 
			
		||||
            context: {
 | 
			
		||||
              item: this.domainObject,
 | 
			
		||||
              supportsMultiSelect: false
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        multiSelect
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,15 @@
 | 
			
		||||
            These settings don't affect the view while editing, but will be applied after editing is
 | 
			
		||||
            finished.
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="c-inspect-properties__label" title="Compact view.">Compact View</div>
 | 
			
		||||
          <div v-if="canEdit" class="c-inspect-properties__value">
 | 
			
		||||
            <input v-model="isCompact" type="checkbox" @change="updateCompactView()" />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div v-else class="c-inspect-properties__value">
 | 
			
		||||
            {{ isCompact ? 'Enabled' : 'Disabled' }}
 | 
			
		||||
          </div>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li class="c-inspect-properties__row">
 | 
			
		||||
          <div class="c-inspect-properties__label" title="Sort order of the timelist.">
 | 
			
		||||
            Sort Order
 | 
			
		||||
          </div>
 | 
			
		||||
@@ -89,7 +98,8 @@ export default {
 | 
			
		||||
      sortOrderIndex: this.domainObject.configuration.sortOrderIndex,
 | 
			
		||||
      sortOrderOptions: SORT_ORDER_OPTIONS,
 | 
			
		||||
      eventTypes: EVENT_TYPES,
 | 
			
		||||
      isEditing: this.openmct.editor.isEditing()
 | 
			
		||||
      isEditing: this.openmct.editor.isEditing(),
 | 
			
		||||
      isCompact: this.domainObject.configuration.isCompact
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
@@ -117,6 +127,9 @@ export default {
 | 
			
		||||
      const key = data.property;
 | 
			
		||||
      const value = data.value;
 | 
			
		||||
      this.updateProperty(key, value);
 | 
			
		||||
    },
 | 
			
		||||
    updateCompactView() {
 | 
			
		||||
      this.updateProperty('isCompact', this.isCompact);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,9 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
$c: #ccc;
 | 
			
		||||
$cr: 3px;
 | 
			
		||||
$interiorMargin: 5px;
 | 
			
		||||
 | 
			
		||||
.c-timelist {
 | 
			
		||||
  & .nowMarker.hasCurrent {
 | 
			
		||||
@@ -57,3 +60,97 @@
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-timelist--large {
 | 
			
		||||
  $textSm: 0.8em;
 | 
			
		||||
  $textLg: 1.3em;
 | 
			
		||||
 | 
			
		||||
  .c-tli {
 | 
			
		||||
    background: #444;
 | 
			
		||||
    border-radius: $cr;
 | 
			
		||||
    display: grid;
 | 
			
		||||
    padding: $interiorMargin;
 | 
			
		||||
    grid-template-columns: min-content 2fr 40px 1fr;
 | 
			
		||||
    grid-column-gap: $interiorMargin;
 | 
			
		||||
 | 
			
		||||
    &__activity-color {
 | 
			
		||||
      align-items: start;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      padding-top: 3px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__activity-color-swatch {
 | 
			
		||||
      $d: 16px;
 | 
			
		||||
      border-radius: 50%;
 | 
			
		||||
      width: $d;
 | 
			
		||||
      height: $d;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__bounds {
 | 
			
		||||
      font-size: $textSm;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__title {
 | 
			
		||||
      font-size: $textLg;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    &__time-hero {
 | 
			
		||||
      //background: rgba(deeppink, 0.2);
 | 
			
		||||
      flex-wrap: wrap;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: end;
 | 
			
		||||
 | 
			
		||||
      > * + * {
 | 
			
		||||
        margin-left: $interiorMargin;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__time-hero-context {
 | 
			
		||||
      font-size: $textSm;
 | 
			
		||||
      text-transform: uppercase;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__time-hero-time {
 | 
			
		||||
      font-size: $textLg;
 | 
			
		||||
 | 
			
		||||
      &:before {
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        margin-right: 3px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      &.--is-countdown {
 | 
			
		||||
        &:before {
 | 
			
		||||
          content: '-'; // TODO: replace with symbolsfont dash
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      &.--is-countup {
 | 
			
		||||
        &:before {
 | 
			
		||||
          content: '+'; // TODO: replace with symbolsfont dash
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-svg-progress {
 | 
			
		||||
  &__bg {
 | 
			
		||||
    fill: rgba(black, 0.2);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &__ticks {
 | 
			
		||||
    fill: none;
 | 
			
		||||
    stroke: #666;
 | 
			
		||||
    stroke-width: 6;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &__progress {
 | 
			
		||||
    fill: $c;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,7 @@
 | 
			
		||||
          :key="item.key"
 | 
			
		||||
          :item="item"
 | 
			
		||||
          :item-properties="itemProperties"
 | 
			
		||||
          @click="itemSelected(item, $event)"
 | 
			
		||||
        />
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
@@ -86,6 +87,7 @@ export default {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  emits: ['itemSelectionChanged'],
 | 
			
		||||
  data() {
 | 
			
		||||
    let sortBy = this.defaultSort.property;
 | 
			
		||||
    let ascending = this.defaultSort.defaultDirection;
 | 
			
		||||
@@ -156,6 +158,10 @@ export default {
 | 
			
		||||
          })
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    itemSelected(item, event) {
 | 
			
		||||
      event.stopPropagation();
 | 
			
		||||
      this.$emit('itemSelectionChanged', item, event.currentTarget);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user