diff --git a/src/plugins/inspectorViews/elements/ElementItem.vue b/src/plugins/inspectorViews/elements/ElementItem.vue
index f668f6b8b2..e2770b1646 100644
--- a/src/plugins/inspectorViews/elements/ElementItem.vue
+++ b/src/plugins/inspectorViews/elements/ElementItem.vue
@@ -45,6 +45,7 @@
:object-path="[elementObject, domainObject]"
@context-click-active="setContextClickState"
/>
+
diff --git a/src/plugins/inspectorViews/elements/ElementsPool.vue b/src/plugins/inspectorViews/elements/ElementsPool.vue
index 06ee6de757..b465c9cbf0 100644
--- a/src/plugins/inspectorViews/elements/ElementsPool.vue
+++ b/src/plugins/inspectorViews/elements/ElementsPool.vue
@@ -42,7 +42,11 @@
:allow-drop="allowDrop"
@dragstart-custom="moveFrom(index)"
@drop-custom="moveTo(index)"
- />
+ >
+
+
+
+
No contained elements
diff --git a/src/plugins/inspectorViews/elements/ElementsViewProvider.js b/src/plugins/inspectorViews/elements/ElementsViewProvider.js
index ce9748861a..dc66f3a3b1 100644
--- a/src/plugins/inspectorViews/elements/ElementsViewProvider.js
+++ b/src/plugins/inspectorViews/elements/ElementsViewProvider.js
@@ -29,6 +29,7 @@ export default function ElementsViewProvider(openmct) {
key: 'elementsView',
name: 'Elements',
canView: function (selection) {
+ // TODO - only use this view provider if another custom provider has not been applied
const hasValidSelection = selection?.length;
const isOverlayPlot = selection?.[0]?.[0]?.context?.item?.type === 'telemetry.plot.overlay';
diff --git a/src/plugins/timeline/TimelineElementsContent.vue b/src/plugins/timeline/TimelineElementsContent.vue
new file mode 100644
index 0000000000..4c38750e1b
--- /dev/null
+++ b/src/plugins/timeline/TimelineElementsContent.vue
@@ -0,0 +1,82 @@
+
+
+
+ px
+
+
+
+
diff --git a/src/plugins/timeline/TimelineElementsPool.vue b/src/plugins/timeline/TimelineElementsPool.vue
new file mode 100644
index 0000000000..43c9e3df2a
--- /dev/null
+++ b/src/plugins/timeline/TimelineElementsPool.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/src/plugins/timeline/TimelineElementsViewProvider.js b/src/plugins/timeline/TimelineElementsViewProvider.js
new file mode 100644
index 0000000000..73b5023915
--- /dev/null
+++ b/src/plugins/timeline/TimelineElementsViewProvider.js
@@ -0,0 +1,76 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
+import mount from 'utils/mount';
+
+import TimelineElementsPool from './TimelineElementsPool.vue';
+
+export default function TimelineElementsViewProvider(openmct) {
+ return {
+ key: 'timelineElementsView',
+ name: 'Elements',
+ canView: function (selection) {
+ return selection?.[0]?.[0]?.context?.item?.type === 'time-strip';
+ },
+ view: function (selection) {
+ let _destroy = null;
+
+ const domainObject = selection?.[0]?.[0]?.context?.item;
+
+ return {
+ show: function (element) {
+ const { destroy } = mount(
+ {
+ el: element,
+ components: {
+ TimelineElementsPool
+ },
+ provide: {
+ openmct,
+ domainObject
+ },
+ template: ``
+ },
+ {
+ app: openmct.app,
+ element
+ }
+ );
+ _destroy = destroy;
+ },
+ showTab: function (isEditing) {
+ const hasComposition = Boolean(domainObject && openmct.composition.get(domainObject));
+
+ return hasComposition && isEditing;
+ },
+ priority: function () {
+ return openmct.priority.HIGH - 1;
+ },
+ destroy: function () {
+ if (_destroy) {
+ _destroy();
+ }
+ }
+ };
+ }
+ };
+}
diff --git a/src/plugins/timeline/TimelineObjectView.vue b/src/plugins/timeline/TimelineObjectView.vue
index 7d3db098d5..e1afbc3035 100644
--- a/src/plugins/timeline/TimelineObjectView.vue
+++ b/src/plugins/timeline/TimelineObjectView.vue
@@ -32,7 +32,8 @@
:hide-button="!item.isEventTelemetry"
:button-click-on="enableExtendEventLines"
:button-click-off="disableExtendEventLines"
- :style="[{ 'flex-basis': sizeString }]"
+ :class="sizeClass"
+ :style="sizeStyle"
>
{{ item.domainObject.name }}
@@ -64,12 +65,12 @@ export default {
type: Object,
required: true
},
- extendedLinesBus: {
+ container: {
type: Object,
required: true
},
- size: {
- type: Number,
+ extendedLinesBus: {
+ type: Object,
required: true
}
},
@@ -81,8 +82,17 @@ export default {
};
},
computed: {
- sizeString() {
- return `${this.size}%`;
+ size() {
+ return this.container.size;
+ },
+ fixed() {
+ return this.container.fixed;
+ },
+ sizeClass() {
+ return `--${this.fixed ? 'fixed' : 'scales'}`;
+ },
+ sizeStyle() {
+ return `flex-basis: ${this.size}${this.fixed ? 'px' : '%'}`;
}
},
watch: {
diff --git a/src/plugins/timeline/TimelineViewLayout.vue b/src/plugins/timeline/TimelineViewLayout.vue
index a41c89a5d5..1b8382bb32 100644
--- a/src/plugins/timeline/TimelineViewLayout.vue
+++ b/src/plugins/timeline/TimelineViewLayout.vue
@@ -47,7 +47,7 @@
{
+ let isConfigurationChanged = false;
+ composition.value.forEach((object, index) => {
const containerIndex = domainObject.configuration.containers.findIndex((container) =>
openmct.objects.areIdsEqual(container.domainObjectIdentifier, object.identifier)
);
+
+ if (containerIndex !== index) {
+ isConfigurationChanged = true;
+ }
+
if (containerIndex > -1) {
existingContainers.push(domainObject.configuration.containers[containerIndex]);
} else {
@@ -182,7 +193,13 @@ export default {
}
});
- setContainers(existingContainers);
+ // add check for total size not equal to 100? if comp and containers same, probably safe
+
+ if (isConfigurationChanged) {
+ console.log('yo');
+ setContainers(existingContainers);
+ mutateContainers();
+ }
});
function addItem(_domainObject) {
@@ -267,6 +284,16 @@ export default {
openmct.objects.mutate(domainObject, 'configuration.containers', containers.value);
}
+ // context action called from outside component
+ function toggleFixedContextAction(index, fixed) {
+ toggleFixed(index, fixed);
+ }
+
+ // context action called from outside component
+ function changeSizeContextAction(index, size) {
+ sizeFixedContainer(index, size);
+ }
+
onBeforeUnmount(() => {
compositionCollection.off('add', addItem);
compositionCollection.off('remove', removeItem);
@@ -292,7 +319,9 @@ export default {
startContainerResizing,
containerResizing,
endContainerResizing,
- mutateContainers
+ mutateContainers,
+ toggleFixedContextAction,
+ changeSizeContextAction
};
},
data() {
diff --git a/src/plugins/timeline/TimelineViewProvider.js b/src/plugins/timeline/TimelineViewProvider.js
index b76409057f..3bb43875d3 100644
--- a/src/plugins/timeline/TimelineViewProvider.js
+++ b/src/plugins/timeline/TimelineViewProvider.js
@@ -61,7 +61,8 @@ export default function TimelineViewProvider(openmct, extendedLinesBus) {
isEditing
};
},
- template: ''
+ template:
+ ''
},
{
app: openmct.app,
@@ -71,6 +72,11 @@ export default function TimelineViewProvider(openmct, extendedLinesBus) {
component = vNode.componentInstance;
_destroy = destroy;
},
+ contextAction(action, ...args) {
+ if (component?.$refs?.timeline?.[action]) {
+ component.$refs.timeline[action](...args);
+ }
+ },
onEditModeChange(isEditing) {
component.isEditing = isEditing;
},
diff --git a/src/plugins/timeline/plugin.js b/src/plugins/timeline/plugin.js
index f0fd2294f7..9704fd7234 100644
--- a/src/plugins/timeline/plugin.js
+++ b/src/plugins/timeline/plugin.js
@@ -23,9 +23,9 @@
import { configuration } from './configuration.js';
import ExtendedLinesBus from './ExtendedLinesBus.js';
import TimelineCompositionPolicy from './TimelineCompositionPolicy.js';
+import TimelineElementsViewProvider from './TimelineElementsViewProvider.js';
import timelineInterceptor from './timelineInterceptor.js';
import TimelineViewProvider from './TimelineViewProvider.js';
-
const extendedLinesBus = new ExtendedLinesBus();
export { extendedLinesBus };
@@ -48,6 +48,7 @@ export default function () {
openmct.composition.addPolicy(new TimelineCompositionPolicy(openmct).allow);
openmct.objectViews.addProvider(new TimelineViewProvider(openmct, extendedLinesBus));
+ openmct.inspectorViews.addProvider(new TimelineElementsViewProvider(openmct));
}
install.extendedLinesBus = extendedLinesBus;
diff --git a/src/plugins/timeline/timeline.scss b/src/plugins/timeline/timeline.scss
index dc81b19676..b83c64ea32 100644
--- a/src/plugins/timeline/timeline.scss
+++ b/src/plugins/timeline/timeline.scss
@@ -45,6 +45,18 @@
}
}
+ &__content {
+ &.--scales {
+ flex-grow: 1;
+ flex-shrink: 1;
+ }
+
+ &.--fixed {
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+ }
+
&__overlay-lines {
@include abs();
opacity: 0.5;
diff --git a/src/utils/vue/useFlexContainers.js b/src/utils/vue/useFlexContainers.js
index 0475c70fd2..f2d68c59f2 100644
--- a/src/utils/vue/useFlexContainers.js
+++ b/src/utils/vue/useFlexContainers.js
@@ -89,7 +89,9 @@ export function useFlexContainers(
const beforeContainer = getBeforeContainer(index);
const afterContainer = getAfterContainer(index);
- maxMoveSize.value = beforeContainer.size + afterContainer.size;
+ if (beforeContainer && afterContainer && !beforeContainer.fixed && !afterContainer.fixed) {
+ maxMoveSize.value = beforeContainer.size + afterContainer.size;
+ }
}
function getBeforeContainer(index) {
@@ -108,24 +110,14 @@ export function useFlexContainers(
const afterContainer = getAfterContainer(index);
const percentageMoved = Math.round((delta / getElSize()) * 100);
- if (beforeContainer === undefined || afterContainer === undefined) {
+ if (beforeContainer && afterContainer && !beforeContainer.fixed && !afterContainer.fixed) {
+ beforeContainer.size = getContainerSize(beforeContainer.size + percentageMoved);
+ afterContainer.size = getContainerSize(afterContainer.size - percentageMoved);
+ } else {
console.warn(
- 'Cannot drag to resize a single flex sized containers. Use Elements Tab in Inspector to resize fixed containers.'
+ 'Drag requires two flexible containers. Use Elements Tab in Inspector to resize.'
);
-
- return;
}
-
- if (beforeContainer.fixed === true && afterContainer.fixed === true) {
- console.warn(
- 'Cannot drag to resize two fixed sized containers. Use Elements Tab in Inspector to resize.'
- );
-
- return;
- }
-
- beforeContainer.size = getContainerSize(beforeContainer.size + percentageMoved);
- afterContainer.size = getContainerSize(afterContainer.size - percentageMoved);
}
function endContainerResizing() {
@@ -163,45 +155,92 @@ export function useFlexContainers(
* due to composition edits outside of view
*
* @param {*} items
+ * @param {Number} (optional) index of the item to apply excess to in the event of rounding errors
*/
- function sizeItems(items) {
+ function sizeItems(items, index) {
let totalSize;
- const itemsWithSize = items.filter((item) => item.size);
- const itemsWithoutSize = items.filter((item) => !item.size);
- // total number of items, adjusted by each item scale
- const totalScale = items.reduce((total, item) => {
+ const flexItems = items.filter((item) => !item.fixed);
+ const flexItemsWithSize = flexItems.filter((item) => item.size);
+ const flexItemsWithoutSize = flexItems.filter((item) => !item.size);
+ // total number of flexible items, adjusted by each item scale
+ const totalScale = flexItems.reduce((total, item) => {
const scale = item.scale ?? 1;
return total + scale;
}, 0);
- itemsWithoutSize.forEach((item) => {
+ flexItemsWithoutSize.forEach((item) => {
const scale = item.scale ?? 1;
item.size = Math.round((100 * scale) / totalScale);
});
- totalSize = items.reduce((total, item) => total + item.size, 0);
+ totalSize = flexItems.reduce((total, item) => total + item.size, 0);
if (totalSize > 100) {
- const addedSize = itemsWithoutSize.reduce((total, item) => total + item.size, 0);
+ const addedSize = flexItemsWithoutSize.reduce((total, item) => total + item.size, 0);
const remainingSize = 100 - addedSize;
- itemsWithSize.forEach((item) => {
+ flexItemsWithSize.forEach((item) => {
const scale = item.scale ?? 1;
item.size = Math.round((item.size * scale * remainingSize) / 100);
});
} else if (totalSize < 100) {
const sizeToFill = 100 - totalSize;
- items.forEach((item) => {
+ flexItems.forEach((item) => {
const scale = item.scale ?? 1;
item.size = Math.round((item.size * scale * 100) / sizeToFill);
});
}
// Ensure items add up to 100 in case of rounding error.
- totalSize = items.reduce((total, item) => total + item.size, 0);
+ totalSize = flexItems.reduce((total, item) => total + item.size, 0);
const excess = Math.round(100 - totalSize);
- items[items.length - 1].size += excess;
+
+ if (excess) {
+ const _index = index !== undefined && !items[index].fixed ? index : items.length - 1;
+ items[_index].size += excess;
+ }
+ }
+
+ function toggleFixed(index, fixed) {
+ let addExcessToContainer;
+ const remainingItems = containers.value.slice();
+ const container = remainingItems.splice(index, 1)[0];
+
+ if (container.fixed !== fixed) {
+ if (fixed) {
+ const sizeToFill = 100 - container.size;
+ container.size = Math.round((container.size / 100) * getElSize());
+ remainingItems.forEach((item) => {
+ const scale = item.scale ?? 1;
+ item.size = Math.round((item.size * scale * 100) / sizeToFill);
+ });
+ } else {
+ container.size = Math.round((container.size * 100) / (getElSize() + container.size));
+ addExcessToContainer = index;
+ const remainingSize = 100 - container.size;
+ remainingItems.forEach((item) => {
+ const scale = item.scale ?? 1;
+ item.size = Math.round((item.size * scale * remainingSize) / 100);
+ });
+ }
+
+ container.fixed = fixed;
+ sizeItems(containers.value, addExcessToContainer);
+ callback?.();
+ }
+ }
+
+ function sizeFixedContainer(index, size) {
+ const container = containers.value[index];
+
+ if (container.fixed) {
+ container.size = size;
+
+ callback?.();
+ } else {
+ console.warn('Use view drag resizing to resize flexible containers.');
+ }
}
return {
@@ -212,6 +251,8 @@ export function useFlexContainers(
containers,
startContainerResizing,
containerResizing,
- endContainerResizing
+ endContainerResizing,
+ toggleFixed,
+ sizeFixedContainer
};
}