Compare commits

...

39 Commits

Author SHA1 Message Date
Andrew Henry
6d760bae75 Don't serialize objects twice on drag 2018-12-14 15:42:46 -08:00
Andrew Henry
6180bb23e6 Show drop hints without being in edit mode. 2018-12-14 15:38:43 -08:00
Andrew Henry
3d351ec8d1 Drag-drop edit mode from capture handler. Drag-drop composition from bubble handler. Check composability on drag start 2018-12-14 15:06:47 -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
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
Pegah Sarram
e07cfc9394 Layout drawing (#2232)
* - Show "Add" button in the toolbar when a display layout object is selected.
- Add a flag to object view's show() method to indicate immediate selection of the view. If the view implements getSelectionContext() use it, otherwise set to default context.

* Create a component for each element.

* Saving work

* Add element factory for creating new instances of elements.

* Mutate element when a new one added and get elements when the component is mounted.

* Add create() method for creating a new telemetry and element in their respective view configuration.

* Add some of the toolbar controls for box, text, image and line elements. Also, add X, Y, Width and Height controls for alhpanumeric elements.

* Pass name to addElement as type.

* Add c-frame-inspectable class if item is inspectable.

* Clean up

* Hide frame for summary widgets by default.

* Better styling for editing

- s-selected on shell__main-container;
- Better edit grid coloring for espresso;

* - Update toolbar-button to support dialogs.
- Update toolbar to construct a value object based on form keys if a toolbar item has a dialogi, and mutate the form keys.
- Add toolbar controls for editing text and url for 'Text' and 'Image' elements respectively.

* Editing-related changes

- Removed hard-coded .is-selectable and .is-moveable from
LayoutItem.vue, updates accordingly to _global.scss;
- Theme constants updated;
- TODO: apply changes to Flexible Layouts;

* Better defaults

- Better default grid size and object size;

* - Fix toolbar-input to read value as a number if type is 'number'.
- Remove rawPosition from view configuration and instead get the position and dimensions from the properties (x, y, width and height) directly.
- Set the style property on the view configuration instead of the layout item.
- Move the logic for updating the style to the view configuration.

* Fix default dimensions for telemetry items and subobjects since the default grid size is changed.

* Remove form definition for display layout type.

* Reword the comment

* Let subobject view configuration handle new panel creation.

* Add default grid size back and remove unused code.

* Pass in only the needed method.

* Define default position in case the object is not added via drag 'n drop.
2018-12-04 09:12:45 -08:00
Andrew Henry
32a0baa7a3 Context menu actions (#2229)
* Adding jsdoc to Context Menu Registry

* Remove attachTo function - make context menu gesture a mixin. Update object path when objects change.

* Added context menu from arrow button. Some minor refactoring

* Clarify variable naming

* Moved Context Menu component

* Reorder function definitions

* Addressed code review comments
2018-12-04 09:09:09 -08:00
Pegah Sarram
f06427cb3e Show field name as title. (#2233) 2018-12-04 08:15:33 -08:00
Andrew Henry
9ae4e66c91 Instantiate legacy objects inline 2018-11-23 15:31:38 -08:00
Andrew Henry
eeab6e9bde Do not allow context menu to overrun edge of screen 2018-11-23 15:14:55 -08:00
Andrew Henry
cabc410e0a Fix SaveAs import 2018-11-23 15:13:53 -08:00
Andrew Henry
2dcff00fa7 Added legacy action layer 2018-11-23 11:52:13 -08:00
Andrew Henry
94cdce3551 Support events other than 'contextmenu' 2018-11-23 09:05:01 -08:00
Andrew Henry
a7948ce83e Implemented context menu action registry 2018-11-22 15:53:50 -08:00
Pegah Sarram
74faf1bd48 Create a base view configuration class. (#2225) 2018-11-16 12:56:51 -05:00
Charles Hacskaylo
3e7527d55c Fixes for Flex Layout in TCR (#2221)
- Object view now displays objects;
- FL frame header fixed;
- Fixed grippy look and positioning for now;
2018-11-13 19:02:54 -05:00
charlesh88
9733674d6e Fixed baseline in symbols font 2018-11-10 22:41:38 -08:00
Pete Richards
e05dbadea2 Ensure labels and tree items stay in sync 2018-11-09 11:46:57 -08:00
Pete Richards
bc512f3766 Retrieve latest object before create
Retrieve latest object before creation to ensure we are not
mutating an outdated version.
2018-11-09 11:00:24 -08:00
Andrew Henry
ae51e2e437 Table header columnWidth prop should be number not object 2018-11-09 10:34:15 -08:00
Pete Richards
0e06a7b403 Hide openmct with closures to prevent it becoming reactive 2018-11-09 08:33:50 -08:00
Pete Richards
ff7df9ad1e context menu and shared object link generation (#2199)
* temporarily disable remove dialog which is broken

* temporarily remove broken context action policy

* let openmct generate legacy objects

* ensure composition loads in specified order

* redo nav and add context menu support to tree

* componentize grid and list view, add context menus
2018-11-08 17:21:18 -08:00
Deep Tailor
1069a45cfc Flexible Layout (#2201)
* first cut of flexible layout

* better drag handling

* add drop targets to every row

* enable drag and drop between columns and rows

* enable persistance

* add editing capability

* chage rows to frames and columns to containers, switch draggable to whole frame object

* Merge latest, resolve conflicts.

Need to just apply these changes to Deep's branch and push

* enhancements to drag targets

* WIP in flexibleLayout, container.vue files

- Refined classes and markup;
- min-width changed to flex-basis;
- Added toggle direction button;

* Significant progress but still WIP

- Refined classes and markup;
- Layout toggling working;
- Add Container working properly;
- TODOs: fix sizing in empty container, fix bordering, more refinements;

* add resizing of frames - still wip

* Significant enhancements

- Moved all CSS into flexibleLayout.vue;
- Layout now improved for empty container and drop hints;
- Proportional sizing now better for frames and containers;

* Resize handle WIP

* abstract splitter and logic into self contained component that will emit an event when mouse is moving

* Resize handle WIP

- Minor tweak to handle padding and hover;

* add container resize todo persist

* persist container resize

* add frame header, fix column resize on last column

* Refinements to resize-handle

- Fixed sizing;
- Transition on hover;
- TODOs: needs is-dragging to maintain hover style while dragging;

* fix drop hints showing after drop

* move header

* improve mouse move gesture

* Added frame size indicator

* add snapto functionality

* Refined container and frame size indicators

- Also added overflow handling to l-grid-view

* improve resizing logic

* add selection on frames

* Various resizing-frames related

- Fixed overflow - now frame widths can be collapsed to 5% minimum;
- Sizing indicators refined, better positioning and layout;
- Added grippy drag indicators to column heads;
- TODOs: add column head cursors and hover effects, hide indicators
when not in edit mode, handle nested layout and flex layouts while
editing

* Selecting and emtpy layout messaging

- Better empty layout message;
- Moved s-selected to proper element in c-fl-frame;

* Drop-hint and sizing related various

- Drop-hints for first placeholder container now display;
- Drop-hints moved into drag-wrapper;

* add delete frame

* Editing various

- Adjust Snow theme constants related to editing;
- Changed delete message wording;

* Updated icon and added description

* add toggle and remove container to toolbar

* miscellaneous cleanup

* add container button to toolbar

* improve toolbar

* code cleanup in plugin.js

* Various icons, toolbar separator

- Copied in c-toolbar__separator and associated changes in _controls
from Pegah's layout_alpha branch - may have conflicts later.
- Added separator to FL toolbar;
- Updated icons for grippy-ew, toolbar icons;

* add check for empty containers"

* logic to resize frames on drop

* fix delete frame and persisting toolbar

* Significant changes to edit / selection styling

- Both Flexible and fixed Display Layouts addressed;
- Both themes addressed;
- Changed drop-hint icon to icon-plus;

* add correct icons to frame header and fix toolbars showing up in wrong views

* Moving and resizing various

- Cursors;
- Grippy added to frame resize-handle, WIP!;

* add container reordering

* add frame/no frame support to toolbar'

* fix regression of resize handles showing after last frame in container

* force selection of flexible-layout when editing is first clicked, to apply correct toolbar

* make changes to simplify toolbar

* Modified sizing algorithm slightly

* make changes reviewer requested

* fix regression that causes top drop hint to not show

* remove unused variables and bind events to vue

* unsub selection before destroy
2018-11-08 17:17:14 -08:00
Pegah Sarram
d13d59bfa0 Display layout alphanumeric (#2203)
* Support displaying and adding telemerty points in display layouts.

* Create TelemetryView component. Also disable the toolbar frame button for telemetry objects.

* Add 'components' directory and move the toolbar provider definition to a separate file.

* Saving work

* Saving work

* Saving work

* Fix telemetryClass

* Fixes for .no-frame in new markup structure

- CSS cleaned up and reorganized;
- Added .c-telemetry-view classes;

* Add computed properties for hiding label and value.

* Filter value meta data based on the item config display mode.

* Add drop down menus for display mode and value

* Add toolbar controls for telemerty points

* Set border and fill related styles on telemetry view instead of layout item

* Refinements to telemetry view

- Stoke and fill styling now work;
- Internal element layout now much better when sizing in a Layout frame;
- Tweaked color of frame border while editing;

* Prevents adding a new (panel) object if it's already in the composition.

* Fix for jumping edit area

- Removed v-if from Toolbar.vue;
- Refined c-toolbar styling;
- TODO: don't include toolbar component when not editing, and for
components that don't use a toolbar;

* Add a separator toolbar control

* Check for domainObject being on the toolbar item as not all controls have that property

* Hide 'no fill' option from the text palette.
Modify the color-picker component to say 'No border' for stroke palette.

* Move the listener for hasFrame to the subobject view configuration

* Fixes for toolbar-separator

- New mixin;
- Corrected markup for separator;
- New class for .c-toolbar__separator;
- Updated DisplayLayoutToolbar.js to include separators in the right
spots;

* Get type from item.

* Include copyright notice.

* Use arrow function for consistency and define a TEXT_SIZE constant.

* Use composition API to add non-telemetry objects instead of relying on the drop handler.
Display a blocking dialog if an existing non-telemetry object is dropped.

* Fix text color picker icon

* Address reviewer's feedback

* Load the composition and update addObject() to render existing panels as well. Also, cache the telemetry value formatter.

* Add listener for changes to time bounds.

* Code cleanup

* Use getFormatMap() to store formats. Reset telemetry value and class before fetching new data.

* Fix a typo

* Define telemetry class and value as computed properties.

* Change context object definition

* Look at the telemetry metadata to find a good default for the value key instead of defaulting to 'sin'. Also, make formats reactive.

* Use let instead of var.
2018-11-08 17:09:17 -08:00
charlesh88
55d3ab5e8a Added new glyph 2018-11-07 22:27:03 -08:00
Andrew Henry
c073a21ba6 [Table] Custom column widths and order (#2204)
* Renders with resize hotzones

* Implemented basic reordering of columns

* Refactored column headers into component

* Custom column widths persist during resize

* Initial working version of fixed column sizes

* Only calculate default sizes when first data received.

* Further fixes for fixed column widths. Add option to switch to auto-widths

* Fixed bug with table auto-sizing

* Only allow reorder and resize in edit mode

* Bug fixes

* Allow for scroll bar width

* Bug fix with tables reverting to 100% width

* Fixed bug with drop position indicator when scrolled

* Moved events on to component

* Do not throttle mouse events. Let Vue throttle them

* Do not hard code vertical offset for drop target

* Addressed review issues

* Clarified mouse event handling on column resize
2018-11-07 11:04:56 -08:00
Charles Hacskaylo
ed8137726d cstyle update 1106 (#2211)
* Temp fixes for legacy styling of overlay and forms

* Added new glyphs: rows, columns, plus-in-rect
2018-11-07 09:58:33 -08:00
Charles Hacskaylo
3ebdab5e51 Symbols font update (#2206)
* Very significant update to symbols font

- New Icomoon project and font files for 16px symbols;
- Symbol char codes now PUA and hexadecimal compliant;
- Symbols reorganized;
- New symbols added;
- Remove loading of legacy glyphs scss file;

* Added new fonts

- Updated Tabs View icon art;
- Generators icons added;

* Added new icon glyphs and background SVG art

- New icons for generators;
- New icon for Flexible Layout;
- New icon-grippy-ew, for Flexible Layout, others;

* Remove unused legacy font files
2018-11-06 13:22:44 -08:00
169 changed files with 7648 additions and 6421 deletions

View File

@@ -49,7 +49,7 @@ define([
{
"key": "eventGenerator",
"name": "Event Message Generator",
"cssClass": "icon-folder-new",
"cssClass": "icon-generator-events",
"description": "For development use. Creates sample event message data that mimics a live data stream.",
"priority": 10,
"features": "creation",

View File

@@ -38,7 +38,7 @@ define([
openmct.types.addType("example.state-generator", {
name: "State Generator",
description: "For development use. Generates test enumerated telemetry by cycling through a given set of states",
cssClass: "icon-telemetry",
cssClass: "icon-generator-telemetry",
creatable: true,
form: [
{
@@ -66,7 +66,7 @@ define([
openmct.types.addType("generator", {
name: "Sine Wave Generator",
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
cssClass: "icon-telemetry",
cssClass: "icon-generator-telemetry",
creatable: true,
form: [
{

View File

@@ -78,6 +78,7 @@
openmct.install(openmct.plugins.Notebook());
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.start();

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

@@ -34,9 +34,6 @@ define([
"./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",
@@ -69,9 +66,6 @@ define([
CancelAction,
EditActionPolicy,
EditPersistableObjectsPolicy,
EditableLinkPolicy,
EditableMovePolicy,
EditContextualActionPolicy,
EditRepresenter,
EditorCapability,
TransactionCapabilityDecorator,
@@ -174,7 +168,7 @@ define([
"name": "Remove",
"description": "Remove this object from its containing object.",
"depends": [
"dialogService",
"openmct",
"navigationService"
]
},
@@ -240,19 +234,6 @@ define([
"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 +330,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();
}
]
};
dialog = this.dialogService.showBlockingMessage(model);
}
]
});
};
return RemoveDialog;

View File

@@ -1,72 +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() : {};
// 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 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

@@ -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,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

@@ -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,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

@@ -32,7 +32,7 @@ define([
{
key: "exportService",
implementation: function () {
return new ExportService(saveAs);
return new ExportService(saveAs.saveAs);
}
}
],

View File

@@ -104,9 +104,9 @@ define([
}
return (openmct.editor.isEditing() &&
(selection[0] && selection[0].context.elementProxy &&
selection[1] && selection[1].context.item.type === 'telemetry.fixed' ||
selection[0] && selection[0].context.item.type === 'telemetry.fixed'));
selection[0] && selection[0].context.elementProxy &&
((selection[1] && selection[1].context.item.type === 'telemetry.fixed') ||
(selection[0] && selection[0].context.item && selection[0].context.item.type === 'telemetry.fixed')));
},
toolbar: function (selection) {
var imageProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "url"];

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,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.
*****************************************************************************/
define(
[
'painterro'
],
function (Painterro) {
function SnapshotPreviewController($scope, openmct) {
$scope.previewImage = function (imageUrl) {
var image = document.createElement('img');
image.src = imageUrl;
openmct.overlays.overlay(
{
element: image,
size: 'large'
}
);
};
$scope.annotateImage = function (ngModel, field, imageUrl) {
$scope.imageUrl = imageUrl;
var div = document.createElement('div'),
painterroInstance = {},
save = false;
div.id = 'snap-annotation';
openmct.overlays.overlay(
{
element: div,
size: 'large',
buttons: [
{
label: 'Cancel',
callback: function () {
save = false;
painterroInstance.save();
}
},
{
label: 'Save',
callback: function () {
save = true;
painterroInstance.save();
}
}
]
}
);
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) {
var 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,7 +40,9 @@ 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',
'vue'
], function (
EventEmitter,
@@ -63,6 +65,8 @@ define([
coreStyles,
NotebookStyles,
Layout,
DomainObjectImpl,
ContextualDomainObject,
Vue
) {
/**
@@ -220,10 +224,13 @@ define([
this.overlays = new OverlayAPI.default();
this.contextMenu = new api.ContextMenuRegistry();
this.legacyRegistry = defaultRegistry;
this.install(this.plugins.Plot());
this.install(this.plugins.TelemetryTable());
this.install(this.plugins.DisplayLayout());
this.install(this.plugins.Preview());
if (typeof BUILD_CONSTANTS !== 'undefined') {
this.install(buildInfoPlugin(BUILD_CONSTANTS));
@@ -241,6 +248,42 @@ define([
this.legacyBundle.extensions[category].push(extension);
};
/**
* Return a legacy object, for compatibility purposes only. This method
* will be deprecated and removed in the future.
* @private
*/
MCT.prototype.legacyObject = function (domainObject) {
let capabilityService = this.$injector.get('capabilityService');
function instantiate(model, keyString) {
var capabilities = capabilityService.getCapabilities(model, keyString);
model.id = keyString;
return new DomainObjectImpl(keyString, model, capabilities);
}
if (Array.isArray(domainObject)) {
// an array of domain objects. [object, ...ancestors] representing
// a single object with a given chain of ancestors. We instantiate
// as a single contextual domain object.
return domainObject
.map((o) => {
let keyString = objectUtils.makeKeyString(o.identifier);
let oldModel = objectUtils.toOldFormat(o);
return instantiate(oldModel, keyString);
})
.reverse()
.reduce((parent, child) => {
return new ContextualDomainObject(child, parent);
});
} else {
let keyString = objectUtils.makeKeyString(domainObject.identifier);
let oldModel = objectUtils.toOldFormat(domainObject);
return instantiate(oldModel, keyString);
}
};
/**
* Set path to where assets are hosted. This should be the path to main.js.
* @memberof module:openmct.MCT#

View File

@@ -20,32 +20,18 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* Module defining ContextMenuController. Created by vwoeltje on 11/17/14.
*/
define(
[],
function () {
import LegacyContextMenuAction from './LegacyContextMenuAction';
/**
* 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);
export default function LegacyActionAdapter(openmct, legacyActions) {
function contextualCategoryOnly(action) {
if (action.category === 'contextual' || (Array.isArray(action.category) && action.category.includes('contextual'))) {
return true;
}
return ContextMenuController;
console.warn(`DEPRECATION WARNING: Action ${action.definition.key} in bundle ${action.bundle.path} is non-contextual and should be migrated.`);
return false;
}
);
legacyActions.filter(contextualCategoryOnly)
.map(LegacyAction => new LegacyContextMenuAction(openmct, LegacyAction))
.forEach(openmct.contextMenu.registerAction);
}

View File

@@ -0,0 +1,83 @@
/*****************************************************************************
* 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.
*****************************************************************************/
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) {
this.openmct = openmct;
this.name = LegacyAction.definition.name;
this.description = LegacyAction.definition.description;
this.cssClass = LegacyAction.definition.cssClass;
this.LegacyAction = LegacyAction;
}
invoke(objectPath) {
let context = {
category: 'contextual',
domainObject: this.openmct.legacyObject(objectPath)
}
let legacyAction = new this.LegacyAction(context);
if (!legacyAction.getMetadata) {
let metadata = Object.create(this.LegacyAction.definition);
metadata.context = context;
legacyAction.getMetadata = function () {
return metadata;
}.bind(legacyAction);
}
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

@@ -36,7 +36,8 @@ define([
'./runs/RegisterLegacyTypes',
'./services/LegacyObjectAPIInterceptor',
'./views/installLegacyViews',
'./policies/legacyCompositionPolicyAdapter'
'./policies/legacyCompositionPolicyAdapter',
'./actions/LegacyActionAdapter'
], function (
legacyRegistry,
ActionDialogDecorator,
@@ -53,7 +54,8 @@ define([
RegisterLegacyTypes,
LegacyObjectAPIInterceptor,
installLegacyViews,
legacyCompositionPolicyAdapter
legacyCompositionPolicyAdapter,
LegacyActionAdapter
) {
legacyRegistry.register('src/adapter', {
"extensions": {
@@ -169,6 +171,13 @@ define([
depends: [
"openmct"
]
},
{
implementation: LegacyActionAdapter.default,
depends: [
"openmct",
"actions[]"
]
}
],
licenses: [

View File

@@ -77,7 +77,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

@@ -28,6 +28,7 @@ define([
'./telemetry/TelemetryAPI',
'./indicators/IndicatorAPI',
'./notifications/NotificationAPI',
'./contextMenu/ContextMenuAPI',
'./Editor'
], function (
@@ -38,6 +39,7 @@ define([
TelemetryAPI,
IndicatorAPI,
NotificationAPI,
ContextMenuAPI,
EditorAPI
) {
return {
@@ -48,6 +50,7 @@ define([
TelemetryAPI: TelemetryAPI,
IndicatorAPI: IndicatorAPI,
NotificationAPI: NotificationAPI.default,
EditorAPI: EditorAPI
EditorAPI: EditorAPI,
ContextMenuRegistry: ContextMenuAPI.default
};
});

View File

@@ -177,7 +177,11 @@ define([
CompositionCollection.prototype.load = function () {
return this.provider.load(this.domainObject)
.then(function (children) {
return Promise.all(children.map(this.onProviderAdd, this));
return Promise.all(children.map((c) => this.publicAPI.objects.get(c)));
}.bind(this))
.then(function (childObjects) {
childObjects.forEach(c => this.add(c, true));
return childObjects;
}.bind(this))
.then(function (children) {
this.emit('load');

View File

@@ -0,0 +1,20 @@
<template>
<div class="c-menu">
<ul>
<li v-for="action in actions"
:key="action.name"
:class="action.cssClass"
:title="action.description"
@click="action.invoke(objectPath)">
{{ action.name }}
</li>
<li v-if="actions.length === 0">No actions defined.</li>
</ul>
</div>
</template>
<script>
export default {
inject: ['actions', 'objectPath']
}
</script>

View File

@@ -0,0 +1,145 @@
/*****************************************************************************
* 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.
*****************************************************************************/
import ContextMenuComponent from './ContextMenu.vue';
import Vue from 'vue';
/**
* The ContextMenuAPI allows the addition of new context menu actions, and for the context menu to be launched from
* custom HTML elements.
* @interface ContextMenuAPI
* @memberof module:openmct
*/
class ContextMenuAPI {
constructor() {
this._allActions = [];
this._activeContextMenu = undefined;
this._hideActiveContextMenu = this._hideActiveContextMenu.bind(this);
this.registerAction = this.registerAction.bind(this);
}
/**
* Defines an item to be added to context menus. Allows specification of text, appearance, and behavior when
* selected. Applicabilioty can be restricted by specification of an `appliesTo` function.
*
* @interface ContextMenuAction
* @memberof module:openmct
* @property {string} name the human-readable name of this view
* @property {string} description a longer-form description (typically
* a single sentence or short paragraph) of this kind of view
* @property {string} cssClass the CSS class to apply to labels for this
* view (to add icons, for instance)
*/
/**
* @method appliesTo
* @memberof module:openmct.ContextMenuAction#
* @param {DomainObject[]} objectPath the path of the object that the context menu has been invoked on.
* @returns {boolean} true if the action applies to the objects specified in the 'objectPath', otherwise false.
*/
/**
* Code to be executed when the action is selected from a context menu
* @method invoke
* @memberof module:openmct.ContextMenuAction#
* @param {DomainObject[]} objectPath the path of the object to invoke the action on.
*/
/**
* @param {ContextMenuAction} actionDefinition
*/
registerAction(actionDefinition) {
this._allActions.push(actionDefinition);
}
/**
* @private
*/
_showContextMenuForObjectPath(objectPath, x, y) {
let applicableActions = this._allActions.filter((action) => {
if (action.appliesTo === undefined) {
return true;
}
return action.appliesTo(objectPath);
});
if (this._activeContextMenu) {
this._hideActiveContextMenu();
}
this._activeContextMenu = this._createContextMenuForObject(objectPath, applicableActions);
this._activeContextMenu.$mount();
document.body.appendChild(this._activeContextMenu.$el);
let position = this._calculatePopupPosition(x, y, this._activeContextMenu.$el);
this._activeContextMenu.$el.style.left = `${position.x}px`;
this._activeContextMenu.$el.style.top = `${position.y}px`;
document.addEventListener('click', this._hideActiveContextMenu);
}
/**
* @private
*/
_calculatePopupPosition(eventPosX, eventPosY, menuElement) {
let menuDimensions = menuElement.getBoundingClientRect();
let overflowX = (eventPosX + menuDimensions.width) - document.body.clientWidth;
let overflowY = (eventPosY + menuDimensions.height) - document.body.clientHeight;
if (overflowX > 0) {
eventPosX = eventPosX - overflowX;
}
if (overflowY > 0) {
eventPosY = eventPosY - overflowY;
}
return {
x: eventPosX,
y: eventPosY
}
}
/**
* @private
*/
_hideActiveContextMenu() {
document.removeEventListener('click', this._hideActiveContextMenu);
document.body.removeChild(this._activeContextMenu.$el);
this._activeContextMenu.$destroy();
this._activeContextMenu = undefined;
}
/**
* @private
*/
_createContextMenuForObject(objectPath, actions) {
return new Vue({
components: {
ContextMenu: ContextMenuComponent
},
provide: {
actions: actions,
objectPath: objectPath
},
template: '<ContextMenu></ContextMenu>'
});
}
}
export default ContextMenuAPI;

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

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';

View File

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

View File

@@ -40,7 +40,7 @@
.c-overlay {
@include abs();
z-index: 100;
z-index: 70;
&__blocker {
display: none; // Mobile-first

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

@@ -1,312 +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="l-layout"
@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>
</div>
<layout-frame v-for="item in frameItems"
class="l-layout__frame"
:key="item.id"
:item="item"
:gridSize="gridSize"
@drilledIn="updateDrilledInState"
@dragInProgress="updatePosition"
@endDrag="endDrag">
</layout-frame>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.l-layout {
@include abs();
display: flex;
flex-direction: column;
&__grid-holder {
display: none;
}
&__object {
flex: 1 1 auto;
overflow: auto;
}
&__frame {
position: absolute;
}
}
.l-shell__main-container {
> .l-layout {
[s-selected] {
border: $browseBorderSelected;
}
}
}
// Styles moved to _global.scss;
</style>
<script>
import LayoutFrame from './LayoutFrame.vue';
const DEFAULT_GRID_SIZE = [32, 32],
DEFAULT_DIMENSIONS = [12, 8],
DEFAULT_POSITION = [0, 0],
MINIMUM_FRAME_SIZE = [320, 180],
DEFAULT_HIDDEN_FRAME_TYPES = [
'hyperlink'
];
export default {
data() {
return {
gridSize: [],
frameItems: [],
frames: [],
frameStyles: [],
rawPositions: {},
drilledIn: undefined
}
},
inject: ['openmct'],
props: ['domainObject'],
components: {
LayoutFrame
},
created: function () {
this.newDomainObject = this.domainObject;
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;
this.composition = this.openmct.composition.get(this.newDomainObject);
this.Listeners = [];
let panels = (((this.newDomainObject.configuration || {}).layout || {}).panels || {});
if (this.composition !== undefined) {
this.composition.load().then((composition) => {
composition.forEach(function (domainObject) {
this.readLayoutConfiguration(domainObject, panels);
this.makeFrameItem(domainObject, false);
}.bind(this));
this.composition.on('add', this.onAddComposition);
this.composition.on('remove', this.onRemoveComposition);
});
}
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;;
}.bind(this));
},
methods: {
readLayoutConfiguration(domainObject, panels) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
this.rawPositions[id] = {
position: panels[id].position || DEFAULT_POSITION,
dimensions: panels[id].dimensions || this.defaultDimensions()
};
this.frameStyles[id] = this.convertPosition(this.rawPositions[id]);
this.frames[id] = panels[id].hasOwnProperty('hasFrame') ?
panels[id].hasFrame :
this.hasFrameByDefault(domainObject.type);
},
makeFrameItem(domainObject, initSelect) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
this.frameItems.push({
id: id,
hasFrame: this.frames[id],
domainObject,
style: this.frameStyles[id],
drilledIn: this.isDrilledIn(id),
initSelect: initSelect,
rawPosition: this.rawPositions[id]
});
},
onAddComposition(domainObject) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
this.rawPositions[id] = {
position: [
Math.floor(this.droppedObjectPosition.x / this.gridSize[0]),
Math.floor(this.droppedObjectPosition.y / this.gridSize[1])
],
dimensions: this.defaultDimensions()
};
this.frameStyles[id] = this.convertPosition(this.rawPositions[id]);
this.frames[id] = this.hasFrameByDefault(domainObject.type);
let newPanel = this.rawPositions[id];
newPanel.hasFrame = this.frames[id];
this.mutate("configuration.layout.panels[" + id + "]", newPanel);
this.makeFrameItem(domainObject, true);
},
onRemoveComposition(identifier) {
// TODO: remove the object from frameItems
},
defaultDimensions() {
let gridSize = this.gridSize;
return MINIMUM_FRAME_SIZE.map(function (min, i) {
return Math.max(
Math.ceil(min / gridSize[i]),
DEFAULT_DIMENSIONS[i]
);
});
},
convertPosition(raw) {
return {
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'
};
},
/**
* Checks if the frame should be hidden or not.
*
* @param type the domain object type
* @return {boolean} true if the object should have
* frame by default, false, otherwise
*/
hasFrameByDefault(type) {
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
},
setSelection(selection) {
if (selection.length === 0) {
return;
}
this.removeListeners();
let domainObject = selection[0].context.item;
if (selection[1] && domainObject) {
this.attachSelectionListeners(domainObject.identifier);
}
this.updateDrilledInState();
},
attachSelectionListeners(identifier) {
let id = this.openmct.objects.makeKeyString(identifier);
let path = "configuration.layout.panels[" + id + "]";
this.listeners.push(this.openmct.objects.observe(this.newDomainObject, path + ".hasFrame", function (newValue) {
this.frameItems.forEach(function (item) {
if (item.id === id) {
item.hasFrame = newValue;
}
});
this.frames[id] = newValue;
}.bind(this)));
},
updateDrilledInState(id) {
this.drilledIn = id;
this.frameItems.forEach(function (item) {
item.drilledIn = item.id === id;
});
},
isDrilledIn(id) {
return this.drilledIn === id;
},
updatePosition(id, newPosition) {
let newStyle = this.convertPosition(newPosition);
this.frameStyles[id] = newStyle;
this.rawPositions[id] = newPosition;
this.frameItems.forEach(function (item) {
if (item.id === id) {
item.style = newStyle;
item.rawPosition = newPosition;
}
});
},
bypassSelection($event) {
if (this.dragInProgress) {
if ($event) {
$event.stopImmediatePropagation();
}
return;
}
},
endDrag(id) {
this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
let path = "configuration.layout.panels[" + id + "]";
this.mutate(path + ".dimensions", this.rawPositions[id].dimensions);
this.mutate(path + ".position", this.rawPositions[id].position);
},
mutate(path, value) {
this.openmct.objects.mutate(this.newDomainObject, path, value);
},
handleDrop($event) {
$event.preventDefault();
let child = JSON.parse($event.dataTransfer.getData('domainObject'));
let elementRect = this.$el.getBoundingClientRect();
this.droppedObjectPosition = {
x: $event.pageX - elementRect.left,
y: $event.pageY - elementRect.top
}
},
handleDragOver($event){
$event.preventDefault();
},
removeListeners() {
if (this.listeners) {
this.listeners.forEach(function (l) {
l();
})
}
this.listeners = [];
}
},
mounted() {
this.openmct.selection.on('change', this.setSelection);
},
destroyed: function () {
this.composition.off('add', this.onAddComposition);
this.composition.off('remove', this.onRemoveComposition);
this.openmct.off('change', this.selection);
this.unlisten();
this.removeListeners();
}
}
</script>

View File

@@ -0,0 +1,354 @@
/*****************************************************************************
* 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 () {
function DisplayLayoutToolbar(openmct) {
return {
name: "Display Layout Toolbar",
key: "layout",
description: "A toolbar for objects inside a display layout.",
forSelection: function (selection) {
// Apply the layout toolbar if the edit mode is on, and the selected object
// is inside a layout, or the main layout is selected.
return (openmct.editor.isEditing() && selection &&
((selection[1] && selection[1].context.item && selection[1].context.item.type === 'layout') ||
(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, {});
}
let selectedParent = selection[1] && selection[1].context.item,
selectedObject = selection[0].context.item,
layoutItem = selection[0].context.layoutItem,
layoutItemIndex = selection[0].context.index,
toolbar = [];
if (selectedObject && selectedObject.type === 'layout') {
toolbar.push({
control: "menu",
domainObject: selectedObject,
method: function (option) {
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",
label: "Add",
options: [
{
"name": "Box",
"class": "icon-box-round-corners"
},
{
"name": "Line",
"class": "icon-line-horz"
},
{
"name": "Text",
"class": "icon-font"
},
{
"name": "Image",
"class": "icon-image"
}
]
});
}
if (!layoutItem) {
return toolbar;
}
let path = `configuration.items[${layoutItemIndex}]`;
let separator = {
control: "separator"
};
if (layoutItem.type === 'subobject-view') {
if (toolbar.length > 0) {
toolbar.push(separator);
}
toolbar.push({
control: "toggle-button",
domainObject: selectedParent,
property: path + ".hasFrame",
options: [
{
value: false,
icon: 'icon-frame-hide',
title: "Hide frame"
},
{
value: true,
icon: 'icon-frame-show',
title: "Show frame"
}
]
});
} else {
const TEXT_SIZE = [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96];
let fill = {
control: "color-picker",
domainObject: selectedParent,
property: path + ".fill",
icon: "icon-paint-bucket",
title: "Set fill color"
},
stroke = {
control: "color-picker",
domainObject: selectedParent,
property: path + ".stroke",
icon: "icon-line-horz",
title: "Set border color"
},
color = {
control: "color-picker",
domainObject: selectedParent,
property: path + ".color",
icon: "icon-font",
mandatory: true,
title: "Set text color",
preventNone: true
},
size = {
control: "select-menu",
domainObject: selectedParent,
property: path + ".size",
title: "Set text size",
options: TEXT_SIZE.map(size => {
return {
value: size + "px"
};
})
},
x = {
control: "input",
type: "number",
domainObject: selectedParent,
property: path + ".x",
label: "X:",
title: "X position"
},
y = {
control: "input",
type: "number",
domainObject: selectedParent,
property: path + ".y",
label: "Y:",
title: "Y position",
},
width = {
control: 'input',
type: 'number',
domainObject: selectedParent,
property: path + ".width",
label: 'W:',
title: 'Resize object width'
},
height = {
control: 'input',
type: 'number',
domainObject: selectedParent,
property: path + ".height",
label: 'H:',
title: 'Resize object height'
};
if (layoutItem.type === 'telemetry-view') {
let displayMode = {
control: "select-menu",
domainObject: selectedParent,
property: path + ".displayMode",
title: "Set display mode",
options: [
{
name: 'Label + Value',
value: 'all'
},
{
name: "Label only",
value: "label"
},
{
name: "Value only",
value: "value"
}
]
},
value = {
control: "select-menu",
domainObject: selectedParent,
property: path + ".value",
title: "Set value",
options: openmct.telemetry.getMetadata(selectedObject).values().map(value => {
return {
name: value.name,
value: value.key
}
})
};
toolbar = [
displayMode,
separator,
value,
separator,
fill,
stroke,
color,
separator,
size,
separator,
x,
y,
height,
width
];
} else if (layoutItem.type === 'text-view' ) {
let text = {
control: "button",
domainObject: selectedParent,
property: path,
icon: "icon-gear",
title: "Edit text properties",
dialog: DIALOG_FORM['text']
};
toolbar = [
fill,
stroke,
color,
separator,
size,
separator,
x,
y,
height,
width,
separator,
text
];
} else if (layoutItem.type === 'box-view') {
toolbar = [
fill,
stroke,
separator,
x,
y,
height,
width
];
} else if (layoutItem.type === 'image-view') {
let url = {
control: "button",
domainObject: selectedParent,
property: path,
icon: "icon-image",
title: "Edit image properties",
dialog: DIALOG_FORM['image']
};
toolbar = [
stroke,
separator,
x,
y,
height,
width,
separator,
url
];
} else if (layoutItem.type === 'line-view') {
let x2 = {
control: "input",
type: "number",
domainObject: selectedParent,
property: path + ".x2",
label: "X2:",
title: "X2 position"
},
y2 = {
control: "input",
type: "number",
domainObject: selectedParent,
property: path + ".y2",
label: "Y2:",
title: "Y2 position",
};
toolbar = [
stroke,
separator,
x,
y,
x2,
y2
];
}
}
return toolbar;
}
}
}
return DisplayLayoutToolbar;
});

View File

@@ -29,13 +29,12 @@ define(function () {
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
layout: {
panels: {}
}
items: [],
layoutGrid: [10, 10],
};
}
}
}
return DisplayLayoutType;
});
});

View File

@@ -0,0 +1,96 @@
/*****************************************************************************
* 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>
<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">
@import '~styles/sass-base';
.c-box-view {
display: flex;
align-items: stretch;
.c-frame & {
@include abs();
}
}
</style>
<script>
import LayoutFrame from './LayoutFrame.vue'
export default {
makeDefinition() {
return {
fill: '#717171',
stroke: 'transparent',
x: 1,
y: 1,
width: 10,
height: 5
};
},
inject: ['openmct'],
components: {
LayoutFrame
},
props: {
item: Object,
gridSize: Array,
index: Number,
initSelect: Boolean
},
computed: {
style() {
return {
backgroundColor: this.item.fill,
border: '1px solid ' + this.item.stroke
};
}
},
mounted() {
let context = {
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el,
context,
this.initSelect
);
},
destroyed() {
if (this.removeSelectable) {
this.removeSelectable();
}
}
}
</script>

View File

@@ -0,0 +1,280 @@
/*****************************************************************************
* 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"
@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>
</div>
<component v-for="(item, index) in layoutItems"
:is="item.type"
:item="item"
:gridSize="gridSize"
:initSelect="initSelectIndex === index"
:index="index"
@drilledIn="updateDrilledIn"
@endDrag="endDrag"
>
</component>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.l-layout {
@include abs();
display: flex;
flex-direction: column;
&__grid-holder {
display: none;
}
&__object {
flex: 1 1 auto;
overflow: auto;
}
&__frame {
position: absolute;
}
}
.l-shell__main-container {
> .l-layout {
[s-selected] {
border: $browseBorderSelected;
}
}
}
// Styles moved to _global.scss;
</style>
<script>
import uuid from 'uuid';
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
};
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 {
drilledIn: undefined,
internalDomainObject: domainObject,
initSelectIndex: undefined
};
},
computed: {
gridSize() {
return this.internalDomainObject.configuration.layoutGrid;
},
layoutItems() {
return this.internalDomainObject.configuration.items;
}
},
inject: ['openmct'],
props: ['domainObject'],
components: ITEM_TYPE_VIEW_MAP,
methods: {
addElement(itemType, element) {
this.addItem(itemType + '-view', element);
},
setSelection(selection) {
if (selection.length === 0) {
return;
}
this.updateDrilledIn();
},
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 = this.openmct.objects.makeKeyString(item.identifier) === identifier;
}
});
},
bypassSelection($event) {
if (this.dragInProgress) {
if ($event) {
$event.stopImmediatePropagation();
}
return;
}
},
endDrag(item, updates) {
this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
let index = this.layoutItems.indexOf(item);
Object.assign(item, updates);
this.mutate(`configuration.items[${index}]`, item);
},
mutate(path, value) {
this.openmct.objects.mutate(this.internalDomainObject, path, value);
},
handleDrop($event) {
if (!$event.dataTransfer.types.includes('openmct/domain-object')) {
return;
}
$event.preventDefault();
let domainObject = JSON.parse($event.dataTransfer.getData('openmct/domain-object'));
let elementRect = this.$el.getBoundingClientRect();
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.addItem('telemetry-view', domainObject, droppedObjectPosition);
} else {
let identifier = this.openmct.objects.makeKeyString(domainObject.identifier);
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();
}
}
]
});
}
}
},
handleDragOver($event){
$event.preventDefault();
},
isTelemetry(domainObject) {
if (this.openmct.telemetry.isTelemetryObject(domainObject)
&& domainObject.type !== 'summary-widget') {
return true;
} else {
return false;
}
},
addItem(itemType, ...options) {
let item = getItemDefinition(itemType, this.openmct, this.gridSize, ...options);
item.type = itemType;
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.type === "telemetry-view") {
this.telemetryViewMap[this.openmct.objects.makeKeyString(item.identifier)] = true;
} else if (item.type === "subobject-view") {
this.objectViewMap[this.openmct.objects.makeKeyString(item.identifier)] = true;
}
},
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) {
// TODO: implement
}
},
mounted() {
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.initializeItems();
this.composition = this.openmct.composition.get(this.internalDomainObject);
this.composition.on('add', this.addChild);
this.composition.on('remove', this.removeChild);
this.composition.load();
},
destroyed: function () {
this.openmct.off('change', this.setSelection);
this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild);
this.unlisten();
}
}
</script>

View File

@@ -0,0 +1,94 @@
/*****************************************************************************
* 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>
<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">
@import '~styles/sass-base';
.c-image-view {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
.c-frame & {
@include abs();
border: 1px solid transparent;
}
}
</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
};
},
inject: ['openmct'],
props: {
item: Object,
gridSize: Array,
index: Number,
initSelect: Boolean
},
components: {
LayoutFrame
},
computed: {
style() {
return {
backgroundImage: 'url(' + this.item.url + ')',
border: '1px solid ' + this.item.stroke
}
}
},
mounted() {
let context = {
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, context, this.initSelect);
},
destroyed() {
if (this.removeSelectable) {
this.removeSelectable();
}
}
}
</script>

View File

@@ -21,21 +21,16 @@
*****************************************************************************/
<template>
<div class="c-frame has-local-controls is-selectable is-moveable"
:style="item.style"
:class="classObject"
@dblclick="drill(item.id, $event)">
<div class="c-frame__header">
<div class="c-frame__header__start">
<div class="c-frame__name icon-object">{{ item.domainObject.name }}</div>
<div class="c-frame__context-actions c-disclosure-button"></div>
</div>
<div class="c-frame__header__end">
<div class="c-button icon-expand local-controls--hidden"></div>
</div>
</div>
<object-view class="c-frame__object-view"
:object="item.domainObject"></object-view>
<div class="l-layout__frame c-frame has-local-controls"
:class="{
'no-frame': !item.hasFrame,
'u-inspectable': inspectable,
'is-drilled-in': item.drilledIn
}"
:style="style"
@dblclick="drill($event)">
<slot></slot>
<!-- Drag handles -->
<div class="c-frame-edit">
@@ -60,68 +55,11 @@
.c-frame {
display: flex;
flex-direction: column;
border-width: 1px;
border-color: transparent;
/*************************** 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();
}
}
}
border: 1px solid transparent;
/*************************** NO-FRAME */
&.no-frame {
> [class*="__header"] {
> [class*="contents"] > [class*="__header"] {
display: none;
}
}
@@ -131,15 +69,12 @@
border: 1px solid $colorInteriorBorder;
padding: $interiorMargin;
}
// Styles moved to _global.scss;
}
</style>
<script>
import ObjectView from '../../ui/components/layout/ObjectView.vue'
import LayoutDrag from './LayoutDrag'
import LayoutDrag from './../LayoutDrag'
export default {
inject: ['openmct'],
@@ -147,41 +82,51 @@
item: Object,
gridSize: Array
},
components: {
ObjectView
data() {
return {
dragPosition: undefined
}
},
computed: {
classObject: function () {
return {
'is-drilled-in': this.item.drilledIn,
'no-frame': !this.item.hasFrame
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: {
drill(id, $event) {
drill($event) {
if ($event) {
$event.stopPropagation();
}
if (!this.isBeingEdited(this.item.domainObject)) {
if (!this.openmct.editor.isEditing() || !this.item.identifier) {
return;
}
if (this.openmct.composition.get(this.item.domainObject) === undefined) {
return;
}
this.openmct.objects.get(this.item.identifier)
.then(domainObject => {
if (this.openmct.composition.get(domainObject) === undefined) {
return;
}
// Disable for fixed position.
if (this.item.domainObject.type === 'telemetry.fixed') {
return;
}
this.$emit('drilledIn', id);
},
isBeingEdited(object) {
// TODO: add logic when inEditContext() is implemented in Vue.
return true;
this.$emit('drilledIn', this.item);
});
},
updatePosition(event) {
let currentPosition = [event.pageX, event.pageY];
@@ -194,43 +139,31 @@
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.item.rawPosition,
posFactor,
dimFactor,
this.gridSize
);
this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize);
event.preventDefault();
},
continueDrag(event) {
event.preventDefault();
this.updatePosition(event);
if (this.activeDrag) {
this.$emit('dragInProgress', this.item.id, this.activeDrag.getAdjustedPosition(this.delta));
}
this.dragPosition = 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.id);
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;
event.preventDefault();
}
},
mounted() {
this.removeSelectable = this.openmct.selection.selectable(
this.$el,
{
item: this.item.domainObject
},
this.item.initSelect
);
},
destroyed() {
this.removeSelectable();
}
}
</script>
</script>

View File

@@ -0,0 +1,231 @@
/*****************************************************************************
* 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 has-local-controls 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'
};
},
inject: ['openmct'],
props: {
item: Object,
gridSize: Array,
initSelect: Boolean,
index: Number,
},
data() {
return {
dragPosition: undefined
};
},
computed: {
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`,
minWidth: `${width}px`,
minHeight: `${height}px`,
position: 'absolute'
};
},
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;
}
},
mounted() {
let context = {
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el,
context,
this.initSelect
);
},
destroyed() {
if (this.removeSelectable) {
this.removeSelectable();
}
}
}
</script>

View File

@@ -0,0 +1,112 @@
/*****************************************************************************
* 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>
<layout-frame :item="item"
:grid-size="gridSize"
@endDrag="(item, updates) => $emit('endDrag', item, updates)"
@drilledIn="item => $emit('drilledIn', item)">
<object-frame v-if="domainObject"
:domain-object="domainObject"
:object-path="objectPath"
:has-frame="item.hasFrame">
</object-frame>
</layout-frame>
</template>
<script>
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)
};
},
inject: ['openmct'],
props: {
item: Object,
gridSize: Array,
initSelect: Boolean,
index: Number
},
data() {
return {
domainObject: undefined,
objectPath: []
}
},
components: {
ObjectFrame,
LayoutFrame
},
methods: {
setObject(domainObject) {
this.domainObject = domainObject;
this.objectPath = [this.domainObject].concat(this.openmct.router.path);
let context = {
item: domainObject,
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, context, this.initSelect);
}
},
mounted() {
console.log(this.item);
this.openmct.objects.get(this.item.identifier)
.then(this.setObject);
},
destroyed() {
if (this.removeSelectable) {
this.removeSelectable();
}
}
}
</script>

View File

@@ -0,0 +1,233 @@
/*****************************************************************************
* 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>
<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>
</div>
</layout-frame>
</template>
<style lang="scss">
@import '~styles/sass-base';
.c-telemetry-view {
display: flex;
align-items: stretch;
> * {
// Label and value holders
flex: 1 1 auto;
display: flex;
flex-direction: row;
// justify-content: center;
align-items: center;
overflow: hidden;
padding: $interiorMargin;
> * {
// Text elements
@include ellipsize();
}
}
> * + * {
margin-left: $interiorMargin;
}
.c-frame & {
@include abs();
border: 1px solid transparent;
}
}
</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",
};
},
inject: ['openmct'],
props: {
item: Object,
gridSize: Array,
initSelect: Boolean,
index: Number
},
components: {
LayoutFrame
},
computed: {
showLabel() {
let displayMode = this.item.displayMode;
return displayMode === 'all' || displayMode === 'label';
},
showValue() {
let displayMode = this.item.displayMode;
return displayMode === 'all' || displayMode === 'value';
},
styleObject() {
return {
backgroundColor: this.item.fill,
borderColor: this.item.stroke,
color: this.item.color,
fontSize: this.item.size
}
},
fieldName() {
return this.valueMetadata && this.valueMetadata.name;
},
valueMetadata() {
return this.datum && this.metadata.value(this.item.value);
},
valueFormatter() {
return this.formats[this.item.value];
},
telemetryValue() {
if (!this.datum) {
return;
}
return this.valueFormatter && this.valueFormatter.format(this.datum);
},
telemetryClass() {
if (!this.datum) {
return;
}
let alarm = this.limitEvaluator && this.limitEvaluator.evaluate(this.datum, this.valueMetadata);
return alarm && alarm.cssClass;
}
},
data() {
return {
datum: undefined,
formats: undefined,
domainObject: undefined
}
},
methods: {
requestHistoricalData() {
let bounds = this.openmct.time.bounds();
let options = {
start: bounds.start,
end: bounds.end,
size: 1
};
this.openmct.telemetry.request(this.domainObject, options)
.then(data => {
if (data.length > 0) {
this.updateView(data[data.length - 1]);
}
});
},
subscribeToObject() {
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
if (this.openmct.time.clock() !== undefined) {
this.updateView(datum);
}
}.bind(this));
},
updateView(datum) {
this.datum = datum;
},
removeSubscription() {
if (this.subscription) {
this.subscription();
this.subscription = undefined;
}
},
refreshData(bounds, isTick) {
if (!isTick) {
this.datum = undefined;
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();
let context = {
item: domainObject,
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, context, this.initSelect);
}
},
mounted() {
this.openmct.objects.get(this.item.identifier)
.then(this.setObject);
this.openmct.time.on("bounds", this.refreshData);
},
destroyed() {
this.removeSubscription();
if (this.removeSelectable) {
this.removeSelectable();
}
this.openmct.time.off("bounds", this.refreshData);
}
}
</script>

View File

@@ -0,0 +1,100 @@
/*****************************************************************************
* 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>
<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">
@import '~styles/sass-base';
.c-text-view {
display: flex;
align-items: stretch;
.c-frame & {
@include abs();
border: 1px solid transparent;
}
}
</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
};
},
inject: ['openmct'],
props: {
item: Object,
gridSize: Array,
index: Number,
initSelect: Boolean
},
components: {
LayoutFrame
},
computed: {
style() {
return {
backgroundColor: this.item.fill,
borderColor: this.item.stroke,
color: this.item.color,
fontSize: this.item.size
};
}
},
mounted() {
let context = {
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, context, this.initSelect);
},
destroyed() {
if (this.removeSelectable) {
this.removeSelectable();
}
}
}
</script>

View File

@@ -20,10 +20,11 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Layout from './DisplayLayout.vue'
import Layout from './components/DisplayLayout.vue'
import Vue from 'vue'
import objectUtils from '../../api/objects/object-utils.js'
import DisplayLayoutType from './DisplayLayoutType.js'
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js'
export default function () {
return function (openmct) {
@@ -40,7 +41,7 @@ export default function () {
components: {
Layout
},
template: '<layout :domain-object="domainObject"></layout>',
template: '<layout ref="displayLayout" :domain-object="domainObject"></layout>',
provide: {
openmct,
objectUtils
@@ -53,6 +54,12 @@ export default function () {
}
});
},
getSelectionContext() {
return {
item: domainObject,
addElement: component && component.$refs.displayLayout.addElement
}
},
destroy() {
component.$destroy();
}
@@ -63,6 +70,7 @@ export default function () {
}
});
openmct.types.addType('layout', DisplayLayoutType());
openmct.toolbars.addProvider(new DisplayLayoutToolbar(openmct));
openmct.composition.addPolicy((parent, child) => {
if (parent.type === 'layout' && child.type === 'folder') {
return false;
@@ -70,41 +78,5 @@ export default function () {
return true;
}
});
openmct.toolbars.addProvider({
name: "Display Layout Toolbar",
key: "layout",
description: "A toolbar for objects inside a display layout.",
forSelection: function (selection) {
// Apply the layout toolbar if the selected object is inside a layout,
// and in edit mode.
return (selection &&
selection[1] &&
selection[1].context.item &&
selection[1].context.item.type === 'layout' &&
openmct.editor.isEditing());
},
toolbar: function (selection) {
let id = openmct.objects.makeKeyString(selection[0].context.item.identifier);
return [
{
control: "toggle-button",
domainObject: selection[1].context.item,
property: "configuration.layout.panels[" + id + "].hasFrame",
options: [
{
value: false,
icon: 'icon-frame-hide',
title: "Hide frame"
},
{
value: true,
icon: 'icon-frame-show',
title: "Show frame"
}
]
}
];
}
});
}
}

View File

@@ -0,0 +1,203 @@
/*****************************************************************************
* 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-container"
:style="[{'flex-basis': sizeString}]"
:class="{'is-empty': !frames.length}">
<div class="c-fl-container__header icon-grippy-ew"
v-show="isEditing"
draggable="true"
@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">
<template
v-for="(frame, i) in frames">
<frame-component
class="c-fl-container__frame"
:key="frame.id"
:frame="frame"
:index="i"
: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 !== frames.length - 1)"
:key="i"
:index="i"
:orientation="rowsLayout ? 'horizontal' : 'vertical'"
@init-move="startFrameResizing"
@move="frameResizing"
@end-move="endFrameResizing">
</resize-handle>
</template>
</div>
</div>
</template>
<script>
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 MIN_FRAME_SIZE = 5;
export default {
inject:['openmct'],
props: ['container', 'index', 'rowsLayout'],
mixins: [isEditingMixin],
components: {
FrameComponent,
ResizeHandle,
DropHint
},
computed: {
frames() {
return this.container.frames;
},
sizeString() {
return `${Math.round(this.container.size)}%`
}
},
methods: {
allowDrop(event, index) {
if (event.dataTransfer.getData('openmct/domain-object')) {
return true;
}
let frameId = event.dataTransfer.getData('frameid'),
containerIndex = Number(event.dataTransfer.getData('containerIndex'));
if (!frameId) {
return false;
}
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')) {
// create frame using domain object
let domainObject = JSON.parse(event.dataTransfer.getData('openmct/domain-object'));
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.size + afterFrame.size;
},
frameResizing(index, delta, event) {
let percentageMoved = Math.round(delta / this.getElSize() * 100),
beforeFrame = this.frames[index],
afterFrame = this.frames[index + 1];
beforeFrame.size = this.getFrameSize(beforeFrame.size + percentageMoved);
afterFrame.size = this.getFrameSize(afterFrame.size - percentageMoved);
},
endFrameResizing(index, event) {
this.persist();
},
getElSize() {
if (this.rowsLayout) {
return this.$el.offsetWidth;
} else {
return this.$el.offsetHeight;
}
},
getFrameSize(size) {
if (size < MIN_FRAME_SIZE) {
return MIN_FRAME_SIZE
} else if (size > (this.maxMoveSize - MIN_FRAME_SIZE)) {
return (this.maxMoveSize - MIN_FRAME_SIZE);
} else {
return size;
}
},
persist() {
this.$emit('persist', this.index);
},
startContainerDrag(event) {
event.dataTransfer.setData('containerid', this.container.id);
}
},
mounted() {
let context = {
item: this.$parent.domainObject,
addContainer: this.addContainer,
type: 'container',
containerId: this.container.id
}
this.unsubscribeSelection = this.openmct.selection.selectable(this.$el, context, false);
},
beforeDestroy() {
this.unsubscribeSelection();
}
}
</script>

View File

@@ -0,0 +1,80 @@
/*****************************************************************************
* 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 v-show="isValidTarget">
<div class="c-drop-hint c-drop-hint--always-show"
:class="{'is-mouse-over': isMouseOver}"
@dragenter="dragenter"
@dragleave="dragleave"
@drop="dropHandler">
</div>
</div>
</template>
<style lang="scss">
</style>
<script>
export default {
props:{
index: Number,
allowDrop: {
type: Function,
required: true
}
},
data() {
return {
isMouseOver: false,
isValidTarget: false
}
},
methods: {
dragenter() {
this.isMouseOver = true;
},
dragleave() {
this.isMouseOver = false;
},
dropHandler(event) {
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

@@ -0,0 +1,624 @@
/*****************************************************************************
* 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"
v-if="areAllContainersEmpty()">
<span class="c-fl__empty-message">This Flexible Layout is currently empty</span>
</div>
<div class="c-fl__container-holder"
:class="{
'c-fl--rows': rowsLayout === true
}">
<template v-for="(container, index) in containers">
<drop-hint
class="c-fl-frame__drop-hint"
v-if="index === 0 && containers.length > 1"
:key="index"
:index="-1"
:allow-drop="allowContainerDrop"
@object-drop-to="moveContainer">
</drop-hint>
<container-component
class="c-fl__container"
:key="container.id"
:index="index"
:container="container"
:rowsLayout="rowsLayout"
@move-frame="moveFrame"
@create-frame="createFrame"
@persist="persist">
</container-component>
<resize-handle
v-if="index !== (containers.length - 1)"
:key="index"
:index="index"
:orientation="rowsLayout ? 'vertical' : 'horizontal'"
@init-move="startContainerResizing"
@move="containerResizing"
@end-move="endContainerResizing">
</resize-handle>
<drop-hint
class="c-fl-frame__drop-hint"
v-if="containers.length > 1"
:key="index"
:index="index"
:allowDrop="allowContainerDrop"
@object-drop-to="moveContainer">
</drop-hint>
</template>
</div>
</div>
</template>
<style lang="scss">
@import '~styles/sass-base';
.c-fl {
@include abs();
display: flex;
flex-direction: column; // TEMP: only needed to support temp-toolbar element
> * + * { margin-top: $interiorMargin; }
.temp-toolbar {
flex: 0 0 auto;
}
&__container-holder {
display: flex;
flex: 1 1 100%; // Must needs to be 100% to work
// Columns by default
flex-direction: row;
> * + * { margin-left: 1px; }
&[class*='--rows'] {
//@include test(blue, 0.1);
flex-direction: column;
> * + * {
margin-left: 0;
margin-top: 1px;
}
}
}
&__empty {
@include abs();
background: rgba($colorBodyFg, 0.1);
display: flex;
align-items: center;
justify-content: center;
text-align: center;
> * {
font-style: italic;
opacity: 0.5;
}
}
}
.c-fl-container {
/***************************************************** CONTAINERS */
$headerSize: 16px;
border: 1px solid transparent;
display: flex;
flex-direction: column;
overflow: auto;
// flex-basis is set with inline style in code, controls size
flex-grow: 1;
flex-shrink: 1;
&__header {
// Only displayed when editing
background: $editSelectableColor;
color: $editSelectableColorFg;
cursor: move;
display: flex;
align-items: center;
flex: 0 0 $headerSize;
&:before {
// Drag grippy
font-size: 0.8em;
opacity: 0.5;
position: absolute;
left: 50%; top: 50%;
transform-origin: center;
transform: translate(-50%, -50%);
}
}
&__size-indicator {
position: absolute;
display: inline-block;
right: $interiorMargin;
}
&__frames-holder {
display: flex;
flex: 1 1 100%; // Must be 100% to work
flex-direction: column; // Default
align-content: stretch;
align-items: stretch;
overflow: hidden; // This sucks, but doing in the short-term
}
.is-editing & {
//background: $editCanvasColorBg;
border-color: $editSelectableColor;
&:hover {
border-color: $editSelectableColorHov;
}
&[s-selected] {
border-color: $editSelectableColorSelected;
.c-fl-container__header {
background: $editSelectableColorSelected;
color: $editSelectableColorSelectedFg;
}
}
}
/****** THEIR FRAMES */
// 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 */
.c-fl--rows & {
// Layout is rows
flex-direction: row;
&__header {
flex-basis: $headerSize;
overflow: hidden;
&:before {
// Drag grippy
transform: rotate(90deg) translate(-50%, 50%);
}
}
&__size-indicator {
right: 0;
top: $interiorMargin;
transform-origin: top right;
transform: rotate(-90deg) translateY(-100%);
}
&__frames-holder {
flex-direction: row;
}
}
}
.c-fl-frame {
/***************************************************** CONTAINER FRAMES */
$sizeIndicatorM: 16px;
$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
&__drag-wrapper {
flex: 1 1 auto;
overflow: auto;
.is-editing & {
> * {
pointer-events: none;
}
}
}
&__header {
flex: 0 0 auto;
margin-bottom: $interiorMargin;
}
&__object-view {
flex: 1 1 auto;
overflow: auto;
}
&__size-indicator {
$size: 35px;
@include ellipsize();
background: $colorBtnBg;
border-top-left-radius: $controlCr;
color: $colorBtnFg;
display: inline-block;
padding: $interiorMarginSm 0;
position: absolute;
pointer-events: none;
text-align: center;
width: $size;
z-index: 2;
// Changed when layout is different, see below
border-top-right-radius: $controlCr;
bottom: 1px;
right: $sizeIndicatorM;
}
&__drop-hint {
flex: 0 0 $dropHintSize;
.c-drop-hint {
border-radius: $smallCr;
}
}
&__resize-handle {
$size: 2px;
$margin: 3px;
$marginHov: 0;
$grippyThickness: $size + 6;
$grippyLen: $grippyThickness * 2;
display: flex;
flex-direction: column;
flex: 0 0 ($margin * 2) + $size;
transition: $transOut;
&:before {
// The visible resize line
background: $editColor;
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;
}
}
&.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;
}
}
}
// Hide the resize-handles in first and last c-fl-frame elements
&:first-child,
&:last-child {
.c-fl-frame__resize-handle {
display: none;
}
}
.c-fl--rows & {
flex-direction: row;
&__size-indicator {
border-bottom-left-radius: $controlCr;
border-top-right-radius: 0;
bottom: $sizeIndicatorM;
right: 1px;
}
}
&--first-in-container {
border: none;
flex: 0 0 0;
.c-fl-frame__drag-wrapper {
display: none;
}
&.is-dragging {
flex-basis: $dropHintSize;
}
}
.is-empty & {
&.c-fl-frame--first-in-container {
flex: 1 1 auto;
}
&__drop-hint {
flex: 1 0 100%;
margin: 0;
}
}
.c-object-view {
display: contents;
}
}
</style>
<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 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', 'layoutObject'],
mixins: [isEditingMixin],
components: {
ContainerComponent,
ResizeHandle,
DropHint
},
data() {
return {
domainObject: this.layoutObject
}
},
computed: {
layoutDirectionStr() {
if (this.rowsLayout) {
return 'Rows'
} 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).length;
},
addContainer() {
let container = new Container();
this.containers.push(container);
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]);
} else {
this.openmct.objects.mutate(this.domainObject, 'configuration.containers', this.containers);
}
},
startContainerResizing(index) {
let beforeContainer = this.containers[index],
afterContainer = this.containers[index + 1];
this.maxMoveSize = beforeContainer.size + afterContainer.size;
},
containerResizing(index, delta, event) {
let percentageMoved = Math.round(delta / this.getElSize() * 100),
beforeContainer = this.containers[index],
afterContainer = this.containers[index + 1];
beforeContainer.size = this.getContainerSize(beforeContainer.size + percentageMoved);
afterContainer.size = this.getContainerSize(afterContainer.size - percentageMoved);
},
endContainerResizing(event) {
this.persist();
},
getElSize() {
if (this.rowsLayout) {
return this.$el.offsetHeight;
} else {
return this.$el.offsetWidth;
}
},
getContainerSize(size) {
if (size < MIN_CONTAINER_SIZE) {
return MIN_CONTAINER_SIZE
} else if (size > (this.maxMoveSize - MIN_CONTAINER_SIZE)) {
return (this.maxMoveSize - MIN_CONTAINER_SIZE);
} else {
return size;
}
},
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 {
this.containers.splice(toIndex, 0, container);
}
this.persist();
}
},
mounted() {
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.unobserve = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
},
beforeDestroy() {
this.unsubscribeSelection();
this.unobserve();
}
}
</script>

View File

@@ -0,0 +1,109 @@
/*****************************************************************************
* 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-frame"
:style="{
'flex-basis': `${frame.size}%`
}">
<div class="c-frame c-fl-frame__drag-wrapper is-selectable is-moveable"
draggable="true"
@dragstart="initDrag"
ref="frame">
<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.size && frame.size < 100">
{{frame.size}}%
</div>
</div>
</div>
</template>
<script>
import ResizeHandle from './resizeHandle.vue';
import ObjectFrame from '../../../ui/components/ObjectFrame.vue';
import isEditingMixin from '../mixins/isEditing';
export default {
inject: ['openmct'],
props: ['frame', 'index', 'containerIndex'],
mixins: [isEditingMixin],
data() {
return {
domainObject: undefined,
objectPath: undefined
}
},
components: {
ResizeHandle,
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) {
event.dataTransfer.setData('frameid', this.frame.id);
event.dataTransfer.setData('containerIndex', this.containerIndex);
}
},
mounted() {
if (this.frame.domainObjectIdentifier) {
this.openmct.objects.get(this.frame.domainObjectIdentifier).then((object)=>{
this.setDomainObject(object);
});
}
},
beforeDestroy() {
if (this.unsubscribeSelection) {
this.unsubscribeSelection();
}
}
}
</script>

View File

@@ -0,0 +1,91 @@
/*****************************************************************************
* 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-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,
isDragging: false,
}
},
methods: {
mousedown(event) {
event.preventDefault();
this.$emit('init-move', this.index);
document.body.addEventListener('mousemove', this.mousemove);
document.body.addEventListener('mouseup', this.mouseup);
},
mousemove(event) {
event.preventDefault();
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) {
this.$emit('end-move', event);
document.body.removeEventListener('mousemove', this.mousemove);
document.body.removeEventListener('mouseup', this.mouseup);
},
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

@@ -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/flexibleLayout.vue',
'vue'
], function (
FlexibleLayoutComponent,
Vue
) {
function FlexibleLayoutViewProvider(openmct) {
return {
key: 'flexible-layout',
name: 'FlexibleLayout',
cssClass: 'icon-layout-view',
canView: function (domainObject) {
return domainObject.type === 'flexible-layout';
},
view: function (domainObject) {
let component;
return {
show: function (element) {
component = new Vue({
components: {
FlexibleLayoutComponent: FlexibleLayoutComponent.default
},
provide: {
openmct,
layoutObject: domainObject
},
el: element,
template: '<flexible-layout-component></flexible-layout-component>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}
return FlexibleLayoutViewProvider;
});

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

@@ -20,47 +20,37 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'vue',
'../../res/templates/viewSnapshot.html'
'./flexibleLayoutViewProvider',
'./utils/container',
'./toolbarProvider'
], function (
Vue,
snapshotOverlayTemplate
FlexibleLayoutViewProvider,
Container,
ToolBarProvider
) {
function SnapshotOverlay (embedObject, formatTime) {
this.embedObject = embedObject;
return function plugin() {
this.snapshotOverlayVue = new Vue({
template: snapshotOverlayTemplate,
data: function () {
return {
embed: embedObject
};
},
methods: {
close: this.close.bind(this),
formatTime: formatTime
}
});
return function install(openmct) {
openmct.objectViews.addProvider(new FlexibleLayoutViewProvider(openmct));
this.open();
}
openmct.types.addType('flexible-layout', {
name: "Flexible Layout",
creatable: true,
description: "A fluid, flexible layout canvas that can display multiple objects in rows or columns.",
cssClass: 'icon-flexible-layout',
initialize: function (domainObject) {
domainObject.configuration = {
containers: [new Container.default(50), new Container.default(50)],
rowsLayout: false
};
domainObject.composition = [];
}
});
SnapshotOverlay.prototype.open = function () {
this.overlay = document.createElement('div');
this.overlay.classList.add('abs');
let toolbar = ToolBarProvider.default(openmct);
document.body.appendChild(this.overlay);
this.overlay.appendChild(this.snapshotOverlayVue.$mount().$el);
openmct.toolbars.addProvider(toolbar);
};
};
SnapshotOverlay.prototype.close = function (event) {
event.stopPropagation();
this.snapshotOverlayVue.$destroy();
this.overlay.parentNode.removeChild(this.overlay);
};
return SnapshotOverlay;
});

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

@@ -0,0 +1,11 @@
import uuid from 'uuid';
class Container {
constructor (size) {
this.id = uuid();
this.frames = [];
this.size = size;
}
}
export default Container;

View File

@@ -0,0 +1,13 @@
import uuid from 'uuid';
class Frame {
constructor(domainObjectIdentifier, size) {
this.id = uuid();
this.domainObjectIdentifier = domainObjectIdentifier;
this.size = size;
this.noFrame = false;
}
}
export default Frame;

View File

@@ -0,0 +1,159 @@
<template>
<a class="l-grid-view__item c-grid-item"
:class="{ 'is-alias': item.isAlias === true }"
:href="objectLink">
<div class="c-grid-item__type-icon"
:class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'">
</div>
<div class="c-grid-item__details">
<!-- Name and metadata -->
<div class="c-grid-item__name"
:title="item.model.name">{{item.model.name}}</div>
<div class="c-grid-item__metadata"
:title="item.type.name">
<span class="c-grid-item__metadata__type">{{item.type.name}}</span>
</div>
</div>
<div class="c-grid-item__controls">
<div class="icon-people" title='Shared'></div>
<button class="c-click-icon icon-info c-info-button" title='More Info'></button>
<div class="icon-pointer-right c-pointer-icon"></div>
</div>
</a>
</template>
<style lang="scss">
@import "~styles/sass-base";
/******************************* GRID ITEMS */
.c-grid-item {
// Mobile-first
@include button($bg: $colorItemBg, $fg: $colorItemFg);
cursor: pointer;
display: flex;
padding: $interiorMarginLg;
&__type-icon {
filter: $colorKeyFilter;
flex: 0 0 $gridItemMobile;
font-size: floor($gridItemMobile / 2);
margin-right: $interiorMarginLg;
}
&.is-alias {
// Object is an alias to an original.
[class*='__type-icon'] {
@include isAlias();
color: $colorIconAliasForKeyFilter;
}
}
&__details {
display: flex;
flex-flow: column nowrap;
flex: 1 1 auto;
}
&__name {
@include ellipsize();
color: $colorItemFg;
@include headerFont(1.2em);
margin-bottom: $interiorMarginSm;
}
&__metadata {
color: $colorItemFgDetails;
font-size: 0.9em;
body.mobile & {
[class*='__item-count'] {
&:before {
content: ' - ';
}
}
}
}
&__controls {
color: $colorItemFgDetails;
flex: 0 0 64px;
font-size: 1.2em;
display: flex;
align-items: center;
justify-content: flex-end;
> * + * {
margin-left: $interiorMargin;
}
}
body.desktop & {
$transOutMs: 300ms;
flex-flow: column nowrap;
transition: background $transOutMs ease-in-out;
&:hover {
background: $colorItemBgHov;
transition: $transIn;
.c-grid-item__type-icon {
filter: $colorKeyFilterHov;
transform: scale(1);
transition: $transInBounce;
}
}
> * {
margin: 0; // Reset from mobile
}
&__controls {
align-items: start;
flex: 0 0 auto;
order: 1;
.c-info-button,
.c-pointer-icon { display: none; }
}
&__type-icon {
flex: 1 1 auto;
font-size: floor($gridItemDesk / 3);
margin: $interiorMargin 22.5% $interiorMargin * 3 22.5%;
order: 2;
transform: scale(0.9);
transform-origin: center;
transition: all $transOutMs ease-in-out;
}
&__details {
flex: 0 0 auto;
justify-content: flex-end;
order: 3;
}
&__metadata {
display: flex;
&__type {
flex: 1 1 auto;
@include ellipsize();
}
&__item-count {
opacity: 0.7;
flex: 0 0 auto;
}
}
}
}
</style>
<script>
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
import objectLink from '../../../ui/mixins/object-link';
export default {
mixins: [contextMenuGesture, objectLink],
props: ['item']
}
</script>

View File

@@ -1,28 +1,10 @@
<template>
<div class="l-grid-view">
<div v-for="(item, index) in items"
v-bind:key="index"
class="l-grid-view__item c-grid-item"
:class="{ 'is-alias': item.isAlias === true }"
@click="navigate(item)">
<div class="c-grid-item__type-icon"
:class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'">
</div>
<div class="c-grid-item__details">
<!-- Name and metadata -->
<div class="c-grid-item__name"
:title="item.model.name">{{item.model.name}}</div>
<div class="c-grid-item__metadata"
:title="item.type.name">
<span class="c-grid-item__metadata__type">{{item.type.name}}</span>
</div>
</div>
<div class="c-grid-item__controls">
<div class="icon-people" title='Shared'></div>
<button class="c-click-icon icon-info c-info-button" title='More Info'></button>
<div class="icon-pointer-right c-pointer-icon"></div>
</div>
</div>
<grid-item v-for="(item, index) in items"
:key="index"
:item="item"
:object-path="item.objectPath">
</grid-item>
</div>
</template>
@@ -33,6 +15,7 @@
.l-grid-view {
display: flex;
flex-flow: column nowrap;
overflow: auto;
&__item {
flex: 0 0 auto;
@@ -176,17 +159,11 @@
<script>
import compositionLoader from './composition-loader';
import GridItem from './GridItem.vue';
export default {
components: {GridItem},
mixins: [compositionLoader],
inject: ['domainObject', 'openmct'],
methods: {
navigate(item) {
let currentLocation = this.openmct.router.currentLocation.path,
navigateToPath = `${currentLocation}/${this.openmct.objects.makeKeyString(item.model.identifier)}`;
this.openmct.router.setPath(navigateToPath);
}
}
inject: ['openmct']
}
</script>

View File

@@ -0,0 +1,72 @@
<template>
<tr class="c-list-item"
:class="{ 'is-alias': item.isAlias === true }"
@click="navigate">
<td class="c-list-item__name">
<a :href="objectLink" ref="objectLink">
<div class="c-list-item__type-icon"
:class="item.type.cssClass"></div>
{{item.model.name}}
</a>
</td>
<td class="c-list-item__type">{{ item.type.name }}</td>
<td class="c-list-item__date-created">{{ formatTime(item.model.persisted, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
<td class="c-list-item__date-updated">{{ formatTime(item.model.modified, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
</tr>
</template>
<style lang="scss">
@import "~styles/sass-base";
/******************************* LIST ITEM */
.c-list-item {
&__name {
@include ellipsize();
}
&__type-icon {
color: $colorKey;
display: inline-block;
width: 1em;
margin-right:$interiorMarginSm;
}
&.is-alias {
// Object is an alias to an original.
[class*='__type-icon'] {
&:after {
color: $colorIconAlias;
content: $glyph-icon-link;
font-family: symbolsfont;
display: block;
position: absolute;
text-shadow: rgba(black, 0.5) 0 1px 2px;
top: auto; left: -1px; bottom: 1px; right: auto;
transform-origin: bottom left;
transform: scale(0.65);
}
}
}
}
</style>
<script>
import moment from 'moment';
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
import objectLink from '../../../ui/mixins/object-link';
export default {
mixins: [contextMenuGesture, objectLink],
props: ['item'],
methods: {
formatTime(timestamp, format) {
return moment(timestamp).format(format);
},
navigate() {
this.$refs.objectLink.click();
}
}
}
</script>

View File

@@ -2,60 +2,50 @@
<div class="c-table c-table--sortable c-list-view">
<table class="c-table__body">
<thead class="c-table__header">
<tr>
<th class="is-sortable"
:class="{
'is-sorting': sortBy === 'model.name',
'asc': ascending,
'desc': !ascending
}"
@click="sort('model.name', true)">
Name
</th>
<th class="is-sortable"
:class="{
'is-sorting': sortBy === 'type.name',
'asc': ascending,
'desc': !ascending
}"
@click="sort('type.name', true)">
Type
</th>
<th class="is-sortable"
:class="{
'is-sorting': sortBy === 'model.persisted',
'asc': ascending,
'desc': !ascending
}"
@click="sort('model.persisted', false)">
Created Date
</th>
<th class="is-sortable"
:class="{
'is-sorting': sortBy === 'model.modified',
'asc': ascending,
'desc': !ascending
}"
@click="sort('model.modified', false)">
Updated Date
</th>
</tr>
<tr>
<th class="is-sortable"
:class="{
'is-sorting': sortBy === 'model.name',
'asc': ascending,
'desc': !ascending
}"
@click="sort('model.name', true)">
Name
</th>
<th class="is-sortable"
:class="{
'is-sorting': sortBy === 'type.name',
'asc': ascending,
'desc': !ascending
}"
@click="sort('type.name', true)">
Type
</th>
<th class="is-sortable"
:class="{
'is-sorting': sortBy === 'model.persisted',
'asc': ascending,
'desc': !ascending
}"
@click="sort('model.persisted', false)">
Created Date
</th>
<th class="is-sortable"
:class="{
'is-sorting': sortBy === 'model.modified',
'asc': ascending,
'desc': !ascending
}"
@click="sort('model.modified', false)">
Updated Date
</th>
</tr>
</thead>
<tbody>
<tr class="c-list-item"
v-for="(item,index) in sortedItems"
v-bind:key="index"
:class="{ 'is-alias': item.isAlias === true }"
@click="navigate(item)">
<td class="c-list-item__name">
<div class="c-list-item__type-icon"
:class="item.type.cssClass"></div>
{{item.model.name}}
</td>
<td class="c-list-item__type">{{ item.type.name }}</td>
<td class="c-list-item__date-created">{{ formatTime(item.model.persisted, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
<td class="c-list-item__date-updated">{{ formatTime(item.model.modified, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
</tr>
<list-item v-for="(item,index) in sortedItems"
:item="item"
:object-path="item.objectPath">
</list-item>
</tbody>
</table>
</div>
@@ -96,48 +86,16 @@
}
}
}
.c-list-item {
&__name {
@include ellipsize();
}
&__type-icon {
color: $colorKey;
display: inline-block;
width: 1em;
margin-right:$interiorMarginSm;
}
&.is-alias {
// Object is an alias to an original.
[class*='__type-icon'] {
&:after {
color: $colorIconAlias;
content: $glyph-icon-link;
font-family: symbolsfont;
display: block;
position: absolute;
text-shadow: rgba(black, 0.5) 0 1px 2px;
top: auto; left: -1px; bottom: 1px; right: auto;
transform-origin: bottom left;
transform: scale(0.65);
}
}
}
}
/******************************* LIST ITEM */
</style>
<script>
import lodash from 'lodash';
import moment from 'moment';
import compositionLoader from './composition-loader';
import ListItem from './ListItem.vue';
export default {
components: {ListItem},
mixins: [compositionLoader],
inject: ['domainObject', 'openmct'],
data() {
@@ -156,15 +114,6 @@ export default {
}
},
methods: {
formatTime(timestamp, format) {
return moment(timestamp).format(format);
},
navigate(item) {
let currentLocation = this.openmct.router.currentLocation.path,
navigateToPath = `${currentLocation}/${this.openmct.objects.makeKeyString(item.model.identifier)}`;
this.openmct.router.setPath(navigateToPath);
},
sort(field, defaultDirection) {
if (this.sortBy === field) {
this.ascending = !this.ascending;

View File

@@ -31,16 +31,19 @@ export default {
methods: {
add(child, index, anything) {
var type = this.openmct.types.get(child.type) || unknownObjectType;
this.items.push({
model: child,
type: type.definition,
isAlias: this.domainObject.identifier.key !== child.location
isAlias: this.domainObject.identifier.key !== child.location,
objectPath: [child].concat(openmct.router.path)
});
},
remove(child) {
// TODO: implement remove action
console.log('remove child? might be identifier');
remove(identifier) {
this.items = this.items
.filter((i) => {
return i.model.identifier.key !== identifier.key
|| i.model.identifier.namespace !== identifier.namespace
});
}
}
}

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

@@ -1,5 +1,5 @@
<li class="c-notebook__entry c-ne has-local-controls"
v-on:drop="dropOnEntry(entry.id)"
v-on:drop="dropOnEntry(entry.id, $event)"
v-on:dragover="dragoverOnEntry"
>
<div class="c-ne__time-and-content">
@@ -19,8 +19,9 @@
<div class="c-ne__embeds">
<notebook-embed
v-for="(embed, index) in entry.embeds"
v-bind:embed="embed"
v-bind:entry="entry"
:key="index"
:embed="embed"
:entry="entry"
></notebook-embed>
</div>
</div>

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,25 @@
</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)"
id="newEntry">
<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()">
<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>

View File

@@ -1,162 +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.
*****************************************************************************/
/**
* Module defining viewSnapshot (Originally NewWindowAction). Created by vwoeltje on 11/18/14.
*/
define(
["painterro", "zepto"],
function (Painterro, $) {
var annotationStruct = {
title: "Annotate Snapshot",
template: "annotate-snapshot",
options: [{
name: "OK",
key: "ok",
description: "save annotation"
},
{
name: "Cancel",
key: "cancel",
description: "cancel editing"
}]
};
function AnnotateSnapshot(dialogService, dndService, $rootScope, context) {
context = context || {};
// Choose the object to be opened into a new tab
this.domainObject = context.selectedObject || context.domainObject;
this.dialogService = dialogService;
this.dndService = dndService;
this.$rootScope = $rootScope;
}
AnnotateSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId) {
var DOMAIN_OBJECT = this.domainObject;
var ROOTSCOPE = this.$rootScope;
var painterro;
var save = false;
var controller = ['$scope', '$timeout', function PainterroController($scope, $timeout) {
$(document.body).find('.l-dialog .outer-holder').addClass('annotation-dialog');
// Timeout is necessary because Painterro uses document.getElementById, and mct-include
// hasn't added the dialog to the DOM yet.
$timeout(function () {
painterro = 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) {
if (entryId && embedId) {
var elementPos = DOMAIN_OBJECT.model.entries.map(function (x) {
return x.createdOn;
}).indexOf(entryId);
var entryEmbeds = DOMAIN_OBJECT.model.entries[elementPos].embeds;
var embedPos = entryEmbeds.map(function (x) {
return x.id;
}).indexOf(embedId);
saveSnap(image.asBlob(), embedPos, elementPos, DOMAIN_OBJECT);
}else {
ROOTSCOPE.snapshot = {'src': image.asDataURL('image/png'),
'modified': Date.now()};
}
}
done(true);
}
}).show(snapshot);
});
}];
annotationStruct.model = {'controller': controller};
function saveNotes(param) {
if (param === 'ok') {
save = true;
}else {
save = false;
ROOTSCOPE.snapshot = "annotationCancelled";
}
painterro.save();
}
function rejectNotes() {
save = false;
ROOTSCOPE.snapshot = "annotationCancelled";
painterro.save();
}
function saveSnap(url, embedPos, entryPos, domainObject) {
var snap = false;
if (embedPos !== -1 && entryPos !== -1) {
var reader = new window.FileReader();
reader.readAsDataURL(url);
reader.onloadend = function () {
snap = reader.result;
domainObject.useCapability('mutation', function (model) {
if (model.entries[entryPos]) {
model.entries[entryPos].embeds[embedPos].snapshot = {
'src': snap,
'type': url.type,
'size': url.size,
'modified': Date.now()
};
model.entries[entryPos].embeds[embedPos].id = Date.now();
}
});
};
}
}
this.dialogService.getUserChoice(annotationStruct)
.then(saveNotes, rejectNotes);
};
return AnnotateSnapshot;
}
);

View File

@@ -1,204 +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 SNAPSHOT_TEMPLATE = '<mct-representation key="\'draggedEntry\'"' +
'class="t-rep-frame holder"' +
'mct-object="selObj">' +
'</mct-representation>';
var NEW_TASK_FORM = {
name: "Create a Notebook Entry",
hint: "Please select one Notebook",
sections: [{
rows: [{
name: 'Entry',
key: 'entry',
control: 'textarea',
required: false,
"cssClass": "l-textarea-sm"
},
{
name: 'Embed Type',
key: 'withSnapshot',
control: 'snapshot-select',
"options": [
{
"name": "Link and Snapshot",
"value": true
},
{
"name": "Link only",
"value": false
}
]
},
{
name: 'Embed',
key: 'embedObject',
control: 'embed-control'
},
{
name: 'Save in Notebook',
key: 'saveNotebook',
control: 'locator',
validate: validateLocation
}]
}]
};
function NewEntryContextual($compile, $rootScope, dialogService, notificationService, linkService, context) {
context = context || {};
this.domainObject = context.selectedObject || context.domainObject;
this.dialogService = dialogService;
this.notificationService = notificationService;
this.linkService = linkService;
this.$rootScope = $rootScope;
this.$compile = $compile;
}
function validateLocation(newParentObj) {
return newParentObj.model.type === 'notebook';
}
NewEntryContextual.prototype.perform = function () {
var self = this;
var domainObj = this.domainObject;
var notification = this.notificationService;
var dialogService = this.dialogService;
var rootScope = this.$rootScope;
rootScope.newEntryText = '';
// // Create the overlay element and add it to the document's body
this.$rootScope.selObj = domainObj;
this.$rootScope.selValue = "";
var newScope = rootScope.$new();
newScope.selObj = domainObj;
newScope.selValue = "";
this.$compile(SNAPSHOT_TEMPLATE)(newScope);
this.$rootScope.$watch("snapshot", setSnapshot);
function setSnapshot(value) {
if (value === "annotationCancelled") {
rootScope.snapshot = rootScope.lastValue;
rootScope.lastValue = '';
} else if (value && value !== rootScope.lastValue) {
var overlayModel = {
title: NEW_TASK_FORM.name,
message: NEW_TASK_FORM.message,
structure: NEW_TASK_FORM,
value: {'entry': rootScope.newEntryText || ""}
};
rootScope.currentDialog = overlayModel;
dialogService.getDialogResponse(
"overlay-dialog",
overlayModel,
function () {
return overlayModel.value;
}
).then(addNewEntry);
rootScope.lastValue = value;
}
}
function addNewEntry(options) {
options.selectedModel = options.embedObject.getModel();
options.cssClass = options.embedObject.getCapability('type').typeDef.cssClass;
if (self.$rootScope.snapshot) {
options.snapshot = self.$rootScope.snapshot;
self.$rootScope.snapshot = undefined;
}else {
options.snapshot = undefined;
}
if (!options.withSnapshot) {
options.snapshot = '';
}
createSnap(options);
}
function createSnap(options) {
options.saveNotebook.useCapability('mutation', function (model) {
var entries = model.entries;
var lastEntry = entries[entries.length - 1];
var date = Date.now();
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
model.entries.push({
'id': date,
'createdOn': date,
'text': options.entry,
'embeds': [{'type': options.embedObject.getId(),
'id': '' + date,
'cssClass': options.cssClass,
'name': options.selectedModel.name,
'snapshot': options.snapshot
}]
});
}else {
model.entries[entries.length - 1] = {
'id': date,
'createdOn': date,
'text': options.entry,
'embeds': [{'type': options.embedObject.getId(),
'id': '' + date,
'cssClass': options.cssClass,
'name': options.selectedModel.name,
'snapshot': options.snapshot
}]
};
}
});
notification.info({
title: "Notebook Entry created"
});
}
};
NewEntryContextual.appliesTo = function (context) {
var domainObject = context.domainObject;
if (domainObject) {
if (domainObject.getModel().type === 'notebook') {
// do not allow in context of a notebook
return false;
} else if (domainObject.getModel().type.includes('imagery')) {
// do not allow in the context of an object with imagery
// (because of cross domain issue with snapshot)
return false;
}
}
return true;
};
return NewEntryContextual;
}
);

View File

@@ -1,130 +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(
['zepto'],
function ($) {
function SnapshotAction (exportImageService, dialogService, context) {
this.exportImageService = exportImageService;
this.dialogService = dialogService;
this.domainObject = context.domainObject;
}
SnapshotAction.prototype.perform = function () {
var elementToSnapshot =
$(document.body).find(".overlay .object-holder")[0] ||
$(document.body).find("[key='representation.selected.key']")[0];
$(elementToSnapshot).addClass("s-status-taking-snapshot");
this.exportImageService.exportPNGtoSRC(elementToSnapshot).then(function (blob) {
$(elementToSnapshot).removeClass("s-status-taking-snapshot");
if (blob) {
var reader = new window.FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function () {
this.saveSnapshot(reader.result, blob.type, blob.size);
}.bind(this);
}
}.bind(this));
};
SnapshotAction.prototype.saveSnapshot = function (imageURL, imageType, imageSize) {
var taskForm = this.generateTaskForm(),
domainObject = this.domainObject,
domainObjectId = domainObject.getId(),
cssClass = domainObject.getCapability('type').typeDef.cssClass,
name = domainObject.model.name;
this.dialogService.getDialogResponse(
'overlay-dialog',
taskForm,
function () {
return taskForm.value;
}
).then(function (options) {
var snapshotObject = {
src: imageURL,
type: imageType,
size: imageSize
};
options.notebook.useCapability('mutation', function (model) {
var date = Date.now();
model.entries.push({
id: 'entry-' + date,
createdOn: date,
text: options.entry,
embeds: [{
name: name,
cssClass: cssClass,
type: domainObjectId,
id: 'embed-' + date,
createdOn: date,
snapshot: snapshotObject
}]
});
});
});
};
SnapshotAction.prototype.generateTaskForm = function () {
var taskForm = {
name: "Create a Notebook Entry",
hint: "Please select a Notebook",
sections: [{
rows: [{
name: 'Entry',
key: 'entry',
control: 'textarea',
required: false,
"cssClass": "l-textarea-sm"
},
{
name: 'Save in Notebook',
key: 'notebook',
control: 'locator',
validate: validateLocation
}]
}]
};
var overlayModel = {
title: taskForm.name,
message: 'AHAHAH',
structure: taskForm,
value: {'entry': ""}
};
function validateLocation(newParentObj) {
return newParentObj.model.type === 'notebook';
}
return overlayModel;
};
return SnapshotAction;
}
);

View File

@@ -275,7 +275,6 @@ function (
function menuClickHandler(e) {
e.stopPropagation();
window.setTimeout(dismiss, 300);
}
// Dismiss any menu which was already showing

View File

@@ -105,23 +105,27 @@ function (
}
};
EntryController.prototype.dropOnEntry = function (entryId) {
var selectedObject = this.dndService.getData('mct-domain-object'),
selectedObjectId = selectedObject.getId(),
selectedModel = selectedObject.getModel(),
cssClass = selectedObject.getCapability('type').typeDef.cssClass,
entryPos = this.entryPosById(entryId),
currentEntryEmbeds = this.domainObject.entries[entryPos].embeds,
newEmbed = {
type: selectedObjectId,
id: '' + Date.now(),
cssClass: cssClass,
name: selectedModel.name,
snapshot: ''
};
EntryController.prototype.dropOnEntry = function (entryid, event) {
currentEntryEmbeds.push(newEmbed);
this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].embeds', currentEntryEmbeds);
var data = event.dataTransfer.getData('openmct/domain-object');
if (data) {
var selectedObject = JSON.parse(data),
selectedObjectId = selectedObject.identifier.key,
cssClass = this.openmct.types.get(selectedObject.type),
entryPos = this.entryPosById(entryid),
currentEntryEmbeds = this.domainObject.entries[entryPos].embeds,
newEmbed = {
type: selectedObjectId,
id: '' + Date.now(),
cssClass: cssClass,
name: selectedObject.name,
snapshot: ''
};
currentEntryEmbeds.push(newEmbed);
this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].embeds', currentEntryEmbeds);
}
};
EntryController.prototype.dragoverOnEntry = function () {
@@ -132,7 +136,6 @@ function (
return {
openmct: this.openmct,
domainObject: this.domainObject,
dndService: this.dndService,
dialogService: this.dialogService,
currentEntryValue: this.currentEntryValue
};

View File

@@ -1,65 +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.
*****************************************************************************/
/**
* Module defining NewEntryController. */
define(
[],
function () {
function NewEntryController($scope,$rootScope) {
$scope.snapshot = undefined;
$scope.snapToggle = true;
$scope.entryText = '';
var annotateAction = $rootScope.selObj.getCapability('action').getActions({key: 'annotate-snapshot'})[0];
$scope.$parent.$parent.ngModel[$scope.$parent.$parent.field] = $rootScope.selObj;
$scope.objectName = $rootScope.selObj.getModel().name;
$scope.cssClass = $rootScope.selObj.getCapability('type').typeDef.cssClass;
$scope.annotateSnapshot = function ($event) {
if ($rootScope.currentDialog.value) {
$rootScope.newEntryText = $scope.$parent.$parent.ngModel.entry;
$rootScope.currentDialog.cancel();
annotateAction.perform($event, $rootScope.snapshot.src);
$rootScope.currentDialog = undefined;
}
};
function updateSnapshot(img) {
$scope.snapshot = img;
}
// Update set of actions whenever the action capability
// changes or becomes available.
$rootScope.$watch("snapshot", updateSnapshot);
$rootScope.$watch("selValue", toggleEmbed);
function toggleEmbed(value) {
$scope.snapToggle = value;
}
}
return NewEntryController;
}
);

View File

@@ -27,7 +27,7 @@ define([
'../../res/templates/notebook.html',
'../../res/templates/entry.html',
'../../res/templates/embed.html',
'../../../../ui/components/controls/search.vue'
'../../../../ui/components/search.vue'
],
function (
Vue,
@@ -90,19 +90,23 @@ function (
return {
entrySearch: self.entrySearch,
showTime: '0',
sortEntries: '-createdOn',
sortEntries: self.domainObject.defaultSort,
entries: self.domainObject.entries,
currentEntryValue: ''
};
},
computed: {
filteredAndSortedEntries() {
return this.sort(this.filterBySearch(this.entries, this.entrySearch), this.sortEntries);
}
},
methods: {
search: function (event) {
if (event.target.value) {
this.entrySearch = event.target.value;
}
search(value) {
this.entrySearch = value;
},
newEntry: self.newEntry,
filterBySearch: self.filterBySearch
filterBySearch: self.filterBySearch,
sort: self.sort
}
});
@@ -111,25 +115,48 @@ function (
};
NotebookController.prototype.newEntry = function (event) {
this.NotebookVue.search('');
var date = Date.now(),
embed;
if (event.dataTransfer && event.dataTransfer.getData('openmct/domain-object')) {
var selectedObject = JSON.parse(event.dataTransfer.getData('openmct/domain-object')),
selectedObjectId = selectedObject.identifier.key,
cssClass = this.openmct.types.get(selectedObject.type);
embed = {
type: selectedObjectId,
id: '' + date,
cssClass: cssClass,
name: selectedObject.name,
snapshot: ''
};
}
var entries = this.domainObject.entries,
lastEntryIndex = entries.length - 1,
lastEntry = entries[lastEntryIndex],
date = Date.now();
lastEntryIndex = this.NotebookVue.sortEntries === 'newest' ? 0 : entries.length - 1,
lastEntry = entries[lastEntryIndex];
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds.length) {
var createdEntry = {'id': 'entry-' + date, 'createdOn': date, 'embeds':[]};
if (embed) {
createdEntry.embeds.push(embed);
}
entries.push(createdEntry);
this.openmct.objects.mutate(this.domainObject, 'entries', entries);
} else {
lastEntry.createdOn = date;
this.openmct.objects.mutate(this.domainObject, 'entries[entries.length-1]', lastEntry);
this.focusOnEntry.bind(this.NotebookVue.$children[lastEntryIndex])();
}
if(embed) {
lastEntry.embeds.push(embed);
}
this.entrySearch = '';
this.openmct.objects.mutate(this.domainObject, 'entries[entries.length-1]', lastEntry);
this.focusOnEntry.bind(this.NotebookVue.$children[lastEntryIndex+1])();
}
};
NotebookController.prototype.entryPosById = function (entryId) {
@@ -167,6 +194,33 @@ function (
}
};
NotebookController.prototype.sort = function (array, sortDirection) {
let oldest = (a,b) => {
if (a.createdOn < b.createdOn) {
return -1;
} else if (a.createdOn > b.createdOn) {
return 1;
} else {
return 0;
}
},
newest = (a,b) => {
if (a.createdOn < b.createdOn) {
return 1;
} else if (a.createdOn > b.createdOn) {
return -1;
} else {
return 0;
}
};
if (sortDirection === 'newest') {
return array.sort(newest);
} else {
return array.sort(oldest);
}
};
NotebookController.prototype.show = function (container) {
this.initializeVue(container);
};

View File

@@ -1,126 +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'], function ($) {
function EntryDnd($rootScope,$compile,dndService,typeService,notificationService) {
function link($scope, $element) {
function drop(e) {
var selectedObject = dndService.getData('mct-domain-object');
var selectedModel = selectedObject.getModel();
var cssClass = selectedObject.getCapability('type').typeDef.cssClass;
var entryId = -1;
var embedId = -1;
$scope.clearSearch();
if ($element[0].id === 'newEntry') {
entryId = $scope.domainObject.model.entries.length;
embedId = 0;
var lastEntry = $scope.domainObject.model.entries[entryId - 1];
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
$scope.domainObject.useCapability('mutation', function (model) {
model.entries.push({'createdOn': +Date.now(),
'id': +Date.now(),
'embeds': [{'type': selectedObject.getId(),
'id': '' + Date.now(),
'cssClass': cssClass,
'name': selectedModel.name,
'snapshot': ''
}]
});
});
}else {
$scope.domainObject.useCapability('mutation', function (model) {
model.entries[entryId - 1] =
{'createdOn': +Date.now(),
'embeds': [{'type': selectedObject.getId(),
'id': '' + Date.now(),
'cssClass': cssClass,
'name': selectedModel.name,
'snapshot': ''
}]
};
});
}
$scope.scrollToTop();
notificationService.info({
title: "Notebook Entry created"
});
}else {
entryId = $scope.findEntryPositionById(Number($element[0].id.replace('entry_', '')));
if (!$scope.domainObject.model.entries[entryId].embeds) {
$scope.domainObject.model.entries[entryId].embeds = [];
}
$scope.domainObject.useCapability('mutation', function (model) {
model.entries[entryId].embeds.push({'type': selectedObject.getId(),
'id': '' + Date.now(),
'cssClass': cssClass,
'name': selectedModel.name,
'snapshot': ''
});
});
embedId = $scope.domainObject.model.entries[entryId].embeds.length - 1;
if (selectedObject) {
e.preventDefault();
}
}
if ($(e.currentTarget).hasClass('drag-active')) {
$(e.currentTarget).removeClass('drag-active');
}
}
function dragover(e) {
if (!$(e.currentTarget).hasClass('drag-active')) {
$(e.currentTarget).addClass('drag-active');
}
}
// Listen for the drop itself
$element.on('dragover', dragover);
$element.on('drop', drop);
$scope.$on('$destroy', function () {
$element.off('dragover', dragover);
$element.off('drop', drop);
});
}
return {
restrict: 'A',
link: link
};
}
return EntryDnd;
});

View File

@@ -1,86 +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'], function ($) {
function MCTSnapshot($rootScope, $document, exportImageService, dialogService, notificationService) {
var document = $document[0];
function link($scope, $element, $attrs) {
var objectElement = $(document.body).find(".overlay .object-holder")[0] || $(document.body).find("[key='representation.selected.key']")[0],
takeSnapshot,
makeImg,
saveImg;
$(objectElement).addClass("s-status-taking-snapshot");
saveImg = function (url, entryId, embedId) {
$scope.$parent.$parent.$parent.saveSnap(url, embedId, entryId);
};
makeImg = function (el) {
var scope = $scope;
exportImageService.exportPNGtoSRC(el).then(function (img) {
$(objectElement).removeClass("s-status-taking-snapshot");
if (img) {
if ($element[0].dataset.entry && $element[0].dataset.embed) {
saveImg(img, +$element[0].dataset.entry, +$element[0].dataset.embed);
} else {
var reader = new window.FileReader();
reader.readAsDataURL(img);
reader.onloadend = function () {
$($element[0]).attr("data-snapshot", reader.result);
$rootScope.snapshot = {
'src': reader.result,
'type': img.type,
'size': img.size,
'modified': Date.now()
};
scope.$destroy();
};
}
}
});
};
takeSnapshot = function () {
makeImg(objectElement);
};
takeSnapshot();
$scope.$on('$destroy', function () {
$element.remove();
});
}
return {
restrict: 'A',
link: link
};
}
return MCTSnapshot;
});

View File

@@ -137,8 +137,8 @@ define(
* @returns {promise}
*/
ExportImageService.prototype.exportPNGtoSRC = function (element) {
return this.renderElement(element, "png");
ExportImageService.prototype.exportPNGtoSRC = function (element, className) {
return this.renderElement(element, "png", className);
};
/**

View File

@@ -37,8 +37,10 @@ define([
'./notebook/plugin',
'./displayLayout/plugin',
'./folderView/plugin',
'./flexibleLayout/plugin',
'./tabs/plugin',
'../../platform/features/fixed/plugin'
'../../platform/features/fixed/plugin',
'./preview/plugin'
], function (
_,
UTCTimeSystem,
@@ -56,8 +58,10 @@ define([
Notebook,
DisplayLayoutPlugin,
FolderView,
FlexibleLayout,
Tabs,
FixedView
FixedView,
PreviewPlugin
) {
var bundleMap = {
LocalStorage: 'platform/persistence/local',
@@ -171,6 +175,8 @@ define([
plugins.FolderView = FolderView;
plugins.Tabs = Tabs;
plugins.FixedView = FixedView;
plugins.FlexibleLayout = FlexibleLayout;
plugins.Preview = PreviewPlugin.default;
return plugins;
});

View File

@@ -0,0 +1,93 @@
/*****************************************************************************
* 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-preview-window">
<div class="l-preview-window__object-name l-browse-bar__object-name--w" :class="type.cssClass">
<span class="l-browse-bar__object-name">
{{ domainObject.name }}
</span>
<context-menu-drop-down :object-path="objectPath"></context-menu-drop-down>
</div>
<div class="l-preview-window__object-view">
<div ref="objectView">
</div>
</div>
</div>
</template>
<style lang="scss">
@import '~styles/sass-base';
.l-preview-window {
display: flex;
flex-direction: column;
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
> * + * {
margin-top: $interiorMargin;
}
&__object-name {
flex: 0 0 auto;
}
&__object-view {
flex: 1 1 auto;
> div:not([class]) {
// Target an immediate child div without a class and make it display: contents
display: contents;
}
}
}
</style>
<script>
import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue';
export default {
components: {
ContextMenuDropDown
},
inject: [
'openmct',
'objectPath'
],
data() {
let domainObject = this.objectPath[0];
let type = this.openmct.types.get(domainObject.type);
return {
domainObject: domainObject,
type: type
};
},
mounted() {
let viewProvider = this.openmct.objectViews.get(this.domainObject)[0];
this.view = viewProvider.view(this.domainObject);
this.view.show(this.$refs.objectView);
},
destroy() {
this.view.destroy();
}
}
</script>

View File

@@ -19,40 +19,46 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Preview from './Preview.vue';
import Vue from 'vue';
define(['zepto'], function ($) {
function HoverGesture(hoverManager) {
this.hoverManager = hoverManager;
export default class PreviewAction {
constructor(openmct) {
/**
* Metadata
*/
this.name = 'Preview';
this.description = 'Preview in large dialog';
this.cssClass = 'icon-eye-open';
/**
* Dependencies
*/
this._openmct = openmct;
}
invoke(objectPath) {
let preview = new Vue({
components: {
Preview
},
provide: {
openmct: this._openmct,
objectPath: objectPath
},
template: '<Preview></Preview>'
});
preview.$mount();
HoverGesture.prototype.apply = function (htmlElement) {
var $element = $(htmlElement);
var hoverManager = this.hoverManager;
function update() {
$(hoverManager.all()).removeClass('hovering');
$(hoverManager.top()).addClass('hovering');
}
function enter() {
hoverManager.add(htmlElement);
update();
}
function leave() {
hoverManager.remove(htmlElement);
update();
}
$element.on('mouseenter', enter);
$element.on('mouseleave', leave);
return function () {
leave();
$element.off('mouseenter', enter);
$element.off('mouseleave', leave);
}.bind(this);
};
return HoverGesture;
});
let overlay = this._openmct.overlays.overlay({
element: preview.$el,
size: 'large',
buttons: [
{
label: 'Done',
callback: () => overlay.dismiss()
}
],
onDestroy: () => preview.$destroy()
});
}
}

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -19,26 +19,10 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import PreviewAction from './PreviewAction.js';
/**
* Module defining SelectSnapshotController. */
define(
[],
function () {
function SelectSnapshotController($scope,$rootScope) {
$scope.selectModel = true;
function selectprint(value) {
$rootScope.selValue = value;
$scope.$parent.$parent.ngModel[$scope.$parent.$parent.field] = value;
}
$scope.$watch("selectModel", selectprint);
}
return SelectSnapshotController;
export default function () {
return function (openmct) {
openmct.contextMenu.registerAction(new PreviewAction(openmct));
}
);
}

View File

@@ -15,14 +15,14 @@
v-for="(tab,index) in tabsList"
:key="index"
:class="[
{'is-current': tab=== currentTab},
{'is-current': tab=== currentTab},
tab.type.definition.cssClass
]"
@click="showTab(tab)">
<span class="c-button__label">{{tab.model.name}}</span>
</button>
</div>
<div class="c-tabs-view__object-holder"
<div class="c-tabs-view__object-holder"
v-for="(object, index) in tabsList"
:key="index"
:class="{'invisible': object !== currentTab}">
@@ -66,6 +66,7 @@
&__object-name {
flex: 0 0 auto;
@include headerFont();
font-size: 1.2em !important;
margin: $interiorMargin 0 $interiorMarginLg 0;
}
@@ -85,7 +86,7 @@
</style>
<script>
import ObjectView from '../../../ui/components/layout/ObjectView.vue';
import ObjectView from '../../../ui/components/ObjectView.vue';
var unknownObjectType = {
definition: {
@@ -146,7 +147,7 @@ export default {
this.setCurrentTab = true;
},
dragstart (e) {
if (e.dataTransfer.getData('domainObject')) {
if (e.dataTransfer.getData('openmct/domain-object')) {
this.isDragging = true;
}
},

View File

@@ -46,6 +46,10 @@ define([
getConfiguration() {
let configuration = this.domainObject.configuration || {};
configuration.hiddenColumns = configuration.hiddenColumns || {};
configuration.columnWidths = configuration.columnWidths || {};
configuration.columnOrder = configuration.columnOrder || [];
configuration.autosize = configuration.autosize === undefined ? true : configuration.autosize;
return configuration;
}
@@ -113,7 +117,7 @@ define([
let headers = _.uniq(flattenedColumns, false, column => column.getKey())
.reduce(fromColumnsToHeadersMap, {});
function fromColumnsToHeadersMap(headersMap, column){
function fromColumnsToHeadersMap(headersMap, column) {
headersMap[column.getKey()] = column.getTitle();
return headersMap;
}
@@ -122,16 +126,42 @@ define([
}
getVisibleHeaders() {
let headers = this.getAllHeaders();
let allHeaders = this.getAllHeaders();
let configuration = this.getConfiguration();
Object.keys(headers).forEach((headerKey) => {
if (configuration.hiddenColumns[headerKey] === true) {
delete headers[headerKey];
}
});
let orderedColumns = this.getColumnOrder();
let unorderedColumns = _.difference(Object.keys(allHeaders), orderedColumns);
return headers;
return orderedColumns.concat(unorderedColumns)
.filter((headerKey) => {
return configuration.hiddenColumns[headerKey] !== true;
})
.reduce((headers, headerKey) => {
headers[headerKey] = allHeaders[headerKey];
return headers;
}, {});
}
getColumnWidths() {
let configuration = this.getConfiguration();
return configuration.columnWidths;
}
setColumnWidths(columnWidths) {
let configuration = this.getConfiguration();
configuration.columnWidths = columnWidths;
this.updateConfiguration(configuration);
}
getColumnOrder() {
let configuration = this.getConfiguration();
return configuration.columnOrder;
}
setColumnOrder(columnOrder) {
let configuration = this.getConfiguration();
configuration.columnOrder = columnOrder;
this.updateConfiguration(configuration);
}
destroy() {
@@ -140,4 +170,4 @@ define([
}
return TelemetryTableConfiguration;
});
});

View File

@@ -29,7 +29,8 @@ define(function () {
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
columns: {}
columnWidths: {},
hiddenColumns: {}
};
}
};

View File

@@ -0,0 +1,182 @@
/*****************************************************************************
* 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>
<th
:style="{ width: columnWidth + 'px', 'max-width': columnWidth + 'px'}"
:draggable="isEditing"
@mouseup="sort"
v-on="isEditing ? {
dragstart: columnMoveStart,
drop: columnMoveEnd,
dragleave: hideDropTarget,
dragover: dragOverColumn
} : {}">
<div class="c-telemetry-table__headers__content" :class="[
isSortable ? 'is-sortable' : '',
isSortable && sortOptions.key === headerKey ? 'is-sorting' : '',
isSortable && sortOptions.direction].join(' ')">
<div v-if="isEditing" class="c-telemetry-table__resize-hotzone c-telemetry-table__resize-hotzone--right"
@mousedown="resizeColumnStart"
></div>
<slot></slot>
</div>
</th>
</template>
<style lang="scss">
@import "~styles/sass-base";
@import "~styles/table";
$hotzone-size: 6px;
.c-telemetry-table__headers__content {
width: 100%;
}
.c-table.c-telemetry-table {
.c-telemetry-table__resize-hotzone {
display: block;
position: absolute;
height: 100%;
padding: 0;
margin: 0;
width: $hotzone-size;
min-width: $hotzone-size;
cursor: col-resize;
border: none;
right: 0px;
margin-right: -$tabularTdPadLR - 1 - $hotzone-size / 2;
}
}
</style>
<script>
import _ from 'lodash';
const MOVE_COLUMN_DT_TYPE = 'movecolumnfromindex';
export default {
inject: ['openmct'],
data() {
return {
isEditing: this.openmct.editor.isEditing()
}
},
props: {
headerKey: String,
headerIndex: Number,
isHeaderTitle: Boolean,
sortOptions: Object,
columnWidth: Number,
hotzone: Boolean
},
computed: {
isSortable() {
return this.sortOptions !== undefined;
}
},
methods: {
resizeColumnStart(event) {
this.resizeStartX = event.clientX;
this.resizeStartWidth = this.columnWidth;
document.addEventListener('mouseup', this.resizeColumnEnd, {once: true, capture: true});
document.addEventListener('mousemove', this.resizeColumn);
event.preventDefault();
},
resizeColumnEnd(event) {
this.resizeStartX = undefined;
this.resizeStartWidth = undefined;
document.removeEventListener('mousemove', this.resizeColumn);
event.preventDefault();
event.stopPropagation();
this.$emit('resizeColumnEnd');
},
resizeColumn(event) {
let delta = event.clientX - this.resizeStartX;
let newWidth = this.resizeStartWidth + delta;
let minWidth = parseInt(window.getComputedStyle(this.$el).minWidth);
if (newWidth > minWidth) {
this.$emit('resizeColumn', this.headerKey, newWidth);
}
},
columnMoveStart(event) {
event.dataTransfer.setData(MOVE_COLUMN_DT_TYPE, this.headerIndex);
},
isColumnMoveEvent(event) {
return [...event.dataTransfer.types].includes(MOVE_COLUMN_DT_TYPE);
},
dragOverColumn(event) {
if (this.isColumnMoveEvent(event)){
event.preventDefault();
this.updateDropOffset(event.currentTarget, event.clientX);
} else {
return false;
}
},
updateDropOffset(element, clientX) {
let thClientLeft = element.getBoundingClientRect().x;
let offsetInHeader = clientX - thClientLeft;
let dropOffsetLeft;
if (offsetInHeader < element.offsetWidth / 2) {
dropOffsetLeft = element.offsetLeft;
} else {
dropOffsetLeft = element.offsetLeft + element.offsetWidth;
}
this.$emit('dropTargetOffsetChanged', dropOffsetLeft);
this.$emit('dropTargetActive', true);
},
hideDropTarget(){
this.$emit('dropTargetActive', false);
},
columnMoveEnd(event){
if (this.isColumnMoveEvent(event)){
let toIndex = this.headerIndex;
let fromIndex = event.dataTransfer.getData(MOVE_COLUMN_DT_TYPE);
if (event.offsetX < event.target.offsetWidth / 2) {
if (toIndex > fromIndex){
toIndex--;
}
} else {
if (toIndex < fromIndex){
toIndex++;
}
}
if (toIndex !== fromIndex) {
this.$emit('reorderColumn', fromIndex, toIndex);
}
}
},
sort() {
this.$emit("sort");
},
toggleEditMode(isEditing) {
this.isEditing = isEditing;
}
},
created() {
this.openmct.editor.on('isEditing', this.toggleEditMode);
},
destroyed() {
this.openmct.editor.off('isEditing', this.toggleEditMode);
}
}
</script>

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