mirror of
https://github.com/nasa/openmct.git
synced 2025-06-24 18:25:19 +00:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
15b7eedc71 | |||
36d9c5a35e | |||
4c2b59d4a1 | |||
40373abfe3 | |||
86d4244ace | |||
da299e9b95 | |||
f0dcf2ba21 |
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -8,7 +8,7 @@ Closes <!--- Insert Issue Number(s) this PR addresses. Start by typing # will op
|
||||
|
||||
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
|
||||
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
|
||||
* [ ] Is this change backwards compatible? For example, developers won't need to change how they are calling the API or how they've extended core plugins such as Tables or Plots.
|
||||
* [ ] Is this a notable change that will require a special callout in the release notes [Notable Change](../docs/src/process/release.md) ? For example, will this break compatibility with existing APIs or projects which source these plugins?
|
||||
|
||||
### Author Checklist
|
||||
|
||||
|
5
.github/release.yml
vendored
5
.github/release.yml
vendored
@ -1,5 +1,8 @@
|
||||
changelog:
|
||||
categories:
|
||||
- title: 💥 Notable Changes
|
||||
labels:
|
||||
- notable_change
|
||||
- title: 🏕 Features
|
||||
labels:
|
||||
- type:feature
|
||||
@ -20,4 +23,4 @@ changelog:
|
||||
- dependencies
|
||||
- title: 🐛 Bug Fixes
|
||||
labels:
|
||||
- '*'
|
||||
- "*"
|
||||
|
56
API.md
56
API.md
@ -1304,3 +1304,59 @@ View provider Example:
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Visibility-Based Rendering in View Providers
|
||||
|
||||
To enhance performance and resource efficiency in OpenMCT, a visibility-based rendering feature has been added. This feature is designed to defer the execution of rendering logic for views that are not currently visible. It ensures that views are only updated when they are in the viewport, similar to how modern browsers handle rendering of inactive tabs but optimized for the OpenMCT tabbed display. It also works when views are scrolled outside the viewport (e.g., in a Display Layout).
|
||||
|
||||
### Overview
|
||||
|
||||
The show function is responsible for the rendering of a view. An [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) is used internally to determine whether the view is visible. This observer drives the visibility-based rendering feature, accessed via the `renderWhenVisible` function provided in the `viewOptions` parameter.
|
||||
|
||||
### Implementing Visibility-Based Rendering
|
||||
|
||||
The `renderWhenVisible` function is passed to the show function as a required part of the `viewOptions` object. This function should be used for all rendering logic that would otherwise be executed within a `requestAnimationFrame` call. When called, `renderWhenVisible` will either execute the provided function immediately (via `requestAnimationFrame`) if the view is currently visible, or defer its execution until the view becomes visible.
|
||||
|
||||
Additionally, `renderWhenVisible` returns a boolean value indicating whether the provided function was executed immediately (`true`) or deferred (`false`).
|
||||
|
||||
Here’s the signature for the show function:
|
||||
|
||||
`show(element, isEditing, viewOptions)`
|
||||
|
||||
* `element` (HTMLElement) - The DOM element where the view should be rendered.
|
||||
* `isEditing` (boolean) - Indicates whether the view is in editing mode.
|
||||
* `viewOptions` (Object) - A required object with configuration options for the view, including:
|
||||
* `renderWhenVisible` (Function) - This function wraps the `requestAnimationFrame` and only triggers the provided render logic when the view is visible in the viewport.
|
||||
|
||||
### Example
|
||||
|
||||
An OpenMCT view provider might implement the show function as follows:
|
||||
|
||||
```js
|
||||
// Define your view provider
|
||||
const myViewProvider = {
|
||||
// ... other properties and methods ...
|
||||
show: function (element, isEditing, viewOptions) {
|
||||
// Callback for rendering view content
|
||||
const renderCallback = () => {
|
||||
// Your view rendering logic goes here
|
||||
};
|
||||
|
||||
// Use the renderWhenVisible function to ensure rendering only happens when view is visible
|
||||
const wasRenderedImmediately = viewOptions.renderWhenVisible(renderCallback);
|
||||
|
||||
// Optionally handle the immediate rendering return value
|
||||
if (wasRenderedImmediately) {
|
||||
console.debug('🪞 Rendering triggered immediately as the view is visible.');
|
||||
} else {
|
||||
console.debug('🛑 Rendering has been deferred until the view becomes visible.');
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
Note that `renderWhenVisible` defers rendering while the view is not visible and caters to the latest execution call. This provides responsiveness for dynamic content while ensuring performance optimizations.
|
||||
|
||||
Ensure your view logic is prepared to handle potentially multiple deferrals if using this API, as only the last call to renderWhenVisible will be queued for execution upon the view becoming visible.
|
||||
|
||||
|
30
docs/src/process/release.md
Normal file
30
docs/src/process/release.md
Normal file
@ -0,0 +1,30 @@
|
||||
|
||||
# Release of NASA Open MCT NPM Package
|
||||
|
||||
This document outlines the process and key considerations for releasing a new version of the NASA Open MCT project as an NPM (Node Package Manager) package.
|
||||
|
||||
## 1. Pre-requisites
|
||||
|
||||
Before releasing a new version of the NASA Open MCT NPM package, ensure all dependencies are updated, and comprehensive tests are performed. This ensures compatibility and performance of the Open MCT within the Node.js ecosystem.
|
||||
|
||||
## 2. Versioning
|
||||
|
||||
Versioning is a critical step for package release. The Open MCT team follows [Semantic Versioning (SemVer)](https://semver.org) that consists of three major components: MAJOR.MINOR.PATCH. These ensure a structured process for updating, bug fixes, backward compatibility, and software progress.
|
||||
|
||||
## 3. Changelog Maintenance
|
||||
|
||||
A comprehensive changelog file, `CHANGELOG.md`, documents any changes, adding a high level of transparencies for anyone desiring to look into the status of new and past progress. It includes the summation of any major new enhancements, changes, bug fixes, and the credits to the users responsible for each unique progress.
|
||||
|
||||
## 4. Notable Changes Labels on GitHub PRs
|
||||
|
||||
For the Open MCT package, we leverage GitHub's Pull Request (PR) mechanisms extensively, with three important PR labels dedicated to signifying 'notable_changes':
|
||||
|
||||
- **Breaking Change** Highlights the integration of changes that are suspected to break, or without a doubt will break, backward compatibility. These should signal to users the upgrade might be seamless only if dependency and integration factors are properly managed, if not, one should expect to manage atypical technical snags.
|
||||
- **API change** Signifies when a contribution makes any complete or under layer changes to the communication or its supporting access processes. This label flags required see-through insight on how the web-based control panel sees and manipulates any value and or network logs.
|
||||
- **Default Behavior Change:** In the incident an update either adjusts a form to or integrates a not previously kept setting or plugin. i.e. autoscale is enabled by default when working with plots.
|
||||
|
||||
## 6. Community & Contributions
|
||||
|
||||
A flat community and the rounded center are kept in continuous celebration, with the given station open for two open-specifying dialogues, research, and all-for development probing. State the ownership for a handed looped, a welcome for even structure-core and architectural draft and impend.
|
||||
|
||||
Thank you for your collaboration and commitment to moving the project onto a text big club.
|
@ -21,24 +21,24 @@
|
||||
*****************************************************************************/
|
||||
|
||||
const DEFAULT_IMAGE_SAMPLES = [
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18734.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18735.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18736.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18737.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18738.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18739.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18740.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18741.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18742.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18743.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18744.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18745.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18746.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg',
|
||||
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg'
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18732.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18733.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18734.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18735.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18736.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18737.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18738.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18739.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18740.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18741.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18742.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18743.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18744.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18745.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18746.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18747.jpg',
|
||||
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18748.jpg'
|
||||
];
|
||||
const DEFAULT_IMAGE_LOAD_DELAY_IN_MILLISECONDS = 20000;
|
||||
const MIN_IMAGE_LOAD_DELAY_IN_MILLISECONDS = 5000;
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "3.2.0-next",
|
||||
"version": "3.2.1",
|
||||
"description": "The Open MCT core platform",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "7.22.5",
|
||||
|
@ -34,7 +34,7 @@ export default class LADTableView {
|
||||
this._destroy = null;
|
||||
}
|
||||
|
||||
show(element) {
|
||||
show(element, isEditing, { renderWhenVisible }) {
|
||||
let ladTableConfiguration = new LADTableConfiguration(this.domainObject, this.openmct);
|
||||
|
||||
const { vNode, destroy } = mount(
|
||||
@ -46,7 +46,8 @@ export default class LADTableView {
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
currentView: this,
|
||||
ladTableConfiguration
|
||||
ladTableConfiguration,
|
||||
renderWhenVisible
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
|
@ -54,12 +54,11 @@ const BLANK_VALUE = '---';
|
||||
import identifierToString from '/src/tools/url';
|
||||
import PreviewAction from '@/ui/preview/PreviewAction.js';
|
||||
|
||||
import NicelyCalled from '../../../api/nice/NicelyCalled';
|
||||
import tooltipHelpers from '../../../api/tooltips/tooltipMixins';
|
||||
|
||||
export default {
|
||||
mixins: [tooltipHelpers],
|
||||
inject: ['openmct', 'currentView'],
|
||||
inject: ['openmct', 'currentView', 'renderWhenVisible'],
|
||||
props: {
|
||||
domainObject: {
|
||||
type: Object,
|
||||
@ -190,7 +189,6 @@ export default {
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.nicelyCalled = new NicelyCalled(this.$refs.tableRow);
|
||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
@ -239,12 +237,11 @@ export default {
|
||||
this.previewAction.off('isVisible', this.togglePreviewState);
|
||||
|
||||
this.telemetryCollection.destroy();
|
||||
this.nicelyCalled.destroy();
|
||||
},
|
||||
methods: {
|
||||
updateView() {
|
||||
if (!this.updatingView) {
|
||||
this.updatingView = this.nicelyCalled.execute(() => {
|
||||
this.updatingView = this.renderWhenVisible(() => {
|
||||
this.timestamp = this.getParsedTimestamp(this.latestDatum);
|
||||
this.datum = this.latestDatum;
|
||||
this.updatingView = false;
|
||||
|
@ -24,6 +24,7 @@ import {
|
||||
getLatestTelemetry,
|
||||
getMockObjects,
|
||||
getMockTelemetry,
|
||||
renderWhenVisible,
|
||||
resetApplicationState,
|
||||
spyOnBuiltins
|
||||
} from 'utils/testing';
|
||||
@ -225,7 +226,7 @@ describe('The LAD Table', () => {
|
||||
(viewProvider) => viewProvider.key === ladTableKey
|
||||
);
|
||||
ladTableView = ladTableViewProvider.view(mockObj.ladTable, [mockObj.ladTable]);
|
||||
ladTableView.show(child, true);
|
||||
ladTableView.show(child, true, { renderWhenVisible });
|
||||
|
||||
await Promise.all([
|
||||
telemetryRequestPromise,
|
||||
|
@ -73,14 +73,18 @@ import {
|
||||
} from '@/plugins/notebook/utils/notebook-storage.js';
|
||||
import stalenessMixin from '@/ui/mixins/staleness-mixin';
|
||||
|
||||
import NicelyCalled from '../../../api/nice/NicelyCalled';
|
||||
import tooltipHelpers from '../../../api/tooltips/tooltipMixins';
|
||||
import conditionalStylesMixin from '../mixins/objectStyles-mixin';
|
||||
import LayoutFrame from './LayoutFrame.vue';
|
||||
|
||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
|
||||
const DEFAULT_POSITION = [1, 1];
|
||||
const CONTEXT_MENU_ACTIONS = ['copyToClipboard', 'copyToNotebook', 'viewHistoricalData'];
|
||||
const CONTEXT_MENU_ACTIONS = [
|
||||
'copyToClipboard',
|
||||
'copyToNotebook',
|
||||
'viewHistoricalData',
|
||||
'renderWhenVisible'
|
||||
];
|
||||
|
||||
export default {
|
||||
makeDefinition(openmct, gridSize, domainObject, position) {
|
||||
@ -106,7 +110,7 @@ export default {
|
||||
LayoutFrame
|
||||
},
|
||||
mixins: [conditionalStylesMixin, stalenessMixin, tooltipHelpers],
|
||||
inject: ['openmct', 'objectPath', 'currentView'],
|
||||
inject: ['openmct', 'objectPath', 'currentView', 'renderWhenVisible'],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
@ -274,7 +278,6 @@ export default {
|
||||
}
|
||||
this.setObject(foundObject);
|
||||
await this.$nextTick();
|
||||
this.nicelyCalled = new NicelyCalled(this.$refs.telemetryViewWrapper);
|
||||
},
|
||||
formattedValueForCopy() {
|
||||
const timeFormatterKey = this.openmct.time.timeSystem().key;
|
||||
@ -291,7 +294,7 @@ export default {
|
||||
},
|
||||
updateView() {
|
||||
if (!this.updatingView) {
|
||||
this.updatingView = this.nicelyCalled.execute(() => {
|
||||
this.updatingView = this.renderWhenVisible(() => {
|
||||
this.datum = this.latestDatum;
|
||||
this.updatingView = false;
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ class DisplayLayoutView {
|
||||
this.component = null;
|
||||
}
|
||||
|
||||
show(container, isEditing) {
|
||||
show(container, isEditing, { renderWhenVisible }) {
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
el: container,
|
||||
@ -50,7 +50,8 @@ class DisplayLayoutView {
|
||||
openmct: this.openmct,
|
||||
objectPath: this.objectPath,
|
||||
options: this.options,
|
||||
currentView: this
|
||||
currentView: this,
|
||||
renderWhenVisible
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
|
@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import { createOpenMct, resetApplicationState } from 'utils/testing';
|
||||
import { createOpenMct, renderWhenVisible, resetApplicationState } from 'utils/testing';
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
import DisplayLayoutPlugin from './plugin';
|
||||
@ -114,7 +114,7 @@ describe('the plugin', function () {
|
||||
let error;
|
||||
|
||||
try {
|
||||
view.show(child, false);
|
||||
view.show(child, false, { renderWhenVisible });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@ -161,7 +161,7 @@ describe('the plugin', function () {
|
||||
(viewProvider) => viewProvider.key === 'layout.view'
|
||||
);
|
||||
const view = displayLayoutViewProvider.view(displayLayoutItem, displayLayoutItem);
|
||||
view.show(child, false);
|
||||
view.show(child, false, { renderWhenVisible });
|
||||
|
||||
nextTick(done);
|
||||
});
|
||||
|
@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import { debounce } from 'lodash';
|
||||
import { createOpenMct, resetApplicationState } from 'utils/testing';
|
||||
import { createOpenMct, renderWhenVisible, resetApplicationState } from 'utils/testing';
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
let gaugeDomainObject = {
|
||||
@ -172,7 +172,7 @@ describe('Gauge plugin', () => {
|
||||
return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
|
||||
mutablegaugeObject = mutableObject;
|
||||
gaugeView = gaugeViewProvider.view(mutablegaugeObject, [mutablegaugeObject]);
|
||||
gaugeView.show(child);
|
||||
gaugeView.show(child, false, { renderWhenVisible });
|
||||
|
||||
return nextTick();
|
||||
});
|
||||
@ -314,7 +314,7 @@ describe('Gauge plugin', () => {
|
||||
return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
|
||||
mutablegaugeObject = mutableObject;
|
||||
gaugeView = gaugeViewProvider.view(mutablegaugeObject, [mutablegaugeObject]);
|
||||
gaugeView.show(child);
|
||||
gaugeView.show(child, false, { renderWhenVisible });
|
||||
|
||||
return nextTick();
|
||||
});
|
||||
@ -456,7 +456,7 @@ describe('Gauge plugin', () => {
|
||||
return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
|
||||
mutablegaugeObject = mutableObject;
|
||||
gaugeView = gaugeViewProvider.view(mutablegaugeObject, [mutablegaugeObject]);
|
||||
gaugeView.show(child);
|
||||
gaugeView.show(child, false, { renderWhenVisible });
|
||||
|
||||
return nextTick();
|
||||
});
|
||||
@ -560,7 +560,7 @@ describe('Gauge plugin', () => {
|
||||
mutablegaugeObject = mutableObject;
|
||||
|
||||
gaugeView = gaugeViewProvider.view(mutablegaugeObject, [mutablegaugeObject]);
|
||||
gaugeView.show(child);
|
||||
gaugeView.show(child, false, { renderWhenVisible });
|
||||
|
||||
return nextTick();
|
||||
});
|
||||
@ -643,7 +643,7 @@ describe('Gauge plugin', () => {
|
||||
mutablegaugeObject = mutableObject;
|
||||
|
||||
gaugeView = gaugeViewProvider.view(mutablegaugeObject, [mutablegaugeObject]);
|
||||
gaugeView.show(child);
|
||||
gaugeView.show(child, false, { renderWhenVisible });
|
||||
|
||||
return nextTick();
|
||||
});
|
||||
@ -771,7 +771,7 @@ describe('Gauge plugin', () => {
|
||||
return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
|
||||
mutablegaugeObject = mutableObject;
|
||||
gaugeView = gaugeViewProvider.view(mutablegaugeObject, [mutablegaugeObject]);
|
||||
gaugeView.show(child);
|
||||
gaugeView.show(child, false, { renderWhenVisible });
|
||||
|
||||
return nextTick();
|
||||
});
|
||||
|
@ -41,7 +41,7 @@ export default function GaugeViewProvider(openmct) {
|
||||
let _destroy = null;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
show: function (element, isEditing, { renderWhenVisible }) {
|
||||
const { destroy } = mount(
|
||||
{
|
||||
el: element,
|
||||
@ -51,7 +51,8 @@ export default function GaugeViewProvider(openmct) {
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
composition: openmct.composition.get(domainObject)
|
||||
composition: openmct.composition.get(domainObject),
|
||||
renderWhenVisible
|
||||
},
|
||||
template: '<gauge-component></gauge-component>'
|
||||
},
|
||||
|
@ -336,7 +336,6 @@
|
||||
<script>
|
||||
import stalenessMixin from '@/ui/mixins/staleness-mixin';
|
||||
|
||||
import NicelyCalled from '../../../api/nice/NicelyCalled';
|
||||
import tooltipHelpers from '../../../api/tooltips/tooltipMixins';
|
||||
import { DIAL_VALUE_DEG_OFFSET, getLimitDegree } from '../gauge-limit-util';
|
||||
|
||||
@ -345,7 +344,7 @@ const DEFAULT_CURRENT_VALUE = '--';
|
||||
|
||||
export default {
|
||||
mixins: [stalenessMixin, tooltipHelpers],
|
||||
inject: ['openmct', 'domainObject', 'composition'],
|
||||
inject: ['openmct', 'domainObject', 'composition', 'renderWhenVisible'],
|
||||
data() {
|
||||
let gaugeController = this.domainObject.configuration.gaugeController;
|
||||
|
||||
@ -539,7 +538,6 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.nicelyCalled = new NicelyCalled(this.$refs.gaugeWrapper);
|
||||
this.composition.on('add', this.addedToComposition);
|
||||
this.composition.on('remove', this.removeTelemetryObject);
|
||||
|
||||
@ -563,8 +561,6 @@ export default {
|
||||
|
||||
this.openmct.time.off('bounds', this.refreshData);
|
||||
this.openmct.time.off('timeSystem', this.setTimeSystem);
|
||||
|
||||
this.nicelyCalled.destroy();
|
||||
},
|
||||
methods: {
|
||||
getLimitDegree: getLimitDegree,
|
||||
@ -737,7 +733,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isRendering = this.nicelyCalled.execute(() => {
|
||||
this.isRendering = this.renderWhenVisible(() => {
|
||||
this.isRendering = false;
|
||||
|
||||
this.curVal = this.round(this.formats[this.valueKey].format(this.datum), this.precision);
|
||||
|
@ -633,13 +633,22 @@ describe('The Imagery View Layouts', () => {
|
||||
imageWrapper[2].dispatchEvent(mouseDownEvent);
|
||||
await nextTick();
|
||||
const timestamp = imageWrapper[2].id.replace('wrapper-', '');
|
||||
expect(componentView.previewAction.invoke).toHaveBeenCalledWith(
|
||||
[componentView.objectPath[0]],
|
||||
{
|
||||
timestamp: Number(timestamp),
|
||||
objectPath: componentView.objectPath
|
||||
}
|
||||
);
|
||||
const mockInvoke = componentView.previewAction.invoke;
|
||||
// Make sure the function was called
|
||||
expect(mockInvoke).toHaveBeenCalled();
|
||||
|
||||
// Get the arguments of the first call
|
||||
const firstArg = mockInvoke.calls.mostRecent().args[0];
|
||||
const secondArg = mockInvoke.calls.mostRecent().args[1];
|
||||
|
||||
// Compare the first argument
|
||||
expect(firstArg).toEqual([componentView.objectPath[0]]);
|
||||
|
||||
// Compare the "timestamp" property of the second argument
|
||||
expect(secondArg.timestamp).toEqual(Number(timestamp));
|
||||
|
||||
// Compare the "objectPath" property of the second argument
|
||||
expect(secondArg.objectPath).toEqual(componentView.objectPath);
|
||||
});
|
||||
|
||||
it('should remove images when clock advances', async () => {
|
||||
|
@ -198,7 +198,7 @@ export default {
|
||||
MctTicks,
|
||||
MctChart
|
||||
},
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
inject: ['openmct', 'domainObject', 'path', 'renderWhenVisible'],
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
|
@ -65,7 +65,7 @@ export default function PlotViewProvider(openmct) {
|
||||
let component = null;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
show: function (element, isEditing, { renderWhenVisible }) {
|
||||
let isCompact = isCompactView(objectPath);
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
@ -76,7 +76,8 @@ export default function PlotViewProvider(openmct) {
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
path: objectPath
|
||||
path: objectPath,
|
||||
renderWhenVisible
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -45,7 +45,6 @@
|
||||
import mount from 'utils/mount';
|
||||
import { toRaw } from 'vue';
|
||||
|
||||
import NicelyCalled from '../../../api/nice/NicelyCalled';
|
||||
import configStore from '../configuration/ConfigStore';
|
||||
import PlotConfigurationModel from '../configuration/PlotConfigurationModel';
|
||||
import { DrawLoader } from '../draw/DrawLoader';
|
||||
@ -100,7 +99,7 @@ const HANDLED_ATTRIBUTES = {
|
||||
|
||||
export default {
|
||||
components: { LimitLine, LimitLabel },
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
inject: ['openmct', 'domainObject', 'path', 'renderWhenVisible'],
|
||||
props: {
|
||||
rectangles: {
|
||||
type: Array,
|
||||
@ -199,7 +198,6 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.nicelyCalled = new NicelyCalled(this.$refs.chart);
|
||||
this.seriesModels = [];
|
||||
this.config = this.getConfig();
|
||||
this.isDestroyed = false;
|
||||
@ -258,7 +256,6 @@ export default {
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.destroy();
|
||||
this.nicelyCalled.destroy();
|
||||
},
|
||||
methods: {
|
||||
getConfig() {
|
||||
@ -509,6 +506,7 @@ export default {
|
||||
this.overlay = overlayCanvas;
|
||||
this.drawAPI = DrawLoader.getFallbackDrawAPI(this.canvas, this.overlay);
|
||||
this.$emit('plot-reinitialize-canvas');
|
||||
console.warn(`📈 fallback to 2D canvas`);
|
||||
},
|
||||
removeChartElement(series) {
|
||||
const elements = this.seriesElements.get(toRaw(series));
|
||||
@ -650,7 +648,7 @@ export default {
|
||||
},
|
||||
scheduleDraw() {
|
||||
if (!this.drawScheduled) {
|
||||
const called = this.nicelyCalled.execute(this.draw);
|
||||
const called = this.renderWhenVisible(this.draw);
|
||||
this.drawScheduled = called;
|
||||
}
|
||||
},
|
||||
|
@ -154,14 +154,14 @@ DrawWebGL.prototype.initContext = function () {
|
||||
DrawWebGL.prototype.destroy = function () {
|
||||
// Lose the context and delete all associated resources
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#lose_contexts_eagerly
|
||||
this.gl.getExtension('WEBGL_lose_context').loseContext();
|
||||
this.gl.deleteBuffer(this.buffer);
|
||||
this.gl?.getExtension('WEBGL_lose_context')?.loseContext();
|
||||
this.gl?.deleteBuffer(this.buffer);
|
||||
this.buffer = undefined;
|
||||
this.gl.deleteProgram(this.program);
|
||||
this.gl?.deleteProgram(this.program);
|
||||
this.program = undefined;
|
||||
this.gl.deleteShader(this.vertexShader);
|
||||
this.gl?.deleteShader(this.vertexShader);
|
||||
this.vertexShader = undefined;
|
||||
this.gl.deleteShader(this.fragmentShader);
|
||||
this.gl?.deleteShader(this.fragmentShader);
|
||||
this.fragmentShader = undefined;
|
||||
this.gl = undefined;
|
||||
|
||||
|
@ -47,7 +47,7 @@ export default function OverlayPlotViewProvider(openmct) {
|
||||
let component = null;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
show: function (element, isEditing, { renderWhenVisible }) {
|
||||
let isCompact = isCompactView(objectPath);
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
@ -58,7 +58,8 @@ export default function OverlayPlotViewProvider(openmct) {
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
path: objectPath
|
||||
path: objectPath,
|
||||
renderWhenVisible
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -25,6 +25,7 @@ import mount from 'utils/mount';
|
||||
import {
|
||||
createMouseEvent,
|
||||
createOpenMct,
|
||||
renderWhenVisible,
|
||||
resetApplicationState,
|
||||
spyOnBuiltins
|
||||
} from 'utils/testing';
|
||||
@ -314,7 +315,8 @@ describe('the plugin', function () {
|
||||
openmct,
|
||||
domainObject: overlayPlotObject,
|
||||
composition,
|
||||
path: [overlayPlotObject]
|
||||
path: [overlayPlotObject],
|
||||
renderWhenVisible
|
||||
},
|
||||
template: '<plot ref="plotComponent"></plot>'
|
||||
},
|
||||
@ -505,7 +507,8 @@ describe('the plugin', function () {
|
||||
openmct: openmct,
|
||||
domainObject: overlayPlotObject,
|
||||
composition,
|
||||
path: [overlayPlotObject]
|
||||
path: [overlayPlotObject],
|
||||
renderWhenVisible
|
||||
},
|
||||
template: '<plot ref="plotComponent"></plot>'
|
||||
},
|
||||
|
@ -25,6 +25,7 @@ import mount from 'utils/mount';
|
||||
import {
|
||||
createMouseEvent,
|
||||
createOpenMct,
|
||||
renderWhenVisible,
|
||||
resetApplicationState,
|
||||
spyOnBuiltins
|
||||
} from 'utils/testing';
|
||||
@ -372,7 +373,7 @@ describe('the plugin', function () {
|
||||
applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
||||
plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'plot-single');
|
||||
plotView = plotViewProvider.view(testTelemetryObject, []);
|
||||
plotView.show(child, true);
|
||||
plotView.show(child, true, { renderWhenVisible });
|
||||
|
||||
return nextTick();
|
||||
});
|
||||
@ -654,7 +655,7 @@ describe('the plugin', function () {
|
||||
plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'plot-single');
|
||||
plotView = plotViewProvider.view(testTelemetryObject, []);
|
||||
|
||||
plotView.show(child, true);
|
||||
plotView.show(child, true, { renderWhenVisible });
|
||||
|
||||
resizePromise = new Promise((resolve) => {
|
||||
resizePromiseResolve = resolve;
|
||||
@ -811,7 +812,8 @@ describe('the plugin', function () {
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: selection[0][0].context.item,
|
||||
path: [selection[0][0].context.item, selection[0][1].context.item]
|
||||
path: [selection[0][0].context.item, selection[0][1].context.item],
|
||||
renderWhenVisible
|
||||
},
|
||||
template: '<plot-options ref="root"/>'
|
||||
},
|
||||
|
@ -76,7 +76,7 @@ export default {
|
||||
StackedPlotItem,
|
||||
PlotLegend
|
||||
},
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
inject: ['openmct', 'domainObject', 'path', 'renderWhenVisible'],
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
|
@ -34,7 +34,7 @@ import conditionalStylesMixin from './mixins/objectStyles-mixin';
|
||||
|
||||
export default {
|
||||
mixins: [conditionalStylesMixin, stalenessMixin],
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
inject: ['openmct', 'domainObject', 'path', 'renderWhenVisible'],
|
||||
props: {
|
||||
childObject: {
|
||||
type: Object,
|
||||
@ -217,7 +217,8 @@ export default {
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject: object,
|
||||
path
|
||||
path,
|
||||
renderWhenVisible: this.renderWhenVisible
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -48,7 +48,7 @@ export default function StackedPlotViewProvider(openmct) {
|
||||
let component = null;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
show: function (element, isEditing, { renderWhenVisible }) {
|
||||
let isCompact = isCompactView(objectPath);
|
||||
|
||||
const { vNode, destroy } = mount(
|
||||
@ -60,7 +60,8 @@ export default function StackedPlotViewProvider(openmct) {
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
path: objectPath
|
||||
path: objectPath,
|
||||
renderWhenVisible
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -25,6 +25,7 @@ import mount from 'utils/mount';
|
||||
import {
|
||||
createMouseEvent,
|
||||
createOpenMct,
|
||||
renderWhenVisible,
|
||||
resetApplicationState,
|
||||
spyOnBuiltins
|
||||
} from 'utils/testing';
|
||||
@ -329,7 +330,8 @@ describe('the plugin', function () {
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject: stackedPlotObject,
|
||||
path: [stackedPlotObject]
|
||||
path: [stackedPlotObject],
|
||||
renderWhenVisible
|
||||
},
|
||||
template: '<stacked-plot ref="stackedPlotRef"></stacked-plot>'
|
||||
},
|
||||
@ -619,7 +621,8 @@ describe('the plugin', function () {
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: selection[0][0].context.item,
|
||||
path: [selection[0][0].context.item]
|
||||
path: [selection[0][0].context.item],
|
||||
renderWhenVisible
|
||||
},
|
||||
template: '<plot-options/>'
|
||||
},
|
||||
@ -774,7 +777,8 @@ describe('the plugin', function () {
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: selection[0][0].context.item,
|
||||
path: [selection[0][0].context.item, selection[0][1].context.item]
|
||||
path: [selection[0][0].context.item, selection[0][1].context.item],
|
||||
renderWhenVisible
|
||||
},
|
||||
template: '<plot-options />'
|
||||
},
|
||||
|
@ -65,7 +65,7 @@ export default class TelemetryTableView {
|
||||
}
|
||||
}
|
||||
|
||||
show(element, editMode) {
|
||||
show(element, editMode, { renderWhenVisible }) {
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
el: element,
|
||||
@ -76,7 +76,8 @@ export default class TelemetryTableView {
|
||||
openmct: this.openmct,
|
||||
objectPath: this.objectPath,
|
||||
table: this.table,
|
||||
currentView: this
|
||||
currentView: this,
|
||||
renderWhenVisible
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -280,7 +280,6 @@ import { toRaw } from 'vue';
|
||||
|
||||
import stalenessMixin from '@/ui/mixins/staleness-mixin';
|
||||
|
||||
import NicelyCalled from '../../../api/nice/NicelyCalled';
|
||||
import CSVExporter from '../../../exporters/CSVExporter.js';
|
||||
import ProgressBar from '../../../ui/components/ProgressBar.vue';
|
||||
import Search from '../../../ui/components/SearchComponent.vue';
|
||||
@ -306,7 +305,7 @@ export default {
|
||||
ProgressBar
|
||||
},
|
||||
mixins: [stalenessMixin],
|
||||
inject: ['openmct', 'objectPath', 'table', 'currentView'],
|
||||
inject: ['openmct', 'objectPath', 'table', 'currentView', 'renderWhenVisible'],
|
||||
props: {
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
@ -481,7 +480,6 @@ export default {
|
||||
this.filterChanged = _.debounce(this.filterChanged, 500);
|
||||
},
|
||||
mounted() {
|
||||
this.nicelyCalled = new NicelyCalled(this.$refs.root);
|
||||
this.csvExporter = new CSVExporter();
|
||||
this.rowsAdded = _.throttle(this.rowsAdded, 200);
|
||||
this.rowsRemoved = _.throttle(this.rowsRemoved, 200);
|
||||
@ -547,13 +545,11 @@ export default {
|
||||
this.table.configuration.destroy();
|
||||
|
||||
this.table.destroy();
|
||||
|
||||
this.nicelyCalled.destroy();
|
||||
},
|
||||
methods: {
|
||||
updateVisibleRows() {
|
||||
if (!this.updatingView) {
|
||||
this.updatingView = this.nicelyCalled.execute(() => {
|
||||
this.updatingView = this.renderWhenVisible(() => {
|
||||
let start = 0;
|
||||
let end = VISIBLE_ROW_COUNT;
|
||||
let tableRows = this.table.tableRows.getRows();
|
||||
@ -829,7 +825,7 @@ export default {
|
||||
let scrollTop = this.scrollable.scrollTop;
|
||||
|
||||
this.resizePollHandle = setInterval(() => {
|
||||
this.nicelyCalled.execute(() => {
|
||||
this.renderWhenVisible(() => {
|
||||
if ((el.clientWidth !== width || el.clientHeight !== height) && this.isAutosizeEnabled) {
|
||||
this.calculateTableSize();
|
||||
// On some resize events scrollTop is reset to 0. Possibly due to a transition we're using?
|
||||
|
@ -22,6 +22,7 @@
|
||||
import {
|
||||
createMouseEvent,
|
||||
createOpenMct,
|
||||
renderWhenVisible,
|
||||
resetApplicationState,
|
||||
spyOnBuiltins
|
||||
} from 'utils/testing';
|
||||
@ -236,7 +237,7 @@ describe('the plugin', () => {
|
||||
applicableViews = openmct.objectViews.get(testTelemetryObject, []);
|
||||
tableViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'table');
|
||||
tableView = tableViewProvider.view(testTelemetryObject, [testTelemetryObject]);
|
||||
tableView.show(child, true);
|
||||
tableView.show(child, true, { renderWhenVisible });
|
||||
|
||||
tableInstance = tableView.getTable();
|
||||
|
||||
|
@ -33,6 +33,8 @@ import StyleRuleManager from '@/plugins/condition/StyleRuleManager';
|
||||
import { STYLE_CONSTANTS } from '@/plugins/condition/utils/constants';
|
||||
import stalenessMixin from '@/ui/mixins/staleness-mixin';
|
||||
|
||||
import VisibilityObserver from '../../utils/visibility/VisibilityObserver';
|
||||
|
||||
export default {
|
||||
mixins: [stalenessMixin],
|
||||
inject: ['openmct'],
|
||||
@ -113,6 +115,9 @@ export default {
|
||||
this.actionCollection.destroy();
|
||||
delete this.actionCollection;
|
||||
}
|
||||
if (this.visibilityObserver) {
|
||||
this.visibilityObserver.destroy();
|
||||
}
|
||||
this.$refs.objectViewWrapper.removeEventListener('dragover', this.onDragOver, {
|
||||
capture: true
|
||||
});
|
||||
@ -125,6 +130,7 @@ export default {
|
||||
this.debounceUpdateView = _.debounce(this.updateView, 10);
|
||||
},
|
||||
mounted() {
|
||||
this.visibilityObserver = new VisibilityObserver(this.$refs.objectViewWrapper);
|
||||
this.updateView();
|
||||
this.$refs.objectViewWrapper.addEventListener('dragover', this.onDragOver, {
|
||||
capture: true
|
||||
@ -290,7 +296,9 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
this.currentView.show(this.viewContainer, this.openmct.editor.isEditing());
|
||||
this.currentView.show(this.viewContainer, this.openmct.editor.isEditing(), {
|
||||
renderWhenVisible: this.visibilityObserver.renderWhenVisible
|
||||
});
|
||||
|
||||
if (immediatelySelect) {
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
|
@ -22,6 +22,7 @@
|
||||
<template>
|
||||
<div class="l-preview-window js-preview-window">
|
||||
<PreviewHeader
|
||||
ref="previewHeader"
|
||||
:current-view="currentViewProvider"
|
||||
:action-collection="actionCollection"
|
||||
:domain-object="domainObject"
|
||||
@ -48,7 +49,7 @@ export default {
|
||||
viewOptions: {
|
||||
type: Object,
|
||||
default() {
|
||||
return undefined;
|
||||
return {};
|
||||
}
|
||||
},
|
||||
existingView: {
|
||||
@ -147,6 +148,11 @@ export default {
|
||||
if (isExistingView) {
|
||||
this.viewContainer.appendChild(this.existingViewElement);
|
||||
} else {
|
||||
// in preview mode, we're always visible
|
||||
this.viewOptions.renderWhenVisible = (func) => {
|
||||
window.requestAnimationFrame(func);
|
||||
return true;
|
||||
};
|
||||
this.view.show(this.viewContainer, false, this.viewOptions);
|
||||
}
|
||||
|
||||
|
@ -279,6 +279,12 @@ export function getMockTelemetry(opts = {}) {
|
||||
return telemetry;
|
||||
}
|
||||
|
||||
// used to inject into tests that require a render
|
||||
export function renderWhenVisible(func) {
|
||||
func();
|
||||
return true;
|
||||
}
|
||||
|
||||
// copy objects a bit more easily
|
||||
function copyObj(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
|
@ -23,36 +23,38 @@
|
||||
/**
|
||||
* Optimizes `requestAnimationFrame` calls to only execute when the element is visible in the viewport.
|
||||
*/
|
||||
export default class NicelyCalled {
|
||||
export default class VisibilityObserver {
|
||||
#element;
|
||||
#isIntersecting;
|
||||
#observer;
|
||||
#lastUnfiredFunc;
|
||||
lastUnfiredFunc;
|
||||
|
||||
/**
|
||||
* Constructs a NicelyCalled instance to manage visibility-based requestAnimationFrame calls.
|
||||
* Constructs a VisibilityObserver instance to manage visibility-based requestAnimationFrame calls.
|
||||
*
|
||||
* @param {HTMLElement} element - The DOM element to observe for visibility changes.
|
||||
* @throws {Error} If element is not provided.
|
||||
*/
|
||||
constructor(element) {
|
||||
if (!element) {
|
||||
throw new Error(`Nice visibility must be created with an element`);
|
||||
throw new Error(`VisibilityObserver must be created with an element`);
|
||||
}
|
||||
// set the id to some random 4 letters
|
||||
this.id = Math.random().toString(36).substring(2, 6);
|
||||
this.#element = element;
|
||||
this.#isIntersecting = true;
|
||||
this.isIntersecting = true;
|
||||
|
||||
this.#observer = new IntersectionObserver(this.#observerCallback);
|
||||
this.#observer.observe(this.#element);
|
||||
this.#lastUnfiredFunc = null;
|
||||
this.lastUnfiredFunc = null;
|
||||
this.renderWhenVisible = this.renderWhenVisible.bind(this);
|
||||
}
|
||||
|
||||
#observerCallback = ([entry]) => {
|
||||
if (entry.target === this.#element) {
|
||||
this.#isIntersecting = entry.isIntersecting;
|
||||
if (this.#isIntersecting && this.#lastUnfiredFunc) {
|
||||
window.requestAnimationFrame(this.#lastUnfiredFunc);
|
||||
this.#lastUnfiredFunc = null;
|
||||
this.isIntersecting = entry.isIntersecting;
|
||||
if (this.isIntersecting && this.lastUnfiredFunc) {
|
||||
window.requestAnimationFrame(this.lastUnfiredFunc);
|
||||
this.lastUnfiredFunc = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -65,12 +67,12 @@ export default class NicelyCalled {
|
||||
* @param {Function} func - The function to execute.
|
||||
* @returns {boolean} True if the function was executed immediately, false otherwise.
|
||||
*/
|
||||
execute(func) {
|
||||
if (this.#isIntersecting) {
|
||||
renderWhenVisible(func) {
|
||||
if (this.isIntersecting) {
|
||||
window.requestAnimationFrame(func);
|
||||
return true;
|
||||
} else {
|
||||
this.#lastUnfiredFunc = func;
|
||||
this.lastUnfiredFunc = func;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -81,8 +83,8 @@ export default class NicelyCalled {
|
||||
destroy() {
|
||||
this.#observer.unobserve(this.#element);
|
||||
this.#element = null;
|
||||
this.#isIntersecting = null;
|
||||
this.isIntersecting = null;
|
||||
this.#observer = null;
|
||||
this.#lastUnfiredFunc = null;
|
||||
this.lastUnfiredFunc = null;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user