mirror of
https://github.com/nasa/openmct.git
synced 2025-07-04 05:53:23 +00:00
Compare commits
32 Commits
expose-vue
...
duplicate-
Author | SHA1 | Date | |
---|---|---|---|
3a40eeb14b | |||
2a1e322230 | |||
300b98bd54 | |||
c946609d13 | |||
7ca559fbe4 | |||
71392915c1 | |||
2889e88a97 | |||
d56d176aac | |||
925518c83f | |||
fa5aceb7b3 | |||
6755ef4641 | |||
333e8b5583 | |||
9d8a8b36d2 | |||
b484a4a959 | |||
64e7c62d98 | |||
6483fe2402 | |||
a123889d6a | |||
a40867d544 | |||
dbed9262c0 | |||
43ac66233e | |||
04e85c176a | |||
8274c23129 | |||
5fafde5f23 | |||
80a6e7f719 | |||
2c13aeecce | |||
ac015c3e45 | |||
ae1a4bcc6a | |||
e1e0eeac56 | |||
c90dfb2a1f | |||
1dfa5e5b8c | |||
99896b72ea | |||
979ba77c8e |
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!--- This is for filing bugs. If you have a general question, please -->
|
||||||
|
<!--- visit https://github.com/nasa/openmct/discussions -->
|
||||||
|
|
||||||
|
---
|
||||||
|
name: Bug Report
|
||||||
|
about: File a Bug !
|
||||||
|
|
||||||
|
---
|
||||||
|
<!--- Focus on user impact in the title. Use the Summary Field to -->
|
||||||
|
<!--- describe the problem technically. -->
|
||||||
|
|
||||||
|
#### Summary
|
||||||
|
<!--- A description of the issue encountered. When possible, a description -->
|
||||||
|
<!--- of the impact of the issue. What use case does it impede?-->
|
||||||
|
|
||||||
|
#### Expected vs Current Behavior
|
||||||
|
<!--- Tell us what should have happened -->
|
||||||
|
|
||||||
|
#### Impact Check List
|
||||||
|
<!--- Please select from the following options -->
|
||||||
|
|
||||||
|
- [ ] Data loss or misrepresented data?
|
||||||
|
- [ ] Regression? Did this used to work or has it always been broken?
|
||||||
|
- [ ] Is there a workaround available?
|
||||||
|
- [ ] Does this impact a critical component?
|
||||||
|
- [ ] Is this just a visual bug?
|
||||||
|
|
||||||
|
#### Steps to Reproduce
|
||||||
|
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||||
|
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.
|
||||||
|
|
||||||
|
#### Environment
|
||||||
|
* Open MCT Version: <!--- date of build, version, or SHA -->
|
||||||
|
* Deployment Type: <!--- npm dev? VIPER Dev? openmct-yams? -->
|
||||||
|
* OS:
|
||||||
|
* Browser:
|
||||||
|
|
||||||
|
#### Additional Information
|
||||||
|
<!--- Include any screenshots, gifs, or logs which will expedite triage -->
|
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!--- This is for filing enhancements or features. If you have a general -->
|
||||||
|
<!--- question, please visit https://github.com/nasa/openmct/discussions -->
|
||||||
|
|
||||||
|
---
|
||||||
|
name: Feature Request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Thank you for suggesting an idea to make Open MCT better.
|
||||||
|
|
||||||
|
Please fill in as much of the template below as you're able.
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
<!-- Please describe the problem you are trying to solve. -->
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
<!--- Please describe the desired behavior. -->
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
<!--- Please describe alternative solutions or features you have considered. -->
|
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
### All Submissions:
|
||||||
|
|
||||||
|
* [ ] 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?
|
||||||
|
|
||||||
|
### Author Checklist
|
||||||
|
|
||||||
|
* [ ] Changes address original issue?
|
||||||
|
* [ ] Unit tests included and/or updated with changes?
|
||||||
|
* [ ] Command line build passes?
|
||||||
|
* [ ] Has this been smoke tested?
|
||||||
|
* [ ] Testing instructions included in associated issue?
|
18
.github/workflows/lighthouse.yml
vendored
Normal file
18
.github/workflows/lighthouse.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
name: lighthouse
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Which branch do you want to test?' # Limited to branch for now
|
||||||
|
required: false
|
||||||
|
default: 'master'
|
||||||
|
jobs:
|
||||||
|
lighthouse:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.inputs.version }}
|
||||||
|
- uses: actions/setup-node@v1
|
||||||
|
- run: npm install && npm install -g @lhci/cli #Don't want to include this in our deps
|
||||||
|
- run: lhci autorun
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -40,4 +40,7 @@ npm-debug.log
|
|||||||
# karma reports
|
# karma reports
|
||||||
report.*.json
|
report.*.json
|
||||||
|
|
||||||
|
# Lighthouse reports
|
||||||
|
.lighthouseci
|
||||||
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
16
API.md
16
API.md
@ -423,7 +423,7 @@ attribute | type | flags | notes
|
|||||||
|
|
||||||
###### Value Hints
|
###### Value Hints
|
||||||
|
|
||||||
Each telemetry value description has an object defining hints. Keys in this this object represent the hint itself, and the value represents the weight of that hint. A lower weight means the hint has a higher priority. For example, multiple values could be hinted for use as the y-axis of a plot (raw, engineering), but the highest priority would be the default choice. Likewise, a table will use hints to determine the default order of columns.
|
Each telemetry value description has an object defining hints. Keys in this object represent the hint itself, and the value represents the weight of that hint. A lower weight means the hint has a higher priority. For example, multiple values could be hinted for use as the y-axis of a plot (raw, engineering), but the highest priority would be the default choice. Likewise, a table will use hints to determine the default order of columns.
|
||||||
|
|
||||||
Known hints:
|
Known hints:
|
||||||
|
|
||||||
@ -595,9 +595,17 @@ section.
|
|||||||
|
|
||||||
#### Limit Evaluators **draft**
|
#### Limit Evaluators **draft**
|
||||||
|
|
||||||
Limit evaluators allow a telemetry integrator to define how limits should be
|
Limit evaluators allow a telemetry integrator to define which limits exist for a
|
||||||
applied to telemetry from a given domain object. For an example of a limit
|
telemetry endpoint and how limits should be applied to telemetry from a given domain object.
|
||||||
evaluator, take a look at `examples/generator/SinewaveLimitProvider.js`.
|
|
||||||
|
A limit evaluator can implement the `evalute` method which is used to define how limits
|
||||||
|
should be applied to telemetry and the `getLimits` method which is used to specify
|
||||||
|
what the limit values are for different limit levels.
|
||||||
|
|
||||||
|
Limit levels can be mapped to one of 5 colors for visualization:
|
||||||
|
`purple`, `red`, `orange`, `yellow` and `cyan`.
|
||||||
|
|
||||||
|
For an example of a limit evaluator, take a look at `examples/generator/SinewaveLimitProvider.js`.
|
||||||
|
|
||||||
### Telemetry Consumer APIs **draft**
|
### Telemetry Consumer APIs **draft**
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ accept changes from external contributors.
|
|||||||
|
|
||||||
The short version:
|
The short version:
|
||||||
|
|
||||||
1. Write your contribution.
|
1. Write your contribution or describe your idea in the form of an [GitHub issue](https://github.com/nasa/openmct/issues/new/choose) or [Starting a GitHub Discussion](https://github.com/nasa/openmct/discussions)
|
||||||
2. Make sure your contribution meets code, test, and commit message
|
2. Make sure your contribution meets code, test, and commit message
|
||||||
standards as described below.
|
standards as described below.
|
||||||
3. Submit a pull request from a topic branch back to `master`. Include a check
|
3. Submit a pull request from a topic branch back to `master`. Include a check
|
||||||
@ -18,6 +18,7 @@ The short version:
|
|||||||
for review.)
|
for review.)
|
||||||
4. Respond to any discussion. When the reviewer decides it's ready, they
|
4. Respond to any discussion. When the reviewer decides it's ready, they
|
||||||
will merge back `master` and fill out their own check list.
|
will merge back `master` and fill out their own check list.
|
||||||
|
5. If you are a first-time contributor, please see [this discussion](https://github.com/nasa/openmct/discussions/3821) for further information.
|
||||||
|
|
||||||
## Contribution Process
|
## Contribution Process
|
||||||
|
|
||||||
@ -115,7 +116,7 @@ the pull request containing the reviewer checklist (from below) and complete
|
|||||||
the merge back to the master branch.
|
the merge back to the master branch.
|
||||||
|
|
||||||
Additionally:
|
Additionally:
|
||||||
* Every pull request must link to the issue that it addresses. Eg. “Addresses #1234” or “Closes #1234”. This is the responsibility of the pull request’s __author__. If no issue exists, create one.
|
* Every pull request must link to the issue that it addresses. Eg. “Addresses #1234” or “Closes #1234”. This is the responsibility of the pull request’s __author__. If no issue exists, [create one](https://github.com/nasa/openmct/issues/new/choose).
|
||||||
* Every __author__ must include testing instructions. These instructions should identify the areas of code affected, and some minimal test steps. If addressing a bug, reproduction steps should be included, if they were not included in the original issue. If reproduction steps were included on the original issue, and are sufficient, refer to them.
|
* Every __author__ must include testing instructions. These instructions should identify the areas of code affected, and some minimal test steps. If addressing a bug, reproduction steps should be included, if they were not included in the original issue. If reproduction steps were included on the original issue, and are sufficient, refer to them.
|
||||||
* A pull request that closes an issue should say so in the description. Including the text “Closes #1234” will cause the linked issue to be automatically closed when the pull request is merged. This is the responsibility of the pull request’s __author__.
|
* A pull request that closes an issue should say so in the description. Including the text “Closes #1234” will cause the linked issue to be automatically closed when the pull request is merged. This is the responsibility of the pull request’s __author__.
|
||||||
* When a pull request is merged, and the corresponding issue closed, the __reviewer__ must add the tag “unverified” to the original issue. This will indicate that although the issue is closed, it has not been tested yet.
|
* When a pull request is merged, and the corresponding issue closed, the __reviewer__ must add the tag “unverified” to the original issue. This will indicate that although the issue is closed, it has not been tested yet.
|
||||||
@ -296,23 +297,12 @@ these standards.
|
|||||||
|
|
||||||
Issues are tracked at https://github.com/nasa/openmct/issues.
|
Issues are tracked at https://github.com/nasa/openmct/issues.
|
||||||
|
|
||||||
Issues should include:
|
|
||||||
|
|
||||||
* A short description of the issue encountered.
|
|
||||||
* A longer-form description of the issue encountered. When possible, steps to
|
|
||||||
reproduce the issue.
|
|
||||||
* When possible, a description of the impact of the issue. What use case does
|
|
||||||
it impede?
|
|
||||||
* An assessment of the severity of the issue.
|
|
||||||
|
|
||||||
Issue severity is categorized as follows (in ascending order):
|
Issue severity is categorized as follows (in ascending order):
|
||||||
|
|
||||||
* _Trivial_: Minimal impact on the usefulness and functionality of the
|
* _Trivial_: Minimal impact on the usefulness and functionality of the software; a "nice-to-have." Visual impact without functional impact,
|
||||||
software; a "nice-to-have."
|
* _Medium_: Some impairment of use, but simple workarounds exist
|
||||||
* _(Unspecified)_: Major loss of functionality or impairment of use.
|
* _Critical_: Significant loss of functionality or impairment of use. Display of telemetry data is not affected though.
|
||||||
* _Critical_: Large-scale loss of functionality or impairment of use,
|
* _Blocker_: Major functionality is impaired or lost, threatening mission success. Display of telemetry data is impaired or blocked by the bug, which could lead to loss of situational awareness.
|
||||||
such that remaining utility becomes marginal.
|
|
||||||
* _Blocker_: Harmful or otherwise unacceptable behavior. Must fix.
|
|
||||||
|
|
||||||
## Check Lists
|
## Check Lists
|
||||||
|
|
||||||
@ -322,16 +312,19 @@ checklist).
|
|||||||
|
|
||||||
### Author Checklist
|
### Author Checklist
|
||||||
|
|
||||||
1. Changes address original issue?
|
[Within PR Template](.github/PULL_REQUEST_TEMPLATE.md)
|
||||||
2. Unit tests included and/or updated with changes?
|
|
||||||
3. Command line build passes?
|
|
||||||
4. Changes have been smoke-tested?
|
|
||||||
5. Testing instructions included?
|
|
||||||
|
|
||||||
### Reviewer Checklist
|
### Reviewer Checklist
|
||||||
|
|
||||||
1. Changes appear to address issue?
|
* [ ] Changes appear to address issue?
|
||||||
2. Appropriate unit tests included?
|
* [ ] Appropriate unit tests included?
|
||||||
3. Code style and in-line documentation are appropriate?
|
* [ ] Code style and in-line documentation are appropriate?
|
||||||
4. Commit messages meet standards?
|
* [ ] Commit messages meet standards?
|
||||||
5. Has associated issue been labelled `unverified`? (only applicable if this PR closes the issue)
|
* [ ] Has associated issue been labelled `unverified`? (only applicable if this PR closes the issue)
|
||||||
|
* [ ] Has associated issue been labelled `bug`? (only applicable if this PR is for a bug fix)
|
||||||
|
* [ ] List of Acceptance Tests Performed.
|
||||||
|
|
||||||
|
Write out a small list of tests performed with just enough detail for another developer on the team
|
||||||
|
to execute.
|
||||||
|
|
||||||
|
i.e. ```When Clicking on Add button, new `object` appears in dropdown.```
|
@ -1,9 +1,11 @@
|
|||||||
# Open MCT [](http://www.apache.org/licenses/LICENSE-2.0)
|
# Open MCT [](http://www.apache.org/licenses/LICENSE-2.0) [](https://lgtm.com/projects/g/nasa/openmct/context:javascript)
|
||||||
|
|
||||||
Open MCT (Open Mission Control Technologies) is a next-generation mission control framework for visualization of data on desktop and mobile devices. It is developed at NASA's Ames Research Center, and is being used by NASA for data analysis of spacecraft missions, as well as planning and operation of experimental rover systems. As a generalizable and open source framework, Open MCT could be used as the basis for building applications for planning, operation, and analysis of any systems producing telemetry data.
|
Open MCT (Open Mission Control Technologies) is a next-generation mission control framework for visualization of data on desktop and mobile devices. It is developed at NASA's Ames Research Center, and is being used by NASA for data analysis of spacecraft missions, as well as planning and operation of experimental rover systems. As a generalizable and open source framework, Open MCT could be used as the basis for building applications for planning, operation, and analysis of any systems producing telemetry data.
|
||||||
|
|
||||||
Please visit our [Official Site](https://nasa.github.io/openmct/) and [Getting Started Guide](https://nasa.github.io/openmct/getting-started/)
|
Please visit our [Official Site](https://nasa.github.io/openmct/) and [Getting Started Guide](https://nasa.github.io/openmct/getting-started/)
|
||||||
|
|
||||||
|
Once you've created something amazing with Open MCT, showcase your work in our GitHub Discussions [Show and Tell](https://github.com/nasa/openmct/discussions/categories/show-and-tell) section. We love seeing unique and wonderful implementations of Open MCT!
|
||||||
|
|
||||||
## See Open MCT in Action
|
## See Open MCT in Action
|
||||||
|
|
||||||
Try Open MCT now with our [live demo](https://openmct-demo.herokuapp.com/).
|
Try Open MCT now with our [live demo](https://openmct-demo.herokuapp.com/).
|
||||||
@ -44,7 +46,7 @@ The clearest examples for developing Open MCT plugins are in the
|
|||||||
our documentation.
|
our documentation.
|
||||||
|
|
||||||
We want Open MCT to be as easy to use, install, run, and develop for as
|
We want Open MCT to be as easy to use, install, run, and develop for as
|
||||||
possible, and your feedback will help us get there! Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues), or by emailing us at [arc-dl-openmct@mail.nasa.gov](mailto:arc-dl-openmct@mail.nasa.gov).
|
possible, and your feedback will help us get there! Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues/new/choose), [Starting a GitHub Discussion](https://github.com/nasa/openmct/discussions), or by emailing us at [arc-dl-openmct@mail.nasa.gov](mailto:arc-dl-openmct@mail.nasa.gov).
|
||||||
|
|
||||||
## Building Applications With Open MCT
|
## Building Applications With Open MCT
|
||||||
|
|
||||||
|
@ -74,10 +74,10 @@ resources not needed for this effort should be used to begin work
|
|||||||
for the subsequent sprint.
|
for the subsequent sprint.
|
||||||
|
|
||||||
| Week | Mon | Tue | Wed | Thu | Fri |
|
| Week | Mon | Tue | Wed | Thu | Fri |
|
||||||
|:-----:|:-------------------------:|:------:|:---:|:----------------------------:|:-----------:|
|
|:-----:|:-------------------------:|:------:|:---:|:----------------------------:|:-------------------------------------:|
|
||||||
| __1__ | Sprint plan | Tag-up | | | |
|
| __1__ | Sprint plan | Tag-up | | | |
|
||||||
| __2__ | | Tag-up | | | Code freeze |
|
| __2__ | | Tag-up | | | Code freeze and sprint branch |
|
||||||
| __3__ | Per-sprint testing | Triage | | _Per-sprint testing*_ | Ship |
|
| __3__ | Per-sprint testing | Triage | | _Per-sprint testing*_ | Ship and merge sprint branch to master|
|
||||||
|
|
||||||
* If necessary.
|
* If necessary.
|
||||||
|
|
||||||
@ -105,14 +105,20 @@ emphasis on testing.
|
|||||||
that team may begin work for that sprint during the
|
that team may begin work for that sprint during the
|
||||||
third week, since testing and blocker resolution is unlikely
|
third week, since testing and blocker resolution is unlikely
|
||||||
to require all available resources.
|
to require all available resources.
|
||||||
|
* Testing success criteria identified per issue (where necessary). This could be in the form of acceptance tests on the issue or detailing performance tests, for example.
|
||||||
* __Tag-up.__ Check in and status update among development team.
|
* __Tag-up.__ Check in and status update among development team.
|
||||||
May amend plan for sprint as-needed.
|
May amend plan for sprint as-needed.
|
||||||
* __Code freeze.__ Any new work from this sprint
|
* __Code freeze.__ Any new work from this sprint
|
||||||
(features, bug fixes, enhancements) must be integrated by the
|
(features, bug fixes, enhancements) must be integrated by the
|
||||||
end of the second week of the sprint. After code freeze
|
end of the second week of the sprint. After code freeze, a sprint
|
||||||
(and until the end of the sprint) the only changes that should be
|
branch will be created (and until the end of the sprint) the only
|
||||||
merged into the master branch should directly address issues
|
changes that should be merged into the sprint branch should
|
||||||
needed to pass acceptance testing.
|
directly address issues needed to pass acceptance testing.
|
||||||
|
During this time, any other feature development will continue to
|
||||||
|
be merged into the master branch for the next sprint.
|
||||||
|
* __Sprint branch merge to master.__ After acceptance testing, the sprint branch
|
||||||
|
will be merged back to the master branch. Any code conflicts that
|
||||||
|
arise will be resolved by the team.
|
||||||
* [__Per-release Testing.__](testing/plan.md#per-release-testing)
|
* [__Per-release Testing.__](testing/plan.md#per-release-testing)
|
||||||
Structured testing with predefined
|
Structured testing with predefined
|
||||||
success criteria. No release should ship without passing
|
success criteria. No release should ship without passing
|
||||||
@ -126,8 +132,8 @@ emphasis on testing.
|
|||||||
* [__Testathon.__](testing/plan.md#user-testing)
|
* [__Testathon.__](testing/plan.md#user-testing)
|
||||||
Multi-user testing, involving as many users as
|
Multi-user testing, involving as many users as
|
||||||
is feasible, plus development team. Open-ended; should verify
|
is feasible, plus development team. Open-ended; should verify
|
||||||
completed work from this sprint, test exploratorily for
|
completed work from this sprint using the sprint branch, test
|
||||||
regressions, et cetera.
|
exploratorily for regressions, et cetera.
|
||||||
* [__Long-Duration Test.__](testing/plan.md#long-duration-testing) A
|
* [__Long-Duration Test.__](testing/plan.md#long-duration-testing) A
|
||||||
test to verify that the software remains
|
test to verify that the software remains
|
||||||
stable after running for longer durations. May include some
|
stable after running for longer durations. May include some
|
||||||
@ -143,7 +149,7 @@ emphasis on testing.
|
|||||||
Subset of Pre-release Testing
|
Subset of Pre-release Testing
|
||||||
which should be performed before shipping at the end of any
|
which should be performed before shipping at the end of any
|
||||||
sprint. Time is allocated for a second round of
|
sprint. Time is allocated for a second round of
|
||||||
Pre-release Testing if the first round is not passed.
|
Pre-release Testing if the first round is not passed. Smoke tests collected from issues/PRs
|
||||||
* __Triage.__ Team reviews issues from acceptance testing and uses
|
* __Triage.__ Team reviews issues from acceptance testing and uses
|
||||||
success criteria to determine whether or not they should block
|
success criteria to determine whether or not they should block
|
||||||
release, then formulates a plan to address these issues before
|
release, then formulates a plan to address these issues before
|
||||||
|
@ -19,7 +19,7 @@ Testing for Open MCT includes:
|
|||||||
|
|
||||||
Manual, non-rigorous testing of the software and/or specific features
|
Manual, non-rigorous testing of the software and/or specific features
|
||||||
of interest. Verifies that the software runs and that basic functionality
|
of interest. Verifies that the software runs and that basic functionality
|
||||||
is present.
|
is present. The outcome of Smoke Testing should be a simplified list of Acceptance Tests which could be executed by another team member with sufficient context.
|
||||||
|
|
||||||
### Unit Testing
|
### Unit Testing
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ User testing will focus on the following activities:
|
|||||||
* General "trying to break things."
|
* General "trying to break things."
|
||||||
|
|
||||||
During user testing, users will
|
During user testing, users will
|
||||||
[report issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
|
[report issues](https://github.com/nasa/openmct/issues/new/choose)
|
||||||
as they are encountered.
|
as they are encountered.
|
||||||
|
|
||||||
Desired outcomes of user testing are:
|
Desired outcomes of user testing are:
|
||||||
@ -71,7 +71,7 @@ usage. After twenty-four hours, the software is evaluated for:
|
|||||||
at the start of the test? Is it as responsive?
|
at the start of the test? Is it as responsive?
|
||||||
|
|
||||||
Any defects or unexpected behavior identified during testing should be
|
Any defects or unexpected behavior identified during testing should be
|
||||||
[reported as issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
|
[reported as issues](https://github.com/nasa/openmct/issues/new/choose)
|
||||||
and reviewed for severity.
|
and reviewed for severity.
|
||||||
|
|
||||||
## Test Performance
|
## Test Performance
|
||||||
|
@ -92,8 +92,8 @@ should update (or delegate the task of updating) Open MCT version
|
|||||||
numbers by the following process:
|
numbers by the following process:
|
||||||
|
|
||||||
1. Update version number in `package.json`
|
1. Update version number in `package.json`
|
||||||
1. Create a new branch off the `master` branch.
|
1. Checkout branch created for the last sprint that has been successfully tested.
|
||||||
2. Remove `-SNAPSHOT` suffix from the version in `package.json`.
|
2. Remove a `-SNAPSHOT` suffix from the version in `package.json`.
|
||||||
3. Verify that resulting version number meets semantic versioning
|
3. Verify that resulting version number meets semantic versioning
|
||||||
requirements relative to previous stable version. Increment the
|
requirements relative to previous stable version. Increment the
|
||||||
version number if necessary.
|
version number if necessary.
|
||||||
@ -132,6 +132,7 @@ numbers by the following process:
|
|||||||
4. Test the package before publishing by doing `npm publish --dry-run`
|
4. Test the package before publishing by doing `npm publish --dry-run`
|
||||||
if necessary.
|
if necessary.
|
||||||
5. Publish the package to the npmjs registry (e.g. `npm publish --access public`)
|
5. Publish the package to the npmjs registry (e.g. `npm publish --access public`)
|
||||||
|
NOTE: Use the `--tag unstable` flag to the npm publishj if this is a prerelease.
|
||||||
6. Confirm the package has been published (e.g. `https://www.npmjs.com/package/openmct`)
|
6. Confirm the package has been published (e.g. `https://www.npmjs.com/package/openmct`)
|
||||||
5. Update snapshot status in `package.json`
|
5. Update snapshot status in `package.json`
|
||||||
1. Create a new branch off the `master` branch.
|
1. Create a new branch off the `master` branch.
|
||||||
|
@ -26,14 +26,26 @@ define([
|
|||||||
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var RED = {
|
var PURPLE = {
|
||||||
|
sin: 2.2,
|
||||||
|
cos: 2.2
|
||||||
|
},
|
||||||
|
RED = {
|
||||||
sin: 0.9,
|
sin: 0.9,
|
||||||
cos: 0.9
|
cos: 0.9
|
||||||
},
|
},
|
||||||
|
ORANGE = {
|
||||||
|
sin: 0.7,
|
||||||
|
cos: 0.7
|
||||||
|
},
|
||||||
YELLOW = {
|
YELLOW = {
|
||||||
sin: 0.5,
|
sin: 0.5,
|
||||||
cos: 0.5
|
cos: 0.5
|
||||||
},
|
},
|
||||||
|
CYAN = {
|
||||||
|
sin: 0.45,
|
||||||
|
cos: 0.45
|
||||||
|
},
|
||||||
LIMITS = {
|
LIMITS = {
|
||||||
rh: {
|
rh: {
|
||||||
cssClass: "is-limit--upr is-limit--red",
|
cssClass: "is-limit--upr is-limit--red",
|
||||||
@ -93,5 +105,70 @@ define([
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SinewaveLimitProvider.prototype.getLimits = function (domainObject) {
|
||||||
|
|
||||||
|
return {
|
||||||
|
limits: function () {
|
||||||
|
return Promise.resolve({
|
||||||
|
WATCH: {
|
||||||
|
low: {
|
||||||
|
color: "cyan",
|
||||||
|
sin: -CYAN.sin,
|
||||||
|
cos: -CYAN.cos
|
||||||
|
},
|
||||||
|
high: {
|
||||||
|
color: "cyan",
|
||||||
|
...CYAN
|
||||||
|
}
|
||||||
|
},
|
||||||
|
WARNING: {
|
||||||
|
low: {
|
||||||
|
color: "yellow",
|
||||||
|
sin: -YELLOW.sin,
|
||||||
|
cos: -YELLOW.cos
|
||||||
|
},
|
||||||
|
high: {
|
||||||
|
color: "yellow",
|
||||||
|
...YELLOW
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DISTRESS: {
|
||||||
|
low: {
|
||||||
|
color: "orange",
|
||||||
|
sin: -ORANGE.sin,
|
||||||
|
cos: -ORANGE.cos
|
||||||
|
},
|
||||||
|
high: {
|
||||||
|
color: "orange",
|
||||||
|
...ORANGE
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CRITICAL: {
|
||||||
|
low: {
|
||||||
|
color: "red",
|
||||||
|
sin: -RED.sin,
|
||||||
|
cos: -RED.cos
|
||||||
|
},
|
||||||
|
high: {
|
||||||
|
color: "red",
|
||||||
|
...RED
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SEVERE: {
|
||||||
|
low: {
|
||||||
|
color: "purple",
|
||||||
|
sin: -PURPLE.sin,
|
||||||
|
cos: -PURPLE.cos
|
||||||
|
},
|
||||||
|
high: {
|
||||||
|
color: "purple",
|
||||||
|
...PURPLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return SinewaveLimitProvider;
|
return SinewaveLimitProvider;
|
||||||
});
|
});
|
||||||
|
@ -78,6 +78,7 @@ module.exports = (config) => {
|
|||||||
preserveDescribeNesting: true,
|
preserveDescribeNesting: true,
|
||||||
foldAll: false
|
foldAll: false
|
||||||
},
|
},
|
||||||
|
browserConsoleLogOptions: { level: "error", format: "%b %T: %m", terminal: true },
|
||||||
coverageIstanbulReporter: {
|
coverageIstanbulReporter: {
|
||||||
fixWebpackSourcePaths: true,
|
fixWebpackSourcePaths: true,
|
||||||
dir: process.env.CIRCLE_ARTIFACTS ?
|
dir: process.env.CIRCLE_ARTIFACTS ?
|
||||||
|
96
lighthouserc.yml
Normal file
96
lighthouserc.yml
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
ci:
|
||||||
|
collect:
|
||||||
|
urls:
|
||||||
|
- http://localhost/
|
||||||
|
numberOfRuns: 5
|
||||||
|
settings:
|
||||||
|
onlyCategories:
|
||||||
|
- performance
|
||||||
|
- best-practices
|
||||||
|
upload:
|
||||||
|
target: temporary-public-storage
|
||||||
|
assert:
|
||||||
|
preset: lighthouse:recommended
|
||||||
|
assertions:
|
||||||
|
### Applicable assertions
|
||||||
|
bootup-time:
|
||||||
|
- warn
|
||||||
|
- minScore: 0.88 #Original value was calculated at 0.88
|
||||||
|
dom-size:
|
||||||
|
- error
|
||||||
|
- maxNumericValue: 200 #Original value was calculated at 188
|
||||||
|
first-contentful-paint:
|
||||||
|
- error
|
||||||
|
- minScore: 0.07 #Original value was calculated at 0.08
|
||||||
|
mainthread-work-breakdown:
|
||||||
|
- warn
|
||||||
|
- minScore: 0.8 #Original value was calculated at 0.8
|
||||||
|
unused-javascript:
|
||||||
|
- warn
|
||||||
|
- maxLength: 1
|
||||||
|
- error
|
||||||
|
- maxNumericValue: 2000 #Original value was calculated at 1855
|
||||||
|
unused-css-rules: warn
|
||||||
|
installable-manifest: warn
|
||||||
|
service-worker: warn
|
||||||
|
### Disabled seo, accessibility, and pwa assertions, below
|
||||||
|
categories:seo: 'off'
|
||||||
|
categories:accessibility: 'off'
|
||||||
|
categories:pwa: 'off'
|
||||||
|
accesskeys: 'off'
|
||||||
|
apple-touch-icon: 'off'
|
||||||
|
aria-allowed-attr: 'off'
|
||||||
|
aria-command-name: 'off'
|
||||||
|
aria-hidden-body: 'off'
|
||||||
|
aria-hidden-focus: 'off'
|
||||||
|
aria-input-field-name: 'off'
|
||||||
|
aria-meter-name: 'off'
|
||||||
|
aria-progressbar-name: 'off'
|
||||||
|
aria-required-attr: 'off'
|
||||||
|
aria-required-children: 'off'
|
||||||
|
aria-required-parent: 'off'
|
||||||
|
aria-roles: 'off'
|
||||||
|
aria-toggle-field-name: 'off'
|
||||||
|
aria-tooltip-name: 'off'
|
||||||
|
aria-treeitem-name: 'off'
|
||||||
|
aria-valid-attr: 'off'
|
||||||
|
aria-valid-attr-value: 'off'
|
||||||
|
button-name: 'off'
|
||||||
|
bypass: 'off'
|
||||||
|
canonical: 'off'
|
||||||
|
color-contrast: 'off'
|
||||||
|
content-width: 'off'
|
||||||
|
crawlable-anchors: 'off'
|
||||||
|
csp-xss: 'off'
|
||||||
|
font-display: 'off'
|
||||||
|
font-size: 'off'
|
||||||
|
maskable-icon: 'off'
|
||||||
|
heading-order: 'off'
|
||||||
|
hreflang: 'off'
|
||||||
|
html-has-lang: 'off'
|
||||||
|
html-lang-valid: 'off'
|
||||||
|
http-status-code: 'off'
|
||||||
|
image-alt: 'off'
|
||||||
|
input-image-alt: 'off'
|
||||||
|
is-crawlable: 'off'
|
||||||
|
label: 'off'
|
||||||
|
link-name: 'off'
|
||||||
|
link-text: 'off'
|
||||||
|
list: 'off'
|
||||||
|
listitem: 'off'
|
||||||
|
meta-description: 'off'
|
||||||
|
meta-refresh: 'off'
|
||||||
|
meta-viewport: 'off'
|
||||||
|
object-alt: 'off'
|
||||||
|
plugins: 'off'
|
||||||
|
robots-txt: 'off'
|
||||||
|
splash-screen: 'off'
|
||||||
|
tabindex: 'off'
|
||||||
|
tap-targets: 'off'
|
||||||
|
td-headers-attr: 'off'
|
||||||
|
th-has-data-cells: 'off'
|
||||||
|
themed-omnibox: 'off'
|
||||||
|
valid-lang: 'off'
|
||||||
|
video-caption: 'off'
|
||||||
|
viewport: 'off'
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "1.7.1-SNAPSHOT",
|
"version": "1.7.4-SNAPSHOT",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -41,10 +41,10 @@
|
|||||||
"jsdoc": "^3.3.2",
|
"jsdoc": "^3.3.2",
|
||||||
"karma": "5.1.1",
|
"karma": "5.1.1",
|
||||||
"karma-chrome-launcher": "3.1.0",
|
"karma-chrome-launcher": "3.1.0",
|
||||||
"karma-firefox-launcher": "1.3.0",
|
|
||||||
"karma-cli": "2.0.0",
|
"karma-cli": "2.0.0",
|
||||||
"karma-coverage": "2.0.3",
|
"karma-coverage": "2.0.3",
|
||||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||||
|
"karma-firefox-launcher": "1.3.0",
|
||||||
"karma-html-reporter": "0.2.7",
|
"karma-html-reporter": "0.2.7",
|
||||||
"karma-jasmine": "3.3.1",
|
"karma-jasmine": "3.3.1",
|
||||||
"karma-sourcemap-loader": "0.3.7",
|
"karma-sourcemap-loader": "0.3.7",
|
||||||
@ -60,7 +60,7 @@
|
|||||||
"moment-timezone": "0.5.28",
|
"moment-timezone": "0.5.28",
|
||||||
"node-bourbon": "^4.2.3",
|
"node-bourbon": "^4.2.3",
|
||||||
"node-sass": "^4.14.1",
|
"node-sass": "^4.14.1",
|
||||||
"painterro": "^1.0.35",
|
"painterro": "^1.2.56",
|
||||||
"printj": "^1.2.1",
|
"printj": "^1.2.1",
|
||||||
"raw-loader": "^0.5.1",
|
"raw-loader": "^0.5.1",
|
||||||
"request": "^2.69.0",
|
"request": "^2.69.0",
|
||||||
@ -78,7 +78,8 @@
|
|||||||
"zepto": "^1.2.0"
|
"zepto": "^1.2.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf ./dist",
|
"clean": "rm -rf ./dist /node_modules; rm package-lock.json",
|
||||||
|
"clean-test-lint": "npm run clean; npm install ; npm run test; npm run lint",
|
||||||
"start": "node app.js",
|
"start": "node app.js",
|
||||||
"lint": "eslint platform example src --ext .js,.vue openmct.js",
|
"lint": "eslint platform example src --ext .js,.vue openmct.js",
|
||||||
"lint:fix": "eslint platform example src --ext .js,.vue openmct.js --fix",
|
"lint:fix": "eslint platform example src --ext .js,.vue openmct.js --fix",
|
||||||
|
@ -24,7 +24,6 @@ define([
|
|||||||
"./src/navigation/NavigationService",
|
"./src/navigation/NavigationService",
|
||||||
"./src/navigation/NavigateAction",
|
"./src/navigation/NavigateAction",
|
||||||
"./src/navigation/OrphanNavigationHandler",
|
"./src/navigation/OrphanNavigationHandler",
|
||||||
"./src/windowing/NewTabAction",
|
|
||||||
"./res/templates/browse.html",
|
"./res/templates/browse.html",
|
||||||
"./res/templates/browse-object.html",
|
"./res/templates/browse-object.html",
|
||||||
"./res/templates/browse/object-header.html",
|
"./res/templates/browse/object-header.html",
|
||||||
@ -37,7 +36,6 @@ define([
|
|||||||
NavigationService,
|
NavigationService,
|
||||||
NavigateAction,
|
NavigateAction,
|
||||||
OrphanNavigationHandler,
|
OrphanNavigationHandler,
|
||||||
NewTabAction,
|
|
||||||
browseTemplate,
|
browseTemplate,
|
||||||
browseObjectTemplate,
|
browseObjectTemplate,
|
||||||
objectHeaderTemplate,
|
objectHeaderTemplate,
|
||||||
@ -128,23 +126,6 @@ define([
|
|||||||
"depends": [
|
"depends": [
|
||||||
"navigationService"
|
"navigationService"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "window",
|
|
||||||
"name": "Open In New Tab",
|
|
||||||
"implementation": NewTabAction,
|
|
||||||
"description": "Open in a new browser tab",
|
|
||||||
"category": [
|
|
||||||
"view-control",
|
|
||||||
"contextual"
|
|
||||||
],
|
|
||||||
"depends": [
|
|
||||||
"urlService",
|
|
||||||
"$window"
|
|
||||||
],
|
|
||||||
"group": "windowing",
|
|
||||||
"priority": 10,
|
|
||||||
"cssClass": "icon-new-window"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"runs": [
|
"runs": [
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../../src/windowing/NewTabAction"],
|
|
||||||
function (NewTabAction) {
|
|
||||||
|
|
||||||
describe("The new tab action", function () {
|
|
||||||
var actionSelected,
|
|
||||||
actionCurrent,
|
|
||||||
mockWindow,
|
|
||||||
mockContextCurrent,
|
|
||||||
mockContextSelected,
|
|
||||||
mockUrlService;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockWindow = jasmine.createSpyObj("$window", ["open", "location"]);
|
|
||||||
|
|
||||||
// Context if the current object is selected
|
|
||||||
// For example, when the top right new tab
|
|
||||||
// button is clicked, the user is using the
|
|
||||||
// current domainObject
|
|
||||||
mockContextCurrent = jasmine.createSpyObj("context", ["domainObject"]);
|
|
||||||
|
|
||||||
// Context if the selected object is selected
|
|
||||||
// For example, when an object in the left
|
|
||||||
// tree is opened in a new tab using the
|
|
||||||
// context menu
|
|
||||||
mockContextSelected = jasmine.createSpyObj("context", ["selectedObject",
|
|
||||||
"domainObject"]);
|
|
||||||
|
|
||||||
// Mocks the urlService used to make the new tab's url from a
|
|
||||||
// domainObject and mode
|
|
||||||
mockUrlService = jasmine.createSpyObj("urlService", ["urlForNewTab"]);
|
|
||||||
|
|
||||||
// Action done using the current context or mockContextCurrent
|
|
||||||
actionCurrent = new NewTabAction(mockUrlService, mockWindow,
|
|
||||||
mockContextCurrent);
|
|
||||||
|
|
||||||
// Action done using the selected context or mockContextSelected
|
|
||||||
actionSelected = new NewTabAction(mockUrlService, mockWindow,
|
|
||||||
mockContextSelected);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("new tab with current url is opened", function () {
|
|
||||||
actionCurrent.perform();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("new tab with a selected url is opened", function () {
|
|
||||||
actionSelected.perform();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -86,7 +86,7 @@ define(
|
|||||||
})
|
})
|
||||||
.join('/');
|
.join('/');
|
||||||
|
|
||||||
window.location.href = url;
|
openmct.router.navigate(url);
|
||||||
|
|
||||||
if (isFirstViewEditable(object.useCapability('adapter'), objectPath)) {
|
if (isFirstViewEditable(object.useCapability('adapter'), objectPath)) {
|
||||||
openmct.editor.edit();
|
openmct.editor.edit();
|
||||||
|
@ -23,13 +23,11 @@
|
|||||||
define([
|
define([
|
||||||
"moment-timezone",
|
"moment-timezone",
|
||||||
"./src/indicators/ClockIndicator",
|
"./src/indicators/ClockIndicator",
|
||||||
"./src/indicators/FollowIndicator",
|
|
||||||
"./src/services/TickerService",
|
"./src/services/TickerService",
|
||||||
"./src/services/TimerService",
|
"./src/services/TimerService",
|
||||||
"./src/controllers/ClockController",
|
"./src/controllers/ClockController",
|
||||||
"./src/controllers/TimerController",
|
"./src/controllers/TimerController",
|
||||||
"./src/controllers/RefreshingController",
|
"./src/controllers/RefreshingController",
|
||||||
"./src/actions/FollowTimerAction",
|
|
||||||
"./src/actions/StartTimerAction",
|
"./src/actions/StartTimerAction",
|
||||||
"./src/actions/RestartTimerAction",
|
"./src/actions/RestartTimerAction",
|
||||||
"./src/actions/StopTimerAction",
|
"./src/actions/StopTimerAction",
|
||||||
@ -39,13 +37,11 @@ define([
|
|||||||
], function (
|
], function (
|
||||||
MomentTimezone,
|
MomentTimezone,
|
||||||
ClockIndicator,
|
ClockIndicator,
|
||||||
FollowIndicator,
|
|
||||||
TickerService,
|
TickerService,
|
||||||
TimerService,
|
TimerService,
|
||||||
ClockController,
|
ClockController,
|
||||||
TimerController,
|
TimerController,
|
||||||
RefreshingController,
|
RefreshingController,
|
||||||
FollowTimerAction,
|
|
||||||
StartTimerAction,
|
StartTimerAction,
|
||||||
RestartTimerAction,
|
RestartTimerAction,
|
||||||
StopTimerAction,
|
StopTimerAction,
|
||||||
@ -144,15 +140,6 @@ define([
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": [
|
"actions": [
|
||||||
{
|
|
||||||
"key": "timer.follow",
|
|
||||||
"implementation": FollowTimerAction,
|
|
||||||
"depends": ["timerService"],
|
|
||||||
"category": "contextual",
|
|
||||||
"name": "Follow Timer",
|
|
||||||
"cssClass": "icon-clock",
|
|
||||||
"priority": "optional"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "timer.start",
|
"key": "timer.start",
|
||||||
"implementation": StartTimerAction,
|
"implementation": StartTimerAction,
|
||||||
@ -299,10 +286,7 @@ define([
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"runs": [{
|
"runs": [],
|
||||||
"implementation": FollowIndicator,
|
|
||||||
"depends": ["openmct", "timerService"]
|
|
||||||
}],
|
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
"name": "moment-duration-format",
|
"name": "moment-duration-format",
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2009-2016, 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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"../../src/actions/FollowTimerAction"
|
|
||||||
], function (FollowTimerAction) {
|
|
||||||
var TIMER_SERVICE_METHODS =
|
|
||||||
['setTimer', 'getTimer', 'clearTimer', 'on', 'off'];
|
|
||||||
|
|
||||||
describe("The Follow Timer action", function () {
|
|
||||||
var testContext;
|
|
||||||
var testModel;
|
|
||||||
var testAdaptedObject;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
testModel = {};
|
|
||||||
testContext = {
|
|
||||||
domainObject: jasmine.createSpyObj('domainObject', [
|
|
||||||
'getModel',
|
|
||||||
'useCapability'
|
|
||||||
])
|
|
||||||
};
|
|
||||||
testAdaptedObject = { foo: 'bar' };
|
|
||||||
testContext.domainObject.getModel.and.returnValue(testModel);
|
|
||||||
testContext.domainObject.useCapability.and.callFake(function (c) {
|
|
||||||
return c === 'adapter' && testAdaptedObject;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("is applicable to timers", function () {
|
|
||||||
testModel.type = "timer";
|
|
||||||
expect(FollowTimerAction.appliesTo(testContext)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("is inapplicable to non-timers", function () {
|
|
||||||
testModel.type = "folder";
|
|
||||||
expect(FollowTimerAction.appliesTo(testContext)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when instantiated", function () {
|
|
||||||
var mockTimerService;
|
|
||||||
var action;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockTimerService = jasmine.createSpyObj(
|
|
||||||
'timerService',
|
|
||||||
TIMER_SERVICE_METHODS
|
|
||||||
);
|
|
||||||
action = new FollowTimerAction(mockTimerService, testContext);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not interact with the timer service", function () {
|
|
||||||
TIMER_SERVICE_METHODS.forEach(function (method) {
|
|
||||||
expect(mockTimerService[method]).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("and performed", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
action.perform();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets the active timer", function () {
|
|
||||||
expect(mockTimerService.setTimer)
|
|
||||||
.toHaveBeenCalledWith(testAdaptedObject);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,96 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2009-2018, 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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"../../src/indicators/FollowIndicator",
|
|
||||||
"../../src/services/TimerService",
|
|
||||||
"../../../../../src/MCT",
|
|
||||||
'zepto'
|
|
||||||
], function (
|
|
||||||
FollowIndicator,
|
|
||||||
TimerService,
|
|
||||||
MCT,
|
|
||||||
$
|
|
||||||
) {
|
|
||||||
xdescribe("The timer-following indicator", function () {
|
|
||||||
var timerService;
|
|
||||||
var openmct;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
openmct = new MCT();
|
|
||||||
timerService = new TimerService(openmct);
|
|
||||||
spyOn(openmct.indicators, "add");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("adds an indicator when installed", function () {
|
|
||||||
FollowIndicator(openmct, timerService);
|
|
||||||
expect(openmct.indicators.add).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("indicates that no timer is being followed", function () {
|
|
||||||
FollowIndicator(openmct, timerService);
|
|
||||||
var simpleIndicator = openmct.indicators.add.calls.mostRecent().args[0];
|
|
||||||
var element = simpleIndicator.element;
|
|
||||||
var text = $('.indicator-text', element).text().trim();
|
|
||||||
expect(text).toEqual('No timer being followed');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when a timer is set", function () {
|
|
||||||
var testObject;
|
|
||||||
var simpleIndicator;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
testObject = {
|
|
||||||
identifier: {
|
|
||||||
namespace: 'namespace',
|
|
||||||
key: 'key'
|
|
||||||
},
|
|
||||||
name: "some timer!"
|
|
||||||
};
|
|
||||||
timerService.setTimer(testObject);
|
|
||||||
FollowIndicator(openmct, timerService);
|
|
||||||
simpleIndicator = openmct.indicators.add.calls.mostRecent().args[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays the timer's name", function () {
|
|
||||||
var element = simpleIndicator.element;
|
|
||||||
var text = $('.indicator-text', element).text().trim();
|
|
||||||
expect(text).toEqual('Following timer ' + testObject.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays the timer's name when it changes", function () {
|
|
||||||
var secondTimer = {
|
|
||||||
identifier: {
|
|
||||||
namespace: 'namespace',
|
|
||||||
key: 'key2'
|
|
||||||
},
|
|
||||||
name: "Some other timer"
|
|
||||||
};
|
|
||||||
var element = simpleIndicator.element;
|
|
||||||
timerService.setTimer(secondTimer);
|
|
||||||
var text = $('.indicator-text', element).text().trim();
|
|
||||||
expect(text).toEqual('Following timer ' + secondTimer.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -252,7 +252,7 @@ define([
|
|||||||
|
|
||||||
this.status = new api.StatusAPI(this);
|
this.status = new api.StatusAPI(this);
|
||||||
|
|
||||||
this.router = new ApplicationRouter();
|
this.router = new ApplicationRouter(this);
|
||||||
|
|
||||||
this.branding = BrandingAPI.default;
|
this.branding = BrandingAPI.default;
|
||||||
|
|
||||||
@ -274,6 +274,7 @@ define([
|
|||||||
this.install(ImageryPlugin.default());
|
this.install(ImageryPlugin.default());
|
||||||
this.install(this.plugins.FlexibleLayout());
|
this.install(this.plugins.FlexibleLayout());
|
||||||
this.install(this.plugins.GoToOriginalAction());
|
this.install(this.plugins.GoToOriginalAction());
|
||||||
|
this.install(this.plugins.OpenInNewTabAction());
|
||||||
this.install(this.plugins.ImportExport());
|
this.install(this.plugins.ImportExport());
|
||||||
this.install(this.plugins.WebPage());
|
this.install(this.plugins.WebPage());
|
||||||
this.install(this.plugins.Condition());
|
this.install(this.plugins.Condition());
|
||||||
|
@ -161,6 +161,22 @@ define([
|
|||||||
evaluate: function (datum, property) {
|
evaluate: function (datum, property) {
|
||||||
return limitEvaluator.evaluate(datum, property && property.key);
|
return limitEvaluator.evaluate(datum, property && property.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
LegacyTelemetryProvider.prototype.getLimits = function (domainObject) {
|
||||||
|
const oldObject = this.instantiate(
|
||||||
|
utils.toOldFormat(domainObject),
|
||||||
|
utils.makeKeyString(domainObject.identifier)
|
||||||
|
);
|
||||||
|
const limitEvaluator = oldObject.getCapability("limit");
|
||||||
|
|
||||||
|
return {
|
||||||
|
limits: function () {
|
||||||
|
return limitEvaluator.limits();
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -48,12 +48,12 @@ define(
|
|||||||
* Converts an HTML element into a PNG or JPG Blob.
|
* Converts an HTML element into a PNG or JPG Blob.
|
||||||
* @private
|
* @private
|
||||||
* @param {node} element that will be converted to an image
|
* @param {node} element that will be converted to an image
|
||||||
* @param {string} type of image to convert the element to.
|
* @param {object} options Image options.
|
||||||
* @returns {promise}
|
* @returns {promise}
|
||||||
*/
|
*/
|
||||||
ExportImageService.prototype.renderElement = function (element, imageType, className) {
|
ExportImageService.prototype.renderElement = function (element, {imageType, className, thumbnailSize}) {
|
||||||
|
const self = this;
|
||||||
const dialogService = this.dialogService;
|
const dialogService = this.dialogService;
|
||||||
|
|
||||||
const dialog = dialogService.showBlockingMessage({
|
const dialog = dialogService.showBlockingMessage({
|
||||||
title: "Capturing...",
|
title: "Capturing...",
|
||||||
hint: "Capturing an image",
|
hint: "Capturing an image",
|
||||||
@ -90,7 +90,16 @@ define(
|
|||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
return canvas.toBlob(resolve, mimeType);
|
if (thumbnailSize) {
|
||||||
|
const thumbnail = self.getThumbnail(canvas, mimeType, thumbnailSize);
|
||||||
|
|
||||||
|
return canvas.toBlob(blob => resolve({
|
||||||
|
blob,
|
||||||
|
thumbnail
|
||||||
|
}), mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canvas.toBlob(blob => resolve({ blob }), mimeType);
|
||||||
});
|
});
|
||||||
}, function (error) {
|
}, function (error) {
|
||||||
console.log('error capturing image', error);
|
console.log('error capturing image', error);
|
||||||
@ -109,6 +118,17 @@ define(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ExportImageService.prototype.getThumbnail = function (canvas, mimeType, size) {
|
||||||
|
const thumbnailCanvas = document.createElement('canvas');
|
||||||
|
thumbnailCanvas.setAttribute('width', size.width);
|
||||||
|
thumbnailCanvas.setAttribute('height', size.height);
|
||||||
|
const ctx = thumbnailCanvas.getContext('2d');
|
||||||
|
ctx.globalCompositeOperation = "copy";
|
||||||
|
ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, size.width, size.height);
|
||||||
|
|
||||||
|
return thumbnailCanvas.toDataURL(mimeType);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a screenshot of a DOM node and exports to JPG.
|
* Takes a screenshot of a DOM node and exports to JPG.
|
||||||
* @param {node} element to be exported
|
* @param {node} element to be exported
|
||||||
@ -119,8 +139,12 @@ define(
|
|||||||
ExportImageService.prototype.exportJPG = function (element, filename, className) {
|
ExportImageService.prototype.exportJPG = function (element, filename, className) {
|
||||||
const processedFilename = replaceDotsWithUnderscores(filename);
|
const processedFilename = replaceDotsWithUnderscores(filename);
|
||||||
|
|
||||||
return this.renderElement(element, "jpg", className).then(function (img) {
|
return this.renderElement(element, {
|
||||||
saveAs(img, processedFilename);
|
imageType: 'jpg',
|
||||||
|
className
|
||||||
|
})
|
||||||
|
.then(function (img) {
|
||||||
|
saveAs(img.blob, processedFilename);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -134,8 +158,12 @@ define(
|
|||||||
ExportImageService.prototype.exportPNG = function (element, filename, className) {
|
ExportImageService.prototype.exportPNG = function (element, filename, className) {
|
||||||
const processedFilename = replaceDotsWithUnderscores(filename);
|
const processedFilename = replaceDotsWithUnderscores(filename);
|
||||||
|
|
||||||
return this.renderElement(element, "png", className).then(function (img) {
|
return this.renderElement(element, {
|
||||||
saveAs(img, processedFilename);
|
imageType: 'png',
|
||||||
|
className
|
||||||
|
})
|
||||||
|
.then(function (img) {
|
||||||
|
saveAs(img.blob, processedFilename);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -146,8 +174,12 @@ define(
|
|||||||
* @returns {promise}
|
* @returns {promise}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ExportImageService.prototype.exportPNGtoSRC = function (element, className) {
|
ExportImageService.prototype.exportPNGtoSRC = function (element, options) {
|
||||||
return this.renderElement(element, "png", className);
|
|
||||||
|
return this.renderElement(element, {
|
||||||
|
imageType: 'png',
|
||||||
|
...options
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function replaceDotsWithUnderscores(filename) {
|
function replaceDotsWithUnderscores(filename) {
|
||||||
|
@ -119,7 +119,8 @@ describe('The ActionCollection', () => {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
actionCollection.destroy();
|
actionCollection.destroy();
|
||||||
resetApplicationState(openmct);
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("disable method invoked with action keys", () => {
|
describe("disable method invoked with action keys", () => {
|
||||||
|
@ -99,7 +99,7 @@ describe('The Actions API', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("register method", () => {
|
describe("register method", () => {
|
||||||
|
@ -215,12 +215,12 @@ define([
|
|||||||
* @memberof {module:openmct.CompositionCollection#}
|
* @memberof {module:openmct.CompositionCollection#}
|
||||||
* @name load
|
* @name load
|
||||||
*/
|
*/
|
||||||
CompositionCollection.prototype.load = function () {
|
CompositionCollection.prototype.load = function (abortSignal) {
|
||||||
this.cleanUpMutables();
|
this.cleanUpMutables();
|
||||||
|
|
||||||
return this.provider.load(this.domainObject)
|
return this.provider.load(this.domainObject)
|
||||||
.then(function (children) {
|
.then(function (children) {
|
||||||
return Promise.all(children.map((c) => this.publicAPI.objects.get(c)));
|
return Promise.all(children.map((c) => this.publicAPI.objects.get(c, abortSignal)));
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
.then(function (childObjects) {
|
.then(function (childObjects) {
|
||||||
childObjects.forEach(c => this.add(c, true));
|
childObjects.forEach(c => this.add(c, true));
|
||||||
|
@ -76,7 +76,7 @@ describe ('The Menu API', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("showMenu method", () => {
|
describe("showMenu method", () => {
|
||||||
|
@ -45,6 +45,8 @@ function ObjectAPI(typeRegistry, openmct) {
|
|||||||
this.rootProvider = new RootObjectProvider(this.rootRegistry);
|
this.rootProvider = new RootObjectProvider(this.rootRegistry);
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
this.interceptorRegistry = new InterceptorRegistry();
|
this.interceptorRegistry = new InterceptorRegistry();
|
||||||
|
|
||||||
|
this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'plan'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -404,11 +406,16 @@ ObjectAPI.prototype._toMutable = function (object) {
|
|||||||
let provider = this.getProvider(identifier);
|
let provider = this.getProvider(identifier);
|
||||||
|
|
||||||
if (provider !== undefined
|
if (provider !== undefined
|
||||||
&& provider.observe !== undefined) {
|
&& provider.observe !== undefined
|
||||||
|
&& this.SYNCHRONIZED_OBJECT_TYPES.includes(object.type)) {
|
||||||
let unobserve = provider.observe(identifier, (updatedModel) => {
|
let unobserve = provider.observe(identifier, (updatedModel) => {
|
||||||
|
if (updatedModel.persisted > mutableObject.modified) {
|
||||||
|
//Don't replace with a stale model. This can happen on slow connections when multiple mutations happen
|
||||||
|
//in rapid succession and intermediate persistence states are returned by the observe function.
|
||||||
mutableObject.$refresh(updatedModel);
|
mutableObject.$refresh(updatedModel);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
mutableObject.$on('$destroy', () => {
|
mutableObject.$on('$_destroy', () => {
|
||||||
unobserve();
|
unobserve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -163,14 +163,22 @@ describe("The Object API", () => {
|
|||||||
key: 'test-key'
|
key: 'test-key'
|
||||||
},
|
},
|
||||||
name: 'test object',
|
name: 'test object',
|
||||||
|
type: 'notebook',
|
||||||
otherAttribute: 'other-attribute-value',
|
otherAttribute: 'other-attribute-value',
|
||||||
|
modified: 0,
|
||||||
|
persisted: 0,
|
||||||
objectAttribute: {
|
objectAttribute: {
|
||||||
embeddedObject: {
|
embeddedObject: {
|
||||||
embeddedKey: 'embedded-value'
|
embeddedKey: 'embedded-value'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
updatedTestObject = Object.assign({otherAttribute: 'changed-attribute-value'}, testObject);
|
updatedTestObject = Object.assign({
|
||||||
|
otherAttribute: 'changed-attribute-value'
|
||||||
|
}, testObject);
|
||||||
|
updatedTestObject.modified = 1;
|
||||||
|
updatedTestObject.persisted = 1;
|
||||||
|
|
||||||
mockProvider = jasmine.createSpyObj("mock provider", [
|
mockProvider = jasmine.createSpyObj("mock provider", [
|
||||||
"get",
|
"get",
|
||||||
"create",
|
"create",
|
||||||
@ -182,6 +190,8 @@ describe("The Object API", () => {
|
|||||||
mockProvider.observeObjectChanges.and.callFake(() => {
|
mockProvider.observeObjectChanges.and.callFake(() => {
|
||||||
callbacks[0](updatedTestObject);
|
callbacks[0](updatedTestObject);
|
||||||
callbacks.splice(0, 1);
|
callbacks.splice(0, 1);
|
||||||
|
|
||||||
|
return () => {};
|
||||||
});
|
});
|
||||||
mockProvider.observe.and.callFake((id, callback) => {
|
mockProvider.observe.and.callFake((id, callback) => {
|
||||||
if (callbacks.length === 0) {
|
if (callbacks.length === 0) {
|
||||||
@ -189,6 +199,8 @@ describe("The Object API", () => {
|
|||||||
} else {
|
} else {
|
||||||
callbacks[0] = callback;
|
callbacks[0] = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return () => {};
|
||||||
});
|
});
|
||||||
|
|
||||||
objectAPI.addProvider(TEST_NAMESPACE, mockProvider);
|
objectAPI.addProvider(TEST_NAMESPACE, mockProvider);
|
||||||
|
@ -22,7 +22,7 @@ describe("The Status API", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("set function", () => {
|
describe("set function", () => {
|
||||||
|
@ -504,6 +504,26 @@ define([
|
|||||||
return this.getLimitEvaluator(domainObject);
|
return this.getLimitEvaluator(domainObject);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a limits for this domain object.
|
||||||
|
* Limits help you display limits and alarms of
|
||||||
|
* telemetry for display purposes without having to interact directly
|
||||||
|
* with the Limit API.
|
||||||
|
*
|
||||||
|
* This method is optional.
|
||||||
|
* If a provider does not implement this method, it is presumed
|
||||||
|
* that no limits are defined for this domain object's telemetry.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain
|
||||||
|
* object for which to get limits
|
||||||
|
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
|
||||||
|
* @method limits
|
||||||
|
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||||
|
*/
|
||||||
|
TelemetryAPI.prototype.limitDefinition = function (domainObject) {
|
||||||
|
return this.getLimits(domainObject);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a limit evaluator for this domain object.
|
* Get a limit evaluator for this domain object.
|
||||||
* Limit Evaluators help you evaluate limit and alarm status of individual
|
* Limit Evaluators help you evaluate limit and alarm status of individual
|
||||||
@ -531,5 +551,45 @@ define([
|
|||||||
return provider.getLimitEvaluator(domainObject);
|
return provider.getLimitEvaluator(domainObject);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a limit definitions for this domain object.
|
||||||
|
* Limit Definitions help you indicate limits and alarms of
|
||||||
|
* telemetry for display purposes without having to interact directly
|
||||||
|
* with the Limit API.
|
||||||
|
*
|
||||||
|
* This method is optional.
|
||||||
|
* If a provider does not implement this method, it is presumed
|
||||||
|
* that no limits are defined for this domain object's telemetry.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain
|
||||||
|
* object for which to display limits
|
||||||
|
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
|
||||||
|
* @method limits returns a limits object of
|
||||||
|
* type {
|
||||||
|
* level1: {
|
||||||
|
* low: { key1: value1, key2: value2, color: <supportedColor> },
|
||||||
|
* high: { key1: value1, key2: value2, color: <supportedColor> }
|
||||||
|
* },
|
||||||
|
* level2: {
|
||||||
|
* low: { key1: value1, key2: value2 },
|
||||||
|
* high: { key1: value1, key2: value2 }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* supported colors are purple, red, orange, yellow and cyan
|
||||||
|
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||||
|
*/
|
||||||
|
TelemetryAPI.prototype.getLimits = function (domainObject) {
|
||||||
|
const provider = this.findLimitEvaluator(domainObject);
|
||||||
|
if (!provider || !provider.getLimits) {
|
||||||
|
return {
|
||||||
|
limits: function () {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.getLimits(domainObject);
|
||||||
|
};
|
||||||
|
|
||||||
return TelemetryAPI;
|
return TelemetryAPI;
|
||||||
});
|
});
|
||||||
|
@ -73,7 +73,7 @@ describe('the plugin', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('provides a folder to hold plans', () => {
|
it('provides a folder to hold plans', () => {
|
||||||
openmct.objects.get(identifier).then((object) => {
|
return openmct.objects.get(identifier).then((object) => {
|
||||||
expect(object).toEqual({
|
expect(object).toEqual({
|
||||||
identifier,
|
identifier,
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
@ -83,7 +83,7 @@ describe('the plugin', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('provides composition for couch search folders', () => {
|
it('provides composition for couch search folders', () => {
|
||||||
composition.load().then((objects) => {
|
return composition.load().then((objects) => {
|
||||||
expect(objects.length).toEqual(2);
|
expect(objects.length).toEqual(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -43,15 +43,15 @@ export default function LADTableSetViewProvider(openmct) {
|
|||||||
components: {
|
components: {
|
||||||
LadTableSet: LadTableSet
|
LadTableSet: LadTableSet
|
||||||
},
|
},
|
||||||
|
provide: {
|
||||||
|
openmct,
|
||||||
|
objectPath
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
domainObject
|
domainObject
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
provide: {
|
|
||||||
openmct,
|
|
||||||
objectPath
|
|
||||||
},
|
|
||||||
template: '<lad-table-set :domain-object="domainObject"></lad-table-set>'
|
template: '<lad-table-set :domain-object="domainObject"></lad-table-set>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -67,10 +67,6 @@ describe("The LAD Table", () => {
|
|||||||
|
|
||||||
// this setups up the app
|
// this setups up the app
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
const appHolder = document.createElement('div');
|
|
||||||
appHolder.style.width = '640px';
|
|
||||||
appHolder.style.height = '480px';
|
|
||||||
|
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
|
|
||||||
parent = document.createElement('div');
|
parent = document.createElement('div');
|
||||||
@ -90,7 +86,7 @@ describe("The LAD Table", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless(appHolder);
|
openmct.startHeadless();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -113,7 +109,8 @@ describe("The LAD Table", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ladTableCompositionCollection = openmct.composition.get(mockObj.ladTable);
|
ladTableCompositionCollection = openmct.composition.get(mockObj.ladTable);
|
||||||
ladTableCompositionCollection.load();
|
|
||||||
|
return ladTableCompositionCollection.load();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should accept telemetry producing objects", () => {
|
it("should accept telemetry producing objects", () => {
|
||||||
@ -192,8 +189,6 @@ describe("The LAD Table", () => {
|
|||||||
|
|
||||||
await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]);
|
await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]);
|
||||||
await Vue.nextTick();
|
await Vue.nextTick();
|
||||||
|
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show one row per object in the composition", () => {
|
it("should show one row per object in the composition", () => {
|
||||||
@ -242,13 +237,6 @@ describe("The LAD Table Set", () => {
|
|||||||
let ladPlugin;
|
let ladPlugin;
|
||||||
let parent;
|
let parent;
|
||||||
let child;
|
let child;
|
||||||
let telemetryCount = 3;
|
|
||||||
let timeFormat = 'utc';
|
|
||||||
|
|
||||||
let mockTelemetry = getMockTelemetry({
|
|
||||||
count: telemetryCount,
|
|
||||||
format: timeFormat
|
|
||||||
});
|
|
||||||
|
|
||||||
let mockObj = getMockObjects({
|
let mockObj = getMockObjects({
|
||||||
objectKeyStrings: ['ladTable', 'ladTableSet', 'telemetry']
|
objectKeyStrings: ['ladTable', 'ladTableSet', 'telemetry']
|
||||||
@ -264,38 +252,36 @@ describe("The LAD Table Set", () => {
|
|||||||
mockObj.ladTableSet.composition.push(mockObj.ladTable.identifier);
|
mockObj.ladTableSet.composition.push(mockObj.ladTable.identifier);
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
const appHolder = document.createElement('div');
|
|
||||||
|
|
||||||
appHolder.style.width = '640px';
|
|
||||||
appHolder.style.height = '480px';
|
|
||||||
|
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
|
|
||||||
parent = document.createElement('div');
|
parent = document.createElement('div');
|
||||||
child = document.createElement('div');
|
child = document.createElement('div');
|
||||||
parent.appendChild(child);
|
parent.appendChild(child);
|
||||||
|
|
||||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
|
||||||
|
|
||||||
ladPlugin = new LadPlugin();
|
ladPlugin = new LadPlugin();
|
||||||
openmct.install(ladPlugin);
|
openmct.install(ladPlugin);
|
||||||
|
|
||||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
|
||||||
|
|
||||||
openmct.time.bounds({
|
openmct.time.bounds({
|
||||||
start: bounds.start,
|
start: bounds.start,
|
||||||
end: bounds.end
|
end: bounds.end
|
||||||
});
|
});
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.start(appHolder);
|
openmct.startHeadless();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
openmct.time.timeSystem('utc', {
|
||||||
|
start: 0,
|
||||||
|
end: 1
|
||||||
|
});
|
||||||
|
|
||||||
return resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should provide a lad table set view only for lad table set objects", () => {
|
it("should provide a lad table set view only for lad table set objects", () => {
|
||||||
|
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||||
|
|
||||||
let applicableViews = openmct.objectViews.get(mockObj.ladTableSet, []);
|
let applicableViews = openmct.objectViews.get(mockObj.ladTableSet, []);
|
||||||
|
|
||||||
let ladTableSetView = applicableViews.find(
|
let ladTableSetView = applicableViews.find(
|
||||||
@ -310,8 +296,11 @@ describe("The LAD Table Set", () => {
|
|||||||
let ladTableSetCompositionCollection;
|
let ladTableSetCompositionCollection;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||||
|
|
||||||
ladTableSetCompositionCollection = openmct.composition.get(mockObj.ladTableSet);
|
ladTableSetCompositionCollection = openmct.composition.get(mockObj.ladTableSet);
|
||||||
ladTableSetCompositionCollection.load();
|
|
||||||
|
return ladTableSetCompositionCollection.load();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should accept lad table objects", () => {
|
it("should accept lad table objects", () => {
|
||||||
@ -349,41 +338,17 @@ describe("The LAD Table Set", () => {
|
|||||||
otherObj.ladTable.composition.push(mockObj.telemetry.identifier);
|
otherObj.ladTable.composition.push(mockObj.telemetry.identifier);
|
||||||
mockObj.ladTableSet.composition.push(otherObj.ladTable.identifier);
|
mockObj.ladTableSet.composition.push(otherObj.ladTable.identifier);
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(() => {
|
||||||
let telemetryRequestResolve;
|
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||||
let ladObjectResolve;
|
|
||||||
let anotherLadObjectResolve;
|
|
||||||
|
|
||||||
let telemetryRequestPromise = new Promise((resolve) => {
|
spyOn(openmct.objects, 'get').and.callFake((obj) => {
|
||||||
telemetryRequestResolve = resolve;
|
|
||||||
});
|
|
||||||
|
|
||||||
let ladObjectPromise = new Promise((resolve) => {
|
|
||||||
ladObjectResolve = resolve;
|
|
||||||
});
|
|
||||||
|
|
||||||
let anotherLadObjectPromise = new Promise((resolve) => {
|
|
||||||
anotherLadObjectResolve = resolve;
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.telemetry.request.and.callFake(() => {
|
|
||||||
telemetryRequestResolve(mockTelemetry);
|
|
||||||
|
|
||||||
return telemetryRequestPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.objects.get.and.callFake((obj) => {
|
|
||||||
if (obj.key === 'lad-object') {
|
if (obj.key === 'lad-object') {
|
||||||
ladObjectResolve(mockObj.ladObject);
|
return Promise.resolve(mockObj.ladTable);
|
||||||
|
|
||||||
return ladObjectPromise;
|
|
||||||
} else if (obj.key === 'another-lad-object') {
|
} else if (obj.key === 'another-lad-object') {
|
||||||
anotherLadObjectResolve(otherObj.ladObject);
|
return Promise.resolve(otherObj.ladTable);
|
||||||
|
} else if (obj.key === 'telemetry-object') {
|
||||||
return anotherLadObjectPromise;
|
return Promise.resolve(mockObj.telemetry);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve({});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
openmct.time.bounds({
|
openmct.time.bounds({
|
||||||
@ -394,20 +359,19 @@ describe("The LAD Table Set", () => {
|
|||||||
applicableViews = openmct.objectViews.get(mockObj.ladTableSet, []);
|
applicableViews = openmct.objectViews.get(mockObj.ladTableSet, []);
|
||||||
ladTableSetViewProvider = applicableViews.find((viewProvider) => viewProvider.key === ladTableSetKey);
|
ladTableSetViewProvider = applicableViews.find((viewProvider) => viewProvider.key === ladTableSetKey);
|
||||||
ladTableSetView = ladTableSetViewProvider.view(mockObj.ladTableSet, [mockObj.ladTableSet]);
|
ladTableSetView = ladTableSetViewProvider.view(mockObj.ladTableSet, [mockObj.ladTableSet]);
|
||||||
ladTableSetView.show(child, true);
|
ladTableSetView.show(child);
|
||||||
|
|
||||||
await Promise.all([telemetryRequestPromise, ladObjectPromise, anotherLadObjectPromise]);
|
return Vue.nextTick();
|
||||||
await Vue.nextTick();
|
|
||||||
|
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show one row per lad table object in the composition", () => {
|
it("should show one row per lad table object in the composition", () => {
|
||||||
|
const ladTableSetCompositionCollection = openmct.composition.get(mockObj.ladTableSet);
|
||||||
|
|
||||||
|
return ladTableSetCompositionCollection.load().then(() => {
|
||||||
const rowCount = parent.querySelectorAll(LAD_SET_TABLE_HEADERS).length;
|
const rowCount = parent.querySelectorAll(LAD_SET_TABLE_HEADERS).length;
|
||||||
|
|
||||||
expect(rowCount).toBe(mockObj.ladTableSet.composition.length);
|
expect(rowCount).toBe(mockObj.ladTableSet.composition.length);
|
||||||
pending();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
@ -22,12 +22,14 @@
|
|||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
|
"utils/testing",
|
||||||
"./URLIndicator",
|
"./URLIndicator",
|
||||||
"./URLIndicatorPlugin",
|
"./URLIndicatorPlugin",
|
||||||
"../../MCT",
|
"../../MCT",
|
||||||
"zepto"
|
"zepto"
|
||||||
],
|
],
|
||||||
function (
|
function (
|
||||||
|
testingUtils,
|
||||||
URLIndicator,
|
URLIndicator,
|
||||||
URLIndicatorPlugin,
|
URLIndicatorPlugin,
|
||||||
MCT,
|
MCT,
|
||||||
@ -44,7 +46,7 @@ define(
|
|||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
jasmine.clock().install();
|
jasmine.clock().install();
|
||||||
openmct = new MCT();
|
openmct = new testingUtils.createOpenMct();
|
||||||
spyOn(openmct.indicators, 'add');
|
spyOn(openmct.indicators, 'add');
|
||||||
spyOn($, 'ajax');
|
spyOn($, 'ajax');
|
||||||
$.ajax.and.callFake(function (options) {
|
$.ajax.and.callFake(function (options) {
|
||||||
@ -55,6 +57,8 @@ define(
|
|||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
$.ajax = defaultAjaxFunction;
|
$.ajax = defaultAjaxFunction;
|
||||||
jasmine.clock().uninstall();
|
jasmine.clock().uninstall();
|
||||||
|
|
||||||
|
return testingUtils.resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("on initialization", function () {
|
describe("on initialization", function () {
|
||||||
|
@ -19,10 +19,6 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import {
|
|
||||||
getAllSearchParams,
|
|
||||||
setAllSearchParams
|
|
||||||
} from 'utils/openmctLocation';
|
|
||||||
|
|
||||||
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
|
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
|
||||||
const SEARCH_MODE = 'tc.mode';
|
const SEARCH_MODE = 'tc.mode';
|
||||||
@ -49,9 +45,8 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
this.updateTimeSettings();
|
this.openmct.router.on('change:params', this.updateTimeSettings);
|
||||||
|
|
||||||
window.addEventListener('hashchange', this.updateTimeSettings);
|
|
||||||
TIME_EVENTS.forEach(event => {
|
TIME_EVENTS.forEach(event => {
|
||||||
this.openmct.time.on(event, this.setUrlFromTimeApi);
|
this.openmct.time.on(event, this.setUrlFromTimeApi);
|
||||||
});
|
});
|
||||||
@ -59,7 +54,8 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
window.removeEventListener('hashchange', this.updateTimeSettings);
|
this.openmct.router.off('change:params', this.updateTimeSettings);
|
||||||
|
|
||||||
this.openmct.off('start', this.initialize);
|
this.openmct.off('start', this.initialize);
|
||||||
this.openmct.off('destroy', this.destroy);
|
this.openmct.off('destroy', this.destroy);
|
||||||
|
|
||||||
@ -70,22 +66,18 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateTimeSettings() {
|
updateTimeSettings() {
|
||||||
// Prevent from triggering self
|
|
||||||
if (!this.isUrlUpdateInProgress) {
|
|
||||||
let timeParameters = this.parseParametersFromUrl();
|
let timeParameters = this.parseParametersFromUrl();
|
||||||
|
|
||||||
if (this.areTimeParametersValid(timeParameters)) {
|
if (this.areTimeParametersValid(timeParameters)) {
|
||||||
this.setTimeApiFromUrl(timeParameters);
|
this.setTimeApiFromUrl(timeParameters);
|
||||||
|
this.openmct.router.setLocationFromUrl();
|
||||||
} else {
|
} else {
|
||||||
this.setUrlFromTimeApi();
|
this.setUrlFromTimeApi();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.isUrlUpdateInProgress = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parseParametersFromUrl() {
|
parseParametersFromUrl() {
|
||||||
let searchParams = getAllSearchParams();
|
let searchParams = this.openmct.router.getAllSearchParams();
|
||||||
|
|
||||||
let mode = searchParams.get(SEARCH_MODE);
|
let mode = searchParams.get(SEARCH_MODE);
|
||||||
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
|
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
|
||||||
@ -148,7 +140,7 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setUrlFromTimeApi() {
|
setUrlFromTimeApi() {
|
||||||
let searchParams = getAllSearchParams();
|
let searchParams = this.openmct.router.getAllSearchParams();
|
||||||
let clock = this.openmct.time.clock();
|
let clock = this.openmct.time.clock();
|
||||||
let bounds = this.openmct.time.bounds();
|
let bounds = this.openmct.time.bounds();
|
||||||
let clockOffsets = this.openmct.time.clockOffsets();
|
let clockOffsets = this.openmct.time.clockOffsets();
|
||||||
@ -176,8 +168,7 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
|
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
|
||||||
this.isUrlUpdateInProgress = true;
|
this.openmct.router.setAllSearchParams(searchParams);
|
||||||
setAllSearchParams(searchParams);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
areTimeParametersValid(timeParameters) {
|
areTimeParametersValid(timeParameters) {
|
||||||
|
@ -25,306 +25,118 @@ import {
|
|||||||
} from 'utils/testing';
|
} from 'utils/testing';
|
||||||
|
|
||||||
describe("The URLTimeSettingsSynchronizer", () => {
|
describe("The URLTimeSettingsSynchronizer", () => {
|
||||||
|
let appHolder;
|
||||||
let openmct;
|
let openmct;
|
||||||
let testClock;
|
let resolveFunction;
|
||||||
|
let oldHash;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
|
openmct.install(openmct.plugins.MyItems());
|
||||||
openmct.install(openmct.plugins.LocalTimeSystem());
|
openmct.install(openmct.plugins.LocalTimeSystem());
|
||||||
testClock = jasmine.createSpyObj("testClock", ["start", "stop", "tick", "currentValue", "on", "off"]);
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
testClock.key = "test-clock";
|
|
||||||
testClock.currentValue.and.returnValue(0);
|
|
||||||
|
|
||||||
openmct.time.addClock(testClock);
|
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless();
|
|
||||||
|
appHolder = document.createElement("div");
|
||||||
|
openmct.start(appHolder);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => resetApplicationState(openmct));
|
afterEach(() => {
|
||||||
|
openmct.time.stopClock();
|
||||||
|
openmct.router.removeListener('change:hash', resolveFunction);
|
||||||
|
|
||||||
describe("realtime mode", () => {
|
appHolder = undefined;
|
||||||
it("when the clock is set via the time API, it is immediately reflected in the URL", () => {
|
openmct = undefined;
|
||||||
//Test expected initial conditions
|
resolveFunction = undefined;
|
||||||
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial clock is set to fixed is reflected in URL", (done) => {
|
||||||
|
resolveFunction = () => {
|
||||||
|
oldHash = window.location.hash;
|
||||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
|
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
|
||||||
|
|
||||||
|
openmct.router.removeListener('change:hash', resolveFunction);
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
openmct.router.on('change:hash', resolveFunction);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when the clock is set via the time API, it is reflected in the URL", (done) => {
|
||||||
|
let success;
|
||||||
|
|
||||||
|
resolveFunction = () => {
|
||||||
openmct.time.clock('local', {
|
openmct.time.clock('local', {
|
||||||
start: -1000,
|
|
||||||
end: 100
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
|
|
||||||
|
|
||||||
//Test that expected initial conditions are no longer true
|
|
||||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
|
||||||
});
|
|
||||||
it("when offsets are set via the time API, they are immediately reflected in the URL", () => {
|
|
||||||
//Test expected initial conditions
|
|
||||||
expect(window.location.hash.includes('tc.startDelta')).toBe(false);
|
|
||||||
expect(window.location.hash.includes('tc.endDelta')).toBe(false);
|
|
||||||
|
|
||||||
openmct.time.clock('local', {
|
|
||||||
start: -1000,
|
|
||||||
end: 100
|
|
||||||
});
|
|
||||||
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
|
|
||||||
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
|
|
||||||
|
|
||||||
openmct.time.clockOffsets({
|
|
||||||
start: -2000,
|
start: -2000,
|
||||||
end: 200
|
end: 200
|
||||||
});
|
});
|
||||||
expect(window.location.hash.includes('tc.startDelta=2000')).toBe(true);
|
|
||||||
expect(window.location.hash.includes('tc.endDelta=200')).toBe(true);
|
|
||||||
|
|
||||||
//Test that expected initial conditions are no longer true
|
const hasStartDelta = window.location.hash.includes('tc.startDelta=2000');
|
||||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
const hasEndDelta = window.location.hash.includes('tc.endDelta=200');
|
||||||
|
const hasLocalClock = window.location.hash.includes('tc.mode=local');
|
||||||
|
success = hasStartDelta && hasEndDelta && hasLocalClock;
|
||||||
|
if (success) {
|
||||||
|
expect(success).toBe(true);
|
||||||
|
|
||||||
|
openmct.router.removeListener('change:hash', resolveFunction);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
openmct.router.on('change:hash', resolveFunction);
|
||||||
});
|
});
|
||||||
describe("when set in the url", () => {
|
|
||||||
it("will change from fixed to realtime mode when the mode changes", () => {
|
|
||||||
expectLocationToBeInFixedMode();
|
|
||||||
|
|
||||||
return switchToRealtimeMode().then(() => {
|
it("when the clock mode is set to local, it is reflected in the URL", (done) => {
|
||||||
let clock = openmct.time.clock();
|
let success;
|
||||||
|
|
||||||
expect(clock).toBeDefined();
|
resolveFunction = () => {
|
||||||
expect(clock.key).toBe('local');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("the clock is correctly set in the API from the URL parameters", () => {
|
|
||||||
return switchToRealtimeMode().then(() => {
|
|
||||||
let resolveFunction;
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
resolveFunction = resolve;
|
|
||||||
|
|
||||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
|
||||||
//detected in the API.
|
|
||||||
openmct.time.on('clock', resolveFunction);
|
|
||||||
let hash = window.location.hash;
|
let hash = window.location.hash;
|
||||||
hash = hash.replace('tc.mode=local', 'tc.mode=test-clock');
|
hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
|
||||||
window.location.hash = hash;
|
window.location.hash = hash;
|
||||||
}).then(() => {
|
|
||||||
let clock = openmct.time.clock();
|
|
||||||
expect(clock).toBeDefined();
|
|
||||||
expect(clock.key).toBe('test-clock');
|
|
||||||
openmct.time.off('clock', resolveFunction);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("the clock offsets are correctly set in the API from the URL parameters", () => {
|
|
||||||
return switchToRealtimeMode().then(() => {
|
|
||||||
let resolveFunction;
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
success = window.location.hash.includes('tc.mode=local');
|
||||||
resolveFunction = resolve;
|
if (success) {
|
||||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
expect(success).toBe(true);
|
||||||
//detected in the API.
|
done();
|
||||||
openmct.time.on('clockOffsets', resolveFunction);
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
openmct.router.on('change:hash', resolveFunction);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when the clock mode is set to local, it is reflected in the URL", (done) => {
|
||||||
|
let success;
|
||||||
|
|
||||||
|
resolveFunction = () => {
|
||||||
let hash = window.location.hash;
|
let hash = window.location.hash;
|
||||||
hash = hash.replace('tc.startDelta=1000', 'tc.startDelta=2000');
|
|
||||||
hash = hash.replace('tc.endDelta=100', 'tc.endDelta=200');
|
hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
|
||||||
window.location.hash = hash;
|
|
||||||
}).then(() => {
|
|
||||||
let clockOffsets = openmct.time.clockOffsets();
|
|
||||||
expect(clockOffsets).toBeDefined();
|
|
||||||
expect(clockOffsets.start).toBe(-2000);
|
|
||||||
expect(clockOffsets.end).toBe(200);
|
|
||||||
openmct.time.off('clockOffsets', resolveFunction);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("the time system is correctly set in the API from the URL parameters", () => {
|
|
||||||
return switchToRealtimeMode().then(() => {
|
|
||||||
let resolveFunction;
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
resolveFunction = resolve;
|
|
||||||
|
|
||||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
|
||||||
//detected in the API.
|
|
||||||
openmct.time.on('timeSystem', resolveFunction);
|
|
||||||
let hash = window.location.hash;
|
|
||||||
hash = hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
|
|
||||||
window.location.hash = hash;
|
|
||||||
}).then(() => {
|
|
||||||
let timeSystem = openmct.time.timeSystem();
|
|
||||||
expect(timeSystem).toBeDefined();
|
|
||||||
expect(timeSystem.key).toBe('local');
|
|
||||||
openmct.time.off('timeSystem', resolveFunction);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe("fixed timespan mode", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
openmct.time.stopClock();
|
|
||||||
openmct.time.timeSystem('utc', {
|
|
||||||
start: 0,
|
|
||||||
end: 1
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when bounds are set via the time API, they are immediately reflected in the URL", () => {
|
|
||||||
//Test expected initial conditions
|
|
||||||
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
|
|
||||||
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
|
|
||||||
|
|
||||||
openmct.time.bounds({
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(window.location.hash.includes('tc.startBound=10')).toBe(true);
|
|
||||||
expect(window.location.hash.includes('tc.endBound=20')).toBe(true);
|
|
||||||
|
|
||||||
//Test that expected initial conditions are no longer true
|
|
||||||
expect(window.location.hash.includes('tc.startBound=0')).toBe(false);
|
|
||||||
expect(window.location.hash.includes('tc.endBound=1')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when time system is set via the time API, it is immediately reflected in the URL", () => {
|
|
||||||
//Test expected initial conditions
|
|
||||||
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(true);
|
|
||||||
|
|
||||||
openmct.time.timeSystem('local', {
|
|
||||||
start: 20,
|
|
||||||
end: 30
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(window.location.hash.includes('tc.timeSystem=local')).toBe(true);
|
|
||||||
|
|
||||||
//Test that expected initial conditions are no longer true
|
|
||||||
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(false);
|
|
||||||
});
|
|
||||||
describe("when set in the url", () => {
|
|
||||||
it("time system changes are reflected in the API", () => {
|
|
||||||
let resolveFunction;
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
let timeSystem = openmct.time.timeSystem();
|
|
||||||
resolveFunction = resolve;
|
|
||||||
|
|
||||||
expect(timeSystem.key).toBe('utc');
|
|
||||||
window.location.hash = window.location.hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
|
|
||||||
|
|
||||||
openmct.time.on('timeSystem', resolveFunction);
|
|
||||||
}).then(() => {
|
|
||||||
let timeSystem = openmct.time.timeSystem();
|
|
||||||
expect(timeSystem.key).toBe('local');
|
|
||||||
|
|
||||||
openmct.time.off('timeSystem', resolveFunction);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("mode can be changed from realtime to fixed", () => {
|
|
||||||
return switchToRealtimeMode().then(() => {
|
|
||||||
expectLocationToBeInRealtimeMode();
|
|
||||||
|
|
||||||
expect(openmct.time.clock()).toBeDefined();
|
|
||||||
}).then(switchToFixedMode).then(() => {
|
|
||||||
let clock = openmct.time.clock();
|
|
||||||
expect(clock).not.toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("bounds are correctly set in the API from the URL parameters", () => {
|
|
||||||
let resolveFunction;
|
|
||||||
|
|
||||||
expectLocationToBeInFixedMode();
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
resolveFunction = resolve;
|
|
||||||
openmct.time.on('bounds', resolveFunction);
|
|
||||||
let hash = window.location.hash;
|
|
||||||
hash = hash.replace('tc.startBound=0', 'tc.startBound=222')
|
|
||||||
.replace('tc.endBound=1', 'tc.endBound=333');
|
|
||||||
window.location.hash = hash;
|
|
||||||
}).then(() => {
|
|
||||||
let bounds = openmct.time.bounds();
|
|
||||||
|
|
||||||
expect(bounds).toBeDefined();
|
|
||||||
expect(bounds.start).toBe(222);
|
|
||||||
expect(bounds.end).toBe(333);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("bounds are correctly set in the API from the URL parameters where only the end bound changes", () => {
|
|
||||||
let resolveFunction;
|
|
||||||
|
|
||||||
expectLocationToBeInFixedMode();
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
resolveFunction = resolve;
|
|
||||||
openmct.time.on('bounds', resolveFunction);
|
|
||||||
let hash = window.location.hash;
|
|
||||||
hash = hash.replace('tc.endBound=1', 'tc.endBound=333');
|
|
||||||
window.location.hash = hash;
|
|
||||||
}).then(() => {
|
|
||||||
let bounds = openmct.time.bounds();
|
|
||||||
|
|
||||||
expect(bounds).toBeDefined();
|
|
||||||
expect(bounds.start).toBe(0);
|
|
||||||
expect(bounds.end).toBe(333);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function setRealtimeLocationParameters() {
|
|
||||||
let hash = window.location.hash.toString()
|
|
||||||
.replace('tc.mode=fixed', 'tc.mode=local')
|
|
||||||
.replace('tc.startBound=0', 'tc.startDelta=1000')
|
|
||||||
.replace('tc.endBound=1', 'tc.endDelta=100');
|
|
||||||
|
|
||||||
window.location.hash = hash;
|
window.location.hash = hash;
|
||||||
|
success = window.location.hash.includes('tc.mode=local');
|
||||||
|
if (success) {
|
||||||
|
expect(success).toBe(true);
|
||||||
|
done();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function setFixedLocationParameters() {
|
openmct.router.on('change:hash', resolveFunction);
|
||||||
let hash = window.location.hash.toString()
|
|
||||||
.replace('tc.mode=local', 'tc.mode=fixed')
|
|
||||||
.replace('tc.timeSystem=utc', 'tc.timeSystem=local')
|
|
||||||
.replace('tc.startDelta=1000', 'tc.startBound=50')
|
|
||||||
.replace('tc.endDelta=100', 'tc.endBound=60');
|
|
||||||
|
|
||||||
window.location.hash = hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
function switchToRealtimeMode() {
|
|
||||||
let resolveFunction;
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
resolveFunction = resolve;
|
|
||||||
openmct.time.on('clock', resolveFunction);
|
|
||||||
setRealtimeLocationParameters();
|
|
||||||
}).then(() => {
|
|
||||||
openmct.time.off('clock', resolveFunction);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reset hash", (done) => {
|
||||||
|
let success;
|
||||||
|
|
||||||
|
window.location.hash = oldHash;
|
||||||
|
resolveFunction = () => {
|
||||||
|
success = window.location.hash === oldHash;
|
||||||
|
if (success) {
|
||||||
|
expect(success).toBe(true);
|
||||||
|
done();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function switchToFixedMode() {
|
openmct.router.on('change:hash', resolveFunction);
|
||||||
let resolveFunction;
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
resolveFunction = resolve;
|
|
||||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
|
||||||
//detected in the API.
|
|
||||||
openmct.time.on('clock', resolveFunction);
|
|
||||||
setFixedLocationParameters();
|
|
||||||
}).then(() => {
|
|
||||||
openmct.time.off('clock', resolveFunction);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
function expectLocationToBeInRealtimeMode() {
|
|
||||||
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
|
|
||||||
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
|
|
||||||
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
|
|
||||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function expectLocationToBeInFixedMode() {
|
|
||||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
|
|
||||||
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
|
|
||||||
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
|
|
||||||
expect(window.location.hash.includes('tc.mode=local')).toBe(false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
@ -28,8 +28,10 @@ import {
|
|||||||
resetApplicationState,
|
resetApplicationState,
|
||||||
spyOnBuiltins
|
spyOnBuiltins
|
||||||
} from 'utils/testing';
|
} from 'utils/testing';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
describe("AutoflowTabularPlugin", () => {
|
// TODO lots of its without expects
|
||||||
|
xdescribe("AutoflowTabularPlugin", () => {
|
||||||
let testType;
|
let testType;
|
||||||
let testObject;
|
let testObject;
|
||||||
let mockmct;
|
let mockmct;
|
||||||
@ -51,7 +53,7 @@ describe("AutoflowTabularPlugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(mockmct);
|
return resetApplicationState(mockmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("installs a view provider", () => {
|
it("installs a view provider", () => {
|
||||||
@ -101,7 +103,7 @@ describe("AutoflowTabularPlugin", () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach(() => {
|
||||||
callbacks = {};
|
callbacks = {};
|
||||||
|
|
||||||
spyOnBuiltins(['requestAnimationFrame']);
|
spyOnBuiltins(['requestAnimationFrame']);
|
||||||
@ -180,7 +182,7 @@ describe("AutoflowTabularPlugin", () => {
|
|||||||
view = provider.view(testObject);
|
view = provider.view(testObject);
|
||||||
view.show(testContainer);
|
view.show(testContainer);
|
||||||
|
|
||||||
return done();
|
return Vue.nextTick();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -27,15 +27,17 @@ export default class StyleRuleManager extends EventEmitter {
|
|||||||
super();
|
super();
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
this.refreshData = this.refreshData.bind(this);
|
||||||
|
this.toggleSubscription = this.toggleSubscription.bind(this);
|
||||||
if (suppressSubscriptionOnEdit) {
|
if (suppressSubscriptionOnEdit) {
|
||||||
this.openmct.editor.on('isEditing', this.toggleSubscription.bind(this));
|
this.openmct.editor.on('isEditing', this.toggleSubscription);
|
||||||
this.isEditing = this.openmct.editor.editing;
|
this.isEditing = this.openmct.editor.editing;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (styleConfiguration) {
|
if (styleConfiguration) {
|
||||||
this.initialize(styleConfiguration);
|
this.initialize(styleConfiguration);
|
||||||
if (styleConfiguration.conditionSetIdentifier) {
|
if (styleConfiguration.conditionSetIdentifier) {
|
||||||
this.openmct.time.on("bounds", this.refreshData.bind(this));
|
this.openmct.time.on("bounds", this.refreshData);
|
||||||
this.subscribeToConditionSet();
|
this.subscribeToConditionSet();
|
||||||
} else {
|
} else {
|
||||||
this.applyStaticStyle();
|
this.applyStaticStyle();
|
||||||
|
@ -52,7 +52,6 @@
|
|||||||
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
||||||
<a v-if="conditionSetDomainObject"
|
<a v-if="conditionSetDomainObject"
|
||||||
class="c-object-label icon-conditional"
|
class="c-object-label icon-conditional"
|
||||||
:href="navigateToPath"
|
|
||||||
@click="navigateOrPreview"
|
@click="navigateOrPreview"
|
||||||
>
|
>
|
||||||
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
|
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
|
||||||
@ -286,6 +285,8 @@ export default {
|
|||||||
if (this.openmct.editor.isEditing()) {
|
if (this.openmct.editor.isEditing()) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.previewAction.invoke(this.objectPath);
|
this.previewAction.invoke(this.objectPath);
|
||||||
|
} else {
|
||||||
|
this.openmct.router.navigate(this.navigateToPath);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeConditionSet() {
|
removeConditionSet() {
|
||||||
|
@ -66,7 +66,6 @@
|
|||||||
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
||||||
<a v-if="conditionSetDomainObject"
|
<a v-if="conditionSetDomainObject"
|
||||||
class="c-object-label"
|
class="c-object-label"
|
||||||
:href="navigateToPath"
|
|
||||||
@click="navigateOrPreview"
|
@click="navigateOrPreview"
|
||||||
>
|
>
|
||||||
<span class="c-object-label__type-icon icon-conditional"></span>
|
<span class="c-object-label__type-icon icon-conditional"></span>
|
||||||
@ -309,6 +308,8 @@ export default {
|
|||||||
if (this.openmct.editor.isEditing()) {
|
if (this.openmct.editor.isEditing()) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.previewAction.invoke(this.objectPath);
|
this.previewAction.invoke(this.objectPath);
|
||||||
|
} else {
|
||||||
|
this.openmct.router.navigate(this.navigateToPath);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isItemType(type, item) {
|
isItemType(type, item) {
|
||||||
|
@ -46,7 +46,7 @@ xdescribe("the plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('installs the new folder action', () => {
|
it('installs the new folder action', () => {
|
||||||
|
@ -235,7 +235,7 @@ define(['lodash'], function (_) {
|
|||||||
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
|
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: 'Ok',
|
label: 'OK',
|
||||||
emphasis: 'true',
|
emphasis: 'true',
|
||||||
callback: function () {
|
callback: function () {
|
||||||
removeItem(getAllTypes(selection));
|
removeItem(getAllTypes(selection));
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
margin-left: $interiorMargin;
|
margin-left: $interiorMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
@include isLimit();
|
||||||
|
}
|
||||||
|
|
||||||
.c-frame & {
|
.c-frame & {
|
||||||
@include abs();
|
@include abs();
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
|
@ -37,7 +37,15 @@ export default class DuplicateAction {
|
|||||||
let duplicationTask = new DuplicateTask(this.openmct);
|
let duplicationTask = new DuplicateTask(this.openmct);
|
||||||
let originalObject = objectPath[0];
|
let originalObject = objectPath[0];
|
||||||
let parent = objectPath[1];
|
let parent = objectPath[1];
|
||||||
let userInput = await this.getUserInput(originalObject, parent);
|
let userInput;
|
||||||
|
|
||||||
|
try {
|
||||||
|
userInput = await this.getUserInput(originalObject, parent);
|
||||||
|
} catch (error) {
|
||||||
|
// user most likely canceled
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let newParent = userInput.location;
|
let newParent = userInput.location;
|
||||||
let inNavigationPath = this.inNavigationPath(originalObject);
|
let inNavigationPath = this.inNavigationPath(originalObject);
|
||||||
|
|
||||||
@ -71,7 +79,8 @@ export default class DuplicateAction {
|
|||||||
|
|
||||||
updateNameCheck(object, name) {
|
updateNameCheck(object, name) {
|
||||||
if (object.name !== name) {
|
if (object.name !== name) {
|
||||||
this.openmct.objects.mutate(object, 'name', name);
|
object.name = name;
|
||||||
|
this.openmct.objects.save(object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +104,7 @@ export default class DuplicateAction {
|
|||||||
cssClass: "l-input-lg"
|
cssClass: "l-input-lg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "location",
|
name: "Location",
|
||||||
cssClass: "grows",
|
cssClass: "grows",
|
||||||
control: "locator",
|
control: "locator",
|
||||||
validate: this.validate(object, parent),
|
validate: this.validate(object, parent),
|
||||||
|
@ -112,7 +112,7 @@ describe("The Duplicate Action plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be defined", () => {
|
it("should be defined", () => {
|
||||||
@ -121,10 +121,9 @@ describe("The Duplicate Action plugin", () => {
|
|||||||
|
|
||||||
describe("when moving an object to a new parent", () => {
|
describe("when moving an object to a new parent", () => {
|
||||||
|
|
||||||
beforeEach(async (done) => {
|
beforeEach(async () => {
|
||||||
duplicateTask = new DuplicateTask(openmct);
|
duplicateTask = new DuplicateTask(openmct);
|
||||||
await duplicateTask.duplicate(parentObject, anotherParentObject);
|
await duplicateTask.duplicate(parentObject, anotherParentObject);
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("the duplicate child object's name (when not changing) should be the same as the original object", async () => {
|
it("the duplicate child object's name (when not changing) should be the same as the original object", async () => {
|
||||||
@ -143,15 +142,15 @@ describe("The Duplicate Action plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("when a new name is provided for the duplicated object", () => {
|
describe("when a new name is provided for the duplicated object", () => {
|
||||||
|
it("the name is updated", () => {
|
||||||
const NEW_NAME = 'New Name';
|
const NEW_NAME = 'New Name';
|
||||||
|
let childName;
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
duplicateTask = new DuplicateAction(openmct);
|
duplicateTask = new DuplicateAction(openmct);
|
||||||
duplicateTask.updateNameCheck(parentObject, NEW_NAME);
|
duplicateTask.updateNameCheck(parentObject, NEW_NAME);
|
||||||
});
|
|
||||||
|
|
||||||
it("the name is updated", () => {
|
childName = parentObject.name;
|
||||||
let childName = parentObject.name;
|
|
||||||
expect(childName).toEqual(NEW_NAME);
|
expect(childName).toEqual(NEW_NAME);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -97,7 +97,7 @@ function ToolbarProvider(openmct) {
|
|||||||
message: `This action will remove this frame from this Flexible Layout. Do you want to continue?`,
|
message: `This action will remove this frame from this Flexible Layout. Do you want to continue?`,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: 'Ok',
|
label: 'OK',
|
||||||
emphasis: 'true',
|
emphasis: 'true',
|
||||||
callback: function () {
|
callback: function () {
|
||||||
deleteFrameAction(primary.context.frameId);
|
deleteFrameAction(primary.context.frameId);
|
||||||
@ -162,7 +162,7 @@ function ToolbarProvider(openmct) {
|
|||||||
message: 'This action will permanently delete this container from this Flexible Layout',
|
message: 'This action will permanently delete this container from this Flexible Layout',
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: 'Ok',
|
label: 'OK',
|
||||||
emphasis: 'true',
|
emphasis: 'true',
|
||||||
callback: function () {
|
callback: function () {
|
||||||
removeContainer(containerId);
|
removeContainer(containerId);
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
'is-alias': item.isAlias === true,
|
'is-alias': item.isAlias === true,
|
||||||
'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1
|
'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1
|
||||||
}, statusClass]"
|
}, statusClass]"
|
||||||
:href="objectLink"
|
@click="navigate"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c-grid-item__type-icon"
|
class="c-grid-item__type-icon"
|
||||||
@ -49,11 +49,17 @@ import statusListener from './status-listener';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [contextMenuGesture, objectLink, statusListener],
|
mixins: [contextMenuGesture, objectLink, statusListener],
|
||||||
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
navigate() {
|
||||||
|
this.openmct.router.navigate(this.objectLink);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
ref="objectLink"
|
ref="objectLink"
|
||||||
class="c-object-label"
|
class="c-object-label"
|
||||||
:class="[statusClass]"
|
:class="[statusClass]"
|
||||||
:href="objectLink"
|
@click="navigate"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c-object-label__type-icon c-list-item__name__type-icon"
|
class="c-object-label__type-icon c-list-item__name__type-icon"
|
||||||
@ -45,6 +45,7 @@ import statusListener from './status-listener';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [contextMenuGesture, objectLink, statusListener],
|
mixins: [contextMenuGesture, objectLink, statusListener],
|
||||||
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -56,7 +57,7 @@ export default {
|
|||||||
return moment(timestamp).format(format);
|
return moment(timestamp).format(format);
|
||||||
},
|
},
|
||||||
navigate() {
|
navigate() {
|
||||||
this.$refs.objectLink.click();
|
this.openmct.router.navigate(this.objectLink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
margin: 0 $interiorMargin $interiorMargin 0;
|
margin: 0 $interiorMargin $interiorMargin 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.mobile & {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************* GRID ITEMS */
|
/******************************* GRID ITEMS */
|
||||||
|
@ -41,7 +41,7 @@ export default class GoToOriginalAction {
|
|||||||
.slice(1)
|
.slice(1)
|
||||||
.join('/');
|
.join('/');
|
||||||
|
|
||||||
window.location.href = url;
|
this._openmct.router.navigate(url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
appliesTo(objectPath) {
|
appliesTo(objectPath) {
|
||||||
|
@ -24,10 +24,15 @@ import {
|
|||||||
resetApplicationState
|
resetApplicationState
|
||||||
} from 'utils/testing';
|
} from 'utils/testing';
|
||||||
|
|
||||||
describe("the plugin", () => {
|
describe("the goToOriginalAction plugin", () => {
|
||||||
let openmct;
|
let openmct;
|
||||||
let goToFolderAction;
|
let goToOriginalAction;
|
||||||
|
let mockRootFolder;
|
||||||
|
let mockSubFolder;
|
||||||
|
let mockSubSubFolder;
|
||||||
|
let mockObject;
|
||||||
let mockObjectPath;
|
let mockObjectPath;
|
||||||
|
let hash;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
@ -35,7 +40,7 @@ describe("the plugin", () => {
|
|||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless();
|
openmct.startHeadless();
|
||||||
|
|
||||||
goToFolderAction = openmct.actions._allActions.goToOriginal;
|
goToOriginalAction = openmct.actions._allActions.goToOriginal;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -43,31 +48,153 @@ describe("the plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('installs the go to folder action', () => {
|
it('installs the go to folder action', () => {
|
||||||
expect(goToFolderAction).toBeDefined();
|
expect(goToOriginalAction).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when invoked', () => {
|
describe('when invoked', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockObjectPath = [{
|
mockRootFolder = getMockObject('mock-root');
|
||||||
name: 'mock folder',
|
mockSubFolder = getMockObject('mock-sub');
|
||||||
type: 'folder',
|
mockSubSubFolder = getMockObject('mock-sub-sub');
|
||||||
identifier: {
|
mockObject = getMockObject('mock-table');
|
||||||
key: 'mock-folder',
|
|
||||||
namespace: ''
|
mockObjectPath = [
|
||||||
}
|
mockObject,
|
||||||
}];
|
mockSubSubFolder,
|
||||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({
|
mockSubFolder,
|
||||||
identifier: {
|
mockRootFolder
|
||||||
namespace: '',
|
];
|
||||||
key: 'test'
|
|
||||||
}
|
spyOn(openmct.objects, 'get').and.callFake(identifier => {
|
||||||
}));
|
const mockedObject = getMockObject(identifier);
|
||||||
goToFolderAction.invoke(mockObjectPath);
|
|
||||||
|
return Promise.resolve(mockedObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
spyOn(openmct.router, 'navigate').and.callFake(navigateTo => {
|
||||||
|
hash = navigateTo;
|
||||||
|
});
|
||||||
|
|
||||||
|
return goToOriginalAction.invoke(mockObjectPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes to the original location', () => {
|
it('goes to the original location', () => {
|
||||||
expect(window.location.href).toContain('context.html#/browse/?tc.mode=fixed&tc.startBound=0&tc.endBound=1&tc.timeSystem=utc');
|
const originalLocationHash = '#/browse/mock-root/mock-table';
|
||||||
|
|
||||||
|
return waitForNavigation(() => {
|
||||||
|
return hash === originalLocationHash;
|
||||||
|
}).then(() => {
|
||||||
|
expect(hash).toEqual(originalLocationHash);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function waitForNavigation(navigated) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
|
checkNavigated();
|
||||||
|
|
||||||
|
function checkNavigated() {
|
||||||
|
const elapsed = Date.now() - start;
|
||||||
|
|
||||||
|
if (navigated()) {
|
||||||
|
resolve();
|
||||||
|
} else if (elapsed >= jasmine.DEFAULT_TIMEOUT_INTERVAL - 1000) {
|
||||||
|
reject("didn't navigate in time");
|
||||||
|
} else {
|
||||||
|
setTimeout(checkNavigated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMockObject(key) {
|
||||||
|
const id = typeof key === 'string' ? key : key.key;
|
||||||
|
|
||||||
|
const mockMCTObjects = {
|
||||||
|
"ROOT": {
|
||||||
|
"composition": [
|
||||||
|
{
|
||||||
|
"namespace": "",
|
||||||
|
"key": "mock-root"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"identifier": {
|
||||||
|
"namespace": "",
|
||||||
|
"key": "mock-root"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mock-root": {
|
||||||
|
"composition": [
|
||||||
|
{
|
||||||
|
"namespace": "",
|
||||||
|
"key": "mock-sub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "",
|
||||||
|
"key": "mock-table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "root",
|
||||||
|
"type": "folder",
|
||||||
|
"id": "mock-root",
|
||||||
|
"location": "ROOT",
|
||||||
|
"identifier": {
|
||||||
|
"namespace": "",
|
||||||
|
"key": "mock-root"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mock-sub": {
|
||||||
|
"composition": [
|
||||||
|
{
|
||||||
|
"namespace": "",
|
||||||
|
"key": "mock-sub-sub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "",
|
||||||
|
"key": "mock-table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "sub",
|
||||||
|
"type": "folder",
|
||||||
|
"location": "mock-root",
|
||||||
|
"identifier": {
|
||||||
|
"namespace": "",
|
||||||
|
"key": "mock-sub"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mock-table": {
|
||||||
|
"composition": [],
|
||||||
|
"configuration": {
|
||||||
|
"columnWidths": {},
|
||||||
|
"hiddenColumns": {}
|
||||||
|
},
|
||||||
|
"name": "table",
|
||||||
|
"type": "table",
|
||||||
|
"location": "mock-root",
|
||||||
|
"identifier": {
|
||||||
|
"namespace": "",
|
||||||
|
"key": "mock-table"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mock-sub-sub": {
|
||||||
|
"composition": [
|
||||||
|
{
|
||||||
|
"namespace": "",
|
||||||
|
"key": "mock-table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "sub sub",
|
||||||
|
"type": "folder",
|
||||||
|
"location": "mock-sub",
|
||||||
|
"identifier": {
|
||||||
|
"namespace": "",
|
||||||
|
"key": "mock-sub-sub"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return mockMCTObjects[id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
:camera-pan="cameraPan"
|
:camera-pan="cameraPan"
|
||||||
/>
|
/>
|
||||||
<CompassRose
|
<CompassRose
|
||||||
v-if="true"
|
v-if="hasCameraFieldOfView"
|
||||||
:heading="heading"
|
:heading="heading"
|
||||||
:sized-image-width="sizedImageDimensions.width"
|
:sized-image-width="sizedImageDimensions.width"
|
||||||
:sun-heading="sunHeading"
|
:sun-heading="sunHeading"
|
||||||
|
@ -390,7 +390,9 @@ export default {
|
|||||||
delete this.unsubscribe;
|
delete this.unsubscribe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.imageContainerResizeObserver) {
|
||||||
this.imageContainerResizeObserver.disconnect();
|
this.imageContainerResizeObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
||||||
this.relatedTelemetry.destroy();
|
this.relatedTelemetry.destroy();
|
||||||
@ -702,7 +704,7 @@ export default {
|
|||||||
window.clearInterval(this.durationTracker);
|
window.clearInterval(this.durationTracker);
|
||||||
},
|
},
|
||||||
updateDuration() {
|
updateDuration() {
|
||||||
let currentTime = this.openmct.time.clock().currentValue();
|
let currentTime = this.openmct.time.clock() && this.openmct.time.clock().currentValue();
|
||||||
this.numericDuration = currentTime - this.parsedSelectedTime;
|
this.numericDuration = currentTime - this.parsedSelectedTime;
|
||||||
},
|
},
|
||||||
resetAgeCSS() {
|
resetAgeCSS() {
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import ImageryPlugin from './plugin.js';
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import {
|
import {
|
||||||
createOpenMct,
|
createOpenMct,
|
||||||
@ -89,15 +89,11 @@ describe("The Imagery View Layout", () => {
|
|||||||
const START = Date.now();
|
const START = Date.now();
|
||||||
const COUNT = 10;
|
const COUNT = 10;
|
||||||
|
|
||||||
|
let resolveFunction;
|
||||||
|
|
||||||
let openmct;
|
let openmct;
|
||||||
let imageryPlugin;
|
|
||||||
let parent;
|
let parent;
|
||||||
let child;
|
let child;
|
||||||
let timeFormat = 'utc';
|
|
||||||
let bounds = {
|
|
||||||
start: START - TEN_MINUTES,
|
|
||||||
end: START
|
|
||||||
};
|
|
||||||
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
|
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
|
||||||
let imageryObject = {
|
let imageryObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
@ -205,6 +201,10 @@ describe("The Imagery View Layout", () => {
|
|||||||
|
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
|
|
||||||
|
openmct.install(openmct.plugins.MyItems());
|
||||||
|
openmct.install(openmct.plugins.LocalTimeSystem());
|
||||||
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
|
|
||||||
parent = document.createElement('div');
|
parent = document.createElement('div');
|
||||||
child = document.createElement('div');
|
child = document.createElement('div');
|
||||||
parent.appendChild(child);
|
parent.appendChild(child);
|
||||||
@ -215,22 +215,18 @@ describe("The Imagery View Layout", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||||
|
|
||||||
imageryPlugin = new ImageryPlugin();
|
|
||||||
openmct.install(imageryPlugin);
|
|
||||||
|
|
||||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||||
|
|
||||||
openmct.time.timeSystem(timeFormat, {
|
|
||||||
start: 0,
|
|
||||||
end: 4
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless(appHolder);
|
openmct.start(appHolder);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
openmct.time.timeSystem('utc', {
|
||||||
|
start: 0,
|
||||||
|
end: 1
|
||||||
|
});
|
||||||
|
|
||||||
return resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -248,7 +244,7 @@ describe("The Imagery View Layout", () => {
|
|||||||
let imageryViewProvider;
|
let imageryViewProvider;
|
||||||
let imageryView;
|
let imageryView;
|
||||||
|
|
||||||
beforeEach(async (done) => {
|
beforeEach(async () => {
|
||||||
let telemetryRequestResolve;
|
let telemetryRequestResolve;
|
||||||
let telemetryRequestPromise = new Promise((resolve) => {
|
let telemetryRequestPromise = new Promise((resolve) => {
|
||||||
telemetryRequestResolve = resolve;
|
telemetryRequestResolve = resolve;
|
||||||
@ -260,23 +256,18 @@ describe("The Imagery View Layout", () => {
|
|||||||
return telemetryRequestPromise;
|
return telemetryRequestPromise;
|
||||||
});
|
});
|
||||||
|
|
||||||
openmct.time.clock('local', {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end + 100
|
|
||||||
});
|
|
||||||
|
|
||||||
applicableViews = openmct.objectViews.get(imageryObject, []);
|
applicableViews = openmct.objectViews.get(imageryObject, []);
|
||||||
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
|
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
|
||||||
imageryView = imageryViewProvider.view(imageryObject);
|
imageryView = imageryViewProvider.view(imageryObject);
|
||||||
imageryView.show(child);
|
imageryView.show(child);
|
||||||
|
|
||||||
await telemetryRequestPromise;
|
await telemetryRequestPromise;
|
||||||
await Vue.nextTick();
|
|
||||||
|
|
||||||
return done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
openmct.time.stopClock();
|
||||||
|
openmct.router.removeListener('change:hash', resolveFunction);
|
||||||
|
|
||||||
imageryView.destroy();
|
imageryView.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -286,33 +277,33 @@ describe("The Imagery View Layout", () => {
|
|||||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show the clicked thumbnail as the main image", async () => {
|
it("should show the clicked thumbnail as the main image", (done) => {
|
||||||
const target = imageTelemetry[5].url;
|
const target = imageTelemetry[5].url;
|
||||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
await Vue.nextTick();
|
Vue.nextTick(() => {
|
||||||
const imageInfo = getImageInfo(parent);
|
const imageInfo = getImageInfo(parent);
|
||||||
|
|
||||||
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
|
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show that an image is new", async (done) => {
|
xit("should show that an image is new", (done) => {
|
||||||
await Vue.nextTick();
|
Vue.nextTick(() => {
|
||||||
|
|
||||||
// used in code, need to wait to the 500ms here too
|
// used in code, need to wait to the 500ms here too
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const imageIsNew = isNew(parent);
|
const imageIsNew = isNew(parent);
|
||||||
|
|
||||||
expect(imageIsNew).toBeTrue();
|
expect(imageIsNew).toBeTrue();
|
||||||
done();
|
done();
|
||||||
}, REFRESH_CSS_MS);
|
}, REFRESH_CSS_MS);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should show that an image is not new", async (done) => {
|
xit("should show that an image is not new", (done) => {
|
||||||
const target = imageTelemetry[2].url;
|
const target = imageTelemetry[2].url;
|
||||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
|
|
||||||
await Vue.nextTick();
|
Vue.nextTick(() => {
|
||||||
|
|
||||||
// used in code, need to wait to the 500ms here too
|
// used in code, need to wait to the 500ms here too
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const imageIsNew = isNew(parent);
|
const imageIsNew = isNew(parent);
|
||||||
@ -321,8 +312,9 @@ describe("The Imagery View Layout", () => {
|
|||||||
done();
|
done();
|
||||||
}, REFRESH_CSS_MS);
|
}, REFRESH_CSS_MS);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should navigate via arrow keys", async () => {
|
it("should navigate via arrow keys", (done) => {
|
||||||
let keyOpts = {
|
let keyOpts = {
|
||||||
element: parent.querySelector('.c-imagery'),
|
element: parent.querySelector('.c-imagery'),
|
||||||
key: 'ArrowLeft',
|
key: 'ArrowLeft',
|
||||||
@ -332,14 +324,15 @@ describe("The Imagery View Layout", () => {
|
|||||||
|
|
||||||
simulateKeyEvent(keyOpts);
|
simulateKeyEvent(keyOpts);
|
||||||
|
|
||||||
await Vue.nextTick();
|
Vue.nextTick(() => {
|
||||||
|
|
||||||
const imageInfo = getImageInfo(parent);
|
const imageInfo = getImageInfo(parent);
|
||||||
|
|
||||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 2].timeId)).not.toEqual(-1);
|
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 2].timeId)).not.toEqual(-1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should navigate via numerous arrow keys", async () => {
|
it("should navigate via numerous arrow keys", (done) => {
|
||||||
let element = parent.querySelector('.c-imagery');
|
let element = parent.querySelector('.c-imagery');
|
||||||
let type = 'keyup';
|
let type = 'keyup';
|
||||||
let leftKeyOpts = {
|
let leftKeyOpts = {
|
||||||
@ -362,12 +355,12 @@ describe("The Imagery View Layout", () => {
|
|||||||
// right once
|
// right once
|
||||||
simulateKeyEvent(rightKeyOpts);
|
simulateKeyEvent(rightKeyOpts);
|
||||||
|
|
||||||
await Vue.nextTick();
|
Vue.nextTick(() => {
|
||||||
|
|
||||||
const imageInfo = getImageInfo(parent);
|
const imageInfo = getImageInfo(parent);
|
||||||
|
|
||||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 3].timeId)).not.toEqual(-1);
|
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 3].timeId)).not.toEqual(-1);
|
||||||
});
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -30,10 +30,6 @@ describe('the plugin', function () {
|
|||||||
const TEST_NAMESPACE = 'test';
|
const TEST_NAMESPACE = 'test';
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
const appHolder = document.createElement('div');
|
|
||||||
appHolder.style.width = '640px';
|
|
||||||
appHolder.style.height = '480px';
|
|
||||||
|
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
openmct.install(new InterceptorPlugin(openmct));
|
openmct.install(new InterceptorPlugin(openmct));
|
||||||
|
|
||||||
@ -46,7 +42,7 @@ describe('the plugin', function () {
|
|||||||
element.appendChild(child);
|
element.appendChild(child);
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless(appHolder);
|
openmct.startHeadless();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -55,6 +51,7 @@ describe('the plugin', function () {
|
|||||||
|
|
||||||
describe('the missingObjectInterceptor', () => {
|
describe('the missingObjectInterceptor', () => {
|
||||||
let mockProvider;
|
let mockProvider;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockProvider = jasmine.createSpyObj("mock provider", [
|
mockProvider = jasmine.createSpyObj("mock provider", [
|
||||||
"get"
|
"get"
|
||||||
@ -63,27 +60,28 @@ describe('the plugin', function () {
|
|||||||
openmct.objects.addProvider(TEST_NAMESPACE, mockProvider);
|
openmct.objects.addProvider(TEST_NAMESPACE, mockProvider);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns missing objects', (done) => {
|
it('returns missing objects', () => {
|
||||||
const identifier = {
|
const identifier = {
|
||||||
namespace: TEST_NAMESPACE,
|
namespace: TEST_NAMESPACE,
|
||||||
key: 'hello'
|
key: 'hello'
|
||||||
};
|
};
|
||||||
openmct.objects.get(identifier).then((testObject) => {
|
|
||||||
|
return openmct.objects.get(identifier).then((testObject) => {
|
||||||
expect(testObject).toEqual({
|
expect(testObject).toEqual({
|
||||||
identifier,
|
identifier,
|
||||||
type: 'unknown',
|
type: 'unknown',
|
||||||
name: 'Missing: test:hello'
|
name: 'Missing: test:hello'
|
||||||
});
|
});
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the My items object if not found', (done) => {
|
it('returns the My items object if not found', () => {
|
||||||
const identifier = {
|
const identifier = {
|
||||||
namespace: TEST_NAMESPACE,
|
namespace: TEST_NAMESPACE,
|
||||||
key: 'mine'
|
key: 'mine'
|
||||||
};
|
};
|
||||||
openmct.objects.get(identifier).then((testObject) => {
|
|
||||||
|
return openmct.objects.get(identifier).then((testObject) => {
|
||||||
expect(testObject).toEqual({
|
expect(testObject).toEqual({
|
||||||
identifier,
|
identifier,
|
||||||
"name": "My Items",
|
"name": "My Items",
|
||||||
@ -91,7 +89,6 @@ describe('the plugin', function () {
|
|||||||
"composition": [],
|
"composition": [],
|
||||||
"location": "ROOT"
|
"location": "ROOT"
|
||||||
});
|
});
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ describe("The local time", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
localTimeSystem = openmct.time.timeSystem(LOCAL_SYSTEM_KEY, {
|
localTimeSystem = openmct.time.timeSystem(LOCAL_SYSTEM_KEY, {
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 4
|
end: 1
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -37,7 +37,14 @@ export default class MoveAction {
|
|||||||
let oldParent = objectPath[1];
|
let oldParent = objectPath[1];
|
||||||
let dialogService = this.openmct.$injector.get('dialogService');
|
let dialogService = this.openmct.$injector.get('dialogService');
|
||||||
let dialogForm = this.getDialogForm(object, oldParent);
|
let dialogForm = this.getDialogForm(object, oldParent);
|
||||||
let userInput = await dialogService.getUserInput(dialogForm, { name: object.name });
|
let userInput;
|
||||||
|
|
||||||
|
try {
|
||||||
|
userInput = await dialogService.getUserInput(dialogForm, { name: object.name });
|
||||||
|
} catch (err) {
|
||||||
|
// user canceled, most likely
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// if we need to update name
|
// if we need to update name
|
||||||
if (object.name !== userInput.name) {
|
if (object.name !== userInput.name) {
|
||||||
@ -104,13 +111,13 @@ export default class MoveAction {
|
|||||||
{
|
{
|
||||||
key: "name",
|
key: "name",
|
||||||
control: "textfield",
|
control: "textfield",
|
||||||
name: "Folder Name",
|
name: "Name",
|
||||||
pattern: "\\S+",
|
pattern: "\\S+",
|
||||||
required: true,
|
required: true,
|
||||||
cssClass: "l-input-lg"
|
cssClass: "l-input-lg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "location",
|
name: "Location",
|
||||||
control: "locator",
|
control: "locator",
|
||||||
validate: this.validate(object, parent),
|
validate: this.validate(object, parent),
|
||||||
key: 'location'
|
key: 'location'
|
||||||
|
@ -19,8 +19,6 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import MoveActionPlugin from './plugin.js';
|
|
||||||
import MoveAction from './MoveAction.js';
|
|
||||||
import {
|
import {
|
||||||
createOpenMct,
|
createOpenMct,
|
||||||
resetApplicationState,
|
resetApplicationState,
|
||||||
@ -37,10 +35,6 @@ describe("The Move Action plugin", () => {
|
|||||||
|
|
||||||
// this setups up the app
|
// this setups up the app
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
const appHolder = document.createElement('div');
|
|
||||||
appHolder.style.width = '640px';
|
|
||||||
appHolder.style.height = '480px';
|
|
||||||
|
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
|
|
||||||
childObject = getMockObjects({
|
childObject = getMockObjects({
|
||||||
@ -73,25 +67,23 @@ describe("The Move Action plugin", () => {
|
|||||||
}
|
}
|
||||||
}).folder;
|
}).folder;
|
||||||
|
|
||||||
// already installed by default, but never hurts, just adds to context menu
|
|
||||||
openmct.install(MoveActionPlugin());
|
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless(appHolder);
|
openmct.startHeadless();
|
||||||
|
|
||||||
|
moveAction = openmct.actions._allActions.move;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be defined", () => {
|
it("should be defined", () => {
|
||||||
expect(MoveActionPlugin).toBeDefined();
|
expect(moveAction).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when moving an object to a new parent and removing from the old parent", () => {
|
describe("when moving an object to a new parent and removing from the old parent", () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
moveAction = new MoveAction(openmct);
|
|
||||||
moveAction.addToNewParent(childObject, anotherParentObject);
|
moveAction.addToNewParent(childObject, anotherParentObject);
|
||||||
moveAction.removeFromOldParent(parentObject, childObject);
|
moveAction.removeFromOldParent(parentObject, childObject);
|
||||||
});
|
});
|
||||||
|
@ -79,7 +79,7 @@ describe("the plugin", () => {
|
|||||||
spyOn(compositionAPI, 'get').and.returnValue(mockComposition);
|
spyOn(compositionAPI, 'get').and.returnValue(mockComposition);
|
||||||
spyOn(openmct.objects, 'save').and.returnValue(Promise.resolve(true));
|
spyOn(openmct.objects, 'save').and.returnValue(Promise.resolve(true));
|
||||||
|
|
||||||
newFolderAction.invoke(mockObjectPath);
|
return newFolderAction.invoke(mockObjectPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets user input for folder name', () => {
|
it('gets user input for folder name', () => {
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<SearchResults v-if="search.length"
|
<SearchResults v-if="search.length"
|
||||||
ref="searchResults"
|
ref="searchResults"
|
||||||
:domain-object="internalDomainObject"
|
:domain-object="domainObject"
|
||||||
:results="searchResults"
|
:results="searchResults"
|
||||||
@changeSectionPage="changeSelectedSection"
|
@changeSectionPage="changeSelectedSection"
|
||||||
@updateEntries="updateEntries"
|
@updateEntries="updateEntries"
|
||||||
@ -43,15 +43,18 @@
|
|||||||
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
|
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
|
||||||
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
|
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
|
||||||
:default-page-id="defaultPageId"
|
:default-page-id="defaultPageId"
|
||||||
|
:selected-page-id="selectedPageId"
|
||||||
:default-section-id="defaultSectionId"
|
:default-section-id="defaultSectionId"
|
||||||
:domain-object="internalDomainObject"
|
:selected-section-id="selectedSectionId"
|
||||||
:page-title="internalDomainObject.configuration.pageTitle"
|
:domain-object="domainObject"
|
||||||
:section-title="internalDomainObject.configuration.sectionTitle"
|
:page-title="domainObject.configuration.pageTitle"
|
||||||
|
:section-title="domainObject.configuration.sectionTitle"
|
||||||
:sections="sections"
|
:sections="sections"
|
||||||
:selected-section="selectedSection"
|
|
||||||
:sidebar-covers-entries="sidebarCoversEntries"
|
:sidebar-covers-entries="sidebarCoversEntries"
|
||||||
@pagesChanged="pagesChanged"
|
@pagesChanged="pagesChanged"
|
||||||
|
@selectPage="selectPage"
|
||||||
@sectionsChanged="sectionsChanged"
|
@sectionsChanged="sectionsChanged"
|
||||||
|
@selectSection="selectSection"
|
||||||
@toggleNav="toggleNav"
|
@toggleNav="toggleNav"
|
||||||
/>
|
/>
|
||||||
<div class="c-notebook__page-view">
|
<div class="c-notebook__page-view">
|
||||||
@ -61,10 +64,10 @@
|
|||||||
></button>
|
></button>
|
||||||
<div class="c-notebook__page-view__path c-path">
|
<div class="c-notebook__page-view__path c-path">
|
||||||
<span class="c-notebook__path__section c-path__item">
|
<span class="c-notebook__path__section c-path__item">
|
||||||
{{ getSelectedSection() ? getSelectedSection().name : '' }}
|
{{ selectedSection ? selectedSection.name : '' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="c-notebook__path__page c-path__item">
|
<span class="c-notebook__path__page c-path__item">
|
||||||
{{ getSelectedPage() ? getSelectedPage().name : '' }}
|
{{ selectedPage ? selectedPage.name : '' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-notebook__page-view__controls">
|
<div class="c-notebook__page-view__controls">
|
||||||
@ -115,9 +118,9 @@
|
|||||||
<NotebookEntry v-for="entry in filteredAndSortedEntries"
|
<NotebookEntry v-for="entry in filteredAndSortedEntries"
|
||||||
:key="entry.id"
|
:key="entry.id"
|
||||||
:entry="entry"
|
:entry="entry"
|
||||||
:domain-object="internalDomainObject"
|
:domain-object="domainObject"
|
||||||
:selected-page="getSelectedPage()"
|
:selected-page="selectedPage"
|
||||||
:selected-section="getSelectedSection()"
|
:selected-section="selectedSection"
|
||||||
:read-only="false"
|
:read-only="false"
|
||||||
@deleteEntry="deleteEntry"
|
@deleteEntry="deleteEntry"
|
||||||
@updateEntry="updateEntry"
|
@updateEntry="updateEntry"
|
||||||
@ -135,6 +138,7 @@ import SearchResults from './SearchResults.vue';
|
|||||||
import Sidebar from './Sidebar.vue';
|
import Sidebar from './Sidebar.vue';
|
||||||
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
||||||
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
|
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
|
||||||
|
import { NOTEBOOK_VIEW_TYPE } from '../notebook-constants';
|
||||||
import objectUtils from 'objectUtils';
|
import objectUtils from 'objectUtils';
|
||||||
|
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
@ -151,14 +155,19 @@ export default {
|
|||||||
SearchResults,
|
SearchResults,
|
||||||
Sidebar
|
Sidebar
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'domainObject', 'snapshotContainer'],
|
inject: ['openmct', 'snapshotContainer'],
|
||||||
|
props: {
|
||||||
|
domainObject: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
defaultPageId: getDefaultNotebook() ? getDefaultNotebook().page.id : '',
|
selectedSectionId: this.getDefaultSectionId(),
|
||||||
defaultSectionId: getDefaultNotebook() ? getDefaultNotebook().section.id : '',
|
selectedPageId: this.getDefaultPageId(),
|
||||||
defaultSort: this.domainObject.configuration.defaultSort,
|
defaultSort: this.domainObject.configuration.defaultSort,
|
||||||
focusEntryId: null,
|
focusEntryId: null,
|
||||||
internalDomainObject: this.domainObject,
|
|
||||||
search: '',
|
search: '',
|
||||||
searchResults: [],
|
searchResults: [],
|
||||||
showTime: 0,
|
showTime: 0,
|
||||||
@ -167,9 +176,15 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
defaultPageId() {
|
||||||
|
return this.getDefaultPageId();
|
||||||
|
},
|
||||||
|
defaultSectionId() {
|
||||||
|
return this.getDefaultSectionId();
|
||||||
|
},
|
||||||
filteredAndSortedEntries() {
|
filteredAndSortedEntries() {
|
||||||
const filterTime = Date.now();
|
const filterTime = Date.now();
|
||||||
const pageEntries = getNotebookEntries(this.internalDomainObject, this.selectedSection, this.selectedPage) || [];
|
const pageEntries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage) || [];
|
||||||
|
|
||||||
const hours = parseInt(this.showTime, 10);
|
const hours = parseInt(this.showTime, 10);
|
||||||
const filteredPageEntriesByTime = hours
|
const filteredPageEntriesByTime = hours
|
||||||
@ -184,22 +199,28 @@ export default {
|
|||||||
return this.getPages() || [];
|
return this.getPages() || [];
|
||||||
},
|
},
|
||||||
sections() {
|
sections() {
|
||||||
return this.internalDomainObject.configuration.sections || [];
|
return this.getSections();
|
||||||
},
|
},
|
||||||
selectedPage() {
|
selectedPage() {
|
||||||
const pages = this.getPages();
|
const pages = this.getPages();
|
||||||
if (!pages) {
|
const selectedPage = pages.find(page => page.id === this.selectedPageId);
|
||||||
return null;
|
|
||||||
|
if (selectedPage) {
|
||||||
|
return selectedPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pages.find(page => page.isSelected);
|
if (!selectedPage && !pages.length) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages[0];
|
||||||
},
|
},
|
||||||
selectedSection() {
|
selectedSection() {
|
||||||
if (!this.sections.length) {
|
if (!this.sections.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.sections.find(section => section.isSelected);
|
return this.sections.find(section => section.id === this.selectedSectionId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -209,15 +230,14 @@ export default {
|
|||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
this.getSearchResults = debounce(this.getSearchResults, 500);
|
this.getSearchResults = debounce(this.getSearchResults, 500);
|
||||||
|
this.syncUrlWithPageAndSection = debounce(this.syncUrlWithPageAndSection, 100);
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
|
|
||||||
this.formatSidebar();
|
this.formatSidebar();
|
||||||
|
this.setSectionAndPageFromUrl();
|
||||||
|
|
||||||
window.addEventListener('orientationchange', this.formatSidebar);
|
window.addEventListener('orientationchange', this.formatSidebar);
|
||||||
window.addEventListener("hashchange", this.navigateToSectionPage, false);
|
window.addEventListener('hashchange', this.setSectionAndPageFromUrl);
|
||||||
|
|
||||||
this.navigateToSectionPage();
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.unlisten) {
|
if (this.unlisten) {
|
||||||
@ -225,7 +245,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.removeEventListener('orientationchange', this.formatSidebar);
|
window.removeEventListener('orientationchange', this.formatSidebar);
|
||||||
window.removeEventListener("hashchange", this.navigateToSectionPage);
|
window.removeEventListener('hashchange', this.setSectionAndPageFromUrl);
|
||||||
},
|
},
|
||||||
updated: function () {
|
updated: function () {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@ -233,6 +253,28 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
changeSectionPage(newParams, oldParams, changedParams) {
|
||||||
|
if (newParams.view !== NOTEBOOK_VIEW_TYPE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pageId = newParams.pageId;
|
||||||
|
let sectionId = newParams.sectionId;
|
||||||
|
|
||||||
|
if (!pageId && !sectionId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sections.forEach(section => {
|
||||||
|
section.isSelected = Boolean(section.id === sectionId);
|
||||||
|
|
||||||
|
if (section.isSelected) {
|
||||||
|
section.pages.forEach(page => {
|
||||||
|
page.isSelected = Boolean(page.id === pageId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
changeSelectedSection({ sectionId, pageId }) {
|
changeSelectedSection({ sectionId, pageId }) {
|
||||||
const sections = this.sections.map(s => {
|
const sections = this.sections.map(s => {
|
||||||
s.isSelected = false;
|
s.isSelected = false;
|
||||||
@ -259,14 +301,21 @@ export default {
|
|||||||
this.sectionsChanged({ sections });
|
this.sectionsChanged({ sections });
|
||||||
this.resetSearch();
|
this.resetSearch();
|
||||||
},
|
},
|
||||||
|
setSectionAndPageFromUrl() {
|
||||||
|
let sectionId = this.getSectionIdFromUrl() || this.selectedSectionId;
|
||||||
|
let pageId = this.getPageIdFromUrl() || this.selectedPageId;
|
||||||
|
|
||||||
|
this.selectSection(sectionId);
|
||||||
|
this.selectPage(pageId);
|
||||||
|
},
|
||||||
createNotebookStorageObject() {
|
createNotebookStorageObject() {
|
||||||
const notebookMeta = {
|
const notebookMeta = {
|
||||||
name: this.internalDomainObject.name,
|
name: this.domainObject.name,
|
||||||
identifier: this.internalDomainObject.identifier,
|
identifier: this.domainObject.identifier,
|
||||||
link: this.getLinktoNotebook()
|
link: this.getLinktoNotebook()
|
||||||
};
|
};
|
||||||
const page = this.getSelectedPage();
|
const page = this.selectedPage;
|
||||||
const section = this.getSelectedSection();
|
const section = this.selectedSection;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
notebookMeta,
|
notebookMeta,
|
||||||
@ -275,8 +324,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
deleteEntry(entryId) {
|
deleteEntry(entryId) {
|
||||||
const self = this;
|
const entryPos = getEntryPosById(entryId, this.domainObject, this.selectedSection, this.selectedPage);
|
||||||
const entryPos = getEntryPosById(entryId, this.internalDomainObject, this.selectedSection, this.selectedPage);
|
|
||||||
if (entryPos === -1) {
|
if (entryPos === -1) {
|
||||||
this.openmct.notifications.alert('Warning: unable to delete entry');
|
this.openmct.notifications.alert('Warning: unable to delete entry');
|
||||||
console.error(`unable to delete entry ${entryId} from section ${this.selectedSection}, page ${this.selectedPage}`);
|
console.error(`unable to delete entry ${entryId} from section ${this.selectedSection}, page ${this.selectedPage}`);
|
||||||
@ -292,9 +340,9 @@ export default {
|
|||||||
label: "Ok",
|
label: "Ok",
|
||||||
emphasis: true,
|
emphasis: true,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const entries = getNotebookEntries(self.internalDomainObject, self.selectedSection, self.selectedPage);
|
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
||||||
entries.splice(entryPos, 1);
|
entries.splice(entryPos, 1);
|
||||||
self.updateEntries(entries);
|
this.updateEntries(entries);
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -370,6 +418,37 @@ export default {
|
|||||||
const sidebarCoversEntries = (isPhone || (isTablet && isPortrait) || isInLayout);
|
const sidebarCoversEntries = (isPhone || (isTablet && isPortrait) || isInLayout);
|
||||||
this.sidebarCoversEntries = sidebarCoversEntries;
|
this.sidebarCoversEntries = sidebarCoversEntries;
|
||||||
},
|
},
|
||||||
|
getDefaultPageId() {
|
||||||
|
let defaultPageId;
|
||||||
|
|
||||||
|
if (this.isDefaultNotebook()) {
|
||||||
|
defaultPageId = getDefaultNotebook().page.id;
|
||||||
|
} else {
|
||||||
|
const firstSection = this.getSections()[0];
|
||||||
|
defaultPageId = firstSection && firstSection.pages[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultPageId;
|
||||||
|
},
|
||||||
|
isDefaultNotebook() {
|
||||||
|
const defaultNotebook = getDefaultNotebook();
|
||||||
|
const defaultNotebookIdentifier = defaultNotebook && defaultNotebook.notebookMeta.identifier;
|
||||||
|
|
||||||
|
return defaultNotebookIdentifier !== null
|
||||||
|
&& this.openmct.objects.areIdsEqual(defaultNotebookIdentifier, this.domainObject.identifier);
|
||||||
|
},
|
||||||
|
getDefaultSectionId() {
|
||||||
|
let defaultSectionId;
|
||||||
|
|
||||||
|
if (this.isDefaultNotebook()) {
|
||||||
|
defaultSectionId = getDefaultNotebook().section.id;
|
||||||
|
} else {
|
||||||
|
const firstSection = this.getSections()[0];
|
||||||
|
defaultSectionId = firstSection && firstSection.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultSectionId;
|
||||||
|
},
|
||||||
getDefaultNotebookObject() {
|
getDefaultNotebookObject() {
|
||||||
const oldNotebookStorage = getDefaultNotebook();
|
const oldNotebookStorage = getDefaultNotebook();
|
||||||
if (!oldNotebookStorage) {
|
if (!oldNotebookStorage) {
|
||||||
@ -398,14 +477,17 @@ export default {
|
|||||||
getSection(id) {
|
getSection(id) {
|
||||||
return this.sections.find(s => s.id === id);
|
return this.sections.find(s => s.id === id);
|
||||||
},
|
},
|
||||||
|
getSections() {
|
||||||
|
return this.domainObject.configuration.sections || [];
|
||||||
|
},
|
||||||
getSearchResults() {
|
getSearchResults() {
|
||||||
if (!this.search.length) {
|
if (!this.search.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const output = [];
|
const output = [];
|
||||||
const sections = this.internalDomainObject.configuration.sections;
|
const sections = this.domainObject.configuration.sections;
|
||||||
const entries = this.internalDomainObject.configuration.entries;
|
const entries = this.domainObject.configuration.entries;
|
||||||
const searchTextLower = this.search.toLowerCase();
|
const searchTextLower = this.search.toLowerCase();
|
||||||
const originalSearchText = this.search;
|
const originalSearchText = this.search;
|
||||||
let sectionTrackPageHit;
|
let sectionTrackPageHit;
|
||||||
@ -484,75 +566,25 @@ export default {
|
|||||||
this.searchResults = output;
|
this.searchResults = output;
|
||||||
},
|
},
|
||||||
getPages() {
|
getPages() {
|
||||||
const selectedSection = this.getSelectedSection();
|
const selectedSection = this.selectedSection;
|
||||||
if (!selectedSection || !selectedSection.pages.length) {
|
if (!selectedSection || !selectedSection.pages.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectedSection.pages;
|
return selectedSection.pages;
|
||||||
},
|
},
|
||||||
getSelectedPage() {
|
|
||||||
const pages = this.getPages();
|
|
||||||
if (!pages) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedPage = pages.find(page => page.isSelected);
|
|
||||||
if (selectedPage) {
|
|
||||||
return selectedPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!selectedPage && !pages.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pages[0].isSelected = true;
|
|
||||||
|
|
||||||
return pages[0];
|
|
||||||
},
|
|
||||||
getSelectedSection() {
|
|
||||||
if (!this.sections.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.sections.find(section => section.isSelected);
|
|
||||||
},
|
|
||||||
navigateToSectionPage() {
|
|
||||||
const { pageId, sectionId } = this.openmct.router.getParams();
|
|
||||||
if (!pageId || !sectionId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sections = this.sections.map(s => {
|
|
||||||
s.isSelected = false;
|
|
||||||
if (s.id === sectionId) {
|
|
||||||
s.isSelected = true;
|
|
||||||
s.pages.forEach(p => p.isSelected = (p.id === pageId));
|
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedSectionId = this.selectedSection && this.selectedSection.id;
|
|
||||||
const selectedPageId = this.selectedPage && this.selectedPage.id;
|
|
||||||
if (selectedPageId === pageId && selectedSectionId === sectionId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sectionsChanged({ sections });
|
|
||||||
},
|
|
||||||
newEntry(embed = null) {
|
newEntry(embed = null) {
|
||||||
this.resetSearch();
|
this.resetSearch();
|
||||||
const notebookStorage = this.createNotebookStorageObject();
|
const notebookStorage = this.createNotebookStorageObject();
|
||||||
this.updateDefaultNotebook(notebookStorage);
|
this.updateDefaultNotebook(notebookStorage);
|
||||||
const id = addNotebookEntry(this.openmct, this.internalDomainObject, notebookStorage, embed);
|
const id = addNotebookEntry(this.openmct, this.domainObject, notebookStorage, embed);
|
||||||
this.focusEntryId = id;
|
this.focusEntryId = id;
|
||||||
},
|
},
|
||||||
orientationChange() {
|
orientationChange() {
|
||||||
this.formatSidebar();
|
this.formatSidebar();
|
||||||
},
|
},
|
||||||
pagesChanged({ pages = [], id = null}) {
|
pagesChanged({ pages = [], id = null}) {
|
||||||
const selectedSection = this.getSelectedSection();
|
const selectedSection = this.selectedSection;
|
||||||
if (!selectedSection) {
|
if (!selectedSection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -567,7 +599,6 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.sectionsChanged({ sections });
|
this.sectionsChanged({ sections });
|
||||||
this.updateDefaultNotebookPage(pages, id);
|
|
||||||
},
|
},
|
||||||
removeDefaultClass(domainObject) {
|
removeDefaultClass(domainObject) {
|
||||||
if (!domainObject) {
|
if (!domainObject) {
|
||||||
@ -586,10 +617,10 @@ export default {
|
|||||||
async updateDefaultNotebook(notebookStorage) {
|
async updateDefaultNotebook(notebookStorage) {
|
||||||
const defaultNotebookObject = await this.getDefaultNotebookObject();
|
const defaultNotebookObject = await this.getDefaultNotebookObject();
|
||||||
if (!defaultNotebookObject) {
|
if (!defaultNotebookObject) {
|
||||||
setDefaultNotebook(this.openmct, notebookStorage, this.internalDomainObject);
|
setDefaultNotebook(this.openmct, notebookStorage, this.domainObject);
|
||||||
} else if (objectUtils.makeKeyString(defaultNotebookObject.identifier) !== objectUtils.makeKeyString(notebookStorage.notebookMeta.identifier)) {
|
} else if (objectUtils.makeKeyString(defaultNotebookObject.identifier) !== objectUtils.makeKeyString(notebookStorage.notebookMeta.identifier)) {
|
||||||
this.removeDefaultClass(defaultNotebookObject);
|
this.removeDefaultClass(defaultNotebookObject);
|
||||||
setDefaultNotebook(this.openmct, notebookStorage, this.internalDomainObject);
|
setDefaultNotebook(this.openmct, notebookStorage, this.domainObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.defaultSectionId && this.defaultSectionId.length === 0 || this.defaultSectionId !== notebookStorage.section.id) {
|
if (this.defaultSectionId && this.defaultSectionId.length === 0 || this.defaultSectionId !== notebookStorage.section.id) {
|
||||||
@ -609,7 +640,7 @@ export default {
|
|||||||
|
|
||||||
const notebookStorage = getDefaultNotebook();
|
const notebookStorage = getDefaultNotebook();
|
||||||
if (!notebookStorage
|
if (!notebookStorage
|
||||||
|| notebookStorage.notebookMeta.identifier.key !== this.internalDomainObject.identifier.key) {
|
|| notebookStorage.notebookMeta.identifier.key !== this.domainObject.identifier.key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -618,7 +649,7 @@ export default {
|
|||||||
if (!page && defaultNotebookPage.id === id) {
|
if (!page && defaultNotebookPage.id === id) {
|
||||||
this.defaultSectionId = null;
|
this.defaultSectionId = null;
|
||||||
this.defaultPageId = null;
|
this.defaultPageId = null;
|
||||||
this.removeDefaultClass(this.internalDomainObject);
|
this.removeDefaultClass(this.domainObject);
|
||||||
clearDefaultNotebook();
|
clearDefaultNotebook();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -637,7 +668,7 @@ export default {
|
|||||||
|
|
||||||
const notebookStorage = getDefaultNotebook();
|
const notebookStorage = getDefaultNotebook();
|
||||||
if (!notebookStorage
|
if (!notebookStorage
|
||||||
|| notebookStorage.notebookMeta.identifier.key !== this.internalDomainObject.identifier.key) {
|
|| notebookStorage.notebookMeta.identifier.key !== this.domainObject.identifier.key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -646,7 +677,7 @@ export default {
|
|||||||
if (!section && defaultNotebookSection.id === id) {
|
if (!section && defaultNotebookSection.id === id) {
|
||||||
this.defaultSectionId = null;
|
this.defaultSectionId = null;
|
||||||
this.defaultPageId = null;
|
this.defaultPageId = null;
|
||||||
this.removeDefaultClass(this.internalDomainObject);
|
this.removeDefaultClass(this.domainObject);
|
||||||
clearDefaultNotebook();
|
clearDefaultNotebook();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -659,50 +690,46 @@ export default {
|
|||||||
setDefaultNotebookSection(section);
|
setDefaultNotebookSection(section);
|
||||||
},
|
},
|
||||||
updateEntry(entry) {
|
updateEntry(entry) {
|
||||||
const entries = getNotebookEntries(this.internalDomainObject, this.selectedSection, this.selectedPage);
|
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
||||||
const entryPos = getEntryPosById(entry.id, this.internalDomainObject, this.selectedSection, this.selectedPage);
|
const entryPos = getEntryPosById(entry.id, this.domainObject, this.selectedSection, this.selectedPage);
|
||||||
entries[entryPos] = entry;
|
entries[entryPos] = entry;
|
||||||
|
|
||||||
this.updateEntries(entries);
|
this.updateEntries(entries);
|
||||||
},
|
},
|
||||||
updateEntries(entries) {
|
updateEntries(entries) {
|
||||||
const configuration = this.internalDomainObject.configuration;
|
const configuration = this.domainObject.configuration;
|
||||||
const notebookEntries = configuration.entries || {};
|
const notebookEntries = configuration.entries || {};
|
||||||
notebookEntries[this.selectedSection.id][this.selectedPage.id] = entries;
|
notebookEntries[this.selectedSection.id][this.selectedPage.id] = entries;
|
||||||
|
|
||||||
mutateObject(this.openmct, this.internalDomainObject, 'configuration.entries', notebookEntries);
|
mutateObject(this.openmct, this.domainObject, 'configuration.entries', notebookEntries);
|
||||||
},
|
},
|
||||||
updateInternalDomainObject(domainObject) {
|
getPageIdFromUrl() {
|
||||||
this.internalDomainObject = domainObject;
|
return this.openmct.router.getParams().pageId;
|
||||||
},
|
},
|
||||||
updateParams(sections) {
|
getSectionIdFromUrl() {
|
||||||
const selectedSection = sections.find(s => s.isSelected);
|
return this.openmct.router.getParams().sectionId;
|
||||||
if (!selectedSection) {
|
},
|
||||||
return;
|
syncUrlWithPageAndSection() {
|
||||||
}
|
|
||||||
|
|
||||||
const selectedPage = selectedSection.pages.find(p => p.isSelected);
|
|
||||||
if (!selectedPage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sectionId = selectedSection.id;
|
|
||||||
const pageId = selectedPage.id;
|
|
||||||
|
|
||||||
if (!sectionId || !pageId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.openmct.router.updateParams({
|
this.openmct.router.updateParams({
|
||||||
sectionId,
|
pageId: this.selectedPageId,
|
||||||
pageId
|
sectionId: this.selectedSectionId
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
sectionsChanged({ sections, id = null }) {
|
sectionsChanged({ sections, id = null }) {
|
||||||
mutateObject(this.openmct, this.internalDomainObject, 'configuration.sections', sections);
|
mutateObject(this.openmct, this.domainObject, 'configuration.sections', sections);
|
||||||
|
|
||||||
this.updateParams(sections);
|
|
||||||
this.updateDefaultNotebookSection(sections, id);
|
this.updateDefaultNotebookSection(sections, id);
|
||||||
|
},
|
||||||
|
selectPage(pageId) {
|
||||||
|
this.selectedPageId = pageId;
|
||||||
|
this.syncUrlWithPageAndSection();
|
||||||
|
},
|
||||||
|
selectSection(sectionId) {
|
||||||
|
this.selectedSectionId = sectionId;
|
||||||
|
|
||||||
|
const defaultPageId = this.selectedSection.pages[0].id;
|
||||||
|
this.selectPage(defaultPageId);
|
||||||
|
|
||||||
|
this.syncUrlWithPageAndSection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
class="c-ne__embed__snap-thumb"
|
class="c-ne__embed__snap-thumb"
|
||||||
@click="openSnapshot()"
|
@click="openSnapshot()"
|
||||||
>
|
>
|
||||||
<img :src="embed.snapshot.src">
|
<img :src="thumbnailImage">
|
||||||
</div>
|
</div>
|
||||||
<div class="c-ne__embed__info">
|
<div class="c-ne__embed__info">
|
||||||
<div class="c-ne__embed__name">
|
<div class="c-ne__embed__name">
|
||||||
@ -25,11 +25,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Moment from 'moment';
|
import Moment from 'moment';
|
||||||
import PopupMenu from './PopupMenu.vue';
|
|
||||||
import PreviewAction from '../../../ui/preview/PreviewAction';
|
import PreviewAction from '../../../ui/preview/PreviewAction';
|
||||||
import RemoveDialog from '../utils/removeDialog';
|
import RemoveDialog from '../utils/removeDialog';
|
||||||
import PainterroInstance from '../utils/painterroInstance';
|
import PainterroInstance from '../utils/painterroInstance';
|
||||||
import SnapshotTemplate from './snapshot-template.html';
|
import SnapshotTemplate from './snapshot-template.html';
|
||||||
|
|
||||||
|
import { updateNotebookImageDomainObject } from '../utils/notebook-image';
|
||||||
|
|
||||||
|
import PopupMenu from './PopupMenu.vue';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -59,6 +62,11 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
createdOn() {
|
createdOn() {
|
||||||
return this.formatTime(this.embed.createdOn, 'YYYY-MM-DD HH:mm:ss');
|
return this.formatTime(this.embed.createdOn, 'YYYY-MM-DD HH:mm:ss');
|
||||||
|
},
|
||||||
|
thumbnailImage() {
|
||||||
|
return this.embed.snapshot.thumbnailImage
|
||||||
|
? this.embed.snapshot.thumbnailImage.src
|
||||||
|
: this.embed.snapshot.src;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -85,7 +93,7 @@ export default {
|
|||||||
template: '<div id="snap-annotation"></div>'
|
template: '<div id="snap-annotation"></div>'
|
||||||
}).$mount();
|
}).$mount();
|
||||||
|
|
||||||
const painterroInstance = new PainterroInstance(annotateVue.$el, this.updateSnapshot);
|
const painterroInstance = new PainterroInstance(annotateVue.$el);
|
||||||
const annotateOverlay = this.openmct.overlays.overlay({
|
const annotateOverlay = this.openmct.overlays.overlay({
|
||||||
element: annotateVue.$el,
|
element: annotateVue.$el,
|
||||||
size: 'large',
|
size: 'large',
|
||||||
@ -102,10 +110,12 @@ export default {
|
|||||||
{
|
{
|
||||||
label: 'Save',
|
label: 'Save',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
painterroInstance.save();
|
painterroInstance.save((snapshotObject) => {
|
||||||
annotateOverlay.dismiss();
|
annotateOverlay.dismiss();
|
||||||
this.snapshotOverlay.dismiss();
|
this.snapshotOverlay.dismiss();
|
||||||
this.openSnapshot();
|
this.updateSnapshot(snapshotObject);
|
||||||
|
this.openSnapshotOverlay(snapshotObject.fullSizeImage.src);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -115,7 +125,19 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
painterroInstance.intialize();
|
painterroInstance.intialize();
|
||||||
|
|
||||||
|
const fullSizeImageObjectIdentifier = this.embed.snapshot.fullSizeImageObjectIdentifier;
|
||||||
|
if (!fullSizeImageObjectIdentifier) {
|
||||||
|
// legacy image data stored in embed
|
||||||
painterroInstance.show(this.embed.snapshot.src);
|
painterroInstance.show(this.embed.snapshot.src);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openmct.objects.get(fullSizeImageObjectIdentifier)
|
||||||
|
.then(object => {
|
||||||
|
painterroInstance.show(object.configuration.fullSizeImageURL);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
changeLocation() {
|
changeLocation() {
|
||||||
const hash = this.embed.historicLink;
|
const hash = this.embed.historicLink;
|
||||||
@ -145,7 +167,7 @@ export default {
|
|||||||
|
|
||||||
const relativeHash = hash.slice(hash.indexOf('#'));
|
const relativeHash = hash.slice(hash.indexOf('#'));
|
||||||
const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`);
|
const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`);
|
||||||
window.location.hash = url.hash;
|
this.openmct.router.navigate(url.hash);
|
||||||
},
|
},
|
||||||
formatTime(unixTime, timeFormat) {
|
formatTime(unixTime, timeFormat) {
|
||||||
return Moment.utc(unixTime).format(timeFormat);
|
return Moment.utc(unixTime).format(timeFormat);
|
||||||
@ -159,12 +181,29 @@ export default {
|
|||||||
removeDialog.show();
|
removeDialog.show();
|
||||||
},
|
},
|
||||||
openSnapshot() {
|
openSnapshot() {
|
||||||
|
const fullSizeImageObjectIdentifier = this.embed.snapshot.fullSizeImageObjectIdentifier;
|
||||||
|
if (!fullSizeImageObjectIdentifier) {
|
||||||
|
// legacy image data stored in embed
|
||||||
|
this.openSnapshotOverlay(this.embed.snapshot.src);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openmct.objects.get(fullSizeImageObjectIdentifier)
|
||||||
|
.then(object => {
|
||||||
|
this.openSnapshotOverlay(object.configuration.fullSizeImageURL);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
openSnapshotOverlay(src) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
this.snapshot = new Vue({
|
this.snapshot = new Vue({
|
||||||
data: () => {
|
data: () => {
|
||||||
return {
|
return {
|
||||||
createdOn: this.createdOn,
|
createdOn: this.createdOn,
|
||||||
embed: this.embed
|
name: this.embed.name,
|
||||||
|
cssClass: this.embed.cssClass,
|
||||||
|
src
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -217,7 +256,9 @@ export default {
|
|||||||
this.$emit('updateEmbed', embed);
|
this.$emit('updateEmbed', embed);
|
||||||
},
|
},
|
||||||
updateSnapshot(snapshotObject) {
|
updateSnapshot(snapshotObject) {
|
||||||
this.embed.snapshot = snapshotObject;
|
this.embed.snapshot.thumbnailImage = snapshotObject.thumbnailImage;
|
||||||
|
|
||||||
|
updateNotebookImageDomainObject(this.openmct, this.embed.snapshot.fullSizeImageObjectIdentifier, snapshotObject.fullSizeImage);
|
||||||
this.updateEmbed(this.embed);
|
this.updateEmbed(this.embed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,6 @@
|
|||||||
<NotebookEmbed v-for="embed in entry.embeds"
|
<NotebookEmbed v-for="embed in entry.embeds"
|
||||||
:key="embed.id"
|
:key="embed.id"
|
||||||
:embed="embed"
|
:embed="embed"
|
||||||
:entry="entry"
|
|
||||||
@removeEmbed="removeEmbed"
|
@removeEmbed="removeEmbed"
|
||||||
@updateEmbed="updateEmbed"
|
@updateEmbed="updateEmbed"
|
||||||
/>
|
/>
|
||||||
@ -254,6 +253,7 @@ export default {
|
|||||||
},
|
},
|
||||||
removeEmbed(id) {
|
removeEmbed(id) {
|
||||||
const embedPosition = this.findPositionInArray(this.entry.embeds, id);
|
const embedPosition = this.findPositionInArray(this.entry.embeds, id);
|
||||||
|
// TODO: remove notebook snapshot object using object remove API
|
||||||
this.entry.embeds.splice(embedPosition, 1);
|
this.entry.embeds.splice(embedPosition, 1);
|
||||||
|
|
||||||
this.$emit('updateEntry', this.entry);
|
this.$emit('updateEntry', this.entry);
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
>
|
>
|
||||||
<Page ref="pageComponent"
|
<Page ref="pageComponent"
|
||||||
:default-page-id="defaultPageId"
|
:default-page-id="defaultPageId"
|
||||||
|
:selected-page-id="selectedPageId"
|
||||||
:page="page"
|
:page="page"
|
||||||
:page-title="pageTitle"
|
:page-title="pageTitle"
|
||||||
@deletePage="deletePage"
|
@deletePage="deletePage"
|
||||||
@ -33,11 +34,13 @@ export default {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
selectedPageId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default() {
|
required: true
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@ -66,7 +69,17 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
pages() {
|
||||||
|
if (!this.containsPage(this.selectedPageId)) {
|
||||||
|
this.selectPage(this.pages[0].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
containsPage(pageId) {
|
||||||
|
return this.pages.some(page => page.id === pageId);
|
||||||
|
},
|
||||||
deletePage(id) {
|
deletePage(id) {
|
||||||
const selectedSection = this.sections.find(s => s.isSelected);
|
const selectedSection = this.sections.find(s => s.isSelected);
|
||||||
const page = this.pages.find(p => p.id === id);
|
const page = this.pages.find(p => p.id === id);
|
||||||
@ -78,37 +91,29 @@ export default {
|
|||||||
const isPageSelected = selectedPage && selectedPage.id === id;
|
const isPageSelected = selectedPage && selectedPage.id === id;
|
||||||
const isPageDefault = defaultpage && defaultpage.id === id;
|
const isPageDefault = defaultpage && defaultpage.id === id;
|
||||||
const pages = this.pages.filter(s => s.id !== id);
|
const pages = this.pages.filter(s => s.id !== id);
|
||||||
|
let selectedPageId;
|
||||||
|
|
||||||
if (isPageSelected && defaultpage) {
|
if (isPageSelected && defaultpage) {
|
||||||
pages.forEach(s => {
|
pages.forEach(s => {
|
||||||
s.isSelected = false;
|
s.isSelected = false;
|
||||||
if (defaultpage && defaultpage.id === s.id) {
|
if (defaultpage && defaultpage.id === s.id) {
|
||||||
s.isSelected = true;
|
selectedPageId = s.id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pages.length && isPageSelected && (!defaultpage || isPageDefault)) {
|
if (pages.length && isPageSelected && (!defaultpage || isPageDefault)) {
|
||||||
pages[0].isSelected = true;
|
selectedPageId = pages[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('updatePage', {
|
this.$emit('updatePage', {
|
||||||
pages,
|
pages,
|
||||||
id
|
id
|
||||||
});
|
});
|
||||||
|
this.$emit('selectPage', selectedPageId);
|
||||||
},
|
},
|
||||||
selectPage(id) {
|
selectPage(id) {
|
||||||
const pages = this.pages.map(page => {
|
this.$emit('selectPage', id);
|
||||||
const isSelected = page.id === id;
|
|
||||||
page.isSelected = isSelected;
|
|
||||||
|
|
||||||
return page;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$emit('updatePage', {
|
|
||||||
pages,
|
|
||||||
id
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add test here for whether or not to toggle the nav
|
// Add test here for whether or not to toggle the nav
|
||||||
if (this.sidebarCoversEntries) {
|
if (this.sidebarCoversEntries) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-list__item js-list__item"
|
<div class="c-list__item js-list__item"
|
||||||
:class="[{ 'is-selected': page.isSelected, 'is-notebook-default' : (defaultPageId === page.id) }]"
|
:class="[{ 'is-selected': isSelected, 'is-notebook-default' : (defaultPageId === page.id) }]"
|
||||||
:data-id="page.id"
|
:data-id="page.id"
|
||||||
@click="selectPage"
|
@click="selectPage"
|
||||||
>
|
>
|
||||||
@ -29,6 +29,10 @@ export default {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
selectedPageId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
page: {
|
page: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
@ -46,6 +50,11 @@ export default {
|
|||||||
removeActionString: `Delete ${this.pageTitle}`
|
removeActionString: `Delete ${this.pageTitle}`
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
isSelected() {
|
||||||
|
return this.selectedPageId === this.page.id;
|
||||||
|
}
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
page(newPage) {
|
page(newPage) {
|
||||||
this.toggleContentEditable(newPage);
|
this.toggleContentEditable(newPage);
|
||||||
@ -73,7 +82,7 @@ export default {
|
|||||||
this.$emit('deletePage', this.page.id);
|
this.$emit('deletePage', this.page.id);
|
||||||
},
|
},
|
||||||
getRemoveDialog() {
|
getRemoveDialog() {
|
||||||
const message = 'This action will delete this page and all of its entries. Do you want to continue?';
|
const message = 'Other users may be editing entries in this page, and deleting it is permanent. Do you want to continue?';
|
||||||
const options = {
|
const options = {
|
||||||
name: this.removeActionString,
|
name: this.removeActionString,
|
||||||
callback: this.deletePage.bind(this),
|
callback: this.deletePage.bind(this),
|
||||||
|
@ -4,8 +4,9 @@
|
|||||||
:key="section.id"
|
:key="section.id"
|
||||||
class="c-list__item-h"
|
class="c-list__item-h"
|
||||||
>
|
>
|
||||||
<sectionComponent ref="sectionComponent"
|
<NotebookSection ref="sectionComponent"
|
||||||
:default-section-id="defaultSectionId"
|
:default-section-id="defaultSectionId"
|
||||||
|
:selected-section-id="selectedSectionId"
|
||||||
:section="section"
|
:section="section"
|
||||||
:section-title="sectionTitle"
|
:section-title="sectionTitle"
|
||||||
@deleteSection="deleteSection"
|
@deleteSection="deleteSection"
|
||||||
@ -19,11 +20,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import { deleteNotebookEntries } from '../utils/notebook-entries';
|
import { deleteNotebookEntries } from '../utils/notebook-entries';
|
||||||
import { getDefaultNotebook } from '../utils/notebook-storage';
|
import { getDefaultNotebook } from '../utils/notebook-storage';
|
||||||
import sectionComponent from './SectionComponent.vue';
|
import SectionComponent from './SectionComponent.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
sectionComponent
|
NotebookSection: SectionComponent
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
@ -33,6 +34,10 @@ export default {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
selectedSectionId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default() {
|
default() {
|
||||||
@ -53,12 +58,22 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
sections() {
|
||||||
|
if (!this.containsSection(this.selectedSectionId)) {
|
||||||
|
this.selectSection(this.sections[0].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
containsSection(sectionId) {
|
||||||
|
return this.sections.some(section => section.id === sectionId);
|
||||||
|
},
|
||||||
deleteSection(id) {
|
deleteSection(id) {
|
||||||
const section = this.sections.find(s => s.id === id);
|
const section = this.sections.find(s => s.id === id);
|
||||||
deleteNotebookEntries(this.openmct, this.domainObject, section);
|
deleteNotebookEntries(this.openmct, this.domainObject, section);
|
||||||
|
|
||||||
const selectedSection = this.sections.find(s => s.isSelected);
|
const selectedSection = this.sections.find(s => s.id === this.selectedSectionId);
|
||||||
const defaultNotebook = getDefaultNotebook();
|
const defaultNotebook = getDefaultNotebook();
|
||||||
const defaultSection = defaultNotebook && defaultNotebook.section;
|
const defaultSection = defaultNotebook && defaultNotebook.section;
|
||||||
const isSectionSelected = selectedSection && selectedSection.id === id;
|
const isSectionSelected = selectedSection && selectedSection.id === id;
|
||||||
@ -83,18 +98,8 @@ export default {
|
|||||||
id
|
id
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
selectSection(id, newSections) {
|
selectSection(id) {
|
||||||
const currentSections = newSections || this.sections;
|
this.$emit('selectSection', id);
|
||||||
const sections = currentSections.map(section => {
|
|
||||||
const isSelected = section.id === id;
|
|
||||||
section.isSelected = isSelected;
|
|
||||||
|
|
||||||
return section;
|
|
||||||
});
|
|
||||||
this.$emit('updateSection', {
|
|
||||||
sections,
|
|
||||||
id
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
updateSection(newSection) {
|
updateSection(newSection) {
|
||||||
const id = newSection.id;
|
const id = newSection.id;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-list__item js-list__item"
|
<div class="c-list__item js-list__item"
|
||||||
:class="[{ 'is-selected': section.isSelected, 'is-notebook-default' : (defaultSectionId === section.id) }]"
|
:class="[{ 'is-selected': isSelected, 'is-notebook-default' : (defaultSectionId === section.id) }]"
|
||||||
:data-id="section.id"
|
:data-id="section.id"
|
||||||
@click="selectSection"
|
@click="selectSection"
|
||||||
>
|
>
|
||||||
@ -13,9 +13,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PopupMenu from './PopupMenu.vue';
|
import PopupMenu from './PopupMenu.vue';
|
||||||
import RemoveDialog from '../utils/removeDialog';
|
import RemoveDialog from '../utils/removeDialog';
|
||||||
@ -32,6 +29,10 @@ export default {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
selectedSectionId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
section: {
|
section: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
@ -49,6 +50,11 @@ export default {
|
|||||||
removeActionString: `Delete ${this.sectionTitle}`
|
removeActionString: `Delete ${this.sectionTitle}`
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
isSelected() {
|
||||||
|
return this.selectedSectionId === this.section.id;
|
||||||
|
}
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
section(newSection) {
|
section(newSection) {
|
||||||
this.toggleContentEditable(newSection);
|
this.toggleContentEditable(newSection);
|
||||||
@ -76,7 +82,7 @@ export default {
|
|||||||
this.$emit('deleteSection', this.section.id);
|
this.$emit('deleteSection', this.section.id);
|
||||||
},
|
},
|
||||||
getRemoveDialog() {
|
getRemoveDialog() {
|
||||||
const message = 'This action will delete this section and all of its pages and entries. Do you want to continue?';
|
const message = 'Other users may be editing entries in this section, and deleting it is permanent. Do you want to continue?';
|
||||||
const options = {
|
const options = {
|
||||||
name: this.removeActionString,
|
name: this.removeActionString,
|
||||||
callback: this.deleteSection.bind(this),
|
callback: this.deleteSection.bind(this),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-sidebar c-drawer c-drawer--align-left">
|
<div class="c-sidebar c-drawer c-drawer--align-left">
|
||||||
<div class="c-sidebar__pane">
|
<div class="c-sidebar__pane js-sidebar-sections">
|
||||||
<div class="c-sidebar__header-w">
|
<div class="c-sidebar__header-w">
|
||||||
<div class="c-sidebar__header">
|
<div class="c-sidebar__header">
|
||||||
<span class="c-sidebar__header-label">{{ sectionTitle }}</span>
|
<span class="c-sidebar__header-label">{{ sectionTitle }}</span>
|
||||||
@ -15,14 +15,16 @@
|
|||||||
</button>
|
</button>
|
||||||
<SectionCollection class="c-sidebar__contents"
|
<SectionCollection class="c-sidebar__contents"
|
||||||
:default-section-id="defaultSectionId"
|
:default-section-id="defaultSectionId"
|
||||||
|
:selected-section-id="selectedSectionId"
|
||||||
:domain-object="domainObject"
|
:domain-object="domainObject"
|
||||||
:sections="sections"
|
:sections="sections"
|
||||||
:section-title="sectionTitle"
|
:section-title="sectionTitle"
|
||||||
@updateSection="sectionsChanged"
|
@updateSection="sectionsChanged"
|
||||||
|
@selectSection="selectSection"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-sidebar__pane">
|
<div class="c-sidebar__pane js-sidebar-pages">
|
||||||
<div class="c-sidebar__header-w">
|
<div class="c-sidebar__header-w">
|
||||||
<div class="c-sidebar__header">
|
<div class="c-sidebar__header">
|
||||||
<span class="c-sidebar__header-label">{{ pageTitle }}</span>
|
<span class="c-sidebar__header-label">{{ pageTitle }}</span>
|
||||||
@ -42,6 +44,7 @@
|
|||||||
<PageCollection ref="pageCollection"
|
<PageCollection ref="pageCollection"
|
||||||
class="c-sidebar__contents"
|
class="c-sidebar__contents"
|
||||||
:default-page-id="defaultPageId"
|
:default-page-id="defaultPageId"
|
||||||
|
:selected-page-id="selectedPageId"
|
||||||
:domain-object="domainObject"
|
:domain-object="domainObject"
|
||||||
:pages="pages"
|
:pages="pages"
|
||||||
:sections="sections"
|
:sections="sections"
|
||||||
@ -49,6 +52,7 @@
|
|||||||
:page-title="pageTitle"
|
:page-title="pageTitle"
|
||||||
@toggleNav="toggleNav"
|
@toggleNav="toggleNav"
|
||||||
@updatePage="pagesChanged"
|
@updatePage="pagesChanged"
|
||||||
|
@selectPage="selectPage"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -73,12 +77,24 @@ export default {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
selectedPageId: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
defaultSectionId: {
|
defaultSectionId: {
|
||||||
type: String,
|
type: String,
|
||||||
default() {
|
default() {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
selectedSectionId: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default() {
|
default() {
|
||||||
@ -111,13 +127,9 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
pages() {
|
pages() {
|
||||||
const selectedSection = this.sections.find(section => section.isSelected);
|
const selectedSection = this.sections.find(section => section.id === this.selectedSectionId);
|
||||||
|
|
||||||
return selectedSection && selectedSection.pages || [];
|
return selectedSection && selectedSection.pages || [];
|
||||||
}
|
}
|
||||||
@ -148,6 +160,7 @@ export default {
|
|||||||
pages,
|
pages,
|
||||||
id: newPage.id
|
id: newPage.id
|
||||||
});
|
});
|
||||||
|
this.$emit('selectPage', newPage.id);
|
||||||
},
|
},
|
||||||
addSection() {
|
addSection() {
|
||||||
const newSection = this.createNewSection();
|
const newSection = this.createNewSection();
|
||||||
@ -157,6 +170,8 @@ export default {
|
|||||||
sections,
|
sections,
|
||||||
id: newSection.id
|
id: newSection.id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$emit('selectSection', newSection.id);
|
||||||
},
|
},
|
||||||
addNewPage(page) {
|
addNewPage(page) {
|
||||||
const pages = this.pages.map(p => {
|
const pages = this.pages.map(p => {
|
||||||
@ -212,6 +227,12 @@ export default {
|
|||||||
id
|
id
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
selectPage(pageId) {
|
||||||
|
this.$emit('selectPage', pageId);
|
||||||
|
},
|
||||||
|
selectSection(sectionId) {
|
||||||
|
this.$emit('selectSection', sectionId);
|
||||||
|
},
|
||||||
sectionsChanged({ sections, id }) {
|
sectionsChanged({ sections, id }) {
|
||||||
this.$emit('sectionsChanged', {
|
this.$emit('sectionsChanged', {
|
||||||
sections,
|
sections,
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
<div class="l-browse-bar__start">
|
<div class="l-browse-bar__start">
|
||||||
<div class="l-browse-bar__object-name--w">
|
<div class="l-browse-bar__object-name--w">
|
||||||
<span class="c-object-label l-browse-bar__object-name"
|
<span class="c-object-label l-browse-bar__object-name"
|
||||||
v-bind:class="embed.cssClass"
|
v-bind:class="cssClass"
|
||||||
>
|
>
|
||||||
<span class="c-object-label__name">{{ embed.name }}</span>
|
<span class="c-object-label__name">{{ name }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -40,7 +40,7 @@
|
|||||||
<div
|
<div
|
||||||
ref="snapshot-image"
|
ref="snapshot-image"
|
||||||
class="c-notebook-snapshot__image"
|
class="c-notebook-snapshot__image"
|
||||||
:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }"
|
:style="{ backgroundImage: 'url(' + src + ')' }"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
export const NOTEBOOK_TYPE = 'notebook';
|
||||||
export const EVENT_SNAPSHOTS_UPDATED = 'SNAPSHOTS_UPDATED';
|
export const EVENT_SNAPSHOTS_UPDATED = 'SNAPSHOTS_UPDATED';
|
||||||
export const NOTEBOOK_DEFAULT = 'DEFAULT';
|
export const NOTEBOOK_DEFAULT = 'DEFAULT';
|
||||||
export const NOTEBOOK_SNAPSHOT = 'SNAPSHOT';
|
export const NOTEBOOK_SNAPSHOT = 'SNAPSHOT';
|
||||||
|
export const NOTEBOOK_VIEW_TYPE = 'notebook-vue';
|
||||||
|
@ -2,18 +2,20 @@ import CopyToNotebookAction from './actions/CopyToNotebookAction';
|
|||||||
import Notebook from './components/Notebook.vue';
|
import Notebook from './components/Notebook.vue';
|
||||||
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
|
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
|
||||||
import SnapshotContainer from './snapshot-container';
|
import SnapshotContainer from './snapshot-container';
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
let installed = false;
|
import { notebookImageMigration } from '../notebook/utils/notebook-migration';
|
||||||
|
import { NOTEBOOK_TYPE } from './notebook-constants';
|
||||||
|
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
export default function NotebookPlugin() {
|
export default function NotebookPlugin() {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
if (installed) {
|
if (openmct._NOTEBOOK_PLUGIN_INSTALLED) {
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
openmct._NOTEBOOK_PLUGIN_INSTALLED = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
installed = true;
|
|
||||||
|
|
||||||
openmct.actions.register(new CopyToNotebookAction(openmct));
|
openmct.actions.register(new CopyToNotebookAction(openmct));
|
||||||
|
|
||||||
const notebookType = {
|
const notebookType = {
|
||||||
@ -84,7 +86,20 @@ export default function NotebookPlugin() {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
openmct.types.addType('notebook', notebookType);
|
openmct.types.addType(NOTEBOOK_TYPE, notebookType);
|
||||||
|
|
||||||
|
const notebookSnapshotImageType = {
|
||||||
|
name: 'Notebook Snapshot Image Storage',
|
||||||
|
description: 'Notebook Snapshot Image Storage object',
|
||||||
|
creatable: false,
|
||||||
|
initialize: domainObject => {
|
||||||
|
domainObject.configuration = {
|
||||||
|
fullSizeImageURL: undefined,
|
||||||
|
thumbnailImageURL: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
openmct.types.addType('notebookSnapshotImage', notebookSnapshotImageType);
|
||||||
|
|
||||||
const snapshotContainer = new SnapshotContainer(openmct);
|
const snapshotContainer = new SnapshotContainer(openmct);
|
||||||
const notebookSnapshotIndicator = new Vue ({
|
const notebookSnapshotIndicator = new Vue ({
|
||||||
@ -123,10 +138,14 @@ export default function NotebookPlugin() {
|
|||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
domainObject,
|
|
||||||
snapshotContainer
|
snapshotContainer
|
||||||
},
|
},
|
||||||
template: '<Notebook></Notebook>'
|
data() {
|
||||||
|
return {
|
||||||
|
domainObject
|
||||||
|
};
|
||||||
|
},
|
||||||
|
template: '<Notebook :domain-object="domainObject"></Notebook>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
destroy() {
|
destroy() {
|
||||||
@ -135,5 +154,16 @@ export default function NotebookPlugin() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
openmct.objects.addGetInterceptor({
|
||||||
|
appliesTo: (identifier, domainObject) => {
|
||||||
|
return domainObject && domainObject.type === 'notebook';
|
||||||
|
},
|
||||||
|
invoke: (identifier, domainObject) => {
|
||||||
|
notebookImageMigration(openmct, domainObject);
|
||||||
|
|
||||||
|
return domainObject;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -21,29 +21,32 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import { createOpenMct, createMouseEvent, resetApplicationState } from 'utils/testing';
|
import { createOpenMct, createMouseEvent, resetApplicationState } from 'utils/testing';
|
||||||
import NotebookPlugin from './plugin';
|
import notebookPlugin from './plugin';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
describe("Notebook plugin:", () => {
|
||||||
let openmct;
|
let openmct;
|
||||||
let notebookDefinition;
|
let notebookDefinition;
|
||||||
let notebookPlugin;
|
|
||||||
let element;
|
let element;
|
||||||
let child;
|
let child;
|
||||||
let appHolder;
|
let appHolder;
|
||||||
|
let objectProviderObserver;
|
||||||
|
|
||||||
const notebookDomainObject = {
|
let notebookDomainObject;
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
notebookDomainObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
key: 'notebook',
|
key: 'notebook',
|
||||||
namespace: ''
|
namespace: 'test-namespace'
|
||||||
},
|
},
|
||||||
type: 'notebook'
|
type: 'notebook'
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("Notebook plugin:", () => {
|
|
||||||
beforeAll(done => {
|
|
||||||
appHolder = document.createElement('div');
|
appHolder = document.createElement('div');
|
||||||
appHolder.style.width = '640px';
|
appHolder.style.width = '640px';
|
||||||
appHolder.style.height = '480px';
|
appHolder.style.height = '480px';
|
||||||
|
document.body.appendChild(appHolder);
|
||||||
|
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
|
|
||||||
@ -51,21 +54,19 @@ describe("Notebook plugin:", () => {
|
|||||||
child = document.createElement('div');
|
child = document.createElement('div');
|
||||||
element.appendChild(child);
|
element.appendChild(child);
|
||||||
|
|
||||||
notebookPlugin = new NotebookPlugin();
|
openmct.install(notebookPlugin());
|
||||||
openmct.install(notebookPlugin);
|
|
||||||
|
|
||||||
notebookDefinition = openmct.types.get('notebook').definition;
|
notebookDefinition = openmct.types.get('notebook').definition;
|
||||||
notebookDefinition.initialize(notebookDomainObject);
|
notebookDefinition.initialize(notebookDomainObject);
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.start(appHolder);
|
openmct.start(appHolder);
|
||||||
|
|
||||||
document.body.append(appHolder);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterEach(() => {
|
||||||
appHolder.remove();
|
appHolder.remove();
|
||||||
resetApplicationState(openmct);
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("has type as Notebook", () => {
|
it("has type as Notebook", () => {
|
||||||
@ -79,39 +80,96 @@ describe("Notebook plugin:", () => {
|
|||||||
describe("Notebook view:", () => {
|
describe("Notebook view:", () => {
|
||||||
let notebookViewProvider;
|
let notebookViewProvider;
|
||||||
let notebookView;
|
let notebookView;
|
||||||
|
let notebookViewObject;
|
||||||
|
let mutableNotebookObject;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const notebookViewObject = {
|
notebookViewObject = {
|
||||||
...notebookDomainObject,
|
...notebookDomainObject,
|
||||||
id: "test-object",
|
id: "test-object",
|
||||||
name: 'Notebook',
|
name: 'Notebook',
|
||||||
configuration: {
|
configuration: {
|
||||||
defaultSort: 'oldest',
|
defaultSort: 'oldest',
|
||||||
entries: {},
|
entries: {
|
||||||
|
"test-section-1": {
|
||||||
|
"test-page-1": [{
|
||||||
|
"id": "entry-0",
|
||||||
|
"createdOn": 0,
|
||||||
|
"text": "First Test Entry",
|
||||||
|
"embeds": []
|
||||||
|
}, {
|
||||||
|
"id": "entry-1",
|
||||||
|
"createdOn": 0,
|
||||||
|
"text": "Second Test Entry",
|
||||||
|
"embeds": []
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
pageTitle: 'Page',
|
pageTitle: 'Page',
|
||||||
sections: [],
|
sections: [{
|
||||||
|
"id": "test-section-1",
|
||||||
|
"isDefault": false,
|
||||||
|
"isSelected": false,
|
||||||
|
"name": "Test Section",
|
||||||
|
"pages": [{
|
||||||
|
"id": "test-page-1",
|
||||||
|
"isDefault": false,
|
||||||
|
"isSelected": false,
|
||||||
|
"name": "Test Page 1",
|
||||||
|
"pageTitle": "Page"
|
||||||
|
}, {
|
||||||
|
"id": "test-page-2",
|
||||||
|
"isDefault": false,
|
||||||
|
"isSelected": false,
|
||||||
|
"name": "Test Page 2",
|
||||||
|
"pageTitle": "Page"
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"id": "test-section-2",
|
||||||
|
"isDefault": false,
|
||||||
|
"isSelected": false,
|
||||||
|
"name": "Test Section 2",
|
||||||
|
"pages": [{
|
||||||
|
"id": "test-page-3",
|
||||||
|
"isDefault": false,
|
||||||
|
"isSelected": false,
|
||||||
|
"name": "Test Page 3",
|
||||||
|
"pageTitle": "Page"
|
||||||
|
}]
|
||||||
|
}],
|
||||||
sectionTitle: 'Section',
|
sectionTitle: 'Section',
|
||||||
type: 'General'
|
type: 'General'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const testObjectProvider = jasmine.createSpyObj('testObjectProvider', [
|
||||||
|
'get',
|
||||||
|
'create',
|
||||||
|
'update',
|
||||||
|
'observe'
|
||||||
|
]);
|
||||||
|
|
||||||
const notebookObject = {
|
const applicableViews = openmct.objectViews.get(notebookViewObject, [notebookViewObject]);
|
||||||
name: 'Notebook View',
|
notebookViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'notebook-vue');
|
||||||
key: 'notebook-vue',
|
|
||||||
creatable: true
|
|
||||||
};
|
|
||||||
|
|
||||||
const applicableViews = openmct.objectViews.get(notebookViewObject, []);
|
testObjectProvider.get.and.returnValue(Promise.resolve(notebookViewObject));
|
||||||
notebookViewProvider = applicableViews.find(viewProvider => viewProvider.key === notebookObject.key);
|
openmct.objects.addProvider('test-namespace', testObjectProvider);
|
||||||
notebookView = notebookViewProvider.view(notebookViewObject);
|
testObjectProvider.observe.and.returnValue(() => {});
|
||||||
|
|
||||||
|
return openmct.objects.getMutable(notebookViewObject.identifier).then((mutableObject) => {
|
||||||
|
mutableNotebookObject = mutableObject;
|
||||||
|
objectProviderObserver = testObjectProvider.observe.calls.mostRecent().args[1];
|
||||||
|
|
||||||
|
notebookView = notebookViewProvider.view(mutableNotebookObject);
|
||||||
notebookView.show(child);
|
notebookView.show(child);
|
||||||
|
|
||||||
return Vue.nextTick();
|
return Vue.nextTick();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
notebookView.destroy();
|
notebookView.destroy();
|
||||||
|
openmct.objects.destroyMutable(mutableNotebookObject);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("provides notebook view", () => {
|
it("provides notebook view", () => {
|
||||||
@ -132,6 +190,114 @@ describe("Notebook plugin:", () => {
|
|||||||
|
|
||||||
expect(hasMajorElements).toBe(true);
|
expect(hasMajorElements).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders a row for each entry", () => {
|
||||||
|
const notebookEntryElements = element.querySelectorAll('.c-notebook__entry');
|
||||||
|
const firstEntryText = getEntryText(0);
|
||||||
|
expect(notebookEntryElements.length).toBe(2);
|
||||||
|
expect(firstEntryText.innerText).toBe('First Test Entry');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("synchronization", () => {
|
||||||
|
|
||||||
|
it("updates an entry when another user modifies it", () => {
|
||||||
|
expect(getEntryText(0).innerText).toBe("First Test Entry");
|
||||||
|
notebookViewObject.configuration.entries["test-section-1"]["test-page-1"][0].text = "Modified entry text";
|
||||||
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
|
return Vue.nextTick().then(() => {
|
||||||
|
expect(getEntryText(0).innerText).toBe("Modified entry text");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows new entry when another user adds one", () => {
|
||||||
|
expect(allNotebookEntryElements().length).toBe(2);
|
||||||
|
notebookViewObject.configuration.entries["test-section-1"]["test-page-1"].push({
|
||||||
|
"id": "entry-3",
|
||||||
|
"createdOn": 0,
|
||||||
|
"text": "Third Test Entry",
|
||||||
|
"embeds": []
|
||||||
|
});
|
||||||
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
|
return Vue.nextTick().then(() => {
|
||||||
|
expect(allNotebookEntryElements().length).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("removes an entry when another user removes one", () => {
|
||||||
|
expect(allNotebookEntryElements().length).toBe(2);
|
||||||
|
let entries = notebookViewObject.configuration.entries["test-section-1"]["test-page-1"];
|
||||||
|
notebookViewObject.configuration.entries["test-section-1"]["test-page-1"] = entries.splice(0, 1);
|
||||||
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
|
return Vue.nextTick().then(() => {
|
||||||
|
expect(allNotebookEntryElements().length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates the notebook when a user adds a page", () => {
|
||||||
|
const newPage = {
|
||||||
|
"id": "test-page-4",
|
||||||
|
"isDefault": false,
|
||||||
|
"isSelected": false,
|
||||||
|
"name": "Test Page 4",
|
||||||
|
"pageTitle": "Page"
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(allNotebookPageElements().length).toBe(2);
|
||||||
|
notebookViewObject.configuration.sections[0].pages.push(newPage);
|
||||||
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
|
return Vue.nextTick().then(() => {
|
||||||
|
expect(allNotebookPageElements().length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates the notebook when a user removes a page", () => {
|
||||||
|
expect(allNotebookPageElements().length).toBe(2);
|
||||||
|
notebookViewObject.configuration.sections[0].pages.splice(0, 1);
|
||||||
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
|
return Vue.nextTick().then(() => {
|
||||||
|
expect(allNotebookPageElements().length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates the notebook when a user adds a section", () => {
|
||||||
|
const newSection = {
|
||||||
|
"id": "test-section-3",
|
||||||
|
"isDefault": false,
|
||||||
|
"isSelected": false,
|
||||||
|
"name": "Test Section 3",
|
||||||
|
"pages": [{
|
||||||
|
"id": "test-page-4",
|
||||||
|
"isDefault": false,
|
||||||
|
"isSelected": false,
|
||||||
|
"name": "Test Page 4",
|
||||||
|
"pageTitle": "Page"
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(allNotebookSectionElements().length).toBe(2);
|
||||||
|
notebookViewObject.configuration.sections.push(newSection);
|
||||||
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
|
return Vue.nextTick().then(() => {
|
||||||
|
expect(allNotebookSectionElements().length).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates the notebook when a user removes a section", () => {
|
||||||
|
expect(allNotebookSectionElements().length).toBe(2);
|
||||||
|
notebookViewObject.configuration.sections.splice(0, 1);
|
||||||
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
|
return Vue.nextTick().then(() => {
|
||||||
|
expect(allNotebookSectionElements().length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Notebook Snapshots view:", () => {
|
describe("Notebook Snapshots view:", () => {
|
||||||
@ -146,16 +312,22 @@ describe("Notebook plugin:", () => {
|
|||||||
button.dispatchEvent(clickEvent);
|
button.dispatchEvent(clickEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeEach(() => {
|
||||||
snapshotIndicator = openmct.indicators.indicatorObjects
|
snapshotIndicator = openmct.indicators.indicatorObjects
|
||||||
.find(indicator => indicator.key === 'notebook-snapshot-indicator').element;
|
.find(indicator => indicator.key === 'notebook-snapshot-indicator').element;
|
||||||
|
|
||||||
element.append(snapshotIndicator);
|
element.append(snapshotIndicator);
|
||||||
|
|
||||||
return Vue.nextTick();
|
return Vue.nextTick().then(() => {
|
||||||
|
drawerElement = document.querySelector('.l-shell__drawer');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterEach(() => {
|
||||||
|
if (drawerElement) {
|
||||||
|
drawerElement.classList.remove('is-expanded');
|
||||||
|
}
|
||||||
|
|
||||||
snapshotIndicator.remove();
|
snapshotIndicator.remove();
|
||||||
snapshotIndicator = undefined;
|
snapshotIndicator = undefined;
|
||||||
|
|
||||||
@ -165,16 +337,6 @@ describe("Notebook plugin:", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
drawerElement = document.querySelector('.l-shell__drawer');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
if (drawerElement) {
|
|
||||||
drawerElement.classList.remove('is-expanded');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("has Snapshots indicator", () => {
|
it("has Snapshots indicator", () => {
|
||||||
const hasSnapshotIndicator = snapshotIndicator !== null && snapshotIndicator !== undefined;
|
const hasSnapshotIndicator = snapshotIndicator !== null && snapshotIndicator !== undefined;
|
||||||
expect(hasSnapshotIndicator).toBe(true);
|
expect(hasSnapshotIndicator).toBe(true);
|
||||||
@ -218,4 +380,20 @@ describe("Notebook plugin:", () => {
|
|||||||
expect(snapshotsText).toBe('Notebook Snapshots');
|
expect(snapshotsText).toBe('Notebook Snapshots');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getEntryText(entryNumber) {
|
||||||
|
return element.querySelectorAll('.c-notebook__entry .c-ne__text')[entryNumber];
|
||||||
|
}
|
||||||
|
|
||||||
|
function allNotebookEntryElements() {
|
||||||
|
return element.querySelectorAll('.c-notebook__entry');
|
||||||
|
}
|
||||||
|
|
||||||
|
function allNotebookSectionElements() {
|
||||||
|
return element.querySelectorAll('.js-sidebar-sections .js-list__item');
|
||||||
|
}
|
||||||
|
|
||||||
|
function allNotebookPageElements() {
|
||||||
|
return element.querySelectorAll('.js-sidebar-pages .js-list__item');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { addNotebookEntry, createNewEmbed } from './utils/notebook-entries';
|
import { addNotebookEntry, createNewEmbed } from './utils/notebook-entries';
|
||||||
import { getDefaultNotebook, getDefaultNotebookLink, setDefaultNotebook } from './utils/notebook-storage';
|
import { getDefaultNotebook, getDefaultNotebookLink, setDefaultNotebook } from './utils/notebook-storage';
|
||||||
import { NOTEBOOK_DEFAULT } from '@/plugins/notebook/notebook-constants';
|
import { NOTEBOOK_DEFAULT } from '@/plugins/notebook/notebook-constants';
|
||||||
|
import { createNotebookImageDomainObject, DEFAULT_SIZE } from './utils/notebook-image';
|
||||||
|
|
||||||
import SnapshotContainer from './snapshot-container';
|
import SnapshotContainer from './snapshot-container';
|
||||||
|
|
||||||
export default class Snapshot {
|
export default class Snapshot {
|
||||||
@ -14,12 +16,17 @@ export default class Snapshot {
|
|||||||
|
|
||||||
capture(snapshotMeta, notebookType, domElement) {
|
capture(snapshotMeta, notebookType, domElement) {
|
||||||
const exportImageService = this.openmct.$injector.get('exportImageService');
|
const exportImageService = this.openmct.$injector.get('exportImageService');
|
||||||
exportImageService.exportPNGtoSRC(domElement, 's-status-taking-snapshot')
|
|
||||||
.then(function (blob) {
|
const options = {
|
||||||
|
className: 's-status-taking-snapshot',
|
||||||
|
thumbnailSize: DEFAULT_SIZE
|
||||||
|
};
|
||||||
|
exportImageService.exportPNGtoSRC(domElement, options)
|
||||||
|
.then(function ({blob, thumbnail}) {
|
||||||
const reader = new window.FileReader();
|
const reader = new window.FileReader();
|
||||||
reader.readAsDataURL(blob);
|
reader.readAsDataURL(blob);
|
||||||
reader.onloadend = function () {
|
reader.onloadend = function () {
|
||||||
this._saveSnapShot(notebookType, reader.result, snapshotMeta);
|
this._saveSnapShot(notebookType, reader.result, thumbnail, snapshotMeta);
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}
|
}
|
||||||
@ -27,8 +34,14 @@ export default class Snapshot {
|
|||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_saveSnapShot(notebookType, imageUrl, snapshotMeta) {
|
_saveSnapShot(notebookType, fullSizeImageURL, thumbnailImageURL, snapshotMeta) {
|
||||||
const snapshot = imageUrl ? { src: imageUrl } : '';
|
createNotebookImageDomainObject(this.openmct, fullSizeImageURL)
|
||||||
|
.then(object => {
|
||||||
|
const thumbnailImage = { src: thumbnailImageURL || '' };
|
||||||
|
const snapshot = {
|
||||||
|
fullSizeImageObjectIdentifier: object.identifier,
|
||||||
|
thumbnailImage
|
||||||
|
};
|
||||||
const embed = createNewEmbed(snapshotMeta, snapshot);
|
const embed = createNewEmbed(snapshotMeta, snapshot);
|
||||||
if (notebookType === NOTEBOOK_DEFAULT) {
|
if (notebookType === NOTEBOOK_DEFAULT) {
|
||||||
this._saveToDefaultNoteBook(embed);
|
this._saveToDefaultNoteBook(embed);
|
||||||
@ -37,6 +50,7 @@ export default class Snapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._saveToNotebookSnapshots(embed);
|
this._saveToNotebookSnapshots(embed);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,7 +112,7 @@ let openmct;
|
|||||||
let mockIdentifierService;
|
let mockIdentifierService;
|
||||||
|
|
||||||
describe('Notebook Entries:', () => {
|
describe('Notebook Entries:', () => {
|
||||||
beforeEach(done => {
|
beforeEach(() => {
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||||
mockIdentifierService = jasmine.createSpyObj(
|
mockIdentifierService = jasmine.createSpyObj(
|
||||||
@ -134,13 +134,12 @@ describe('Notebook Entries:', () => {
|
|||||||
'update'
|
'update'
|
||||||
]));
|
]));
|
||||||
window.localStorage.setItem('notebook-storage', null);
|
window.localStorage.setItem('notebook-storage', null);
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
notebookDomainObject.configuration.entries[selectedSection.id][selectedPage.id] = [];
|
notebookDomainObject.configuration.entries[selectedSection.id][selectedPage.id] = [];
|
||||||
resetApplicationState(openmct);
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getNotebookEntries has no entries', () => {
|
it('getNotebookEntries has no entries', () => {
|
||||||
@ -149,12 +148,11 @@ describe('Notebook Entries:', () => {
|
|||||||
expect(entries.length).toEqual(0);
|
expect(entries.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('addNotebookEntry adds entry', (done) => {
|
it('addNotebookEntry adds entry', () => {
|
||||||
const unlisten = openmct.objects.observe(notebookDomainObject, '*', (object) => {
|
const unlisten = openmct.objects.observe(notebookDomainObject, '*', (object) => {
|
||||||
const entries = NotebookEntries.getNotebookEntries(notebookDomainObject, selectedSection, selectedPage);
|
const entries = NotebookEntries.getNotebookEntries(notebookDomainObject, selectedSection, selectedPage);
|
||||||
|
|
||||||
expect(entries.length).toEqual(1);
|
expect(entries.length).toEqual(1);
|
||||||
done();
|
|
||||||
unlisten();
|
unlisten();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
78
src/plugins/notebook/utils/notebook-image.js
Normal file
78
src/plugins/notebook/utils/notebook-image.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import uuid from 'uuid';
|
||||||
|
|
||||||
|
export const DEFAULT_SIZE = {
|
||||||
|
width: 30,
|
||||||
|
height: 30
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createNotebookImageDomainObject(openmct, fullSizeImageURL) {
|
||||||
|
const identifier = {
|
||||||
|
key: uuid(),
|
||||||
|
namespace: ''
|
||||||
|
};
|
||||||
|
const viewType = 'notebookSnapshotImage';
|
||||||
|
|
||||||
|
const object = {
|
||||||
|
name: 'Notebook Snapshot Image',
|
||||||
|
type: viewType,
|
||||||
|
identifier,
|
||||||
|
configuration: {
|
||||||
|
fullSizeImageURL
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
openmct.objects.save(object)
|
||||||
|
.then(result => {
|
||||||
|
if (result) {
|
||||||
|
resolve(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
reject();
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getThumbnailURLFromCanvas(canvas, size = DEFAULT_SIZE) {
|
||||||
|
const thumbnailCanvas = document.createElement('canvas');
|
||||||
|
thumbnailCanvas.setAttribute('width', size.width);
|
||||||
|
thumbnailCanvas.setAttribute('height', size.height);
|
||||||
|
const ctx = thumbnailCanvas.getContext('2d');
|
||||||
|
ctx.globalCompositeOperation = "copy";
|
||||||
|
ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, size.width, size.height);
|
||||||
|
|
||||||
|
return thumbnailCanvas.toDataURL('image/png');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getThumbnailURLFromimageUrl(imageUrl, size = DEFAULT_SIZE) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const image = new Image();
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = size.width;
|
||||||
|
canvas.height = size.height;
|
||||||
|
|
||||||
|
image.onload = function () {
|
||||||
|
canvas.getContext('2d')
|
||||||
|
.drawImage(image, 0, 0, size.width, size.height);
|
||||||
|
|
||||||
|
resolve(canvas.toDataURL('image/png'));
|
||||||
|
};
|
||||||
|
|
||||||
|
image.src = imageUrl;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateNotebookImageDomainObject(openmct, identifier, fullSizeImage) {
|
||||||
|
openmct.objects.get(identifier)
|
||||||
|
.then(domainObject => {
|
||||||
|
const configuration = domainObject.configuration;
|
||||||
|
configuration.fullSizeImageURL = fullSizeImage.src;
|
||||||
|
|
||||||
|
openmct.objects.mutate(domainObject, 'configuration', configuration);
|
||||||
|
});
|
||||||
|
}
|
43
src/plugins/notebook/utils/notebook-migration.js
Normal file
43
src/plugins/notebook/utils/notebook-migration.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { createNotebookImageDomainObject, getThumbnailURLFromimageUrl } from './notebook-image';
|
||||||
|
import { mutateObject } from './notebook-entries';
|
||||||
|
|
||||||
|
export function notebookImageMigration(openmct, domainObject) {
|
||||||
|
const configuration = domainObject.configuration;
|
||||||
|
const notebookEntries = configuration.entries;
|
||||||
|
|
||||||
|
const imageMigrationVer = configuration.imageMigrationVer;
|
||||||
|
if (imageMigrationVer && imageMigrationVer === 'v1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration.imageMigrationVer = 'v1';
|
||||||
|
|
||||||
|
// to avoid muliple notebookImageMigration calls updating images.
|
||||||
|
mutateObject(openmct, domainObject, 'configuration', configuration);
|
||||||
|
|
||||||
|
configuration.sections.forEach(section => {
|
||||||
|
const sectionId = section.id;
|
||||||
|
section.pages.forEach(page => {
|
||||||
|
const pageId = page.id;
|
||||||
|
const notebookSection = notebookEntries && notebookEntries[sectionId] || {};
|
||||||
|
const pageEntries = notebookSection && notebookSection[pageId] || [];
|
||||||
|
pageEntries.forEach(entry => {
|
||||||
|
entry.embeds.forEach(async (embed) => {
|
||||||
|
const snapshot = embed.snapshot;
|
||||||
|
const fullSizeImageURL = snapshot.src;
|
||||||
|
if (fullSizeImageURL) {
|
||||||
|
const thumbnailImageURL = await getThumbnailURLFromimageUrl(fullSizeImageURL);
|
||||||
|
const notebookImageDomainObject = await createNotebookImageDomainObject(openmct, fullSizeImageURL);
|
||||||
|
|
||||||
|
embed.snapshot = {
|
||||||
|
fullSizeImageObjectIdentifier: notebookImageDomainObject.identifier,
|
||||||
|
thumbnailImage: { src: thumbnailImageURL || '' }
|
||||||
|
};
|
||||||
|
|
||||||
|
mutateObject(openmct, domainObject, 'configuration.entries', notebookEntries);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -60,7 +60,7 @@ let openmct;
|
|||||||
let mockIdentifierService;
|
let mockIdentifierService;
|
||||||
|
|
||||||
describe('Notebook Storage:', () => {
|
describe('Notebook Storage:', () => {
|
||||||
beforeEach((done) => {
|
beforeEach(() => {
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||||
mockIdentifierService = jasmine.createSpyObj(
|
mockIdentifierService = jasmine.createSpyObj(
|
||||||
@ -79,11 +79,10 @@ describe('Notebook Storage:', () => {
|
|||||||
'create',
|
'create',
|
||||||
'update'
|
'update'
|
||||||
]));
|
]));
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has empty local Storage', () => {
|
it('has empty local Storage', () => {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import Painterro from 'painterro';
|
import Painterro from 'painterro';
|
||||||
|
import { getThumbnailURLFromimageUrl } from './notebook-image';
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
activeColor: '#ff0000',
|
activeColor: '#ff0000',
|
||||||
@ -25,11 +26,11 @@ const DEFAULT_CONFIG = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default class PainterroInstance {
|
export default class PainterroInstance {
|
||||||
constructor(element, saveCallback) {
|
constructor(element) {
|
||||||
this.elementId = element.id;
|
this.elementId = element.id;
|
||||||
this.isSave = false;
|
this.isSave = false;
|
||||||
this.painterroInstance = null;
|
this.painterroInstance = undefined;
|
||||||
this.saveCallback = saveCallback;
|
this.saveCallback = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
dismiss() {
|
dismiss() {
|
||||||
@ -46,31 +47,41 @@ export default class PainterroInstance {
|
|||||||
this.painterro = Painterro(this.config);
|
this.painterro = Painterro(this.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save(callback) {
|
||||||
|
this.saveCallback = callback;
|
||||||
this.isSave = true;
|
this.isSave = true;
|
||||||
this.painterroInstance.save();
|
this.painterroInstance.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
saveHandler(image, done) {
|
saveHandler(image, done) {
|
||||||
if (this.isSave) {
|
if (this.isSave) {
|
||||||
const self = this;
|
|
||||||
const url = image.asBlob();
|
const url = image.asBlob();
|
||||||
|
|
||||||
const reader = new window.FileReader();
|
const reader = new window.FileReader();
|
||||||
reader.readAsDataURL(url);
|
reader.readAsDataURL(url);
|
||||||
reader.onloadend = () => {
|
reader.onloadend = async () => {
|
||||||
const snapshot = reader.result;
|
const fullSizeImageURL = reader.result;
|
||||||
|
const thumbnailURL = await getThumbnailURLFromimageUrl(fullSizeImageURL);
|
||||||
const snapshotObject = {
|
const snapshotObject = {
|
||||||
src: snapshot,
|
fullSizeImage: {
|
||||||
|
src: fullSizeImageURL,
|
||||||
type: url.type,
|
type: url.type,
|
||||||
size: url.size,
|
size: url.size,
|
||||||
modified: Date.now()
|
modified: Date.now()
|
||||||
|
},
|
||||||
|
thumbnailImage: {
|
||||||
|
src: thumbnailURL,
|
||||||
|
modified: Date.now()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.saveCallback(snapshotObject);
|
this.saveCallback(snapshotObject);
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
done(true);
|
done(true);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
done(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
show(src) {
|
show(src) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
@ -19,38 +19,20 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
import objectPathToUrl from '/src/tools/url';
|
||||||
|
export default class OpenInNewTab {
|
||||||
|
constructor(openmct) {
|
||||||
|
this.name = 'Open In New Tab';
|
||||||
|
this.key = 'newTab';
|
||||||
|
this.description = 'Open in a new browser tab';
|
||||||
|
this.group = "windowing";
|
||||||
|
this.priority = 10;
|
||||||
|
this.cssClass = "icon-new-window";
|
||||||
|
|
||||||
define(
|
this._openmct = openmct;
|
||||||
[],
|
}
|
||||||
function () {
|
invoke(objectPath) {
|
||||||
|
let url = objectPathToUrl(this._openmct, objectPath);
|
||||||
/**
|
window.open(url);
|
||||||
* Designates a specific timer for following. Timelines, for example,
|
|
||||||
* use the actively followed timer to display a time-of-interest line
|
|
||||||
* and interpret time conductor bounds in the Timeline's relative
|
|
||||||
* time frame.
|
|
||||||
*
|
|
||||||
* @implements {Action}
|
|
||||||
* @memberof platform/features/clock
|
|
||||||
* @constructor
|
|
||||||
* @param {ActionContext} context the context for this action
|
|
||||||
*/
|
|
||||||
function FollowTimerAction(timerService, context) {
|
|
||||||
var domainObject =
|
|
||||||
context.domainObject
|
|
||||||
&& context.domainObject.useCapability('adapter');
|
|
||||||
this.perform =
|
|
||||||
timerService.setTimer.bind(timerService, domainObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FollowTimerAction.appliesTo = function (context) {
|
|
||||||
var model =
|
|
||||||
(context.domainObject && context.domainObject.getModel())
|
|
||||||
|| {};
|
|
||||||
|
|
||||||
return model.type === 'timer';
|
|
||||||
};
|
|
||||||
|
|
||||||
return FollowTimerAction;
|
|
||||||
}
|
}
|
||||||
);
|
|
@ -1,5 +1,5 @@
|
|||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2009-2018, United States Government
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
@ -19,33 +19,10 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
import OpenInNewTabAction from './openInNewTabAction';
|
||||||
|
|
||||||
define([], function () {
|
export default function () {
|
||||||
|
return function (openmct) {
|
||||||
/**
|
openmct.actions.register(new OpenInNewTabAction(openmct));
|
||||||
* Indicator that displays the active timer, as well as its
|
|
||||||
* current state.
|
|
||||||
* @memberof platform/features/clock
|
|
||||||
*/
|
|
||||||
return function installFollowIndicator(openmct, timerService) {
|
|
||||||
var indicator = openmct.indicators.simpleIndicator();
|
|
||||||
var timer = timerService.getTimer();
|
|
||||||
setIndicatorStatus(timer);
|
|
||||||
|
|
||||||
function setIndicatorStatus(newTimer) {
|
|
||||||
if (newTimer !== undefined) {
|
|
||||||
indicator.iconClass('icon-timer');
|
|
||||||
indicator.statusClass('s-status-on');
|
|
||||||
indicator.text('Following timer ' + newTimer.name);
|
|
||||||
} else {
|
|
||||||
indicator.iconClass('icon-timer');
|
|
||||||
indicator.statusClass('s-status-disabled');
|
|
||||||
indicator.text('No timer being followed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timerService.on('change', setIndicatorStatus);
|
|
||||||
|
|
||||||
openmct.indicators.add(indicator);
|
|
||||||
};
|
};
|
||||||
});
|
}
|
75
src/plugins/openInNewTabAction/pluginSpec.js
Normal file
75
src/plugins/openInNewTabAction/pluginSpec.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import {
|
||||||
|
createOpenMct,
|
||||||
|
resetApplicationState,
|
||||||
|
spyOnBuiltins
|
||||||
|
} from 'utils/testing';
|
||||||
|
|
||||||
|
describe("the plugin", () => {
|
||||||
|
let openmct;
|
||||||
|
let openInNewTabAction;
|
||||||
|
let mockObjectPath;
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
openmct = createOpenMct();
|
||||||
|
|
||||||
|
openmct.on('start', done);
|
||||||
|
openmct.startHeadless();
|
||||||
|
|
||||||
|
openInNewTabAction = openmct.actions._allActions.newTab;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('installs the open in new tab action', () => {
|
||||||
|
expect(openInNewTabAction).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when invoked', () => {
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
mockObjectPath = [{
|
||||||
|
name: 'mock folder',
|
||||||
|
type: 'folder',
|
||||||
|
identifier: {
|
||||||
|
key: 'mock-folder',
|
||||||
|
namespace: ''
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({
|
||||||
|
identifier: {
|
||||||
|
namespace: '',
|
||||||
|
key: 'test'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
spyOnBuiltins(['open']);
|
||||||
|
await openInNewTabAction.invoke(mockObjectPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it opens in a new tab', () => {
|
||||||
|
expect(window.open).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
import CouchDocument from "./CouchDocument";
|
import CouchDocument from "./CouchDocument";
|
||||||
import CouchObjectQueue from "./CouchObjectQueue";
|
import CouchObjectQueue from "./CouchObjectQueue";
|
||||||
|
import { NOTEBOOK_TYPE } from '../../notebook/notebook-constants.js';
|
||||||
|
|
||||||
const REV = "_rev";
|
const REV = "_rev";
|
||||||
const ID = "_id";
|
const ID = "_id";
|
||||||
@ -29,24 +30,14 @@ const HEARTBEAT = 50000;
|
|||||||
const ALL_DOCS = "_all_docs?include_docs=true";
|
const ALL_DOCS = "_all_docs?include_docs=true";
|
||||||
|
|
||||||
export default class CouchObjectProvider {
|
export default class CouchObjectProvider {
|
||||||
// options {
|
|
||||||
// url: couchdb url,
|
|
||||||
// disableObserve: disable auto feed from couchdb to keep objects in sync,
|
|
||||||
// filter: selector to find objects to sync in couchdb
|
|
||||||
// }
|
|
||||||
constructor(openmct, options, namespace) {
|
constructor(openmct, options, namespace) {
|
||||||
options = this._normalize(options);
|
options = this._normalize(options);
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.url = options.url;
|
this.url = options.url;
|
||||||
this.namespace = namespace;
|
this.namespace = namespace;
|
||||||
this.objectQueue = {};
|
this.objectQueue = {};
|
||||||
this.observeEnabled = options.disableObserve !== true;
|
|
||||||
this.observers = {};
|
this.observers = {};
|
||||||
this.batchIds = [];
|
this.batchIds = [];
|
||||||
|
|
||||||
if (this.observeEnabled) {
|
|
||||||
this.observeObjectChanges(options.filter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//backwards compatibility, options used to be a url. Now it's an object
|
//backwards compatibility, options used to be a url. Now it's an object
|
||||||
@ -133,8 +124,12 @@ export default class CouchObjectProvider {
|
|||||||
this.objectQueue[key] = new CouchObjectQueue(undefined, response[REV]);
|
this.objectQueue[key] = new CouchObjectQueue(undefined, response[REV]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (object.type === NOTEBOOK_TYPE) {
|
||||||
|
//Temporary measure until object sync is supported for all object types
|
||||||
|
//Always update notebook revision number because we have realtime sync, so always assume it's the latest.
|
||||||
|
this.objectQueue[key].updateRevision(response[REV]);
|
||||||
|
} else if (!this.objectQueue[key].pending) {
|
||||||
//Sometimes CouchDB returns the old rev which fetching the object if there is a document update in progress
|
//Sometimes CouchDB returns the old rev which fetching the object if there is a document update in progress
|
||||||
if (!this.objectQueue[key].pending) {
|
|
||||||
this.objectQueue[key].updateRevision(response[REV]);
|
this.objectQueue[key].updateRevision(response[REV]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,49 +308,63 @@ export default class CouchObjectProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
observe(identifier, callback) {
|
observe(identifier, callback) {
|
||||||
if (!this.observeEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyString = this.openmct.objects.makeKeyString(identifier);
|
const keyString = this.openmct.objects.makeKeyString(identifier);
|
||||||
this.observers[keyString] = this.observers[keyString] || [];
|
this.observers[keyString] = this.observers[keyString] || [];
|
||||||
this.observers[keyString].push(callback);
|
this.observers[keyString].push(callback);
|
||||||
|
|
||||||
|
if (!this.isObservingObjectChanges()) {
|
||||||
|
this.observeObjectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
this.observers[keyString] = this.observers[keyString].filter(observer => observer !== callback);
|
this.observers[keyString] = this.observers[keyString].filter(observer => observer !== callback);
|
||||||
|
if (this.observers[keyString].length === 0) {
|
||||||
|
delete this.observers[keyString];
|
||||||
|
if (Object.keys(this.observers).length === 0) {
|
||||||
|
this.stopObservingObjectChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
isObservingObjectChanges() {
|
||||||
* @private
|
return this.stopObservingObjectChanges !== undefined;
|
||||||
*/
|
|
||||||
abortGetChanges() {
|
|
||||||
if (this.controller) {
|
|
||||||
this.controller.abort();
|
|
||||||
this.controller = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async observeObjectChanges(filter) {
|
async observeObjectChanges() {
|
||||||
let intermediateResponse = this.getIntermediateResponse();
|
|
||||||
|
|
||||||
if (!this.observeEnabled) {
|
|
||||||
intermediateResponse.reject('Observe for changes is disabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const signal = controller.signal;
|
const signal = controller.signal;
|
||||||
|
let filter = {selector: {}};
|
||||||
|
|
||||||
if (this.controller) {
|
if (this.openmct.objects.SYNCHRONIZED_OBJECT_TYPES.length > 1) {
|
||||||
this.abortGetChanges();
|
filter.selector.$or = this.openmct.objects.SYNCHRONIZED_OBJECT_TYPES
|
||||||
|
.map(type => {
|
||||||
|
return {
|
||||||
|
'model': {
|
||||||
|
type
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
filter.selector.model = {
|
||||||
|
type: this.openmct.objects.SYNCHRONIZED_OBJECT_TYPES[0]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.controller = controller;
|
let error = false;
|
||||||
|
|
||||||
|
if (typeof this.stopObservingObjectChanges === 'function') {
|
||||||
|
this.stopObservingObjectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stopObservingObjectChanges = () => {
|
||||||
|
controller.abort();
|
||||||
|
delete this.stopObservingObjectChanges;
|
||||||
|
};
|
||||||
|
|
||||||
// feed=continuous maintains an indefinitely open connection with a keep-alive of HEARTBEAT milliseconds until this client closes the connection
|
// feed=continuous maintains an indefinitely open connection with a keep-alive of HEARTBEAT milliseconds until this client closes the connection
|
||||||
// style=main_only returns only the current winning revision of the document
|
// style=main_only returns only the current winning revision of the document
|
||||||
let url = `${this.url}/_changes?feed=continuous&style=main_only&heartbeat=${HEARTBEAT}`;
|
let url = `${this.url}/_changes?feed=continuous&style=main_only&heartbeat=${HEARTBEAT}`;
|
||||||
@ -374,14 +383,20 @@ export default class CouchObjectProvider {
|
|||||||
},
|
},
|
||||||
body
|
body
|
||||||
});
|
});
|
||||||
const reader = response.body.getReader();
|
|
||||||
let completed = false;
|
|
||||||
|
|
||||||
while (!completed) {
|
let reader;
|
||||||
|
|
||||||
|
if (response.body === undefined) {
|
||||||
|
error = true;
|
||||||
|
} else {
|
||||||
|
reader = response.body.getReader();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!error) {
|
||||||
const {done, value} = await reader.read();
|
const {done, value} = await reader.read();
|
||||||
//done is true when we lose connection with the provider
|
//done is true when we lose connection with the provider
|
||||||
if (done) {
|
if (done) {
|
||||||
completed = true;
|
error = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
@ -414,11 +429,9 @@ export default class CouchObjectProvider {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//We're done receiving from the provider. No more chunks.
|
if (error && Object.keys(this.observers).length > 0) {
|
||||||
intermediateResponse.resolve(true);
|
this.observeObjectChanges();
|
||||||
|
}
|
||||||
return intermediateResponse.promise;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,8 +27,6 @@ import {
|
|||||||
|
|
||||||
describe('the plugin', () => {
|
describe('the plugin', () => {
|
||||||
let openmct;
|
let openmct;
|
||||||
let element;
|
|
||||||
let child;
|
|
||||||
let provider;
|
let provider;
|
||||||
let testPath = '/test/db';
|
let testPath = '/test/db';
|
||||||
let options;
|
let options;
|
||||||
@ -36,6 +34,8 @@ describe('the plugin', () => {
|
|||||||
let mockDomainObject;
|
let mockDomainObject;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
|
spyOnBuiltins(['fetch'], window);
|
||||||
|
|
||||||
mockDomainObject = {
|
mockDomainObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
namespace: '',
|
namespace: '',
|
||||||
@ -51,8 +51,6 @@ describe('the plugin', () => {
|
|||||||
};
|
};
|
||||||
openmct = createOpenMct(false);
|
openmct = createOpenMct(false);
|
||||||
|
|
||||||
spyOnBuiltins(['fetch'], window);
|
|
||||||
|
|
||||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||||
mockIdentifierService = jasmine.createSpyObj(
|
mockIdentifierService = jasmine.createSpyObj(
|
||||||
'identifierService',
|
'identifierService',
|
||||||
@ -70,10 +68,6 @@ describe('the plugin', () => {
|
|||||||
|
|
||||||
openmct.types.addType('mock-type', {creatable: true});
|
openmct.types.addType('mock-type', {creatable: true});
|
||||||
|
|
||||||
element = document.createElement('div');
|
|
||||||
child = document.createElement('div');
|
|
||||||
element.appendChild(child);
|
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless();
|
openmct.startHeadless();
|
||||||
|
|
||||||
@ -117,7 +111,7 @@ describe('the plugin', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates an object', () => {
|
it('updates an object', (done) => {
|
||||||
return openmct.objects.save(mockDomainObject).then((result) => {
|
return openmct.objects.save(mockDomainObject).then((result) => {
|
||||||
expect(result).toBeTrue();
|
expect(result).toBeTrue();
|
||||||
expect(provider.create).toHaveBeenCalled();
|
expect(provider.create).toHaveBeenCalled();
|
||||||
@ -128,6 +122,7 @@ describe('the plugin', () => {
|
|||||||
return openmct.objects.save(mockDomainObject).then((updatedResult) => {
|
return openmct.objects.save(mockDomainObject).then((updatedResult) => {
|
||||||
expect(updatedResult).toBeTrue();
|
expect(updatedResult).toBeTrue();
|
||||||
expect(provider.update).toHaveBeenCalled();
|
expect(provider.update).toHaveBeenCalled();
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -29,9 +29,10 @@ describe('the plugin', function () {
|
|||||||
let element;
|
let element;
|
||||||
let child;
|
let child;
|
||||||
let openmct;
|
let openmct;
|
||||||
|
let appHolder;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
const appHolder = document.createElement('div');
|
appHolder = document.createElement('div');
|
||||||
appHolder.style.width = '640px';
|
appHolder.style.width = '640px';
|
||||||
appHolder.style.height = '480px';
|
appHolder.style.height = '480px';
|
||||||
|
|
||||||
@ -103,7 +104,7 @@ describe('the plugin', function () {
|
|||||||
];
|
];
|
||||||
let planView;
|
let planView;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach(() => {
|
||||||
planDomainObject = {
|
planDomainObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
key: 'test-object',
|
key: 'test-object',
|
||||||
@ -140,9 +141,7 @@ describe('the plugin', function () {
|
|||||||
let view = planView.view(planDomainObject, mockObjectPath);
|
let view = planView.view(planDomainObject, mockObjectPath);
|
||||||
view.show(child, true);
|
view.show(child, true);
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick();
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads activities into the view', () => {
|
it('loads activities into the view', () => {
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
:series="config.series.models"
|
:series="config.series.models"
|
||||||
:highlights="highlights"
|
:highlights="highlights"
|
||||||
:legend="config.legend"
|
:legend="config.legend"
|
||||||
|
@legendHoverChanged="legendHoverChanged"
|
||||||
/>
|
/>
|
||||||
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
|
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
|
||||||
<y-axis v-if="config.series.models.length > 0"
|
<y-axis v-if="config.series.models.length > 0"
|
||||||
@ -67,12 +68,15 @@
|
|||||||
>
|
>
|
||||||
<mct-chart :rectangles="rectangles"
|
<mct-chart :rectangles="rectangles"
|
||||||
:highlights="highlights"
|
:highlights="highlights"
|
||||||
|
:show-limit-line-labels="showLimitLineLabels"
|
||||||
@plotReinitializeCanvas="initCanvas"
|
@plotReinitializeCanvas="initCanvas"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover">
|
<div class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover">
|
||||||
<div class="c-button-set c-button-set--strip-h">
|
<div v-if="!options.compact"
|
||||||
|
class="c-button-set c-button-set--strip-h js-zoom"
|
||||||
|
>
|
||||||
<button class="c-button icon-minus"
|
<button class="c-button icon-minus"
|
||||||
title="Zoom out"
|
title="Zoom out"
|
||||||
@click="zoom('out', 0.2)"
|
@click="zoom('out', 0.2)"
|
||||||
@ -84,8 +88,8 @@
|
|||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="plotHistory.length"
|
<div v-if="plotHistory.length && !options.compact"
|
||||||
class="c-button-set c-button-set--strip-h"
|
class="c-button-set c-button-set--strip-h js-pan"
|
||||||
>
|
>
|
||||||
<button class="c-button icon-arrow-left"
|
<button class="c-button icon-arrow-left"
|
||||||
title="Restore previous pan/zoom"
|
title="Restore previous pan/zoom"
|
||||||
@ -99,7 +103,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isRealTime"
|
<div v-if="isRealTime"
|
||||||
class="c-button-set c-button-set--strip-h"
|
class="c-button-set c-button-set--strip-h js-pause"
|
||||||
>
|
>
|
||||||
<button v-if="!isFrozen"
|
<button v-if="!isFrozen"
|
||||||
class="c-button icon-pause"
|
class="c-button icon-pause"
|
||||||
@ -159,6 +163,7 @@ import MctTicks from "./MctTicks.vue";
|
|||||||
import MctChart from "./chart/MctChart.vue";
|
import MctChart from "./chart/MctChart.vue";
|
||||||
import XAxis from "./axis/XAxis.vue";
|
import XAxis from "./axis/XAxis.vue";
|
||||||
import YAxis from "./axis/YAxis.vue";
|
import YAxis from "./axis/YAxis.vue";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -212,7 +217,8 @@ export default {
|
|||||||
pending: 0,
|
pending: 0,
|
||||||
isRealTime: this.openmct.time.clock() !== undefined,
|
isRealTime: this.openmct.time.clock() !== undefined,
|
||||||
loaded: false,
|
loaded: false,
|
||||||
isTimeOutOfSync: false
|
isTimeOutOfSync: false,
|
||||||
|
showLimitLineLabels: undefined
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -489,13 +495,19 @@ export default {
|
|||||||
|
|
||||||
this.canvas = this.$refs.chartContainer.querySelectorAll('canvas')[1];
|
this.canvas = this.$refs.chartContainer.querySelectorAll('canvas')[1];
|
||||||
|
|
||||||
|
if (!this.options.compact) {
|
||||||
this.listenTo(this.canvas, 'mousemove', this.trackMousePosition, this);
|
this.listenTo(this.canvas, 'mousemove', this.trackMousePosition, this);
|
||||||
this.listenTo(this.canvas, 'mouseleave', this.untrackMousePosition, this);
|
this.listenTo(this.canvas, 'mouseleave', this.untrackMousePosition, this);
|
||||||
this.listenTo(this.canvas, 'mousedown', this.onMouseDown, this);
|
this.listenTo(this.canvas, 'mousedown', this.onMouseDown, this);
|
||||||
this.listenTo(this.canvas, 'wheel', this.wheelZoom, this);
|
this.listenTo(this.canvas, 'wheel', this.wheelZoom, this);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
|
_.debounce(this.handleWindowResize, 400);
|
||||||
|
this.plotContainerResizeObserver = new ResizeObserver(this.handleWindowResize);
|
||||||
|
this.plotContainerResizeObserver.observe(this.$parent.$refs.plotWrapper);
|
||||||
|
|
||||||
// Setup canvas etc.
|
// Setup canvas etc.
|
||||||
this.xScale = new LinearScale(this.config.xAxis.get('displayRange'));
|
this.xScale = new LinearScale(this.config.xAxis.get('displayRange'));
|
||||||
this.yScale = new LinearScale(this.config.yAxis.get('displayRange'));
|
this.yScale = new LinearScale(this.config.yAxis.get('displayRange'));
|
||||||
@ -506,12 +518,7 @@ export default {
|
|||||||
this.chartElementBounds = undefined;
|
this.chartElementBounds = undefined;
|
||||||
this.tickUpdate = false;
|
this.tickUpdate = false;
|
||||||
|
|
||||||
this.canvas = this.$refs.chartContainer.querySelectorAll('canvas')[1];
|
this.initCanvas();
|
||||||
|
|
||||||
this.listenTo(this.canvas, 'mousemove', this.trackMousePosition, this);
|
|
||||||
this.listenTo(this.canvas, 'mouseleave', this.untrackMousePosition, this);
|
|
||||||
this.listenTo(this.canvas, 'mousedown', this.onMouseDown, this);
|
|
||||||
this.listenTo(this.canvas, 'wheel', this.wheelZoom, this);
|
|
||||||
|
|
||||||
this.config.yAxisLabel = this.config.yAxis.get('label');
|
this.config.yAxisLabel = this.config.yAxis.get('label');
|
||||||
|
|
||||||
@ -999,12 +1006,23 @@ export default {
|
|||||||
this.removeStatusListener();
|
this.removeStatusListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.plotContainerResizeObserver.disconnect();
|
||||||
|
|
||||||
this.openmct.time.off('clock', this.updateRealTime);
|
this.openmct.time.off('clock', this.updateRealTime);
|
||||||
this.openmct.time.off('bounds', this.updateDisplayBounds);
|
this.openmct.time.off('bounds', this.updateDisplayBounds);
|
||||||
this.openmct.objectViews.off('clearData', this.clearData);
|
this.openmct.objectViews.off('clearData', this.clearData);
|
||||||
},
|
},
|
||||||
updateStatus(status) {
|
updateStatus(status) {
|
||||||
this.$emit('statusUpdated', status);
|
this.$emit('statusUpdated', status);
|
||||||
|
},
|
||||||
|
handleWindowResize() {
|
||||||
|
if (this.offsetWidth !== this.$parent.$refs.plotWrapper.offsetWidth) {
|
||||||
|
this.offsetWidth = this.$parent.$refs.plotWrapper.offsetWidth;
|
||||||
|
this.config.series.models.forEach(this.loadSeriesData, this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legendHoverChanged(data) {
|
||||||
|
this.showLimitLineLabels = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -228,8 +228,8 @@ export default {
|
|||||||
|
|
||||||
doTickUpdate() {
|
doTickUpdate() {
|
||||||
if (this.shouldCheckWidth) {
|
if (this.shouldCheckWidth) {
|
||||||
const tickElements = this.$refs.tickContainer.querySelectorAll('.gl-plot-tick > span');
|
const tickElements = this.$refs.tickContainer && this.$refs.tickContainer.querySelectorAll('.gl-plot-tick > span');
|
||||||
|
if (tickElements) {
|
||||||
const tickWidth = Number([].reduce.call(tickElements, function (memo, first) {
|
const tickWidth = Number([].reduce.call(tickElements, function (memo, first) {
|
||||||
return Math.max(memo, first.offsetWidth);
|
return Math.max(memo, first.offsetWidth);
|
||||||
}, 0));
|
}, 0));
|
||||||
@ -238,6 +238,7 @@ export default {
|
|||||||
this.$emit('plotTickWidth', tickWidth);
|
this.$emit('plotTickWidth', tickWidth);
|
||||||
this.shouldCheckWidth = false;
|
this.shouldCheckWidth = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.tickUpdate = false;
|
this.tickUpdate = false;
|
||||||
}
|
}
|
||||||
|
51
src/plugins/plot/chart/LimitLabel.vue
Normal file
51
src/plugins/plot/chart/LimitLabel.vue
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<div class="c-plot-limit"
|
||||||
|
:style="styleObj"
|
||||||
|
:class="limitClass"
|
||||||
|
>
|
||||||
|
<div class="c-plot-limit__label">
|
||||||
|
<span class="c-plot-limit__direction-icon"></span>
|
||||||
|
<span class="c-plot-limit__severity-icon"></span>
|
||||||
|
<span class="c-plot-limit__limit-value">{{ limit.value }}</span>
|
||||||
|
<span class="c-plot-limit__series-color-swatch"
|
||||||
|
:style="{ 'background-color': limit.seriesColor }"
|
||||||
|
></span>
|
||||||
|
<span class="c-plot-limit__series-name">{{ limit.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getLimitClass } from "./limitUtil";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
limit: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
point: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
styleObj() {
|
||||||
|
const top = `${this.point.top}px`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
'top': top
|
||||||
|
};
|
||||||
|
},
|
||||||
|
limitClass() {
|
||||||
|
return getLimitClass(this.limit, 'c-plot-limit--');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
40
src/plugins/plot/chart/LimitLine.vue
Normal file
40
src/plugins/plot/chart/LimitLine.vue
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div :style="styleObj"
|
||||||
|
class="c-plot-limit-line js-limit-line"
|
||||||
|
:class="limitClass"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getLimitClass } from "./limitUtil";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
point: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
styleObj() {
|
||||||
|
const top = `${this.point.top}px`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
'top': top
|
||||||
|
};
|
||||||
|
},
|
||||||
|
limitClass() {
|
||||||
|
return getLimitClass(this.limit, 'c-plot-limit-line--');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
108
src/plugins/plot/chart/MCTChartAlarmLineSet.js
Normal file
108
src/plugins/plot/chart/MCTChartAlarmLineSet.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, 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 eventHelpers from '../lib/eventHelpers';
|
||||||
|
|
||||||
|
export default class MCTChartAlarmLineSet {
|
||||||
|
constructor(series, chart, offset, bounds) {
|
||||||
|
this.series = series;
|
||||||
|
this.chart = chart;
|
||||||
|
this.offset = offset;
|
||||||
|
this.bounds = bounds;
|
||||||
|
this.limits = [];
|
||||||
|
|
||||||
|
eventHelpers.extend(this);
|
||||||
|
this.listenTo(series, 'limitBounds', this.updateBounds, this);
|
||||||
|
this.listenTo(series, 'limits', this.getLimitPoints, this);
|
||||||
|
this.listenTo(series, 'change:xKey', this.getLimitPoints, this);
|
||||||
|
|
||||||
|
if (series.limits) {
|
||||||
|
this.getLimitPoints(series);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBounds(bounds) {
|
||||||
|
this.bounds = bounds;
|
||||||
|
this.getLimitPoints(this.series);
|
||||||
|
}
|
||||||
|
|
||||||
|
color() {
|
||||||
|
return this.series.get('color');
|
||||||
|
}
|
||||||
|
|
||||||
|
name() {
|
||||||
|
return this.series.get('name');
|
||||||
|
}
|
||||||
|
|
||||||
|
makePoint(point, series) {
|
||||||
|
if (!this.offset.xVal) {
|
||||||
|
this.chart.setOffset(point, undefined, series);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: this.offset.xVal(point, series),
|
||||||
|
y: this.offset.yVal(point, series)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getLimitPoints(series) {
|
||||||
|
this.limits = [];
|
||||||
|
let xKey = series.get('xKey');
|
||||||
|
Object.keys(series.limits).forEach((key) => {
|
||||||
|
const limitForLevel = series.limits[key];
|
||||||
|
if (limitForLevel.high) {
|
||||||
|
this.limits.push({
|
||||||
|
seriesKey: series.keyString,
|
||||||
|
level: key.toLowerCase(),
|
||||||
|
name: this.name(),
|
||||||
|
seriesColor: series.get('color').asHexString(),
|
||||||
|
point: this.makePoint(Object.assign({ [xKey]: this.bounds.start }, limitForLevel.high), series),
|
||||||
|
value: series.getYVal(limitForLevel.high),
|
||||||
|
color: limitForLevel.high.color,
|
||||||
|
isUpper: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limitForLevel.low) {
|
||||||
|
this.limits.push({
|
||||||
|
seriesKey: series.keyString,
|
||||||
|
level: key.toLowerCase(),
|
||||||
|
name: this.name(),
|
||||||
|
seriesColor: series.get('color').asHexString(),
|
||||||
|
point: this.makePoint(Object.assign({ [xKey]: this.bounds.start }, limitForLevel.low), series),
|
||||||
|
value: series.getYVal(limitForLevel.low),
|
||||||
|
color: limitForLevel.low.color,
|
||||||
|
isUpper: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.limits = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.stopListening();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -23,6 +23,9 @@
|
|||||||
<div class="gl-plot-chart-area">
|
<div class="gl-plot-chart-area">
|
||||||
<span v-html="canvasTemplate"></span>
|
<span v-html="canvasTemplate"></span>
|
||||||
<span v-html="canvasTemplate"></span>
|
<span v-html="canvasTemplate"></span>
|
||||||
|
<div ref="limitArea"
|
||||||
|
class="js-limit-area"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -34,11 +37,16 @@ import MCTChartLineLinear from './MCTChartLineLinear';
|
|||||||
import MCTChartLineStepAfter from './MCTChartLineStepAfter';
|
import MCTChartLineStepAfter from './MCTChartLineStepAfter';
|
||||||
import MCTChartPointSet from './MCTChartPointSet';
|
import MCTChartPointSet from './MCTChartPointSet';
|
||||||
import MCTChartAlarmPointSet from './MCTChartAlarmPointSet';
|
import MCTChartAlarmPointSet from './MCTChartAlarmPointSet';
|
||||||
|
import MCTChartAlarmLineSet from "./MCTChartAlarmLineSet";
|
||||||
import configStore from "../configuration/configStore";
|
import configStore from "../configuration/configStore";
|
||||||
import PlotConfigurationModel from "../configuration/PlotConfigurationModel";
|
import PlotConfigurationModel from "../configuration/PlotConfigurationModel";
|
||||||
|
import LimitLine from "./LimitLine.vue";
|
||||||
|
import LimitLabel from "./LimitLabel.vue";
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
const MARKER_SIZE = 6.0;
|
const MARKER_SIZE = 6.0;
|
||||||
const HIGHLIGHT_SIZE = MARKER_SIZE * 2.0;
|
const HIGHLIGHT_SIZE = MARKER_SIZE * 2.0;
|
||||||
|
const CLEARANCE = 15;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
@ -54,6 +62,12 @@ export default {
|
|||||||
default() {
|
default() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
showLimitLineLabels: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -67,18 +81,22 @@ export default {
|
|||||||
},
|
},
|
||||||
rectangles() {
|
rectangles() {
|
||||||
this.scheduleDraw();
|
this.scheduleDraw();
|
||||||
|
},
|
||||||
|
showLimitLineLabels() {
|
||||||
|
this.drawLimitLines();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
eventHelpers.extend(this);
|
eventHelpers.extend(this);
|
||||||
|
|
||||||
this.config = this.getConfig();
|
this.config = this.getConfig();
|
||||||
this.isDestroyed = false;
|
this.isDestroyed = false;
|
||||||
this.lines = [];
|
this.lines = [];
|
||||||
|
this.limitLines = [];
|
||||||
this.pointSets = [];
|
this.pointSets = [];
|
||||||
this.alarmSets = [];
|
this.alarmSets = [];
|
||||||
this.offset = {};
|
this.offset = {};
|
||||||
this.seriesElements = new WeakMap();
|
this.seriesElements = new WeakMap();
|
||||||
|
this.seriesLimits = new WeakMap();
|
||||||
|
|
||||||
let canvasEls = this.$parent.$refs.chartContainer.querySelectorAll("canvas");
|
let canvasEls = this.$parent.$refs.chartContainer.querySelectorAll("canvas");
|
||||||
const mainCanvas = canvasEls[1];
|
const mainCanvas = canvasEls[1];
|
||||||
@ -90,8 +108,8 @@ export default {
|
|||||||
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
|
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
|
||||||
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
|
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
|
||||||
this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this);
|
this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this);
|
||||||
this.listenTo(this.config.yAxis, 'change', this.scheduleDraw);
|
this.listenTo(this.config.yAxis, 'change', this.updateLimitsAndDraw);
|
||||||
this.listenTo(this.config.xAxis, 'change', this.scheduleDraw);
|
this.listenTo(this.config.xAxis, 'change', this.updateLimitsAndDraw);
|
||||||
this.config.series.forEach(this.onSeriesAdd, this);
|
this.config.series.forEach(this.onSeriesAdd, this);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
@ -116,15 +134,18 @@ export default {
|
|||||||
this.changeInterpolate(mode, o, series);
|
this.changeInterpolate(mode, o, series);
|
||||||
this.changeMarkers(mode, o, series);
|
this.changeMarkers(mode, o, series);
|
||||||
this.changeAlarmMarkers(mode, o, series);
|
this.changeAlarmMarkers(mode, o, series);
|
||||||
|
this.changeLimitLines(mode, o, series);
|
||||||
},
|
},
|
||||||
onSeriesAdd(series) {
|
onSeriesAdd(series) {
|
||||||
this.listenTo(series, 'change:xKey', this.reDraw, this);
|
this.listenTo(series, 'change:xKey', this.reDraw, this);
|
||||||
this.listenTo(series, 'change:interpolate', this.changeInterpolate, this);
|
this.listenTo(series, 'change:interpolate', this.changeInterpolate, this);
|
||||||
this.listenTo(series, 'change:markers', this.changeMarkers, this);
|
this.listenTo(series, 'change:markers', this.changeMarkers, this);
|
||||||
this.listenTo(series, 'change:alarmMarkers', this.changeAlarmMarkers, this);
|
this.listenTo(series, 'change:alarmMarkers', this.changeAlarmMarkers, this);
|
||||||
|
this.listenTo(series, 'change:limitLines', this.changeLimitLines, this);
|
||||||
this.listenTo(series, 'change', this.scheduleDraw);
|
this.listenTo(series, 'change', this.scheduleDraw);
|
||||||
this.listenTo(series, 'add', this.scheduleDraw);
|
this.listenTo(series, 'add', this.scheduleDraw);
|
||||||
this.makeChartElement(series);
|
this.makeChartElement(series);
|
||||||
|
this.makeLimitLines(series);
|
||||||
},
|
},
|
||||||
changeInterpolate(mode, o, series) {
|
changeInterpolate(mode, o, series) {
|
||||||
if (mode === o) {
|
if (mode === o) {
|
||||||
@ -178,6 +199,14 @@ export default {
|
|||||||
this.pointSets.push(pointSet);
|
this.pointSets.push(pointSet);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
changeLimitLines(mode, o, series) {
|
||||||
|
if (mode === o) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.makeLimitLines(series);
|
||||||
|
this.updateLimitsAndDraw();
|
||||||
|
},
|
||||||
onSeriesRemove(series) {
|
onSeriesRemove(series) {
|
||||||
this.stopListening(series);
|
this.stopListening(series);
|
||||||
this.removeChartElement(series);
|
this.removeChartElement(series);
|
||||||
@ -187,6 +216,7 @@ export default {
|
|||||||
this.isDestroyed = true;
|
this.isDestroyed = true;
|
||||||
this.stopListening();
|
this.stopListening();
|
||||||
this.lines.forEach(line => line.destroy());
|
this.lines.forEach(line => line.destroy());
|
||||||
|
this.limitLines.forEach(line => line.destroy());
|
||||||
DrawLoader.releaseDrawAPI(this.drawAPI);
|
DrawLoader.releaseDrawAPI(this.drawAPI);
|
||||||
},
|
},
|
||||||
clearOffset() {
|
clearOffset() {
|
||||||
@ -199,6 +229,9 @@ export default {
|
|||||||
this.lines.forEach(function (line) {
|
this.lines.forEach(function (line) {
|
||||||
line.reset();
|
line.reset();
|
||||||
});
|
});
|
||||||
|
this.limitLines.forEach(function (line) {
|
||||||
|
line.reset();
|
||||||
|
});
|
||||||
this.pointSets.forEach(function (pointSet) {
|
this.pointSets.forEach(function (pointSet) {
|
||||||
pointSet.reset();
|
pointSet.reset();
|
||||||
});
|
});
|
||||||
@ -269,6 +302,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.seriesElements.delete(series);
|
this.seriesElements.delete(series);
|
||||||
|
|
||||||
|
this.clearLimitLines(series);
|
||||||
},
|
},
|
||||||
lineForSeries(series) {
|
lineForSeries(series) {
|
||||||
if (series.get('interpolate') === 'linear') {
|
if (series.get('interpolate') === 'linear') {
|
||||||
@ -287,6 +322,14 @@ export default {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
limitLineForSeries(series) {
|
||||||
|
return new MCTChartAlarmLineSet(
|
||||||
|
series,
|
||||||
|
this,
|
||||||
|
this.offset,
|
||||||
|
this.openmct.time.bounds()
|
||||||
|
);
|
||||||
|
},
|
||||||
pointSetForSeries(series) {
|
pointSetForSeries(series) {
|
||||||
if (series.get('markers')) {
|
if (series.get('markers')) {
|
||||||
return new MCTChartPointSet(
|
return new MCTChartPointSet(
|
||||||
@ -308,7 +351,8 @@ export default {
|
|||||||
makeChartElement(series) {
|
makeChartElement(series) {
|
||||||
const elements = {
|
const elements = {
|
||||||
lines: [],
|
lines: [],
|
||||||
pointSets: []
|
pointSets: [],
|
||||||
|
limitLines: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const line = this.lineForSeries(series);
|
const line = this.lineForSeries(series);
|
||||||
@ -330,6 +374,37 @@ export default {
|
|||||||
|
|
||||||
this.seriesElements.set(series, elements);
|
this.seriesElements.set(series, elements);
|
||||||
},
|
},
|
||||||
|
makeLimitLines(series) {
|
||||||
|
this.clearLimitLines(series);
|
||||||
|
|
||||||
|
if (!series.get('limitLines')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const limitElements = {
|
||||||
|
limitLines: []
|
||||||
|
};
|
||||||
|
|
||||||
|
const limitLine = this.limitLineForSeries(series);
|
||||||
|
if (limitLine) {
|
||||||
|
limitElements.limitLines.push(limitLine);
|
||||||
|
this.limitLines.push(limitLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.seriesLimits.set(series, limitElements);
|
||||||
|
},
|
||||||
|
clearLimitLines(series) {
|
||||||
|
const seriesLimits = this.seriesLimits.get(series);
|
||||||
|
|
||||||
|
if (seriesLimits) {
|
||||||
|
seriesLimits.limitLines.forEach(function (line) {
|
||||||
|
this.limitLines.splice(this.limitLines.indexOf(line), 1);
|
||||||
|
line.destroy();
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.seriesLimits.delete(series);
|
||||||
|
}
|
||||||
|
},
|
||||||
canDraw() {
|
canDraw() {
|
||||||
if (!this.offset.x || !this.offset.y) {
|
if (!this.offset.x || !this.offset.y) {
|
||||||
return false;
|
return false;
|
||||||
@ -337,6 +412,10 @@ export default {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
updateLimitsAndDraw() {
|
||||||
|
this.drawLimitLines();
|
||||||
|
this.scheduleDraw();
|
||||||
|
},
|
||||||
scheduleDraw() {
|
scheduleDraw() {
|
||||||
if (!this.drawScheduled) {
|
if (!this.drawScheduled) {
|
||||||
requestAnimationFrame(this.draw);
|
requestAnimationFrame(this.draw);
|
||||||
@ -385,6 +464,97 @@ export default {
|
|||||||
this.pointSets.forEach(this.drawPoints, this);
|
this.pointSets.forEach(this.drawPoints, this);
|
||||||
this.alarmSets.forEach(this.drawAlarmPoints, this);
|
this.alarmSets.forEach(this.drawAlarmPoints, this);
|
||||||
},
|
},
|
||||||
|
drawLimitLines() {
|
||||||
|
if (this.canDraw()) {
|
||||||
|
this.updateViewport();
|
||||||
|
|
||||||
|
if (!this.drawAPI.origin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
|
||||||
|
let limitPointOverlap = [];
|
||||||
|
this.limitLines.forEach((limitLine) => {
|
||||||
|
let limitContainerEl = this.$refs.limitArea;
|
||||||
|
limitLine.limits.forEach((limit) => {
|
||||||
|
const showLabels = this.showLabels(limit.seriesKey);
|
||||||
|
if (showLabels) {
|
||||||
|
const overlap = this.getLimitOverlap(limit, limitPointOverlap);
|
||||||
|
limitPointOverlap.push(overlap);
|
||||||
|
let limitLabelEl = this.getLimitLabel(limit, overlap);
|
||||||
|
limitContainerEl.appendChild(limitLabelEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
let limitEl = this.getLimitElement(limit);
|
||||||
|
limitContainerEl.appendChild(limitEl);
|
||||||
|
|
||||||
|
}, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showLabels(seriesKey) {
|
||||||
|
return this.showLimitLineLabels.seriesKey
|
||||||
|
&& (this.showLimitLineLabels.seriesKey === seriesKey);
|
||||||
|
},
|
||||||
|
getLimitElement(limit) {
|
||||||
|
let point = {
|
||||||
|
left: 0,
|
||||||
|
top: this.drawAPI.y(limit.point.y)
|
||||||
|
};
|
||||||
|
let LimitLineClass = Vue.extend(LimitLine);
|
||||||
|
const component = new LimitLineClass({
|
||||||
|
propsData: {
|
||||||
|
point,
|
||||||
|
limit
|
||||||
|
}
|
||||||
|
});
|
||||||
|
component.$mount();
|
||||||
|
|
||||||
|
return component.$el;
|
||||||
|
},
|
||||||
|
getLimitOverlap(limit, overlapMap) {
|
||||||
|
//calculate if limit lines are too close to each other
|
||||||
|
let limitTop = this.drawAPI.y(limit.point.y);
|
||||||
|
const needsVerticalAdjustment = limitTop - CLEARANCE <= 0;
|
||||||
|
let needsHorizontalAdjustment = false;
|
||||||
|
overlapMap.forEach(value => {
|
||||||
|
let diffTop;
|
||||||
|
if (limitTop > value.overlapTop) {
|
||||||
|
diffTop = limitTop - value.overlapTop;
|
||||||
|
} else {
|
||||||
|
diffTop = value.overlapTop - limitTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
//need to compare +ves to +ves and -ves to -ves
|
||||||
|
if (!needsHorizontalAdjustment
|
||||||
|
&& Math.abs(diffTop) <= CLEARANCE
|
||||||
|
&& value.needsHorizontalAdjustment !== true) {
|
||||||
|
needsHorizontalAdjustment = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
needsHorizontalAdjustment,
|
||||||
|
needsVerticalAdjustment,
|
||||||
|
overlapTop: limitTop
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getLimitLabel(limit, overlap) {
|
||||||
|
let point = {
|
||||||
|
left: 0,
|
||||||
|
top: this.drawAPI.y(limit.point.y)
|
||||||
|
};
|
||||||
|
let LimitLabelClass = Vue.extend(LimitLabel);
|
||||||
|
const component = new LimitLabelClass({
|
||||||
|
propsData: {
|
||||||
|
limit: Object.assign({}, overlap, limit),
|
||||||
|
point
|
||||||
|
}
|
||||||
|
});
|
||||||
|
component.$mount();
|
||||||
|
|
||||||
|
return component.$el;
|
||||||
|
},
|
||||||
drawAlarmPoints(alarmSet) {
|
drawAlarmPoints(alarmSet) {
|
||||||
this.drawAPI.drawLimitPoints(
|
this.drawAPI.drawLimitPoints(
|
||||||
alarmSet.points,
|
alarmSet.points,
|
||||||
@ -401,11 +571,12 @@ export default {
|
|||||||
chartElement.series.get('markerShape')
|
chartElement.series.get('markerShape')
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
drawLine(chartElement) {
|
drawLine(chartElement, disconnected) {
|
||||||
this.drawAPI.drawLine(
|
this.drawAPI.drawLine(
|
||||||
chartElement.getBuffer(),
|
chartElement.getBuffer(),
|
||||||
chartElement.color().asRGBAArray(),
|
chartElement.color().asRGBAArray(),
|
||||||
chartElement.count
|
chartElement.count,
|
||||||
|
disconnected
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
drawHighlights() {
|
drawHighlights() {
|
||||||
|
32
src/plugins/plot/chart/limitUtil.js
Normal file
32
src/plugins/plot/chart/limitUtil.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
export function getLimitClass(limit, prefix) {
|
||||||
|
let cssClass = '';
|
||||||
|
//If color exists then use it, fall back to the cssClass
|
||||||
|
if (limit.color) {
|
||||||
|
cssClass = `${cssClass} ${prefix}${limit.color}`;
|
||||||
|
} else if (limit.cssClass) {
|
||||||
|
cssClass = `${cssClass}${limit.cssClass}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we applied the cssClass then skip these classes
|
||||||
|
if (limit.cssClass === undefined) {
|
||||||
|
if (limit.isUpper) {
|
||||||
|
cssClass = `${cssClass} ${prefix}upr`;
|
||||||
|
} else {
|
||||||
|
cssClass = `${cssClass} ${prefix}lwr`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limit.level) {
|
||||||
|
cssClass = `${cssClass} ${prefix}${limit.level}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limit.needsHorizontalAdjustment) {
|
||||||
|
cssClass = `${cssClass} --align-label-right`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limit.needsVerticalAdjustment) {
|
||||||
|
cssClass = `${cssClass} --align-label-below`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cssClass;
|
||||||
|
}
|
@ -97,7 +97,8 @@ export default class PlotSeries extends Model {
|
|||||||
markers: true,
|
markers: true,
|
||||||
markerShape: 'point',
|
markerShape: 'point',
|
||||||
markerSize: 2.0,
|
markerSize: 2.0,
|
||||||
alarmMarkers: true
|
alarmMarkers: true,
|
||||||
|
limitLines: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,9 +116,26 @@ export default class PlotSeries extends Model {
|
|||||||
this.domainObject = options.domainObject;
|
this.domainObject = options.domainObject;
|
||||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
|
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
|
||||||
|
this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject);
|
||||||
|
this.limits = [];
|
||||||
|
this.limitDefinition.limits().then(response => {
|
||||||
|
this.limits = [];
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
this.limits = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('limits', this);
|
||||||
|
|
||||||
|
});
|
||||||
|
this.openmct.time.on('bounds', this.updateLimits);
|
||||||
this.on('destroy', this.onDestroy, this);
|
this.on('destroy', this.onDestroy, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateLimits(bounds) {
|
||||||
|
this.emit('limitBounds', bounds);
|
||||||
|
}
|
||||||
|
|
||||||
locateOldObject(oldStyleParent) {
|
locateOldObject(oldStyleParent) {
|
||||||
return oldStyleParent.useCapability('composition')
|
return oldStyleParent.useCapability('composition')
|
||||||
.then(function (children) {
|
.then(function (children) {
|
||||||
|
@ -60,6 +60,14 @@
|
|||||||
{{ alarmMarkers ? "Enabled" : "Disabled" }}
|
{{ alarmMarkers ? "Enabled" : "Disabled" }}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Display lines visually denoting alarm limits."
|
||||||
|
>Limit lines</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
{{ limitLines ? "Enabled" : "Disabled" }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
<li class="grid-row">
|
<li class="grid-row">
|
||||||
<div class="grid-cell label"
|
<div class="grid-cell label"
|
||||||
title="The plot line and marker color for this series."
|
title="The plot line and marker color for this series."
|
||||||
@ -135,6 +143,9 @@ export default {
|
|||||||
alarmMarkers() {
|
alarmMarkers() {
|
||||||
return this.series.get('alarmMarkers');
|
return this.series.get('alarmMarkers');
|
||||||
},
|
},
|
||||||
|
limitLines() {
|
||||||
|
return this.series.get('limitLines');
|
||||||
|
},
|
||||||
seriesHexColor() {
|
seriesHexColor() {
|
||||||
return this.series.get('color').asHexString();
|
return this.series.get('color').asHexString();
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,17 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Display limit lines"
|
||||||
|
>Limit lines</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
<input v-model="limitLines"
|
||||||
|
type="checkbox"
|
||||||
|
@change="updateForm('limitLines')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
<li v-show="markers || alarmMarkers"
|
<li v-show="markers || alarmMarkers"
|
||||||
class="grid-row"
|
class="grid-row"
|
||||||
>
|
>
|
||||||
@ -168,6 +179,7 @@ export default {
|
|||||||
markers: this.series.get('markers'),
|
markers: this.series.get('markers'),
|
||||||
markerShape: this.series.get('markerShape'),
|
markerShape: this.series.get('markerShape'),
|
||||||
alarmMarkers: this.series.get('alarmMarkers'),
|
alarmMarkers: this.series.get('alarmMarkers'),
|
||||||
|
limitLines: this.series.get('limitLines'),
|
||||||
markerSize: this.series.get('markerSize'),
|
markerSize: this.series.get('markerSize'),
|
||||||
validation: {},
|
validation: {},
|
||||||
swatchActive: false
|
swatchActive: false
|
||||||
@ -240,6 +252,11 @@ export default {
|
|||||||
modelProp: 'alarmMarkers',
|
modelProp: 'alarmMarkers',
|
||||||
coerce: Boolean,
|
coerce: Boolean,
|
||||||
objectPath: this.dynamicPathForKey('alarmMarkers')
|
objectPath: this.dynamicPathForKey('alarmMarkers')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'limitLines',
|
||||||
|
coerce: Boolean,
|
||||||
|
objectPath: this.dynamicPathForKey('limitLines')
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
:highlights="highlights"
|
:highlights="highlights"
|
||||||
:value-to-show-when-collapsed="legend.get('valueToShowWhenCollapsed')"
|
:value-to-show-when-collapsed="legend.get('valueToShowWhenCollapsed')"
|
||||||
:series-object="seriesObject"
|
:series-object="seriesObject"
|
||||||
:closest="seriesObject.closest"
|
@legendHoverChanged="legendHoverChanged"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- EXPANDED PLOT LEGEND -->
|
<!-- EXPANDED PLOT LEGEND -->
|
||||||
@ -89,6 +89,7 @@
|
|||||||
:series-object="seriesObject"
|
:series-object="seriesObject"
|
||||||
:highlights="highlights"
|
:highlights="highlights"
|
||||||
:legend="legend"
|
:legend="legend"
|
||||||
|
@legendHoverChanged="legendHoverChanged"
|
||||||
/>
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -133,19 +134,36 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLegendHidden: this.legend.get('hideLegendWhenSmall') !== true,
|
isLegendExpanded: this.legend.get('expanded') === true
|
||||||
isLegendExpanded: this.legend.get('expanded') === true,
|
|
||||||
showTimestampWhenExpanded: this.legend.get('showTimestampWhenExpanded') === true,
|
|
||||||
showValueWhenExpanded: this.legend.get('showValueWhenExpanded') === true,
|
|
||||||
showUnitsWhenExpanded: this.legend.get('showUnitsWhenExpanded') === true,
|
|
||||||
showMinimumWhenExpanded: this.legend.get('showMinimumWhenExpanded') === true,
|
|
||||||
showMaximumWhenExpanded: this.legend.get('showMaximumWhenExpanded') === true
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
showUnitsWhenExpanded() {
|
||||||
|
return this.legend.get('showUnitsWhenExpanded') === true;
|
||||||
|
},
|
||||||
|
showMinimumWhenExpanded() {
|
||||||
|
return this.legend.get('showMinimumWhenExpanded') === true;
|
||||||
|
},
|
||||||
|
showMaximumWhenExpanded() {
|
||||||
|
return this.legend.get('showMaximumWhenExpanded') === true;
|
||||||
|
},
|
||||||
|
showValueWhenExpanded() {
|
||||||
|
return this.legend.get('showValueWhenExpanded') === true;
|
||||||
|
},
|
||||||
|
showTimestampWhenExpanded() {
|
||||||
|
return this.legend.get('showTimestampWhenExpanded') === true;
|
||||||
|
},
|
||||||
|
isLegendHidden() {
|
||||||
|
return this.legend.get('hideLegendWhenSmall') === true;
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
expandLegend() {
|
expandLegend() {
|
||||||
this.isLegendExpanded = !this.isLegendExpanded;
|
this.isLegendExpanded = !this.isLegendExpanded;
|
||||||
this.legend.set('expanded', this.isLegendExpanded);
|
this.legend.set('expanded', this.isLegendExpanded);
|
||||||
|
},
|
||||||
|
legendHoverChanged(data) {
|
||||||
|
this.$emit('legendHoverChanged', data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
:class="{
|
:class="{
|
||||||
'is-status--missing': isMissing
|
'is-status--missing': isMissing
|
||||||
}"
|
}"
|
||||||
|
@mouseover="toggleHover(true)"
|
||||||
|
@mouseleave="toggleHover(false)"
|
||||||
>
|
>
|
||||||
<div class="plot-series-swatch-and-name">
|
<div class="plot-series-swatch-and-name">
|
||||||
<span class="plot-series-color-swatch"
|
<span class="plot-series-color-swatch"
|
||||||
@ -47,6 +49,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import {getLimitClass} from "@/plugins/plot/chart/limitUtil";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
valueToShowWhenCollapsed: {
|
valueToShowWhenCollapsed: {
|
||||||
@ -108,7 +112,7 @@ export default {
|
|||||||
if (closest) {
|
if (closest) {
|
||||||
this.formattedYValue = seriesObject.formatY(closest);
|
this.formattedYValue = seriesObject.formatY(closest);
|
||||||
this.formattedXValue = seriesObject.formatX(closest);
|
this.formattedXValue = seriesObject.formatX(closest);
|
||||||
this.mctLimitStateClass = closest.mctLimitState ? `${closest.mctLimitState.cssClass}` : '';
|
this.mctLimitStateClass = closest.mctLimitState ? getLimitClass(closest.mctLimitState, 'c-plot-limit--') : '';
|
||||||
} else {
|
} else {
|
||||||
this.formattedYValue = '';
|
this.formattedYValue = '';
|
||||||
this.formattedXValue = '';
|
this.formattedXValue = '';
|
||||||
@ -121,6 +125,12 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.formattedYValueFromStats = '';
|
this.formattedYValueFromStats = '';
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
toggleHover(hover) {
|
||||||
|
this.hover = hover;
|
||||||
|
this.$emit('legendHoverChanged', {
|
||||||
|
seriesKey: this.hover ? this.seriesObject.keyString : ''
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
:class="{
|
:class="{
|
||||||
'is-status--missing': isMissing
|
'is-status--missing': isMissing
|
||||||
}"
|
}"
|
||||||
|
@mouseover="toggleHover(true)"
|
||||||
|
@mouseleave="toggleHover(false)"
|
||||||
>
|
>
|
||||||
<td class="plot-series-swatch-and-name">
|
<td class="plot-series-swatch-and-name">
|
||||||
<span class="plot-series-color-swatch"
|
<span class="plot-series-color-swatch"
|
||||||
@ -72,6 +74,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {getLimitClass} from "@/plugins/plot/chart/limitUtil";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
seriesObject: {
|
seriesObject: {
|
||||||
@ -94,11 +98,6 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showTimestampWhenExpanded: this.legend.get('showTimestampWhenExpanded'),
|
|
||||||
showValueWhenExpanded: this.legend.get('showValueWhenExpanded'),
|
|
||||||
showUnitsWhenExpanded: this.legend.get('showUnitsWhenExpanded'),
|
|
||||||
showMinimumWhenExpanded: this.legend.get('showMinimumWhenExpanded'),
|
|
||||||
showMaximumWhenExpanded: this.legend.get('showMaximumWhenExpanded'),
|
|
||||||
isMissing: false,
|
isMissing: false,
|
||||||
colorAsHexString: '',
|
colorAsHexString: '',
|
||||||
name: '',
|
name: '',
|
||||||
@ -110,6 +109,23 @@ export default {
|
|||||||
mctLimitStateClass: ''
|
mctLimitStateClass: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
showUnitsWhenExpanded() {
|
||||||
|
return this.legend.get('showUnitsWhenExpanded') === true;
|
||||||
|
},
|
||||||
|
showMinimumWhenExpanded() {
|
||||||
|
return this.legend.get('showMinimumWhenExpanded') === true;
|
||||||
|
},
|
||||||
|
showMaximumWhenExpanded() {
|
||||||
|
return this.legend.get('showMaximumWhenExpanded') === true;
|
||||||
|
},
|
||||||
|
showValueWhenExpanded() {
|
||||||
|
return this.legend.get('showValueWhenExpanded') === true;
|
||||||
|
},
|
||||||
|
showTimestampWhenExpanded() {
|
||||||
|
return this.legend.get('showTimestampWhenExpanded') === true;
|
||||||
|
}
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
highlights(newHighlights) {
|
highlights(newHighlights) {
|
||||||
const highlightedObject = newHighlights.find(highlight => highlight.series.keyString === this.seriesObject.keyString);
|
const highlightedObject = newHighlights.find(highlight => highlight.series.keyString === this.seriesObject.keyString);
|
||||||
@ -133,7 +149,7 @@ export default {
|
|||||||
if (closest) {
|
if (closest) {
|
||||||
this.formattedYValue = seriesObject.formatY(closest);
|
this.formattedYValue = seriesObject.formatY(closest);
|
||||||
this.formattedXValue = seriesObject.formatX(closest);
|
this.formattedXValue = seriesObject.formatX(closest);
|
||||||
this.mctLimitStateClass = seriesObject.closest.mctLimitState ? seriesObject.closest.mctLimitState.cssClass : '';
|
this.mctLimitStateClass = seriesObject.closest.mctLimitState ? getLimitClass(seriesObject.closest.mctLimitState, 'c-plot-limit--') : '';
|
||||||
} else {
|
} else {
|
||||||
this.formattedYValue = '';
|
this.formattedYValue = '';
|
||||||
this.formattedXValue = '';
|
this.formattedXValue = '';
|
||||||
@ -148,6 +164,12 @@ export default {
|
|||||||
this.formattedMinY = '';
|
this.formattedMinY = '';
|
||||||
this.formattedMaxY = '';
|
this.formattedMaxY = '';
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
toggleHover(hover) {
|
||||||
|
this.hover = hover;
|
||||||
|
this.$emit('legendHoverChanged', {
|
||||||
|
seriesKey: this.hover ? this.seriesObject.keyString : ''
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -21,36 +21,36 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
export const COLOR_PALETTE = [
|
export const COLOR_PALETTE = [
|
||||||
[0x20, 0xB2, 0xAA],
|
[0x00, 0x37, 0xFF],
|
||||||
[0x9A, 0xCD, 0x32],
|
[0xF0, 0x60, 0x00],
|
||||||
[0xFF, 0x8C, 0x00],
|
[0x00, 0x70, 0x40],
|
||||||
[0xD2, 0xB4, 0x8C],
|
[0xFB, 0x49, 0x49],
|
||||||
[0x40, 0xE0, 0xD0],
|
[0xC8, 0x00, 0xCF],
|
||||||
[0x41, 0x69, 0xFF],
|
[0x55, 0x77, 0xF2],
|
||||||
[0xFF, 0xD7, 0x00],
|
[0xFF, 0xA6, 0x3D],
|
||||||
[0x6A, 0x5A, 0xCD],
|
[0x05, 0xA3, 0x00],
|
||||||
[0xEE, 0x82, 0xEE],
|
[0xF0, 0x00, 0x6C],
|
||||||
[0xCC, 0x99, 0x66],
|
[0x77, 0x17, 0x7A],
|
||||||
[0x99, 0xCC, 0xCC],
|
[0x23, 0xA9, 0xDB],
|
||||||
[0x66, 0xCC, 0x33],
|
[0xFA, 0xF0, 0x6F],
|
||||||
[0xFF, 0xCC, 0x00],
|
[0x4E, 0xF0, 0x48],
|
||||||
[0xFF, 0x66, 0x33],
|
[0xAD, 0x50, 0x72],
|
||||||
[0xCC, 0x66, 0xFF],
|
[0x94, 0x25, 0xEA],
|
||||||
[0xFF, 0x00, 0x66],
|
[0x21, 0x87, 0x82],
|
||||||
[0xFF, 0xFF, 0x00],
|
[0x8F, 0x6E, 0x47],
|
||||||
[0x80, 0x00, 0x80],
|
[0xf0, 0x59, 0xcb],
|
||||||
[0x00, 0x86, 0x8B],
|
[0x34, 0xB6, 0x7D],
|
||||||
[0x00, 0x8A, 0x00],
|
[0x6A, 0x36, 0xFF],
|
||||||
[0xFF, 0x00, 0x00],
|
[0x56, 0xF0, 0xE8],
|
||||||
[0x00, 0x00, 0xFF],
|
[0xA1, 0x8C, 0x1C],
|
||||||
[0xF5, 0xDE, 0xB3],
|
[0xCB, 0xE1, 0x44],
|
||||||
[0xBC, 0x8F, 0x8F],
|
[0xFF, 0x84, 0x9E],
|
||||||
[0x46, 0x82, 0xB4],
|
[0xB7, 0x79, 0xE7],
|
||||||
[0xFF, 0xAF, 0xAF],
|
[0x8C, 0xC9, 0xFD],
|
||||||
[0x43, 0xCD, 0x80],
|
[0xDB, 0xAA, 0x6E],
|
||||||
[0xCD, 0xC1, 0xC5],
|
[0xB8, 0xDF, 0x97],
|
||||||
[0xA0, 0x52, 0x2D],
|
[0xFF, 0xBC, 0xDA],
|
||||||
[0x64, 0x95, 0xED]
|
[0xD3, 0xB6, 0xDE]
|
||||||
];
|
];
|
||||||
|
|
||||||
export function isDefaultColor(color) {
|
export function isDefaultColor(color) {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user