Compare commits
9 Commits
dependabot
...
mission-st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14d6c57bfa | ||
|
|
31ee4d925e | ||
|
|
c24f310126 | ||
|
|
458b2822c3 | ||
|
|
f11e4aa7a1 | ||
|
|
ebc67ebbc9 | ||
|
|
0d1a6f97c2 | ||
|
|
5984315929 | ||
|
|
8040d1d5be |
@@ -100,6 +100,52 @@ export default class StatusAPI extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the currently logged in user set the mission status.
|
||||
* @returns {Promise<Boolean>} true if the currently logged in user can set the mission status, false otherwise.
|
||||
*/
|
||||
canSetMissionStatus() {
|
||||
const provider = this.#userAPI.getProvider();
|
||||
|
||||
if (provider.canSetMissionStatus) {
|
||||
return provider.canSetMissionStatus();
|
||||
} else {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the list of possible mission status options
|
||||
* @returns {Promise<MissionStatusOption[]>} the current mission status
|
||||
*/
|
||||
async getPossibleMissionStatusOptions() {
|
||||
const provider = this.#userAPI.getProvider();
|
||||
|
||||
if (provider.getPossibleMissionStatusOptions) {
|
||||
const possibleOptions = await provider.getPossibleMissionStatusOptions();
|
||||
|
||||
return possibleOptions;
|
||||
} else {
|
||||
this.#userAPI.error('User provider does not support mission status options');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the list of possible mission status options
|
||||
* @returns {Promise<MissionStatusOption[]>} the current mission status
|
||||
*/
|
||||
async getPossibleMissionStatuses() {
|
||||
const provider = this.#userAPI.getProvider();
|
||||
|
||||
if (provider.getPossibleMissionStatuses) {
|
||||
const possibleStatuses = await provider.getPossibleMissionStatuses();
|
||||
|
||||
return possibleStatuses;
|
||||
} else {
|
||||
this.#userAPI.error('User provider does not support mission statuses');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Array<Status>>} the complete list of possible states that an operator can reply to a poll question with.
|
||||
*/
|
||||
@@ -166,6 +212,21 @@ export default class StatusAPI extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MissionStatusRole} role
|
||||
* @param {MissionStatusOption} status
|
||||
* @returns {Promise<Boolean>} true if operation was successful, otherwise false.
|
||||
*/
|
||||
setStatusForMissionRole(role, status) {
|
||||
const provider = this.#userAPI.getProvider();
|
||||
|
||||
if (provider.setStatusForMissionRole) {
|
||||
return provider.setStatusForMissionRole(role, status);
|
||||
} else {
|
||||
this.#userAPI.error('User provider does not support setting mission role status');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the status of the provided role back to its default status.
|
||||
* @param {import("./UserAPI").Role} role The role to set the status for.
|
||||
@@ -276,6 +337,19 @@ export default class StatusAPI extends EventEmitter {
|
||||
* @property {Number} timestamp - The time that the poll question was set.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The MissionStatus type
|
||||
* @typedef {Object} MissionStatusOption
|
||||
* @extends {Status}
|
||||
* @property {String} color A color to be used when displaying the mission status
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MissionStatusRole
|
||||
* @property {String} key A unique identifier for this role
|
||||
* @property {String} label A human readable label for this role
|
||||
*/
|
||||
|
||||
/**
|
||||
* The Status type
|
||||
* @typedef {Object} Status
|
||||
|
||||
@@ -20,11 +20,7 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<template>
|
||||
<div
|
||||
:style="position"
|
||||
class="c-status-poll-panel c-status-poll-panel--operator"
|
||||
@click.stop="noop"
|
||||
>
|
||||
<div :style="position" class="c-status-poll-panel c-status-poll-panel--operator" @click.stop>
|
||||
<div class="c-status-poll-panel__section c-status-poll-panel__top">
|
||||
<div class="c-status-poll-panel__title">Status Poll</div>
|
||||
<div class="c-status-poll-panel__user-role icon-person">{{ role }}</div>
|
||||
@@ -191,8 +187,7 @@ export default {
|
||||
} else {
|
||||
return status;
|
||||
}
|
||||
},
|
||||
noop() {}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
57
src/plugins/userIndicator/components/MissionStatusPopup.vue
Normal file
57
src/plugins/userIndicator/components/MissionStatusPopup.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div class="c-user-control-panel__component">
|
||||
<div class="c-user-control-panel__header">
|
||||
<div class="c-user-control-panel__title">Mission Status</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="c-icon-button c-icon-button--sm t-close-btn icon-x"
|
||||
@click.stop="onDismiss"
|
||||
></button>
|
||||
</div>
|
||||
<div class="c-ucp-mission-status">
|
||||
<template v-for="status in missionStatuses" :key="status">
|
||||
<label class="c-ucp-mission-status__label" :for="status">{{ status }}</label>
|
||||
<div class="c-ucp-mission-status__widget --is-no-go">NO GO</div>
|
||||
<div class="c-ucp-mission-status__select">
|
||||
<select :id="status.label">
|
||||
<option v-for="option in missionStatusOptions" :key="option.key">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
emits: ['dismiss'],
|
||||
data() {
|
||||
return {
|
||||
missionStatuses: [],
|
||||
missionStatusOptions: []
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.missionStatuses = ['Command', 'Drive', 'Camera'];
|
||||
|
||||
this.missionStatusOptions = [
|
||||
{
|
||||
key: '0',
|
||||
label: 'NO GO'
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
label: 'GO'
|
||||
}
|
||||
];
|
||||
},
|
||||
methods: {
|
||||
onDismiss() {
|
||||
this.$emit('dismiss');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -21,18 +21,59 @@
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="c-indicator icon-person c-indicator--clickable">
|
||||
<div
|
||||
ref="userIndicatorRef"
|
||||
class="c-indicator c-indicator--user icon-person"
|
||||
:class="canSetMissionStatus ? 'c-indicator--clickable' : ''"
|
||||
v-bind="$attrs"
|
||||
@click.stop="togglePopup"
|
||||
>
|
||||
<span class="label c-indicator__label" aria-label="User Role">
|
||||
{{ role ? `${userName}: ${role}` : userName }}
|
||||
<button v-if="availableRoles?.length > 1" @click="promptForRoleSelection">Change Role</button>
|
||||
<button v-if="availableRoles?.length > 1" @click.stop="promptForRoleSelection">
|
||||
Change Role
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
<div v-show="isPopupVisible" ref="popupRef" class="c-user-control-panel" :style="popupStyle">
|
||||
<MissionStatusPopup v-show="true" @dismiss="togglePopup" />
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
|
||||
import ActiveRoleSynchronizer from '../../../api/user/ActiveRoleSynchronizer.js';
|
||||
import { useEventListener } from '../../../ui/composables/event.js';
|
||||
import { useWindowResize } from '../../../ui/composables/resize.js';
|
||||
import MissionStatusPopup from './MissionStatusPopup.vue';
|
||||
|
||||
export default {
|
||||
name: 'UserIndicator',
|
||||
components: {
|
||||
MissionStatusPopup
|
||||
},
|
||||
inject: ['openmct'],
|
||||
inheritAttrs: false,
|
||||
setup() {
|
||||
const { windowSize } = useWindowResize();
|
||||
const isPopupVisible = ref(false);
|
||||
const userIndicatorRef = ref(null);
|
||||
const popupRef = ref(null);
|
||||
|
||||
// eslint-disable-next-line func-style
|
||||
const handleOutsideClick = (event) => {
|
||||
if (isPopupVisible.value && popupRef.value && !popupRef.value.contains(event.target)) {
|
||||
isPopupVisible.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
useEventListener(document, 'click', handleOutsideClick);
|
||||
|
||||
return { windowSize, isPopupVisible, popupRef, userIndicatorRef };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userName: undefined,
|
||||
@@ -40,29 +81,64 @@ export default {
|
||||
availableRoles: [],
|
||||
loggedIn: false,
|
||||
inputRoleSelection: undefined,
|
||||
roleSelectionDialog: undefined
|
||||
roleSelectionDialog: undefined,
|
||||
canSetMissionStatus: true
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
popupStyle() {
|
||||
return {
|
||||
top: `${this.position.top}px`,
|
||||
left: `${this.position.left}px`
|
||||
};
|
||||
},
|
||||
position() {
|
||||
if (!this.isPopupVisible) {
|
||||
return { top: 0, left: 0 };
|
||||
}
|
||||
const indicator = this.userIndicatorRef;
|
||||
const indicatorRect = indicator.getBoundingClientRect();
|
||||
let top = indicatorRect.bottom;
|
||||
let left = indicatorRect.left;
|
||||
|
||||
async mounted() {
|
||||
const popupRect = this.popupRef.getBoundingClientRect();
|
||||
const popupWidth = popupRect.width;
|
||||
const popupHeight = popupRect.height;
|
||||
|
||||
// Check if the popup goes beyond the right edge of the window
|
||||
if (left + popupWidth > this.windowSize.width) {
|
||||
left = this.windowSize.width - popupWidth; // Adjust left to fit within the window
|
||||
}
|
||||
|
||||
// Check if the popup goes beyond the bottom edge of the window
|
||||
if (top + popupHeight > this.windowSize.height) {
|
||||
top = indicatorRect.top - popupHeight; // Place popup above the indicator
|
||||
}
|
||||
|
||||
return { top, left };
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.getUserInfo();
|
||||
},
|
||||
mounted() {
|
||||
// need to wait for openmct to be loaded before using openmct.overlays.selection
|
||||
// as document.body could be null
|
||||
this.openmct.on('start', this.fetchOrPromptForRole);
|
||||
|
||||
await this.getUserInfo();
|
||||
this.roleChannel = new ActiveRoleSynchronizer(this.openmct);
|
||||
this.roleChannel.subscribeToRoleChanges(this.onRoleChange);
|
||||
// this.openmct.on('start', this.fetchOrPromptForRole);
|
||||
// this.roleChannel = new ActiveRoleSynchronizer(this.openmct);
|
||||
// this.roleChannel.subscribeToRoleChanges(this.onRoleChange);
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.roleChannel.unsubscribeFromRoleChanges(this.onRoleChange);
|
||||
this.openmct.off('start', this.fetchOrPromptForRole);
|
||||
// this.roleChannel.unsubscribeFromRoleChanges(this.onRoleChange);
|
||||
// this.openmct.off('start', this.fetchOrPromptForRole);
|
||||
},
|
||||
methods: {
|
||||
async getUserInfo() {
|
||||
const user = await this.openmct.user.getCurrentUser();
|
||||
this.userName = user.getName();
|
||||
this.role = this.openmct.user.getActiveRole();
|
||||
this.loggedIn = this.openmct.user.isLoggedIn();
|
||||
// const user = await this.openmct.user.getCurrentUser();
|
||||
this.canSetMissionStatus = true;
|
||||
this.userName = 'Charles';
|
||||
this.role = 'Cool guy';
|
||||
this.loggedIn = true;
|
||||
},
|
||||
async fetchOrPromptForRole() {
|
||||
const UserAPI = this.openmct.user;
|
||||
@@ -138,6 +214,9 @@ export default {
|
||||
this.openmct.user.setActiveRole(role);
|
||||
// update other tabs through broadcast channel
|
||||
this.roleChannel.broadcastNewRole(role);
|
||||
},
|
||||
togglePopup() {
|
||||
this.isPopupVisible = !this.isPopupVisible;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,43 +20,25 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import mount from 'utils/mount';
|
||||
|
||||
import UserIndicator from './components/UserIndicator.vue';
|
||||
|
||||
export default function UserIndicatorPlugin() {
|
||||
function addIndicator(openmct) {
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
components: {
|
||||
UserIndicator
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct
|
||||
},
|
||||
template: '<UserIndicator />'
|
||||
},
|
||||
{
|
||||
app: openmct.app
|
||||
}
|
||||
);
|
||||
|
||||
openmct.indicators.add({
|
||||
key: 'user-indicator',
|
||||
element: vNode.el,
|
||||
priority: openmct.priority.HIGH,
|
||||
destroy: destroy
|
||||
vueComponent: UserIndicator,
|
||||
priority: openmct.priority.HIGH
|
||||
});
|
||||
}
|
||||
|
||||
return function install(openmct) {
|
||||
if (openmct.user.hasProvider()) {
|
||||
addIndicator(openmct);
|
||||
} else {
|
||||
// back up if user provider added after indicator installed
|
||||
openmct.user.on('providerAdded', () => {
|
||||
addIndicator(openmct);
|
||||
});
|
||||
}
|
||||
// if (openmct.user.hasProvider()) {
|
||||
addIndicator(openmct);
|
||||
// } else {
|
||||
// back up if user provider added after indicator installed
|
||||
// openmct.user.on('providerAdded', () => {
|
||||
// addIndicator(openmct);
|
||||
// });
|
||||
// }
|
||||
};
|
||||
}
|
||||
|
||||
190
src/plugins/userIndicator/user-indicator.scss
Normal file
190
src/plugins/userIndicator/user-indicator.scss
Normal file
@@ -0,0 +1,190 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
.c-indicator {
|
||||
&:before {
|
||||
// Indicator icon
|
||||
color: $colorKey;
|
||||
}
|
||||
|
||||
&--clickable {
|
||||
cursor: pointer;
|
||||
max-width: 250px;
|
||||
|
||||
@include hover() {
|
||||
background: $colorIndicatorBgHov;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$statusCountWidth: 30px;
|
||||
|
||||
.c-user-control-panel {
|
||||
@include menuOuter();
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: $interiorMarginLg;
|
||||
min-width: max-content;
|
||||
max-width: 35%;
|
||||
|
||||
> * + * {
|
||||
margin-top: $interiorMarginLg;
|
||||
}
|
||||
|
||||
*:before {
|
||||
font-size: 0.8em;
|
||||
margin-right: $interiorMarginSm; // WTF - this is BAD!
|
||||
}
|
||||
|
||||
&__section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMarginLg;
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
column-gap: $interiorMargin;
|
||||
text-transform: uppercase;
|
||||
|
||||
> * { flex: 0 0 auto; }
|
||||
|
||||
[class*='title'] {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.t-close-btn {
|
||||
&:before {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__component {
|
||||
// General classes for new control panel component
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $interiorMargin;
|
||||
}
|
||||
|
||||
&__top {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&__user-role,
|
||||
&__updated {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
&__updated {
|
||||
flex: 1 1 auto;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&__poll-question {
|
||||
background: $colorBodyFg;
|
||||
color: $colorBodyBg;
|
||||
border-radius: $controlCr;
|
||||
font-weight: bold;
|
||||
padding: $interiorMarginSm $interiorMargin;
|
||||
|
||||
.c-user-control-panel--admin & {
|
||||
background: rgba($colorBodyFg, 0.1);
|
||||
color: $colorBodyFg;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************** ADMIN INTERFACE */
|
||||
&__content {
|
||||
$m: $interiorMargin;
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
grid-column-gap: $m;
|
||||
grid-row-gap: $m;
|
||||
|
||||
[class*='__label'] {
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
[class*='__label'] {
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
[class*='__poll-table'] {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
[class*='new-question'] {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1 1 auto;
|
||||
height: $btnStdH;
|
||||
}
|
||||
|
||||
button {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**************** STYLES FOR THE MISSION STATUS USER CONTROL PANEL */
|
||||
.c-ucp-mission-status {
|
||||
$bg: rgba(black, 0.7);
|
||||
display: grid;
|
||||
grid-template-columns: max-content max-content 1fr;
|
||||
align-items: center;
|
||||
grid-column-gap: $interiorMarginLg;
|
||||
grid-row-gap: $interiorMargin;
|
||||
|
||||
&__widget {
|
||||
border-radius: $basicCr;
|
||||
background: $bg;
|
||||
border: 1px solid transparent;
|
||||
padding: $interiorMarginSm $interiorMarginLg;
|
||||
text-align: center;
|
||||
|
||||
&.--is-go {
|
||||
$c: #2C7527;
|
||||
background: $c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.--is-no-go {
|
||||
$c: #FBC147;
|
||||
background: $bg;
|
||||
border: 1px solid $c;
|
||||
color: $c;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,6 +222,10 @@ button {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
}
|
||||
|
||||
&--sm {
|
||||
padding: $interiorMarginSm $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
.c-list-button {
|
||||
|
||||
@@ -98,6 +98,7 @@ body.desktop {
|
||||
}
|
||||
|
||||
div,
|
||||
ul,
|
||||
span {
|
||||
// Firefox
|
||||
scrollbar-color: $scrollbarThumbColor $scrollbarTrackColorBg;
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
@import '../plugins/notebook/components/sidebar.scss';
|
||||
@import '../plugins/gauge/gauge.scss';
|
||||
@import '../plugins/faultManagement/fault-manager.scss';
|
||||
@import '../plugins/operatorStatus/operator-status';
|
||||
@import '../plugins/operatorStatus/operator-status.scss';
|
||||
@import '../plugins/userIndicator/user-indicator.scss';
|
||||
@import '../plugins/inspectorDataVisualization/inspector-data-visualization.scss';
|
||||
|
||||
#splash-screen {
|
||||
|
||||
77
src/ui/composables/event.js
Normal file
77
src/ui/composables/event.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
/* eslint-disable func-style */
|
||||
|
||||
import { isRef, onBeforeMount, onBeforeUnmount, onMounted, watch } from 'vue';
|
||||
|
||||
/**
|
||||
* Registers an event listener on the specified target and automatically removes it when the
|
||||
* component is unmounted.
|
||||
* This is a Vue composition API utility function.
|
||||
* @param {EventTarget} target - The target to attach the event listener to.
|
||||
* @param {string} event - The name of the event to listen for.
|
||||
* @param {Function} callback - The callback function to execute when the event is triggered.
|
||||
*/
|
||||
export function useEventListener(target, event, handler) {
|
||||
const addListener = (el) => {
|
||||
if (el) {
|
||||
el.addEventListener(event, handler);
|
||||
}
|
||||
};
|
||||
|
||||
const removeListener = (el) => {
|
||||
if (el) {
|
||||
el.removeEventListener(event, handler);
|
||||
}
|
||||
};
|
||||
|
||||
// If target is a reactive ref, watch it for changes
|
||||
if (isRef(target)) {
|
||||
watch(
|
||||
target,
|
||||
(newTarget, oldTarget) => {
|
||||
if (newTarget !== oldTarget) {
|
||||
removeListener(oldTarget);
|
||||
addListener(newTarget);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
} else {
|
||||
// Otherwise use lifecycle hooks to add/remove listener
|
||||
onMounted(() => addListener(target));
|
||||
onBeforeUnmount(() => removeListener(target));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an event listener on the specified EventEmitter instance and automatically removes it
|
||||
* when the component is unmounted.
|
||||
* This is a Vue composition API utility function.
|
||||
* @param {import('eventemitter3').EventEmitter} emitter - The EventEmitter instance to attach the event listener to.
|
||||
* @param {string} event - The name of the event to listen for.
|
||||
* @param {Function} callback - The callback function to execute when the event is triggered.
|
||||
*/
|
||||
export function useEventEmitter(emitter, event, callback) {
|
||||
onBeforeMount(() => emitter.on(event, callback));
|
||||
onBeforeUnmount(() => emitter.off(event, callback));
|
||||
}
|
||||
68
src/ui/composables/resize.js
Normal file
68
src/ui/composables/resize.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
/* eslint-disable func-style */
|
||||
import { onBeforeUnmount, reactive } from 'vue';
|
||||
|
||||
import throttle from '../../utils/throttle.js';
|
||||
import { useEventListener } from './event.js';
|
||||
|
||||
export function useResizeObserver() {
|
||||
const size = reactive({ width: 0, height: 0 });
|
||||
let observer;
|
||||
|
||||
const startObserving = (element) => {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
observer = new ResizeObserver((entries) => {
|
||||
if (entries[0]) {
|
||||
const { width, height } = entries[0].contentRect;
|
||||
size.width = width;
|
||||
size.height = height;
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(element);
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
return { size, startObserving };
|
||||
}
|
||||
|
||||
export function useWindowResize() {
|
||||
const windowSize = reactive({ width: window.innerWidth, height: window.innerHeight });
|
||||
|
||||
const handleResize = throttle(() => {
|
||||
windowSize.width = window.innerWidth;
|
||||
windowSize.height = window.innerHeight;
|
||||
}, 100);
|
||||
|
||||
useEventListener(window, 'resize', handleResize);
|
||||
|
||||
return { windowSize };
|
||||
}
|
||||
Reference in New Issue
Block a user