Compare commits

...

36 Commits

Author SHA1 Message Date
Andrew Henry
f6e6d96f71 Hide date picker if not UTC based time system. Improve Conductor configuration error messages. 2019-01-25 10:55:18 -08:00
Andrew Henry
d34dda62c0 Set time system / bounds immediately based on conductor configuration 2019-01-24 18:42:28 -08:00
Deep Tailor
ac11f898d4 A bunch of fixes for TCR (#2250)
* fix add links by drag and drop

* fix dialog component logging errors

* fix search component errors

* fix sort

* remove unused entrydnd file

* remove unnecessary console logs

* fix preview action in embed dropdown, which was throwing a type error

* invoke PreviewAction once in NotebookController and pass into git add -a

* add navigation capability to embeds, and a bunch of cleanup

* code cleanup and avoid calling dismiss twice on overlay destroy, which was throwing a DOM error. Calling code should dismiss the overlay

* only show elements pool if domainObject has composition

* fix error in inspector when no selection is present

* wire up object view expand button

* listen to composition#remove in TabsView

* make reviewer requested changes

* make reviewer requested changes, and fix for an edge case where removed tab is at the end of the array and currentTab is not set properly

* remove array wrapping dynamic classes in embed.html

* add title='View Large' to expand button

* change model variable to domainObject in tabs.vue

* dismiss top level overlay when esc is pressed (only if overlay is dismissable)

* fix link navigation from embeds

* use positive flag dismissable instead of negative notDismissable for overlays

* make overlays dismissable by default, unless set to false
2019-01-24 16:23:50 -08:00
Pegah Sarram
dd31de6935 Add form to display layout type to allow setting grid size. Also fix generator's form. (#2274) 2019-01-24 16:03:09 -08:00
Pegah Sarram
9e811e722f [Display Layout] support ordering items (#2266)
* Add a toolbar menu for changing the display order of items.

* - Wire up orderItem
- Modify the toolbar API to support function as alternative for property path.
2019-01-23 10:51:29 -08:00
Charles Hacskaylo
8ef53d85c4 R&I Layout editing mods (#2256)
* Layout mods for sub object editing without is-drilled-in
* Remove drilled-in logic
2019-01-22 11:52:56 -08:00
Andrew Henry
abcc5cb023 Implemented go to parent (#2264) 2019-01-17 16:32:12 -08:00
Pegah Sarram
931871ff95 [Display Layout] Snap to grid toolbar toggle button (#2262)
* Add a  button in the toolbar to toggle snapp to grid.

* Convert item's coordinates and size from grid unit to pixels depending on useGrid value.

* Rename selectionListener to removeSelectionListener
2019-01-17 09:24:59 -08:00
Andrew Henry
6b1e8862ef Tables composition error (#2260)
* Removed debubgging statement

* Change order of mutation events so that composition handlers are working with latest object version.

* Remove suppression of mutation event

* Minor code reformatting
2019-01-15 09:55:22 -08:00
Pete Richards
00ce246fc5 Merge pull request #2261 from nasa/object-path-on-drop
Change domain-object event transfer data to domain-object-path
2019-01-15 09:48:32 -08:00
Andrew Henry
c0c7d96429 Change domain-object event transfer data to domain-object-path 2019-01-14 12:07:31 -08:00
Andrew Henry
92b2582d0d Preview instead of navigate in edit mode + highlight navigated object (#2252)
* Preview instead of navigate when in edit mode.

* Prevent preview of navigated object

* Removed trailing comma

* Observe navigation from tree items instead of mct-tree

* Cleanup of redundant code

* .is-selected -> .is-navigated-object
2019-01-11 11:21:52 -08:00
Andrew Henry
4084a1ac86 Drag and drop fixes (#2249)
* Drag-drop edit mode from capture handler. Drag-drop composition from bubble handler. Check composability on drag start

* Show drop hints without being in edit mode.

* Don't serialize objects twice on drag
2019-01-11 11:20:57 -08:00
Charles Hacskaylo
cb1a1c2616 R&I Clock timer fixes (#2254)
* Clocks and timers styling WIP

- Markup, new styles;
- Renamed _legacy-plots.scss > _legacy;
- New $colorBodyFgEm constants - theme files all updated;

* Clocks and timers styling complete

* Styles clean up

- Moved legacy styles out of _global into _legacy.scss;
2018-12-24 14:02:37 -08:00
Deep Tailor
ce6c1f173e Lad table vue (#2231)
* working telemetry subs

* Styling for LAD table

- Fixed markup;
- Corrected icon, added description;
- Expand c-table styles slightly, moved some definitions into
c-telemetry-table;

* add LADTableSummary

* change function names

* make reviewer requested changes

* add lad table headers in sets

* make column widths fixed

* handle composition remove in sets and tables

* finish updateTimeSystem

* add correct support for limits

* LAD Table and Table Set styling

- Removed fixed table layout;
- Col widths set to 33% for now;
- New c-lad-table class and styling;
- New __group-header class added;
- All themes updated with __group-header style colors;

* make reviewer requested changes

* use lodash findIndex
2018-12-20 13:18:22 -08:00
Charles Hacskaylo
30a4888363 R&I Misc UI 3 (#2253)
* Limits-related changes; WIP

- Renamed CSS classes: `s-limit-*` > `is-limit--*`;
- Removed load of legacy _status.scss and added new _status.scss file;
- Minor re-org of limit/status constants in theme files;
- Limit colors in theme constants all updated;

* Fixes for s-selected always displaying the move cursor

- Mod s-selected;
- Reinstitute `.is-moveable` class;

* Layout-related cleanup and refinements

- LineView cleaned up;
- Selection, hover, etc. classes for c-frame cleaned up;
- Constant names normalized, theme files updated;

* Fixes for editing plot options

- Color palette now more flexible;
- Styles for color palette button refined;
- c-input--flex added for more flexible inputs;

* Various swatch-related changes

- Swatch refinements, new .c-click-swatch class;
- Added .c-click-icon--major modifier style, applied in markup;

* Local controls class application and behavior cleanup

- Remmoved .has-local-controls from selected markup;
- Refined CSS selector for better hover behavior;

* Misc UI tweaks

- click-icon--major in Notebook;
- .test mixin improved;

* Update _constants-espresso.scss

* Update _constants-maelstrom.scss
2018-12-20 13:17:44 -08:00
Deep Tailor
b0917a9866 Pending fixes for Grid and List Folder views (#2247)
* fix view names, and persist sort order across list views by persisting in local storage

* use keystring as key

* add openmct namespace to localstorage persisted listview-sort-order to avoid name collision, rename key in composition-loader to objectKeyString
2018-12-20 13:16:23 -08:00
Pegah Sarram
464e5de947 [Display Layout] Add delete button (#2251)
* Add delete button in the toolbar for removing items

* Mutate composition if there are no telemetry objects. Select the parent layout after deleting an item.

* Saving work

* Watch for index in the components and update it in the context.

* Select the parent after a composition is removed.

* Address reviewer's feedback.
- Rename mutatComposition() to removeFromComposition().
- Inline logic for filtering composition
- Use separate branches for each item type in trackItem().

* Address reviewer's requested changes
2018-12-20 13:15:23 -08:00
Pete Richards
47a07da17d Merge pull request #2246 from nasa/conditional-edit-button
Conditional edit button
2018-12-20 09:37:43 -08:00
Charles Hacskaylo
ec4c443299 Misc ui 2 (#2248)
* Create button disabled when editing
* New disabled mixin;
* Tree styling
* Fixed icons for fullscreen toggle button
* Local controls fixed for Imagery and Plots
* Range control styling updated;
* Plot styling, significant mods
* Disclosure controls improved;
* New _legacy-plots.scss file added, no longer loads legacy plot SCSS
files;
* Removed 12px crosshair cursor in legend hover;
* Inspector tree styling in plot options
* Fix z-indexing related to Overlays
2018-12-18 11:07:09 -08:00
Andrew Henry
3122168b0e Removed unused legacy code 2018-12-13 17:11:39 -08:00
Andrew Henry
da3af4b3db Merged from TCR 2018-12-13 15:36:13 -08:00
Pete Richards
850fa28bf6 Update component locations 2018-12-13 09:45:50 -08:00
Pete Richards
270684c5fd Merge pull request #2244 from nasa/component-reorg
Reorganize components, create ObjectFrame
2018-12-13 09:19:40 -08:00
Pete Richards
afa1589cb5 Reorganize components, create ObjectFrame 2018-12-13 09:16:42 -08:00
Deep Tailor
18a94d938f Notebook Snapshot and Annotate (#2240)
Notebook Snapshots and Annotations from new Browse Bar
2018-12-12 18:57:45 -08:00
Andrew Henry
d026bc2134 Show edit only if view is editable. Rename editable to canEdit 2018-12-12 17:17:49 -08:00
Pete Richards
c0b7276787 tree and toolbar fixes (#2241)
[tree] fixes warning from viewControl about improper mutation in child component
[tree] show links in tree
[toolbar] close open menu when clicking on another menu.
2018-12-12 10:58:36 -08:00
Charles Hacskaylo
bb8342f62b Style updates (#2243)
* Create button disabled when editing

- New disabled mixin;
- Selector def for .is-editing .c-create-button;

* Symbolsfont install generation, Elements pool

- Moved font src statement to prevent genning of symbolsfont in wrong
directory locations;
- Elements pool styling WIP, todo: hover item styles, class naming;

* Tree styling

- Hover behavior refined;
- Styling for .is-selected state;

* Fixed icons for fullscreen toggle button

* Tree-related styling for is-selected, is-being-edited
2018-12-11 19:29:29 -08:00
Andrew Henry
0d8dad1559 update preview action to use new action registry
* Support category arrays for legacy actions

* Fixed object path listener. Removed old context menus

* Removed old fullscreen action and Screenfull dependency

* Restore confirmation dialog on remove

* Restored tests

* Remove unused legacy policies

* WIP re-implementation of Preview action

* WIP for Charles to take a look at flex issues

* Disable legacy preview action

* Style and markup fixes for new Preview

- LegacyViewProvider modded to wrap legacy elements in .u-contents div;
- Added selector that automatically defines display: contents on
`no-class` divs injected into __object-view;
- Unit tested with legacy and new components (telem tables, plot view,
Notebook, etc.)

* Tweak z-indexes, fix open in new tab action, add 'done' button

* Removed legacy action

* reuse contextMenuDropDown component
2018-12-11 19:25:20 -08:00
Pegah Sarram
c1ef701eb2 merge display layout view config with components, add line view.
* Hacky WIP

* WIP

* check for 'domainobject' in data transfer

* Metadata manager can return default display value

* Refactor subobject and telemetry views to use layout frame

* Use data domainObject

* Get metadata for selected object.

* Don't inject lodash via vue

* move selection to specific layout types

* restore toolbar functionality

* Support creating line

* Add controls for setting x, y, x2 and y2 for lines from the toolbar.

* Initial attempt at resizing lines

* Check for duplicate panel

* line resize handles working

* Get Text and Box elements working.

* Refactor image view to use layout frame

* Fix drill in

* Check for object before accessing the identifier to avoid type error.

* Add inspectable class if item is subobject or telemetry view.

* Delete unused files

* remove unused imports

* Fix typos

* Fix cssClass and objectPath

* object can be undefined so check for it not being undefined before adding a listener.

* Set cssClass when domain object is available

* Get user input for text and image in the toolbar when adding element.
2018-12-11 11:28:16 -08:00
Deep Tailor
c6a181a2e7 Notebook bug fixes from Testathon - 12/4/2018 (#2236)
* fix add links by drag and drop

* fix dialog component logging errors

* fix search component errors

* fix sort

* remove unused entrydnd file

* remove unnecessary console logs
2018-12-07 14:50:14 -08:00
Pegah Sarram
981392ea07 Define metadata when component is created. (#2237) 2018-12-07 10:34:31 -08:00
Charles Hacskaylo
5928a102a6 Theme refactoring (#2234)
* Theme refactoring

- New hero, header and body font mixins added to theme files;
- Mods to body, griditem, frames, browsebar, etc. to use new font
mixins;
- Moved symbolsfont definition into _constants.scss;

* Tweak - make SubobjectView use header font;

* Add new theme 'maelstrom';

* Add comment

* Remove deprecated .svg and .eot symbol font files
2018-12-07 10:26:54 -08:00
Andrew Henry
c748569433 Address testathon issues relating to context menu (#2235)
* Support category arrays for legacy actions

* Fixed object path listener. Removed old context menus

* Removed old fullscreen action and Screenfull dependency

* Restore confirmation dialog on remove

* Restored tests

* Remove unused legacy policies
2018-12-07 10:15:11 -08:00
Deep Tailor
a87fc51fbb Flexible Layout Refactor (#2223)
* only store identifiers in frames

* move drop hints outside frame

* fix resizing

* fix drag and drop frames

* fix reordering of columns

* multiple improvements

* fix styling for drop hint in empty container

* fix frame reorder in same column

* better drop target show logic

* better frame drop to logic

* fix container reordering

* add uuids for frames and containers to prevent clashing

* toolbar updates

* use shared subobject component to ease styling

* add type cssClass to subobject component header, and delete frame header vue file

* add context menu in subobject views

* change height and width to size for both frames and containers

* remove uneccesary methods from resizeHanfle and inline logic instead

* remove left click logic from context-menu mixin, add a click handler to dropDownContextMenu to show menu

* make a mixin to listen for isEditing

* encapsulate drop hints, and pass allowDrop logic to check if drop hint is valid

* use event.dataTransfer instead of vue events for container reordering

* remove vue events for frame dnd and use html events

* better implementation of toolbar

* remove unused event

* fix container resizing when adding new container

* make reviewer requested changes

* add containerId to event vs having a JSON object

* watch domainObject from flexible layouts and pass down to components

* change domainObject directly on add Container

* update domainObject on change, and cahnge toolBarProvider to function that returns an object

* fix plugin

* set domainObject as data property in felxibleLayouts

* use class instead of inline styles

* Cleanup code

inline this.$el for components that measure their own size
replace snapToPercentage with Math.round
inject object as layoutObject, not dObject.

* reuse sizing logic between frames and containers

* clean up handlers

split handlers for createFrame and moveFrame events in container.
reorganize methods for each to clarify how they operate.

* ObjectView only stops propagation when it handles event

* use ids in toolbar to ensure correct items are mutated

Because index may change due to drag and drop events,
deleteFrame and deleteContainer should operate using identifiers
instead of index.

Also, generate path for hasFrame using indexes when object is
selected, otherwise hasFrame may refer to the incorrect path.
2018-12-07 09:34:33 -08:00
187 changed files with 5866 additions and 6166 deletions

View File

@@ -37,25 +37,25 @@ define([
},
LIMITS = {
rh: {
cssClass: "s-limit-upr s-limit-red",
cssClass: "is-limit--upr is-limit--red",
low: RED,
high: Number.POSITIVE_INFINITY,
name: "Red High"
},
rl: {
cssClass: "s-limit-lwr s-limit-red",
cssClass: "is-limit--lwr is-limit--red",
high: -RED,
low: Number.NEGATIVE_INFINITY,
name: "Red Low"
},
yh: {
cssClass: "s-limit-upr s-limit-yellow",
cssClass: "is-limit--upr is-limit--yellow",
low: YELLOW,
high: RED,
name: "Yellow High"
},
yl: {
cssClass: "s-limit-lwr s-limit-yellow",
cssClass: "is-limit--lwr is-limit--yellow",
low: -RED,
high: -YELLOW,
name: "Yellow Low"

View File

@@ -43,15 +43,14 @@ define([
form: [
{
name: "State Duration (seconds)",
control: "textfield",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "duration",
required: true,
property: [
"telemetry",
"duration"
],
pattern: "^\\d*(\\.\\d*)?$"
]
}
],
initialize: function (object) {

View File

@@ -36,7 +36,9 @@
<body>
</body>
<script>
var THIRTY_MINUTES = 30 * 60 * 1000;
const FIVE_MINUTES = 5 * 60 * 1000;
const THIRTY_MINUTES = 30 * 60 * 1000;
[
'example/eventGenerator',
'example/styleguide'
@@ -68,8 +70,8 @@
timeSystem: 'utc',
clock: 'local',
clockOffsets: {
start: -25 * 60 * 1000,
end: 5 * 60 * 1000
start: - THIRTY_MINUTES,
end: FIVE_MINUTES
}
}
]
@@ -79,206 +81,7 @@
openmct.install(openmct.plugins.FolderView());
openmct.install(openmct.plugins.Tabs());
openmct.install(openmct.plugins.FlexibleLayout());
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
openmct.time.timeSystem('utc');
openmct.install(openmct.plugins.LADTable());
openmct.start();
// openmct.toolbars.addProvider({
// name: "Testing Toolbar",
// key: "testing",
// description: "a mock toolbar that exercises all controls",
// forSelection: function (selection) {
// return true; // always applies.
// },
// toolbar: function (selection) {
// return [
// {
// control: 'menu',
// icon: 'icon-plus',
// label: 'Add',
// options: [
// { name: 'Box', class: 'icon-box', title: 'Add Box' },
// { name: 'Line', class: 'icon-line-horz', title: 'Add Line' },
// { name: 'Text', class: 'icon-font', title: 'Add Text' },
// { name: 'Image', class: 'icon-image', title: 'Add Image' }
// ]
// },
// {
// control: 'separator'
// },
// {
// control: 'color-picker',
// icon: 'icon-paint-bucket',
// value: '#33ff00',
// },
// {
// control: 'color-picker',
// icon: 'icon-pencil',
// value: '#ffffff',
// },
// {
// control: 'color-picker',
// icon: 'icon-font',
// value: '#333333',
// },
//
// {
// control: 'separator'
// },
// {
// control: 'select-menu',
// value: 11,
// options: [
// { value: 9, name: '9 px' },
// { value: 10, name: '10 px' },
// { value: 11, name: '11 px' },
// { value: 12, name: '12 px' },
// { value: 13, name: '13 px' },
// { value: 14, name: '14 px' },
// { value: 16, name: '16 px' },
// { value: 18, name: '18 px' },
// { value: 20, name: '20 px' },
// { value: 24, name: '24 px' },
// { value: 28, name: '28 px' },
// { value: 32, name: '32 px' },
// { value: 40, name: '40 px' },
// { value: 48, name: '48 px' },
// { value: 56, name: '56 px' },
// { value: 64, name: '64 px' },
// { value: 72, name: '72 px' },
// { value: 80, name: '80 px' },
// { value: 88, name: '88 px' },
// { value: 96, name: '96 px' },
// { value: 128, name: '128 px' },
// { value: 160, name: '160 px' }
// ]
// },
//
// {
// control: 'separator'
// },
// {
// control: 'menu',
// icon: 'icon-layers',
// options: [
// { name: 'Move to top', class: 'icon-arrow-double-up', title: 'Move to top' },
// { name: 'Move up', class: 'icon-arrow-up', title: 'Move up' },
// { name: 'Move down', class: 'icon-arrow-down', title: 'Move down' },
// { name: 'Move to bottom', class: 'icon-arrow-double-down', title: 'Move to bottom' }
// ]
// },
//
// {
// control: 'separator'
// },
// {
// control: 'button',
// icon: 'icon-gear'
// },
//
// {
// control: 'separator'
// },
// {
// control: 'input',
// type: 'number',
// label: 'X',
// value: 1,
// title: 'X position'
// },
// {
// control: 'input',
// type: 'number',
// label: 'Y',
// value: 2,
// title: 'Y position'
// },
// {
// control: 'input',
// type: 'number',
// label: 'W',
// value: 3,
// title: 'Width'
// },
// {
// control: 'input',
// type: 'number',
// label: 'H',
// value: 4,
// title: 'Height'
// },
//
// {
// control: 'separator'
// },
// {
// control: 'button',
// icon: 'icon-trash',
// label: 'delete',
// modifier: 'caution'
// },
//
// {
// control: 'separator'
// },
// {
// control: 'checkbox',
// name: 'this is a checkbox',
// },
// {
// control: 'separator'
// },
// {
// control: 'toggle-button',
// title: 'Toggle Frame',
// property: 'hideFrame',
// value: false,
// options: [
// {
// value: true,
// icon: 'icon-frame-hide'
// },
// {
// value: false,
// icon: 'icon-frame-show'
// }
// ]
// },
// {
// control: 'toggle-button',
// title: 'Snap to grid',
// property: 'snapToGrid',
// value: true,
// options: [
// {
// value: true,
// icon: 'icon-grid-snap-to'
// },
// {
// value: false,
// icon: 'icon-grid-snap-no'
// }
// ]
// },
// {
// control: 'toggle-button',
// title: 'Toggle label',
// property: 'showLabel',
// value: true,
// options: [
// {
// value: true,
// icon: 'icon-two-parts-both'
// },
// {
// value: false,
// icon: 'icon-two-parts-one-only'
// }
// ]
// }
// ];
// }
// });
</script>
</html>

View File

@@ -58,7 +58,6 @@
"printj": "^1.1.0",
"raw-loader": "^0.5.1",
"request": "^2.69.0",
"screenfull": "^3.3.2",
"split": "^1.0.0",
"style-loader": "^0.21.0",
"v8-compile-cache": "^1.1.0",

View File

@@ -31,7 +31,6 @@ define([
"./src/navigation/NavigateAction",
"./src/navigation/OrphanNavigationHandler",
"./src/windowing/NewTabAction",
"./src/windowing/FullscreenAction",
"./src/windowing/WindowTitler",
"./res/templates/browse.html",
"./res/templates/browse-object.html",
@@ -53,7 +52,6 @@ define([
NavigateAction,
OrphanNavigationHandler,
NewTabAction,
FullscreenAction,
WindowTitler,
browseTemplate,
browseObjectTemplate,
@@ -225,13 +223,6 @@ define([
"group": "windowing",
"cssClass": "icon-new-window",
"priority": "preferred"
},
{
"key": "fullscreen",
"implementation": FullscreenAction,
"category": "view-control",
"group": "windowing",
"priority": "default"
}
],
"runs": [
@@ -265,18 +256,6 @@ define([
key: "inspectorRegion",
template: inspectorRegionTemplate
}
],
"licenses": [
{
"name": "screenfull.js",
"version": "1.2.0",
"description": "Wrapper for cross-browser usage of fullscreen API",
"author": "Sindre Sorhus",
"website": "https://github.com/sindresorhus/screenfull.js/",
"copyright": "Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)",
"license": "license-mit",
"link": "https://github.com/sindresorhus/screenfull.js/blob/gh-pages/license"
}
]
}
});

View File

@@ -1,64 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
/**
* Module defining FullscreenAction. Created by vwoeltje on 11/18/14.
*/
define(
["screenfull"],
function (screenfull) {
var ENTER_FULLSCREEN = "Enter full screen mode",
EXIT_FULLSCREEN = "Exit full screen mode";
/**
* The fullscreen action toggles between fullscreen display
* and regular in-window display.
* @memberof platform/commonUI/browse
* @constructor
* @implements {Action}
*/
function FullscreenAction(context) {
this.context = context;
}
FullscreenAction.prototype.perform = function () {
screenfull.toggle();
};
FullscreenAction.prototype.getMetadata = function () {
// We override getMetadata, because the icon cssClass and
// description need to be determined at run-time
// based on whether or not we are currently
// full screen.
var metadata = Object.create(FullscreenAction);
metadata.cssClass = screenfull.isFullscreen ? "icon-fullscreen-expand" : "icon-fullscreen-collapse";
metadata.description = screenfull.isFullscreen ?
EXIT_FULLSCREEN : ENTER_FULLSCREEN;
metadata.group = "windowing";
metadata.context = this.context;
return metadata;
};
return FullscreenAction;
}
);

View File

@@ -1,59 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
/**
* MCTRepresentationSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../../src/windowing/FullscreenAction", "screenfull"],
function (FullscreenAction, screenfull) {
describe("The fullscreen action", function () {
var action,
oldToggle;
beforeEach(function () {
// Screenfull is not shimmed or injected, so
// we need to spy on it in the global scope.
oldToggle = screenfull.toggle;
screenfull.toggle = jasmine.createSpy("toggle");
action = new FullscreenAction({});
});
afterEach(function () {
screenfull.toggle = oldToggle;
});
it("toggles fullscreen mode when performed", function () {
action.perform();
expect(screenfull.toggle).toHaveBeenCalled();
});
it("provides displayable metadata", function () {
expect(action.getMetadata().cssClass).toBeDefined();
});
});
}
);

View File

@@ -95,7 +95,10 @@ define(
// Create the overlay element and add it to the document's body
element = this.$compile(TEMPLATE)(scope);
this.findBody().prepend(element);
// Append so that most recent dialog is last in DOM. This means the most recent dialog will be on top when
// multiple overlays with the same z-index are active.
this.findBody().append(element);
return {
dismiss: dismiss

View File

@@ -32,11 +32,7 @@ define([
"./src/actions/SaveAndStopEditingAction",
"./src/actions/SaveAsAction",
"./src/actions/CancelAction",
"./src/policies/EditActionPolicy",
"./src/policies/EditPersistableObjectsPolicy",
"./src/policies/EditableLinkPolicy",
"./src/policies/EditableMovePolicy",
"./src/policies/EditContextualActionPolicy",
"./src/representers/EditRepresenter",
"./src/capabilities/EditorCapability",
"./src/capabilities/TransactionCapabilityDecorator",
@@ -67,11 +63,7 @@ define([
SaveAndStopEditingAction,
SaveAsAction,
CancelAction,
EditActionPolicy,
EditPersistableObjectsPolicy,
EditableLinkPolicy,
EditableMovePolicy,
EditContextualActionPolicy,
EditRepresenter,
EditorCapability,
TransactionCapabilityDecorator,
@@ -174,7 +166,7 @@ define([
"name": "Remove",
"description": "Remove this object from its containing object.",
"depends": [
"dialogService",
"openmct",
"navigationService"
]
},
@@ -231,28 +223,11 @@ define([
}
],
"policies": [
{
"category": "action",
"implementation": EditActionPolicy
},
{
"category": "action",
"implementation": EditPersistableObjectsPolicy,
"depends": ["openmct"]
},
{
"category": "action",
"implementation": EditContextualActionPolicy,
"depends": ["navigationService", "editModeBlacklist", "nonEditContextBlacklist"]
},
{
"category": "action",
"implementation": EditableMovePolicy
},
{
"category": "action",
"implementation": EditableLinkPolicy
},
{
"implementation": CreationPolicy,
"category": "creation"
@@ -349,16 +324,6 @@ define([
]
}
],
"constants": [
{
"key": "editModeBlacklist",
"value": ["copy", "follow", "link", "locate"]
},
{
"key": "nonEditContextBlacklist",
"value": ["copy", "follow", "properties", "move", "link", "remove", "locate"]
}
],
"capabilities": [
{
"key": "editor",

View File

@@ -42,9 +42,9 @@ define([
* @constructor
* @implements {Action}
*/
function RemoveAction(dialogService, navigationService, context) {
function RemoveAction(openmct, navigationService, context) {
this.domainObject = (context || {}).domainObject;
this.dialogService = dialogService;
this.openmct = openmct;
this.navigationService = navigationService;
}
@@ -53,7 +53,6 @@ define([
*/
RemoveAction.prototype.perform = function () {
var dialog,
dialogService = this.dialogService,
domainObject = this.domainObject,
navigationService = this.navigationService;
/*
@@ -104,13 +103,13 @@ define([
* capability. Based on object's location and selected object's location
* user may be navigated to existing parent object
*/
function removeFromContext(object) {
var contextCapability = object.getCapability('context'),
function removeFromContext() {
var contextCapability = domainObject.getCapability('context'),
parent = contextCapability.getParent();
// If currently within path of removed object(s),
// navigates to existing object up tree
checkObjectNavigation(object, parent);
checkObjectNavigation(domainObject, parent);
return parent.useCapability('mutation', doMutate);
}
@@ -119,7 +118,7 @@ define([
* Pass in the function to remove the domain object so it can be
* associated with an 'OK' button press
*/
dialog = new RemoveDialog(dialogService, domainObject, removeFromContext);
dialog = new RemoveDialog(this.openmct, domainObject, removeFromContext);
dialog.show();
};

View File

@@ -36,8 +36,8 @@ define([], function () {
* @memberof platform/commonUI/edit
* @constructor
*/
function RemoveDialog(dialogService, domainObject, removeCallback) {
this.dialogService = dialogService;
function RemoveDialog(openmct, domainObject, removeCallback) {
this.openmct = openmct;
this.domainObject = domainObject;
this.removeCallback = removeCallback;
}
@@ -46,31 +46,26 @@ define([], function () {
* Display a dialog to confirm the removal of a domain object.
*/
RemoveDialog.prototype.show = function () {
var dialog,
domainObject = this.domainObject,
removeCallback = this.removeCallback,
model = {
title: 'Remove ' + domainObject.getModel().name,
actionText: 'Warning! This action will permanently remove this object. Are you sure you want to continue?',
severity: 'alert',
primaryOption: {
let dialog = this.openmct.overlays.dialog({
title: 'Remove ' + this.domainObject.getModel().name,
iconClass: 'alert',
message: 'Warning! This action will permanently remove this object. Are you sure you want to continue?',
buttons: [
{
label: 'OK',
callback: function () {
removeCallback(domainObject);
callback: () => {
this.removeCallback();
dialog.dismiss();
}
},
options: [
{
label: 'Cancel',
callback: function () {
dialog.dismiss();
}
{
label: 'Cancel',
callback: () => {
dialog.dismiss();
}
]
};
setTimeout(() => this.removeCallback(domainObject));
}
]
});
};
return RemoveDialog;

View File

@@ -1,111 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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(
[],
function () {
/**
* Policy controlling when the `edit` and/or `properties` actions
* can appear as applicable actions of the `view-control` category
* (shown as buttons in the top-right of browse mode.)
* @memberof platform/commonUI/edit
* @constructor
* @implements {Policy.<Action, ActionContext>}
*/
function EditActionPolicy(policyService) {
this.policyService = policyService;
}
/**
* Get a count of views which are not flagged as non-editable.
* @private
*/
EditActionPolicy.prototype.countEditableViews = function (context) {
var domainObject = context.domainObject,
count = 0,
type, views;
if (!domainObject) {
return count;
}
type = domainObject.getCapability('type');
views = domainObject.useCapability('view');
// A view is editable unless explicitly flagged as not
(views || []).forEach(function (view) {
if (isEditable(view) ||
(view.key === 'plot' && type.getKey() === 'telemetry.panel') ||
(view.key === 'table' && type.getKey() === 'table') ||
(view.key === 'rt-table' && type.getKey() === 'rttable')
) {
count++;
}
});
function isEditable(view) {
if (typeof view.editable === Function) {
return view.editable(domainObject.useCapability('adapter'));
} else {
return view.editable === true;
}
}
return count;
};
/**
* Checks whether the domain object is currently being edited. If
* so, the edit action is not applicable.
* @param context
* @returns {*|boolean}
*/
function isEditing(context) {
var domainObject = (context || {}).domainObject;
return domainObject &&
domainObject.hasCapability('editor') &&
domainObject.getCapability('editor').isEditContextRoot();
}
EditActionPolicy.prototype.allow = function (action, context) {
var key = action.getMetadata().key,
category = (context || {}).category;
// Restrict 'edit' to cases where there are editable
// views (similarly, restrict 'properties' to when
// the converse is true), and where the domain object is not
// already being edited.
if (key === 'edit') {
return this.countEditableViews(context) > 0 && !isEditing(context);
} else if (key === 'properties' && category === 'view-control') {
return this.countEditableViews(context) < 1 && !isEditing(context);
}
// Like all policies, allow by default.
return true;
};
return EditActionPolicy;
}
);

View File

@@ -1,75 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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(
[],
function () {
/**
* Policy controlling whether the context menu is visible when
* objects are being edited
* @param navigationService
* @param editModeBlacklist A blacklist of actions disallowed from
* context menu when navigated object is being edited
* @param nonEditContextBlacklist A blacklist of actions disallowed
* from context menu of non-editable objects, when navigated object
* is being edited
* @constructor
* @param editModeBlacklist A blacklist of actions disallowed from
* context menu when navigated object is being edited
* @param nonEditContextBlacklist A blacklist of actions disallowed
* from context menu of non-editable objects, when navigated object
* @implements {Policy.<Action, ActionContext>}
*/
function EditContextualActionPolicy(navigationService, editModeBlacklist, nonEditContextBlacklist) {
this.navigationService = navigationService;
//The list of objects disallowed on target object when in edit mode
this.editModeBlacklist = editModeBlacklist;
//The list of objects disallowed on target object that is not in
// edit mode (ie. the context menu in the tree on the LHS).
this.nonEditContextBlacklist = nonEditContextBlacklist;
}
EditContextualActionPolicy.prototype.allow = function (action, context) {
var selectedObject = context.domainObject,
navigatedObject = this.navigationService.getNavigation(),
actionMetadata = action.getMetadata ? action.getMetadata() : {};
// FIXME: need to restore support for changing contextual actions
// based on edit mode.
// if (navigatedObject.hasCapability("editor") && navigatedObject.getCapability("editor").isEditContextRoot()) {
// if (selectedObject.hasCapability("editor") && selectedObject.getCapability("editor").inEditContext()) {
// return this.editModeBlacklist.indexOf(actionMetadata.key) === -1;
// } else {
// //Target is in the context menu
// return this.nonEditContextBlacklist.indexOf(actionMetadata.key) === -1;
// }
// } else {
// return true;
// }
return true;
};
return EditContextualActionPolicy;
}
);

View File

@@ -1,51 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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([], function () {
/**
* Policy suppressing links when the linked-to domain object is in
* edit mode. Domain objects being edited may not have been persisted,
* so creating links to these can result in inconsistent state.
*
* @memberof platform/commonUI/edit
* @constructor
* @implements {Policy.<View, DomainObject>}
*/
function EditableLinkPolicy() {
}
EditableLinkPolicy.prototype.allow = function (action, context) {
var key = action.getMetadata().key,
object;
if (key === 'link') {
object = context.selectedObject || context.domainObject;
return !(object.hasCapability("editor") && object.getCapability("editor").inEditContext());
}
// Like all policies, allow by default.
return true;
};
return EditableLinkPolicy;
});

View File

@@ -1,52 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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([], function () {
/**
* Policy suppressing move actions among editable and non-editable
* domain objects.
* @memberof platform/commonUI/edit
* @constructor
* @implements {Policy.<View, DomainObject>}
*/
function EditableMovePolicy() {
}
EditableMovePolicy.prototype.allow = function (action, context) {
var domainObject = context.domainObject,
selectedObject = context.selectedObject,
key = action.getMetadata().key,
isDomainObjectEditing = domainObject.hasCapability('editor') &&
domainObject.getCapability('editor').inEditContext();
if (key === 'move' && isDomainObjectEditing) {
return !!selectedObject && selectedObject.hasCapability('editor') &&
selectedObject.getCapability('editor').inEditContext();
}
// Like all policies, allow by default.
return true;
};
return EditableMovePolicy;
});

View File

@@ -1,49 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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(
[],
function () {
/**
* Policy controlling which views should be visible in Edit mode.
* @memberof platform/commonUI/edit
* @constructor
* @implements {Policy.<View, DomainObject>}
*/
function EditableViewPolicy() {
}
EditableViewPolicy.prototype.allow = function (view, domainObject) {
// If a view is flagged as non-editable, only allow it
// while we're not in Edit mode.
if ((view || {}).editable === false) {
return !(domainObject.hasCapability('editor') && domainObject.getCapability('editor').inEditContext());
}
// Like all policies, allow by default.
return true;
};
return EditableViewPolicy;
}
);

View File

@@ -29,7 +29,7 @@ define(
actionContext,
capabilities,
mockContext,
mockDialogService,
mockOverlayAPI,
mockDomainObject,
mockMutation,
mockNavigationService,
@@ -68,9 +68,9 @@ define(
}
};
mockDialogService = jasmine.createSpyObj(
"dialogService",
["showBlockingMessage"]
mockOverlayAPI = jasmine.createSpyObj(
"overlayAPI",
["dialog"]
);
mockNavigationService = jasmine.createSpyObj(
@@ -96,7 +96,7 @@ define(
actionContext = { domainObject: mockDomainObject };
action = new RemoveAction(mockDialogService, mockNavigationService, actionContext);
action = new RemoveAction({overlays: mockOverlayAPI}, mockNavigationService, actionContext);
});
it("only applies to objects with parents", function () {
@@ -118,7 +118,7 @@ define(
action.perform();
expect(mockDialogService.showBlockingMessage).toHaveBeenCalled();
expect(mockOverlayAPI.dialog).toHaveBeenCalled();
// Also check that no mutation happens at this point
expect(mockParent.useCapability).not.toHaveBeenCalledWith("mutation", jasmine.any(Function));
@@ -158,13 +158,13 @@ define(
mockGrandchildContext = jasmine.createSpyObj("context", ["getParent"]);
mockRootContext = jasmine.createSpyObj("context", ["getParent"]);
mockDialogService.showBlockingMessage.and.returnValue(mockDialogHandle);
mockOverlayAPI.dialog.and.returnValue(mockDialogHandle);
});
it("mutates the parent when performed", function () {
action.perform();
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
.primaryOption.callback();
mockOverlayAPI.dialog.calls.mostRecent().args[0]
.buttons[0].callback();
expect(mockMutation.invoke)
.toHaveBeenCalledWith(jasmine.any(Function));
@@ -174,8 +174,8 @@ define(
var mutator, result;
action.perform();
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
.primaryOption.callback();
mockOverlayAPI.dialog.calls.mostRecent().args[0]
.buttons[0].callback();
mutator = mockMutation.invoke.calls.mostRecent().args[0];
result = mutator(model);
@@ -212,8 +212,8 @@ define(
mockType.hasFeature.and.returnValue(true);
action.perform();
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
.primaryOption.callback();
mockOverlayAPI.dialog.calls.mostRecent().args[0]
.buttons[0].callback();
// Expects navigation to parent of domainObject (removed object)
expect(mockNavigationService.setNavigation).toHaveBeenCalledWith(mockParent);
@@ -242,8 +242,8 @@ define(
mockType.hasFeature.and.returnValue(true);
action.perform();
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
.primaryOption.callback();
mockOverlayAPI.dialog.calls.mostRecent().args[0]
.buttons[0].callback();
// Expects no navigation to occur
expect(mockNavigationService.setNavigation).not.toHaveBeenCalled();

View File

@@ -1,138 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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/policies/EditActionPolicy"],
function (EditActionPolicy) {
describe("The Edit action policy", function () {
var editableView,
nonEditableView,
testViews,
testContext,
mockDomainObject,
mockEditAction,
mockPropertiesAction,
mockTypeCapability,
mockEditorCapability,
capabilities,
plotView,
policy;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
'domainObject',
[
'useCapability',
'hasCapability',
'getCapability'
]
);
mockEditorCapability = jasmine.createSpyObj('editorCapability', ['isEditContextRoot']);
mockTypeCapability = jasmine.createSpyObj('type', ['getKey']);
capabilities = {
'editor': mockEditorCapability,
'type': mockTypeCapability
};
mockEditAction = jasmine.createSpyObj('edit', ['getMetadata']);
mockPropertiesAction = jasmine.createSpyObj('edit', ['getMetadata']);
mockDomainObject.getCapability.and.callFake(function (capability) {
return capabilities[capability];
});
mockDomainObject.hasCapability.and.callFake(function (capability) {
return !!capabilities[capability];
});
editableView = { editable: true };
nonEditableView = { editable: false };
plotView = { key: "plot", editable: false };
testViews = [];
mockDomainObject.useCapability.and.callFake(function (c) {
// Provide test views, only for the view capability
return c === 'view' && testViews;
});
mockEditAction.getMetadata.and.returnValue({ key: 'edit' });
mockPropertiesAction.getMetadata.and.returnValue({ key: 'properties' });
testContext = {
domainObject: mockDomainObject,
category: 'view-control'
};
policy = new EditActionPolicy();
});
it("allows the edit action when there are editable views", function () {
testViews = [editableView];
expect(policy.allow(mockEditAction, testContext)).toBe(true);
});
it("allows the edit properties action when there are no editable views", function () {
testViews = [nonEditableView, nonEditableView];
expect(policy.allow(mockPropertiesAction, testContext)).toBe(true);
});
it("disallows the edit action when there are no editable views", function () {
testViews = [nonEditableView, nonEditableView];
expect(policy.allow(mockEditAction, testContext)).toBe(false);
});
it("disallows the edit properties action when there are" +
" editable views", function () {
testViews = [editableView];
expect(policy.allow(mockPropertiesAction, testContext)).toBe(false);
});
it("disallows the edit action when object is already being" +
" edited", function () {
testViews = [editableView];
mockEditorCapability.isEditContextRoot.and.returnValue(true);
expect(policy.allow(mockEditAction, testContext)).toBe(false);
});
it("allows editing of panels in plot view", function () {
testViews = [plotView];
mockTypeCapability.getKey.and.returnValue('telemetry.panel');
expect(policy.allow(mockEditAction, testContext)).toBe(true);
});
it("disallows editing of plot view when object not a panel type", function () {
testViews = [plotView];
mockTypeCapability.getKey.and.returnValue('something.else');
expect(policy.allow(mockEditAction, testContext)).toBe(false);
});
it("allows the edit properties outside of the 'view-control' category", function () {
testViews = [nonEditableView];
testContext.category = "something-else";
expect(policy.allow(mockPropertiesAction, testContext)).toBe(true);
});
});
}
);

View File

@@ -1,120 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
/*global describe,it,expect,beforeEach,jasmine*/
define(
["../../src/policies/EditContextualActionPolicy"],
function (EditContextualActionPolicy) {
describe("The Edit contextual action policy", function () {
var policy,
navigationService,
mockAction,
context,
navigatedObject,
mockDomainObject,
mockEditorCapability,
metadata,
editModeBlacklist = ["copy", "follow", "window", "link", "locate"],
nonEditContextBlacklist = ["copy", "follow", "properties", "move", "link", "remove", "locate"];
beforeEach(function () {
mockEditorCapability = jasmine.createSpyObj("editorCapability", ["isEditContextRoot", "inEditContext"]);
navigatedObject = jasmine.createSpyObj("navigatedObject", ["hasCapability", "getCapability"]);
navigatedObject.getCapability.and.returnValue(mockEditorCapability);
navigatedObject.hasCapability.and.returnValue(false);
mockDomainObject = jasmine.createSpyObj("domainObject", ["hasCapability", "getCapability"]);
mockDomainObject.hasCapability.and.returnValue(false);
mockDomainObject.getCapability.and.returnValue(mockEditorCapability);
navigationService = jasmine.createSpyObj("navigationService", ["getNavigation"]);
navigationService.getNavigation.and.returnValue(navigatedObject);
metadata = {key: "move"};
mockAction = jasmine.createSpyObj("action", ["getMetadata"]);
mockAction.getMetadata.and.returnValue(metadata);
context = {domainObject: mockDomainObject};
policy = new EditContextualActionPolicy(navigationService, editModeBlacklist, nonEditContextBlacklist);
});
it('Allows all actions when navigated object not in edit mode', function () {
expect(policy.allow(mockAction, context)).toBe(true);
});
it('Allows "window" action when navigated object in edit mode,' +
' but selected object not in edit mode ', function () {
navigatedObject.hasCapability.and.returnValue(true);
mockEditorCapability.isEditContextRoot.and.returnValue(true);
metadata.key = "window";
expect(policy.allow(mockAction, context)).toBe(true);
});
it('Allows "remove" action when navigated object in edit mode,' +
' and selected object not editable, but its parent is.',
function () {
var mockParent = jasmine.createSpyObj("parentObject", ["hasCapability"]),
mockContextCapability = jasmine.createSpyObj("contextCapability", ["getParent"]);
mockParent.hasCapability.and.returnValue(true);
mockContextCapability.getParent.and.returnValue(mockParent);
navigatedObject.hasCapability.and.returnValue(true);
mockDomainObject.getCapability.and.returnValue(mockContextCapability);
mockDomainObject.hasCapability.and.callFake(function (capability) {
switch (capability) {
case "editor": return false;
case "context": return true;
}
});
metadata.key = "remove";
expect(policy.allow(mockAction, context)).toBe(true);
});
it('Disallows "move" action when navigated object in edit mode,' +
' but selected object not in edit mode ', function () {
navigatedObject.hasCapability.and.returnValue(true);
mockEditorCapability.isEditContextRoot.and.returnValue(true);
mockEditorCapability.inEditContext.and.returnValue(false);
metadata.key = "move";
expect(policy.allow(mockAction, context)).toBe(false);
});
it('Disallows copy action when navigated object and' +
' selected object in edit mode', function () {
navigatedObject.hasCapability.and.returnValue(true);
mockDomainObject.hasCapability.and.returnValue(true);
mockEditorCapability.isEditContextRoot.and.returnValue(true);
mockEditorCapability.inEditContext.and.returnValue(true);
metadata.key = "copy";
expect(policy.allow(mockAction, context)).toBe(false);
});
});
}
);

View File

@@ -1,79 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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/policies/EditableViewPolicy"],
function (EditableViewPolicy) {
describe("The editable view policy", function () {
var mockDomainObject,
testMode,
policy;
beforeEach(function () {
testMode = true; // Act as if we're in Edit mode by default
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['hasCapability', 'getCapability']
);
mockDomainObject.getCapability.and.returnValue({
inEditContext: function () {
return true;
}
});
mockDomainObject.hasCapability.and.callFake(function (c) {
return (c === 'editor') && testMode;
});
policy = new EditableViewPolicy();
});
it("disallows views in edit mode that are flagged as non-editable", function () {
expect(policy.allow({ editable: false }, mockDomainObject))
.toBeFalsy();
});
it("allows views in edit mode that are flagged as editable", function () {
expect(policy.allow({ editable: true }, mockDomainObject))
.toBeTruthy();
});
it("allows any view outside of edit mode", function () {
var testViews = [
{ editable: false },
{ editable: true },
{ someKey: "some value" }
];
testMode = false; // Act as if we're not in Edit mode
testViews.forEach(function (testView) {
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
});
});
it("treats views with no defined 'editable' property as editable", function () {
expect(policy.allow({ someKey: "some value" }, mockDomainObject))
.toBeTruthy();
});
});
}
);

View File

@@ -31,7 +31,6 @@ define([
"./src/controllers/TreeNodeController",
"./src/controllers/ActionGroupController",
"./src/controllers/ToggleController",
"./src/controllers/ContextMenuController",
"./src/controllers/ClickAwayController",
"./src/controllers/ViewSwitcherController",
"./src/controllers/GetterSetterController",
@@ -49,8 +48,6 @@ define([
"./src/directives/MCTSplitter",
"./src/directives/MCTTree",
"./src/directives/MCTIndicators",
"./src/directives/MCTPreview",
"./src/actions/MCTPreviewAction",
"./src/filters/ReverseFilter",
"./res/templates/bottombar.html",
"./res/templates/controls/action-button.html",
@@ -65,13 +62,11 @@ define([
"./res/templates/tree-node.html",
"./res/templates/label.html",
"./res/templates/controls/action-group.html",
"./res/templates/menu/context-menu.html",
"./res/templates/controls/switcher.html",
"./res/templates/object-inspector.html",
"./res/templates/controls/selector.html",
"./res/templates/controls/datetime-picker.html",
"./res/templates/controls/datetime-field.html",
"./res/templates/preview.html",
'legacyRegistry'
], function (
UrlService,
@@ -84,7 +79,6 @@ define([
TreeNodeController,
ActionGroupController,
ToggleController,
ContextMenuController,
ClickAwayController,
ViewSwitcherController,
GetterSetterController,
@@ -102,8 +96,6 @@ define([
MCTSplitter,
MCTTree,
MCTIndicators,
MCTPreview,
MCTPreviewAction,
ReverseFilter,
bottombarTemplate,
actionButtonTemplate,
@@ -118,13 +110,11 @@ define([
treeNodeTemplate,
labelTemplate,
actionGroupTemplate,
contextMenuTemplate,
switcherTemplate,
objectInspectorTemplate,
selectorTemplate,
datetimePickerTemplate,
datetimeFieldTemplate,
previewTemplate,
legacyRegistry
) {
@@ -252,13 +242,6 @@ define([
"key": "ToggleController",
"implementation": ToggleController
},
{
"key": "ContextMenuController",
"implementation": ContextMenuController,
"depends": [
"$scope"
]
},
{
"key": "ClickAwayController",
"implementation": ClickAwayController,
@@ -394,31 +377,6 @@ define([
"key": "mctIndicators",
"implementation": MCTIndicators,
"depends": ['openmct']
},
{
"key": "mctPreview",
"implementation": MCTPreview,
"depends": [
"$document"
]
}
],
"actions": [
{
"key": "mct-preview-action",
"implementation": MCTPreviewAction,
"name": "Preview",
"cssClass": "hide-in-t-main-view icon-eye-open",
"description": "Preview in large dialog",
"category": [
"contextual",
"view-control"
],
"depends": [
"$compile",
"$rootScope"
],
"priority": "preferred"
}
],
"constants": [
@@ -517,13 +475,6 @@ define([
"action"
]
},
{
"key": "context-menu",
"template": contextMenuTemplate,
"uses": [
"action"
]
},
{
"key": "switcher",
"template": switcherTemplate,
@@ -534,10 +485,6 @@ define([
{
"key": "object-inspector",
"template": objectInspectorTemplate
},
{
"key": "mct-preview",
"template": previewTemplate
}
],
"controls": [

View File

@@ -1,45 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2017, 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.
-->
<div class="t-frame-inner abs t-object-type-{{ domainObject.getModel().type }}" mct-preview>
<div class="abs object-browse-bar l-flex-row">
<div class="left flex-elem l-flex-row grows">
<mct-representation
key="'object-header-frame'"
mct-object="domainObject"
class="l-flex-row flex-elem object-header grows">
</mct-representation>
</div>
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
<mct-representation
key="'switcher'"
ng-model="representation"
mct-object="domainObject">
</mct-representation>
</div>
</div>
<div class="abs object-holder">
<mct-representation
key="representation.selected.key"
mct-object="representation.selected.key && domainObject">
</mct-representation>
</div>
</div>

View File

@@ -1,55 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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(
[],
function () {
var PREVIEW_TEMPLATE = '<mct-representation key="\'mct-preview\'"' +
'class="t-rep-frame holder"' +
'mct-object="domainObject">' +
'</mct-representation>';
function MCTPreviewAction($compile, $rootScope, context) {
context = context || {};
this.domainObject = context.selectedObject || context.domainObject;
this.$rootScope = $rootScope;
this.$compile = $compile;
}
MCTPreviewAction.prototype.perform = function () {
var newScope = this.$rootScope.$new();
newScope.domainObject = this.domainObject;
this.$compile(PREVIEW_TEMPLATE)(newScope);
};
MCTPreviewAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject,
status = domainObject.getCapability('status');
return !(status && status.get('editing'));
};
return MCTPreviewAction;
}
);

View File

@@ -1,51 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
/**
* Module defining ContextMenuController. Created by vwoeltje on 11/17/14.
*/
define(
[],
function () {
/**
* Controller for the context menu. Maintains an up-to-date
* list of applicable actions (those from category "contextual")
*
* @memberof platform/commonUI/general
* @constructor
*/
function ContextMenuController($scope) {
// Refresh variable "menuActions" in the scope
function updateActions() {
$scope.menuActions = $scope.action ?
$scope.action.getActions({ category: 'contextual' }) :
[];
}
// Update using the action capability
$scope.$watch("action", updateActions);
}
return ContextMenuController;
}
);

View File

@@ -1,64 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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(['zepto', '../services/Overlay'], function ($, Overlay) {
function MCTPreview($document) {
function link($scope, $element) {
var actions = $scope.domainObject.getCapability('action'),
notebookAction = actions.getActions({key: 'notebook-new-entry'})[0];
var notebookButton = notebookAction ?
[
{
class: 'icon-notebook new-notebook-entry',
title: 'New Notebook Entry',
clickHandler: function (event) {
event.stopPropagation();
notebookAction.perform();
}
}
] : [];
var overlayService = new Overlay({
$document: $document,
$element: $element[0],
$scope: $scope,
browseBarButtons: notebookButton
});
overlayService.toggleOverlay();
$scope.$on('$destroy', function () {
$element.remove();
});
}
return {
restrict: 'A',
link: link
};
}
return MCTPreview;
});

View File

@@ -82,7 +82,7 @@ define(
}
var searchPath = "?" + arr.join('&'),
newTabPath =
"index.html#" + this.urlForLocation(mode, domainObject) +
"#" + this.urlForLocation(mode, domainObject) +
searchPath;
return newTabPath;
};

View File

@@ -1,60 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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/controllers/ContextMenuController"],
function (ContextMenuController) {
describe("The context menu controller", function () {
var mockScope,
mockActions,
controller;
beforeEach(function () {
mockActions = jasmine.createSpyObj("action", ["getActions"]);
mockScope = jasmine.createSpyObj("$scope", ["$watch"]);
controller = new ContextMenuController(mockScope);
});
it("watches scope that may change applicable actions", function () {
// The action capability
expect(mockScope.$watch).toHaveBeenCalledWith(
"action",
jasmine.any(Function)
);
});
it("populates the scope with grouped and ungrouped actions", function () {
mockScope.action = mockActions;
mockScope.parameters = { category: "test" };
mockActions.getActions.and.returnValue(["a", "b", "c"]);
// Call the watch
mockScope.$watch.calls.mostRecent().args[1]();
// Should have grouped and ungrouped actions in scope now
expect(mockScope.menuActions.length).toEqual(3);
});
});
}
);

View File

@@ -19,16 +19,14 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="l-time-display l-digital l-clock s-clock" ng-controller="ClockController as clock">
<div class="l-elem-wrapper">
<span class="l-elem timezone">
{{clock.zone()}}
</span>
<span class="l-elem value active">
{{clock.text()}}
</span>
<span class="l-elem ampm">
{{clock.ampm()}}
</span>
<div class="c-clock l-time-display" ng-controller="ClockController as clock">
<div class="c-clock__timezone">
{{clock.zone()}}
</div>
<div class="c-clock__value">
{{clock.text()}}
</div>
<div class="c-clock__ampm">
{{clock.ampm()}}
</div>
</div>

View File

@@ -19,21 +19,19 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="l-time-display l-digital l-timer s-timer s-state-{{timer.timerState}}" ng-controller="TimerController as timer">
<div class="l-elem-wrapper l-flex-row">
<div class="l-elem-wrapper l-flex-row controls">
<a ng-click="timer.clickStopButton()"
title="Stop"
class="flex-elem s-icon-button t-btn-stop icon-box"></a>
<a ng-click="timer.clickButton()"
title="{{timer.buttonText()}}"
class="flex-elem s-icon-button t-btn-pauseplay {{timer.buttonCssClass()}}"></a>
</div>
<span class="flex-elem l-value {{timer.signClass()}}">
<span class="value"
ng-class="{ active:timer.text() }">{{timer.text() || "--:--:--"}}
</span>
</span>
<span ng-controller="RefreshingController"></span>
<div class="c-timer is-{{timer.timerState}}" ng-controller="TimerController as timer">
<div class="c-timer__controls">
<button ng-click="timer.clickStopButton()"
ng-hide="timer.timerState == 'stopped'"
title="Reset"
class="c-timer__ctrl-reset c-click-icon c-click-icon--major icon-reset"></button>
<button ng-click="timer.clickButton()"
title="{{timer.buttonText()}}"
class="c-timer__ctrl-pause-play c-click-icon c-click-icon--major {{timer.buttonCssClass()}}"></button>
</div>
<div class="c-timer__direction {{timer.signClass()}}"
ng-hide="!timer.signClass()"></div>
<div class="c-timer__value">{{timer.text() || "--:--:--"}}
</div>
<span class="c-timer__ng-controller u-contents" ng-controller="RefreshingController"></span>
</div>

View File

@@ -30,6 +30,7 @@ define([
"./src/controllers/CompositeController",
"./src/controllers/ColorController",
"./src/controllers/DialogButtonController",
"./src/controllers/SnapshotPreviewController",
"./res/templates/controls/autocomplete.html",
"./res/templates/controls/checkbox.html",
"./res/templates/controls/datetime.html",
@@ -44,6 +45,7 @@ define([
"./res/templates/controls/dialog.html",
"./res/templates/controls/radio.html",
"./res/templates/controls/file-input.html",
"./res/templates/controls/snap-view.html",
'legacyRegistry'
], function (
MCTForm,
@@ -55,6 +57,7 @@ define([
CompositeController,
ColorController,
DialogButtonController,
SnapshotPreviewController,
autocompleteTemplate,
checkboxTemplate,
datetimeTemplate,
@@ -69,6 +72,7 @@ define([
dialogTemplate,
radioTemplate,
fileInputTemplate,
snapViewTemplate,
legacyRegistry
) {
@@ -153,6 +157,10 @@ define([
{
"key": "file-input",
"template": fileInputTemplate
},
{
"key": "snap-view",
"template": snapViewTemplate
}
],
"controllers": [
@@ -186,6 +194,14 @@ define([
"$scope",
"dialogService"
]
},
{
"key": "SnapshotPreviewController",
"implementation": SnapshotPreviewController,
"depends": [
"$scope",
"openmct"
]
}
],
"components": [

View File

@@ -19,15 +19,18 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="menu-element context-menu-wrapper mobile-disable-select" ng-controller="ContextMenuController">
<div class="menu context-menu">
<ul>
<li ng-repeat="menuAction in menuActions"
ng-click="menuAction.perform()"
title="{{menuAction.getMetadata().description}}"
class="{{menuAction.getMetadata().cssClass}}">
{{menuAction.getMetadata().name}}
</li>
</ul>
</div>
</div>
<span ng-controller="SnapshotPreviewController"
class='form-control shell'>
<span class='field control {{structure.cssClass}}'>
<image
class="c-ne__embed__snap-thumb"
src="{{imageUrl || structure.src}}"
ng-click="previewImage(imageUrl || structure.src)"
name="mctControl">
</image>
<br>
<a title="Annotate" class="s-button icon-pencil" ng-click="annotateImage(ngModel, field, imageUrl || structure.src)">
<span class="title-label">Annotate</span>
</a>
</span>
</span>

View File

@@ -0,0 +1,130 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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(
[
'painterro'
],
function (Painterro) {
function SnapshotPreviewController($scope, openmct) {
$scope.previewImage = function (imageUrl) {
let image = document.createElement('img');
image.src = imageUrl;
let previewImageOverlay = openmct.overlays.overlay(
{
element: image,
size: 'large',
buttons: [
{
label: 'Done',
callback: function () {
previewImageOverlay.dismiss();
}
}
]
}
);
};
$scope.annotateImage = function (ngModel, field, imageUrl) {
$scope.imageUrl = imageUrl;
let div = document.createElement('div'),
painterroInstance = {},
save = false;
div.id = 'snap-annotation';
let annotateImageOverlay = openmct.overlays.overlay(
{
element: div,
size: 'large',
buttons: [
{
label: 'Cancel',
callback: function () {
save = false;
painterroInstance.save();
annotateImageOverlay.dismiss();
}
},
{
label: 'Save',
callback: function () {
save = true;
painterroInstance.save();
annotateImageOverlay.dismiss();
}
}
]
}
);
painterroInstance = Painterro({
id: 'snap-annotation',
activeColor: '#ff0000',
activeColorAlpha: 1.0,
activeFillColor: '#fff',
activeFillColorAlpha: 0.0,
backgroundFillColor: '#000',
backgroundFillColorAlpha: 0.0,
defaultFontSize: 16,
defaultLineWidth: 2,
defaultTool: 'ellipse',
hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
translation: {
name: 'en',
strings: {
lineColor: 'Line',
fillColor: 'Fill',
lineWidth: 'Size',
textColor: 'Color',
fontSize: 'Size',
fontStyle: 'Style'
}
},
saveHandler: function (image, done) {
if (save) {
let url = image.asBlob(),
reader = new window.FileReader();
reader.readAsDataURL(url);
reader.onloadend = function () {
$scope.imageUrl = reader.result;
ngModel[field] = reader.result;
};
} else {
ngModel.field = imageUrl;
console.warn('You cancelled the annotation!!!');
}
done(true);
}
}).show(imageUrl);
};
}
return SnapshotPreviewController;
}
);

View File

@@ -25,12 +25,10 @@ define([
"./src/MCTRepresentation",
"./src/gestures/DragGesture",
"./src/gestures/DropGesture",
"./src/gestures/ContextMenuGesture",
"./src/gestures/GestureProvider",
"./src/gestures/GestureRepresenter",
"./src/services/DndService",
"./src/TemplateLinker",
"./src/actions/ContextMenuAction",
"./src/TemplatePrefetcher",
'legacyRegistry'
], function (
@@ -38,12 +36,10 @@ define([
MCTRepresentation,
DragGesture,
DropGesture,
ContextMenuGesture,
GestureProvider,
GestureRepresenter,
DndService,
TemplateLinker,
ContextMenuAction,
TemplatePrefetcher,
legacyRegistry
) {
@@ -88,14 +84,6 @@ define([
"dndService",
"$q"
]
},
{
"key": "menu",
"implementation": ContextMenuGesture,
"depends": [
"$timeout",
"agentService"
]
}
],
"components": [
@@ -136,19 +124,6 @@ define([
"comment": "For internal use by mct-include and mct-representation."
}
],
"actions": [
{
"key": "menu",
"implementation": ContextMenuAction,
"depends": [
"$compile",
"$document",
"$rootScope",
"popupService",
"agentService"
]
}
],
"runs": [
{
"priority": "mandatory",

View File

@@ -1,138 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
/**
* Module defining ContextMenuAction. Created by shale on 06/30/2015.
*/
define(
["../gestures/GestureConstants"],
function (GestureConstants) {
var MENU_TEMPLATE = "<mct-representation key=\"'context-menu'\" " +
"mct-object=\"domainObject\" " +
"ng-class=\"menuClass\" " +
"ng-style=\"menuStyle\">" +
"</mct-representation>",
dismissExistingMenu;
/**
* Launches a custom context menu for the domain object it contains.
*
* @memberof platform/representation
* @constructor
* @param $compile Angular's $compile service
* @param $document the current document
* @param $rootScope Angular's root scope
* @param {platform/commonUI/general.PopupService} popupService
* @param actionContext the context in which the action
* should be performed
* @implements {Action}
*/
function ContextMenuAction(
$compile,
$document,
$rootScope,
popupService,
agentService,
actionContext
) {
this.$compile = $compile;
this.agentService = agentService;
this.actionContext = actionContext;
this.popupService = popupService;
this.getDocument = function () {
return $document;
};
this.getRootScope = function () {
return $rootScope;
};
}
ContextMenuAction.prototype.perform = function () {
var $compile = this.$compile,
$document = this.getDocument(),
$rootScope = this.getRootScope(),
actionContext = this.actionContext,
eventCoords = [
actionContext.event.pageX,
actionContext.event.pageY
],
menuDim = GestureConstants.MCT_MENU_DIMENSIONS,
body = $document.find('body'),
scope = $rootScope.$new(),
initiatingEvent = this.agentService.isMobile() ?
'touchstart' : 'mousedown',
menu,
popup;
// Remove the context menu
function dismiss() {
if (popup) {
popup.dismiss();
popup = undefined;
}
scope.$destroy();
body.off("mousedown", dismiss);
dismissExistingMenu = undefined;
}
// Dismiss any menu which was already showing
if (dismissExistingMenu) {
dismissExistingMenu();
}
// ...and record the presence of this menu.
dismissExistingMenu = dismiss;
// Set up the scope, including menu positioning
scope.domainObject = actionContext.domainObject;
scope.menuClass = { "context-menu-holder": true };
// Create the context menu
menu = $compile(MENU_TEMPLATE)(scope);
popup = this.popupService.display(menu, eventCoords, {
marginX: -menuDim[0],
marginY: -menuDim[1]
});
scope.menuClass['go-left'] = popup.goesLeft();
scope.menuClass['go-up'] = popup.goesUp();
// Stop propagation so that clicks or touches on the menu do not close the menu
menu.on(initiatingEvent, function (event) {
event.stopPropagation();
});
// Dismiss the menu when body is clicked/touched elsewhere
// ('mousedown' because 'click' breaks left-click context menus)
// ('touchstart' because 'touch' breaks context menus up)
body.on(initiatingEvent, dismiss);
// NOTE: Apply to mobile?
menu.on('click', dismiss);
// Don't launch browser's context menu
actionContext.event.preventDefault();
};
return ContextMenuAction;
}
);

View File

@@ -1,100 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
/**
* Module defining ContextMenuGesture.
* Created by vwoeltje on 11/17/14. Modified by shale on 06/30/2015.
*/
define(
function () {
/**
* Add listeners to a representation such that it calls the
* context menu action for the domain object it contains.
*
* @memberof platform/representation
* @constructor
* @param element the jqLite-wrapped element which should exhibit
* the context menu
* @param {DomainObject} domainObject the object on which actions
* in the context menu will be performed
* @implements {Gesture}
*/
function ContextMenuGesture($timeout, agentService, element, domainObject) {
var isPressing,
isDragging,
longTouchTime = 500;
function showMenu(event) {
domainObject.getCapability('action').perform({
key: 'menu',
domainObject: domainObject,
event: event
});
}
// When context menu event occurs, show object actions instead
if (!agentService.isMobile()) {
// When context menu event occurs, show object actions instead
element.on('contextmenu', showMenu);
} else if (agentService.isMobile()) {
// If on mobile device, then start timeout for the single touch event
// during the timeout 'isPressing' is true.
element.on('touchstart', function (event) {
if (event.touches.length < 2) {
isPressing = true;
// After the timeout, if 'isPressing' is
// true, display context menu for object
$timeout(function () {
if (isPressing && !isDragging) {
showMenu(event);
}
}, longTouchTime);
}
});
// If on Mobile Device, and user scrolls/drags set flag to true
element.on('touchmove', function () {
isDragging = true;
});
// Whenever the touch event ends, 'isPressing' & 'isDragging' is false.
element.on('touchend', function () {
isPressing = false;
isDragging = false;
});
}
this.showMenuCallback = showMenu;
this.element = element;
}
ContextMenuGesture.prototype.destroy = function () {
this.element.off('contextmenu', this.showMenu);
};
return ContextMenuGesture;
}
);

View File

@@ -1,202 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
/**
* Module defining ContextMenuActionSpec. Created by shale on 07/02/2015.
*/
define(
["../../src/actions/ContextMenuAction"],
function (ContextMenuAction) {
var JQLITE_FUNCTIONS = ["on", "off", "find", "append", "remove"],
DOMAIN_OBJECT_METHODS = ["getId", "getModel", "getCapability", "hasCapability", "useCapability"];
describe("The 'context menu' action", function () {
var mockCompile,
mockCompiledTemplate,
mockMenu,
mockDocument,
mockBody,
mockPopupService,
mockRootScope,
mockAgentService,
mockScope,
mockDomainObject,
mockEvent,
mockPopup,
mockActionContext,
action;
beforeEach(function () {
mockCompile = jasmine.createSpy("$compile");
mockCompiledTemplate = jasmine.createSpy("template");
mockMenu = jasmine.createSpyObj("menu", JQLITE_FUNCTIONS);
mockDocument = jasmine.createSpyObj("$document", JQLITE_FUNCTIONS);
mockBody = jasmine.createSpyObj("body", JQLITE_FUNCTIONS);
mockPopupService =
jasmine.createSpyObj("popupService", ["display"]);
mockPopup = jasmine.createSpyObj("popup", [
"dismiss",
"goesLeft",
"goesUp"
]);
mockRootScope = jasmine.createSpyObj("$rootScope", ["$new"]);
mockAgentService = jasmine.createSpyObj("agentService", ["isMobile"]);
mockScope = jasmine.createSpyObj("scope", ["$destroy"]);
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
mockEvent = jasmine.createSpyObj("event", ["preventDefault", "stopPropagation"]);
mockEvent.pageX = 123;
mockEvent.pageY = 321;
mockCompile.and.returnValue(mockCompiledTemplate);
mockCompiledTemplate.and.returnValue(mockMenu);
mockDocument.find.and.returnValue(mockBody);
mockRootScope.$new.and.returnValue(mockScope);
mockPopupService.display.and.returnValue(mockPopup);
mockActionContext = {key: 'menu', domainObject: mockDomainObject, event: mockEvent};
action = new ContextMenuAction(
mockCompile,
mockDocument,
mockRootScope,
mockPopupService,
mockAgentService,
mockActionContext
);
});
it("displays a popup when performed", function () {
action.perform();
expect(mockPopupService.display).toHaveBeenCalledWith(
mockMenu,
[mockEvent.pageX, mockEvent.pageY],
jasmine.any(Object)
);
});
it("prevents the default context menu behavior", function () {
action.perform();
expect(mockEvent.preventDefault).toHaveBeenCalled();
});
it("adds classes to menus based on position", function () {
var booleans = [false, true];
booleans.forEach(function (goLeft) {
booleans.forEach(function (goUp) {
mockPopup.goesLeft.and.returnValue(goLeft);
mockPopup.goesUp.and.returnValue(goUp);
action.perform();
expect(!!mockScope.menuClass['go-up'])
.toEqual(goUp);
expect(!!mockScope.menuClass['go-left'])
.toEqual(goLeft);
});
});
});
it("removes a menu when body is clicked", function () {
// Show the menu
action.perform();
// Verify precondition
expect(mockBody.remove).not.toHaveBeenCalled();
// Find and fire body's mousedown listener
mockBody.on.calls.all().forEach(function (call) {
if (call.args[0] === 'mousedown') {
call.args[1]();
}
});
// Menu should have been removed
expect(mockPopup.dismiss).toHaveBeenCalled();
// Listener should have been detached from body
expect(mockBody.off).toHaveBeenCalledWith(
'mousedown',
jasmine.any(Function)
);
});
it("removes a menu when it is clicked", function () {
// Show the menu
action.perform();
// Verify precondition
expect(mockMenu.remove).not.toHaveBeenCalled();
// Find and fire menu's click listener
mockMenu.on.calls.all().forEach(function (call) {
if (call.args[0] === 'click') {
call.args[1]();
}
});
// Menu should have been removed
expect(mockPopup.dismiss).toHaveBeenCalled();
});
it("keeps a menu when menu is clicked", function () {
// Show the menu
action.perform();
// Find and fire body's mousedown listener
mockMenu.on.calls.all().forEach(function (call) {
if (call.args[0] === 'mousedown') {
call.args[1](mockEvent);
}
});
// Menu should have been removed
expect(mockPopup.dismiss).not.toHaveBeenCalled();
// Listener should have been detached from body
expect(mockBody.off).not.toHaveBeenCalled();
});
it("keeps a menu when menu is clicked on mobile", function () {
mockAgentService.isMobile.and.returnValue(true);
action = new ContextMenuAction(
mockCompile,
mockDocument,
mockRootScope,
mockPopupService,
mockAgentService,
mockActionContext
);
action.perform();
mockMenu.on.calls.all().forEach(function (call) {
if (call.args[0] === 'touchstart') {
call.args[1](mockEvent);
}
});
expect(mockPopup.dismiss).not.toHaveBeenCalled();
});
});
}
);

View File

@@ -1,119 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
/**
* Module defining ContextMenuGestureSpec. Created by vwoeltje on 11/22/14.
*/
define(
["../../src/gestures/ContextMenuGesture"],
function (ContextMenuGesture) {
var JQLITE_FUNCTIONS = ["on", "off", "find", "append", "remove"],
DOMAIN_OBJECT_METHODS = ["getId", "getModel", "getCapability", "hasCapability", "useCapability"];
describe("The 'context menu' gesture", function () {
var mockTimeout,
mockElement,
mockAgentService,
mockDomainObject,
mockTouchEvent,
mockContextMenuAction,
mockTouch,
gesture,
fireGesture,
fireTouchStartGesture,
fireTouchEndGesture;
beforeEach(function () {
mockTimeout = jasmine.createSpy("$timeout");
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
mockAgentService = jasmine.createSpyObj("agentService", ["isMobile"]);
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
mockContextMenuAction = jasmine.createSpyObj(
"action",
["perform", "getActions"]
);
mockDomainObject.getCapability.and.returnValue(mockContextMenuAction);
mockContextMenuAction.perform.and.returnValue(jasmine.any(Function));
mockAgentService.isMobile.and.returnValue(false);
gesture = new ContextMenuGesture(mockTimeout, mockAgentService, mockElement, mockDomainObject);
// Capture the contextmenu callback
fireGesture = mockElement.on.calls.mostRecent().args[1];
});
it("attaches a callback for context menu events", function () {
// Fire a click and expect it to happen
fireGesture();
expect(mockElement.on).toHaveBeenCalledWith(
"contextmenu",
jasmine.any(Function)
);
});
it("detaches a callback for context menu events when destroyed", function () {
expect(mockElement.off).not.toHaveBeenCalled();
gesture.destroy();
expect(mockElement.off).toHaveBeenCalledWith(
"contextmenu",
//mockElement.on.calls.mostRecent().args[1]
mockDomainObject.calls
);
});
it("attaches a callback for context menu events on mobile", function () {
// Mock touch event and set to mobile device
mockTouchEvent = jasmine.createSpyObj("event", ["preventDefault", "touches"]);
mockTouch = jasmine.createSpyObj("touch", ["length"]);
mockTouch.length = 1;
mockTouchEvent.touches.and.returnValue(mockTouch);
mockAgentService.isMobile.and.returnValue(true);
// Then create new (mobile) gesture
gesture = new ContextMenuGesture(mockTimeout, mockAgentService, mockElement, mockDomainObject);
// Set calls for the touchstart and touchend gestures
fireTouchStartGesture = mockElement.on.calls.all()[1].args[1];
fireTouchEndGesture = mockElement.on.calls.mostRecent().args[1];
// Fire touchstart and expect touch start to begin
fireTouchStartGesture(mockTouchEvent);
expect(mockElement.on).toHaveBeenCalledWith(
"touchstart",
jasmine.any(Function)
);
// Expect timeout to begin and then fireTouchEnd
expect(mockTimeout).toHaveBeenCalled();
mockTimeout.calls.mostRecent().args[0]();
fireTouchEndGesture(mockTouchEvent);
});
});
}
);

View File

@@ -40,9 +40,10 @@ define([
'../platform/framework/src/Main',
'./styles-new/core.scss',
'./styles-new/notebook.scss',
'./ui/components/layout/Layout.vue',
'./ui/layout/Layout.vue',
'../platform/core/src/objects/DomainObjectImpl',
'../platform/core/src/capabilities/ContextualDomainObject',
'./ui/preview/plugin',
'vue'
], function (
EventEmitter,
@@ -67,6 +68,7 @@ define([
Layout,
DomainObjectImpl,
ContextualDomainObject,
PreviewPlugin,
Vue
) {
/**
@@ -230,6 +232,7 @@ define([
this.install(this.plugins.Plot());
this.install(this.plugins.TelemetryTable());
this.install(this.plugins.DisplayLayout());
this.install(PreviewPlugin.default());
if (typeof BUILD_CONSTANTS !== 'undefined') {
this.install(buildInfoPlugin(BUILD_CONSTANTS));

View File

@@ -24,7 +24,7 @@ import LegacyContextMenuAction from './LegacyContextMenuAction';
export default function LegacyActionAdapter(openmct, legacyActions) {
function contextualCategoryOnly(action) {
if (action.category === 'contextual') {
if (action.category === 'contextual' || (Array.isArray(action.category) && action.category.includes('contextual'))) {
return true;
}
console.warn(`DEPRECATION WARNING: Action ${action.definition.key} in bundle ${action.bundle.path} is non-contextual and should be migrated.`);

View File

@@ -1,5 +1,3 @@
import { timingSafeEqual } from "crypto";
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
@@ -21,6 +19,9 @@ import { timingSafeEqual } from "crypto";
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import _ from 'lodash';
const INSIDE_EDIT_PATH_BLACKLIST = ["copy", "follow", "link", "locate", "move", "link"];
const OUTSIDE_EDIT_PATH_BLACKLIST = ["copy", "follow", "properties", "move", "link", "remove", "locate"];
export default class LegacyContextMenuAction {
constructor(openmct, LegacyAction) {
@@ -31,13 +32,6 @@ export default class LegacyContextMenuAction {
this.LegacyAction = LegacyAction;
}
appliesTo(objectPath) {
let legacyObject = this.openmct.legacyObject(objectPath);
return this.LegacyAction.appliesTo({
domainObject: legacyObject
});
}
invoke(objectPath) {
let context = {
category: 'contextual',
@@ -54,4 +48,36 @@ export default class LegacyContextMenuAction {
}
legacyAction.perform();
}
}
appliesTo(objectPath) {
let legacyObject = this.openmct.legacyObject(objectPath);
return (this.LegacyAction.appliesTo === undefined ||
this.LegacyAction.appliesTo({domainObject: legacyObject})) &&
!this.isBlacklisted(objectPath);
}
/**
* @private
*/
isBlacklisted(objectPath) {
let navigatedObject = this.openmct.router.path[0];
let isEditing = this.openmct.editor.isEditing();
/**
* Is the object being edited, or a child of the object being edited?
*/
function isInsideEditPath() {
return objectPath.some((object) => _.eq(object.identifier, navigatedObject.identifier));
}
if (isEditing) {
if (isInsideEditPath()) {
return INSIDE_EDIT_PATH_BLACKLIST.some(actionKey => this.LegacyAction.key === actionKey);
} else {
return OUTSIDE_EDIT_PATH_BLACKLIST.some(actionKey => this.LegacyAction.key === actionKey);
}
}
return false;
}
}

View File

@@ -58,11 +58,8 @@ define([
handleLegacyMutation = function (legacyObject) {
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId());
//Don't trigger self
this.eventEmitter.off('mutation', handleMutation);
this.eventEmitter.emit(newStyleObject.identifier.key + ":*", newStyleObject);
this.eventEmitter.on('mutation', handleMutation);
this.eventEmitter.emit('mutation', newStyleObject);
}.bind(this);
this.eventEmitter.on('mutation', handleMutation);

View File

@@ -21,7 +21,9 @@ define([
name: legacyView.name,
cssClass: legacyView.cssClass,
description: legacyView.description,
editable: legacyView.editable,
canEdit: function () {
return legacyView.editable === true;
},
canView: function (domainObject) {
if (!domainObject || !domainObject.identifier) {
return false;
@@ -77,7 +79,7 @@ define([
openmct.$angular.element(container),
legacyView
);
container.style.height = '100%';
container.classList.add('u-contents');
}
if (promises.length) {

View File

@@ -28,6 +28,11 @@ export default class Editor extends EventEmitter {
super();
this.editing = false;
this.openmct = openmct;
document.addEventListener('drop', (event) => {
if (!this.isEditing()) {
this.edit();
}
}, {capture: true});
}
/**

View File

@@ -46,6 +46,7 @@ define([
function DefaultCompositionProvider(publicAPI, compositionAPI) {
this.publicAPI = publicAPI;
this.listeningTo = {};
this.onMutation = this.onMutation.bind(this);
this.cannotContainDuplicates = this.cannotContainDuplicates.bind(this);
this.cannotContainItself = this.cannotContainItself.bind(this);
@@ -208,9 +209,10 @@ define([
if (this.topicListener) {
return;
}
var topic = this.publicAPI.$injector.get('topic');
var mutation = topic('mutation');
this.topicListener = mutation.listen(this.onMutation.bind(this));
this.publicAPI.objects.eventEmitter.on('mutation', this.onMutation);
this.topicListener = () => {
this.publicAPI.objects.eventEmitter.off('mutation', this.onMutation)
};
};
/**
@@ -220,7 +222,7 @@ define([
* @private
*/
DefaultCompositionProvider.prototype.onMutation = function (oldDomainObject) {
var id = oldDomainObject.getId();
var id = objectUtils.makeKeyString(oldDomainObject.identifier);
var listeners = this.listeningTo[id];
if (!listeners) {
@@ -228,7 +230,7 @@ define([
}
var oldComposition = listeners.composition.map(objectUtils.makeKeyString);
var newComposition = oldDomainObject.getModel().composition.map(objectUtils.makeKeyString);
var newComposition = oldDomainObject.composition.map(objectUtils.makeKeyString);
var added = _.difference(newComposition, oldComposition).map(objectUtils.parseKeyString);
var removed = _.difference(oldComposition, newComposition).map(objectUtils.parseKeyString);

View File

@@ -73,8 +73,12 @@ class ContextMenuAPI {
* @private
*/
_showContextMenuForObjectPath(objectPath, x, y) {
let applicableActions = this._allActions.filter(
(action) => action.appliesTo(objectPath));
let applicableActions = this._allActions.filter((action) => {
if (action.appliesTo === undefined) {
return true;
}
return action.appliesTo(objectPath);
});
if (this._activeContextMenu) {
this._hideActiveContextMenu();

View File

@@ -83,18 +83,15 @@ define([
this.object = newObject;
}.bind(this);
this.eventEmitter.on(qualifiedEventName(this.object, '*'), handleRecursiveMutation);
//Emit event specific to property
this.eventEmitter.emit(qualifiedEventName(this.object, path), value);
this.eventEmitter.off(qualifiedEventName(this.object, '*'), handleRecursiveMutation);
//Emit wildcare event
//Emit wildcard event
this.eventEmitter.emit(qualifiedEventName(this.object, '*'), this.object);
//Emit a general "any object" event
this.eventEmitter.emit(ANY_OBJECT_EVENT, this.object);
this.eventEmitter.on(qualifiedEventName(this.object, '*'), handleRecursiveMutation);
//Emit event specific to property
this.eventEmitter.emit(qualifiedEventName(this.object, path), value);
this.eventEmitter.off(qualifiedEventName(this.object, '*'), handleRecursiveMutation);
};
return MutableObject;

View File

@@ -3,13 +3,15 @@ import Overlay from './Overlay';
import Vue from 'vue';
class Dialog extends Overlay {
constructor({iconClass, message, title, ...options}) {
constructor({iconClass, message, title, hint, timestamp, ...options}) {
let component = new Vue({
provide: {
iconClass,
message,
title
title,
hint,
timestamp
},
components: {
DialogComponent: DialogComponent
@@ -20,7 +22,7 @@ class Dialog extends Overlay {
super({
element: component.$el,
size: 'fit',
notDismissable: true,
dismissable: false,
...options
});

View File

@@ -12,6 +12,7 @@ class Overlay extends EventEmitter {
constructor(options) {
super();
this.dismissable = options.dismissable !== false ? true : false;
this.container = document.createElement('div');
this.container.classList.add('l-overlay-wrapper', cssClasses[options.size]);
@@ -20,7 +21,7 @@ class Overlay extends EventEmitter {
dismiss: this.dismiss.bind(this),
element: options.element,
buttons: options.buttons,
notDismissable: options.notDismissable ? true : false
dismissable: this.dismissable
},
components: {
OverlayComponent: OverlayComponent
@@ -35,8 +36,8 @@ class Overlay extends EventEmitter {
dismiss() {
this.emit('destroy');
this.component.$destroy();
document.body.removeChild(this.container);
this.component.$destroy();
}
/**

View File

@@ -14,6 +14,14 @@ import ProgressDialog from './ProgressDialog';
class OverlayAPI {
constructor() {
this.activeOverlays = [];
this.dismissLastOverlay = this.dismissLastOverlay.bind(this);
document.addEventListener('keyup', (event) => {
if (event.key === 'Escape') {
this.dismissLastOverlay();
}
});
}
/**
@@ -38,14 +46,24 @@ class OverlayAPI {
}
/**
] * A description of option properties that can be passed into the overlay
* @typedef options
* private
*/
dismissLastOverlay() {
let lastOverlay = this.activeOverlays[this.activeOverlays.length - 1];
if (lastOverlay && lastOverlay.dismissable) {
lastOverlay.dismiss();
}
}
/**
* A description of option properties that can be passed into the overlay
* @typedef options
* @property {object} element DOMElement that is to be inserted/shown on the overlay
* @property {string} size prefered size of the overlay (large, small, fit)
* @property {array} buttons optional button objects with label and callback properties
* @property {function} onDestroy callback to be called when overlay is destroyed
* @property {boolean} notDismissable to prevent user from dismissing the overlay, calling code
* will need to explicitly dismiss the overlay.
* @property {boolean} dismissable allow user to dismiss overlay by using esc, and clicking away
* from overlay. Unless set to false, all overlays will be dismissable by default.
*/
overlay(options) {
let overlay = new Overlay(options);

View File

@@ -1,4 +1,4 @@
import ProgressComponent from '../../ui/components/layout/ProgressBar.vue';
import ProgressComponent from '../../ui/components/ProgressBar.vue';
import Overlay from './Overlay';
import Vue from 'vue';
@@ -25,7 +25,7 @@ class ProgressDialog extends Overlay {
super({
element: component.$el,
size: 'fit',
notDismissable: true,
dismissable: false,
...options
});

View File

@@ -50,7 +50,6 @@
flex: 1 1 auto;
> * + * {
@include test();
margin-top: $interiorMargin;
}
}

View File

@@ -5,7 +5,7 @@
</div>
<div class="c-overlay__outer">
<button class="c-click-icon c-overlay__close-button icon-x-in-circle"
v-if="!notDismissable"
v-if="dismissable"
@click="destroy">
</button>
<div class="c-overlay__contents" ref="element"></div>
@@ -40,7 +40,7 @@
.c-overlay {
@include abs();
z-index: 100;
z-index: 70;
&__blocker {
display: none; // Mobile-first
@@ -129,19 +129,19 @@
<script>
export default {
inject: ['dismiss', 'element', 'buttons', 'notDismissable'],
inject: ['dismiss', 'element', 'buttons', 'dismissable'],
mounted() {
this.$refs.element.appendChild(this.element);
},
methods: {
destroy: function () {
if (!this.notDismissable) {
if (this.dismissable) {
this.dismiss();
}
},
buttonClickHandler: function (method) {
method();
this.destroy();
this.$emit('destroy');
}
}
}

View File

@@ -124,6 +124,22 @@ define([
return sortedMetadata;
};
TelemetryMetadataManager.prototype.getDefaultDisplayValue = function () {
let valueMetadata = this.valuesForHints(['range'])[0];
if (valueMetadata === undefined) {
valueMetadata = this.values().filter(values => {
return !(values.hints.domain);
})[0];
}
if (valueMetadata === undefined) {
valueMetadata = this.values()[0];
}
return valueMetadata.key;
};
return TelemetryMetadataManager;

View File

@@ -20,47 +20,48 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'vue',
'../../res/templates/viewSnapshot.html'
'./components/LadTableSet.vue',
'vue'
], function (
Vue,
snapshotOverlayTemplate
LadTableSet,
Vue
) {
function SnapshotOverlay (embedObject, formatTime) {
this.embedObject = embedObject;
function LADTableSetViewProvider(openmct) {
return {
key: 'LadTableSet',
name: 'LAD Table Set',
cssClass: 'icon-tabular-lad-set',
canView: function (domainObject) {
return domainObject.type === 'LadTableSet';
},
view: function (domainObject) {
let component;
this.snapshotOverlayVue = new Vue({
template: snapshotOverlayTemplate,
data: function () {
return {
embed: embedObject
show: function (element) {
component = new Vue({
components: {
LadTableSet: LadTableSet.default
},
provide: {
openmct,
domainObject
},
el: element,
template: '<lad-table-set></lad-table-set>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
methods: {
close: this.close.bind(this),
formatTime: formatTime
priority: function () {
return 1;
}
});
this.open();
};
}
SnapshotOverlay.prototype.open = function () {
this.overlay = document.createElement('div');
this.overlay.classList.add('abs');
document.body.appendChild(this.overlay);
this.overlay.appendChild(this.snapshotOverlayVue.$mount().$el);
};
SnapshotOverlay.prototype.close = function (event) {
event.stopPropagation();
this.snapshotOverlayVue.$destroy();
this.overlay.parentNode.removeChild(this.overlay);
};
return SnapshotOverlay;
return LADTableSetViewProvider;
});

View File

@@ -0,0 +1,67 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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([
'./components/LadTable.vue',
'vue'
], function (
LadTableComponent,
Vue
) {
function LADTableViewProvider(openmct) {
return {
key: 'LadTable',
name: 'LAD Table',
cssClass: 'icon-tabular-lad',
canView: function (domainObject) {
return domainObject.type === 'LadTable';
},
view: function (domainObject) {
let component;
return {
show: function (element) {
component = new Vue({
components: {
LadTableComponent: LadTableComponent.default
},
provide: {
openmct,
domainObject
},
el: element,
template: '<lad-table-component></lad-table-component>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}
return LADTableViewProvider;
});

View File

@@ -0,0 +1,120 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
<template>
<tr>
<td>{{name}}</td>
<td>{{timestamp}}</td>
<td :class="valueClass">
{{value}}
</td>
</tr>
</template>
<style lang="scss">
</style>
<script>
export default {
inject: ['openmct'],
props: ['domainObject'],
data() {
return {
name: this.domainObject.name,
timestamp: '---',
value: '---',
valueClass: ''
}
},
methods: {
updateValues(datum) {
this.timestamp = this.formats[this.timestampKey].format(datum);
this.value = this.formats[this.valueKey].format(datum);
var limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
if (limit) {
this.valueClass = limit.cssClass;
} else {
this.valueClass = '';
}
},
updateName(name){
this.name = name;
},
updateTimeSystem(timeSystem) {
this.value = '---';
this.timestamp = '---';
this.valueClass = '';
this.timestampKey = timeSystem.key;
this.openmct
.telemetry
.request(this.domainObject, {strategy: 'latest'})
.then((array) => this.updateValues(array[array.length - 1]));
}
},
mounted() {
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
this.limitEvaluator = openmct
.telemetry
.limitEvaluator(this.domainObject);
this.stopWatchingMutation = openmct
.objects
.observe(
this.domainObject,
'*',
this.updateName
);
this.openmct.time.on('timeSystem', this.updateTimeSystem);
this.timestampKey = this.openmct.time.timeSystem().key;
this.valueMetadata = this
.metadata
.valuesForHints(['range'])[0];
this.valueKey = this.valueMetadata.key
this.unsubscribe = this.openmct
.telemetry
.subscribe(this.domainObject, this.updateValues);
this.openmct
.telemetry
.request(this.domainObject, {strategy: 'latest'})
.then((array) => this.updateValues(array[array.length - 1]));
},
destroyed() {
this.stopWatchingMutation();
this.unsubscribe();
this.openmct.off('timeSystem', this.updateTimeSystem);
}
}
</script>

View File

@@ -0,0 +1,82 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
<template>
<table class="c-table c-lad-table">
<thead>
<tr>
<th>Name</th>
<th>Timestamp</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<lad-row
v-for="item in items"
:key="item.key"
:domainObject="item.domainObject">
</lad-row>
</tbody>
</table>
</template>
<script>
import lodash from 'lodash';
import LadRow from './LadRow.vue';
export default {
inject: ['openmct', 'domainObject'],
components: {
LadRow
},
data() {
return {
items: []
}
},
methods: {
addItem(domainObject) {
let item = {};
item.domainObject = domainObject;
item.key = this.openmct.objects.makeKeyString(domainObject.identifier);
this.items.push(item);
},
removeItem(identifier) {
let index = _.findIndex(this.items, (item) => this.openmct.objects.makeKeyString(identifier) === item.key);
this.items.splice(index, 1);
}
},
mounted() {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addItem);
this.composition.on('remove', this.removeItem);
this.composition.load();
},
destroyed() {
this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem);
}
}
</script>

View File

@@ -0,0 +1,135 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
<template>
<table class="c-table c-lad-table">
<thead>
<tr>
<th>Name</th>
<th>Timestamp</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<template
v-for="primary in primaryTelemetryObjects">
<tr class="c-table__group-header"
:key="primary.key">
<td colspan="10">{{primary.domainObject.name}}</td>
</tr>
<lad-row
v-for="secondary in secondaryTelemetryObjects[primary.key]"
:key="secondary.key"
:domainObject="secondary.domainObject">
</lad-row>
</template>
</tbody>
</table>
</template>
<style lang="scss">
</style>
<script>
import lodash from 'lodash';
import LadRow from './LadRow.vue';
export default {
inject: ['openmct', 'domainObject'],
components: {
LadRow
},
data() {
return {
primaryTelemetryObjects: [],
secondaryTelemetryObjects: {},
compositions: []
}
},
methods: {
addPrimary(domainObject) {
let primary = {};
primary.domainObject = domainObject;
primary.key = this.openmct.objects.makeKeyString(domainObject.identifier);
this.$set(this.secondaryTelemetryObjects, primary.key, []);
this.primaryTelemetryObjects.push(primary);
let composition = openmct.composition.get(primary.domainObject),
addCallback = this.addSecondary(primary),
removeCallback = this.removeSecondary(primary);
composition.on('add', addCallback);
composition.on('remove', removeCallback);
composition.load();
this.compositions.push({composition, addCallback, removeCallback});
},
removePrimary(identifier) {
let index = _.findIndex(this.primaryTelemetryObjects, (primary) => this.openmct.objects.makeKeyString(identifier) === primary.key),
primary = this.primaryTelemetryObjects[index];
this.$set(this.secondaryTelemetryObjects, primary.key, undefined);
this.primaryTelemetryObjects.splice(index,1);
primary = undefined;
},
addSecondary(primary) {
return (domainObject) => {
let secondary = {};
secondary.key = this.openmct.objects.makeKeyString(domainObject.identifier);
secondary.domainObject = domainObject;
let array = this.secondaryTelemetryObjects[primary.key];
array.push(secondary);
this.$set(this.secondaryTelemetryObjects, primary.key, array);
}
},
removeSecondary(primary) {
return (identifier) => {
let array = this.secondaryTelemetryObjects[primary.key],
index = _.findIndex(array, (secondary) => this.openmct.objects.makeKeyString(identifier) === secondary.key);
array.splice(index, 1);
this.$set(this.secondaryTelemetryObjects, primary.key, array);
}
}
},
mounted() {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addPrimary);
this.composition.on('remove', this.removePrimary);
this.composition.load();
},
destroyed() {
this.composition.off('add', this.addPrimary);
this.composition.off('remove', this.removePrimary);
this.compositions.forEach(c => {
c.composition.off('add', c.addCallback);
c.composition.off('remove', c.removeCallback);
});
}
}
</script>

View File

@@ -0,0 +1,56 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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([
'./LADTableViewProvider',
'./LADTableSetViewProvider'
], function (
LADTableViewProvider,
LADTableSetViewProvider
) {
return function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new LADTableViewProvider(openmct));
openmct.objectViews.addProvider(new LADTableSetViewProvider(openmct));
openmct.types.addType('LadTable', {
name: "LAD Table",
creatable: true,
description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.",
cssClass: 'icon-tabular-lad',
initialize(domainObject) {
domainObject.composition = [];
}
});
openmct.types.addType('LadTableSet', {
name: "LAD Table Set",
creatable: true,
description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.",
cssClass: 'icon-tabular-lad-set',
initialize(domainObject) {
domainObject.composition = [];
}
});
};
};
});

View File

@@ -35,6 +35,48 @@ define([], function () {
(selection[0].context.item && selection[0].context.item.type === 'layout')));
},
toolbar: function (selection) {
const DIALOG_FORM = {
'text': {
name: "Text Element Properties",
sections: [
{
rows: [
{
key: "text",
control: "textfield",
name: "Text",
required: true
}
]
}
]
},
'image': {
name: "Image Properties",
sections: [
{
rows: [
{
key: "url",
control: "textfield",
name: "Image URL",
"cssClass": "l-input-lg",
required: true
}
]
}
]
}
};
function getUserInput(form) {
return openmct.$injector.get('dialogService').getUserInput(form, {});
}
function getPath() {
return `configuration.items[${selection[0].context.index}]`;
}
let selectedParent = selection[1] && selection[1].context.item,
selectedObject = selection[0].context.item,
layoutItem = selection[0].context.layoutItem,
@@ -45,7 +87,14 @@ define([], function () {
control: "menu",
domainObject: selectedObject,
method: function (option) {
selection[0].context.addElement(option.name.toLowerCase());
let name = option.name.toLowerCase();
let form = DIALOG_FORM[name];
if (form) {
getUserInput(form)
.then(element => selection[0].context.addElement(name, element));
} else {
selection[0].context.addElement(name);
}
},
key: "add",
icon: "icon-plus",
@@ -75,16 +124,102 @@ define([], function () {
return toolbar;
}
if (layoutItem.type === 'subobject-view') {
if (toolbar.length > 0) {
toolbar.push({
control: "separator"
let separator = {
control: "separator"
};
let remove = {
control: "button",
domainObject: selectedParent,
icon: "icon-trash",
title: "Delete the selected object",
method: function () {
let removeItem = selection[1].context.removeItem;
let prompt = openmct.overlays.dialog({
iconClass: 'alert',
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
buttons: [
{
label: 'Ok',
emphasis: 'true',
callback: function () {
removeItem(layoutItem, selection[0].context.index);
prompt.dismiss();
}
},
{
label: 'Cancel',
callback: function () {
prompt.dismiss();
}
}
]
});
}
};
let stackOrder = {
control: "menu",
domainObject: selectedParent,
icon: "icon-layers",
title: "Move the selected object above or below other objects",
options: [
{
name: "Move to Top",
value: "top",
class: "icon-arrow-double-up"
},
{
name: "Move Up",
value: "up",
class: "icon-arrow-up"
},
{
name: "Move Down",
value: "down",
class: "icon-arrow-down"
},
{
name: "Move to Bottom",
value: "bottom",
class: "icon-arrow-double-down"
}
],
method: function (option) {
selection[1].context.orderItem(option.value, selection[0].context.index);
}
};
let useGrid = {
control: "toggle-button",
domainObject: selectedParent,
property: function () {
return getPath() + ".useGrid";
},
options: [
{
value: false,
icon: "icon-grid-snap-no",
title: "Do not snap to grid"
},
{
value: true,
icon: "icon-grid-snap-to",
title: "Snap to grid"
}
]
};
if (layoutItem.type === 'subobject-view') {
if (toolbar.length > 0) {
toolbar.push(separator);
}
toolbar.push(stackOrder);
toolbar.push(separator);
toolbar.push({
control: "toggle-button",
domainObject: selectedParent,
property: "configuration.panels[" + layoutItem.id + "].hasFrame",
property: function () {
return getPath() + ".hasFrame";
},
options: [
{
value: false,
@@ -98,38 +233,36 @@ define([], function () {
}
]
});
toolbar.push(separator);
toolbar.push(useGrid);
toolbar.push(separator);
toolbar.push(remove);
} else {
const TEXT_SIZE = [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96];
let path;
// TODO: get the path from the view configuration
// let path = layoutItem.config.path();
if (layoutItem.type === 'telemetry-view') {
path = "configuration.alphanumerics[" + layoutItem.config.alphanumeric.index + "]";
} else {
path = "configuration.elements[" + layoutItem.config.element.index + "]";
}
let separator = {
control: "separator"
},
fill = {
let fill = {
control: "color-picker",
domainObject: selectedParent,
property: path + ".fill",
property: function () {
return getPath() + ".fill";
},
icon: "icon-paint-bucket",
title: "Set fill color"
},
stroke = {
control: "color-picker",
domainObject: selectedParent,
property: path + ".stroke",
property: function () {
return getPath() + ".stroke";
},
icon: "icon-line-horz",
title: "Set border color"
},
color = {
control: "color-picker",
domainObject: selectedParent,
property: path + ".color",
property: function () {
return getPath() + ".color";
},
icon: "icon-font",
mandatory: true,
title: "Set text color",
@@ -138,7 +271,9 @@ define([], function () {
size = {
control: "select-menu",
domainObject: selectedParent,
property: path + ".size",
property: function () {
return getPath() + ".size";
},
title: "Set text size",
options: TEXT_SIZE.map(size => {
return {
@@ -150,7 +285,9 @@ define([], function () {
control: "input",
type: "number",
domainObject: selectedParent,
property: path + ".x",
property: function () {
return getPath() + ".x";
},
label: "X:",
title: "X position"
},
@@ -158,7 +295,9 @@ define([], function () {
control: "input",
type: "number",
domainObject: selectedParent,
property: path + ".y",
property: function () {
return getPath() + ".y";
},
label: "Y:",
title: "Y position",
},
@@ -166,7 +305,9 @@ define([], function () {
control: 'input',
type: 'number',
domainObject: selectedParent,
property: path + ".width",
property: function () {
return getPath() + ".width";
},
label: 'W:',
title: 'Resize object width'
},
@@ -174,18 +315,20 @@ define([], function () {
control: 'input',
type: 'number',
domainObject: selectedParent,
property: path + ".height",
property: function () {
return getPath() + ".height";
},
label: 'H:',
title: 'Resize object height'
};
if (layoutItem.type === 'telemetry-view') {
// TODO: add "remove", "order", "useGrid"
let metadata = openmct.telemetry.getMetadata(layoutItem.domainObject),
displayMode = {
let displayMode = {
control: "select-menu",
domainObject: selectedParent,
property: path + ".displayMode",
property: function () {
return getPath() + ".displayMode";
},
title: "Set display mode",
options: [
{
@@ -205,9 +348,11 @@ define([], function () {
value = {
control: "select-menu",
domainObject: selectedParent,
property: path + ".value",
property: function () {
return getPath() + ".value";
},
title: "Set value",
options: metadata.values().map(value => {
options: openmct.telemetry.getMetadata(selectedObject).values().map(value => {
return {
name: value.name,
value: value.key
@@ -219,6 +364,7 @@ define([], function () {
separator,
value,
separator,
stackOrder,
fill,
stroke,
color,
@@ -228,95 +374,112 @@ define([], function () {
x,
y,
height,
width
width,
useGrid,
separator,
remove
];
} else if (layoutItem.type === 'text-view' ) {
// TODO: Add "remove", "order", "useGrid"
let text = {
control: "button",
domainObject: selectedParent,
property: path,
property: function () {
return getPath();
},
icon: "icon-gear",
title: "Edit text properties",
dialog: {
name: "Text Element Properties",
sections: [
{
rows: [
{
key: "text",
control: "textfield",
name: "Text",
required: true
}
]
}
]
}
dialog: DIALOG_FORM['text']
};
toolbar = [
stackOrder,
fill,
stroke,
color,
separator,
color,
size,
separator,
x,
y,
height,
width,
useGrid,
separator,
text
text,
separator,
remove
];
} else if (layoutItem.type === 'box-view') {
// TODO: Add "remove", "order", "useGrid"
toolbar = [
stackOrder,
fill,
stroke,
separator,
x,
y,
height,
width
width,
useGrid,
separator,
remove
];
} else if (layoutItem.type === 'image-view') {
// TODO: Add "remove", "order", "useGrid"
let url = {
control: "button",
domainObject: selectedParent,
property: path,
property: function () {
return getPath();
},
icon: "icon-image",
title: "Edit image properties",
dialog: {
name: "Image Properties",
sections: [
{
rows: [
{
key: "url",
control: "textfield",
name: "Image URL",
"cssClass": "l-input-lg",
required: true
}
]
}
]
}
};
dialog: DIALOG_FORM['image']
};
toolbar = [
stackOrder,
stroke,
separator,
x,
y,
height,
width,
useGrid,
separator,
url
url,
separator,
remove
];
} else if (layoutItem.type === 'line-view') {
// TODO: Add "remove", "order", "useGrid", "x1", "y1", x2", "y2"
toolbar = [stroke];
let x2 = {
control: "input",
type: "number",
domainObject: selectedParent,
property: function () {
return getPath() + ".x2";
},
label: "X2:",
title: "X2 position"
},
y2 = {
control: "input",
type: "number",
domainObject: selectedParent,
property: function () {
return getPath() + ".y2";
},
label: "Y2:",
title: "Y2 position",
};
toolbar = [
stackOrder,
stroke,
separator,
x,
y,
x2,
y2,
useGrid,
separator,
remove
];
}
}

View File

@@ -25,17 +25,41 @@ define(function () {
return {
name: "Display Layout",
creatable: true,
description: 'Assemble other objects and components together into a reusable screen layout. Simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.',
cssClass: 'icon-layout',
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
panels: {},
alphanumerics: [],
elements: []
items: [],
layoutGrid: [10, 10],
};
}
},
form: [
{
name: "Horizontal grid (px)",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
property: [
"configuration",
"layoutGrid",
0
],
required: true
},
{
name: "Vertical grid (px)",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
property: [
"configuration",
"layoutGrid",
1
],
required: true
}
]
}
}
return DisplayLayoutType;
});
});

View File

@@ -1,171 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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(
['./ViewConfiguration'],
function (ViewConfiguration) {
class ElementViewConfiguration extends ViewConfiguration {
static create(type, openmct) {
const DEFAULT_WIDTH = 10,
DEFAULT_HEIGHT = 5,
DEFAULT_X = 1,
DEFAULT_Y = 1;
const INITIAL_STATES = {
"image": {
stroke: "transparent"
},
"box": {
fill: "#717171",
stroke: "transparent"
},
"line": {
x: 5,
y: 3,
x2: 6,
y2: 6,
stroke: "#717171"
},
"text": {
fill: "transparent",
stroke: "transparent",
size: "13px",
color: ""
}
};
const DIALOGS = {
"image": {
name: "Image Properties",
sections: [
{
rows: [
{
key: "url",
control: "textfield",
name: "Image URL",
"cssClass": "l-input-lg",
required: true
}
]
}
]
},
"text": {
name: "Text Element Properties",
sections: [
{
rows: [
{
key: "text",
control: "textfield",
name: "Text",
required: true
}
]
}
]
}
};
let element = INITIAL_STATES[type] || {};
element = JSON.parse(JSON.stringify(element));
element.x = element.x || DEFAULT_X;
element.y = element.y || DEFAULT_Y;
element.width = DEFAULT_WIDTH;
element.height = DEFAULT_HEIGHT;
element.type = type;
return DIALOGS[type] ?
openmct.$injector.get('dialogService').getUserInput(DIALOGS[type], element) :
element;
}
/**
* @param {Object} configuration the element (line, box, text or image) view configuration
* @param {Object} configuration.element
* @param {Object} configuration.domainObject the telemetry domain object
* @param {Object} configuration.openmct the openmct object
*/
constructor({element, ...rest}) {
super(rest);
this.element = element;
this.updateStyle(this.position());
}
path() {
return "configuration.elements[" + this.element.index + "]";
}
x() {
return this.element.x;
}
y() {
return this.element.y;
}
width() {
return this.element.width;
}
height() {
return this.element.height;
}
observeProperties() {
[
"width",
"height",
"stroke",
"fill",
"x",
"y",
"x1",
"y1",
"x2",
"y2",
"color",
"size",
"text",
"url"
].forEach(property => {
this.attachListener(property, newValue => {
this.element[property] = newValue;
if (property === 'width' || property === 'height' ||
property === 'x' || property === 'y') {
this.updateStyle();
}
});
});
// TODO: attach listener for useGrid
}
inspectable() {
return false;
}
}
return ElementViewConfiguration;
}
);

View File

@@ -1,122 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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(
['./ViewConfiguration'],
function (ViewConfiguration) {
class SubobjectViewConfiguration extends ViewConfiguration {
static create(domainObject, gridSize, position) {
const MINIMUM_FRAME_SIZE = [320, 180],
DEFAULT_DIMENSIONS = [10, 10],
DEFAULT_POSITION = [0, 0],
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget'];
function getDefaultDimensions() {
return MINIMUM_FRAME_SIZE.map((min, index) => {
return Math.max(
Math.ceil(min / gridSize[index]),
DEFAULT_DIMENSIONS[index]
);
});
}
function hasFrameByDefault(type) {
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
}
position = position || DEFAULT_POSITION;
let defaultDimensions = getDefaultDimensions();
let panel = {
width: defaultDimensions[0],
height: defaultDimensions[1],
x: position[0],
y: position[1],
hasFrame: hasFrameByDefault(domainObject.type)
};
return panel;
}
/**
*
* @param {Object} configuration the subobject view configuration
* @param {String} configuration.id the domain object keystring identifier
* @param {Boolean} configuration.panel
* @param {Object} configuration.domainObject the domain object to observe the changes on
* @param {Object} configuration.openmct the openmct object
*/
constructor({panel, id, ...rest}) {
super(rest);
this.id = id;
this.panel = panel;
this.hasFrame = this.hasFrame.bind(this);
this.updateStyle(this.position());
}
path() {
return "configuration.panels[" + this.id + "]";
}
x() {
return this.panel.x;
}
y() {
return this.panel.y;
}
width() {
return this.panel.width;
}
height() {
return this.panel.height;
}
hasFrame() {
return this.panel.hasFrame;
}
observeProperties() {
[
'hasFrame',
'x',
'y',
'width',
'height'
].forEach(property => {
this.attachListener(property, newValue => {
this.panel[property] = newValue;
if (property === 'width' || property === 'height' ||
property === 'x' || property === 'y') {
this.updateStyle();
}
});
});
}
}
return SubobjectViewConfiguration;
}
);

View File

@@ -1,124 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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(
['./ViewConfiguration'],
function (ViewConfiguration) {
class TelemetryViewConfiguration extends ViewConfiguration {
static create(domainObject, position, openmct) {
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
function getDefaultTelemetryValue(domainObject, openmct) {
let metadata = openmct.telemetry.getMetadata(domainObject);
let valueMetadata = metadata.valuesForHints(['range'])[0];
if (valueMetadata === undefined) {
valueMetadata = metadata.values().filter(values => {
return !(values.hints.domain);
})[0];
}
if (valueMetadata === undefined) {
valueMetadata = metadata.values()[0];
}
return valueMetadata.key;
}
let alphanumeric = {
identifier: domainObject.identifier,
x: position[0],
y: position[1],
width: DEFAULT_TELEMETRY_DIMENSIONS[0],
height: DEFAULT_TELEMETRY_DIMENSIONS[1],
displayMode: 'all',
value: getDefaultTelemetryValue(domainObject, openmct),
stroke: "transparent",
fill: "",
color: "",
size: "13px",
};
return alphanumeric;
}
/**
* @param {Object} configuration the telemetry object view configuration
* @param {Object} configuration.alphanumeric
* @param {Object} configuration.domainObject the domain object to observe the changes on
* @param {Object} configuration.openmct the openmct object
*/
constructor({alphanumeric, ...rest}) {
super(rest);
this.alphanumeric = alphanumeric;
this.updateStyle(this.position());
}
path() {
return "configuration.alphanumerics[" + this.alphanumeric.index + "]";
}
x() {
return this.alphanumeric.x;
}
y() {
return this.alphanumeric.y;
}
width() {
return this.alphanumeric.width;
}
height() {
return this.alphanumeric.height;
}
observeProperties() {
[
'displayMode',
'value',
'fill',
'stroke',
'color',
'size',
'x',
'y',
'width',
'height'
].forEach(property => {
this.attachListener(property, newValue => {
this.alphanumeric[property] = newValue;
if (property === 'width' || property === 'height' ||
property === 'x' || property === 'y') {
this.updateStyle();
}
});
});
}
}
return TelemetryViewConfiguration;
}
);

View File

@@ -1,121 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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([],
function () {
class ViewConfiguration {
constructor({domainObject, openmct, gridSize}) {
this.domainObject = domainObject;
this.gridSize = gridSize;
this.mutatePosition = this.mutatePosition.bind(this);
this.listeners = [];
this.observe = openmct.objects.observe.bind(openmct.objects);
this.mutate = function (path, value) {
openmct.objects.mutate(this.domainObject, path, value);
}.bind(this);
this.newPosition = {};
}
mutatePosition() {
this.mutate(this.path() + ".x", this.newPosition.position[0]);
this.mutate(this.path() + ".y", this.newPosition.position[1]);
this.mutate(this.path() + ".width", this.newPosition.dimensions[0]);
this.mutate(this.path() + ".height", this.newPosition.dimensions[1]);
}
attachListener(property, callback) {
this.listeners.push(this.observe(this.domainObject, this.path() + "." + property, callback));
}
attachListeners() {
this.observeProperties();
this.listeners.push(this.observe(this.domainObject, '*', function (obj) {
this.domainObject = JSON.parse(JSON.stringify(obj));
}.bind(this)));
}
removeListeners() {
this.listeners.forEach(listener => {
listener();
});
this.listeners = [];
}
position() {
return {
position: [this.x(), this.y()],
dimensions: [this.width(), this.height()]
};
}
path() {
throw "NOT IMPLEMENTED;"
}
inspectable() {
return true;
}
updateStyle(raw) {
if (!raw) {
raw = this.position();
}
this.style = {
left: (this.gridSize[0] * raw.position[0]) + 'px',
top: (this.gridSize[1] * raw.position[1]) + 'px',
width: (this.gridSize[0] * raw.dimensions[0]) + 'px',
height: (this.gridSize[1] * raw.dimensions[1]) + 'px',
minWidth: (this.gridSize[0] * raw.dimensions[0]) + 'px',
minHeight: (this.gridSize[1] * raw.dimensions[1]) + 'px'
};
}
observeProperties() {
// Not implemented
}
x() {
// Not implemented
}
y() {
// Not implemented
}
width() {
// Not implemented
}
height() {
// Not implemented
}
hasFrame() {
// Not implemented
}
}
return ViewConfiguration;
}
);

View File

@@ -21,9 +21,13 @@
*****************************************************************************/
<template>
<div class="c-box-view"
:style="styleObject">
</div>
<layout-frame :item="item"
:grid-size="gridSize"
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
<div class="c-box-view"
:style="style">
</div>
</layout-frame>
</template>
<style lang="scss">
@@ -40,24 +44,60 @@
</style>
<script>
import LayoutFrame from './LayoutFrame.vue'
export default {
makeDefinition() {
return {
fill: '#717171',
stroke: 'transparent',
x: 1,
y: 1,
width: 10,
height: 5,
useGrid: true
};
},
inject: ['openmct'],
components: {
LayoutFrame
},
props: {
item: Object
item: Object,
gridSize: Array,
index: Number,
initSelect: Boolean
},
computed: {
styleObject() {
let element = this.item.config.element;
style() {
return {
backgroundColor: element.fill,
border: '1px solid ' + element.stroke
backgroundColor: this.item.fill,
border: '1px solid ' + this.item.stroke
};
}
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex;
}
},
mounted() {
this.item.config.attachListeners();
this.context = {
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
},
destroyed() {
this.item.config.removeListeners();
if (this.removeSelectable) {
this.removeSelectable();
}
}
}
</script>
</script>

View File

@@ -25,28 +25,26 @@
@dragover="handleDragOver"
@click="bypassSelection"
@drop="handleDrop">
<div class="l-layout__object">
<!-- Background grid -->
<div class="l-layout__grid-holder c-grid"
v-if="!drilledIn">
<div class="c-grid__x l-grid l-grid-x"
v-if="gridSize[0] >= 3"
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]">
</div>
<div class="c-grid__y l-grid l-grid-y"
v-if="gridSize[1] >= 3"
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
<!-- Background grid -->
<div class="l-layout__grid-holder c-grid">
<div class="c-grid__x l-grid l-grid-x"
v-if="gridSize[0] >= 3"
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]">
</div>
<layout-item v-for="(item, index) in layoutItems"
class="l-layout__frame"
:key="index"
:item="item"
:gridSize="gridSize"
@drilledIn="updateDrilledInState"
@dragInProgress="updatePosition"
@endDrag="endDrag">
</layout-item>
<div class="c-grid__y l-grid l-grid-y"
v-if="gridSize[1] >= 3"
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
</div>
<component v-for="(item, index) in layoutItems"
:is="item.type"
:item="item"
:key="item.id"
:gridSize="item.useGrid ? gridSize : [1, 1]"
:initSelect="initSelectIndex === index"
:index="index"
@endDrag="endDrag"
>
</component>
</div>
</template>
@@ -57,140 +55,168 @@
@include abs();
display: flex;
flex-direction: column;
overflow: auto;
&__grid-holder {
display: none;
}
&__object {
flex: 1 1 auto;
overflow: auto;
}
&__frame {
position: absolute;
}
}
.l-shell__main-container {
> .l-layout {
[s-selected] {
border: $browseBorderSelected;
.is-editing {
.l-shell__main-container {
&[s-selected],
&[s-selected-parent] {
// Display grid in main layout holder when editing
> .l-layout {
background: $editUIGridColorBg;
> [class*="__grid-holder"] {
display: block;
}
}
}
}
.l-layout__frame {
&[s-selected],
&[s-selected-parent] {
// Display grid in nested layouts when editing
> * > * > .l-layout {
background: $editUIGridColorBg;
box-shadow: inset $editUIGridColorFg 0 0 2px 1px;
> [class*='grid-holder'] {
display: block;
}
}
}
}
}
// Styles moved to _global.scss;
</style>
<script>
import LayoutItem from './LayoutItem.vue';
import TelemetryViewConfiguration from './../TelemetryViewConfiguration.js'
import SubobjectViewConfiguration from './../SubobjectViewConfiguration.js'
import ElementViewConfiguration from './../ElementViewConfiguration.js'
import uuid from 'uuid';
const DEFAULT_GRID_SIZE = [10, 10];
import SubobjectView from './SubobjectView.vue'
import TelemetryView from './TelemetryView.vue'
import BoxView from './BoxView.vue'
import TextView from './TextView.vue'
import LineView from './LineView.vue'
import ImageView from './ImageView.vue'
const ITEM_TYPE_VIEW_MAP = {
'subobject-view': SubobjectView,
'telemetry-view': TelemetryView,
'box-view': BoxView,
'line-view': LineView,
'text-view': TextView,
'image-view': ImageView
};
const ORDERS = {
top: Number.POSITIVE_INFINITY,
up: 1,
down: -1,
bottom: Number.NEGATIVE_INFINITY
};
function getItemDefinition(itemType, ...options) {
let itemView = ITEM_TYPE_VIEW_MAP[itemType];
if (!itemView) {
throw `Invalid itemType: ${itemType}`;
}
return itemView.makeDefinition(...options);
}
export default {
data() {
let domainObject = JSON.parse(JSON.stringify(this.domainObject));
return {
gridSize: [],
layoutItems: [],
drilledIn: undefined
internalDomainObject: domainObject,
initSelectIndex: undefined
};
},
computed: {
gridSize() {
return this.internalDomainObject.configuration.layoutGrid;
},
layoutItems() {
return this.internalDomainObject.configuration.items;
}
},
},
inject: ['openmct'],
props: ['domainObject'],
components: {
LayoutItem
},
components: ITEM_TYPE_VIEW_MAP,
methods: {
getAlphanumerics() {
let alphanumerics = this.newDomainObject.configuration.alphanumerics || [];
alphanumerics.forEach((alphanumeric, index) => {
alphanumeric.index = index;
this.makeTelemetryItem(alphanumeric, false);
});
},
getElements() {
let elements = this.newDomainObject.configuration.elements || [];
elements.forEach((element, index) => {
element.index = index;
this.makeElementItem(element, false);
});
},
makeSubobjectItem(panel, initSelect) {
let id = this.openmct.objects.makeKeyString(panel.domainObject.identifier);
let config = new SubobjectViewConfiguration({
domainObject: this.newDomainObject,
panel: panel,
id: id,
openmct: openmct,
gridSize: this.gridSize
});
this.layoutItems.push({
id: id,
domainObject: panel.domainObject,
drilledIn: this.isItemDrilledIn(id),
initSelect: initSelect,
type: 'subobject-view',
config: config
});
},
makeTelemetryItem(alphanumeric, initSelect) {
let id = this.openmct.objects.makeKeyString(alphanumeric.identifier);
this.openmct.objects.get(id).then(domainObject => {
let config = new TelemetryViewConfiguration({
domainObject: this.newDomainObject,
alphanumeric: alphanumeric,
openmct: openmct,
gridSize: this.gridSize
});
this.layoutItems.push({
id: id,
domainObject: domainObject,
initSelect: initSelect,
type: 'telemetry-view',
config: config
});
});
},
makeElementItem(element, initSelect) {
let config = new ElementViewConfiguration({
domainObject: this.newDomainObject,
element: element,
openmct: openmct,
gridSize: this.gridSize
});
this.layoutItems.push({
initSelect: initSelect,
type: element.type + '-view',
config: config
});
addElement(itemType, element) {
this.addItem(itemType + '-view', element);
},
setSelection(selection) {
if (selection.length === 0) {
return;
}
this.updateDrilledInState();
if (this.removeSelectionListener) {
this.removeSelectionListener();
}
let itemIndex = selection[0].context.index;
if (itemIndex !== undefined) {
this.attachSelectionListener(itemIndex);
}
this.updateDrilledIn();
},
updateDrilledInState(id) {
this.drilledIn = id;
this.layoutItems.forEach(function (item) {
attachSelectionListener(index) {
let path = `configuration.items[${index}].useGrid`;
this.removeSelectionListener = this.openmct.objects.observe(this.internalDomainObject, path, function (value) {
let item = this.layoutItems[index];
if (value) {
item.x = Math.round(item.x / this.gridSize[0]);
item.y = Math.round(item.y / this.gridSize[1]);
item.width = Math.round(item.width / this.gridSize[0]);
item.height = Math.round(item.height / this.gridSize[1]);
if (item.x2) {
item.x2 = Math.round(item.x2 / this.gridSize[0]);
}
if (item.y2) {
item.y2 = Math.round(item.y2 / this.gridSize[1]);
}
} else {
item.x = this.gridSize[0] * item.x;
item.y = this.gridSize[1] * item.y;
item.width = this.gridSize[0] * item.width;
item.height = this.gridSize[1] * item.height;
if (item.x2) {
item.x2 = this.gridSize[0] * item.x2;
}
if (item.y2) {
item.y2 = this.gridSize[1] * item.y2;
}
}
item.useGrid = value;
this.mutate(`configuration.items[${index}]`, item);
}.bind(this));
},
updateDrilledIn(drilledInItem) {
let identifier = drilledInItem && this.openmct.objects.makeKeyString(drilledInItem.identifier);
this.drilledIn = identifier;
this.layoutItems.forEach(item => {
if (item.type === 'subobject-view') {
item.drilledIn = item.id === id;
item.drilledIn = this.openmct.objects.makeKeyString(item.identifier) === identifier;
}
});
},
isItemDrilledIn(id) {
return this.drilledIn === id;
},
updatePosition(item, newPosition) {
item.config.newPosition = newPosition;
item.config.updateStyle(newPosition);
},
bypassSelection($event) {
if (this.dragInProgress) {
if ($event) {
@@ -199,59 +225,56 @@
return;
}
},
endDrag(item) {
endDrag(item, updates) {
this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
// TODO: emit "finishResizing" for view components to mutate position?
item.config.mutatePosition();
let index = this.layoutItems.indexOf(item);
Object.assign(item, updates);
this.mutate(`configuration.items[${index}]`, item);
},
mutate(path, value) {
this.openmct.objects.mutate(this.newDomainObject, path, value);
this.openmct.objects.mutate(this.internalDomainObject, path, value);
},
handleDrop($event) {
if (!$event.dataTransfer.types.includes('openmct/domain-object-path')) {
return;
}
$event.preventDefault();
let domainObject = JSON.parse($event.dataTransfer.getData('domainObject'));
let domainObject = JSON.parse($event.dataTransfer.getData('openmct/domain-object-path'))[0];
let elementRect = this.$el.getBoundingClientRect();
this.droppedObjectPosition = [
let droppedObjectPosition = [
Math.floor(($event.pageX - elementRect.left) / this.gridSize[0]),
Math.floor(($event.pageY - elementRect.top) / this.gridSize[1])
];
if (this.isTelemetry(domainObject)) {
this.addAlphanumeric(domainObject, this.droppedObjectPosition);
this.addItem('telemetry-view', domainObject, droppedObjectPosition);
} else {
this.checkForDuplicatePanel(domainObject);
}
},
checkForDuplicatePanel(domainObject) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
let panels = this.newDomainObject.configuration.panels;
let identifier = this.openmct.objects.makeKeyString(domainObject.identifier);
if (panels && panels[id]) {
let prompt = this.openmct.overlays.dialog({
iconClass: 'alert',
message: "This item is already in layout and will not be added again.",
buttons: [
{
label: 'OK',
callback: function () {
prompt.dismiss();
if (!this.objectViewMap[identifier]) {
this.addItem('subobject-view', domainObject, droppedObjectPosition);
} else {
let prompt = this.openmct.overlays.dialog({
iconClass: 'alert',
message: "This item is already in layout and will not be added again.",
buttons: [
{
label: 'OK',
callback: function () {
prompt.dismiss();
}
}
}
]
});
]
});
}
}
},
addAlphanumeric(domainObject, position) {
let alphanumerics = this.newDomainObject.configuration.alphanumerics || [];
let alphanumeric = TelemetryViewConfiguration.create(domainObject, position, this.openmct);
alphanumeric.index = alphanumerics.push(alphanumeric) - 1;
this.mutate("configuration.alphanumerics", alphanumerics);
this.makeTelemetryItem(alphanumeric, true);
},
handleDragOver($event){
$event.preventDefault();
},
@@ -263,63 +286,136 @@
return false;
}
},
addSubobject(domainObject) {
if (!this.isTelemetry(domainObject)) {
let panels = this.newDomainObject.configuration.panels,
id = this.openmct.objects.makeKeyString(domainObject.identifier),
panel = panels[id],
mutateObject = false,
initSelect = false;
addItem(itemType, ...options) {
let item = getItemDefinition(itemType, this.openmct, this.gridSize, ...options);
item.type = itemType;
item.id = uuid();
this.trackItem(item);
this.layoutItems.push(item);
this.openmct.objects.mutate(this.internalDomainObject, "configuration.items", this.layoutItems);
this.initSelectIndex = this.layoutItems.length - 1;
},
trackItem(item) {
if (!item.identifier) {
return;
}
// If the panel doesn't exist, create one and mutate the configuration
if (!panel) {
panel = SubobjectViewConfiguration.create(domainObject, this.gridSize, this.droppedObjectPosition);
initSelect = true;
this.mutate("configuration.panels[" + id + "]", panel);
delete this.droppedObjectPosition;
}
let keyString = this.openmct.objects.makeKeyString(item.identifier);
panel.domainObject = domainObject;
this.makeSubobjectItem(panel, initSelect);
if (item.type === "telemetry-view") {
let count = this.telemetryViewMap[keyString] || 0;
this.telemetryViewMap[keyString] = ++count;
} else if (item.type === "subobject-view") {
this.objectViewMap[keyString] = true;
}
},
removeSubobject() {
// Not yet implemented
removeItem(item, index) {
this.initSelectIndex = -1;
this.layoutItems.splice(index, 1);
this.mutate("configuration.items", this.layoutItems);
this.untrackItem(item);
this.$el.click();
},
addElement(type) {
let elements = this.newDomainObject.configuration.elements || [];
Promise.resolve(ElementViewConfiguration.create(type, this.openmct))
.then(element => {
element.index = elements.push(element) - 1;
this.mutate("configuration.elements", elements);
this.makeElementItem(element, true);
});
untrackItem(item) {
if (!item.identifier) {
return;
}
let keyString = this.openmct.objects.makeKeyString(item.identifier);
if (item.type === 'telemetry-view') {
let count = --this.telemetryViewMap[keyString];
if (count === 0) {
delete this.telemetryViewMap[keyString];
this.removeFromComposition(keyString);
}
} else if (item.type === 'subobject-view') {
delete this.objectViewMap[keyString];
this.removeFromComposition(keyString);
}
},
removeFromComposition(keyString) {
let composition = _.get(this.internalDomainObject, 'composition');
composition = composition.filter(identifier => {
return this.openmct.objects.makeKeyString(identifier) !== keyString;
});
this.mutate("composition", composition);
},
initializeItems() {
this.telemetryViewMap = {};
this.objectViewMap = {};
this.layoutItems.forEach(this.trackItem);
},
addChild(child) {
let identifier = this.openmct.objects.makeKeyString(child.identifier);
if (this.isTelemetry(child)) {
if (!this.telemetryViewMap[identifier]) {
this.addItem('telemetry-view', child);
}
} else if (!this.objectViewMap[identifier]) {
this.addItem('subobject-view', child);
}
},
removeChild(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
if (this.objectViewMap[keyString]) {
delete this.objectViewMap[keyString];
this.removeFromConfiguration(keyString);
} else if (this.telemetryViewMap[keyString]) {
delete this.telemetryViewMap[keyString];
this.removeFromConfiguration(keyString);
}
},
removeFromConfiguration(keyString) {
let layoutItems = this.layoutItems.filter(item => {
if (!item.identifier) {
return true;
} else {
return this.openmct.objects.makeKeyString(item.identifier) !== keyString;
}
});
this.mutate("configuration.items", layoutItems);
this.$el.click();
},
orderItem(position, index) {
let delta = ORDERS[position];
let newIndex = Math.max(Math.min(index + delta, this.layoutItems.length - 1), 0);
let item = this.layoutItems[index];
if (newIndex !== index) {
this.layoutItems.splice(index, 1);
this.layoutItems.splice(newIndex, 0, item);
this.mutate('configuration.items', this.layoutItems);
if (this.removeSelectionListener) {
this.removeSelectionListener();
this.attachSelectionListener(newIndex);
}
}
}
},
mounted() {
this.newDomainObject = this.domainObject;
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;
this.unlisten = this.openmct.objects.observe(this.newDomainObject, '*', function (obj) {
this.newDomainObject = JSON.parse(JSON.stringify(obj));
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;;
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', function (obj) {
this.internalDomainObject = JSON.parse(JSON.stringify(obj));
}.bind(this));
this.openmct.selection.on('change', this.setSelection);
this.composition = this.openmct.composition.get(this.newDomainObject);
this.composition.on('add', this.addSubobject);
this.composition.on('remove', this.removeSubobject);
this.initializeItems();
this.composition = this.openmct.composition.get(this.internalDomainObject);
this.composition.on('add', this.addChild);
this.composition.on('remove', this.removeChild);
this.composition.load();
this.getAlphanumerics();
this.getElements();
},
destroyed: function () {
this.openmct.off('change', this.setSelection);
this.composition.off('add', this.addSubobject);
this.composition.off('remove', this.removeSubobject);
this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild);
this.unlisten();
if (this.removeSelectionListener) {
this.removeSelectionListener();
}
}
}
</script>
</script>

View File

@@ -21,9 +21,13 @@
*****************************************************************************/
<template>
<div class="c-image-view"
:style="styleObject">
</div>
<layout-frame :item="item"
:grid-size="gridSize"
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
<div class="c-image-view"
:style="style">
</div>
</layout-frame>
</template>
<style lang="scss">
@@ -42,24 +46,59 @@
</style>
<script>
import LayoutFrame from './LayoutFrame.vue'
export default {
makeDefinition(openmct, gridSize, element) {
return {
stroke: 'transparent',
x: 1,
y: 1,
width: 10,
height: 5,
url: element.url,
useGrid: true
};
},
inject: ['openmct'],
props: {
item: Object
item: Object,
gridSize: Array,
index: Number,
initSelect: Boolean
},
components: {
LayoutFrame
},
computed: {
styleObject() {
let element = this.item.config.element;
style() {
return {
backgroundImage: 'url(' + element.url + ')',
border: '1px solid ' + element.stroke
backgroundImage: 'url(' + this.item.url + ')',
border: '1px solid ' + this.item.stroke
}
}
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex;
}
},
mounted() {
this.item.config.attachListeners();
this.context = {
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
},
destroyed() {
this.item.config.removeListeners();
if (this.removeSelectable) {
this.removeSelectable();
}
}
}
</script>

View File

@@ -0,0 +1,300 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
<template>
<div class="l-layout__frame c-frame"
:class="{
'no-frame': !item.hasFrame,
'u-inspectable': inspectable,
'is-resizing': isResizing
}"
:style="style">
<slot></slot>
<!-- Drag handles -->
<div class="c-frame-edit">
<div class="c-frame-edit__move"
@mousedown="startDrag([1,1], [0,0], $event, 'move')"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--nw"
@mousedown="startDrag([1,1], [-1,-1], $event, 'resize')"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--ne"
@mousedown="startDrag([0,1], [1,-1], $event, 'resize')"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--sw"
@mousedown="startDrag([1,0], [-1,1], $event, 'resize')"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--se"
@mousedown="startDrag([0,0], [1,1], $event, 'resize')"></div>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
/******************************* FRAME */
.c-frame {
display: flex;
flex-direction: column;
// Whatever is placed into the slot, make it fill the entirety of the space, obeying padding
> *:first-child {
flex: 1 1 auto;
}
&:not(.no-frame) {
background: $colorBodyBg;
border: $browseFrameBorder;
padding: $interiorMargin;
}
}
.c-frame-edit {
// In Layouts, this is the editing rect and handles
// In Fixed Position, this is a wrapper element
@include abs();
display: none;
&__move {
@include abs();
cursor: move;
}
&__handle {
$d: 6px;
$o: floor($d * -0.5);
background: $editFrameColorHandleFg;
box-shadow: $editFrameColorHandleBg 0 0 0 2px;
display: none; // Set to block via s-selected selector
position: absolute;
width: $d; height: $d;
top: auto; right: auto; bottom: auto; left: auto;
&:before {
// Extended hit area
@include abs(-10px);
content: '';
display: block;
z-index: 0;
}
&:hover {
background: $editUIColor;
}
&--nwse {
cursor: nwse-resize;
}
&--nw {
cursor: nw-resize;
left: $o; top: $o;
}
&--ne {
cursor: ne-resize;
right: $o; top: $o;
}
&--se {
cursor: se-resize;
right: $o; bottom: $o;
}
&--sw {
cursor: sw-resize;
left: $o; bottom: $o;
}
}
}
.c-so-view.has-complex-content + .c-frame-edit {
// Target frames that hold domain objects that include header elements, as opposed to drawing and alpha objects
// Make the __move element a more affordable drag UI element
.c-frame-edit__move {
@include userSelectNone();
background: $editFrameMovebarColorBg;
box-shadow: rgba(black, 0.2) 0 1px;
bottom: auto;
height: 0; // Height is set on hover on s-selected.c-frame
opacity: 0.8;
max-height: 100%;
overflow: hidden;
text-align: center;
&:before {
// Grippy
$h: 4px;
$tbOffset: ($editFrameMovebarH - $h) / 2;
$lrOffset: 25%;
@include grippy($editFrameMovebarColorFg);
content: '';
display: block;
position: absolute;
top: $tbOffset; right: $lrOffset; bottom: $tbOffset; left: $lrOffset;
}
&:hover {
background: $editFrameHovMovebarColorBg;
&:before { @include grippy($editFrameHovMovebarColorFg); }
}
}
}
.is-editing {
.c-frame {
$moveBarOutDelay: 500ms;
&.no-frame {
border: $editFrameBorder; // Base border style for a frame element while editing.
}
&-edit {
display: contents;
}
&-edit__move,
.c-so-view {
transition: $transOut;
transition-delay: $moveBarOutDelay;
}
&:not([s-selected]) {
&:hover {
border: $editFrameBorderHov;
}
}
&[s-selected] {
// All frames selected while editing
border: $editFrameSelectedBorder;
box-shadow: $editFrameSelectedShdw;
> .c-frame-edit {
[class*='__handle'] {
display: block;
}
}
}
}
.l-layout__frame:not(.is-resizing) {
// Show and animate the __move bar for sub-object views with complex content
&:hover > .c-so-view.has-complex-content {
// Move content down so the __move bar doesn't cover it.
padding-top: $editFrameMovebarH;
transition: $transIn;
&.c-so-view--no-frame {
// Move content down with a bit more space
padding-top: $editFrameMovebarH + $interiorMarginSm;
}
// Show the move bar
+ .c-frame-edit .c-frame-edit__move {
height: $editFrameMovebarH;
transition: $transIn;
}
}
}
}
</style>
<script>
import LayoutDrag from './../LayoutDrag'
export default {
inject: ['openmct'],
props: {
item: Object,
gridSize: Array
},
data() {
return {
dragPosition: undefined,
isResizing: undefined
}
},
computed: {
style() {
let {x, y, width, height} = this.item;
if (this.dragPosition) {
[x, y] = this.dragPosition.position;
[width, height] = this.dragPosition.dimensions;
}
return {
left: (this.gridSize[0] * x) + 'px',
top: (this.gridSize[1] * y) + 'px',
width: (this.gridSize[0] * width) + 'px',
height: (this.gridSize[1] * height) + 'px',
minWidth: (this.gridSize[0] * width) + 'px',
minHeight: (this.gridSize[1] * height) + 'px'
};
},
inspectable() {
return this.item.type === 'subobject-view' || this.item.type === 'telemetry-view';
}
},
methods: {
updatePosition(event) {
let currentPosition = [event.pageX, event.pageY];
this.initialPosition = this.initialPosition || currentPosition;
this.delta = currentPosition.map(function (value, index) {
return value - this.initialPosition[index];
}.bind(this));
},
startDrag(posFactor, dimFactor, event, type) {
document.body.addEventListener('mousemove', this.continueDrag);
document.body.addEventListener('mouseup', this.endDrag);
this.dragPosition = {
position: [this.item.x, this.item.y],
dimensions: [this.item.width, this.item.height]
};
this.updatePosition(event);
this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize);
this.isResizing = type === 'resize';
event.preventDefault();
},
continueDrag(event) {
event.preventDefault();
this.updatePosition(event);
this.dragPosition = this.activeDrag.getAdjustedPosition(this.delta);
},
endDrag(event) {
document.body.removeEventListener('mousemove', this.continueDrag);
document.body.removeEventListener('mouseup', this.endDrag);
this.continueDrag(event);
let [x, y] = this.dragPosition.position;
let [width, height] = this.dragPosition.dimensions;
this.$emit('endDrag', this.item, {x, y, width, height});
this.dragPosition = undefined;
this.initialPosition = undefined;
this.delta = undefined;
this.isResizing = undefined;
event.preventDefault();
}
}
}
</script>

View File

@@ -1,188 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
<template>
<div class="c-frame has-local-controls"
:style="item.config.style"
:class="[classObject, {
'u-inspectable': item.config.inspectable()
}]"
@dblclick="drill(item.id, $event)">
<component :is="item.type" :item="item" :gridSize="gridSize"></component>
<!-- Drag handles -->
<div class="c-frame-edit">
<div class="c-frame-edit__move"
@mousedown="startDrag([1,1], [0,0], $event)"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--nw"
@mousedown="startDrag([1,1], [-1,-1], $event)"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--ne"
@mousedown="startDrag([0,1], [1,-1], $event)"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--sw"
@mousedown="startDrag([1,0], [-1,1], $event)"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--se"
@mousedown="startDrag([0,0], [1,1], $event)"></div>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
/******************************* FRAME */
.c-frame {
display: flex;
flex-direction: column;
border: 1px solid transparent;
/*************************** NO-FRAME */
&.no-frame {
> [class*="contents"] > [class*="__header"] {
display: none;
}
}
&:not(.no-frame) {
background: $colorBodyBg;
border: 1px solid $colorInteriorBorder;
padding: $interiorMargin;
}
}
</style>
<script>
import SubobjectView from './SubobjectView.vue'
import TelemetryView from './TelemetryView.vue'
import BoxView from './BoxView.vue'
import TextView from './TextView.vue'
import LineView from './LineView.vue'
import ImageView from './ImageView.vue'
import LayoutDrag from './../LayoutDrag'
export default {
inject: ['openmct'],
props: {
item: Object,
gridSize: Array
},
components: {
SubobjectView,
TelemetryView,
BoxView,
TextView,
LineView,
ImageView
},
computed: {
classObject: function () {
return {
'is-drilled-in': this.item.drilledIn,
'no-frame': !this.item.config.hasFrame()
}
}
},
methods: {
drill(id, $event) {
if ($event) {
$event.stopPropagation();
}
if (!this.openmct.editor.isEditing()) {
return;
}
if (!this.item.domainObject) {
return;
}
if (this.openmct.composition.get(this.item.domainObject) === undefined) {
return;
}
// Disable for fixed position.
if (this.item.domainObject.type === 'telemetry.fixed') {
return;
}
this.$emit('drilledIn', id);
},
updatePosition(event) {
let currentPosition = [event.pageX, event.pageY];
this.initialPosition = this.initialPosition || currentPosition;
this.delta = currentPosition.map(function (value, index) {
return value - this.initialPosition[index];
}.bind(this));
},
startDrag(posFactor, dimFactor, event) {
document.body.addEventListener('mousemove', this.continueDrag);
document.body.addEventListener('mouseup', this.endDrag);
this.updatePosition(event);
this.activeDrag = new LayoutDrag(
this.item.config.position(),
posFactor,
dimFactor,
this.gridSize
);
event.preventDefault();
},
continueDrag(event) {
event.preventDefault();
this.updatePosition(event);
if (this.activeDrag) {
this.$emit(
'dragInProgress',
this.item,
this.activeDrag.getAdjustedPosition(this.delta)
);
}
},
endDrag(event) {
document.body.removeEventListener('mousemove', this.continueDrag);
document.body.removeEventListener('mouseup', this.endDrag);
this.continueDrag(event);
this.$emit('endDrag', this.item);
this.initialPosition = undefined;
event.preventDefault();
}
},
mounted() {
let context = {
item: this.item.domainObject,
layoutItem: this.item,
addElement: this.$parent.addElement
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el,
context,
this.item.initSelect
);
},
destroyed() {
this.removeSelectable();
}
}
</script>

View File

@@ -20,37 +20,214 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div>
<svg :width="gridSize[0] * element.width"
:height=" gridSize[1] * element.height">
<line :x1=" gridSize[0] * element.x1 + 1"
:y1="gridSize[1] * element.y1 + 1 "
:x2="gridSize[0] * element.x2 + 1"
:y2=" gridSize[1] * element.y2 + 1 "
:stroke="element.stroke"
stroke-width="2">
</line>
</svg>
</div>
</template>
<template>
<div class="l-layout__frame c-frame no-frame"
:style="style">
<svg width="100%" height="100%">
<line v-bind="linePosition"
:stroke="item.stroke"
stroke-width="2">
</line>
</svg>
<div class="c-frame-edit">
<div class="c-frame-edit__move"
@mousedown="startDrag($event)"></div>
<div class="c-frame-edit__handle"
:class="startHandleClass"
@mousedown="startDrag($event, 'start')"></div>
<div class="c-frame-edit__handle"
:class="endHandleClass"
@mousedown="startDrag($event, 'end')"></div>
</div>
</div>
</template>
<script>
const START_HANDLE_QUADRANTS = {
1: 'c-frame-edit__handle--sw',
2: 'c-frame-edit__handle--se',
3: 'c-frame-edit__handle--ne',
4: 'c-frame-edit__handle--nw'
};
const END_HANDLE_QUADRANTS = {
1: 'c-frame-edit__handle--ne',
2: 'c-frame-edit__handle--nw',
3: 'c-frame-edit__handle--sw',
4: 'c-frame-edit__handle--se'
};
export default {
makeDefinition() {
return {
x: 5,
y: 10,
x2: 10,
y2: 5,
stroke: '#717171',
useGrid: true
};
},
inject: ['openmct'],
props: {
item: Object,
gridSize: Array
gridSize: Array,
initSelect: Boolean,
index: Number,
},
data() {
return {
dragPosition: undefined
};
},
computed: {
element() {
return this.item.config.element;
position() {
let {x, y, x2, y2} = this.item;
if (this.dragPosition) {
({x, y, x2, y2} = this.dragPosition);
}
return {x, y, x2, y2};
},
style() {
let {x, y, x2, y2} = this.position;
let width = this.gridSize[0] * Math.abs(x - x2);
let height = this.gridSize[1] * Math.abs(y - y2);
let left = this.gridSize[0] * Math.min(x, x2);
let top = this.gridSize[1] * Math.min(y, y2);
return {
left: `${left}px`,
top: `${top}px`,
width: `${width}px`,
height: `${height}px`,
};
},
startHandleClass() {
return START_HANDLE_QUADRANTS[this.vectorQuadrant];
},
endHandleClass() {
return END_HANDLE_QUADRANTS[this.vectorQuadrant];
},
vectorQuadrant() {
let {x, y, x2, y2} = this.position;
if (x2 > x) {
if (y2 < y) {
return 1;
}
return 4;
}
if (y2 < y) {
return 2;
}
return 3;
},
linePosition() {
if (this.vectorQuadrant === 1) {
return {
x1: '0%',
y1: '100%',
x2: '100%',
y2: '0%'
};
}
if (this.vectorQuadrant === 4) {
return {
x1: '0%',
y1: '0%',
x2: '100%',
y2: '100%'
};
}
if (this.vectorQuadrant === 2) {
return {
x1: '0%',
y1: '0%',
x2: '100%',
y2: '100%'
};
}
if (this.vectorQuadrant === 3) {
return {
x1: '100%',
y1: '0%',
x2: '0%',
y2: '100%'
};
}
}
},
methods: {
startDrag(event, position) {
this.dragging = position;
document.body.addEventListener('mousemove', this.continueDrag);
document.body.addEventListener('mouseup', this.endDrag);
this.startPosition = [event.pageX, event.pageY];
this.dragPosition = {
x: this.item.x,
y: this.item.y,
x2: this.item.x2,
y2: this.item.y2
};
event.preventDefault();
},
continueDrag(event) {
event.preventDefault();
let pxDeltaX = this.startPosition[0] - event.pageX;
let pxDeltaY = this.startPosition[1] - event.pageY;
this.dragPosition = this.calculateDragPosition(pxDeltaX, pxDeltaY);
},
endDrag(event) {
document.body.removeEventListener('mousemove', this.continueDrag);
document.body.removeEventListener('mouseup', this.endDrag);
let {x, y, x2, y2} = this.dragPosition;
this.$emit('endDrag', this.item, {x, y, x2, y2});
this.dragPosition = undefined;
this.dragging = undefined;
event.preventDefault();
},
calculateDragPosition(pxDeltaX, pxDeltaY) {
let gridDeltaX = Math.round(pxDeltaX / this.gridSize[0]);
let gridDeltaY = Math.round(pxDeltaY / this.gridSize[0]); // TODO: should this be gridSize[1]?
let {x, y, x2, y2} = this.item;
let dragPosition = {x, y, x2, y2};
if (this.dragging === 'start') {
dragPosition.x -= gridDeltaX;
dragPosition.y -= gridDeltaY;
} else if (this.dragging === 'end') {
dragPosition.x2 -= gridDeltaX;
dragPosition.y2 -= gridDeltaY;
} else {
// dragging entire line.
dragPosition.x -= gridDeltaX;
dragPosition.y -= gridDeltaY;
dragPosition.x2 -= gridDeltaX;
dragPosition.y2 -= gridDeltaY;
}
return dragPosition;
}
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex;
}
},
mounted() {
this.item.config.attachListeners();
this.context = {
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
},
destroyed() {
this.item.config.removeListeners();
if (this.removeSelectable) {
this.removeSelectable();
}
}
}
</script>
</script>

View File

@@ -20,99 +20,101 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="u-contents">
<div class="c-so-view__header">
<div class="c-so-view__header__start">
<div class="c-so-view__name icon-object">{{ item.domainObject.name }}</div>
<div class="c-so-view__context-actions c-disclosure-button"></div>
</div>
<div class="c-so-view__header__end">
<div class="c-button icon-expand local-controls--hidden"></div>
</div>
</div>
<object-view class="c-so-view__object-view"
:object="item.domainObject"></object-view>
</div>
<layout-frame :item="item"
:grid-size="gridSize"
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
<object-frame v-if="domainObject"
:domain-object="domainObject"
:object-path="objectPath"
:has-frame="item.hasFrame">
</object-frame>
</layout-frame>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-so-view {
/*************************** HEADER */
&__header {
display: flex;
align-items: center;
flex: 0 0 auto;
margin-bottom: $interiorMargin;
> [class*="__"] {
display: flex;
align-items: center;
}
> * + * {
margin-left: $interiorMargin;
}
[class*="__start"] {
flex: 1 1 auto;
overflow: hidden;
}
[class*="__end"] {
//justify-content: flex-end;
flex: 0 0 auto;
[class*="button"] {
font-size: 0.7em;
}
}
}
&__name {
@include ellipsize();
flex: 0 1 auto;
font-size: 1.2em;
&:before {
// Object type icon
flex: 0 0 auto;
margin-right: $interiorMarginSm;
}
}
/*************************** OBJECT VIEW */
&__object-view {
flex: 1 1 auto;
overflow: auto;
.c-object-view {
.u-fills-container {
// Expand component types that fill a container
@include abs();
}
}
}
}
</style>
<script>
import ObjectView from '../../../ui/components/layout/ObjectView.vue'
import ObjectFrame from '../../../ui/components/ObjectFrame.vue'
import LayoutFrame from './LayoutFrame.vue'
const MINIMUM_FRAME_SIZE = [320, 180],
DEFAULT_DIMENSIONS = [10, 10],
DEFAULT_POSITION = [1, 1],
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget'];
function getDefaultDimensions(gridSize) {
return MINIMUM_FRAME_SIZE.map((min, index) => {
return Math.max(
Math.ceil(min / gridSize[index]),
DEFAULT_DIMENSIONS[index]
);
});
}
function hasFrameByDefault(type) {
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
}
export default {
makeDefinition(openmct, gridSize, domainObject, position) {
let defaultDimensions = getDefaultDimensions(gridSize);
position = position || DEFAULT_POSITION;
return {
width: defaultDimensions[0],
height: defaultDimensions[1],
x: position[0],
y: position[1],
identifier: domainObject.identifier,
hasFrame: hasFrameByDefault(domainObject.type),
useGrid: true
};
},
inject: ['openmct'],
props: {
item: Object
item: Object,
gridSize: Array,
initSelect: Boolean,
index: Number
},
data() {
return {
domainObject: undefined,
objectPath: []
}
},
components: {
ObjectView,
ObjectFrame,
LayoutFrame
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex;
}
},
methods: {
setObject(domainObject) {
this.domainObject = domainObject;
this.objectPath = [this.domainObject].concat(this.openmct.router.path);
this.context = {
item: domainObject,
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
}
},
mounted() {
this.item.config.attachListeners();
this.openmct.objects.get(this.item.identifier)
.then(this.setObject);
},
destroyed() {
this.item.config.removeListeners();
if (this.removeSelectable) {
this.removeSelectable();
}
}
}
</script>

View File

@@ -21,20 +21,25 @@
*****************************************************************************/
<template>
<div class="c-telemetry-view"
:style="styleObject">
<div v-if="showLabel"
class="c-telemetry-view__label">
<div class="c-telemetry-view__label-text">{{ item.domainObject.name }}</div>
</div>
<layout-frame :item="item"
:grid-size="gridSize"
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
<div class="c-telemetry-view"
:style="styleObject"
v-if="domainObject">
<div v-if="showLabel"
class="c-telemetry-view__label">
<div class="c-telemetry-view__label-text">{{ domainObject.name }}</div>
</div>
<div v-if="showValue"
:title="fieldName"
class="c-telemetry-view__value"
:class="[telemetryClass]">
<div class="c-telemetry-view__value-text">{{ telemetryValue }}</div>
<div v-if="showValue"
:title="fieldName"
class="c-telemetry-view__value"
:class="[telemetryClass]">
<div class="c-telemetry-view__value-text">{{ telemetryValue }}</div>
</div>
</div>
</div>
</layout-frame>
</template>
<style lang="scss">
@@ -72,37 +77,66 @@
</style>
<script>
import LayoutFrame from './LayoutFrame.vue'
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
DEFAULT_POSITION = [1, 1];
export default {
makeDefinition(openmct, gridSize, domainObject, position) {
let metadata = openmct.telemetry.getMetadata(domainObject);
position = position || DEFAULT_POSITION;
return {
identifier: domainObject.identifier,
x: position[0],
y: position[1],
width: DEFAULT_TELEMETRY_DIMENSIONS[0],
height: DEFAULT_TELEMETRY_DIMENSIONS[1],
displayMode: 'all',
value: metadata.getDefaultDisplayValue(),
stroke: "transparent",
fill: "",
color: "",
size: "13px",
useGrid: true
};
},
inject: ['openmct'],
props: {
item: Object
item: Object,
gridSize: Array,
initSelect: Boolean,
index: Number
},
components: {
LayoutFrame
},
computed: {
showLabel() {
let displayMode = this.item.config.alphanumeric.displayMode;
let displayMode = this.item.displayMode;
return displayMode === 'all' || displayMode === 'label';
},
showValue() {
let displayMode = this.item.config.alphanumeric.displayMode;
let displayMode = this.item.displayMode;
return displayMode === 'all' || displayMode === 'value';
},
styleObject() {
let alphanumeric = this.item.config.alphanumeric;
return {
backgroundColor: alphanumeric.fill,
borderColor: alphanumeric.stroke,
color: alphanumeric.color,
fontSize: alphanumeric.size
backgroundColor: this.item.fill,
borderColor: this.item.stroke,
color: this.item.color,
fontSize: this.item.size
}
},
fieldName() {
return this.valueMetadata.name;
return this.valueMetadata && this.valueMetadata.name;
},
valueMetadata() {
return this.metadata.value(this.item.config.alphanumeric.value);
return this.datum && this.metadata.value(this.item.value);
},
valueFormatter() {
return this.formats[this.item.config.alphanumeric.value];
return this.formats[this.item.value];
},
telemetryValue() {
if (!this.datum) {
@@ -122,8 +156,18 @@
},
data() {
return {
datum: {},
formats: {}
datum: undefined,
formats: undefined,
domainObject: undefined
}
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex;
}
},
methods: {
@@ -134,7 +178,7 @@
end: bounds.end,
size: 1
};
this.openmct.telemetry.request(this.item.domainObject, options)
this.openmct.telemetry.request(this.domainObject, options)
.then(data => {
if (data.length > 0) {
this.updateView(data[data.length - 1]);
@@ -142,7 +186,7 @@
});
},
subscribeToObject() {
this.subscription = this.openmct.telemetry.subscribe(this.item.domainObject, function (datum) {
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
if (this.openmct.time.clock() !== undefined) {
this.updateView(datum);
}
@@ -160,26 +204,40 @@
refreshData(bounds, isTick) {
if (!isTick) {
this.datum = undefined;
this.requestHistoricalData(this.item.domainObject);
this.requestHistoricalData(this.domainObject);
}
},
setObject(domainObject) {
this.domainObject = domainObject;
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
this.requestHistoricalData();
this.subscribeToObject();
this.context = {
item: domainObject,
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
}
},
mounted() {
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.item.domainObject);
this.metadata = this.openmct.telemetry.getMetadata(this.item.domainObject);
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
this.requestHistoricalData();
this.subscribeToObject();
this.item.config.attachListeners();
this.openmct.objects.get(this.item.identifier)
.then(this.setObject);
this.openmct.time.on("bounds", this.refreshData);
},
destroyed() {
this.removeSubscription();
this.item.config.removeListeners();
if (this.removeSelectable) {
this.removeSelectable();
}
this.openmct.time.off("bounds", this.refreshData);
}
}
</script>
</script>

View File

@@ -21,10 +21,14 @@
*****************************************************************************/
<template>
<div class="c-text-view"
:style="styleObject">
{{ item.config.element.text }}
</div>
<layout-frame :item="item"
:grid-size="gridSize"
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
<div class="c-text-view"
:style="style">
{{ item.text }}
</div>
</layout-frame>
</template>
<style lang="scss">
@@ -42,26 +46,65 @@
</style>
<script>
import LayoutFrame from './LayoutFrame.vue'
export default {
makeDefinition(openmct, gridSize, element) {
return {
fill: 'transparent',
stroke: 'transparent',
size: '13px',
color: '',
x: 1,
y: 1,
width: 10,
height: 5,
text: element.text,
useGrid: true
};
},
inject: ['openmct'],
props: {
item: Object
item: Object,
gridSize: Array,
index: Number,
initSelect: Boolean
},
components: {
LayoutFrame
},
computed: {
styleObject() {
let element = this.item.config.element;
style() {
return {
backgroundColor: element.fill,
borderColor: element.stroke,
color: element.color,
fontSize: element.size
backgroundColor: this.item.fill,
borderColor: this.item.stroke,
color: this.item.color,
fontSize: this.item.size
};
}
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex;
}
},
mounted() {
this.item.config.attachListeners();
this.context = {
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
},
destroyed() {
this.item.config.removeListeners();
if (this.removeSelectable) {
this.removeSelectable();
}
}
}
</script>
</script>

View File

@@ -33,6 +33,9 @@ export default function () {
canView: function (domainObject) {
return domainObject.type === 'layout';
},
canEdit: function (domainObject) {
return domainObject.type === 'layout';
},
view: function (domainObject) {
let component;
return {
@@ -57,7 +60,9 @@ export default function () {
getSelectionContext() {
return {
item: domainObject,
addElement: component && component.$refs.displayLayout.addElement
addElement: component && component.$refs.displayLayout.addElement,
removeItem: component && component.$refs.displayLayout.removeItem,
orderItem: component && component.$refs.displayLayout.orderItem
}
},
destroy() {

View File

@@ -22,47 +22,52 @@
<template>
<div class="c-fl-container"
:style="[{'flex-basis': size}]"
:class="{'is-empty': frames.length === 1}">
<div class="c-fl-container__header icon-grippy-ew"
:style="[{'flex-basis': sizeString}]"
:class="{'is-empty': !frames.length}">
<div class="c-fl-container__header"
v-show="isEditing"
draggable="true"
@dragstart="startContainerDrag"
@dragend="stopContainerDrag">
<span class="c-fl-container__size-indicator">{{ size }}</span>
@dragstart="startContainerDrag">
<span class="c-fl-container__size-indicator">{{ sizeString }}</span>
</div>
<drop-hint
class="c-fl-frame__drop-hint"
:index="-1"
:allow-drop="allowDrop"
@object-drop-to="moveOrCreateFrame">
</drop-hint>
<div class="c-fl-container__frames-holder">
<div class="u-contents"
v-for="(frame, i) in frames"
:key="i">
<template
v-for="(frame, i) in frames">
<frame-component
class="c-fl-container__frame"
:style="{
'flex-basis': `${frame.height}%`
}"
:key="frame.id"
:frame="frame"
:size="frame.height"
:index="i"
:containerIndex="index"
:isEditing="isEditing"
:isDragging="isDragging"
@frame-drag-from="frameDragFrom"
@frame-drop-to="frameDropTo"
@delete-frame="promptBeforeDeletingFrame"
@add-container="addContainer">
:containerIndex="index">
</frame-component>
<drop-hint
class="c-fl-frame__drop-hint"
:key="i"
:index="i"
:allowDrop="allowDrop"
@object-drop-to="moveOrCreateFrame">
</drop-hint>
<resize-handle
v-if="i !== 0 && (i !== frames.length - 1)"
v-show="isEditing"
v-if="(i !== frames.length - 1)"
:key="i"
:index="i"
:orientation="rowsLayout ? 'horizontal' : 'vertical'"
@init-move="startFrameResizing"
@move="frameResizing"
@end-move="endFrameResizing">
</resize-handle>
</div>
</template>
</div>
</div>
</template>
@@ -71,61 +76,98 @@
import FrameComponent from './frame.vue';
import Frame from '../utils/frame';
import ResizeHandle from './resizeHandle.vue';
import DropHint from './dropHint.vue';
import isEditingMixin from '../mixins/isEditing';
const SNAP_TO_PERCENTAGE = 1;
const MIN_FRAME_SIZE = 5;
export default {
inject:['openmct', 'domainObject'],
props: ['size', 'frames', 'index', 'isEditing', 'isDragging', 'rowsLayout'],
inject:['openmct'],
props: ['container', 'index', 'rowsLayout'],
mixins: [isEditingMixin],
components: {
FrameComponent,
ResizeHandle
ResizeHandle,
DropHint
},
data() {
return {
initialPos: 0,
frameIndex: 0,
maxMoveSize: 0
computed: {
frames() {
return this.container.frames;
},
sizeString() {
return `${Math.round(this.container.size)}%`
}
},
methods: {
frameDragFrom(frameIndex) {
this.$emit('frame-drag-from', this.index, frameIndex);
},
frameDropTo(frameIndex, event) {
let domainObject = event.dataTransfer.getData('domainObject'),
frameObject;
allowDrop(event, index) {
if (event.dataTransfer.types.includes('openmct/domain-object-path')) {
return true;
}
let frameId = event.dataTransfer.getData('frameid'),
containerIndex = Number(event.dataTransfer.getData('containerIndex'));
if (domainObject) {
frameObject = new Frame(JSON.parse(domainObject));
if (!frameId) {
return false;
}
this.$emit('frame-drop-to', this.index, frameIndex, frameObject);
if (containerIndex === this.index) {
let frame = this.container.frames.filter((f) => f.id === frameId)[0],
framePos = this.container.frames.indexOf(frame);
if (index === -1) {
return framePos !== 0;
} else {
return framePos !== index && (framePos - 1) !== index
}
} else {
return true;
}
},
moveOrCreateFrame(insertIndex, event) {
if (event.dataTransfer.types.includes('openmct/domain-object-path')) {
// create frame using domain object
let domainObject = JSON.parse(event.dataTransfer.getData('openmct/domain-object-path'))[0];
this.$emit(
'create-frame',
this.index,
insertIndex,
domainObject.identifier
);
return;
};
// move frame.
let frameId = event.dataTransfer.getData('frameid');
let containerIndex = Number(event.dataTransfer.getData('containerIndex'));
this.$emit(
'move-frame',
this.index,
insertIndex,
frameId,
containerIndex
);
},
startFrameResizing(index) {
let beforeFrame = this.frames[index],
afterFrame = this.frames[index + 1];
this.maxMoveSize = beforeFrame.height + afterFrame.height;
this.maxMoveSize = beforeFrame.size + afterFrame.size;
},
frameResizing(index, delta, event) {
let percentageMoved = (delta / this.getElSize(this.$el))*100,
let percentageMoved = Math.round(delta / this.getElSize() * 100),
beforeFrame = this.frames[index],
afterFrame = this.frames[index + 1];
beforeFrame.height = this.snapToPercentage(beforeFrame.height + percentageMoved);
afterFrame.height = this.snapToPercentage(afterFrame.height - percentageMoved);
beforeFrame.size = this.getFrameSize(beforeFrame.size + percentageMoved);
afterFrame.size = this.getFrameSize(afterFrame.size - percentageMoved);
},
endFrameResizing(index, event) {
this.persist();
},
getElSize(el) {
getElSize() {
if (this.rowsLayout) {
return el.offsetWidth;
return this.$el.offsetWidth;
} else {
return el.offsetHeight;
return this.$el.offsetHeight;
}
},
getFrameSize(size) {
@@ -137,76 +179,23 @@ export default {
return size;
}
},
snapToPercentage(value){
let rem = value % SNAP_TO_PERCENTAGE,
roundedValue;
if (rem < 0.5) {
roundedValue = Math.floor(value/SNAP_TO_PERCENTAGE)*SNAP_TO_PERCENTAGE;
} else {
roundedValue = Math.ceil(value/SNAP_TO_PERCENTAGE)*SNAP_TO_PERCENTAGE;
}
return this.getFrameSize(roundedValue);
},
persist() {
this.$emit('persist', this.index);
},
promptBeforeDeletingFrame(frameIndex) {
let deleteFrame = this.deleteFrame;
let prompt = this.openmct.overlays.dialog({
iconClass: 'alert',
message: `This action will remove ${this.frames[frameIndex].domainObject.name} from this Flexible Layout. Do you want to continue?`,
buttons: [
{
label: 'Ok',
emphasis: 'true',
callback: function () {
deleteFrame(frameIndex);
prompt.dismiss();
},
},
{
label: 'Cancel',
callback: function () {
prompt.dismiss();
}
}
]
});
},
deleteFrame(frameIndex) {
this.frames.splice(frameIndex, 1);
this.$parent.recalculateOldFrameSize(this.frames);
this.persist();
},
deleteContainer() {
this.$emit('delete-container', this.index);
},
addContainer() {
this.$emit('add-container', this.index);
},
startContainerDrag(event) {
event.stopPropagation();
this.$emit('start-container-drag', this.index);
},
stopContainerDrag(event) {
event.stopPropagation();
this.$emit('stop-container-drag');
event.dataTransfer.setData('containerid', this.container.id);
}
},
mounted() {
let context = {
item: this.domainObject,
method: this.deleteContainer,
item: this.$parent.domainObject,
addContainer: this.addContainer,
index: this.index,
type: 'container'
type: 'container',
containerId: this.container.id
}
this.unsubscribeSelection = this.openmct.selection.selectable(this.$el, context, false);
},
},
beforeDestroy() {
this.unsubscribeSelection();
}

View File

@@ -21,7 +21,7 @@
*****************************************************************************/
<template>
<div>
<div v-show="isValidTarget">
<div class="c-drop-hint c-drop-hint--always-show"
:class="{'is-mouse-over': isMouseOver}"
@dragenter="dragenter"
@@ -37,10 +37,17 @@
<script>
export default {
props:['index'],
props:{
index: Number,
allowDrop: {
type: Function,
required: true
}
},
data() {
return {
isMouseOver: false
isMouseOver: false,
isValidTarget: false
}
},
methods: {
@@ -51,8 +58,23 @@ export default {
this.isMouseOver = false;
},
dropHandler(event) {
this.$emit('object-drop-to', event, this.index);
this.$emit('object-drop-to', this.index, event);
this.isValidTarget = false;
},
dragstart(event) {
this.isValidTarget = this.allowDrop(event, this.index);
},
dragend() {
this.isValidTarget = false;
}
},
mounted() {
document.addEventListener('dragstart', this.dragstart);
document.addEventListener('dragend', this.dragend);
},
destroyed() {
document.removeEventListener('dragstart', this.dragstart);
document.removeEventListener('dragend', this.dragend);
}
}
</script>

View File

@@ -1,3 +1,25 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
<template>
<div class="c-fl">
<div class="c-fl__empty"
@@ -10,40 +32,31 @@
'c-fl--rows': rowsLayout === true
}">
<div class="u-contents"
v-for="(container, index) in containers"
:key="index">
<template v-for="(container, index) in containers">
<drop-hint
style="flex-basis: 15px;"
class="c-fl-frame__drop-hint"
v-if="index === 0 && containers.length > 1"
v-show="isContainerDragging"
:key="index"
:index="-1"
@object-drop-to="containerDropTo">
:allow-drop="allowContainerDrop"
@object-drop-to="moveContainer">
</drop-hint>
<container-component
class="c-fl__container"
ref="containerComponent"
:key="container.id"
:index="index"
:size="`${Math.round(container.width)}%`"
:frames="container.frames"
:isEditing="isEditing"
:isDragging="isDragging"
:container="container"
:rowsLayout="rowsLayout"
@addFrame="addFrame"
@frame-drag-from="frameDragFromHandler"
@frame-drop-to="frameDropToHandler"
@persist="persist"
@delete-container="promptBeforeDeletingContainer"
@add-container="addContainer"
@start-container-drag="startContainerDrag"
@stop-container-drag="stopContainerDrag">
@move-frame="moveFrame"
@create-frame="createFrame"
@persist="persist">
</container-component>
<resize-handle
v-if="index !== (containers.length - 1)"
v-show="isEditing"
:key="index"
:index="index"
:orientation="rowsLayout ? 'vertical' : 'horizontal'"
@init-move="startContainerResizing"
@@ -52,19 +65,36 @@
</resize-handle>
<drop-hint
style="flex-basis: 15px;"
class="c-fl-frame__drop-hint"
v-if="containers.length > 1"
v-show="isContainerDragging"
:key="index"
:index="index"
@object-drop-to="containerDropTo">
:allowDrop="allowContainerDrop"
@object-drop-to="moveContainer">
</drop-hint>
</div>
</div>
</template>
</div>
</div>
</template>
<style lang="scss">
@import '~styles/sass-base';
@mixin containerGrippy($headerSize, $dir) {
position: absolute;
$h: 6px;
$minorOffset: ($headerSize - $h) / 2;
$majorOffset: 35%;
content: '';
display: block;
position: absolute;
@include grippy($c: $editFrameSelectedMovebarColorFg, $dir: $dir);
@if $dir == 'x' {
top: $minorOffset; right: $majorOffset; bottom: $minorOffset; left: $majorOffset;
} @else {
top: $majorOffset; right: $minorOffset; bottom: $majorOffset; left: $minorOffset;
}
}
.c-fl {
@include abs();
@@ -86,7 +116,6 @@
> * + * { margin-left: 1px; }
&[class*='--rows'] {
//@include test(blue, 0.1);
flex-direction: column;
> * + * {
margin-left: 0;
@@ -114,7 +143,6 @@
/***************************************************** CONTAINERS */
$headerSize: 16px;
border: 1px solid transparent;
display: flex;
flex-direction: column;
overflow: auto;
@@ -124,9 +152,9 @@
flex-shrink: 1;
&__header {
// Only displayed when editing
background: $editSelectableColor;
color: $editSelectableColorFg;
// Only displayed when editing, controlled via JS
background: $editFrameMovebarColorBg;
color: $editFrameMovebarColorFg;
cursor: move;
display: flex;
align-items: center;
@@ -134,12 +162,8 @@
&:before {
// Drag grippy
font-size: 0.8em;
@include containerGrippy($headerSize, 'x');
opacity: 0.5;
position: absolute;
left: 50%; top: 50%;
transform-origin: center;
transform: translate(-50%, -50%);
}
}
@@ -159,19 +183,27 @@
}
.is-editing & {
//background: $editCanvasColorBg;
border-color: $editSelectableColor;
&:hover {
border-color: $editSelectableColorHov;
.c-fl-container__header {
background: $editFrameHovMovebarColorBg;
color: $editFrameHovMovebarColorFg;
&:before {
opacity: .75;
}
}
}
&[s-selected] {
border-color: $editSelectableColorSelected;
border: $editFrameSelectedBorder;
.c-fl-container__header {
background: $editSelectableColorSelected;
color: $editSelectableColorSelectedFg;
background:$editFrameSelectedMovebarColorBg;
color: $editFrameSelectedMovebarColorFg;
&:before {
// Grippy
opacity: 1;
}
}
}
}
@@ -180,11 +212,6 @@
// Frames get styled here because this is particular to their presence in this layout type
.c-fl-frame {
@include browserPrefix(margin-collapse, collapse);
margin: 1px;
//&__drag-wrapper {
// border: 1px solid $colorInteriorBorder; // Now handled by is-selectable
//}
}
/****** ROWS LAYOUT */
@@ -197,8 +224,8 @@
overflow: hidden;
&:before {
// Drag grippy
transform: rotate(90deg) translate(-50%, 50%);
// Grippy
@include containerGrippy($headerSize, 'y');
}
}
@@ -221,8 +248,6 @@
$dropHintSize: 15px;
display: flex;
// justify-content: stretch;
// align-items: stretch;
flex: 1 1;
flex-direction: column;
overflow: hidden; // Needed to allow frames to collapse when sized down
@@ -261,7 +286,6 @@
pointer-events: none;
text-align: center;
width: $size;
z-index: 2;
// Changed when layout is different, see below
border-top-right-radius: $controlCr;
@@ -290,37 +314,16 @@
&:before {
// The visible resize line
background: $editColor;
background: $editUIColor;
content: '';
display: block;
flex: 1 1 auto;
min-height: $size; min-width: $size;
}
&__grippy {
// Grippy element
$d: 4px;
$c: black;
$a: 0.9;
$d: 5px;
background: $editColor;
color: $editColorBg;
border-radius: $smallCr;
font-size: 0.8em;
height: $d;
width: $d * 10;
position: absolute;
left: 50%; top: 50%;
text-align: center;
transform-origin: center center;
transform: translate(-50%, -50%);
z-index: 10;
}
&.vertical {
padding: $margin $size;
&:hover{
// padding: $marginHov 0;
cursor: row-resize;
}
}
@@ -328,20 +331,15 @@
&.horizontal {
padding: $size $margin;
&:hover{
// padding: 0 $marginHov;
cursor: col-resize;
}
[class*='grippy'] {
transform: translate(-50%) rotate(90deg);
}
}
&:hover {
transition: $transOut;
&:before {
// The visible resize line
background: $editColorHov;
background: $editUIColorHov;
}
}
}
@@ -383,7 +381,7 @@
}
&__drop-hint {
flex: 1 1 100%;
flex: 1 0 100%;
margin: 0;
}
}
@@ -397,31 +395,63 @@
<script>
import ContainerComponent from './container.vue';
import Container from '../utils/container';
import Frame from '../utils/frame';
import ResizeHandle from './resizeHandle.vue';
import DropHint from './dropHint.vue';
import isEditingMixin from '../mixins/isEditing';
const SNAP_TO_PERCENTAGE = 1,
MIN_CONTAINER_SIZE = 5;
const MIN_CONTAINER_SIZE = 5;
// Resize items so that newItem fits proportionally (newItem must be an element
// of items). If newItem does not have a size or is sized at 100%, newItem will
// have size set to 1/n * 100, where n is the total number of items.
function sizeItems(items, newItem) {
if (items.length === 1) {
newItem.size = 100;
} else {
if (!newItem.size || newItem.size === 100) {
newItem.size = Math.round(100 / items.length);
}
let oldItems = items.filter(item => item !== newItem);
// Resize oldItems to fit inside remaining space;
let remainder = 100 - newItem.size;
oldItems.forEach((item) => {
item.size = Math.round(item.size * remainder / 100);
});
// Ensure items add up to 100 in case of rounding error.
let total = items.reduce((t, item) => t + item.size, 0);
let excess = Math.round(100 - total);
oldItems[oldItems.length - 1].size += excess;
}
}
// Scales items proportionally so total is equal to 100. Assumes that an item
// was removed from array.
function sizeToFill(items) {
if (items.length === 0) {
return;
}
let oldTotal = items.reduce((total, item) => total + item.size, 0);
items.forEach((item) => {
item.size = Math.round(item.size * 100 / oldTotal);
});
// Ensure items add up to 100 in case of rounding error.
let total = items.reduce((t, item) => t + item.size, 0);
let excess = Math.round(100 - total);
items[items.length - 1].size += excess;
}
export default {
inject: ['openmct', 'domainObject'],
inject: ['openmct', 'layoutObject'],
mixins: [isEditingMixin],
components: {
ContainerComponent,
ResizeHandle,
DropHint
},
data() {
let containers = this.domainObject.configuration.containers,
rowsLayout = this.domainObject.configuration.rowsLayout;
return {
containers: containers,
dragFrom: [],
isEditing: false,
isDragging: false,
isContainerDragging: false,
rowsLayout: rowsLayout,
maxMoveSize: 0
domainObject: this.layoutObject
}
},
computed: {
@@ -431,144 +461,105 @@ export default {
} else {
return 'Columns'
}
},
containers() {
return this.domainObject.configuration.containers;
},
rowsLayout() {
return this.domainObject.configuration.rowsLayout;
}
},
methods: {
areAllContainersEmpty() {
return !!!this.containers.filter(container => container.frames.length > 1).length;
return !!!this.containers.filter(container => container.frames.length).length;
},
addContainer() {
let newSize = 100/(this.containers.length+1);
let container = new Container(newSize)
this.recalculateContainerSize(newSize);
let container = new Container();
this.containers.push(container);
},
recalculateContainerSize(newSize) {
this.containers.forEach((container) => {
container.width = newSize;
});
},
recalculateNewFrameSize(multFactor, framesArray){
framesArray.forEach((frame, index) => {
if (index === 0) {
return;
}
let frameSize = frame.height
frame.height = this.snapToPercentage(multFactor * frameSize);
});
},
recalculateOldFrameSize(framesArray) {
let totalRemainingSum = framesArray.map((frame,i) => {
if (i !== 0) {
return frame.height
} else {
return 0;
}
}).reduce((a, c) => a + c);
framesArray.forEach((frame, index) => {
if (index === 0) {
return;
}
if (framesArray.length === 2) {
frame.height = 100;
} else {
let newSize = frame.height + ((frame.height / totalRemainingSum) * (100 - totalRemainingSum));
frame.height = this.snapToPercentage(newSize);
}
});
},
addFrame(frame, index) {
this.containers[index].addFrame(frame);
},
frameDragFromHandler(containerIndex, frameIndex) {
this.dragFrom = [containerIndex, frameIndex];
},
frameDropToHandler(containerIndex, frameIndex, frameObject) {
let newContainer = this.containers[containerIndex];
this.isDragging = false;
if (!frameObject) {
frameObject = this.containers[this.dragFrom[0]].frames.splice(this.dragFrom[1], 1)[0];
this.recalculateOldFrameSize(this.containers[this.dragFrom[0]].frames);
}
if (!frameObject.height) {
frameObject.height = 100 / Math.max(newContainer.frames.length - 1, 1);
}
newContainer.frames.splice((frameIndex + 1), 0, frameObject);
let newTotalHeight = newContainer.frames.reduce((total, frame) => {
let num = Number(frame.height);
if(isNaN(num)) {
return total;
} else {
return total + num;
}
},0);
let newMultFactor = 100 / newTotalHeight;
this.recalculateNewFrameSize(newMultFactor, newContainer.frames);
sizeItems(this.containers, container);
this.persist();
},
deleteContainer(containerId) {
let container = this.containers.filter(c => c.id === containerId)[0];
let containerIndex = this.containers.indexOf(container);
this.containers.splice(containerIndex, 1);
sizeToFill(this.containers);
this.persist();
},
moveFrame(toContainerIndex, toFrameIndex, frameId, fromContainerIndex) {
let toContainer = this.containers[toContainerIndex];
let fromContainer = this.containers[fromContainerIndex];
let frame = fromContainer.frames.filter(f => f.id === frameId)[0];
let fromIndex = fromContainer.frames.indexOf(frame);
fromContainer.frames.splice(fromIndex, 1);
sizeToFill(fromContainer.frames);
toContainer.frames.splice(toFrameIndex + 1, 0, frame);
sizeItems(toContainer.frames, frame);
this.persist();
},
createFrame(containerIndex, insertFrameIndex, objectIdentifier) {
let frame = new Frame(objectIdentifier);
let container = this.containers[containerIndex];
container.frames.splice(insertFrameIndex + 1, 0, frame);
sizeItems(container.frames, frame);
this.persist();
},
deleteFrame(frameId) {
let container = this.containers
.filter(c => c.frames.some(f => f.id === frameId))[0];
let containerIndex = this.containers.indexOf(container);
let frame = container
.frames
.filter((f => f.id === frameId))[0];
let frameIndex = container.frames.indexOf(frame);
container.frames.splice(frameIndex, 1);
sizeToFill(container.frames);
this.persist(containerIndex);
},
allowContainerDrop(event, index) {
if (!event.dataTransfer.types.includes('containerid')) {
return false;
}
let containerId = event.dataTransfer.getData('containerid'),
container = this.containers.filter((c) => c.id === containerId)[0],
containerPos = this.containers.indexOf(container);
if (index === -1) {
return containerPos !== 0;
} else {
return containerPos !== index && (containerPos - 1) !== index
}
},
persist(index){
if (index) {
this.openmct.objects.mutate(this.domainObject, `.configuration.containers[${index}]`, this.containers[index]);
this.openmct.objects.mutate(this.domainObject, `configuration.containers[${index}]`, this.containers[index]);
} else {
this.openmct.objects.mutate(this.domainObject, '.configuration.containers', this.containers);
this.openmct.objects.mutate(this.domainObject, 'configuration.containers', this.containers);
}
},
isEditingHandler(isEditing) {
this.isEditing = isEditing;
if (this.isEditing) {
this.$el.click(); //force selection of flexible-layout for toolbar
}
if (this.isDragging && isEditing === false) {
this.isDragging = false;
}
},
dragstartHandler() {
if (this.isEditing) {
this.isDragging = true;
}
},
dragendHandler() {
this.isDragging = false;
},
startContainerResizing(index) {
let beforeContainer = this.containers[index],
afterContainer = this.containers[index + 1];
this.maxMoveSize = beforeContainer.width + afterContainer.width;
this.maxMoveSize = beforeContainer.size + afterContainer.size;
},
containerResizing(index, delta, event) {
let percentageMoved = (delta/this.getElSize(this.$el))*100,
let percentageMoved = Math.round(delta / this.getElSize() * 100),
beforeContainer = this.containers[index],
afterContainer = this.containers[index + 1];
beforeContainer.width = this.getContainerSize(this.snapToPercentage(beforeContainer.width + percentageMoved));
afterContainer.width = this.getContainerSize(this.snapToPercentage(afterContainer.width - percentageMoved));
beforeContainer.size = this.getContainerSize(beforeContainer.size + percentageMoved);
afterContainer.size = this.getContainerSize(afterContainer.size - percentageMoved);
},
endContainerResizing(event) {
this.persist();
},
getElSize(el) {
getElSize() {
if (this.rowsLayout) {
return el.offsetHeight;
return this.$el.offsetHeight;
} else {
return el.offsetWidth;
return this.$el.offsetWidth;
}
},
getContainerSize(size) {
@@ -580,80 +571,19 @@ export default {
return size;
}
},
snapToPercentage(value) {
let rem = value % SNAP_TO_PERCENTAGE,
roundedValue;
if (rem < 0.5) {
roundedValue = Math.floor(value/SNAP_TO_PERCENTAGE)*SNAP_TO_PERCENTAGE;
updateDomainObject(newDomainObject) {
this.domainObject = newDomainObject;
},
moveContainer(toIndex, event) {
let containerId = event.dataTransfer.getData('containerid');
let container = this.containers.filter(c => c.id === containerId)[0];
let fromIndex = this.containers.indexOf(container);
this.containers.splice(fromIndex, 1);
if (fromIndex > toIndex) {
this.containers.splice(toIndex + 1, 0, container);
} else {
roundedValue = Math.ceil(value/SNAP_TO_PERCENTAGE)*SNAP_TO_PERCENTAGE;
this.containers.splice(toIndex, 0, container);
}
return roundedValue;
},
toggleLayoutDirection(v) {
this.rowsLayout = v;
},
promptBeforeDeletingContainer(containerIndex) {
let deleteContainer = this.deleteContainer;
let prompt = this.openmct.overlays.dialog({
iconClass: 'alert',
message: `This action will permanently delete container ${containerIndex + 1} from this Flexible Layout`,
buttons: [
{
label: 'Ok',
emphasis: 'true',
callback: function () {
deleteContainer(containerIndex);
prompt.dismiss();
},
},
{
label: 'Cancel',
callback: function () {
prompt.dismiss();
}
}
]
});
},
deleteContainer(containerIndex) {
this.containers.splice(containerIndex, 1);
this.recalculateContainerSize(100/this.containers.length);
this.persist();
},
addContainer(containerIndex) {
let newContainer = new Container();
if (typeof containerIndex === 'number') {
this.containers.splice(containerIndex+1, 0, newContainer);
} else {
this.containers.push(newContainer);
}
this.recalculateContainerSize(100/this.containers.length);
this.persist();
},
startContainerDrag(index) {
this.isContainerDragging = true;
this.containerDragFrom = index;
},
stopContainerDrag() {
this.isContainerDragging = false;
},
containerDropTo(event, index) {
let fromContainer = this.containers.splice(this.containerDragFrom, 1)[0];
if (index === -1) {
this.containers.unshift(fromContainer);
} else {
this.containers.splice(index, 0, fromContainer);
}
this.persist();
}
},
@@ -662,23 +592,17 @@ export default {
let context = {
item: this.domainObject,
addContainer: this.addContainer,
deleteContainer: this.deleteContainer,
deleteFrame: this.deleteFrame,
type: 'flexible-layout'
}
this.unsubscribeSelection = this.openmct.selection.selectable(this.$el, context, true);
this.openmct.objects.observe(this.domainObject, 'configuration.rowsLayout', this.toggleLayoutDirection);
this.openmct.editor.on('isEditing', this.isEditingHandler);
document.addEventListener('dragstart', this.dragstartHandler);
document.addEventListener('dragend', this.dragendHandler);
this.unobserve = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
},
beforeDestroy() {
this.unsubscribeSelection();
this.openmct.editor.off('isEditing', this.isEditingHandler);
document.removeEventListener('dragstart', this.dragstartHandler);
document.removeEventListener('dragend', this.dragendHandler);
this.unobserve();
}
}
</script>

View File

@@ -22,103 +22,82 @@
<template>
<div class="c-fl-frame"
:class="{
'is-dragging': isDragging,
[frame.cssClass]: true
}"
@dragstart="initDrag"
@drag="continueDrag">
:style="{
'flex-basis': `${frame.size}%`
}">
<div class="c-frame c-fl-frame__drag-wrapper is-selectable is-moveable"
:class="{'no-frame': noFrame}"
<div class="c-frame c-fl-frame__drag-wrapper is-selectable u-inspectable is-moveable"
draggable="true"
ref="frame"
v-if="frame.domainObject">
@dragstart="initDrag"
ref="frame">
<frame-header
v-if="index !== 0"
ref="dragObject"
class="c-fl-frame__header"
:domainObject="frame.domainObject">
</frame-header>
<object-view
class="c-fl-frame__object-view"
:object="frame.domainObject">
</object-view>
<object-frame
v-if="domainObject"
:domain-object="domainObject"
:object-path="objectPath"
:has-frame="hasFrame">
</object-frame>
<div class="c-fl-frame__size-indicator"
v-if="isEditing"
v-show="frame.height && frame.height < 100">
{{frame.height}}%
v-show="frame.size && frame.size < 100">
{{frame.size}}%
</div>
</div>
<drop-hint
v-show="isEditing && isDragging"
class="c-fl-frame__drop-hint"
:class="{'is-dragging': isDragging}"
@object-drop-to="dropHandler">
</drop-hint>
</div>
</template>
<script>
import ObjectView from '../../../ui/components/layout/ObjectView.vue';
import DropHint from './dropHint.vue';
import ResizeHandle from './resizeHandle.vue';
import FrameHeader from '../../../ui/components/utils/frameHeader.vue';
import ObjectFrame from '../../../ui/components/ObjectFrame.vue';
import isEditingMixin from '../mixins/isEditing';
export default {
inject: ['openmct', 'domainObject'],
props: ['frame', 'index', 'containerIndex', 'isEditing', 'isDragging'],
inject: ['openmct'],
props: ['frame', 'index', 'containerIndex'],
mixins: [isEditingMixin],
data() {
return {
noFrame: this.frame.noFrame
domainObject: undefined,
objectPath: undefined
}
},
components: {
ObjectView,
DropHint,
ResizeHandle,
FrameHeader
ObjectFrame
},
computed: {
hasFrame() {
return !this.frame.noFrame;
}
},
methods: {
setDomainObject(object) {
console.log('setting object!');
this.domainObject = object;
this.objectPath = [object];
this.setSelection();
},
setSelection() {
let context = {
item: this.domainObject,
addContainer: this.addContainer,
type: 'frame',
frameId: this.frame.id
};
this.unsubscribeSelection = this.openmct.selection.selectable(this.$refs.frame, context, false);
},
initDrag(event) {
this.$emit('frame-drag-from', this.index);
},
dropHandler(event) {
this.$emit('frame-drop-to', this.index, event);
},
continueDrag(event) {
if (!this.isDragging) {
this.isDragging = true;
}
},
deleteFrame() {
this.$emit('delete-frame', this.index);
},
addContainer() {
this.$emit('add-container');
},
toggleFrame(v) {
this.noFrame = v;
event.dataTransfer.setData('frameid', this.frame.id);
event.dataTransfer.setData('containerIndex', this.containerIndex);
}
},
mounted() {
if (this.frame.domainObject.identifier) {
let context = {
item: this.frame.domainObject,
method: this.deleteFrame,
addContainer: this.addContainer,
type: 'frame',
index: this.index
}
this.unsubscribeSelection = this.openmct.selection.selectable(this.$refs.frame, context, false);
this.openmct.objects.observe(this.domainObject, `configuration.containers[${this.containerIndex}].frames[${this.index}].noFrame`, this.toggleFrame);
if (this.frame.domainObjectIdentifier) {
this.openmct.objects.get(this.frame.domainObjectIdentifier).then((object)=>{
this.setDomainObject(object);
});
}
},
beforeDestroy() {

View File

@@ -23,16 +23,21 @@
<template>
<div class="c-fl-frame__resize-handle"
:class="[orientation]"
v-show="isEditing && !isDragging"
@mousedown="mousedown">
</div>
</template>
<script>
import isEditingMixin from '../mixins/isEditing';
export default {
props: ['orientation', 'index'],
mixins: [isEditingMixin],
data() {
return {
initialPos: 0
initialPos: 0,
isDragging: false,
}
},
methods: {
@@ -47,7 +52,18 @@ export default {
mousemove(event) {
event.preventDefault();
let delta = this.getMousePosition(event) - this.getElSizeFromRect(this.$el);
let elSize, mousePos, delta;
if (this.orientation === 'horizontal') {
elSize = this.$el.getBoundingClientRect().x;
mousePos = event.clientX;
} else {
elSize = this.$el.getBoundingClientRect().y;
mousePos = event.clientY;
}
delta = mousePos - elSize;
this.$emit('move', this.index, delta, event);
},
mouseup(event) {
@@ -56,20 +72,20 @@ export default {
document.body.removeEventListener('mousemove', this.mousemove);
document.body.removeEventListener('mouseup', this.mouseup);
},
getMousePosition(event) {
if (this.orientation === 'horizontal') {
return event.clientX;
} else {
return event.clientY;
}
},
getElSizeFromRect(el) {
if (this.orientation === 'horizontal') {
return el.getBoundingClientRect().x;
} else {
return el.getBoundingClientRect().y;
}
setDragging(event) {
this.isDragging = true;
},
unsetDragging(event) {
this.isDragging = false;
}
},
mounted() {
document.addEventListener('dragstart', this.setDragging);
document.addEventListener('dragend', this.unsetDragging);
},
destroyed() {
document.removeEventListener('dragstart', this.setDragging);
document.removeEventListener('dragend', this.unsetDragging);
}
}
</script>

View File

@@ -35,6 +35,9 @@ define([
canView: function (domainObject) {
return domainObject.type === 'flexible-layout';
},
canEdit: function (domainObject) {
return domainObject.type === 'flexible-layout';
},
view: function (domainObject) {
let component;
@@ -46,7 +49,7 @@ define([
},
provide: {
openmct,
domainObject
layoutObject: domainObject
},
el: element,
template: '<flexible-layout-component></flexible-layout-component>'

View File

@@ -0,0 +1,19 @@
export default {
inject: ['openmct'],
data() {
return {
isEditing: this.openmct.editor.isEditing()
};
},
mounted() {
this.openmct.editor.on('isEditing', this.toggleEditing);
},
destroyed() {
this.openmct.editor.off('isEditing', this.toggleEditing);
},
methods: {
toggleEditing(value) {
this.isEditing = value;
}
}
};

View File

@@ -22,10 +22,12 @@
define([
'./flexibleLayoutViewProvider',
'./utils/container'
'./utils/container',
'./toolbarProvider'
], function (
FlexibleLayoutViewProvider,
Container
Container,
ToolBarProvider
) {
return function plugin() {
@@ -42,122 +44,13 @@ define([
containers: [new Container.default(50), new Container.default(50)],
rowsLayout: false
};
domainObject.composition = [];
}
});
openmct.toolbars.addProvider({
name: "Flexible Layout Toolbar",
key: "flex-layout",
description: "A toolbar for objects inside a Flexible Layout.",
forSelection: function (selection) {
let context = selection[0].context;
let toolbar = ToolBarProvider.default(openmct);
return (openmct.editor.isEditing() && context && context.type &&
(context.type === 'flexible-layout' || context.type === 'container' || context.type === 'frame'));
},
toolbar: function (selection) {
let primary = selection[0],
parent = selection[1],
deleteFrame,
toggleContainer,
deleteContainer,
addContainer,
toggleFrame,
separator;
addContainer = {
control: "button",
domainObject: parent ? parent.context.item : primary.context.item,
method: parent ? parent.context.addContainer : primary.context.addContainer,
key: "add",
icon: "icon-plus-in-rect",
title: 'Add Container'
};
separator = {
control: "separator",
domainObject: selection[0].context.item,
key: "separator"
};
toggleContainer = {
control: 'toggle-button',
key: 'toggle-layout',
domainObject: parent ? parent.context.item : primary.context.item,
property: 'configuration.rowsLayout',
options: [
{
value: false,
icon: 'icon-columns',
title: 'Columns'
},
{
value: true,
icon: 'icon-rows',
title: 'Rows'
}
]
};
if (primary.context.type === 'frame') {
deleteFrame = {
control: "button",
domainObject: primary.context.item,
method: primary.context.method,
key: "remove",
icon: "icon-trash",
title: "Remove Frame"
};
toggleFrame = {
control: "toggle-button",
domainObject: parent.context.item,
property: `configuration.containers[${parent.context.index}].frames[${primary.context.index}].noFrame`,
options: [
{
value: true,
icon: 'icon-frame-hide',
title: "Hide frame"
},
{
value: false,
icon: 'icon-frame-show',
title: "Show frame"
}
]
};
} else if (primary.context.type === 'container') {
deleteContainer = {
control: "button",
domainObject: primary.context.item,
method: primary.context.method,
key: "remove",
icon: "icon-trash",
title: "Remove Container"
};
} else if (primary.context.type === 'flexible-layout') {
addContainer = {
control: "button",
domainObject: primary.context.item,
method: primary.context.addContainer,
key: "add",
icon: "icon-plus-in-rect",
title: 'Add Container'
};
}
let toolbar = [toggleContainer, addContainer, toggleFrame, separator, deleteFrame, deleteContainer];
return toolbar.filter(button => button !== undefined);
}
});
openmct.toolbars.addProvider(toolbar);
};
};
});

View File

@@ -0,0 +1,215 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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.
*****************************************************************************/
function ToolbarProvider(openmct) {
return {
name: "Flexible Layout Toolbar",
key: "flex-layout",
description: "A toolbar for objects inside a Flexible Layout.",
forSelection: function (selection) {
let context = selection[0].context;
return (openmct.editor.isEditing() && context && context.type &&
(context.type === 'flexible-layout' || context.type === 'container' || context.type === 'frame'));
},
toolbar: function (selection) {
let primary = selection[0],
secondary = selection[1],
tertiary = selection[2],
deleteFrame,
toggleContainer,
deleteContainer,
addContainer,
toggleFrame,
separator;
separator = {
control: "separator",
domainObject: selection[0].context.item,
key: "separator"
};
toggleContainer = {
control: 'toggle-button',
key: 'toggle-layout',
domainObject: secondary ? secondary.context.item : primary.context.item,
property: 'configuration.rowsLayout',
options: [
{
value: false,
icon: 'icon-columns',
title: 'Columns'
},
{
value: true,
icon: 'icon-rows',
title: 'Rows'
}
]
};
if (primary.context.type === 'frame') {
let frameId = primary.context.frameId;
let layoutObject = tertiary.context.item;
let containers = layoutObject
.configuration
.containers;
let container = containers
.filter(c => c.frames.some(f => f.id === frameId))[0];
let frame = container
.frames
.filter((f => f.id === frameId))[0];
let containerIndex = containers.indexOf(container);
let frameIndex = container.frames.indexOf(frame);
deleteFrame = {
control: "button",
domainObject: primary.context.item,
method: function () {
let deleteFrameAction = tertiary.context.deleteFrame;
let prompt = openmct.overlays.dialog({
iconClass: 'alert',
message: `This action will remove this frame from this Flexible Layout. Do you want to continue?`,
buttons: [
{
label: 'Ok',
emphasis: 'true',
callback: function () {
deleteFrameAction(primary.context.frameId);
prompt.dismiss();
}
},
{
label: 'Cancel',
callback: function () {
prompt.dismiss();
}
}
]
});
},
key: "remove",
icon: "icon-trash",
title: "Remove Frame"
};
toggleFrame = {
control: "toggle-button",
domainObject: secondary.context.item,
property: `configuration.containers[${containerIndex}].frames[${frameIndex}].noFrame`,
options: [
{
value: true,
icon: 'icon-frame-hide',
title: "Hide frame"
},
{
value: false,
icon: 'icon-frame-show',
title: "Show frame"
}
]
};
addContainer = {
control: "button",
domainObject: tertiary.context.item,
method: tertiary.context.addContainer,
key: "add",
icon: "icon-plus-in-rect",
title: 'Add Container'
};
} else if (primary.context.type === 'container') {
deleteContainer = {
control: "button",
domainObject: primary.context.item,
method: function () {
let removeContainer = secondary.context.deleteContainer,
containerId = primary.context.containerId;
let prompt = openmct.overlays.dialog({
iconClass: 'alert',
message: `This action will permanently delete container ${containerIndex + 1} from this Flexible Layout`,
buttons: [
{
label: 'Ok',
emphasis: 'true',
callback: function () {
removeContainer(containerId);
prompt.dismiss();
}
},
{
label: 'Cancel',
callback: function () {
prompt.dismiss();
}
}
]
});
},
key: "remove",
icon: "icon-trash",
title: "Remove Container"
};
addContainer = {
control: "button",
domainObject: secondary.context.item,
method: secondary.context.addContainer,
key: "add",
icon: "icon-plus-in-rect",
title: 'Add Container'
};
} else if (primary.context.type === 'flexible-layout') {
addContainer = {
control: "button",
domainObject: primary.context.item,
method: primary.context.addContainer,
key: "add",
icon: "icon-plus-in-rect",
title: 'Add Container'
};
}
let toolbar = [
toggleContainer,
addContainer,
toggleFrame ? separator: undefined,
toggleFrame,
deleteFrame || deleteContainer ? separator: undefined,
deleteFrame,
deleteContainer
];
return toolbar.filter(button => button !== undefined);
}
};
}
export default ToolbarProvider;

View File

@@ -1,13 +1,10 @@
import Frame from './frame';
import uuid from 'uuid';
class Container {
constructor (width) {
this.frames = [new Frame({}, '', 'c-fl-frame--first-in-container')];
this.width = width;
}
addFrame(frameObject) {
this.frames.push(frameObject);
constructor (size) {
this.id = uuid();
this.frames = [];
this.size = size;
}
}

View File

@@ -1,8 +1,11 @@
import uuid from 'uuid';
class Frame {
constructor(domainObject, height, cssClass) {
this.domainObject = domainObject;
this.height = height;
this.cssClass = cssClass ? cssClass : '';
constructor(domainObjectIdentifier, size) {
this.id = uuid();
this.domainObjectIdentifier = domainObjectIdentifier;
this.size = size;
this.noFrame = false;
}
}

View File

@@ -30,7 +30,7 @@ define([
function FolderGridView(openmct) {
return {
key: 'grid',
name: 'Grid Vue',
name: 'Grid View',
cssClass: 'icon-thumbs-strip',
canView: function (domainObject) {
return domainObject.type === 'folder';

View File

@@ -32,7 +32,7 @@ define([
function FolderListView(openmct) {
return {
key: 'list-view',
name: 'List Vue',
name: 'List View',
cssClass: 'icon-list-view',
canView: function (domainObject) {
return domainObject.type === 'folder';

View File

@@ -57,8 +57,7 @@
&__name {
@include ellipsize();
color: $colorItemFg;
font-size: 1.2em;
font-weight: 400;
@include headerFont(1.2em);
margin-bottom: $interiorMarginSm;
}
@@ -150,11 +149,11 @@
</style>
<script>
import contextMenu from '../../../ui/components/mixins/context-menu';
import objectLink from '../../../ui/components/mixins/object-link';
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
import objectLink from '../../../ui/mixins/object-link';
export default {
mixins: [contextMenu, objectLink],
mixins: [contextMenuGesture, objectLink],
props: ['item']
}
</script>

View File

@@ -54,11 +54,11 @@
<script>
import moment from 'moment';
import contextMenu from '../../../ui/components/mixins/context-menu';
import objectLink from '../../../ui/components/mixins/object-link';
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
import objectLink from '../../../ui/mixins/object-link';
export default {
mixins: [contextMenu, objectLink],
mixins: [contextMenuGesture, objectLink],
props: ['item'],
methods: {
formatTime(timestamp, format) {

View File

@@ -42,7 +42,8 @@
</tr>
</thead>
<tbody>
<list-item v-for="(item,index) in sortedItems"
<list-item v-for="item in sortedItems"
:key="item.objectKeyString"
:item="item"
:object-path="item.objectPath">
</list-item>
@@ -99,9 +100,20 @@ export default {
mixins: [compositionLoader],
inject: ['domainObject', 'openmct'],
data() {
let sortBy = 'model.name',
ascending = true,
persistedSortOrder = window.localStorage.getItem('openmct-listview-sort-order');
if (persistedSortOrder) {
let parsed = JSON.parse(persistedSortOrder);
sortBy = parsed.sortBy;
ascending = parsed.ascending;
}
return {
sortBy: 'model.name',
ascending: true
sortBy,
ascending
};
},
computed: {
@@ -121,6 +133,17 @@ export default {
this.sortBy = field;
this.ascending = defaultDirection;
}
window.localStorage
.setItem(
'openmct-listview-sort-order',
JSON.stringify(
{
sortBy: this.sortBy,
ascending: this.ascending
}
)
);
}
}
}

View File

@@ -35,7 +35,8 @@ export default {
model: child,
type: type.definition,
isAlias: this.domainObject.identifier.key !== child.location,
objectPath: [child].concat(openmct.router.path)
objectPath: [child].concat(this.openmct.router.path),
objectKeyString: this.openmct.objects.makeKeyString(child.identifier)
});
},
remove(identifier) {

View File

@@ -21,29 +21,9 @@
*****************************************************************************/
define([
"./src/controllers/NotebookController",
"./src/controllers/NewEntryController",
"./src/controllers/SelectSnapshotController",
"./src/actions/NewEntryContextual",
"./src/actions/AnnotateSnapshot",
"./src/directives/MCTSnapshot",
"./src/directives/EntryDnd",
"./res/templates/controls/snapSelect.html",
"./res/templates/controls/embedControl.html",
"./res/templates/annotation.html",
"./res/templates/draggedEntry.html"
"./src/controllers/NotebookController"
], function (
NotebookController,
NewEntryController,
SelectSnapshotController,
newEntryAction,
AnnotateSnapshotAction,
MCTSnapshotDirective,
EntryDndDirective,
snapSelectTemplate,
embedControlTemplate,
annotationTemplate,
draggedEntryTemplate
NotebookController
) {
var installed = false;
@@ -67,9 +47,8 @@ define([
features: 'creation',
model: {
entries: [],
composition: [],
entryTypes: [],
defaultSort: '-createdOn'
defaultSort: 'oldest'
},
properties: [
{
@@ -79,112 +58,17 @@ define([
options: [
{
name: 'Newest First',
value: "-createdOn",
value: "newest"
},
{
name: 'Oldest First',
value: "createdOn"
value: "oldest"
}
],
cssClass: 'l-inline'
}
]
}
],
actions: [
{
"key": "notebook-new-entry",
"implementation": newEntryAction,
"name": "New Notebook Entry",
"cssClass": "icon-notebook labeled",
"description": "Add a new Notebook entry",
"category": [
"view-control"
],
"depends": [
"$compile",
"$rootScope",
"dialogService",
"notificationService",
"linkService"
],
"priority": "preferred"
},
{
"key": "annotate-snapshot",
"implementation": AnnotateSnapshotAction,
"name": "Annotate Snapshot",
"cssClass": "icon-pencil labeled",
"description": "Annotate embed's snapshot",
"category": "embed",
"depends": [
"dialogService",
"dndService",
"$rootScope"
]
}
],
controllers: [
{
"key": "NewEntryController",
"implementation": NewEntryController,
"depends": ["$scope",
"$rootScope"
]
},
{
"key": "selectSnapshotController",
"implementation": SelectSnapshotController,
"depends": ["$scope",
"$rootScope"
]
}
],
controls: [
{
"key": "snapshot-select",
"template": snapSelectTemplate
},
{
"key": "embed-control",
"template": embedControlTemplate
}
],
templates: [
{
"key": "annotate-snapshot",
"template": annotationTemplate
}
],
directives: [
{
"key": "mctSnapshot",
"implementation": MCTSnapshotDirective,
"depends": [
"$rootScope",
"$document",
"exportImageService",
"dialogService",
"notificationService"
]
},
{
"key": "mctEntryDnd",
"implementation": EntryDndDirective,
"depends": [
"$rootScope",
"$compile",
"dndService",
"typeService",
"notificationService"
]
}
],
representations: [
{
"key": "draggedEntry",
"template": draggedEntryTemplate
}
]
}
});

View File

@@ -1,2 +0,0 @@
<div class="snap-annotation" id="snap-annotation" ng-controller="ngModel.controller">
</div>

View File

@@ -1,51 +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.
-->
<!--
This element appears in the overlay dialog when initiating a new Notebook Entry from a view's Notebook button -->
<div class='form-control'>
<ng-form name="mctControl">
<div class='fields' ng-controller="NewEntryController">
<div class="l-flex-row new-notebook-entry-embed l-entry-embed {{cssClass}}"
ng-class="{ 'has-snapshot' : snapToggle }">
<div class="holder flex-elem snap-thumb"
ng-if="snapToggle">
<img ng-src="{{snapshot.src}}" alt="{{snapshot.modified}}">
</div>
<div class="holder flex-elem embed-info">
<div class="embed-title">{{objectName}}</div>
<div class="embed-date"
ng-if="snapToggle">{{snapshot.modified| date:'yyyy-MM-dd HH:mm:ss'}}</div>
</div>
<div class="holder flex-elem annotate-new"
ng-if="snapToggle">
<a class="s-button flex-elem icon-pencil "
title="Annotate this snapshot"
ng-click="annotateSnapshot()">
<span class="title-label">Annotate</span>
</a>
</div>
</div>
</div>
</ng-form>
</div>

View File

@@ -1,29 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2017, 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.
-->
<div class='form-control select' ng-controller="selectSnapshotController">
<select
ng-model="selectModel"
ng-options="opt.value as opt.name for opt in options"
ng-required="ngRequired"
name="mctControl">
</select>
</div>

View File

@@ -1,38 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2017, 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.
-->
<div class="frame snap-frame frame-template t-frame-inner abs t-object-type-{{ representation.selected.key }}">
<div class="abs object-browse-bar l-flex-row">
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
<mct-representation
key="'switcher'"
ng-model="representation"
mct-object="domainObject">
</mct-representation>
</div>
</div>
<div class="abs object-holder" data-entry = "{{parameters.entry}}" data-embed = "{{parameters.embed}}" mct-snapshot ng-if="representation.selected.key">
<mct-representation
key="representation.selected.key"
mct-object="representation.selected.key && domainObject">
</mct-representation>
</div>
</div>

View File

@@ -7,18 +7,19 @@
<div class="c-ne__embed__info">
<div class="c-ne__embed__name">
<a class="c-ne__embed__link"
v-on:click="navigate(embed.type)"
v-bind:class="[embed.cssClass]">{{embed.name}}</a>
:href="objectLink"
:class="embed.cssClass">{{embed.name}}</a>
<a class="c-ne__embed__context-available icon-arrow-down"
v-on:click="toggleActionMenu"></a>
@click="toggleActionMenu"></a>
</div>
<div class="hide-menu hidden">
<div class="menu-element context-menu-wrapper mobile-disable-select">
<div class="menu context-menu">
<ul>
<li v-for="action in actions"
v-bind:class="[action.cssClass]"
v-on:click="action.perform(embed, entry)">
<li v-for="action in actions"
:key="action.name"
:class="action.cssClass"
@click="action.perform(embed, entry)">
{{ action.name }}
</li>
</ul>

View File

@@ -1,7 +1,5 @@
<li class="c-notebook__entry c-ne has-local-controls"
v-on:drop="dropOnEntry(entry.id)"
v-on:dragover="dragoverOnEntry"
>
@drop.prevent="dropOnEntry(entry.id, $event)">
<div class="c-ne__time-and-content">
<div class="c-ne__time">
<span>{{formatTime(entry.createdOn, 'YYYY-MM-DD')}}</span>
@@ -11,24 +9,26 @@
<div class="c-ne__text c-input-inline"
contenteditable="true"
ref="contenteditable"
v-on:blur="textBlur($event, entry.id)"
v-on:focus="textFocus($event, entry.id)"
v-bind:key="entry.id"
@blur="textBlur($event, entry.id)"
@focus="textFocus($event, entry.id)"
v-html="entry.text">
</div>
<div class="c-ne__embeds">
<notebook-embed
v-for="(embed, index) in entry.embeds"
v-bind:embed="embed"
v-bind:entry="entry"
></notebook-embed>
v-for="embed in entry.embeds"
:key="embed.id"
:embed="embed"
:objectPath="embed.objectPath"
:entry="entry">
</notebook-embed>
</div>
</div>
</div>
<div class="c-ne__local-controls--hidden">
<button class="c-click-icon icon-trash"
<button class="c-click-icon c-click-icon--major icon-trash"
title="Delete this entry"
v-on:click="deleteEntry"></button>
@click="deleteEntry">
</button>
</div>
</li>

View File

@@ -1,9 +1,10 @@
<div class="c-notebook">
<div class="c-notebook__head">
<search class="c-notebook__search"
v-model="entrySearch"
v-on:input="search($event)"
v-on:clear="entrySearch = ''; search($event)"></search>
:value="entrySearch"
@input="search"
@clear="search">
</search>
<div class="c-notebook__controls">
<div class="select c-notebook__controls__time">
<select v-model="showTime">
@@ -15,23 +16,24 @@
</div>
<div class="select c-notebook__controls__sort">
<select v-model="sortEntries">
<option value="-createdOn" selected="selected">Newest first</option>
<option value="createdOn">Oldest first</option>
<option value="newest" :selected="sortEntries === 'newest'">Newest first</option>
<option value="oldest" :selected="sortEntries === 'oldest'">Oldest first</option>
</select>
</div>
</div>
</div>
<div class="c-notebook__drag-area icon-plus"
v-on:click="newEntry($event)"
id="newEntry" mct-entry-dnd>
@click="newEntry($event)"
@drop="newEntry($event)">
<span class="c-notebook__drag-area__label">To start a new entry, click here or drag and drop any object</span>
</div>
<div class="c-notebook__entries" ng-mouseover="handleActive()">
<div class="c-notebook__entries">
<ul>
<notebook-entry
v-for="entry in filterBySearch(entries, entrySearch)"
v-bind:entry="entry"
></notebook-entry>
<notebook-entry
v-for="(entry,index) in filteredAndSortedEntries"
:key="entry.key"
:entry="entry">
</notebook-entry>
</ul>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More