mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 20:57:53 +00:00
Add new timelist view and plugin (#4766)
* Add new timelist view and plugin * Add inspector properties * calculate list bounds to show/hide events * Add timer to track 'Now' for timelist * Styling for Timelist view Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com> Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov> Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
This commit is contained in:
parent
77c0b16050
commit
6153ad8e1e
@ -195,6 +195,7 @@
|
||||
));
|
||||
openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));
|
||||
openmct.install(openmct.plugins.Timer());
|
||||
openmct.install(openmct.plugins.Timelist());
|
||||
openmct.start();
|
||||
</script>
|
||||
</html>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="c-table c-table--sortable c-list-view">
|
||||
<div class="c-table c-table--sortable c-list-view c-list-view--sticky-header c-list-view--selectable">
|
||||
<table class="c-table__body">
|
||||
<thead class="c-table__header">
|
||||
<tr>
|
||||
|
@ -1,32 +0,0 @@
|
||||
/******************************* LIST VIEW */
|
||||
.c-list-view {
|
||||
overflow-x: auto !important;
|
||||
overflow-y: auto;
|
||||
|
||||
tbody tr {
|
||||
background: $colorListItemBg;
|
||||
transition: $transOut;
|
||||
}
|
||||
|
||||
body.desktop & {
|
||||
tbody tr {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: $colorListItemBgHov;
|
||||
filter: $filterHov;
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
$p: floor($interiorMargin * 1.5);
|
||||
@include ellipsize();
|
||||
line-height: 120%; // Needed for icon alignment
|
||||
max-width: 0;
|
||||
padding-top: $p;
|
||||
padding-bottom: $p;
|
||||
width: 25%;
|
||||
}
|
||||
}
|
@ -76,7 +76,8 @@ define([
|
||||
'./timer/plugin',
|
||||
'./userIndicator/plugin',
|
||||
'../../example/exampleUser/plugin',
|
||||
'./localStorage/plugin'
|
||||
'./localStorage/plugin',
|
||||
'./timelist/plugin'
|
||||
], function (
|
||||
_,
|
||||
UTCTimeSystem,
|
||||
@ -133,7 +134,8 @@ define([
|
||||
Timer,
|
||||
UserIndicator,
|
||||
ExampleUser,
|
||||
LocalStorage
|
||||
LocalStorage,
|
||||
TimeList
|
||||
) {
|
||||
const plugins = {};
|
||||
|
||||
@ -210,6 +212,7 @@ define([
|
||||
plugins.DeviceClassifier = DeviceClassifier.default;
|
||||
plugins.UserIndicator = UserIndicator.default;
|
||||
plugins.LocalStorage = LocalStorage.default;
|
||||
plugins.Timelist = TimeList.default;
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
394
src/plugins/timelist/Timelist.vue
Normal file
394
src/plugins/timelist/Timelist.vue
Normal file
@ -0,0 +1,394 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2022, 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
|
||||
ref="timelistHolder"
|
||||
class="c-timelist"
|
||||
>
|
||||
<list-view
|
||||
:items="planActivities"
|
||||
:header-items="headerItems"
|
||||
:default-sort="defaultSort"
|
||||
class="sticky"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getValidatedPlan} from "../plan/util";
|
||||
import ListView from '../../ui/components/List/ListView.vue';
|
||||
import {getPreciseDuration} from "../../utils/duration";
|
||||
import ticker from 'utils/clock/Ticker';
|
||||
import {SORT_ORDER_OPTIONS} from "./constants";
|
||||
|
||||
import moment from "moment";
|
||||
import uuid from "uuid";
|
||||
|
||||
const SCROLL_TIMEOUT = 10000;
|
||||
const ROW_HEIGHT = 30;
|
||||
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss:SSS';
|
||||
const headerItems = [
|
||||
{
|
||||
defaultDirection: true,
|
||||
isSortable: true,
|
||||
property: 'start',
|
||||
name: 'Start Time',
|
||||
format: function (value, object) {
|
||||
return `${moment(value).format(TIME_FORMAT)}Z`;
|
||||
}
|
||||
}, {
|
||||
defaultDirection: true,
|
||||
isSortable: true,
|
||||
property: 'end',
|
||||
name: 'End Time',
|
||||
format: function (value, object) {
|
||||
return `${moment(value).format(TIME_FORMAT)}Z`;
|
||||
}
|
||||
}, {
|
||||
defaultDirection: false,
|
||||
property: 'duration',
|
||||
name: 'Time To/From',
|
||||
format: function (value) {
|
||||
let result;
|
||||
if (value < 0) {
|
||||
result = `-${getPreciseDuration(Math.abs(value))}`;
|
||||
} else if (value > 0) {
|
||||
result = `+${getPreciseDuration(value)}`;
|
||||
} else {
|
||||
result = 'Now';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}, {
|
||||
defaultDirection: true,
|
||||
property: 'name',
|
||||
name: 'Activity'
|
||||
}
|
||||
];
|
||||
|
||||
const defaultSort = {
|
||||
property: 'start',
|
||||
defaultDirection: true
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ListView
|
||||
},
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
data() {
|
||||
return {
|
||||
viewBounds: undefined,
|
||||
height: 0,
|
||||
planActivities: [],
|
||||
headerItems: headerItems,
|
||||
defaultSort: defaultSort
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.isEditing = this.openmct.editor.isEditing();
|
||||
this.timestamp = Date.now();
|
||||
this.getPlanDataAndSetConfig(this.domainObject);
|
||||
|
||||
this.unlisten = this.openmct.objects.observe(this.domainObject, 'selectFile', this.getPlanDataAndSetConfig);
|
||||
this.unlistenConfig = this.openmct.objects.observe(this.domainObject, 'configuration', this.setViewFromConfig);
|
||||
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
|
||||
this.status = this.openmct.status.get(this.domainObject.identifier);
|
||||
this.unlistenTicker = ticker.listen(this.clearPreviousActivities);
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
|
||||
this.deferAutoScroll = _.debounce(this.deferAutoScroll, 500);
|
||||
this.$el.parentElement.addEventListener('scroll', this.deferAutoScroll, true);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
}
|
||||
|
||||
if (this.unlistenConfig) {
|
||||
this.unlistenConfig();
|
||||
}
|
||||
|
||||
if (this.unlistenTicker) {
|
||||
this.unlistenTicker();
|
||||
}
|
||||
|
||||
if (this.removeStatusListener) {
|
||||
this.removeStatusListener();
|
||||
}
|
||||
|
||||
this.openmct.editor.off('isEditing', this.setEditState);
|
||||
|
||||
this.$el.parentElement.removeEventListener('scroll', this.deferAutoScroll, true);
|
||||
if (this.clearAutoScrollDisabledTimer) {
|
||||
clearTimeout(this.clearAutoScrollDisabledTimer);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getPlanDataAndSetConfig(mutatedObject) {
|
||||
this.getPlanData(mutatedObject);
|
||||
this.setViewFromConfig(mutatedObject.configuration);
|
||||
},
|
||||
setViewFromConfig(configuration) {
|
||||
if (this.isEditing) {
|
||||
this.filterValue = configuration.filter;
|
||||
this.hideAll = false;
|
||||
this.showAll = true;
|
||||
this.listActivities();
|
||||
} else {
|
||||
this.filterValue = configuration.filter;
|
||||
this.setSort();
|
||||
this.setViewBounds();
|
||||
this.listActivities();
|
||||
}
|
||||
},
|
||||
getPlanData(domainObject) {
|
||||
this.planData = getValidatedPlan(domainObject);
|
||||
},
|
||||
setViewBounds() {
|
||||
const pastEventsIndex = this.domainObject.configuration.pastEventsIndex;
|
||||
const currentEventsIndex = this.domainObject.configuration.currentEventsIndex;
|
||||
const futureEventsIndex = this.domainObject.configuration.futureEventsIndex;
|
||||
const pastEventsDuration = this.domainObject.configuration.pastEventsDuration;
|
||||
const pastEventsDurationIndex = this.domainObject.configuration.pastEventsDurationIndex;
|
||||
const futureEventsDuration = this.domainObject.configuration.futureEventsDuration;
|
||||
const futureEventsDurationIndex = this.domainObject.configuration.futureEventsDurationIndex;
|
||||
|
||||
if (pastEventsIndex === 0 && futureEventsIndex === 0 && currentEventsIndex === 0) {
|
||||
//show all events
|
||||
this.showAll = false;
|
||||
this.viewBounds = undefined;
|
||||
this.hideAll = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.hideAll = false;
|
||||
|
||||
if (pastEventsIndex === 1 && futureEventsIndex === 1 && currentEventsIndex === 1) {
|
||||
//show all events
|
||||
this.showAll = true;
|
||||
this.viewBounds = undefined;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.showAll = false;
|
||||
|
||||
this.viewBounds = {};
|
||||
|
||||
this.noCurrent = currentEventsIndex === 0;
|
||||
|
||||
if (pastEventsIndex !== 1) {
|
||||
const pastDurationInMS = this.getDurationInMilliSeconds(pastEventsDuration, pastEventsDurationIndex);
|
||||
this.viewBounds.pastEnd = (timestamp) => {
|
||||
if (pastEventsIndex === 2) {
|
||||
return timestamp - pastDurationInMS;
|
||||
} else if (pastEventsIndex === 0) {
|
||||
return timestamp + 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (futureEventsIndex !== 1) {
|
||||
const futureDurationInMS = this.getDurationInMilliSeconds(futureEventsDuration, futureEventsDurationIndex);
|
||||
this.viewBounds.futureStart = (timestamp) => {
|
||||
if (futureEventsIndex === 2) {
|
||||
return timestamp + futureDurationInMS;
|
||||
} else if (futureEventsIndex === 0) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
getDurationInMilliSeconds(duration, durationIndex) {
|
||||
if (duration > 0) {
|
||||
if (durationIndex === 0) {
|
||||
return duration * 1000;
|
||||
} else if (durationIndex === 1) {
|
||||
return duration * 60 * 1000;
|
||||
} else if (durationIndex === 2) {
|
||||
return duration * 60 * 24 * 1000;
|
||||
}
|
||||
}
|
||||
},
|
||||
listActivities() {
|
||||
let groups = Object.keys(this.planData);
|
||||
let activities = [];
|
||||
|
||||
groups.forEach((key) => {
|
||||
activities = activities.concat(this.planData[key]);
|
||||
});
|
||||
activities = activities.filter(this.filterActivities);
|
||||
activities = this.applyStyles(activities);
|
||||
this.setScrollTop();
|
||||
// sort by start time
|
||||
this.planActivities = activities.sort(this.sortByStartTime);
|
||||
},
|
||||
clearPreviousActivities(time) {
|
||||
if (time instanceof Date) {
|
||||
this.timestamp = time.getTime();
|
||||
} else {
|
||||
this.timestamp = time;
|
||||
}
|
||||
|
||||
this.listActivities();
|
||||
},
|
||||
filterActivities(activity, index) {
|
||||
|
||||
const hasFilterMatch = this.filterByName(activity.name);
|
||||
|
||||
if (hasFilterMatch === false || this.hideAll === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.showAll === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//current event or future start event or past end event
|
||||
const isCurrent = (this.noCurrent === false && this.timestamp >= activity.start && this.timestamp <= activity.end);
|
||||
const isPast = (this.timestamp > activity.end && (this.viewBounds.pastEnd === undefined || activity.end >= this.viewBounds.pastEnd(this.timestamp)));
|
||||
const isFuture = (this.timestamp < activity.start && (this.viewBounds.futureStart === undefined || activity.start <= this.viewBounds.futureStart(this.timestamp)));
|
||||
|
||||
return isCurrent || isPast || isFuture;
|
||||
},
|
||||
filterByName(name) {
|
||||
const filters = this.filterValue.split(',');
|
||||
|
||||
return filters.some((search => {
|
||||
const normalized = search.trim().toLowerCase();
|
||||
const regex = new RegExp(normalized);
|
||||
|
||||
return regex.test(name.toLowerCase());
|
||||
}));
|
||||
},
|
||||
applyStyles(activities) {
|
||||
let firstCurrentActivityIndex = -1;
|
||||
let currentActivitiesCount = 0;
|
||||
const styledActivities = activities.map((activity, index) => {
|
||||
if (this.timestamp >= activity.start && this.timestamp <= activity.end) {
|
||||
activity.cssClass = '--is-current';
|
||||
if (firstCurrentActivityIndex < 0) {
|
||||
firstCurrentActivityIndex = index;
|
||||
}
|
||||
|
||||
currentActivitiesCount = currentActivitiesCount + 1;
|
||||
} else if (this.timestamp < activity.start) {
|
||||
activity.cssClass = '--is-future';
|
||||
} else {
|
||||
activity.cssClass = '--is-past';
|
||||
}
|
||||
|
||||
if (!activity.key) {
|
||||
activity.key = uuid();
|
||||
}
|
||||
|
||||
activity.duration = activity.start - this.timestamp;
|
||||
|
||||
return activity;
|
||||
});
|
||||
|
||||
this.firstCurrentActivityIndex = firstCurrentActivityIndex;
|
||||
this.currentActivitiesCount = currentActivitiesCount;
|
||||
|
||||
return styledActivities;
|
||||
},
|
||||
canAutoScroll() {
|
||||
//this distinguishes between programmatic vs user-triggered scroll events
|
||||
this.autoScrolled = (this.dontAutoScroll !== true);
|
||||
|
||||
return this.autoScrolled;
|
||||
},
|
||||
resetScroll() {
|
||||
if (this.canAutoScroll() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.firstCurrentActivityIndex = -1;
|
||||
this.currentActivitiesCount = 0;
|
||||
this.$el.parentElement.scrollTo({top: 0});
|
||||
this.autoScrolled = false;
|
||||
},
|
||||
setScrollTop() {
|
||||
//scroll to somewhere mid-way of the current activities
|
||||
if (this.firstCurrentActivityIndex > -1) {
|
||||
if (this.canAutoScroll() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollOffset = this.currentActivitiesCount > 0 ? Math.floor(this.currentActivitiesCount / 2) : 0;
|
||||
this.$el.parentElement.scrollTo({
|
||||
top: ROW_HEIGHT * (this.firstCurrentActivityIndex + scrollOffset),
|
||||
behavior: "smooth"
|
||||
});
|
||||
this.autoScrolled = false;
|
||||
} else {
|
||||
this.resetScroll();
|
||||
}
|
||||
},
|
||||
deferAutoScroll() {
|
||||
//if this is not a user-triggered event, don't defer auto scrolling
|
||||
if (this.autoScrolled) {
|
||||
this.autoScrolled = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.dontAutoScroll = true;
|
||||
const self = this;
|
||||
if (this.clearAutoScrollDisabledTimer) {
|
||||
clearTimeout(this.clearAutoScrollDisabledTimer);
|
||||
}
|
||||
|
||||
this.clearAutoScrollDisabledTimer = setTimeout(() => {
|
||||
self.dontAutoScroll = false;
|
||||
self.setScrollTop();
|
||||
}, SCROLL_TIMEOUT);
|
||||
},
|
||||
setSort() {
|
||||
const sortOrder = SORT_ORDER_OPTIONS[this.domainObject.configuration.sortOrderIndex];
|
||||
const property = sortOrder.property;
|
||||
const direction = sortOrder.direction.toLowerCase() === 'asc';
|
||||
this.defaultSort = {
|
||||
property,
|
||||
defaultDirection: direction
|
||||
};
|
||||
},
|
||||
sortByStartTime(a, b) {
|
||||
const numA = parseInt(a.start, 10);
|
||||
const numB = parseInt(b.start, 10);
|
||||
|
||||
return numA - numB;
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
},
|
||||
setEditState(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
this.setViewFromConfig(this.domainObject.configuration);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
67
src/plugins/timelist/TimelistViewProvider.js
Normal file
67
src/plugins/timelist/TimelistViewProvider.js
Normal file
@ -0,0 +1,67 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, 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 Timelist from './Timelist.vue';
|
||||
import { TIMELIST_TYPE } from './constants';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function TimelistViewProvider(openmct) {
|
||||
|
||||
return {
|
||||
key: 'timelist.view',
|
||||
name: 'Time List',
|
||||
cssClass: 'icon-timelist',
|
||||
canView(domainObject) {
|
||||
return domainObject.type === TIMELIST_TYPE;
|
||||
},
|
||||
|
||||
canEdit(domainObject) {
|
||||
return domainObject.type === TIMELIST_TYPE;
|
||||
},
|
||||
|
||||
view: function (domainObject, objectPath) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
Timelist
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
path: objectPath
|
||||
},
|
||||
template: '<timelist></timelist>'
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
24
src/plugins/timelist/constants.js
Normal file
24
src/plugins/timelist/constants.js
Normal file
@ -0,0 +1,24 @@
|
||||
export const SORT_ORDER_OPTIONS = [
|
||||
{
|
||||
label: 'Start ascending',
|
||||
property: 'start',
|
||||
direction: 'ASC'
|
||||
},
|
||||
{
|
||||
label: 'Start descending',
|
||||
property: 'start',
|
||||
direction: 'DESC'
|
||||
},
|
||||
{
|
||||
label: 'End ascending',
|
||||
property: 'end',
|
||||
direction: 'ASC'
|
||||
},
|
||||
{
|
||||
label: 'End descending',
|
||||
property: 'end',
|
||||
direction: 'DESC'
|
||||
}
|
||||
];
|
||||
|
||||
export const TIMELIST_TYPE = 'timelist';
|
124
src/plugins/timelist/inspector/EventProperties.vue
Normal file
124
src/plugins/timelist/inspector/EventProperties.vue
Normal file
@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<li class="c-inspect-properties__row">
|
||||
<div
|
||||
class="c-inspect-properties__label"
|
||||
title="Options for future events."
|
||||
>{{ label }}</div>
|
||||
<div
|
||||
v-if="canEdit"
|
||||
class="c-inspect-properties__value"
|
||||
>
|
||||
<select
|
||||
v-model="index"
|
||||
@change="updateForm('index')"
|
||||
>
|
||||
<option
|
||||
v-for="(activityOption, activityKey) in activitiesOptions"
|
||||
:key="activityKey"
|
||||
:value="activityKey"
|
||||
>{{ activityOption }}</option>
|
||||
</select>
|
||||
<input
|
||||
v-if="index === 2"
|
||||
v-model="duration"
|
||||
class="c-input c-input--sm"
|
||||
type="text"
|
||||
@change="updateForm('duration')"
|
||||
>
|
||||
<select
|
||||
v-if="index === 2"
|
||||
v-model="durationIndex"
|
||||
@change="updateForm('durationIndex')"
|
||||
>
|
||||
<option
|
||||
v-for="(durationOption, durationKey) in durationOptions"
|
||||
:key="durationKey"
|
||||
:value="durationKey"
|
||||
>{{ durationOption }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="c-inspect-properties__value"
|
||||
>
|
||||
{{ activitiesOptions[index] }} <span v-if="index > 1">{{ duration }} {{ durationOptions[durationIndex] }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const ACTIVITIES_OPTIONS = [
|
||||
'Don\'t show',
|
||||
'Show all',
|
||||
'Show starts within',
|
||||
'Show after end for'
|
||||
];
|
||||
|
||||
const DURATION_OPTIONS = [
|
||||
'seconds',
|
||||
'minutes',
|
||||
'hours'
|
||||
];
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
index: this.domainObject.configuration[`${this.prefix}Index`],
|
||||
durationIndex: this.domainObject.configuration[`${this.prefix}DurationIndex`],
|
||||
duration: this.domainObject.configuration[`${this.prefix}Duration`],
|
||||
activitiesOptions: ACTIVITIES_OPTIONS,
|
||||
durationOptions: DURATION_OPTIONS,
|
||||
isEditing: this.openmct.editor.isEditing()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canEdit() {
|
||||
return this.isEditing && !this.domainObject.locked;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.prefix === 'futureEvents') {
|
||||
this.activitiesOptions = ACTIVITIES_OPTIONS.slice(0, 3);
|
||||
} else if (this.prefix === 'pastEvents') {
|
||||
this.activitiesOptions = ACTIVITIES_OPTIONS.filter((item, index) => index !== 2);
|
||||
} else if (this.prefix === 'currentEvents') {
|
||||
this.activitiesOptions = ACTIVITIES_OPTIONS.slice(0, 2);
|
||||
}
|
||||
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.openmct.editor.off('isEditing', this.setEditState);
|
||||
},
|
||||
methods: {
|
||||
updateForm(property) {
|
||||
if (!this.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const capitalized = property.charAt(0).toUpperCase() + property.substr(1);
|
||||
this.$emit('updated', {
|
||||
property: `${this.prefix}${capitalized}`,
|
||||
value: this[property]
|
||||
});
|
||||
},
|
||||
isValid() {
|
||||
return this.index < 2 || (this.durationIndex >= 0 && this.duration > 0);
|
||||
},
|
||||
setEditState(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
91
src/plugins/timelist/inspector/Filtering.vue
Normal file
91
src/plugins/timelist/inspector/Filtering.vue
Normal file
@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<li class="c-inspect-properties__row">
|
||||
<div
|
||||
v-if="canEdit"
|
||||
class="c-inspect-properties__hint span-all"
|
||||
>Filter this view by comma-separated keywords.</div>
|
||||
<div
|
||||
class="c-inspect-properties__label"
|
||||
title="Filter by keyword."
|
||||
>Filters</div>
|
||||
<div
|
||||
v-if="canEdit"
|
||||
class="c-inspect-properties__value"
|
||||
:class="{'form-error': hasError}"
|
||||
>
|
||||
<textarea
|
||||
v-model="filterValue"
|
||||
class="c-input--flex"
|
||||
type="text"
|
||||
@keydown.enter.exact.stop="forceBlur($event)"
|
||||
@keyup="updateForm($event, 'filter')"
|
||||
></textarea>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="c-inspect-properties__value"
|
||||
>
|
||||
{{ filterValue }}
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
return {
|
||||
isEditing: this.openmct.editor.isEditing(),
|
||||
filterValue: this.domainObject.configuration.filter,
|
||||
hasError: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canEdit() {
|
||||
return this.isEditing && !this.domainObject.locked;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.openmct.editor.off('isEditing', this.setEditState);
|
||||
},
|
||||
methods: {
|
||||
setEditState(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
if (!this.isEditing && this.hasError) {
|
||||
this.filterValue = this.domainObject.configuration.filter;
|
||||
this.hasError = false;
|
||||
}
|
||||
},
|
||||
forceBlur(event) {
|
||||
event.target.blur();
|
||||
},
|
||||
updateForm(event, property) {
|
||||
if (!this.isValid()) {
|
||||
this.hasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasError = false;
|
||||
|
||||
this.$emit('updated', {
|
||||
property,
|
||||
value: this.filterValue.replace(/,(\s)*$/, '')
|
||||
});
|
||||
},
|
||||
isValid() {
|
||||
// Test for any word character, any whitespace character or comma
|
||||
if (this.filterValue === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const regex = new RegExp(/^([a-zA-Z0-9_\-\s,])+$/g);
|
||||
|
||||
return regex.test(this.filterValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -0,0 +1,70 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, 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 TimelistPropertiesView from "./TimelistPropertiesView.vue";
|
||||
import { TIMELIST_TYPE } from '../constants';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function TimeListInspectorViewProvider(openmct) {
|
||||
return {
|
||||
key: 'timelist-inspector',
|
||||
name: 'Timelist Inspector View',
|
||||
canView: function (selection) {
|
||||
if (selection.length === 0 || selection[0].length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let context = selection[0][0].context;
|
||||
|
||||
return context && context.item
|
||||
&& context.item.type === TIMELIST_TYPE;
|
||||
},
|
||||
view: function (selection) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
TimelistPropertiesView: TimelistPropertiesView
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject: selection[0][0].context.item
|
||||
},
|
||||
template: '<timelist-properties-view></timelist-properties-view>'
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
146
src/plugins/timelist/inspector/TimelistPropertiesView.vue
Normal file
146
src/plugins/timelist/inspector/TimelistPropertiesView.vue
Normal file
@ -0,0 +1,146 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2022, 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="c-timelist-properties">
|
||||
<div class="c-inspect-properties">
|
||||
<ul class="c-inspect-properties__section">
|
||||
<div
|
||||
class="c-inspect-properties_header"
|
||||
title="'Timeframe options'"
|
||||
>Timeframe</div>
|
||||
<li class="c-inspect-properties__row">
|
||||
<div
|
||||
v-if="canEdit"
|
||||
class="c-inspect-properties__hint span-all"
|
||||
>These settings are not previewed and will be applied after editing is completed.</div>
|
||||
<div
|
||||
class="c-inspect-properties__label"
|
||||
title="Sort order of the timelist."
|
||||
>Sort Order</div>
|
||||
<div
|
||||
v-if="canEdit"
|
||||
class="c-inspect-properties__value"
|
||||
>
|
||||
<select
|
||||
v-model="sortOrderIndex"
|
||||
@change="updateSortOrder()"
|
||||
>
|
||||
<option
|
||||
v-for="(sortOrderOption, index) in sortOrderOptions"
|
||||
:key="index"
|
||||
:value="index"
|
||||
>{{ sortOrderOption.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="c-inspect-properties__value"
|
||||
>
|
||||
{{ sortOrderOptions[sortOrderIndex].label }}
|
||||
</div>
|
||||
</li>
|
||||
<event-properties
|
||||
v-for="type in eventTypes"
|
||||
:key="type.prefix"
|
||||
:label="type.label"
|
||||
:prefix="type.prefix"
|
||||
@updated="eventPropertiesUpdated"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="c-inspect-properties">
|
||||
<ul class="c-inspect-properties__section">
|
||||
<div
|
||||
class="c-inspect-properties_header"
|
||||
title="'Filters'"
|
||||
>Filtering</div>
|
||||
<filtering @updated="eventPropertiesUpdated" />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import EventProperties from './EventProperties.vue';
|
||||
import { SORT_ORDER_OPTIONS } from '../constants';
|
||||
import Filtering from './Filtering.vue';
|
||||
|
||||
const EVENT_TYPES = [
|
||||
{
|
||||
label: 'Future Events',
|
||||
prefix: 'futureEvents'
|
||||
},
|
||||
{
|
||||
label: 'Current Events',
|
||||
prefix: 'currentEvents'
|
||||
},
|
||||
{
|
||||
label: 'Past Events',
|
||||
prefix: 'pastEvents'
|
||||
}
|
||||
];
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Filtering,
|
||||
EventProperties
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
return {
|
||||
sortOrderIndex: this.domainObject.configuration.sortOrderIndex,
|
||||
sortOrderOptions: SORT_ORDER_OPTIONS,
|
||||
eventTypes: EVENT_TYPES,
|
||||
isEditing: this.openmct.editor.isEditing()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canEdit() {
|
||||
return this.isEditing && !this.domainObject.locked;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.openmct.editor.off('isEditing', this.setEditState);
|
||||
},
|
||||
methods: {
|
||||
setEditState(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
},
|
||||
updateSortOrder() {
|
||||
this.updateProperty('sortOrderIndex', this.sortOrderIndex);
|
||||
},
|
||||
updateProperty(key, value) {
|
||||
this.openmct.objects.mutate(this.domainObject, `configuration.${key}`, value);
|
||||
},
|
||||
eventPropertiesUpdated(data) {
|
||||
const key = data.property;
|
||||
const value = data.value;
|
||||
this.updateProperty(key, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
68
src/plugins/timelist/plugin.js
Normal file
68
src/plugins/timelist/plugin.js
Normal file
@ -0,0 +1,68 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, 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 TimelistViewProvider from './TimelistViewProvider';
|
||||
import { TIMELIST_TYPE } from './constants';
|
||||
import TimeListInspectorViewProvider from "./inspector/TimeListInspectorViewProvider";
|
||||
|
||||
export default function () {
|
||||
return function install(openmct) {
|
||||
openmct.types.addType(TIMELIST_TYPE, {
|
||||
name: 'Time List',
|
||||
key: TIMELIST_TYPE,
|
||||
description: 'A configurable, time-ordered list view of activities for a compatible mission plan file.',
|
||||
creatable: true,
|
||||
cssClass: 'icon-timelist',
|
||||
form: [
|
||||
{
|
||||
name: 'Upload Plan (JSON File)',
|
||||
key: 'selectFile',
|
||||
control: 'file-input',
|
||||
required: true,
|
||||
text: 'Select File...',
|
||||
type: 'application/json',
|
||||
property: [
|
||||
"selectFile"
|
||||
]
|
||||
}
|
||||
],
|
||||
initialize: function (domainObject) {
|
||||
domainObject.configuration = {
|
||||
sortOrderIndex: 0,
|
||||
futureEventsIndex: 0,
|
||||
futureEventsDurationIndex: 0,
|
||||
futureEventsDuration: 20,
|
||||
currentEventsIndex: 1,
|
||||
currentEventsDurationIndex: 0,
|
||||
currentEventsDuration: 20,
|
||||
pastEventsIndex: 0,
|
||||
pastEventsDurationIndex: 0,
|
||||
pastEventsDuration: 20,
|
||||
filter: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
openmct.objectViews.addProvider(new TimelistViewProvider(openmct));
|
||||
openmct.inspectorViews.addProvider(new TimeListInspectorViewProvider(openmct));
|
||||
|
||||
};
|
||||
}
|
181
src/plugins/timelist/pluginSpec.js
Normal file
181
src/plugins/timelist/pluginSpec.js
Normal file
@ -0,0 +1,181 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, 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 TimelistPlugin from "./plugin";
|
||||
import { TIMELIST_TYPE } from "./constants";
|
||||
import Vue from 'vue';
|
||||
import moment from "moment";
|
||||
|
||||
const LIST_ITEM_CLASS = '.js-table__body .js-list-item';
|
||||
const LIST_ITEM_VALUE_CLASS = '.js-list-item__value';
|
||||
const LIST_ITEM_BODY_CLASS = '.js-table__body th';
|
||||
|
||||
describe('the plugin', function () {
|
||||
let timelistDefinition;
|
||||
let element;
|
||||
let child;
|
||||
let openmct;
|
||||
let appHolder;
|
||||
let originalRouterPath;
|
||||
|
||||
beforeEach((done) => {
|
||||
appHolder = document.createElement('div');
|
||||
appHolder.style.width = '640px';
|
||||
appHolder.style.height = '480px';
|
||||
|
||||
openmct = createOpenMct();
|
||||
openmct.install(new TimelistPlugin());
|
||||
|
||||
timelistDefinition = openmct.types.get(TIMELIST_TYPE).definition;
|
||||
|
||||
element = document.createElement('div');
|
||||
element.style.width = '640px';
|
||||
element.style.height = '480px';
|
||||
child = document.createElement('div');
|
||||
child.style.width = '640px';
|
||||
child.style.height = '480px';
|
||||
element.appendChild(child);
|
||||
|
||||
originalRouterPath = openmct.router.path;
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.start(appHolder);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
openmct.router.path = originalRouterPath;
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
let mockTimelistObject = {
|
||||
name: 'Timelist',
|
||||
key: TIMELIST_TYPE,
|
||||
creatable: true
|
||||
};
|
||||
|
||||
it('defines a timelist object type with the correct key', () => {
|
||||
expect(timelistDefinition.key).toEqual(mockTimelistObject.key);
|
||||
});
|
||||
|
||||
it('is creatable', () => {
|
||||
expect(timelistDefinition.creatable).toEqual(mockTimelistObject.creatable);
|
||||
});
|
||||
|
||||
describe('the timelist view', () => {
|
||||
it('provides a timelist view', () => {
|
||||
const testViewObject = {
|
||||
id: "test-object",
|
||||
type: TIMELIST_TYPE
|
||||
};
|
||||
openmct.router.path = [testViewObject];
|
||||
|
||||
const applicableViews = openmct.objectViews.get(testViewObject, [testViewObject]);
|
||||
let timelistView = applicableViews.find((viewProvider) => viewProvider.key === 'timelist.view');
|
||||
expect(timelistView).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('the timelist view displays activities', () => {
|
||||
let timelistDomainObject;
|
||||
let timelistView;
|
||||
|
||||
beforeEach(() => {
|
||||
timelistDomainObject = {
|
||||
identifier: {
|
||||
key: 'test-object',
|
||||
namespace: ''
|
||||
},
|
||||
type: TIMELIST_TYPE,
|
||||
id: "test-object",
|
||||
configuration: {
|
||||
sortOrderIndex: 0,
|
||||
futureEventsIndex: 1,
|
||||
futureEventsDurationIndex: 0,
|
||||
futureEventsDuration: 20,
|
||||
currentEventsIndex: 1,
|
||||
currentEventsDurationIndex: 0,
|
||||
currentEventsDuration: 20,
|
||||
pastEventsIndex: 1,
|
||||
pastEventsDurationIndex: 0,
|
||||
pastEventsDuration: 20,
|
||||
filter: ''
|
||||
},
|
||||
selectFile: {
|
||||
body: JSON.stringify({
|
||||
"TEST-GROUP": [
|
||||
{
|
||||
"name": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
|
||||
"start": 1597170002854,
|
||||
"end": 1597171032854,
|
||||
"type": "TEST-GROUP",
|
||||
"color": "fuchsia",
|
||||
"textColor": "black"
|
||||
},
|
||||
{
|
||||
"name": "Sed ut perspiciatis",
|
||||
"start": 1597171132854,
|
||||
"end": 1597171232854,
|
||||
"type": "TEST-GROUP",
|
||||
"color": "fuchsia",
|
||||
"textColor": "black"
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
openmct.router.path = [timelistDomainObject];
|
||||
|
||||
const applicableViews = openmct.objectViews.get(timelistDomainObject, [timelistDomainObject]);
|
||||
timelistView = applicableViews.find((viewProvider) => viewProvider.key === 'timelist.view');
|
||||
let view = timelistView.view(timelistDomainObject, []);
|
||||
view.show(child, true);
|
||||
|
||||
return Vue.nextTick();
|
||||
});
|
||||
|
||||
it('displays the activities', () => {
|
||||
const items = element.querySelectorAll(LIST_ITEM_CLASS);
|
||||
expect(items.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('displays the activity headers', () => {
|
||||
const headers = element.querySelectorAll(LIST_ITEM_BODY_CLASS);
|
||||
expect(headers.length).toEqual(4);
|
||||
});
|
||||
|
||||
it('displays activity details', (done) => {
|
||||
Vue.nextTick(() => {
|
||||
const itemEls = element.querySelectorAll(LIST_ITEM_CLASS);
|
||||
const itemValues = itemEls[0].querySelectorAll(LIST_ITEM_VALUE_CLASS);
|
||||
expect(itemValues.length).toEqual(4);
|
||||
expect(itemValues[3].innerHTML.trim()).toEqual('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua');
|
||||
expect(itemValues[0].innerHTML.trim()).toEqual(`${moment(1597170002854).format('YYYY-MM-DD HH:mm:ss:SSS')}Z`);
|
||||
expect(itemValues[1].innerHTML.trim()).toEqual(`${moment(1597171032854).format('YYYY-MM-DD HH:mm:ss:SSS')}Z`);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
55
src/plugins/timelist/timelist.scss
Normal file
55
src/plugins/timelist/timelist.scss
Normal file
@ -0,0 +1,55 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
.c-timelist {
|
||||
& .nowMarker.hasCurrent {
|
||||
height: 2px;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
background: cyan;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.c-list-item {
|
||||
/* Time Lists */
|
||||
|
||||
&.--is-current {
|
||||
background-color: $colorCurrentBg;
|
||||
border-top: 1px solid $colorCurrentBorder !important;
|
||||
color: $colorCurrentFg;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.--is-future {
|
||||
background-color: $colorFutureBg;
|
||||
border-top-color: $colorFutureBorder !important;
|
||||
color: $colorFutureFg;
|
||||
}
|
||||
|
||||
&__value {
|
||||
&.--duration {
|
||||
width: 5%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -345,7 +345,7 @@ $shdwItemText: none;
|
||||
$colorTabBorder: pullForward($colorBodyBg, 10%);
|
||||
$colorTabBodyBg: $colorBodyBg;
|
||||
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
|
||||
$colorTabHeaderBg: rgba($colorBodyFg, 0.15);
|
||||
$colorTabHeaderBg: #575757;
|
||||
$colorTabHeaderFg: $colorBodyFg;
|
||||
$colorTabHeaderBorder: $colorBodyBg;
|
||||
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);
|
||||
@ -366,6 +366,14 @@ $legendHoverValueBg: rgba($colorBodyFg, 0.2);
|
||||
$legendTableHeadBg: $colorTabHeaderBg;
|
||||
$colorPlotLimitLineBg: rgba($colorBodyBg, 0.2);
|
||||
|
||||
// Time Strip and Lists
|
||||
$colorCurrentBg: rgba($colorStatusAlert, 0.3);
|
||||
$colorCurrentFg: pullForward($colorBodyFg, 20%);
|
||||
$colorCurrentBorder: $colorBodyBg;
|
||||
$colorFutureBg: rgba($colorKey, 0.2);
|
||||
$colorFutureFg: $colorCurrentFg;
|
||||
$colorFutureBorder: $colorCurrentBorder;
|
||||
|
||||
// Tree
|
||||
$colorTreeBg: transparent;
|
||||
$colorItemTreeHoverBg: rgba(#fff, 0.1);
|
||||
|
@ -349,7 +349,7 @@ $shdwItemText: none;
|
||||
$colorTabBorder: pullForward($colorBodyBg, 10%);
|
||||
$colorTabBodyBg: $colorBodyBg;
|
||||
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
|
||||
$colorTabHeaderBg: rgba($colorBodyFg, 0.15);
|
||||
$colorTabHeaderBg: #575757;
|
||||
$colorTabHeaderFg: $colorBodyFg;
|
||||
$colorTabHeaderBorder: $colorBodyBg;
|
||||
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);
|
||||
@ -370,6 +370,14 @@ $legendHoverValueBg: rgba($colorBodyFg, 0.2);
|
||||
$legendTableHeadBg: rgba($colorBodyFg, 0.15);
|
||||
$colorPlotLimitLineBg: rgba($colorBodyBg, 0.2);
|
||||
|
||||
// Time Strip and Lists
|
||||
$colorCurrentBg: rgba($colorStatusAlert, 0.3);
|
||||
$colorCurrentFg: pullForward($colorBodyFg, 20%);
|
||||
$colorCurrentBorder: #fff;
|
||||
$colorFutureBg: rgba($colorKey, 0.2);
|
||||
$colorFutureFg: $colorCurrentFg;
|
||||
$colorFutureBorder: $colorCurrentBorder;
|
||||
|
||||
// Tree
|
||||
$colorTreeBg: transparent;
|
||||
$colorItemTreeHoverBg: rgba(#fff, 0.03);
|
||||
|
@ -345,7 +345,7 @@ $shdwItemText: none;
|
||||
$colorTabBorder: pullForward($colorBodyBg, 10%);
|
||||
$colorTabBodyBg: $colorBodyBg;
|
||||
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
|
||||
$colorTabHeaderBg: rgba($colorBodyFg, 0.2);
|
||||
$colorTabHeaderBg: #e2e2e2;
|
||||
$colorTabHeaderFg: $colorBodyFg;
|
||||
$colorTabHeaderBorder: $colorBodyBg;
|
||||
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);
|
||||
@ -366,6 +366,14 @@ $legendHoverValueBg: rgba($colorBodyFg, 0.2);
|
||||
$legendTableHeadBg: rgba($colorBodyFg, 0.15);
|
||||
$colorPlotLimitLineBg: rgba($colorBodyBg, 0.4);
|
||||
|
||||
// Time Strip and Lists
|
||||
$colorCurrentBg: rgba($colorStatusAlert, 0.3);
|
||||
$colorCurrentFg: pullForward($colorBodyFg, 20%);
|
||||
$colorCurrentBorder: #fff;
|
||||
$colorFutureBg: rgba($colorKey, 0.2);
|
||||
$colorFutureFg: $colorCurrentFg;
|
||||
$colorFutureBorder: $colorCurrentBorder;
|
||||
|
||||
// Tree
|
||||
$colorTreeBg: transparent;
|
||||
$colorItemTreeHoverBg: rgba(black, 0.07);
|
||||
|
@ -263,6 +263,7 @@ $glyph-icon-telemetry-aggregate: '\eb2b';
|
||||
$glyph-icon-bar-chart: '\eb2c';
|
||||
$glyph-icon-map: '\eb2d';
|
||||
$glyph-icon-plan: '\eb2e';
|
||||
$glyph-icon-timelist: '\eb2f';
|
||||
|
||||
/************************** GLYPHS AS DATA URI */
|
||||
// Only objects have been converted, for use in Create menu and folder views
|
||||
@ -317,3 +318,4 @@ $bg-icon-condition-widget: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='h
|
||||
$bg-icon-bar-chart: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M416 0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96ZM133.82 448H64V224h69.82Zm104.73 0h-69.82V64h69.82Zm104.72 0h-69.82V288h69.82ZM448 448h-69.82V128H448Z'/%3e%3c/svg%3e");
|
||||
$bg-icon-map: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 32.7 384 64v448l64-31.3c35.2-17.21 64-60.1 64-95.3v-320c0-35.2-28.8-49.91-64-32.7ZM160 456l193.6 48.4v-448L160 8v448zM129.6.4 128 0 64 31.3C28.8 48.51 0 91.4 0 126.6v320c0 35.2 28.8 49.91 64 32.7l64-31.3 1.6.4Z'/%3e%3c/svg%3e");
|
||||
$bg-icon-plan: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cg data-name='Layer 2'%3e%3cg data-name='Layer 1'%3e%3cpath fill='%23000000' d='M128 96V64a64.19 64.19 0 0 1 64-64h128a64.19 64.19 0 0 1 64 64v32Z'/%3e%3cpath fill='%23000000' d='M416 64v64H96V64c-52.8 0-96 43.2-96 96v256c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V160c0-52.8-43.2-96-96-96ZM64 288v-64h128v64Zm256 128H128v-64h192Zm128 0h-64v-64h64Zm0-128H256v-64h192Z'/%3e%3c/g%3e%3c/g%3e%3c/svg%3e");
|
||||
$bg-icon-timelist: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cg data-name='Layer 2'%3e%3cpath d='M448 0H64A64.19 64.19 0 0 0 0 64v384a64.19 64.19 0 0 0 64 64h384a64.19 64.19 0 0 0 64-64V64a64.19 64.19 0 0 0-64-64ZM213.47 266.73a24 24 0 0 1-32.2 10.74L104 238.83V128a24 24 0 0 1 48 0v81.17l50.73 25.36a24 24 0 0 1 10.74 32.2ZM448 448H288v-64h160Zm0-96H288v-64h160Zm0-96H288v-64h160Zm0-96H288V96h160Z' data-name='Layer 1'/%3e%3c/g%3e%3c/svg%3e");
|
||||
|
@ -194,6 +194,7 @@
|
||||
.icon-bar-chart { @include glyphBefore($glyph-icon-bar-chart); }
|
||||
.icon-map { @include glyphBefore($glyph-icon-map); }
|
||||
.icon-plan { @include glyphBefore($glyph-icon-plan); }
|
||||
.icon-timelist { @include glyphBefore($glyph-icon-timelist); }
|
||||
|
||||
/************************** 12 PX CLASSES */
|
||||
// TODO: sync with 16px redo as of 10/25/18
|
||||
@ -256,3 +257,4 @@
|
||||
.bg-icon-bar-chart { @include glyphBg($bg-icon-bar-chart); }
|
||||
.bg-icon-map { @include glyphBg($bg-icon-map); }
|
||||
.bg-icon-plan { @include glyphBg($bg-icon-plan); }
|
||||
.bg-icon-timelist { @include glyphBg($bg-icon-timelist); }
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -162,4 +162,5 @@
|
||||
<glyph unicode="" glyph-name="icon-bar-graph" d="M832 832h-640c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v640c0 105.6-86.4 192-192 192zM267.64-64h-139.64v448h139.64zM477.1-64h-139.64v768h139.64zM686.54-64h-139.64v320h139.64zM896-64h-139.64v640h139.64z" />
|
||||
<glyph unicode="" glyph-name="icon-map" d="M896 766.6l-128-62.6v-896l128 62.6c70.4 34.42 128 120.2 128 190.6v640c0 70.4-57.6 99.82-128 65.4zM320-80l387.2-96.8v896l-387.2 96.8v-896zM259.2 831.2l-3.2 0.8-128-62.6c-70.4-34.42-128-120.2-128-190.6v-640c0-70.4 57.6-99.82 128-65.4l128 62.6 3.2-0.8z" />
|
||||
<glyph unicode="" glyph-name="icon-plan" d="M256 640v64c0.215 70.606 57.394 127.785 127.979 128h256.021c70.606-0.215 127.785-57.394 128-127.979v-64.021zM832 704v-128h-640v128c-105.6 0-192-86.4-192-192v-512c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v512c0 105.6-86.4 192-192 192zM128 256v128h256v-128zM640 0h-384v128h384zM896 0h-128v128h128zM896 256h-384v128h384z" />
|
||||
<glyph unicode="" glyph-name="icon-timelist" d="M896 832h-768c-70.606-0.215-127.785-57.394-128-127.979v-768.021c0.215-70.606 57.394-127.785 127.979-128h768.021c70.606 0.215 127.785 57.394 128 127.979v768.021c-0.215 70.606-57.394 127.785-127.979 128h-0.021zM426.94 298.54c-8.054-15.864-24.249-26.545-42.938-26.545-7.823 0-15.209 1.871-21.734 5.191l0.273-0.126-154.54 77.28v221.66c0 26.51 21.49 48 48 48s48-21.49 48-48v0-162.34l101.46-50.72c15.864-8.054 26.545-24.249 26.545-42.938 0-7.823-1.871-15.209-5.191-21.734l0.126 0.273zM896-64h-320v128h320zM896 128h-320v128h320zM896 320h-320v128h320zM896 512h-320v128h320z" />
|
||||
</font></defs></svg>
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 63 KiB |
Binary file not shown.
Binary file not shown.
@ -15,7 +15,6 @@
|
||||
@import "../plugins/flexibleLayout/components/flexible-layout.scss";
|
||||
@import "../plugins/folderView/components/grid-view.scss";
|
||||
@import "../plugins/folderView/components/list-item.scss";
|
||||
@import "../plugins/folderView/components/list-view.scss";
|
||||
@import "../plugins/imagery/components/imagery-view.scss";
|
||||
@import "../plugins/imagery/components/Compass/compass.scss";
|
||||
@import "../plugins/telemetryTable/components/table-row.scss";
|
||||
@ -28,6 +27,7 @@
|
||||
@import "../plugins/timeConductor/conductor-mode-icon.scss";
|
||||
@import "../plugins/timeConductor/date-picker.scss";
|
||||
@import "../plugins/timeline/timeline.scss";
|
||||
@import "../plugins/timelist/timelist.scss";
|
||||
@import "../plugins/plan/plan";
|
||||
@import "../plugins/viewDatumAction/components/metadata-list.scss";
|
||||
@import "../ui/components/object-frame.scss";
|
||||
@ -37,6 +37,7 @@
|
||||
@import "../ui/components/swim-lane/swimlane.scss";
|
||||
@import "../ui/components/toggle-switch.scss";
|
||||
@import "../ui/components/timesystem-axis.scss";
|
||||
@import "../ui/components/List/list-view.scss";
|
||||
@import "../ui/inspector/elements.scss";
|
||||
@import "../ui/inspector/inspector.scss";
|
||||
@import "../ui/inspector/location.scss";
|
||||
|
56
src/ui/components/List/ListHeader.vue
Normal file
56
src/ui/components/List/ListHeader.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<th
|
||||
v-if="isSortable"
|
||||
class="is-sortable"
|
||||
:class="{
|
||||
'is-sorting': currentSort === property,
|
||||
'asc': direction,
|
||||
'desc': !direction
|
||||
}"
|
||||
@click="sort(property, direction)"
|
||||
>
|
||||
{{ title }}
|
||||
</th>
|
||||
<th v-else>
|
||||
{{ title }}
|
||||
</th>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
property: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
currentSort: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
direction: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
isSortable: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sort(property, direction) {
|
||||
this.$emit('sort', {
|
||||
property,
|
||||
direction
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
53
src/ui/components/List/ListItem.vue
Normal file
53
src/ui/components/List/ListItem.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<tr
|
||||
class="c-list-item js-list-item"
|
||||
:class="item.cssClass || ''"
|
||||
>
|
||||
<td
|
||||
v-for="itemValue in formattedItemValues"
|
||||
:key="itemValue.key"
|
||||
class="c-list-item__value js-list-item__value"
|
||||
:class="['--' + itemValue.key]"
|
||||
:title="itemValue.text"
|
||||
>
|
||||
{{ itemValue.text }}
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
itemProperties: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
formattedItemValues() {
|
||||
let values = [];
|
||||
this.itemProperties.forEach((property) => {
|
||||
// eslint-disable-next-line you-dont-need-lodash-underscore/get
|
||||
let value = _.get(this.item, property.key);
|
||||
if (property.format) {
|
||||
value = property.format(value, this.item, property.key);
|
||||
}
|
||||
|
||||
values.push({
|
||||
text: value,
|
||||
key: property.key
|
||||
});
|
||||
});
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
142
src/ui/components/List/ListView.vue
Normal file
142
src/ui/components/List/ListView.vue
Normal file
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div class="c-table c-table--sortable c-list-view c-list-view--sticky-header">
|
||||
<table class="c-table__body js-table__body">
|
||||
<thead class="c-table__header">
|
||||
<tr>
|
||||
<list-header
|
||||
v-for="headerItem in headerItems"
|
||||
:key="headerItem.property"
|
||||
:direction="sortBy === headerItem.property ? ascending : headerItem.defaultDirection"
|
||||
:is-sortable="headerItem.isSortable"
|
||||
:title="headerItem.name"
|
||||
:property="headerItem.property"
|
||||
:current-sort="sortBy"
|
||||
@sort="sort"
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<list-item
|
||||
v-for="item in sortedItems"
|
||||
:key="item.key"
|
||||
:item="item"
|
||||
:item-properties="itemProperties"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListItem from './ListItem.vue';
|
||||
import ListHeader from './ListHeader.vue';
|
||||
import _ from 'lodash';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ListItem,
|
||||
ListHeader
|
||||
},
|
||||
inject: ['domainObject', 'openmct'],
|
||||
props: {
|
||||
headerItems: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
defaultSort: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
property: '',
|
||||
defaultDirection: true
|
||||
};
|
||||
}
|
||||
},
|
||||
storageKey: {
|
||||
type: String,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let sortBy = this.defaultSort.property;
|
||||
let ascending = this.defaultSort.defaultDirection;
|
||||
if (this.storageKey) {
|
||||
let persistedSortOrder = window.localStorage.getItem(this.storageKey);
|
||||
|
||||
if (persistedSortOrder) {
|
||||
let parsed = JSON.parse(persistedSortOrder);
|
||||
|
||||
sortBy = parsed.sortBy;
|
||||
ascending = parsed.ascending;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
key: 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;
|
||||
}
|
||||
|
||||
if (this.storageKey) {
|
||||
window.localStorage
|
||||
.setItem(
|
||||
this.storageKey,
|
||||
JSON.stringify(
|
||||
{
|
||||
sortBy: this.sortBy,
|
||||
ascending: this.ascending
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
40
src/ui/components/List/list-view.scss
Normal file
40
src/ui/components/List/list-view.scss
Normal file
@ -0,0 +1,40 @@
|
||||
/******************************* LIST VIEW */
|
||||
.c-list-view {
|
||||
tbody tr {
|
||||
background: $colorListItemBg;
|
||||
transition: $transOut;
|
||||
}
|
||||
|
||||
td {
|
||||
$p: floor($interiorMargin * 1.5);
|
||||
@include ellipsize();
|
||||
line-height: 120%; // Needed for icon alignment
|
||||
max-width: 0;
|
||||
padding-top: $p;
|
||||
padding-bottom: $p;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
&--selectable {
|
||||
body.desktop & {
|
||||
tbody tr {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: $colorListItemBgHov;
|
||||
filter: $filterHov;
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--sticky-header {
|
||||
thead tr {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
@ -79,6 +79,11 @@
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
// When a textarea is in the Inspector, only allow vertical resize
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/************************************************************** LEGACY */
|
||||
.l-inspector-part {
|
||||
display: contents;
|
||||
@ -151,7 +156,8 @@
|
||||
padding: 3px $interiorMarginLg 3px 0;
|
||||
}
|
||||
|
||||
&__label {
|
||||
&__label,
|
||||
&__hint {
|
||||
color: $colorInspectorPropName;
|
||||
|
||||
&[title]:not([title=""]) {
|
||||
@ -167,6 +173,14 @@
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
}
|
||||
|
||||
.is-editing & {
|
||||
.c-inspect-properties {
|
||||
&__value, &__label {
|
||||
line-height: 160%; // Prevent buttons/selects from overlapping when wrapping
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************* INSPECTOR PROPERTIES TAB */
|
||||
|
Loading…
Reference in New Issue
Block a user