From 07de6c0e73c199b87c642a9be4fcf2e66596800b Mon Sep 17 00:00:00 2001 From: David Tsay Date: Fri, 10 Jan 2025 12:58:05 -0800 Subject: [PATCH 01/47] move ResizeHandle to reusable ui location --- src/plugins/flexibleLayout/components/ContainerComponent.vue | 3 ++- src/plugins/flexibleLayout/components/FlexibleLayout.vue | 3 ++- .../components => ui/layout/ResizeHandle}/ResizeHandle.vue | 0 3 files changed, 4 insertions(+), 2 deletions(-) rename src/{plugins/flexibleLayout/components => ui/layout/ResizeHandle}/ResizeHandle.vue (100%) diff --git a/src/plugins/flexibleLayout/components/ContainerComponent.vue b/src/plugins/flexibleLayout/components/ContainerComponent.vue index 8d6c2b026d6..f216259b80e 100644 --- a/src/plugins/flexibleLayout/components/ContainerComponent.vue +++ b/src/plugins/flexibleLayout/components/ContainerComponent.vue @@ -80,9 +80,10 @@ diff --git a/src/plugins/timeline/TimelineElementsPool.vue b/src/plugins/timeline/TimelineElementsPool.vue new file mode 100644 index 00000000000..43c9e3df2ab --- /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 00000000000..73b5023915f --- /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 7d3db098d58..e1afbc30351 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" > - + + diff --git a/src/plugins/timeline/TimelineViewLayout.vue b/src/plugins/timeline/TimelineViewLayout.vue index 20f2f8e2e9c..b1afb5c7808 100644 --- a/src/plugins/timeline/TimelineViewLayout.vue +++ b/src/plugins/timeline/TimelineViewLayout.vue @@ -199,7 +199,17 @@ export default { if (isConfigurationChanged) { console.log('yo'); setContainers(existingContainers); - mutateContainers(); + } + + const selection = openmct.selection.get()[0]; + const selectionContext = selection?.[0]?.context; + const selectionDomainObject = selectionContext?.item; + const selectionType = selectionDomainObject?.type; + + if (selectionType === 'time-strip') { + selectionContext.containers = containers.value; + selectionContext.swimLaneLabelWidth = swimLaneLabelWidth.value; + openmct.selection.select(selection); } }); @@ -295,6 +305,12 @@ export default { sizeFixedContainer(index, size); } + // context action called from outside component + function changeSwimLaneLabelWidthContextAction(size) { + swimLaneLabelWidth.value = size; + mutateSwimLaneLabelWidth(); + } + onBeforeUnmount(() => { compositionCollection.off('add', addItem); compositionCollection.off('remove', removeItem); @@ -322,7 +338,8 @@ export default { endContainerResizing, mutateContainers, toggleFixedContextAction, - changeSizeContextAction + changeSizeContextAction, + changeSwimLaneLabelWidthContextAction }; }, data() { From 30d9c515df21ec5aec0bbe452e61ea9c36be09cb Mon Sep 17 00:00:00 2001 From: David Tsay Date: Fri, 30 May 2025 13:33:53 -0700 Subject: [PATCH 24/47] sizing is now more exact --- .../timeline/TimelineElementsContent.vue | 10 ++++- src/plugins/timeline/TimelineElementsPool.vue | 32 +++++++-------- src/plugins/timeline/TimelineViewLayout.vue | 7 +++- src/utils/vue/useFlexContainers.js | 41 +++++++++++++------ 4 files changed, 56 insertions(+), 34 deletions(-) diff --git a/src/plugins/timeline/TimelineElementsContent.vue b/src/plugins/timeline/TimelineElementsContent.vue index 5f3bf8adbe1..e726bcadbb9 100644 --- a/src/plugins/timeline/TimelineElementsContent.vue +++ b/src/plugins/timeline/TimelineElementsContent.vue @@ -18,7 +18,7 @@ diff --git a/src/plugins/timeline/TimelineViewLayout.vue b/src/plugins/timeline/TimelineViewLayout.vue index b1afb5c7808..0899df2dc93 100644 --- a/src/plugins/timeline/TimelineViewLayout.vue +++ b/src/plugins/timeline/TimelineViewLayout.vue @@ -197,10 +197,13 @@ export default { // add check for total size not equal to 100? if comp and containers same, probably safe if (isConfigurationChanged) { - console.log('yo'); setContainers(existingContainers); } + setSelectionContext(); + }); + + function setSelectionContext() { const selection = openmct.selection.get()[0]; const selectionContext = selection?.[0]?.context; const selectionDomainObject = selectionContext?.item; @@ -211,7 +214,7 @@ export default { selectionContext.swimLaneLabelWidth = swimLaneLabelWidth.value; openmct.selection.select(selection); } - }); + } function addItem(_domainObject) { let rowCount = 0; diff --git a/src/utils/vue/useFlexContainers.js b/src/utils/vue/useFlexContainers.js index f2d68c59f20..0a2a32b995d 100644 --- a/src/utils/vue/useFlexContainers.js +++ b/src/utils/vue/useFlexContainers.js @@ -126,8 +126,10 @@ export function useFlexContainers( function getElSize() { const elSize = rowsLayout === true ? element.value.offsetHeight : element.value.offsetWidth; + // TODO FIXME temporary patch for timeline + const timelineHeight = 32; - return elSize - fixedContainersSize.value; + return elSize - fixedContainersSize.value - timelineHeight; } function getContainerSize(size) { @@ -160,6 +162,16 @@ export function useFlexContainers( function sizeItems(items, index) { let totalSize; const flexItems = items.filter((item) => !item.fixed); + + if (flexItems.length === 0) { + return; + } + + if (flexItems.length === 1) { + flexItems[0].size = 100; + return; + } + const flexItemsWithSize = flexItems.filter((item) => item.size); const flexItemsWithoutSize = flexItems.filter((item) => !item.size); // total number of flexible items, adjusted by each item scale @@ -209,24 +221,27 @@ export function useFlexContainers( if (container.fixed !== fixed) { if (fixed) { - const sizeToFill = 100 - container.size; + // toggle flex to fixed 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); - }); + container.fixed = fixed; + sizeItems(remainingItems); } else { - container.size = Math.round((container.size * 100) / (getElSize() + container.size)); + // toggle fixed to flex addExcessToContainer = index; + container.size = Math.round((container.size * 100) / (getElSize() + container.size)); const remainingSize = 100 - container.size; - remainingItems.forEach((item) => { - const scale = item.scale ?? 1; - item.size = Math.round((item.size * scale * remainingSize) / 100); - }); + + remainingItems + .filter((item) => !item.fixed) + .forEach((item) => { + const scale = item.scale ?? 1; + item.size = Math.round((item.size * scale * remainingSize) / 100); + }); + + container.fixed = fixed; + sizeItems(containers.value, addExcessToContainer); } - container.fixed = fixed; - sizeItems(containers.value, addExcessToContainer); callback?.(); } } From dabbc3e1041c7120ce24fbff75ccfbb08a0c7e20 Mon Sep 17 00:00:00 2001 From: David Tsay Date: Tue, 10 Jun 2025 10:45:23 -0700 Subject: [PATCH 25/47] WIP: move logic into setup --- src/plugins/timeline/TimelineViewLayout.vue | 422 +++++++++++--------- 1 file changed, 232 insertions(+), 190 deletions(-) diff --git a/src/plugins/timeline/TimelineViewLayout.vue b/src/plugins/timeline/TimelineViewLayout.vue index 0899df2dc93..5fbbf05dc54 100644 --- a/src/plugins/timeline/TimelineViewLayout.vue +++ b/src/plugins/timeline/TimelineViewLayout.vue @@ -77,7 +77,7 @@ import _ from 'lodash'; import { useDragResizer } from 'utils/vue/useDragResizer.js'; import { useFlexContainers } from 'utils/vue/useFlexContainers.js'; -import { inject, onBeforeUnmount, provide, ref, toRaw } from 'vue'; +import { inject, onBeforeUnmount, onMounted, provide, ref, toRaw, watch } from 'vue'; import SwimLane from '@/ui/components/swim-lane/SwimLane.vue'; import ResizeHandle from '@/ui/layout/ResizeHandle/ResizeHandle.vue'; @@ -90,7 +90,7 @@ import ExtendedLinesOverlay from './ExtendedLinesOverlay.vue'; import TimelineObjectView from './TimelineObjectView.vue'; const AXES_PADDING = 20; -const PLOT_ITEM_H_PX = 100; +// const PLOT_ITEM_H_PX = 100; export default { components: { @@ -110,18 +110,144 @@ export default { const openmct = inject('openmct'); const domainObject = inject('domainObject'); const path = inject('path'); - const extendedLinesBus = inject('extendedLinesBus'); + const items = ref([]); + + // COMPOSABLE - Time Contexts + const timeSystems = ref([]); + const useIndependentTime = ref(domainObject.configuration.useIndependentTime === true); + const timeOptions = ref(domainObject.configuration.timeOptions); + let timeContext; + + const setupTimeContexts = { + timeSystems + }; + + onMounted(() => { + setTimeContext(); + }); + + onBeforeUnmount(() => { + stopFollowingTimeContext(); + }); + + function getTimeSystems() { + openmct.time.getAllTimeSystems().forEach((timeSystem) => { + timeSystems.value.push({ + timeSystem, + bounds: getBoundsForTimeSystem(timeSystem) + }); + }); + } + + function getBoundsForTimeSystem(timeSystem) { + const currentBounds = timeContext.getBounds(); + + //TODO: Some kind of translation via an offset? of current bounds to target timeSystem + return currentBounds; + } + + function updateViewBounds() { + const bounds = timeContext.getBounds(); + updateContentHeight(); + + let currentTimeSystemIndex = timeSystems.value.findIndex( + (item) => item.timeSystem.key === openmct.time.getTimeSystem().key + ); + if (currentTimeSystemIndex > -1) { + let currentTimeSystem = { + ...timeSystems.value[currentTimeSystemIndex] + }; + currentTimeSystem.bounds = bounds; + timeSystems.value.splice(currentTimeSystemIndex, 1, currentTimeSystem); + } + } + + function setTimeContext() { + stopFollowingTimeContext(); + + timeContext = openmct.time.getContextForView(path); + getTimeSystems(); + updateViewBounds(); + timeContext.on('boundsChanged', updateViewBounds); + timeContext.on('clockChanged', updateViewBounds); + } + + function stopFollowingTimeContext() { + if (timeContext) { + timeContext.off('boundsChanged', updateViewBounds); + timeContext.off('clockChanged', updateViewBounds); + } + } + + // COMPOSABLE - Content Height + const timelineHolder = ref(null); + const height = ref(null); + let handleContentResize; + let contentResizeObserver; + + const setupContentHeight = { + timelineHolder, + height + }; + + onMounted(() => { + handleContentResize = _.debounce(updateContentHeight, 500); + contentResizeObserver = new ResizeObserver(handleContentResize); + contentResizeObserver.observe(timelineHolder.value); + }); + + onBeforeUnmount(() => { + handleContentResize.cancel(); + contentResizeObserver.disconnect(); + }); + + function updateContentHeight() { + const clientHeight = getClientHeight(); + if (height.value !== clientHeight) { + height.value = clientHeight; + } + calculateExtendedLinesLeftOffset(); + } + + function getClientHeight() { + let clientHeight = timelineHolder.value.getBoundingClientRect().height; + + if (!clientHeight) { + //this is a hack - need a better way to find the parent of this component + let parent = timelineHolder.value.closest('.c-object-view'); + if (parent) { + clientHeight = parent.getBoundingClientRect().height; + } + } + + return clientHeight; + } + + // COMPOSABLE - Composition const composition = ref(null); let isCompositionLoaded = false; - const existingContainers = []; + const compositionCollection = openmct.composition.get(toRaw(domainObject)); - compositionCollection.on('add', addItem); - compositionCollection.on('remove', removeItem); - compositionCollection.on('reorder', reorder); - const items = ref([]); + onMounted(() => { + compositionCollection.on('add', addItem); + compositionCollection.on('remove', removeItem); + compositionCollection.on('reorder', reorder); + }); + + onBeforeUnmount(() => { + compositionCollection.off('add', addItem); + compositionCollection.off('remove', removeItem); + compositionCollection.off('reorder', reorder); + }); + + // COMPOSABLE - Extended Lines + const extendedLinesBus = inject('extendedLinesBus'); const extendedLinesPerKey = ref({}); + const extendedLinesLeftOffset = ref(0); + const extendedLineHover = ref({}); + const extendedLineSelection = ref({}); const { alignment: alignmentData, reset: resetAlignment } = useAlignment( domainObject, @@ -129,7 +255,71 @@ export default { openmct ); - // Drag resizer - Swimlane column width + const setupExtendedLines = { + extendedLinesBus, + extendedLinesPerKey, + extendedLinesLeftOffset, + extendedLineHover, + extendedLineSelection, + calculateExtendedLinesLeftOffset + }; + + onMounted(() => { + openmct.selection.on('change', checkForLineSelection); + extendedLinesBus.addEventListener('update-extended-lines', updateExtendedLines); + extendedLinesBus.addEventListener('update-extended-hover', updateExtendedHover); + }); + + onBeforeUnmount(() => { + openmct.selection.off('change', checkForLineSelection); + extendedLinesBus.removeEventListener('update-extended-lines', updateExtendedLines); + extendedLinesBus.removeEventListener('update-extended-hover', updateExtendedHover); + resetAlignment(); + }); + + watch(alignmentData, () => calculateExtendedLinesLeftOffset(), { deep: true }); + + function calculateExtendedLinesLeftOffset() { + extendedLinesLeftOffset.value = alignmentData.leftWidth + calculateSwimlaneOffset(); + } + + function calculateSwimlaneOffset() { + const firstSwimLane = timelineHolder.value.querySelector('.c-swimlane__lane-object'); + if (firstSwimLane) { + const timelineHolderRect = timelineHolder.value.getBoundingClientRect(); + const laneObjectRect = firstSwimLane.getBoundingClientRect(); + const offset = laneObjectRect.left - timelineHolderRect.left; + const hasAxes = alignmentData.axes && Object.keys(alignmentData.axes).length > 0; + const swimLaneOffset = hasAxes ? offset + AXES_PADDING : offset; + return swimLaneOffset; + } else { + return 0; + } + } + + function updateExtendedLines(event) { + const { keyString, lines } = event.detail; + extendedLinesPerKey.value[keyString] = lines; + } + function updateExtendedHover(event) { + const { keyString, id } = event.detail; + extendedLineHover.value = { keyString, id }; + } + + function checkForLineSelection(selection) { + const selectionContext = selection?.[0]?.[0]?.context; + const eventType = selectionContext?.type; + if (eventType === 'time-strip-event-selection') { + const event = selectionContext.event; + const selectedObject = selectionContext.item; + const keyString = openmct.objects.makeKeyString(selectedObject.identifier); + extendedLineSelection.value = { keyString, id: event?.time }; + } else { + extendedLineSelection.value = {}; + } + } + + // COMPOSABLE - Swimlane label width const { x: swimLaneLabelWidth, mousedown } = useDragResizer({ initialX: domainObject.configuration.swimLaneLabelWidth, callback: mutateSwimLaneLabelWidth @@ -138,6 +328,10 @@ export default { provide('swimLaneLabelWidth', swimLaneLabelWidth); provide('mousedown', mousedown); + const setupSwimLaneLabelWidth = { + changeSwimLaneLabelWidthContextAction + }; + function mutateSwimLaneLabelWidth() { openmct.objects.mutate( domainObject, @@ -146,8 +340,16 @@ export default { ); } - // Flex containers - Swimlane height - const timelineHolder = ref(null); + // context action called from outside component + function changeSwimLaneLabelWidthContextAction(size) { + swimLaneLabelWidth.value = size; + mutateSwimLaneLabelWidth(); + } + + // COMPOSABLE - flexible containers for swimlane vertical resizing + const existingContainers = []; + + watch(items, () => console.log('changed')); const { addContainer, @@ -166,6 +368,15 @@ export default { callback: mutateContainers }); + const setupFlexContainers = { + containers, + startContainerResizing, + containerResizing, + endContainerResizing, + toggleFixedContextAction, + changeSizeContextAction + }; + compositionCollection.load().then((loadedComposition) => { composition.value = loadedComposition; isCompositionLoaded = true; @@ -231,17 +442,17 @@ export default { rowCount = Object.keys(_domainObject.configuration.swimlaneVisibility).length; } const isEventTelemetry = hasEventTelemetry(_domainObject); - const height = - typeKey === 'telemetry.plot.stacked' - ? `${_domainObject.composition.length * PLOT_ITEM_H_PX}px` - : 'auto'; + // const itemHeight = + // typeKey === 'telemetry.plot.stacked' + // ? `${_domainObject.composition.length * PLOT_ITEM_H_PX}px` + // : 'auto'; const item = { domainObject: _domainObject, objectPath, type, keyString, rowCount, - height, + // height: itemHeight, isEventTelemetry }; @@ -308,187 +519,18 @@ export default { sizeFixedContainer(index, size); } - // context action called from outside component - function changeSwimLaneLabelWidthContextAction(size) { - swimLaneLabelWidth.value = size; - mutateSwimLaneLabelWidth(); - } - - onBeforeUnmount(() => { - compositionCollection.off('add', addItem); - compositionCollection.off('remove', removeItem); - compositionCollection.off('reorder', reorder); - }); - return { openmct, domainObject, path, composition, - extendedLinesBus, - extendedLinesPerKey, - containers, - getContainerSize, - timelineHolder, items, - addContainer, - removeContainer, - reorderContainers, - alignmentData, - resetAlignment, - startContainerResizing, - containerResizing, - endContainerResizing, - mutateContainers, - toggleFixedContextAction, - changeSizeContextAction, - changeSwimLaneLabelWidthContextAction + ...setupTimeContexts, + ...setupContentHeight, + ...setupExtendedLines, + ...setupSwimLaneLabelWidth, + ...setupFlexContainers }; - }, - data() { - return { - timeSystems: [], - height: 0, - useIndependentTime: this.domainObject.configuration.useIndependentTime === true, - timeOptions: this.domainObject.configuration.timeOptions, - extendedLineHover: {}, - extendedLineSelection: {}, - extendedLinesLeftOffset: 0 - }; - }, - watch: { - alignmentData: { - handler() { - this.calculateExtendedLinesLeftOffset(); - }, - deep: true - } - }, - beforeUnmount() { - this.resetAlignment(); - this.stopFollowingTimeContext(); - this.handleContentResize.cancel(); - this.contentResizeObserver.disconnect(); - this.openmct.selection.off('change', this.checkForLineSelection); - this.extendedLinesBus.removeEventListener('update-extended-lines', this.updateExtendedLines); - this.extendedLinesBus.removeEventListener('update-extended-hover', this.updateExtendedHover); - }, - mounted() { - this.setTimeContext(); - - this.extendedLinesBus.addEventListener('update-extended-lines', this.updateExtendedLines); - this.extendedLinesBus.addEventListener('update-extended-hover', this.updateExtendedHover); - this.openmct.selection.on('change', this.checkForLineSelection); - - this.handleContentResize = _.debounce(this.handleContentResize, 500); - this.contentResizeObserver = new ResizeObserver(this.handleContentResize); - this.contentResizeObserver.observe(this.$refs.timelineHolder); - }, - methods: { - handleContentResize() { - this.updateContentHeight(); - }, - updateContentHeight() { - const clientHeight = this.getClientHeight(); - if (this.height !== clientHeight) { - this.height = clientHeight; - } - this.calculateExtendedLinesLeftOffset(); - }, - getClientHeight() { - let clientHeight = this.$refs.timelineHolder.getBoundingClientRect().height; - - if (!clientHeight) { - //this is a hack - need a better way to find the parent of this component - let parent = this.$el.closest('.c-object-view'); - if (parent) { - clientHeight = parent.getBoundingClientRect().height; - } - } - - return clientHeight; - }, - getTimeSystems() { - const timeSystems = this.openmct.time.getAllTimeSystems(); - timeSystems.forEach((timeSystem) => { - this.timeSystems.push({ - timeSystem, - bounds: this.getBoundsForTimeSystem(timeSystem) - }); - }); - }, - getBoundsForTimeSystem(timeSystem) { - const currentBounds = this.timeContext.getBounds(); - - //TODO: Some kind of translation via an offset? of current bounds to target timeSystem - return currentBounds; - }, - updateViewBounds() { - const bounds = this.timeContext.getBounds(); - this.updateContentHeight(); - let currentTimeSystemIndex = this.timeSystems.findIndex( - (item) => item.timeSystem.key === this.openmct.time.getTimeSystem().key - ); - if (currentTimeSystemIndex > -1) { - let currentTimeSystem = { - ...this.timeSystems[currentTimeSystemIndex] - }; - currentTimeSystem.bounds = bounds; - this.timeSystems.splice(currentTimeSystemIndex, 1, currentTimeSystem); - } - }, - setTimeContext() { - this.stopFollowingTimeContext(); - - this.timeContext = this.openmct.time.getContextForView(this.path); - this.getTimeSystems(); - this.updateViewBounds(); - this.timeContext.on('boundsChanged', this.updateViewBounds); - this.timeContext.on('clockChanged', this.updateViewBounds); - }, - stopFollowingTimeContext() { - if (this.timeContext) { - this.timeContext.off('boundsChanged', this.updateViewBounds); - this.timeContext.off('clockChanged', this.updateViewBounds); - } - }, - updateExtendedLines(event) { - const { keyString, lines } = event.detail; - this.extendedLinesPerKey[keyString] = lines; - }, - updateExtendedHover(event) { - const { keyString, id } = event.detail; - this.extendedLineHover = { keyString, id }; - }, - checkForLineSelection(selection) { - const selectionContext = selection?.[0]?.[0]?.context; - const eventType = selectionContext?.type; - if (eventType === 'time-strip-event-selection') { - const event = selectionContext.event; - const selectedObject = selectionContext.item; - const keyString = this.openmct.objects.makeKeyString(selectedObject.identifier); - this.extendedLineSelection = { keyString, id: event?.time }; - } else { - this.extendedLineSelection = {}; - } - }, - calculateExtendedLinesLeftOffset() { - const swimLaneOffset = this.calculateSwimlaneOffset(); - this.extendedLinesLeftOffset = this.alignmentData.leftWidth + swimLaneOffset; - }, - calculateSwimlaneOffset() { - const firstSwimLane = this.$el.querySelector('.c-swimlane__lane-object'); - if (firstSwimLane) { - const timelineHolderRect = this.$refs.timelineHolder.getBoundingClientRect(); - const laneObjectRect = firstSwimLane.getBoundingClientRect(); - const offset = laneObjectRect.left - timelineHolderRect.left; - const hasAxes = this.alignmentData.axes && Object.keys(this.alignmentData.axes).length > 0; - const swimLaneOffset = hasAxes ? offset + AXES_PADDING : offset; - return swimLaneOffset; - } else { - return 0; - } - } } }; From 29b50c153a043c8c00dc6f11582d5b3c51c612b9 Mon Sep 17 00:00:00 2001 From: David Tsay Date: Wed, 11 Jun 2025 09:53:43 -0700 Subject: [PATCH 26/47] move rounding excess into separate function fix sizing logic --- src/utils/vue/useFlexContainers.js | 56 ++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/src/utils/vue/useFlexContainers.js b/src/utils/vue/useFlexContainers.js index 0a2a32b995d..34ff0f8c0ce 100644 --- a/src/utils/vue/useFlexContainers.js +++ b/src/utils/vue/useFlexContainers.js @@ -54,7 +54,7 @@ export function useFlexContainers( containers.value.push(container); sizeItems(containers.value); - + roundExcess(containers.value); callback?.(); } @@ -65,6 +65,7 @@ export function useFlexContainers( if (isFlexContainer) { sizeItems(containers.value); + roundExcess(containers.value); } callback?.(); @@ -83,6 +84,7 @@ export function useFlexContainers( function setContainers(_containers) { containers.value = _containers; sizeItems(containers.value); + roundExcess(containers.value); } function startContainerResizing(index) { @@ -148,7 +150,6 @@ export function useFlexContainers( * 2. resize item sizes to equal 100 * if total size < 100, resize all items * if total size > 100, resize only items not resized in step 1 (newly added) - * 3. round excess and apply to last item * * Items may have a scale (ie. items with composition) * @@ -156,10 +157,11 @@ export function useFlexContainers( * such as composition out of sync with containers config * due to composition edits outside of view * + * Typically roundExcess is called afterwards to limit pixels and percents to integers + * * @param {*} items - * @param {Number} (optional) index of the item to apply excess to in the event of rounding errors */ - function sizeItems(items, index) { + function sizeItems(items) { let totalSize; const flexItems = items.filter((item) => !item.fixed); @@ -192,25 +194,44 @@ export function useFlexContainers( const remainingSize = 100 - addedSize; flexItemsWithSize.forEach((item) => { - const scale = item.scale ?? 1; - item.size = Math.round((item.size * scale * remainingSize) / 100); + item.size = Math.round((item.size * remainingSize) / 100); }); } else if (totalSize < 100) { - const sizeToFill = 100 - totalSize; - flexItems.forEach((item) => { - const scale = item.scale ?? 1; - item.size = Math.round((item.size * scale * 100) / sizeToFill); + item.size = Math.round((item.size * 100) / totalSize); }); } + } - // Ensure items add up to 100 in case of rounding error. - totalSize = flexItems.reduce((total, item) => total + item.size, 0); + /** + * + * Rounds excess and applies to one of the items + * if an optional index is not specified, excess applied to last item + * + * @param {*} items + * @param {Number} (optional) index of the item to apply excess to in the event of rounding errors + */ + function roundExcess(items, specifiedIndex) { + const flexItems = items.filter((item) => !item.fixed); + + if (!flexItems.length) { + return; + } + + const totalSize = flexItems.reduce((total, item) => total + item.size, 0); const excess = Math.round(100 - totalSize); + let index; + + if (specifiedIndex !== undefined && items[specifiedIndex] && !items[specifiedIndex].fixed) { + index = specifiedIndex; + } + + if (index === undefined) { + index = items.findLastIndex((item) => !item.fixed); + } - if (excess) { - const _index = index !== undefined && !items[index].fixed ? index : items.length - 1; - items[_index].size += excess; + if (index > -1) { + items[index].size += excess; } } @@ -234,14 +255,13 @@ export function useFlexContainers( remainingItems .filter((item) => !item.fixed) .forEach((item) => { - const scale = item.scale ?? 1; - item.size = Math.round((item.size * scale * remainingSize) / 100); + item.size = Math.round((item.size * remainingSize) / 100); }); container.fixed = fixed; - sizeItems(containers.value, addExcessToContainer); } + roundExcess(containers.value, addExcessToContainer); callback?.(); } } From e9e7a39cf2b318ad4a571e25cd2f2ad8f0df0993 Mon Sep 17 00:00:00 2001 From: David Tsay Date: Wed, 11 Jun 2025 09:59:19 -0700 Subject: [PATCH 27/47] explain setup objects --- src/plugins/timeline/TimelineViewLayout.vue | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/plugins/timeline/TimelineViewLayout.vue b/src/plugins/timeline/TimelineViewLayout.vue index 5fbbf05dc54..d4544a46b0f 100644 --- a/src/plugins/timeline/TimelineViewLayout.vue +++ b/src/plugins/timeline/TimelineViewLayout.vue @@ -115,10 +115,9 @@ export default { // COMPOSABLE - Time Contexts const timeSystems = ref([]); - const useIndependentTime = ref(domainObject.configuration.useIndependentTime === true); - const timeOptions = ref(domainObject.configuration.timeOptions); let timeContext; + // returned from composition api setup() const setupTimeContexts = { timeSystems }; @@ -186,6 +185,7 @@ export default { let handleContentResize; let contentResizeObserver; + // returned from composition api setup() const setupContentHeight = { timelineHolder, height @@ -255,6 +255,7 @@ export default { openmct ); + // returned from composition api setup() const setupExtendedLines = { extendedLinesBus, extendedLinesPerKey, @@ -328,6 +329,7 @@ export default { provide('swimLaneLabelWidth', swimLaneLabelWidth); provide('mousedown', mousedown); + // returned from composition api setup() const setupSwimLaneLabelWidth = { changeSwimLaneLabelWidthContextAction }; @@ -368,6 +370,7 @@ export default { callback: mutateContainers }); + // returned from composition api setup() const setupFlexContainers = { containers, startContainerResizing, From afe06c42c1e9c0c59a63f1c05903feaf58127096 Mon Sep 17 00:00:00 2001 From: David Tsay Date: Wed, 11 Jun 2025 10:05:59 -0700 Subject: [PATCH 28/47] remove unused function --- src/plugins/timeline/TimelineViewLayout.vue | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/plugins/timeline/TimelineViewLayout.vue b/src/plugins/timeline/TimelineViewLayout.vue index d4544a46b0f..ba82701e97c 100644 --- a/src/plugins/timeline/TimelineViewLayout.vue +++ b/src/plugins/timeline/TimelineViewLayout.vue @@ -500,14 +500,6 @@ export default { return hasDomain && hasNoRange && hasNoImages; } - function getContainerSize(item) { - const containerForItem = containers.value.find((container) => - openmct.objects.areIdsEqual(container.domainObjectIdentifier, item.domainObject.identifier) - ); - - return containerForItem?.size; - } - function mutateContainers() { openmct.objects.mutate(domainObject, 'configuration.containers', containers.value); } From c13a2f5e9a4e6114d3837dd8ec46f2092fb6ae96 Mon Sep 17 00:00:00 2001 From: David Tsay Date: Wed, 11 Jun 2025 10:32:57 -0700 Subject: [PATCH 29/47] revert removal of height, as is needed for plans --- src/plugins/timeline/TimelineViewLayout.vue | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/timeline/TimelineViewLayout.vue b/src/plugins/timeline/TimelineViewLayout.vue index ba82701e97c..af6e3207834 100644 --- a/src/plugins/timeline/TimelineViewLayout.vue +++ b/src/plugins/timeline/TimelineViewLayout.vue @@ -90,7 +90,7 @@ import ExtendedLinesOverlay from './ExtendedLinesOverlay.vue'; import TimelineObjectView from './TimelineObjectView.vue'; const AXES_PADDING = 20; -// const PLOT_ITEM_H_PX = 100; +const PLOT_ITEM_H_PX = 100; export default { components: { @@ -445,17 +445,17 @@ export default { rowCount = Object.keys(_domainObject.configuration.swimlaneVisibility).length; } const isEventTelemetry = hasEventTelemetry(_domainObject); - // const itemHeight = - // typeKey === 'telemetry.plot.stacked' - // ? `${_domainObject.composition.length * PLOT_ITEM_H_PX}px` - // : 'auto'; + const itemHeight = + typeKey === 'telemetry.plot.stacked' + ? `${_domainObject.composition.length * PLOT_ITEM_H_PX}px` + : 'auto'; const item = { domainObject: _domainObject, objectPath, type, keyString, rowCount, - // height: itemHeight, + height: itemHeight, isEventTelemetry }; From 4404d8f4c012418e999f9582a4f605b78dc80f8c Mon Sep 17 00:00:00 2001 From: David Tsay Date: Wed, 11 Jun 2025 10:43:31 -0700 Subject: [PATCH 30/47] Revert "revert removal of height, as is needed for plans" This reverts commit 6f38ca9a7927cb81c3a2a3a7dd9631bf246438ac and removes commented code. --- src/plugins/timeline/TimelineViewLayout.vue | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/plugins/timeline/TimelineViewLayout.vue b/src/plugins/timeline/TimelineViewLayout.vue index af6e3207834..5a8d34423b3 100644 --- a/src/plugins/timeline/TimelineViewLayout.vue +++ b/src/plugins/timeline/TimelineViewLayout.vue @@ -90,7 +90,6 @@ import ExtendedLinesOverlay from './ExtendedLinesOverlay.vue'; import TimelineObjectView from './TimelineObjectView.vue'; const AXES_PADDING = 20; -const PLOT_ITEM_H_PX = 100; export default { components: { @@ -445,17 +444,13 @@ export default { rowCount = Object.keys(_domainObject.configuration.swimlaneVisibility).length; } const isEventTelemetry = hasEventTelemetry(_domainObject); - const itemHeight = - typeKey === 'telemetry.plot.stacked' - ? `${_domainObject.composition.length * PLOT_ITEM_H_PX}px` - : 'auto'; + const item = { domainObject: _domainObject, objectPath, type, keyString, rowCount, - height: itemHeight, isEventTelemetry }; From 9a0aa8757846da86117d5bd502137a72c80bd557 Mon Sep 17 00:00:00 2001 From: David Tsay Date: Mon, 23 Jun 2025 11:18:04 -0700 Subject: [PATCH 31/47] use custom elements view --- .../inspectorViews/elements/ElementsViewProvider.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/plugins/inspectorViews/elements/ElementsViewProvider.js b/src/plugins/inspectorViews/elements/ElementsViewProvider.js index 05ea7e1bb32..2148953d27b 100644 --- a/src/plugins/inspectorViews/elements/ElementsViewProvider.js +++ b/src/plugins/inspectorViews/elements/ElementsViewProvider.js @@ -24,17 +24,20 @@ import mount from 'utils/mount'; import ElementsPool from './ElementsPool.vue'; +const CUSTOM_ELEMENTS_VIEW_PROVIDER_TYPES = ['time-strip', 'telemetry.plot.overlay']; + export default function ElementsViewProvider(openmct) { return { 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'; const isFolder = selection?.[0]?.[0]?.context?.item?.type === 'folder'; + const type = selection?.[0]?.[0]?.context?.item?.type; + + const hasCustomElementsViewProvider = CUSTOM_ELEMENTS_VIEW_PROVIDER_TYPES.includes(type); - return hasValidSelection && !isOverlayPlot && !isFolder; + return hasValidSelection && !hasCustomElementsViewProvider && !isFolder;; }, view: function (selection) { let _destroy = null; From c9a6faae4b1de6f4572411530c7e18847508ac44 Mon Sep 17 00:00:00 2001 From: David Tsay Date: Mon, 23 Jun 2025 13:31:18 -0700 Subject: [PATCH 32/47] fix for composition mod outside of view breaking containers --- src/plugins/timeline/TimelineViewLayout.vue | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/plugins/timeline/TimelineViewLayout.vue b/src/plugins/timeline/TimelineViewLayout.vue index 5a8d34423b3..23f2f2e5d7d 100644 --- a/src/plugins/timeline/TimelineViewLayout.vue +++ b/src/plugins/timeline/TimelineViewLayout.vue @@ -42,7 +42,7 @@ -
+