Compare commits

...

48 Commits

Author SHA1 Message Date
Pegah Sarram
50e994f982 Store array of items in configuration. 2018-12-06 15:54:03 -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
Deep Tailor
35d1b894e2 Tab view (#2197)
* first cut, working for objects with explicit height

* working for all objects by setting min-height explicitly to 70vh

* add composition listeners

* Tabs view WIP

- Markup and CSS BEMized;
- Stubbed in type icon in tab elements;
- TODO: refine styling on tabs;

* Tabs view WIP

- Layout enhancements;

* add types and header

* remove static icon-layout class

* move Tabs into its own plugin folder:

* simplify on composition add listener callback

* fix icon rendering

* add document dragstart and dragend listeners, with v-if div for drop target

* remove third argument from document listeners, move drop target to tab container

* Sanding and shimming on Tabs View

- WIP

* Tabs View styling

- Shippable to Deep;
- Added new c-drop-hint element in _controls.scss;
- Added new theme constants;
- Added 'empty' tabs view message;
- TODOs: add listener for dragover event for is-mouse-over styling,
integrate forthcoming changes to symbolsfont;

* add is-mouse-over class when drag enters dropHint div, add and remove listeners

* .c-drop-hint styling refined

- Refined hover effects;
- Bg icon instead of glyph character;
- TODOs: Change $bg-icon to plus sign;

* fix bug regarding persisting drop-hint styling even after drop is ended
2018-10-26 14:14:00 -07:00
Pete Richards
7c54ec4f9f Tree listens for composition 2018-10-23 11:11:54 -07:00
Andrew Henry
cbcfd44016 Elements pool and drag drop (#2196)
* Implemented drag-and-drop composition

* Added composition policy for tables

* Reimplemented elements pool in Vue

* No need to resolve all objects on the navigated path

* Only show elements pool in edit mode

* Remove old elements pool

* Updated legacy code to use composition policy API

* Keep object in sync when mutated
2018-10-23 10:52:37 -07:00
Pegah Sarram
a296bc2b81 Fix regression in layout frame edit handle by using a modifier convention. 2018-10-22 10:14:22 -07:00
Pete Richards
06b9e0fa97 Create menu (#2195)
* Wire up create with old create action

* refactor grid and list view to listen on composition, use lodash for sorts, remove item count

* remove item count on grid view

item count needs to be supported by composition API and not by
inspecting model, otherwise it will be inconsistent.

* close menu after create or clickaway
2018-10-19 15:07:30 -07:00
Pegah Sarram
4374a6fa28 Toolbar in Fixed Position (#2194)
* Initial attempt at getting the toolbar for the fixed position working with the Vue toolbar controls.

* Set title for controls

* Significant mods to support legacy Fixed Position

- Moved selection and editing styles into _global.scss;
- Changed class naming in legacy fixed.html to map to newer CSS styles;

* Pass in the color value

* Do not show the toolbar container if structure is empty.

* Use a plugin for fixed position to get access to openmct. Show the toolbar only if the object is being edited.

* Ensure fixedController is on the selection context when editing

* Add listener for a domain object with the same id only once.
Update the toolbar value after the object mutation.
Remove editor isEditing listener on destroyed.

* Remove space between the size and px.
If newObject exists, update the toolbar value.

* Remove --nwse class name which seems to be a typo

* use modifier convention
2018-10-19 11:42:30 -07:00
Deep Tailor
67883519ee Overlay Service - rewritten/redesigned in Vue (#2190)
* modify overlay service to accept bottomBarButtons option, working annotate on snapshot view

* working blocking message

* move blocking message to overlay service

* return dismiss function on show

* added jsdocs for overlayService

* added progress bar, with setter and getter functions

* make reviewer requested changes

* re-overhaul of overlayAPI

* Integrate work in dialog-service-vue-style

- Markup in DialogComponent and OverlayComponent now up to date;
- Colors, constants;
- Notebook entry now passes correct buttons config;
- New bg data URIs added;
- u-icon-bg-* classes added to global.scss;

* Added deprecation comments to .vue files

* Removed styles from deprecated file

* Temp restore of CSS so that dialog doesn't break.

* remove old OverlayService

* Fixed to overlay CSS

- Fixed large, small, fit sizes;
- Added margin to overlay buttons;
- Code cleanup;

* Remove unused constants

* Tweak styles for __close-button

* Code cleanup

* small cleanup

* wip progressDialog

* Progress bar fixed and Vue-ized

- Markup, styles;
- Constants moved from theme files into _constants.scss;

* added jsdocs to new Overlay API

* remove unused example function

* use progressBar.vue in NotificationBanner.vue, wire up the maximize method with progressDialog and Dialog from OverlayAPI

* Styling in progress for status message banners

* openmct members are camelCase

* Remove unimplemented/unused apis
2018-10-19 11:15:57 -07:00
Andrew Henry
6f1b5b4ae3 Notifications progress method (#2193)
* Added progress method to notifications so no longer dependent on reactive properties

* Updated notification launch controller to use new progress method

* Added progress function to Notifications API. Introduced NotificationService compatibility layer for legacy code
2018-10-15 10:00:05 -07:00
Andrew Henry
c3b7e7869e Fix code to remove warnings in conductor (#2192) 2018-10-11 11:33:59 -07:00
Pegah Sarram
d48cc2deee Vue toolbar (#2191)
* Add a toolbar provider for display layout.

* Move toolbar provider to the plugin

* basic toolbar generation

* componentize different toolbar control types

Break toolbar control types down into different parts and provide
a test toolbar generator in index.html that utilizes all the
controls.

* Get the 'Show frame' checkbox working in the toolbar

* - Remove extra listener.
- Display toolbar only when editing.

* Modify the Selection API to set s-selected and s-selected-parent attributes instead of adding to the css class names.

* Move the logic for allowing the toolbar in the edit mode to the provider.

* Use toggle-button component to toggle frame

* Delete old files

* Remove MCTToolbar

* Modify the toggle button component to return the computed value

* Remove reload=true

* Revert to the original setting

* use value from props

* Always update toolbars on edit status change

* restore fixed position bundle

* bring back reload when hmr unavailable
2018-10-11 11:33:33 -07:00
Andrew Henry
64b9d4c24a Vue status bar (#2188)
* Implemented indicators

* WIP

* Fixed templates from notifications example

* Message bar implemented

* Implemented notifications

* Fixed bug with destruction of notifications

* Renamed MessageBanner to NotificationBanner

* Add save success message

* Removed NotificationServiceSpec

* Removed legacy constants from bundle
2018-10-10 17:35:11 -07:00
Charles Hacskaylo
88bcb6078e Conductor fixes (#2189)
* Conductor fixes

- Restore RT update time field;
- Colors tweaked;
- Much better mobile layout;

* Significant fixes in Conductor markup and styling

- Markup/CSS simplified and clearer;
- Better coloring in both Themes;
- Better clarity for axis UI element;
- Fixed hover and focus styles on inputs;
2018-10-10 17:03:52 -07:00
Charles Hacskaylo
5f9f3cd8e8 Topic themes (#2187)
* Bringing over in-progress changes from topic-core-css

- Adds _espresso-constants.scss;
- Cleanup colors and naming;
- Remove conflict res leftover 'domainObject' in mct-tree.vue;
- Still WIP!

* Various

- Remove pushBack / pullForward functions;
- Fix c-input-inline, remove bg until hover;
- TODO: input bg colors
- Increased margin in main-pane;

* Themeing WIP

- Conductor markup: convert to buttons for accessibility;
- Conductor styles consolidated and changed for better theme support;

* Themeing WIP; significant rewrite of pane headers

- Pane headers restructured for better semantics and clarity;
- Espresso design refined and tightened;
- Grid Vue changes for better themeing support;
- TODO: fix mobile version, collapse icon is whack;

* Restored Number-type input styling for correct positioning of spinner
button;

* Themeing mods for click-icon styles

* Bring Snow theme into style parity with Espresso

- TODO: refine Snow colors;

* Mobile styling fixed

- Mobile menu icon significant fixes;
- Hover only applied to desktop;
- Reorg of mixins;

* Bring Snow theme constants into parity with Espresso

- Refined Snow styles;
- Fixed missing scroll and padding in tree;
- Pane collapse button now uses proper color;
- Item Grid view refinement;
- Cleaned up code;

* Color fixes

- Super-menu description;
- Conductor time buttons hover;
- Datepicker "in-month" items color;
- Espresso colorKeyFilter brightened;
2018-10-10 12:45:46 -07:00
charlesh88
814b404614 Fixes for Inspector Plot properties, and more
- Fixed overflow problem when Inspector collapsed;
- Temp legacy styling for Plot inspection, series options, etc.
- Factored out non-useful gridTwoColumn mixin;
- Brought back legacy tree.scss file in legacy-styles.scss;
2018-10-05 15:13:38 -07:00
charlesh88
ba2bb2180b Merge branch 'topic-core-refactor' of https://github.com/nasa/openmct into topic-core-refactor 2018-10-05 12:15:30 -07:00
charlesh88
72cdb352f0 Fix s-selected, edit grid displays 2018-10-05 12:15:20 -07:00
Pete Richards
cedf942c0c apply is-editing only when editing 2018-10-05 11:58:22 -07:00
Pegah Sarram
27506a3757 Remove selectable from display layut 2018-10-05 11:39:41 -07:00
Pete Richards
acc4e03c88 Merge duplicate data method, correct is-editing class 2018-10-05 11:25:30 -07:00
Pete Richards
9a6090cd02 Legacy inspector view support 2018-10-05 10:41:06 -07:00
Deep Tailor
f40c9fa6f9 Overlay Service re-written in Vue (#2186)
* working overlayService

* wire to snapshot, and add onDestroy Callback

* increment overlayId

* New branch from topic-core-refactor to use as central point for common
CSS work

- Manually migrated changes from vue-toolbar, expect conflicts there and
 in vue-layout;

* Manually update constants-snow from vue-toolbar branch

* Update markup to use latest button classnames

- c-menu-button > c-button--menu;
- c-icon-button > c-click-icon;

* Various from vue-conductor-style

- Mods to input styling;
- Input[] styles moved to _controls;
- New/revised constants vals;

* Resolve bizarre merge conflict when applying stash

* Code cleanup

* remove duplicate div

* Alias and type-icon fixes

- More robust approach to alias indicators;
- Added alias indication to tree-item.vue;
- TODO: wire up alias indication tree-item.vue;

* Accessibility mods, convert elements to <button>

- Better reset styles for htmlInputReset mixin to allow use of <button>
without browser default styling;
- Create button;
- BrowseBar action buttons;
- c-click-icons;
- Removed Preview button from BrowseBar.vue;

* Overlay styling WIP

- Base markup pretty solid;
- Stubbed in temp values for overlayTypeCssClass in overlayService.js;
- TODO: styling for contents area;

* add options object, scope variables to show function, add destroy callback to active Overlay Object (for easier retrieval
2018-10-04 15:59:58 -07:00
Pegah Sarram
e7cdb334de Reimplementation of Display Layout in Vue (#2185)
* Saving work

* Fix conflict

* Position the panels by setting the style

* Put the div back with height set to 100% in ObjectView.
Add markup for  drag handles.

* Use default position and dimensions if the layout panel is missing those values. Set s-status-editing on the main div to get the drag handles appear for now.

* Display Layout and frames major improvements

- Moved Toolbar out of Layout.vue and into DisplayLayout.vue;
- Styles for object view, Layout, Frame, etc.
- Major refactor of markup for frame;
- Added abs() mixin;
- Styles for is-editing done;
- Styles for
- TODO: styles for selectable, moveable, etc.

* Implement drill in gesture.

* Hide the background grid when a frame is drilled in

* Edit styling and toolbar WIP

- c-search styles moved mostly into mixin;
- New .c-labeled-input class;
- Browser overrides for number-type input spinners in webkit;

* Toolbar WIP

- Custom wrapped number input added;
- Toolbar buttons WIP;

* New toolbar buttons WIP

* Define a computed property for the css class object.

* Frame edit handles markup and styling

* Toolbar, editing and selection style refinements

- Moved toolbar back into Layout.vue;
- Hard-coded 'is-editing' onto __pane-main for now,
removed from DisplayLayout.vue;
- Styles for frame in LayoutFrame.vue:
-- editing default (dotted border)
-- editing .s-selected
-- .s-drilled-in (renamed .is-drilled-in)

* Refactoring button classes

- Lots of stuff broken right now;
- TODO: lots of renaming (c-menu-button, c-icon-button, etc.);
- Removed import of _controls in search.vue;

* Fixes for selection on nested selected elements

* Fix conflict

* Significant refactoring of button and click-icon classes

- Markup and CSS updated;
- Toolbar in good shape, prior to merge of vue-layout;

* Fix issues with relative font-sizing

* Add color palette markup and CSS

- Also added Layers menu example;

* Font, font-size glyphs and size menu, and more

- Added art for font glyph and renamed from .icon-T;
- Added new glyph for font-size;
- Fixed font-sizing in controls;
- Added font-size menu;
- Re-org'd toolbar items;

* Styling tweak for c-labeled-input

- Code cleanup as well;

* Fixes for toolbar toggleMenus and labeledNumberInput

* Implement resize and move for frames.
Added stub for drag 'n drop.

* Add custom checkbox control.

- Also, code cleanup.

* Add toggleButton component

- Code and examples

* Custom checkbox code cleanups, sanding

* - Persist new position/dimensions on the domain object when frames are moved/resized.
- Bypass the selection of the layout when dragging a frame is finished to keep the frame selected.
- Set the grid size to layoutGrid from domain object or use default if it's not specified.

* Fix conflict

* Implement resize and move for frames.
Added stub for drag 'n drop.

* Remove old layout view, was triggering View Switcher
and massive confusion when viewing Layouts

- TODO: add view provider for new Layout

* Enable drag and drop

* Changed example toggle-button

- Now uses show/hide frame as toggle-button example;

* Added pseudocode for handling drag/drop composition change

* Add copyright notice

* Layout frame and contained components styling

- Hyperlinks, Hyperlink buttons, Summary Widgets now use `.u-links`
which disables their pointer-events when editing;
- Hyperlink buttons, Summary Widgets now expand to fill their
containers in a Layout;
- Markup and JS for Hyperlinks, Hyperlink buttons, Summary Widgets
somewhat
modded to use new classing, applied in legacy scss files;

* Fix icon sizing error in BrowseBar

* Edit and selecting styling for Layouts

- WIP!

* Edit and selecting styling for Layout frames

- Color vars more standardized;
- Hover and *-selected styles;

* Getting vue-toolbar reverted back to latest

- Merging this branch with vue-layout may cause conflicts;

* - Implement drag ’n drop.
- Set hasFrame to a default value if it’s not set on the configuration.
- Emit an event to the parent wrapper component to update the ‘newDomainObject’ prop whenever the domain object is mutated in the display layout child component.

* Revert emitting 'update:object' event to the parent.

* New branch from topic-core-refactor to use as central point for common
CSS work

- Manually migrated changes from vue-toolbar, expect conflicts there and
 in vue-layout;

* Manually update constants-snow from vue-toolbar branch

* Update markup to use latest button classnames

- c-menu-button > c-button--menu;
- c-icon-button > c-click-icon;

* Various from vue-conductor-style

- Mods to input styling;
- Input[] styles moved to _controls;
- New/revised constants vals;

* Resolve bizarre merge conflict when applying stash

* Code cleanup

* Alias and type-icon fixes

- More robust approach to alias indicators;
- Added alias indication to tree-item.vue;
- TODO: wire up alias indication tree-item.vue;

* Accessibility mods, convert elements to <button>

- Better reset styles for htmlInputReset mixin to allow use of <button>
without browser default styling;
- Create button;
- BrowseBar action buttons;
- c-click-icons;
- Removed Preview button from BrowseBar.vue;

* Fix styles that were affected during resolving conflicts

* Moved draggable into __label element rather than whole <li>

* Change the priority to 100 to get the view working properly

* Code cleanup

* Remove angular layout

* Display the object name in the frame header

* Tweaks to __header in LayoutFrame

- Name now does not overflow frame edge;
- Layout strategy now in parity with similar elements in main view;

* Remove test()

* Add a type for display layout to make it appear in the Create menu.

* Change the key type to 'layout'

* Clean up code and hide toolbar

* Enable toolbar, and revert changes in webpack config

* Remove commented code

* revert to the original code
2018-10-04 15:59:23 -07:00
Charles Hacskaylo
afca6cd2e9 Review and merge latest topic-core-css (#2183)
* New branch from topic-core-refactor to use as central point for common
CSS work

- Manually migrated changes from vue-toolbar, expect conflicts there and
 in vue-layout;

* Manually update constants-snow from vue-toolbar branch

* Update markup to use latest button classnames

- c-menu-button > c-button--menu;
- c-icon-button > c-click-icon;

* Various from vue-conductor-style

- Mods to input styling;
- Input[] styles moved to _controls;
- New/revised constants vals;

* Resolve bizarre merge conflict when applying stash

* Code cleanup

* Alias and type-icon fixes

- More robust approach to alias indicators;
- Added alias indication to tree-item.vue;
- TODO: wire up alias indication tree-item.vue;

* Accessibility mods, convert elements to <button>

- Better reset styles for htmlInputReset mixin to allow use of <button>
without browser default styling;
- Create button;
- BrowseBar action buttons;
- c-click-icons;
- Removed Preview button from BrowseBar.vue;

* Add copyright to .scss files; code cleanup

* Improved click area and draggable styling for tree-item

* Removed injection of domainObject

* Removed injection of domainObject

* Remove _constants-mobile.scss

- Moved used var to _constants;
- Normalize mobile detection approach in Layout.vue;

* Mobile styling for Time Conductor

- WIP!

* Mobile styling for Time Conductor

- New modalFullScreen mixin;
- Datepicker now a fullscreen modal element in body.phone;
2018-10-04 14:47:12 -07:00
Andrew Henry
3c324cbea0 Initial implementation of Edit mode (#2181)
* Adding edit mode API and buttons

* Select navigated object by default. Enable table configuration in edit mode.

* Fixed issue with Table configuration

* Removed debugging code

* Added basic documentation to Editor API

* use selectable, table is selection[0], inspector cleans up

Update browse to set selection using openmct.selection.selectable
on navation so that selection contexts can be determined correctly.

Update table configuration to reference the first item in selection
instead of the last item in selection when displaying.

Update inspector code to remove display node from document on destroy.

* properly remove capturing handler

* inspector views respond to editing

InspectorViews respond to editing instead of openmct rerendering
the inspector on edit.
2018-10-04 12:35:03 -07:00
234 changed files with 12577 additions and 8784 deletions

2
app.js
View File

@@ -46,7 +46,7 @@ webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
webpackConfig.plugins.push(function() { this.plugin('watch-run', function(watching, callback) { console.log('Begin compile at ' + new Date()); callback(); }) });
webpackConfig.entry.openmct = [
'webpack-hot-middleware/client',
'webpack-hot-middleware/client?reload=true',
webpackConfig.entry.openmct
];

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

@@ -26,12 +26,16 @@ define([
"./src/NotificationLaunchController",
"./src/DialogLaunchIndicator",
"./src/NotificationLaunchIndicator",
"./res/dialog-launch.html",
"./res/notification-launch.html",
'legacyRegistry'
], function (
DialogLaunchController,
NotificationLaunchController,
DialogLaunchIndicator,
NotificationLaunchIndicator,
DialogLaunch,
NotificationLaunch,
legacyRegistry
) {
"use strict";
@@ -41,11 +45,11 @@ define([
"templates": [
{
"key": "dialogLaunchTemplate",
"templateUrl": "dialog-launch.html"
"template": DialogLaunch
},
{
"key": "notificationLaunchTemplate",
"templateUrl": "notification-launch.html"
"template": NotificationLaunch
}
],
"controllers": [

View File

@@ -51,76 +51,26 @@ define(
return actionTexts[Math.floor(Math.random()*3)];
}
function getExampleActions() {
var actions = [
{
label: "Try Again",
callback: function () {
$log.debug("Try Again pressed");
}
},
{
label: "Remove",
callback: function () {
$log.debug("Remove pressed");
}
},
{
label: "Cancel",
callback: function () {
$log.debug("Cancel pressed");
}
}
];
// Randomly remove some actions off the top; leave at least one
actions.splice(0,Math.floor(Math.random() * actions.length));
return actions;
}
function getExampleSeverity() {
var severities = [
"info",
"alert",
"error"
];
return severities[Math.floor(Math.random() * severities.length)];
}
/**
* Launch a new notification with a severity level of 'Error'.
*/
$scope.newError = function(){
$scope.newError = function () {
notificationService.notify({
title: "Example error notification " + messageCounter++,
hint: "An error has occurred",
severity: "error",
primaryOption: {
label: 'Retry',
callback: function() {
$log.info('Retry clicked');
}
},
options: getExampleActions()});
severity: "error"
});
};
/**
* Launch a new notification with a severity of 'Alert'.
*/
$scope.newAlert = function(){
$scope.newAlert = function () {
notificationService.notify({
title: "Alert notification " + (messageCounter++),
hint: "This is an alert message",
severity: "alert",
primaryOption: {
label: 'Retry',
callback: function() {
$log.info('Retry clicked');
}
},
options: getExampleActions()});
autoDismiss: true
});
};
@@ -128,39 +78,42 @@ define(
* Launch a new notification with a progress bar that is updated
* periodically, tracking an ongoing process.
*/
$scope.newProgress = function(){
$scope.newProgress = function () {
let progress = 0;
var notificationModel = {
title: "Progress notification example",
severity: "info",
progress: 0,
actionText: getExampleActionText(),
unknownProgress: false
progress: progress,
actionText: getExampleActionText()
};
let notification;
/**
* Simulate an ongoing process and update the progress bar.
* @param notification
*/
function incrementProgress(notificationModel) {
notificationModel.progress = Math.min(100, Math.floor(notificationModel.progress + Math.random() * 30));
notificationModel.progressText = ["Estimated time" +
function incrementProgress() {
progress = Math.min(100, Math.floor(progress + Math.random() * 30))
let progressText = ["Estimated time" +
" remaining:" +
" about ", 60 - Math.floor((notificationModel.progress / 100) * 60), " seconds"].join(" ");
if (notificationModel.progress < 100) {
$timeout(function(){incrementProgress(notificationModel);}, 1000);
" about ", 60 - Math.floor((progress / 100) * 60), " seconds"].join(" ");
notification.progress(progress, progressText);
if (progress < 100) {
$timeout(function () {
incrementProgress(notificationModel);
}, 1000);
}
}
notificationService.notify(notificationModel);
incrementProgress(notificationModel);
notification = notificationService.notify(notificationModel);
incrementProgress();
};
/**
* Launch a new notification with severity level of INFO.
*/
$scope.newInfo = function(){
$scope.newInfo = function () {
notificationService.info({
title: "Example Info notification " + messageCounter++
});

View File

@@ -49,6 +49,7 @@
openmct.install(openmct.plugins.ExampleImagery());
openmct.install(openmct.plugins.UTCTimeSystem());
openmct.install(openmct.plugins.ImportExport());
openmct.install(openmct.plugins.FixedView());
openmct.install(openmct.plugins.AutoflowView({
type: "telemetry.panel"
}));
@@ -76,8 +77,208 @@
openmct.install(openmct.plugins.SummaryWidget());
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();
// openmct.toolbars.addProvider({
// name: "Testing Toolbar",
// key: "testing",
// description: "a mock toolbar that exercises all controls",
// forSelection: function (selection) {
// return true; // always applies.
// },
// toolbar: function (selection) {
// return [
// {
// control: 'menu',
// icon: 'icon-plus',
// label: 'Add',
// options: [
// { name: 'Box', class: 'icon-box', title: 'Add Box' },
// { name: 'Line', class: 'icon-line-horz', title: 'Add Line' },
// { name: 'Text', class: 'icon-font', title: 'Add Text' },
// { name: 'Image', class: 'icon-image', title: 'Add Image' }
// ]
// },
// {
// control: 'separator'
// },
// {
// control: 'color-picker',
// icon: 'icon-paint-bucket',
// value: '#33ff00',
// },
// {
// control: 'color-picker',
// icon: 'icon-pencil',
// value: '#ffffff',
// },
// {
// control: 'color-picker',
// icon: 'icon-font',
// value: '#333333',
// },
//
// {
// control: 'separator'
// },
// {
// control: 'select-menu',
// value: 11,
// options: [
// { value: 9, name: '9 px' },
// { value: 10, name: '10 px' },
// { value: 11, name: '11 px' },
// { value: 12, name: '12 px' },
// { value: 13, name: '13 px' },
// { value: 14, name: '14 px' },
// { value: 16, name: '16 px' },
// { value: 18, name: '18 px' },
// { value: 20, name: '20 px' },
// { value: 24, name: '24 px' },
// { value: 28, name: '28 px' },
// { value: 32, name: '32 px' },
// { value: 40, name: '40 px' },
// { value: 48, name: '48 px' },
// { value: 56, name: '56 px' },
// { value: 64, name: '64 px' },
// { value: 72, name: '72 px' },
// { value: 80, name: '80 px' },
// { value: 88, name: '88 px' },
// { value: 96, name: '96 px' },
// { value: 128, name: '128 px' },
// { value: 160, name: '160 px' }
// ]
// },
//
// {
// control: 'separator'
// },
// {
// control: 'menu',
// icon: 'icon-layers',
// options: [
// { name: 'Move to top', class: 'icon-arrow-double-up', title: 'Move to top' },
// { name: 'Move up', class: 'icon-arrow-up', title: 'Move up' },
// { name: 'Move down', class: 'icon-arrow-down', title: 'Move down' },
// { name: 'Move to bottom', class: 'icon-arrow-double-down', title: 'Move to bottom' }
// ]
// },
//
// {
// control: 'separator'
// },
// {
// control: 'button',
// icon: 'icon-gear'
// },
//
// {
// control: 'separator'
// },
// {
// control: 'input',
// type: 'number',
// label: 'X',
// value: 1,
// title: 'X position'
// },
// {
// control: 'input',
// type: 'number',
// label: 'Y',
// value: 2,
// title: 'Y position'
// },
// {
// control: 'input',
// type: 'number',
// label: 'W',
// value: 3,
// title: 'Width'
// },
// {
// control: 'input',
// type: 'number',
// label: 'H',
// value: 4,
// title: 'Height'
// },
//
// {
// control: 'separator'
// },
// {
// control: 'button',
// icon: 'icon-trash',
// label: 'delete',
// modifier: 'caution'
// },
//
// {
// control: 'separator'
// },
// {
// control: 'checkbox',
// name: 'this is a checkbox',
// },
// {
// control: 'separator'
// },
// {
// control: 'toggle-button',
// title: 'Toggle Frame',
// property: 'hideFrame',
// value: false,
// options: [
// {
// value: true,
// icon: 'icon-frame-hide'
// },
// {
// value: false,
// icon: 'icon-frame-show'
// }
// ]
// },
// {
// control: 'toggle-button',
// title: 'Snap to grid',
// property: 'snapToGrid',
// value: true,
// options: [
// {
// value: true,
// icon: 'icon-grid-snap-to'
// },
// {
// value: false,
// icon: 'icon-grid-snap-no'
// }
// ]
// },
// {
// control: 'toggle-button',
// title: 'Toggle label',
// property: 'showLabel',
// value: true,
// options: [
// {
// value: true,
// icon: 'icon-two-parts-both'
// },
// {
// value: false,
// icon: 'icon-two-parts-one-only'
// }
// ]
// }
// ];
// }
// });
</script>
</html>

View File

@@ -58,7 +58,7 @@ define([], function () {
function checkNavigation() {
var navigatedObject = navigationService.getNavigation();
if (navigatedObject.hasCapability('context')) {
if (navigatedObject && navigatedObject.hasCapability('context')) {
if (!navigatedObject.getCapability('editor').isEditContextRoot()) {
preventOrphanNavigation(navigatedObject);
}

View File

@@ -2,31 +2,14 @@
ng-class="'message-severity-' + ngModel.severity">
<div class="w-message-contents">
<div class="top-bar">
<div class="title">{{ngModel.title}}</div>
</div>
<div class="hint" ng-hide="ngModel.hint === undefined">
{{ngModel.hint}}
<span ng-if="ngModel.timestamp !== undefined">[{{ngModel.timestamp}}]</span>
<div class="title">{{ngModel.message}}</div>
</div>
<div class="message-body">
<div class="message-action">
{{ngModel.actionText}}
</div>
<mct-include key="'progress-bar'"
ng-model="ngModel"
ng-show="ngModel.progress !== undefined || ngModel.unknownProgress"></mct-include>
ng-show="ngModel.progressPerc !== undefined"></mct-include>
</div>
<div class="bottom-bar">
<a ng-repeat="dialogOption in ngModel.options"
class="s-button"
ng-click="dialogOption.callback()">
{{dialogOption.label}}
</a>
<a class="s-button major"
ng-if="ngModel.primaryOption"
ng-click="ngModel.primaryOption.callback()">
{{ngModel.primaryOption.label}}
</a>
</div>
</div>
</div>

View File

@@ -23,7 +23,6 @@
define([
"./src/controllers/EditActionController",
"./src/controllers/EditPanesController",
"./src/controllers/ElementsController",
"./src/controllers/EditObjectController",
"./src/actions/EditAndComposeAction",
"./src/actions/EditAction",
@@ -47,7 +46,6 @@ define([
"./src/creation/LocatorController",
"./src/creation/CreationPolicy",
"./src/creation/CreateActionProvider",
"./src/creation/AddActionProvider",
"./src/creation/CreationService",
"./res/templates/create/locator.html",
"./res/templates/create/create-button.html",
@@ -55,13 +53,11 @@ define([
"./res/templates/library.html",
"./res/templates/edit-object.html",
"./res/templates/edit-action-buttons.html",
"./res/templates/elements.html",
"./res/templates/topbar-edit.html",
'legacyRegistry'
], function (
EditActionController,
EditPanesController,
ElementsController,
EditObjectController,
EditAndComposeAction,
EditAction,
@@ -85,7 +81,6 @@ define([
LocatorController,
CreationPolicy,
CreateActionProvider,
AddActionProvider,
CreationService,
locatorTemplate,
createButtonTemplate,
@@ -93,7 +88,6 @@ define([
libraryTemplate,
editObjectTemplate,
editActionButtonsTemplate,
elementsTemplate,
topbarEditTemplate,
legacyRegistry
) {
@@ -115,14 +109,6 @@ define([
"$scope"
]
},
{
"key": "ElementsController",
"implementation": ElementsController,
"depends": [
"$scope",
"openmct"
]
},
{
"key": "EditObjectController",
"implementation": EditObjectController,
@@ -225,10 +211,10 @@ define([
"description": "Save changes made to these objects.",
"depends": [
"$injector",
"policyService",
"dialogService",
"copyService",
"notificationService"
"notificationService",
"openmct"
],
"priority": "mandatory"
},
@@ -296,13 +282,6 @@ define([
"action"
]
},
{
"key": "edit-elements",
"template": elementsTemplate,
"gestures": [
"drop"
]
},
{
"key": "topbar-edit",
"template": topbarEditTemplate
@@ -319,12 +298,6 @@ define([
]
}
],
"templates": [
{
key: "elementsPool",
template: elementsTemplate
}
],
"components": [
{
"type": "decorator",
@@ -356,18 +329,6 @@ define([
"policyService"
]
},
{
"key": "AddActionProvider",
"provides": "actionService",
"type": "provider",
"implementation": AddActionProvider,
"depends": [
"$q",
"typeService",
"dialogService",
"policyService"
]
},
{
"key": "CreationService",
"provides": "creationService",
@@ -405,7 +366,8 @@ define([
"description": "Provides transactional editing capabilities",
"implementation": EditorCapability,
"depends": [
"transactionService"
"transactionService",
"openmct"
]
}
],

View File

@@ -1,49 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2018, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div ng-controller="ElementsController" class="flex-elem l-flex-col holder grows">
<mct-include key="'input-filter'"
class="flex-elem holder"
ng-model="filterBy">
</mct-include>
<div class="flex-elem grows vscroll scroll-pad">
<ul class="tree" id="inspector-elements-tree"
ng-if="composition.length > 0">
<li ng-repeat="containedObject in composition | filter:searchElements">
<span class="tree-item">
<span class="grippy-sm"
ng-if="composition.length > 1"
data-id="{{ containedObject.id }}"
mct-drag-down="dragDown($event)"
mct-drag="drag($event)"
mct-drag-up="dragUp($event)">
</span>
<mct-representation
class="rep-object-label"
key="'label'"
mct-object="containedObject">
</mct-representation>
</span>
</li>
</ul>
<div ng-if="composition.length === 0">No contained elements</div>
</div>
</div>

View File

@@ -69,8 +69,8 @@ define([], function () {
}
]
};
setTimeout(() => this.removeCallback(domainObject));
dialog = this.dialogService.showBlockingMessage(model);
};
return RemoveDialog;

View File

@@ -40,20 +40,20 @@ function (
*/
function SaveAsAction(
$injector,
policyService,
dialogService,
copyService,
notificationService,
openmct,
context
) {
this.domainObject = (context || {}).domainObject;
this.injectObjectService = function () {
this.objectService = $injector.get("objectService");
};
this.policyService = policyService;
this.dialogService = dialogService;
this.copyService = copyService;
this.notificationService = notificationService;
this.openmct = openmct;
}
/**
@@ -63,7 +63,7 @@ function (
return new CreateWizard(
this.domainObject,
parent,
this.policyService
this.openmct
);
};

View File

@@ -36,9 +36,11 @@ define(
*/
function EditorCapability(
transactionService,
openmct,
domainObject
) {
this.transactionService = transactionService;
this.openmct = openmct;
this.domainObject = domainObject;
}
@@ -48,27 +50,22 @@ define(
* or finish() are called.
*/
EditorCapability.prototype.edit = function () {
this.transactionService.startTransaction();
this.domainObject.getCapability('status').set('editing', true);
console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.');
if (!this.openmct.editor.isEditing()) {
this.openmct.editor.edit();
this.domainObject.getCapability('status').set('editing', true);
}
};
function isEditContextRoot(domainObject) {
return domainObject.getCapability('status').get('editing');
}
function isEditing(domainObject) {
return isEditContextRoot(domainObject) ||
domainObject.hasCapability('context') &&
isEditing(domainObject.getCapability('context').getParent());
}
/**
* Determines whether this object, or any of its ancestors are
* currently being edited.
* @returns boolean
*/
EditorCapability.prototype.inEditContext = function () {
return isEditing(this.domainObject);
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
return this.openmct.editor.isEditing();
};
/**
@@ -77,7 +74,8 @@ define(
* @returns {*}
*/
EditorCapability.prototype.isEditContextRoot = function () {
return isEditContextRoot(this.domainObject);
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
return this.openmct.editor.isEditing();
};
/**
@@ -86,10 +84,8 @@ define(
* @returns {*}
*/
EditorCapability.prototype.save = function () {
var transactionService = this.transactionService;
return transactionService.commit().then(function () {
transactionService.startTransaction();
});
console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.');
return Promise.resolve();
};
EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
@@ -100,16 +96,8 @@ define(
* @returns {*}
*/
EditorCapability.prototype.finish = function () {
var domainObject = this.domainObject;
if (this.transactionService.isActive()) {
return this.transactionService.cancel().then(function () {
domainObject.getCapability("status").set("editing", false);
return domainObject;
});
} else {
return Promise.resolve(domainObject);
}
console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.');
return Promise.resolve();
};
/**

View File

@@ -1,197 +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 ($) {
/**
* The ElementsController prepares the elements view for display
*
* @constructor
*/
function ElementsController($scope, openmct) {
this.scope = $scope;
this.scope.composition = [];
this.openmct = openmct;
this.dragDown = this.dragDown.bind(this);
this.dragUp = this.dragUp.bind(this);
var self = this;
function filterBy(text) {
if (typeof text === 'undefined') {
return $scope.searchText;
} else {
$scope.searchText = text;
}
}
function searchElements(value) {
if ($scope.searchText) {
return value.getModel().name.toLowerCase().search(
$scope.searchText.toLowerCase()) !== -1;
} else {
return true;
}
}
function setSelection(selection) {
if (!selection[0]) {
return;
}
if (self.mutationListener) {
self.mutationListener();
delete self.mutationListener;
}
var domainObject = selection[0].context.oldItem;
self.refreshComposition(domainObject);
if (domainObject) {
self.mutationListener = domainObject.getCapability('mutation')
.listen(self.refreshComposition.bind(self, domainObject));
}
}
$scope.filterBy = filterBy;
$scope.searchElements = searchElements;
openmct.selection.on('change', setSelection);
setSelection(openmct.selection.get());
$scope.dragDown = this.dragDown;
$scope.drag = this.drag;
$scope.dragUp = this.dragUp;
$scope.$on("$destroy", function () {
openmct.selection.off("change", setSelection);
});
}
/**
* Invoked on DragStart - Adds reordering class to parent UL element
* Sets selected object ID, to be used on Drag End
*
* @param {object} event | Mouse Event
*/
ElementsController.prototype.dragDown = function (event) {
if (!this.parentUL) {
this.parentUL = $(document).find('#inspector-elements-tree');
}
this.selectedTreeItem = $(event.target).parent();
this.selectedObjectId = event.target.getAttribute('data-id');
this.parentUL.addClass('reordering');
this.selectedTreeItem.addClass('reorder-actor');
};
/**
* Invoked on dragEnd - Removes selected object from position in composition
* and replaces it at the target position. Composition is then updated with current
* scope
*
* @param {object} event - Mouse Event
*/
ElementsController.prototype.dragUp = function (event) {
this.targetObjectId = event.target.getAttribute('data-id');
if (this.targetObjectId && this.selectedObjectId) {
var selectedObjectPosition,
targetObjectPosition;
selectedObjectPosition = findObjectInCompositionFromId(this.selectedObjectId, this.scope.composition);
targetObjectPosition = findObjectInCompositionFromId(this.targetObjectId, this.scope.composition);
if ((selectedObjectPosition !== -1) && (targetObjectPosition !== -1)) {
var selectedObject = this.scope.composition.splice(selectedObjectPosition, 1),
selection = this.openmct.selection.get(),
domainObject = selection ? selection[0].context.oldItem : undefined;
this.scope.composition.splice(targetObjectPosition, 0, selectedObject[0]);
if (domainObject) {
domainObject.getCapability('mutation').mutate(function (model) {
model.composition = this.scope.composition.map(function (dObject) {
return dObject.id;
});
}.bind(this));
}
}
}
if (this.parentUL) {
this.parentUL.removeClass('reordering');
}
if (this.selectedTreeItem) {
this.selectedTreeItem.removeClass('reorder-actor');
}
};
ElementsController.prototype.drag = function (event) {
};
/**
* Gets the composition for the selected object and populates the scope with it.
*
* @param domainObject the selected object
* @private
*/
ElementsController.prototype.refreshComposition = function (domainObject) {
var refreshTracker = {};
this.currentRefresh = refreshTracker;
var selectedObjectComposition = domainObject && domainObject.useCapability('composition');
if (selectedObjectComposition) {
selectedObjectComposition.then(function (composition) {
if (this.currentRefresh === refreshTracker) {
this.scope.composition = composition;
}
}.bind(this));
} else {
this.scope.composition = [];
}
};
/**
* Finds position of object with given ID in Composition
*
* @param {String} id
* @param {Array} composition
* @private
*/
function findObjectInCompositionFromId(id, composition) {
var mapped = composition.map(function (element) {
return element.id;
});
return mapped.indexOf(id);
}
return ElementsController;
}
);

View File

@@ -1,133 +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 AddAction. Created by ahenry on 01/21/16.
*/
define(
[
'./CreateWizard'
],
function (CreateWizard) {
/**
* The Add Action is performed to create new instances of
* domain objects of a specific type that are subobjects of an
* object being edited. This is the action that is performed when a
* user uses the Add menu option.
*
* @memberof platform/commonUI/browse
* @implements {Action}
* @constructor
*
* @param {Type} type the type of domain object to create
* @param {DomainObject} parent the domain object that should
* act as a container for the newly-created object
* (note that the user will have an opportunity to
* override this)
* @param {ActionContext} context the context in which the
* action is being performed
* @param {DialogService} dialogService
*/
function AddAction(type, parent, context, $q, dialogService, policyService) {
this.metadata = {
key: 'add',
cssClass: type.getCssClass(),
name: type.getName(),
type: type.getKey(),
description: type.getDescription(),
context: context
};
this.type = type;
this.parent = parent;
this.$q = $q;
this.dialogService = dialogService;
this.policyService = policyService;
}
/**
*
* Create a new object of the given type.
* This will prompt for user input first.
*
* @returns {Promise} that will be resolved with the object that the
* action was originally invoked on (ie. the 'parent')
*/
AddAction.prototype.perform = function () {
var newModel = this.type.getInitialModel(),
newObject,
parentObject = this.parent,
wizard;
newModel.type = this.type.getKey();
newObject = parentObject.getCapability('instantiation').instantiate(newModel);
newObject.useCapability('mutation', function (model) {
model.location = parentObject.getId();
});
wizard = new CreateWizard(newObject, this.parent, this.policyService);
function populateObjectFromInput(formValue) {
return wizard.populateObjectFromInput(formValue, newObject);
}
function persistAndReturn(domainObject) {
return domainObject.getCapability('persistence')
.persist()
.then(function () {
return domainObject;
});
}
function addToParent(populatedObject) {
parentObject.getCapability('composition').add(populatedObject);
return persistAndReturn(parentObject);
}
return this.dialogService
.getUserInput(wizard.getFormStructure(false), wizard.getInitialFormValue())
.then(populateObjectFromInput)
.then(persistAndReturn)
.then(addToParent);
};
/**
* Metadata associated with a Add action.
* @typedef {ActionMetadata} AddActionMetadata
* @property {string} type the key for the type of domain object
* to be created
*/
/**
* Get metadata about this action.
* @returns {AddActionMetadata} metadata about this action
*/
AddAction.prototype.getMetadata = function () {
return this.metadata;
};
return AddAction;
}
);

View File

@@ -1,82 +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 AddActionProvider.js. Created by ahenry on 01/21/16.
*/
define(
["./AddAction"],
function (AddAction) {
/**
* The AddActionProvider is an ActionProvider which introduces
* an Add action for creating sub objects.
*
* @memberof platform/commonUI/browse
* @constructor
* @implements {ActionService}
*
* @param {TypeService} typeService the type service, used to discover
* available types
* @param {DialogService} dialogService the dialog service, used by
* specific Create actions to get user input to populate the
* model of the newly-created domain object.
* @param {CreationService} creationService the creation service (also
* introduced in this bundle), responsible for handling actual
* object creation.
*/
function AddActionProvider($q, typeService, dialogService, policyService) {
this.typeService = typeService;
this.dialogService = dialogService;
this.$q = $q;
this.policyService = policyService;
}
AddActionProvider.prototype.getActions = function (actionContext) {
var context = actionContext || {},
key = context.key,
destination = context.domainObject;
// We only provide Add actions, and we need a
// domain object to serve as the container for the
// newly-created object (although the user may later
// make a different selection)
if (key !== 'add' || !destination) {
return [];
}
// Introduce one create action per type
return ['timeline', 'activity'].map(function (type) {
return new AddAction(
this.typeService.getType(type),
destination,
context,
this.$q,
this.dialogService,
this.policyService
);
}, this);
};
return AddActionProvider;
}
);

View File

@@ -44,7 +44,7 @@ define(
* @param {ActionContext} context the context in which the
* action is being performed
*/
function CreateAction(type, parent, context) {
function CreateAction(type, parent, context, openmct) {
this.metadata = {
key: 'create',
cssClass: type.getCssClass(),
@@ -55,6 +55,7 @@ define(
};
this.type = type;
this.parent = parent;
this.openmct = openmct;
}
/**
@@ -63,37 +64,28 @@ define(
*/
CreateAction.prototype.perform = function () {
var newModel = this.type.getInitialModel(),
openmct = this.openmct,
newObject,
editAction,
editorCapability;
function closeEditor() {
return editorCapability.finish();
}
editAction;
function onSave() {
return editorCapability.save()
.then(closeEditor);
openmct.editor.save();
}
function onCancel() {
return closeEditor();
openmct.editor.cancel();
}
newModel.type = this.type.getKey();
newModel.location = this.parent.getId();
newObject = this.parent.useCapability('instantiation', newModel);
editorCapability = newObject.hasCapability('editor') && newObject.getCapability("editor");
openmct.editor.edit();
editAction = newObject.getCapability("action").getActions("edit")[0];
//If an edit action is available, perform it
if (editAction) {
return editAction.perform();
} else if (editorCapability) {
//otherwise, use the save as action
editorCapability.edit();
return newObject.getCapability("action").perform("save-as").then(onSave, onCancel);
}
newObject.getCapability("action").perform("save-as").then(onSave, onCancel);
// TODO: support editing object without saving object first.
// Which means we have to toggle createwizard afterwards. For now,
// We will disable this.
};

View File

@@ -34,13 +34,13 @@ define(
* @memberof platform/commonUI/browse
* @constructor
*/
function CreateWizard(domainObject, parent, policyService) {
function CreateWizard(domainObject, parent, openmct) {
this.type = domainObject.getCapability('type');
this.model = domainObject.getModel();
this.domainObject = domainObject;
this.properties = this.type.getProperties();
this.parent = parent;
this.policyService = policyService;
this.openmct = openmct;
}
/**
@@ -56,15 +56,10 @@ define(
*/
CreateWizard.prototype.getFormStructure = function (includeLocation) {
var sections = [],
domainObject = this.domainObject,
policyService = this.policyService;
domainObject = this.domainObject;
function validateLocation(parent) {
return parent && policyService.allow(
"composition",
parent,
domainObject
);
return parent && this.openmct.composition.checkPolicy(parent.useCapability('adapter'), domainObject.useCapability('adapter'));
}
sections.push({
@@ -93,7 +88,7 @@ define(
rows: [{
name: "Save In",
control: "locator",
validate: validateLocation,
validate: validateLocation.bind(this),
key: "createParent"
}]
});

View File

@@ -55,16 +55,19 @@ define(
navigatedObject = this.navigationService.getNavigation(),
actionMetadata = action.getMetadata ? action.getMetadata() : {};
// FIXME: need to restore support for changing contextual actions
// based on edit mode.
// if (navigatedObject.hasCapability("editor") && navigatedObject.getCapability("editor").isEditContextRoot()) {
if (selectedObject.hasCapability("editor") && selectedObject.getCapability("editor").inEditContext()) {
return this.editModeBlacklist.indexOf(actionMetadata.key) === -1;
} else {
//Target is in the context menu
return this.nonEditContextBlacklist.indexOf(actionMetadata.key) === -1;
}
// if (selectedObject.hasCapability("editor") && selectedObject.getCapability("editor").inEditContext()) {
// return this.editModeBlacklist.indexOf(actionMetadata.key) === -1;
// } else {
// //Target is in the context menu
// return this.nonEditContextBlacklist.indexOf(actionMetadata.key) === -1;
// }
// } else {
// return true;
// }
return true;
};
return EditContextualActionPolicy;

View File

@@ -1,254 +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/api/objects/object-utils',
'lodash'
],
function (
objectUtils,
_
) {
/**
* Provides initial structure and state (as suitable for provision
* to the `mct-toolbar` directive) for a view's toolbar, based on
* that view's declaration of what belongs in its toolbar and on
* the current selection.
*
* @param $scope the Angular scope
* @param {Object} openmct the openmct object
* @param structure the toolbar structure
* @memberof platform/commonUI/edit
* @constructor
*/
function EditToolbar($scope, openmct, structure) {
this.toolbarStructure = [];
this.properties = [];
this.toolbarState = [];
this.openmct = openmct;
this.domainObjectsById = {};
this.unobserveObjects = [];
this.stateTracker = [];
$scope.$watchCollection(this.getState.bind(this), this.handleStateChanges.bind(this));
$scope.$on("$destroy", this.destroy.bind(this));
this.updateToolbar(structure);
this.registerListeners(structure);
}
/**
* Updates the toolbar with a new structure.
*
* @param {Array} structure the toolbar structure
*/
EditToolbar.prototype.updateToolbar = function (structure) {
var self = this;
function addKey(item) {
self.stateTracker.push({
id: objectUtils.makeKeyString(item.domainObject.identifier),
domainObject: item.domainObject,
property: item.property
});
self.properties.push(item.property);
return self.properties.length - 1; // Return index of property
}
function convertItem(item) {
var converted = Object.create(item || {});
if (item.property) {
converted.key = addKey(item);
}
if (item.method) {
converted.click = function (value) {
item.method(value);
};
}
return converted;
}
// Get initial value for a given property
function initializeState(property) {
var result;
structure.forEach(function (item) {
if (item.property === property) {
result = _.get(item.domainObject, item.property);
}
});
return result;
}
// Tracks the domain object and property for every element in the state array
this.stateTracker = [];
this.toolbarStructure = structure.map(convertItem);
this.toolbarState = this.properties.map(initializeState);
};
/**
* Gets the structure of the toolbar, as appropriate to
* pass to `mct-toolbar`.
*
* @returns {Array} the toolbar structure
*/
EditToolbar.prototype.getStructure = function () {
return this.toolbarStructure;
};
/**
* Gets the current state of the toolbar, as appropriate
* to two-way bind to the state handled by `mct-toolbar`.
*
* @returns {Array} state of the toolbar
*/
EditToolbar.prototype.getState = function () {
return this.toolbarState;
};
/**
* Mutates the domain object's property with a new value.
*
* @param {Object} dominObject the domain object
* @param {string} property the domain object's property to update
* @param value the property's new value
*/
EditToolbar.prototype.updateDomainObject = function (domainObject, property, value) {
this.openmct.objects.mutate(domainObject, property, value);
};
/**
* Updates state with the new value.
*
* @param {number} index the index of the corresponding
* element in the state array
* @param value the new value to update the state array with
*/
EditToolbar.prototype.updateState = function (index, value) {
this.toolbarState[index] = value;
};
/**
* Register listeners for domain objects to watch for updates.
*
* @param {Array} the toolbar structure
*/
EditToolbar.prototype.registerListeners = function (structure) {
var self = this;
function observeObject(domainObject, id) {
var unobserveObject = self.openmct.objects.observe(domainObject, '*', function (newObject) {
self.domainObjectsById[id].newObject = JSON.parse(JSON.stringify(newObject));
self.scheduleStateUpdate();
});
self.unobserveObjects.push(unobserveObject);
}
structure.forEach(function (item) {
var domainObject = item.domainObject;
var id = objectUtils.makeKeyString(domainObject.identifier);
if (!self.domainObjectsById[id]) {
self.domainObjectsById[id] = {
domainObject: domainObject,
properties: []
};
observeObject(domainObject, id);
}
self.domainObjectsById[id].properties.push(item.property);
});
};
/**
* Delays updating the state.
*/
EditToolbar.prototype.scheduleStateUpdate = function () {
if (this.stateUpdateScheduled) {
return;
}
this.stateUpdateScheduled = true;
setTimeout(this.updateStateAfterMutation.bind(this));
};
EditToolbar.prototype.updateStateAfterMutation = function () {
this.stateTracker.forEach(function (state, index) {
if (!this.domainObjectsById[state.id].newObject) {
return;
}
var domainObject = this.domainObjectsById[state.id].domainObject;
var newObject = this.domainObjectsById[state.id].newObject;
var currentValue = _.get(domainObject, state.property);
var newValue = _.get(newObject, state.property);
state.domainObject = newObject;
if (currentValue !== newValue) {
this.updateState(index, newValue);
}
}, this);
Object.values(this.domainObjectsById).forEach(function (tracker) {
if (tracker.newObject) {
tracker.domainObject = tracker.newObject;
}
delete tracker.newObject;
});
this.stateUpdateScheduled = false;
};
/**
* Removes the listeners.
*/
EditToolbar.prototype.deregisterListeners = function () {
this.unobserveObjects.forEach(function (unobserveObject) {
unobserveObject();
});
this.unobserveObjects = [];
};
EditToolbar.prototype.handleStateChanges = function (state) {
(state || []).map(function (newValue, index) {
var domainObject = this.stateTracker[index].domainObject;
var property = this.stateTracker[index].property;
var currentValue = _.get(domainObject, property);
if (currentValue !== newValue) {
this.updateDomainObject(domainObject, property, newValue);
}
}, this);
};
EditToolbar.prototype.destroy = function () {
this.deregisterListeners();
};
return EditToolbar;
}
);

View File

@@ -1,184 +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/controllers/ElementsController"],
function (ElementsController) {
describe("The Elements Pane controller", function () {
var mockScope,
mockOpenMCT,
mockSelection,
mockDomainObject,
mockMutationCapability,
mockCompositionCapability,
mockCompositionObjects,
mockComposition,
mockUnlisten,
selectable = [],
controller;
function mockPromise(value) {
return {
then: function (thenFunc) {
return mockPromise(thenFunc(value));
}
};
}
function createDomainObject() {
return {
useCapability: function () {
return mockCompositionCapability;
}
};
}
beforeEach(function () {
mockComposition = ["a", "b"];
mockCompositionObjects = mockComposition.map(createDomainObject);
mockCompositionCapability = mockPromise(mockCompositionObjects);
mockUnlisten = jasmine.createSpy('unlisten');
mockMutationCapability = jasmine.createSpyObj("mutationCapability", [
"listen"
]);
mockMutationCapability.listen.and.returnValue(mockUnlisten);
mockDomainObject = jasmine.createSpyObj("domainObject", [
"getCapability",
"useCapability"
]);
mockDomainObject.useCapability.and.returnValue(mockCompositionCapability);
mockDomainObject.getCapability.and.returnValue(mockMutationCapability);
mockScope = jasmine.createSpyObj("$scope", ['$on']);
mockSelection = jasmine.createSpyObj("selection", [
'on',
'off',
'get'
]);
mockSelection.get.and.returnValue([]);
mockOpenMCT = {
selection: mockSelection
};
selectable[0] = {
context: {
oldItem: mockDomainObject
}
};
spyOn(ElementsController.prototype, 'refreshComposition').and.callThrough();
controller = new ElementsController(mockScope, mockOpenMCT);
});
function getModel(model) {
return function () {
return model;
};
}
it("filters objects in elements pool based on input text and" +
" object name", function () {
var objects = [
{
getModel: getModel({name: "first element"})
},
{
getModel: getModel({name: "second element"})
},
{
getModel: getModel({name: "third element"})
},
{
getModel: getModel({name: "THIRD Element 1"})
}
];
mockScope.filterBy("third element");
expect(objects.filter(mockScope.searchElements).length).toBe(2);
mockScope.filterBy("element");
expect(objects.filter(mockScope.searchElements).length).toBe(4);
});
it("refreshes composition on selection", function () {
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
expect(ElementsController.prototype.refreshComposition).toHaveBeenCalledWith(mockDomainObject);
});
it("listens on mutation and refreshes composition", function () {
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
expect(mockDomainObject.getCapability).toHaveBeenCalledWith('mutation');
expect(mockMutationCapability.listen).toHaveBeenCalled();
expect(ElementsController.prototype.refreshComposition.calls.count()).toBe(1);
mockMutationCapability.listen.calls.mostRecent().args[0](mockDomainObject);
expect(ElementsController.prototype.refreshComposition.calls.count()).toBe(2);
});
it("cleans up mutation listener when selection changes", function () {
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
expect(mockMutationCapability.listen).toHaveBeenCalled();
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
expect(mockUnlisten).toHaveBeenCalled();
});
it("does not listen on mutation for element proxy selectable", function () {
selectable[0] = {
context: {
elementProxy: {}
}
};
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
expect(mockDomainObject.getCapability).not.toHaveBeenCalledWith('mutation');
});
it("checks concurrent changes to composition", function () {
var secondMockComposition = ["a", "b", "c"],
secondMockCompositionObjects = secondMockComposition.map(createDomainObject),
firstCompositionCallback,
secondCompositionCallback;
spyOn(mockCompositionCapability, "then").and.callThrough();
controller.refreshComposition(mockDomainObject);
controller.refreshComposition(mockDomainObject);
firstCompositionCallback = mockCompositionCapability.then.calls.all()[0].args[0];
secondCompositionCallback = mockCompositionCapability.then.calls.all()[1].args[0];
secondCompositionCallback(secondMockCompositionObjects);
firstCompositionCallback(mockCompositionObjects);
expect(mockScope.composition).toBe(secondMockCompositionObjects);
});
});
}
);

View File

@@ -1,105 +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 ahenry on 01/21/14.
*/
define(
["../../src/creation/AddActionProvider"],
function (AddActionProvider) {
describe("The add action provider", function () {
var mockTypeService,
mockDialogService,
mockPolicyService,
mockTypeMap,
mockTypes,
mockDomainObject,
mockQ,
provider;
function createMockType(name) {
var mockType = jasmine.createSpyObj(
"type" + name,
[
"getKey",
"getGlyph",
"getCssClass",
"getName",
"getDescription",
"getProperties",
"getInitialModel",
"hasFeature"
]
);
mockType.hasFeature.and.returnValue(true);
mockType.getName.and.returnValue(name);
mockType.getKey.and.returnValue(name);
return mockType;
}
beforeEach(function () {
mockTypeService = jasmine.createSpyObj(
"typeService",
["getType"]
);
mockDialogService = {};
mockPolicyService = {};
mockDomainObject = {};
mockTypes = [
"timeline",
"activity",
"other"
].map(createMockType);
mockTypeMap = {};
mockTypes.forEach(function (type) {
mockTypeMap[type.getKey()] = type;
});
mockTypeService.getType.and.callFake(function (key) {
return mockTypeMap[key];
});
provider = new AddActionProvider(
mockQ,
mockTypeService,
mockDialogService,
mockPolicyService
);
});
it("provides actions for timeline and activity", function () {
var actions = provider.getActions({
key: "add",
domainObject: mockDomainObject
});
expect(actions.length).toBe(2);
expect(actions[0].metadata.type).toBe('timeline');
expect(actions[1].metadata.type).toBe('activity');
// Make sure it was creation which was used to check
});
});
}
);

View File

@@ -1,75 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
['../../src/representers/EditToolbar'],
function (EditToolbar) {
describe("An Edit mode toolbar", function () {
var mockOpenMCT,
mockScope,
mockObjects,
mockDomainObject,
testStructure,
toolbar;
beforeEach(function () {
mockOpenMCT = jasmine.createSpy('openmct', ['objects']);
mockObjects = jasmine.createSpyObj('objects', ['observe']);
mockObjects.observe.and.returnValue();
mockOpenMCT.objects = mockObjects;
mockScope = jasmine.createSpyObj("$scope", [
"$watchCollection",
"$on"
]);
mockScope.$watchCollection.and.returnValue();
mockDomainObject = jasmine.createSpyObj("domainObject", [
'identifier'
]);
testStructure = [
{ name: "A", property: "a", domainObject: mockDomainObject },
{ name: "B", property: "b", domainObject: mockDomainObject },
{ name: "C", property: "c", domainObject: mockDomainObject },
{ name: "X", property: "x", domainObject: mockDomainObject },
{ name: "Y", property: "y", domainObject: mockDomainObject },
{ name: "Z", property: "z", domainObject: mockDomainObject },
{ name: "M", method: "m", domainObject: mockDomainObject }
];
toolbar = new EditToolbar(mockScope, mockOpenMCT, testStructure);
});
it("adds click functions when a method is specified", function () {
var structure = toolbar.getStructure();
expect(structure[6].click).toBeDefined();
});
it("adds key for controls that define a property", function () {
var structure = toolbar.getStructure();
expect(structure[0].key).toEqual(0);
});
});
}
);

View File

@@ -1,10 +1,10 @@
<span class="l-progress-bar s-progress-bar"
ng-class="{ indeterminate:ngModel.unknownProgress }">
ng-class="{ indeterminate:ngModel.progressPerc === 'unknown' }">
<span class="progress-amt-holder">
<span class="progress-amt" style="width: {{ngModel.progress}}%"></span>
<span class="progress-amt" style="width: {{ngModel.progressPerc === 'unknown' ? 100 : ngModel.progressPerc}}%"></span>
</span>
</span>
<div class="progress-info hint" ng-hide="ngModel.progressText === undefined">
<span class="progress-amt-text" ng-show="ngModel.progress > 0">{{ngModel.progress}}% complete. </span>
<span class="progress-amt-text" ng-show="ngModel.progressPerc !== 'unknown' && ngModel.progressPerc > 0">{{ngModel.progressPerc}}% complete. </span>
{{ngModel.progressText}}
</div>

View File

@@ -50,7 +50,7 @@ define(
};
$scope.dismiss = function (notification, $event) {
$event.stopPropagation();
notification.dismissOrMinimize();
notification.dismiss();
};
$scope.maximize = function (notification) {
if (notification.model.severity !== "info") {
@@ -60,7 +60,7 @@ define(
};
//If the notification is dismissed by the user, close
// the dialog.
notification.onDismiss(function () {
notification.on('dismiss', function () {
dialog.dismiss();
});

View File

@@ -36,20 +36,6 @@ define([
legacyRegistry.register("platform/commonUI/notification", {
"extensions": {
"constants": [
{
"key": "DEFAULT_AUTO_DISMISS",
"value": 3000
},
{
"key": "FORCE_AUTO_DISMISS",
"value": 1000
},
{
"key": "MINIMIZE_TIMEOUT",
"value": 300
}
],
"templates": [
{
"key": "notificationIndicatorTemplate",
@@ -62,7 +48,7 @@ define([
"implementation": NotificationIndicatorController,
"depends": [
"$scope",
"notificationService",
"openmct",
"dialogService"
]
}
@@ -76,12 +62,11 @@ define([
"services": [
{
"key": "notificationService",
"implementation": NotificationService,
"implementation": function (openmct) {
return new NotificationService.default(openmct);
},
"depends": [
"$timeout",
"topic",
"DEFAULT_AUTO_DISMISS",
"MINIMIZE_TIMEOUT"
"openmct"
]
}
]

View File

@@ -35,9 +35,9 @@ define(
* @param dialogService
* @constructor
*/
function NotificationIndicatorController($scope, notificationService, dialogService) {
$scope.notifications = notificationService.notifications;
$scope.highest = notificationService.highest;
function NotificationIndicatorController($scope, openmct, dialogService) {
$scope.notifications = openmct.notifications.notifications;
$scope.highest = openmct.notifications.highest;
/**
* Launch a dialog showing a list of current notifications.
@@ -48,7 +48,7 @@ define(
title: "Messages",
//Launch the message list dialog with the models
// from the notifications
messages: notificationService.notifications
messages: openmct.notifications.notifications
}
});

View File

@@ -19,419 +19,46 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* This bundle implements the notification service, which can be used to
* show banner notifications to the user. Banner notifications
* are used to inform users of events in a non-intrusive way. As
* much as possible, notifications share a model with blocking
* dialogs so that the same information can be provided in a dialog
* and then minimized to a banner notification if needed.
*
* @namespace platform/commonUI/notification
*/
define(
['moment'],
function (moment) {
/**
* A representation of a user action. Options are provided to
* dialogs and notifications and are shown as buttons.
*
* @typedef {object} NotificationOption
* @property {string} label the label to appear on the button for
* this action
* @property {function} callback a callback function to be invoked
* when the button is clicked
*/
/**
* A representation of a banner notification. Banner notifications
* are used to inform users of events in a non-intrusive way. As
* much as possible, notifications share a model with blocking
* dialogs so that the same information can be provided in a dialog
* and then minimized to a banner notification if needed, or vice-versa.
*
* @typedef {object} NotificationModel
* @property {string} title The title of the message
* @property {string} severity The importance of the message (one of
* 'info', 'alert', or 'error' where info < alert <error)
* @property {number} [progress] The completion status of a task
* represented numerically
* @property {boolean} [unknownProgress] a boolean indicating that the
* progress of the underlying task is unknown. This will result in a
* visually distinct progress bar.
* @property {boolean} [autoDismiss] If truthy, dialog will
* be automatically minimized or dismissed (depending on severity).
* Additionally, if the provided value is a number, it will be used
* as the delay period before being dismissed.
* @property {boolean} [dismissable=true] If true, notification will
* include an option to dismiss it completely.
* @property {NotificationOption} [primaryOption] the default user
* response to
* this message. Will be represented as a button with the provided
* label and action. May be used by banner notifications to display
* only the most important option to users.
* @property {NotificationOption[]} [options] any additional
* actions the user can take. Will be represented as additional buttons
* that may or may not be available from a banner.
* @see DialogModel
*/
/**
* A wrapper object that is returned as a handle to a newly created
* notification. Wraps the notifications model and decorates with
* functions to dismiss or minimize the notification.
*
* @typedef {object} Notification
* @property {function} dismiss This method is added to the object
* returned by {@link NotificationService#notify} and can be used to
* dismiss this notification. Dismissing a notification will remove
* it completely and it will not appear in the notification indicator
* @property {function} minimize This method is added to the object
* returned by {@link NotificationService#notify} and can be used to
* minimize this notification. Minimizing a notification will send
* it to the notification indicator
* @property {function} dismissOrMinimize This method is added to the
* object returned by {@link NotificationService#notify}. It will
* hide the notification by either dismissing or minimizing it,
* depending on severity. Typically this is the method that should
* be used for dismissing a notification. If more control is
* required, then the minimize or dismiss functions can be called
* individually.
* @property {function} onDismiss Allows listening for on dismiss
* events. This allows cleanup etc. when the notification is dismissed.
*/
/**
* The notification service is responsible for informing the user of
* events via the use of banner notifications.
* @memberof platform/commonUI/notification
* @constructor
* @param $timeout the Angular $timeout service
* @param defaultAutoDismissTimeout The period of time that an
* auto-dismissed message will be displayed for.
* @param minimizeAnimationTimeout When notifications are minimized, a brief
* animation is shown. This animation requires some time to execute,
* so a timeout is required before the notification is hidden
*/
function NotificationService($timeout, topic, defaultAutoDismissTimeout, minimizeAnimationTimeout) {
this.notifications = [];
this.$timeout = $timeout;
this.highest = { severity: "info" };
this.AUTO_DISMISS_TIMEOUT = defaultAutoDismissTimeout;
this.MINIMIZE_ANIMATION_TIMEOUT = minimizeAnimationTimeout;
this.topic = topic;
/*
* A context in which to hold the active notification and a
* handle to its timeout.
*/
this.active = {};
}
/**
* Minimize a notification. The notification will still be available
* from the notification list. Typically notifications with a
* severity of 'info' should not be minimized, but rather
* dismissed. If you're not sure which is appropriate,
* use {@link Notification#dismissOrMinimize}
*
* @private
*/
NotificationService.prototype.minimize = function (service, notification) {
//Check this is a known notification
var index = service.notifications.indexOf(notification);
if (service.active.timeout) {
/*
Method can be called manually (clicking dismiss) or
automatically from an auto-timeout. this.active.timeout
acts as a semaphore to prevent race conditions. Cancel any
timeout in progress (for the case where a manual dismiss
has shortcut an active auto-dismiss), and clear the
semaphore.
*/
service.$timeout.cancel(service.active.timeout);
delete service.active.timeout;
}
if (index >= 0) {
notification.model.minimized = true;
//Add a brief timeout before showing the next notification
// in order to allow the minimize animation to run through.
service.$timeout(function () {
service.setActiveNotification(service.selectNextNotification());
}, service.MINIMIZE_ANIMATION_TIMEOUT);
}
};
/**
* Completely removes a notification. This will dismiss it from the
* message banner and remove it from the list of notifications.
* Typically only notifications with a severity of info should be
* dismissed. If you're not sure whether to dismiss or minimize a
* notification, use {@link Notification#dismissOrMinimize}.
* dismiss
*
* @private
*/
NotificationService.prototype.dismiss = function (service, notification) {
//Check this is a known notification
var index = service.notifications.indexOf(notification);
if (service.active.timeout) {
/* Method can be called manually (clicking dismiss) or
* automatically from an auto-timeout. this.active.timeout
* acts as a semaphore to prevent race conditions. Cancel any
* timeout in progress (for the case where a manual dismiss
* has shortcut an active auto-dismiss), and clear the
* semaphore.
*/
service.$timeout.cancel(service.active.timeout);
delete service.active.timeout;
}
if (index >= 0) {
service.notifications.splice(index, 1);
}
service.setActiveNotification(service.selectNextNotification());
this.setHighestSeverity();
};
/**
* Depending on the severity of the notification will selectively
* dismiss or minimize where appropriate.
*
* @private
*/
NotificationService.prototype.dismissOrMinimize = function (notification) {
var model = notification.model;
if (model.severity === "info") {
if (model.autoDismiss === false) {
notification.minimize();
} else {
notification.dismiss();
}
} else {
notification.minimize();
}
};
/**
* Returns the notification that is currently visible in the banner area
* @returns {Notification}
*/
NotificationService.prototype.getActiveNotification = function () {
return this.active.notification;
};
/**
* A convenience method for info notifications. Notifications
* created via this method will be auto-dismissed after a default
* wait period unless explicitly forbidden by the caller through
* the {autoDismiss} property on the {NotificationModel}, in which
* case the notification will be minimized after the wait.
* @param {NotificationModel | string} message either a string for
* the title of the notification message, or a {@link NotificationModel}
* defining the options notification to display
* @returns {Notification} the provided notification decorated with
* functions to dismiss or minimize
*/
NotificationService.prototype.info = function (message) {
var notificationModel = typeof message === "string" ? {title: message} : message;
notificationModel.severity = "info";
return this.notify(notificationModel);
};
/**
* A convenience method for alert notifications. Notifications
* created via this method will will have severity of "alert" enforced
* @param {NotificationModel | string} message either a string for
* the title of the alert message with default options, or a
* {@link NotificationModel} defining the options notification to
* display
* @returns {Notification} the provided notification decorated with
* functions to dismiss or minimize
*/
NotificationService.prototype.alert = function (message) {
var notificationModel = typeof message === "string" ? {title: message} : message;
notificationModel.severity = "alert";
return this.notify(notificationModel);
};
/**
* A convenience method for error notifications. Notifications
* created via this method will will have severity of "error" enforced
* @param {NotificationModel | string} message either a string for
* the title of the error message with default options, or a
* {@link NotificationModel} defining the options notification to
* display
* @returns {Notification} the provided notification decorated with
* functions to dismiss or minimize
*/
NotificationService.prototype.error = function (message) {
var notificationModel = typeof message === "string" ? {title: message} : message;
notificationModel.severity = "error";
return this.notify(notificationModel);
};
/**
* @private
*/
NotificationService.prototype.setHighestSeverity = function () {
var severity = {
"info": 1,
"alert": 2,
"error": 3
};
this.highest.severity = this.notifications.reduce(function (previous, notification) {
if (severity[notification.model.severity] > severity[previous]) {
return notification.model.severity;
} else {
return previous;
}
}, "info");
};
/**
* Notifies the user of an event. If there is a banner notification
* already active, then it will be dismissed or minimized automatically,
* and the provided notification displayed in its place.
*
* @param {NotificationModel} notificationModel The notification to
* display
* @returns {Notification} the provided notification decorated with
* functions to {@link Notification#dismiss} or {@link Notification#minimize}
*/
NotificationService.prototype.notify = function (notificationModel) {
var self = this,
notification,
activeNotification = self.active.notification,
topic = this.topic();
notificationModel.severity = notificationModel.severity || "info";
notificationModel.timestamp = moment.utc().format('YYYY-MM-DD hh:mm:ss.ms');
notification = {
model: notificationModel,
minimize: function () {
self.minimize(self, notification);
},
dismiss: function () {
self.dismiss(self, notification);
topic.notify();
},
dismissOrMinimize: function () {
self.dismissOrMinimize(notification);
},
onDismiss: function (callback) {
topic.listen(callback);
}
};
//Notifications support a 'dismissable' attribute. This is a
// convenience to support adding a 'dismiss' option to the
// notification for the common case of dismissing a
// notification. Could also be done manually by specifying an
// option on the model
if (notificationModel.dismissable !== false) {
notificationModel.options = notificationModel.options || [];
notificationModel.options.unshift({
label: "Dismiss",
callback: function () {
notification.dismiss();
}
});
}
this.notifications.push(notification);
this.setHighestSeverity();
/*
Check if there is already an active (ie. visible) notification
*/
if (!this.active.notification) {
this.setActiveNotification(notification);
} else if (!this.active.timeout) {
/*
If there is already an active notification, time it out. If it's
already got a timeout in progress (either because it has had
timeout forced because of a queue of messages, or it had an
autodismiss specified), leave it to run. Otherwise force a
timeout.
This notification has been added to queue and will be
serviced as soon as possible.
*/
this.active.timeout = this.$timeout(function () {
activeNotification.dismissOrMinimize();
}, this.AUTO_DISMISS_TIMEOUT);
}
return notification;
};
/**
* Used internally by the NotificationService
* @private
*/
NotificationService.prototype.setActiveNotification = function (notification) {
var shouldAutoDismiss;
this.active.notification = notification;
if (!notification) {
delete this.active.timeout;
return;
}
if (notification.model.severity === "info") {
shouldAutoDismiss = true;
} else {
shouldAutoDismiss = notification.model.autoDismiss;
}
if (shouldAutoDismiss || this.selectNextNotification()) {
this.active.timeout = this.$timeout(function () {
notification.dismissOrMinimize();
}, this.AUTO_DISMISS_TIMEOUT);
} else {
delete this.active.timeout;
}
};
/**
* Used internally by the NotificationService
*
* @private
*/
NotificationService.prototype.selectNextNotification = function () {
var notification,
i = 0;
/*
Loop through the notifications queue and find the first one that
has not already been minimized (manually or otherwise).
*/
for (; i < this.notifications.length; i++) {
notification = this.notifications[i];
if (!notification.model.minimized &&
notification !== this.active.notification) {
return notification;
}
}
};
return NotificationService;
export default class NotificationService {
constructor(openmct) {
this.openmct = openmct;
}
);
info(message) {
if (typeof message === 'string') {
return this.openmct.notifications.info(message);
} else {
if (message.hasOwnProperty('progress')) {
return this.openmct.notifications.progress(message.title, message.progress, message.progressText);
} else {
return this.openmct.notifications.info(message.title);
}
}
}
alert(message) {
if (typeof message === 'string') {
return this.openmct.notifications.alert(message);
} else {
return this.openmct.notifications.alert(message.title);
}
}
error(message) {
if (typeof message === 'string') {
return this.openmct.notifications.error(message);
} else {
return this.openmct.notifications.error(message.title);
}
}
notify(options) {
switch (options.severity) {
case 'info':
return this.info(options);
case 'alert':
return this.alert(options);
case 'error':
return this.error(options);
}
}
getAllNotifications() {
return this.openmct.notifications.notifications;
}
}

View File

@@ -1,275 +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/NotificationService'],
function (NotificationService) {
describe("The notification service ", function () {
var notificationService,
mockTimeout,
mockAutoDismiss,
mockMinimizeTimeout,
mockTopicFunction,
mockTopicObject,
infoModel,
alertModel,
errorModel;
function elapseTimeout() {
mockTimeout.calls.mostRecent().args[0]();
}
beforeEach(function () {
mockTimeout = jasmine.createSpy("$timeout");
mockTopicFunction = jasmine.createSpy("topic");
mockTopicObject = jasmine.createSpyObj("topicObject", ["listen", "notify"]);
mockTopicFunction.and.returnValue(mockTopicObject);
mockAutoDismiss = mockMinimizeTimeout = 1000;
notificationService = new NotificationService(mockTimeout, mockTopicFunction, mockAutoDismiss, mockMinimizeTimeout);
infoModel = {
title: "Mock Info Notification",
severity: "info"
};
alertModel = {
title: "Mock Alert Notification",
severity: "alert"
};
errorModel = {
title: "Mock Error Notification",
severity: "error"
};
});
it("notifies listeners on dismissal of notification", function () {
var dismissListener = jasmine.createSpy("ondismiss");
var notification = notificationService.notify(infoModel);
notification.onDismiss(dismissListener);
expect(mockTopicObject.listen).toHaveBeenCalled();
notification.dismiss();
expect(mockTopicObject.notify).toHaveBeenCalled();
mockTopicObject.listen.calls.mostRecent().args[0]();
expect(dismissListener).toHaveBeenCalled();
});
it("dismisses a notification when the notification's dismiss method is used", function () {
var notification = notificationService.info(infoModel);
notification.dismiss();
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(0);
});
it("minimizes a notification when the notification's minimize method is used", function () {
var notification = notificationService.info(infoModel);
notification.minimize();
elapseTimeout(); // needed for the minimize animation timeout
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
describe("when receiving info notifications", function () {
it("minimizes info notifications if the caller disables auto-dismiss", function () {
infoModel.autoDismiss = false;
var notification = notificationService.info(infoModel);
elapseTimeout();
// 2nd elapse for the minimize animation timeout
elapseTimeout();
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
it("dismisses info notifications if the caller ignores auto-dismiss", function () {
notificationService.info(infoModel);
elapseTimeout();
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(0);
});
it("dismisses info notifications if the caller requests auto-dismiss", function () {
infoModel.autoDismiss = true;
notificationService.info(infoModel);
elapseTimeout();
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(0);
});
});
describe("when receiving alert notifications", function () {
it("minimizes alert notifications if the caller enables auto-dismiss", function () {
alertModel.autoDismiss = true;
var notification = notificationService.alert(alertModel);
elapseTimeout();
elapseTimeout();
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
it("keeps alert notifications active if the caller disables auto-dismiss", function () {
mockTimeout.and.callFake(function (callback, time) {
callback();
});
alertModel.autoDismiss = false;
var notification = notificationService.alert(alertModel);
expect(notificationService.getActiveNotification()).toEqual(notification);
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
it("keeps alert notifications active if the caller ignores auto-dismiss", function () {
mockTimeout.and.callFake(function (callback, time) {
callback();
});
var notification = notificationService.alert(alertModel);
expect(notificationService.getActiveNotification()).toEqual(notification);
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
});
describe("when receiving error notifications", function () {
it("minimizes error notifications if the caller enables auto-dismiss", function () {
errorModel.autoDismiss = true;
var notification = notificationService.error(errorModel);
elapseTimeout();
elapseTimeout();
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
it("keeps error notifications active if the caller disables auto-dismiss", function () {
mockTimeout.and.callFake(function (callback, time) {
callback();
});
errorModel.autoDismiss = false;
var notification = notificationService.error(errorModel);
expect(notificationService.getActiveNotification()).toEqual(notification);
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
it("keeps error notifications active if the caller ignores auto-dismiss", function () {
mockTimeout.and.callFake(function (callback, time) {
callback();
});
var notification = notificationService.error(errorModel);
expect(notificationService.getActiveNotification()).toEqual(notification);
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
});
describe("when called with multiple notifications", function () {
it("auto-dismisses the previously active notification, making the new notification active", function () {
var activeNotification;
infoModel.autoDismiss = false;
//First pre-load with a info message
notificationService.notify(infoModel);
activeNotification = notificationService.getActiveNotification();
//Initially expect the active notification to be info
expect(activeNotification.model).toBe(infoModel);
//Then notify of an error
notificationService.notify(errorModel);
//But it should be auto-dismissed and replaced with the
// error notification
elapseTimeout();
//Two timeouts, one is to force minimization after
// displaying the message for a minimum period, the
// second is to allow minimization animation to take place.
elapseTimeout();
activeNotification = notificationService.getActiveNotification();
expect(activeNotification.model).toBe(errorModel);
});
it("auto-minimizes an active error notification", function () {
var activeNotification;
//First pre-load with an error message
notificationService.notify(errorModel);
//Then notify of info
notificationService.notify(infoModel);
expect(notificationService.notifications.length).toEqual(2);
//Mock the auto-minimize
elapseTimeout();
//Two timeouts, one is to force minimization after
// displaying the message for a minimum period, the
// second is to allow minimization animation to take place.
elapseTimeout();
//Previous error message should be minimized, not
// dismissed
expect(notificationService.notifications.length).toEqual(2);
activeNotification = notificationService.getActiveNotification();
expect(activeNotification.model).toBe(infoModel);
expect(errorModel.minimized).toEqual(true);
});
it("auto-minimizes errors when a number of them arrive in short succession", function () {
var activeNotification,
error2 = {
title: "Second Mock Error Notification",
severity: "error"
},
error3 = {
title: "Third Mock Error Notification",
severity: "error"
};
//First pre-load with a info message
notificationService.notify(errorModel);
//Then notify of a third error
notificationService.notify(error2);
notificationService.notify(error3);
expect(notificationService.notifications.length).toEqual(3);
//Mock the auto-minimize
elapseTimeout();
//Two timeouts, one is to force minimization after
// displaying the message for a minimum period, the
// second is to allow minimization animation to take place.
elapseTimeout();
//Previous error message should be minimized, not
// dismissed
expect(notificationService.notifications.length).toEqual(3);
activeNotification = notificationService.getActiveNotification();
expect(activeNotification.model).toBe(error2);
expect(errorModel.minimized).toEqual(true);
//Mock the second auto-minimize
elapseTimeout();
//Two timeouts, one is to force minimization after
// displaying the message for a minimum period, the
// second is to allow minimization animation to take place.
elapseTimeout();
activeNotification = notificationService.getActiveNotification();
expect(activeNotification.model).toBe(error3);
expect(error2.minimized).toEqual(true);
});
});
});
}
);

View File

@@ -58,7 +58,8 @@ define([
"category": "action",
"implementation": ComposeActionPolicy,
"depends": [
"$injector"
"$injector",
"openmct"
],
"message": "Objects of this type cannot contain objects of that type."
},

View File

@@ -36,10 +36,11 @@ define(
* @memberof platform/containment
* @implements {Policy.<Action, ActionContext>}
*/
function ComposeActionPolicy($injector) {
function ComposeActionPolicy($injector, openmct) {
this.getPolicyService = function () {
return $injector.get('policyService');
};
this.openmct = openmct;
}
ComposeActionPolicy.prototype.allowComposition = function (containerObject, selectedObject) {
@@ -49,11 +50,8 @@ define(
// ...and delegate to the composition policy
return containerObject.getId() !== selectedObject.getId() &&
this.policyService.allow(
'composition',
containerObject,
selectedObject
);
this.openmct.composition.checkPolicy(containerObject.useCapability('adapter'),
selectedObject.useCapability('adapter'));
};
/**

View File

@@ -170,7 +170,7 @@ define([
"description": "Provides a service for moving objects",
"implementation": MoveService,
"depends": [
"policyService",
"openmct",
"linkService",
"$q"
]
@@ -181,7 +181,7 @@ define([
"description": "Provides a service for linking objects",
"implementation": LinkService,
"depends": [
"policyService"
"openmct"
]
},
{
@@ -192,7 +192,7 @@ define([
"depends": [
"$q",
"policyService",
"now"
"openmct"
]
},
{

View File

@@ -33,9 +33,10 @@ define(
* @memberof platform/entanglement
* @implements {platform/entanglement.AbstractComposeService}
*/
function CopyService($q, policyService) {
function CopyService($q, policyService, openmct) {
this.$q = $q;
this.policyService = policyService;
this.openmct = openmct;
}
CopyService.prototype.validate = function (object, parentCandidate) {
@@ -45,11 +46,7 @@ define(
if (parentCandidate.getId() === object.getId()) {
return false;
}
return this.policyService.allow(
"composition",
parentCandidate,
object
);
return this.openmct.composition.checkPolicy(parentCandidate.useCapability('adapter'), object.useCapability('adapter'));
};
/**

View File

@@ -32,8 +32,8 @@ define(
* @memberof platform/entanglement
* @implements {platform/entanglement.AbstractComposeService}
*/
function LinkService(policyService) {
this.policyService = policyService;
function LinkService(openmct) {
this.openmct = openmct;
}
LinkService.prototype.validate = function (object, parentCandidate) {
@@ -49,11 +49,7 @@ define(
if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
return false;
}
return this.policyService.allow(
"composition",
parentCandidate,
object
);
return this.openmct.composition.checkPolicy(parentCandidate.useCapability('adapter'), object.useCapability('adapter'));
};
LinkService.prototype.perform = function (object, parentObject) {

View File

@@ -31,8 +31,8 @@ define(
* @memberof platform/entanglement
* @implements {platform/entanglement.AbstractComposeService}
*/
function MoveService(policyService, linkService) {
this.policyService = policyService;
function MoveService(openmct, linkService) {
this.openmct = openmct;
this.linkService = linkService;
}
@@ -53,10 +53,9 @@ define(
if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
return false;
}
return this.policyService.allow(
"composition",
parentCandidate,
object
return this.openmct.composition.checkPolicy(
parentCandidate.useCapability('adapter'),
object.useCapability('adapter')
);
};

View File

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

View File

@@ -1,399 +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([
"../layout/res/templates/fixed.html",
'legacyRegistry'
], function (
fixedTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/fixed", {
"name": "Fixed position components.",
"description": "Plug in adding Fixed Position object type.",
"extensions": {
"views": [
{
"key": "fixed-display",
"name": "Fixed Position Display",
"cssClass": "icon-box-with-dashed-lines",
"type": "telemetry.fixed",
"template": fixedTemplate,
"uses": [],
"editable": true
}
],
"toolbars": [
{
name: "Fixed Position Toolbar",
key: "fixed.position",
description: "Toolbar for the selected element inside a fixed position display.",
forSelection: function (selection) {
if (!selection) {
return;
}
return (
selection[0] && selection[0].context.elementProxy &&
selection[1] && selection[1].context.item.type === 'telemetry.fixed' ||
selection[0] && selection[0].context.item.type === 'telemetry.fixed'
);
},
toolbar: function (selection) {
var imageProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "url"];
var boxProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill"];
var textProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "text"];
var lineProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "x2", "y2"];
var telemetryProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "titled"];
var fixedPageProperties = ["add"];
var properties = [],
fixedItem = selection[0] && selection[0].context.item,
elementProxy = selection[0] && selection[0].context.elementProxy,
domainObject = selection[1] && selection[1].context.item,
path;
if (elementProxy) {
var type = elementProxy.element.type;
path = "configuration['fixed-display'].elements[" + elementProxy.index + "]";
properties =
type === 'fixed.image' ? imageProperties :
type === 'fixed.text' ? textProperties :
type === 'fixed.box' ? boxProperties :
type === 'fixed.line' ? lineProperties :
type === 'fixed.telemetry' ? telemetryProperties : [];
} else if (fixedItem) {
properties = domainObject && domainObject.type === 'layout' ? [] : fixedPageProperties;
}
return [
{
control: "menu-button",
domainObject: domainObject || selection[0].context.item,
method: function (value) {
selection[0].context.fixedController.add(value);
},
key: "add",
cssClass: "icon-plus",
text: "Add",
options: [
{
"name": "Box",
"cssClass": "icon-box",
"key": "fixed.box"
},
{
"name": "Line",
"cssClass": "icon-line-horz",
"key": "fixed.line"
},
{
"name": "Text",
"cssClass": "icon-T",
"key": "fixed.text"
},
{
"name": "Image",
"cssClass": "icon-image",
"key": "fixed.image"
}
]
},
{
control: "menu-button",
domainObject: domainObject,
method: function (value) {
selection[0].context.fixedController.order(
selection[0].context.elementProxy,
value
);
},
key: "order",
cssClass: "icon-layers",
title: "Layering",
description: "Move the selected object above or below other objects",
options: [
{
"name": "Move to Top",
"cssClass": "icon-arrow-double-up",
"key": "top"
},
{
"name": "Move Up",
"cssClass": "icon-arrow-up",
"key": "up"
},
{
"name": "Move Down",
"cssClass": "icon-arrow-down",
"key": "down"
},
{
"name": "Move to Bottom",
"cssClass": "icon-arrow-double-down",
"key": "bottom"
}
]
},
{
control: "color",
domainObject: domainObject,
property: path + ".fill",
cssClass: "icon-paint-bucket",
title: "Fill color",
description: "Set fill color",
key: 'fill'
},
{
control: "color",
domainObject: domainObject,
property: path + ".stroke",
cssClass: "icon-line-horz",
title: "Border color",
description: "Set border color",
key: 'stroke'
},
{
control: "dialog-button",
domainObject: domainObject,
property: path + ".url",
cssClass: "icon-image",
title: "Image Properties",
description: "Edit image properties",
key: 'url',
dialog: {
control: "textfield",
name: "Image URL",
cssClass: "l-input-lg",
required: true
}
},
{
control: "color",
domainObject: domainObject,
property: path + ".color",
cssClass: "icon-T",
title: "Text color",
mandatory: true,
description: "Set text color",
key: 'color'
},
{
control: "select",
domainObject: domainObject,
property: path + ".size",
title: "Text size",
description: "Set text size",
"options": [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96].map(function (size) {
return { "name": size + " px", "value": size + "px" };
}),
key: 'size'
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".x",
text: "X",
name: "X",
key: "x",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".y",
text: "Y",
name: "Y",
key: "y",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".x",
text: "X1",
name: "X1",
key: "x1",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".y",
text: "Y1",
name: "Y1",
key: "y1",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".x2",
text: "X2",
name: "X2",
key: "x2",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".y2",
text: "Y2",
name: "Y2",
key: "y2",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".height",
text: "H",
name: "H",
key: "height",
cssClass: "l-input-sm",
description: "Resize object height",
min: "1"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".width",
text: "W",
name: "W",
key: "width",
cssClass: "l-input-sm",
description: "Resize object width",
min: "1"
},
{
control: "checkbox",
domainObject: domainObject,
property: path + ".useGrid",
name: "Snap to Grid",
key: "useGrid"
},
{
control: "dialog-button",
domainObject: domainObject,
property: path + ".text",
cssClass: "icon-gear",
title: "Text Properties",
description: "Edit text properties",
key: "text",
dialog: {
control: "textfield",
name: "Text",
required: true
}
},
{
control: "checkbox",
domainObject: domainObject,
property: path + ".titled",
name: "Show Title",
key: "titled"
},
{
control: "button",
domainObject: domainObject,
method: function () {
selection[0].context.fixedController.remove(
selection[0].context.elementProxy
);
},
key: "remove",
cssClass: "icon-trash"
}
].filter(function (item) {
var filtered;
properties.forEach(function (property) {
if (item.property && item.key === property ||
item.method && item.key === property) {
filtered = item;
}
});
return filtered;
});
}
}
],
"types": [
{
"key": "telemetry.fixed",
"name": "Fixed Position Display",
"cssClass": "icon-box-with-dashed-lines",
"description": "Collect and display telemetry elements in " +
"alphanumeric format in a simple canvas workspace. " +
"Elements can be positioned and sized. " +
"Lines, boxes and images can be added as well.",
"priority": 899,
"delegates": [
"telemetry"
],
"features": "creation",
"contains": [
{
"has": "telemetry"
}
],
"model": {
"layoutGrid": [64, 16],
"composition": []
},
"properties": [
{
"name": "Layout Grid",
"control": "composite",
"items": [
{
"name": "Horizontal grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
},
{
"name": "Vertical grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
}
],
"pattern": "^(\\d*[1-9]\\d*)?$",
"property": "layoutGrid",
"conversion": "number[]"
}
],
"views": [
"fixed-display"
]
}
]
}
});
});

View File

@@ -0,0 +1,475 @@
/*****************************************************************************
* 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/FixedController",
"./templates/fixed.html",
"./templates/frame.html",
"./templates/elements/telemetry.html",
"./templates/elements/box.html",
"./templates/elements/line.html",
"./templates/elements/text.html",
"./templates/elements/image.html",
"legacyRegistry"
], function (
FixedController,
fixedTemplate,
frameTemplate,
telemetryTemplate,
boxTemplate,
lineTemplate,
textTemplate,
imageTemplate,
legacyRegistry
) {
return function() {
return function (openmct) {
openmct.legacyRegistry.register("platform/features/fixed", {
"name": "Fixed position components.",
"description": "Plug in adding Fixed Position object type.",
"extensions": {
"views": [
{
"key": "fixed-display",
"name": "Fixed Position Display",
"cssClass": "icon-box-with-dashed-lines",
"type": "telemetry.fixed",
"template": fixedTemplate,
"uses": ["composition"],
"editable": true
}
],
"templates": [
{
"key": "fixed.telemetry",
"template": telemetryTemplate
},
{
"key": "fixed.box",
"template": boxTemplate
},
{
"key": "fixed.line",
"template": lineTemplate
},
{
"key": "fixed.text",
"template": textTemplate
},
{
"key": "fixed.image",
"template": imageTemplate
}
],
"controllers": [
{
"key": "FixedController",
"implementation": FixedController,
"depends": [
"$scope",
"$q",
"dialogService",
"openmct",
"$element"
]
}
],
"toolbars": [
{
name: "Fixed Position Toolbar",
key: "fixed.position",
description: "Toolbar for the selected element inside a fixed position display.",
forSelection: function (selection) {
if (!selection) {
return;
}
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 && selection[0].context.item.type === 'telemetry.fixed')));
},
toolbar: function (selection) {
var imageProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "url"];
var boxProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill"];
var textProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "text"];
var lineProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "x2", "y2"];
var telemetryProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "titled"];
var fixedPageProperties = ["add"];
var properties = [],
fixedItem = selection[0] && selection[0].context.item,
elementProxy = selection[0] && selection[0].context.elementProxy,
domainObject = selection[1] && selection[1].context.item,
path;
if (elementProxy) {
var type = elementProxy.element.type;
path = "configuration['fixed-display'].elements[" + elementProxy.index + "]";
properties =
type === 'fixed.image' ? imageProperties :
type === 'fixed.text' ? textProperties :
type === 'fixed.box' ? boxProperties :
type === 'fixed.line' ? lineProperties :
type === 'fixed.telemetry' ? telemetryProperties : [];
} else if (fixedItem) {
properties = domainObject && domainObject.type === 'layout' ? [] : fixedPageProperties;
}
return [
{
control: "menu",
domainObject: domainObject || selection[0].context.item,
method: function (option) {
selection[0].context.fixedController.add(option.key);
},
key: "add",
icon: "icon-plus",
label: "Add",
options: [
{
"name": "Box",
"class": "icon-box",
"key": "fixed.box"
},
{
"name": "Line",
"class": "icon-line-horz",
"key": "fixed.line"
},
{
"name": "Text",
"class": "icon-T",
"key": "fixed.text"
},
{
"name": "Image",
"class": "icon-image",
"key": "fixed.image"
}
]
},
{
control: "menu",
domainObject: domainObject,
method: function (option) {
console.log('option', option)
selection[0].context.fixedController.order(
selection[0].context.elementProxy,
option.key
);
},
key: "order",
icon: "icon-layers",
title: "Move the selected object above or below other objects",
options: [
{
"name": "Move to Top",
"class": "icon-arrow-double-up",
"key": "top"
},
{
"name": "Move Up",
"class": "icon-arrow-up",
"key": "up"
},
{
"name": "Move Down",
"class": "icon-arrow-down",
"key": "down"
},
{
"name": "Move to Bottom",
"class": "icon-arrow-double-down",
"key": "bottom"
}
]
},
{
control: "color-picker",
domainObject: domainObject,
property: path + ".fill",
icon: "icon-paint-bucket",
title: "Set fill color",
key: 'fill'
},
{
control: "color-picker",
domainObject: domainObject,
property: path + ".stroke",
icon: "icon-line-horz",
title: "Set border color",
key: 'stroke'
},
{
control: "button",
domainObject: domainObject,
property: path + ".url",
icon: "icon-image",
title: "Edit image properties",
key: 'url',
dialog: {
control: "input",
type: "text",
name: "Image URL",
class: "l-input-lg",
required: true
}
},
{
control: "color-picker",
domainObject: domainObject,
property: path + ".color",
icon: "icon-T",
mandatory: true,
title: "Set text color",
key: 'color'
},
{
control: "select-menu",
domainObject: domainObject,
property: path + ".size",
title: "Set text size",
key: 'size',
options: [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96].map(function (size) {
return { "value": size + "px"};
})
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".x",
label: "X",
title: "X position",
key: "x",
class: "l-input-sm",
min: "0"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".y",
label: "Y",
title: "Y position",
key: "y",
class: "l-input-sm",
min: "0"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".x",
label: "X1",
title: "X1 position",
key: "x1",
class: "l-input-sm",
min: "0"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".y",
label: "Y1",
title: "Y1 position",
key: "y1",
class: "l-input-sm",
min: "0"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".x2",
label: "X2",
title: "X2 position",
key: "x2",
class: "l-input-sm",
min: "0"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".y2",
label: "Y2",
title: "Y2 position",
key: "y2",
class: "l-input-sm",
min: "0"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".height",
label: "H",
title: "Resize object height",
key: "height",
class: "l-input-sm",
min: "1"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".width",
label: "W",
title: "Resize object width",
key: "width",
class: "l-input-sm",
min: "1"
},
{
control: "toggle-button",
domainObject: domainObject,
property: path + ".useGrid",
key: "useGrid",
options: [
{
value: true,
icon: 'icon-grid-snap-to',
title: 'Snap to grid'
},
{
value: false,
icon: 'icon-grid-snap-no',
title: "Do not snap to grid"
}
]
},
{
control: "button",
domainObject: domainObject,
property: path + ".text",
icon: "icon-gear",
title: "Edit text properties",
key: "text",
dialog: {
control: "input",
type: "text",
name: "Text",
required: true
}
},
{
control: "toggle-button",
domainObject: domainObject,
property: path + ".titled",
key: "titled",
options: [
{
value: true,
icon: 'icon-two-parts-both',
title: 'Show label'
},
{
value: false,
icon: 'icon-two-parts-one-only',
title: "Hide label"
}
]
},
{
control: "button",
domainObject: domainObject,
method: function () {
selection[0].context.fixedController.remove(
selection[0].context.elementProxy
);
},
key: "remove",
icon: "icon-trash"
}
].filter(function (item) {
var filtered;
properties.forEach(function (property) {
if (item.property && item.key === property ||
item.method && item.key === property) {
filtered = item;
}
});
return filtered;
});
}
}
],
"types": [
{
"key": "telemetry.fixed",
"name": "Fixed Position Display",
"cssClass": "icon-box-with-dashed-lines",
"description": "Collect and display telemetry elements in " +
"alphanumeric format in a simple canvas workspace. " +
"Elements can be positioned and sized. " +
"Lines, boxes and images can be added as well.",
"priority": 899,
"delegates": [
"telemetry"
],
"features": "creation",
"contains": [
{
"has": "telemetry"
}
],
"model": {
"layoutGrid": [64, 16],
"composition": []
},
"properties": [
{
"name": "Layout Grid",
"control": "composite",
"items": [
{
"name": "Horizontal grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
},
{
"name": "Vertical grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
}
],
"pattern": "^(\\d*[1-9]\\d*)?$",
"property": "layoutGrid",
"conversion": "number[]"
}
],
"views": [
"fixed-display"
]
}
]
}
});
openmct.legacyRegistry.enable("platform/features/fixed");
}
}
});

View File

@@ -225,6 +225,8 @@ define(
this.openmct.time.on("bounds", updateDisplayBounds);
this.openmct.selection.on('change', this.setSelection.bind(this));
this.openmct.editor.on('isEditing', this.handleEditing.bind(this));
this.$element.on('click', this.bypassSelection.bind(this));
this.unlisten = this.openmct.objects.observe(this.newDomainObject, '*', function (obj) {
this.newDomainObject = JSON.parse(JSON.stringify(obj));
@@ -421,6 +423,7 @@ define(
this.openmct.selection.off("change", this.setSelection);
this.composition.off('add', this.onCompositionAdd, this);
this.composition.off('remove', this.onCompositionRemove, this);
this.openmct.editor.off('isEditing', this.handleEditing, this);
};
/**
@@ -706,6 +709,12 @@ define(
this.openmct.objects.mutate(this.newDomainObject, path, value);
};
FixedController.prototype.handleEditing = function (isEditing) {
// Listen for edit mode changes and update selection if necessary.
// Mainly to ensure fixedController is on the selection context when editing.
this.setSelection(this.openmct.selection.get());
}
return FixedController;
}
);

View File

@@ -42,7 +42,8 @@ define(
},
"fixed.text": {
fill: "transparent",
stroke: "transparent"
stroke: "transparent",
size: "13px"
}
},
DIALOGS = {

View File

@@ -67,10 +67,6 @@ define(
*/
proxy.size = new AccessorMutator(element, 'size');
if (proxy.size() === undefined) {
proxy.size("13px");
}
return proxy;
}

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -23,18 +23,18 @@
ng-controller="FixedController as controller">
<!-- Background grid -->
<div class="l-grid-holder" ng-click="controller.bypassSelection($event)">
<div class="l-grid l-grid-x"
<div class="l-fixed-position__grid-holder l-grid-holder c-grid" ng-click="controller.bypassSelection($event)">
<div class="c-grid__x l-grid l-grid-x"
ng-if="!controller.getGridSize()[0] < 3"
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
<div class="l-grid l-grid-y"
<div class="c-grid__y l-grid l-grid-y"
ng-if="!controller.getGridSize()[1] < 3"
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
</div>
<!-- Fixed position elements -->
<div ng-repeat="element in controller.getElements()"
class="l-fixed-position-item s-selectable s-moveable s-hover-border"
class="l-fixed-position-item s-selectable s-moveable is-selectable is-moveable"
ng-style="element.style"
mct-selectable="controller.getContext(element)"
mct-init-select="controller.shouldSelect(element)">
@@ -44,15 +44,15 @@
</mct-include>
</div>
<!-- Selection highlight, handles -->
<span class="s-selected s-moveable" ng-if="controller.isElementSelected()">
<div class="l-fixed-position-item t-edit-handle-holder"
<span class="c-frame-edit" ng-if="controller.isElementSelected()">
<div class="c-frame-edit__move"
mct-drag-down="controller.moveHandle().startDrag()"
mct-drag="controller.moveHandle().continueDrag(delta)"
mct-drag-up="controller.endDrag()"
ng-style="controller.getSelectedElementStyle()">
</div>
<div ng-repeat="handle in controller.handles()"
class="l-fixed-position-item-handle edit-corner"
class="c-frame-edit__handle c-frame-edit__handle--nwse"
ng-style="handle.style()"
mct-drag-down="handle.startDrag()"
mct-drag="handle.continueDrag(delta)"

View File

@@ -19,10 +19,10 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<a class="l-hyperlink s-hyperlink" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
<a class="c-hyperlink u-links" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
ng-attr-target="{{hyperlink.openNewTab() ? '_blank' : undefined}}"
ng-class="{
's-button': hyperlink.isButton()
}">
<span class="label">{{domainObject.getModel().displayText}}</span>
'c-hyperlink--button u-fills-container' : hyperlink.isButton(),
'c-hyperlink--link' : !hyperlink.isButton() }">
<span class="c-hyperlink__label">{{domainObject.getModel().displayText}}</span>
</a>

View File

@@ -1,356 +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/LayoutController",
"./src/FixedController",
"./src/LayoutCompositionPolicy",
'./src/MCTTriggerModal',
"./res/templates/layout.html",
"./res/templates/fixed.html",
"./res/templates/frame.html",
"./res/templates/elements/telemetry.html",
"./res/templates/elements/box.html",
"./res/templates/elements/line.html",
"./res/templates/elements/text.html",
"./res/templates/elements/image.html",
'legacyRegistry'
], function (
LayoutController,
FixedController,
LayoutCompositionPolicy,
MCTTriggerModal,
layoutTemplate,
fixedTemplate,
frameTemplate,
telemetryTemplate,
boxTemplate,
lineTemplate,
textTemplate,
imageTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/layout", {
"name": "Layout components.",
"description": "Plug in adding Layout capabilities.",
"extensions": {
"views": [
{
"key": "layout",
"name": "Display Layout",
"cssClass": "icon-layout",
"type": "layout",
"template": layoutTemplate,
"editable": true,
"uses": []
},
{
"key": "fixed",
"name": "Fixed Position",
"cssClass": "icon-box-with-dashed-lines",
"type": "telemetry.panel",
"template": fixedTemplate,
"uses": [
"composition"
],
"toolbar": {
"sections": [
{
"items": [
{
"method": "add",
"cssClass": "icon-plus",
"control": "menu-button",
"text": "Add",
"title": "Add",
"description": "Add new items",
"options": [
{
"name": "Box",
"cssClass": "icon-box",
"key": "fixed.box"
},
{
"name": "Line",
"cssClass": "icon-line-horz",
"key": "fixed.line"
},
{
"name": "Text",
"cssClass": "icon-T",
"key": "fixed.text"
},
{
"name": "Image",
"cssClass": "icon-image",
"key": "fixed.image"
}
]
}
]
},
{
"items": [
{
"method": "order",
"cssClass": "icon-layers",
"control": "menu-button",
"title": "Layering",
"description": "Move the selected object above or below other objects",
"options": [
{
"name": "Move to Top",
"cssClass": "icon-arrow-double-up",
"key": "top"
},
{
"name": "Move Up",
"cssClass": "icon-arrow-up",
"key": "up"
},
{
"name": "Move Down",
"cssClass": "icon-arrow-down",
"key": "down"
},
{
"name": "Move to Bottom",
"cssClass": "icon-arrow-double-down",
"key": "bottom"
}
]
},
{
"property": "fill",
"cssClass": "icon-paint-bucket",
"title": "Fill color",
"description": "Set fill color",
"control": "color"
},
{
"property": "stroke",
"cssClass": "icon-line-horz",
"title": "Border color",
"description": "Set border color",
"control": "color"
},
{
"property": "color",
"cssClass": "icon-T",
"title": "Text color",
"description": "Set text color",
"mandatory": true,
"control": "color"
},
{
"property": "url",
"cssClass": "icon-image",
"control": "dialog-button",
"title": "Image Properties",
"description": "Edit image properties",
"dialog": {
"control": "textfield",
"name": "Image URL",
"cssClass": "l-input-lg",
"required": true
}
},
{
"property": "text",
"cssClass": "icon-gear",
"control": "dialog-button",
"title": "Text Properties",
"description": "Edit text properties",
"dialog": {
"control": "textfield",
"name": "Text",
"required": true
}
},
{
"method": "showTitle",
"cssClass": "icon-two-parts-both",
"control": "button",
"title": "Show title",
"description": "Show telemetry element title"
},
{
"method": "hideTitle",
"cssClass": "icon-two-parts-one-only",
"control": "button",
"title": "Hide title",
"description": "Hide telemetry element title"
}
]
},
{
"items": [
{
"method": "remove",
"control": "button",
"cssClass": "icon-trash",
"title": "Delete",
"description": "Delete the selected item"
}
]
}
]
}
}
],
"representations": [
{
"key": "frame",
"template": frameTemplate
}
],
"directives": [
{
"key": "mctTriggerModal",
"implementation": MCTTriggerModal,
"depends": [
"$document"
]
}
],
"controllers": [
{
"key": "LayoutController",
"implementation": LayoutController,
"depends": [
"$scope",
"$element",
"openmct"
]
},
{
"key": "FixedController",
"implementation": FixedController,
"depends": [
"$scope",
"$q",
"dialogService",
"openmct",
"$element"
]
}
],
"templates": [
{
"key": "fixed.telemetry",
"template": telemetryTemplate
},
{
"key": "fixed.box",
"template": boxTemplate
},
{
"key": "fixed.line",
"template": lineTemplate
},
{
"key": "fixed.text",
"template": textTemplate
},
{
"key": "fixed.image",
"template": imageTemplate
}
],
"policies": [
{
"category": "composition",
"implementation": LayoutCompositionPolicy
}
],
"toolbars": [
{
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.
return (selection && selection[1] && selection[1].context.item.type === 'layout');
},
toolbar: function (selection) {
return [
{
control: "checkbox",
name: "Show frame",
domainObject: selection[1].context.item,
property: "configuration.layout.panels[" + selection[0].context.oldItem.id + "].hasFrame"
}
];
}
}
],
"types": [
{
"key": "layout",
"name": "Display Layout",
"cssClass": "icon-layout",
"description": "Assemble other objects and components together into a reusable screen layout. Working in a simple canvas workspace, simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.",
"priority": 900,
"features": "creation",
"model": {
"composition": [],
configuration: {
layout: {
panels: {
}
}
}
},
"properties": [
{
"name": "Layout Grid",
"control": "composite",
"pattern": "^(\\d*[1-9]\\d*)?$",
"items": [
{
"name": "Horizontal grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
},
{
"name": "Vertical grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
}
],
"key": "layoutGrid",
"conversion": "number[]"
},
{
"name": "Advanced",
"control": "textfield",
"key": "layoutAdvancedCss",
"cssClass": "l-input-lg"
}
]
}
]
}
});
});

View File

@@ -1,83 +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.
-->
<div class="abs l-layout {{ domainObject.getModel().layoutAdvancedCss }}"
ng-controller="LayoutController as controller"
ng-click="controller.bypassSelection($event)">
<!-- Background grid -->
<div class="l-grid-holder"
ng-show="!controller.drilledIn"
ng-click="controller.bypassSelection($event)">
<div class="l-grid l-grid-x"
ng-if="!controller.getGridSize()[0] < 3"
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
<div class="l-grid l-grid-y"
ng-if="!controller.getGridSize()[1] < 3"
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
</div>
<div class="frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border t-object-type-{{ childObject.getModel().type }}"
data-layout-id="{{childObject.getId() + '-' + $id}}"
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
ng-repeat="childObject in composition"
ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
mct-selectable="controller.getContext(childObject)"
ng-dblclick="controller.drill($event, childObject)"
ng-style="controller.getFrameStyle(childObject.getId())">
<mct-representation key="'frame'"
class="t-rep-frame holder contents abs"
mct-object="childObject">
</mct-representation>
<!-- Drag handles -->
<span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject) && !controller.isDrilledIn(childObject)">
<span class="edit-handle edit-move"
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span class="edit-corner edit-resize-nw"
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [-1,-1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span class="edit-corner edit-resize-ne"
mct-drag-down="controller.startDrag(childObject.getId(), [0,1], [1,-1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span class="edit-corner edit-resize-sw"
mct-drag-down="controller.startDrag(childObject.getId(), [1,0], [-1,1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span class="edit-corner edit-resize-se"
mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [1,1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
</span>
</div>
</div>

View File

@@ -1,524 +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.
*****************************************************************************/
/**
* This bundle implements object types and associated views for
* display-building.
* @namespace platform/features/layout
*/
define(
[
'zepto',
'./LayoutDrag'
],
function (
$,
LayoutDrag
) {
var DEFAULT_DIMENSIONS = [12, 8],
DEFAULT_GRID_SIZE = [32, 32],
MINIMUM_FRAME_SIZE = [320, 180];
var DEFAULT_HIDDEN_FRAME_TYPES = [
'hyperlink'
];
/**
* The LayoutController is responsible for supporting the
* Layout view. It arranges frames according to saved configuration
* and provides methods for updating these based on mouse
* movement.
* @memberof platform/features/layout
* @constructor
* @param {Scope} $scope the controller's Angular scope
*/
function LayoutController($scope, $element, openmct) {
var self = this,
callbackCount = 0;
this.$element = $element;
// Update grid size when it changed
function updateGridSize(layoutGrid) {
var oldSize = self.gridSize;
self.gridSize = layoutGrid || DEFAULT_GRID_SIZE;
// Only update panel positions if this actually changed things
if (self.gridSize[0] !== oldSize[0] ||
self.gridSize[1] !== oldSize[1]) {
self.layoutPanels(Object.keys(self.positions));
}
}
// Position a panel after a drop event
function handleDrop(e, id, position) {
if (e.defaultPrevented) {
return;
}
$scope.configuration = $scope.configuration || {};
$scope.configuration.panels = $scope.configuration.panels || {};
self.openmct.objects.get(id).then(function (object) {
$scope.configuration.panels[id] = {
position: [
Math.floor(position.x / self.gridSize[0]),
Math.floor(position.y / self.gridSize[1])
],
dimensions: self.defaultDimensions(),
hasFrame: self.getDefaultFrame(object.type)
};
// Store the id so that the newly-dropped object
// gets selected during refresh composition
self.droppedIdToSelectAfterRefresh = id;
self.commit();
// Populate template-facing position for this id
self.rawPositions[id] = $scope.configuration.panels[id];
self.populatePosition(id);
refreshComposition();
});
// Layout may contain embedded views which will
// listen for drops, so call preventDefault() so
// that they can recognize that this event is handled.
e.preventDefault();
}
//Will fetch fully contextualized composed objects, and populate
// scope with them.
function refreshComposition() {
//Keep a track of how many composition callbacks have been made
var thisCount = ++callbackCount;
$scope.domainObject.useCapability('composition').then(function (composition) {
var ids;
//Is this callback for the most recent composition
// request? If not, discard it. Prevents race condition
if (thisCount === callbackCount) {
ids = composition.map(function (object) {
return object.getId();
}) || [];
$scope.composition = composition;
self.layoutPanels(ids);
self.setFrames(ids);
if (self.selectedId &&
self.selectedId !== $scope.domainObject.getId() &&
composition.indexOf(self.selectedId) === -1) {
// Click triggers selection of layout parent.
self.$element[0].click();
}
}
});
}
// End drag; we don't want to put $scope into this
// because it triggers "cpws" (copy window or scope)
// errors in Angular.
this.endDragInScope = function () {
// Write to configuration; this is watched and
// saved by the EditRepresenter.
$scope.configuration =
$scope.configuration || {};
$scope.configuration.panels =
$scope.configuration.panels || {};
$scope.configuration.panels[self.activeDragId] =
$scope.configuration.panels[self.activeDragId] || {};
$scope.configuration.panels[self.activeDragId].position =
self.rawPositions[self.activeDragId].position;
$scope.configuration.panels[self.activeDragId].dimensions =
self.rawPositions[self.activeDragId].dimensions;
self.commit();
};
// Sets the selectable object in response to the selection change event.
function setSelection(selectable) {
var selection = selectable[0];
if (!selection) {
delete self.selectedId;
return;
}
self.selectedId = selection.context.oldItem.getId();
self.drilledIn = undefined;
self.selectable = selectable;
}
this.positions = {};
this.rawPositions = {};
this.gridSize = DEFAULT_GRID_SIZE;
this.$scope = $scope;
this.drilledIn = undefined;
this.openmct = openmct;
// Watch for changes to the grid size in the model
$scope.$watch("model.layoutGrid", updateGridSize);
// Update composed objects on screen, and position panes
$scope.$watchCollection("model.composition", refreshComposition);
openmct.selection.on('change', setSelection);
$scope.$on("$destroy", function () {
openmct.selection.off("change", setSelection);
self.unlisten();
});
$scope.$on("mctDrop", handleDrop);
self.unlisten = self.$scope.domainObject.getCapability('mutation').listen(function (model) {
$scope.configuration = model.configuration.layout;
$scope.model = model;
var panels = $scope.configuration.panels;
Object.keys(panels).forEach(function (key) {
if (self.frames && self.frames.hasOwnProperty(key)) {
self.frames[key] = panels[key].hasFrame;
}
});
});
}
// Utility function to copy raw positions from configuration,
// without writing directly to configuration (to avoid triggering
// persistence from watchers during drags).
function shallowCopy(obj, keys) {
var copy = {};
keys.forEach(function (k) {
copy[k] = obj[k];
});
return copy;
}
/**
* Set the frames value. If a configuration panel has "hasFrame' property,
* use that value, otherwise set a default value. A 'hyperlink' object should
* have no frame by default.
*
* @param {string[]} ids the object ids
* @private
*/
LayoutController.prototype.setFrames = function (ids) {
var panels = shallowCopy(this.$scope.configuration.panels || {}, ids);
this.frames = {};
this.$scope.composition.forEach(function (object) {
var id = object.getId();
panels[id] = panels[id] || {};
if (panels[id].hasOwnProperty('hasFrame')) {
this.frames[id] = panels[id].hasFrame;
} else {
this.frames[id] = this.getDefaultFrame(object.getModel().type);
}
}, this);
};
/**
* Gets the default value for frame.
*
* @param type the domain object type
* @return {boolean} true if the object should have
* frame by default, false, otherwise
*/
LayoutController.prototype.getDefaultFrame = function (type) {
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
};
// Convert from { positions: ..., dimensions: ... } to an
// appropriate ng-style argument, to position frames.
LayoutController.prototype.convertPosition = function (raw) {
var gridSize = this.gridSize;
// Multiply position/dimensions by grid size
return {
left: (gridSize[0] * raw.position[0]) + 'px',
top: (gridSize[1] * raw.position[1]) + 'px',
width: (gridSize[0] * raw.dimensions[0]) + 'px',
height: (gridSize[1] * raw.dimensions[1]) + 'px',
minWidth: (gridSize[0] * raw.dimensions[0]) + 'px',
minHeight: (gridSize[1] * raw.dimensions[1]) + 'px'
};
};
// Generate default positions for a new panel
LayoutController.prototype.defaultDimensions = function () {
var gridSize = this.gridSize;
return MINIMUM_FRAME_SIZE.map(function (min, i) {
return Math.max(
Math.ceil(min / gridSize[i]),
DEFAULT_DIMENSIONS[i]
);
});
};
// Generate a default position (in its raw format) for a frame.
// Use an index to ensure that default positions are unique.
LayoutController.prototype.defaultPosition = function (index) {
return {
position: [index, index],
dimensions: this.defaultDimensions()
};
};
// Store a computed position for a contained frame by its
// domain object id. Called in a forEach loop, so arguments
// are as expected there.
LayoutController.prototype.populatePosition = function (id, index) {
this.rawPositions[id] =
this.rawPositions[id] || this.defaultPosition(index || 0);
this.positions[id] =
this.convertPosition(this.rawPositions[id]);
};
/**
* Get a style object for a frame with the specified domain
* object identifier, suitable for use in an `ng-style`
* directive to position a frame as configured for this layout.
* @param {string} id the object identifier
* @returns {Object.<string, string>} an object with
* appropriate left, width, etc fields for positioning
*/
LayoutController.prototype.getFrameStyle = function (id) {
// Called in a loop, so just look up; the "positions"
// object is kept up to date by a watch.
return this.positions[id];
};
/**
* Start a drag gesture to move/resize a frame.
*
* The provided position and dimensions factors will determine
* whether this is a move or a resize, and what type it
* will be. For instance, a position factor of [1, 1]
* will move a frame along with the mouse as the drag
* proceeds, while a dimension factor of [0, 0] will leave
* dimensions unchanged. Combining these in different
* ways results in different handles; a position factor of
* [1, 0] and a dimensions factor of [-1, 0] will implement
* a left-edge resize, as the horizontal position will move
* with the mouse while the horizontal dimensions shrink in
* kind (and vertical properties remain unmodified.)
*
* @param {string} id the identifier of the domain object
* in the frame being manipulated
* @param {number[]} posFactor the position factor
* @param {number[]} dimFactor the dimensions factor
*/
LayoutController.prototype.startDrag = function (id, posFactor, dimFactor) {
this.activeDragId = id;
this.activeDrag = new LayoutDrag(
this.rawPositions[id],
posFactor,
dimFactor,
this.gridSize
);
};
/**
* Continue an active drag gesture.
* @param {number[]} delta the offset, in pixels,
* of the current pointer position, relative
* to its position when the drag started
*/
LayoutController.prototype.continueDrag = function (delta) {
if (this.activeDrag) {
this.rawPositions[this.activeDragId] =
this.activeDrag.getAdjustedPosition(delta);
this.populatePosition(this.activeDragId);
}
};
/**
* Compute panel positions based on the layout's object model.
* Defined as member function to facilitate testing.
* @private
*/
LayoutController.prototype.layoutPanels = function (ids) {
var configuration = this.$scope.configuration || {},
self = this;
// Pull panel positions from configuration
this.rawPositions =
shallowCopy(configuration.panels || {}, ids);
// Clear prior computed positions
this.positions = {};
// Update width/height that we are tracking
this.gridSize =
(this.$scope.model || {}).layoutGrid || DEFAULT_GRID_SIZE;
// Compute positions and add defaults where needed
ids.forEach(function (id, index) {
self.populatePosition(id, index);
});
};
/**
* End the active drag gesture. This will update the
* view configuration.
*/
LayoutController.prototype.endDrag = function () {
this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
this.endDragInScope();
};
/**
* Checks if the object is currently selected.
*
* @param {string} obj the object to check for selection
* @returns {boolean} true if selected, otherwise false
*/
LayoutController.prototype.selected = function (obj) {
var sobj = this.openmct.selection.get()[0];
return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
};
/**
* Bypasses selection if drag is in progress.
*
* @param event the angular event object
*/
LayoutController.prototype.bypassSelection = function (event) {
if (this.dragInProgress) {
if (event) {
event.stopPropagation();
}
return;
}
};
/**
* Checks if the domain object is drilled in.
*
* @param domainObject the domain object
* @return true if the object is drilled in, false otherwise
*/
LayoutController.prototype.isDrilledIn = function (domainObject) {
return this.drilledIn === domainObject.getId();
};
/**
* Puts the given object in the drilled-in mode.
*
* @param event the angular event object
* @param domainObject the domain object
*/
LayoutController.prototype.drill = function (event, domainObject) {
if (event) {
event.stopPropagation();
}
if (!domainObject.getCapability('editor').inEditContext()) {
return;
}
if (!domainObject.hasCapability('composition')) {
return;
}
// Disable since fixed position doesn't use the selection API yet
if (domainObject.getModel().type === 'telemetry.fixed') {
return;
}
this.drilledIn = domainObject.getId();
};
/**
* Check if the object has frame.
*
* @param {object} obj the object
* @return {boolean} true if object has frame, otherwise false
*/
LayoutController.prototype.hasFrame = function (obj) {
return this.frames[obj.getId()];
};
/**
* Get the size of the grid, in pixels. The returned array
* is in the form `[x, y]`.
* @returns {number[]} the grid size
*/
LayoutController.prototype.getGridSize = function () {
return this.gridSize;
};
/**
* Gets the selection context.
*
* @param domainObject the domain object
* @returns {object} the context object which includes item and oldItem
*/
LayoutController.prototype.getContext = function (domainObject) {
return {
item: domainObject.useCapability('adapter'),
oldItem: domainObject
};
};
LayoutController.prototype.commit = function () {
var model = this.$scope.model;
model.configuration = model.configuration || {};
model.configuration.layout = this.$scope.configuration;
this.$scope.domainObject.useCapability('mutation', function () {
return model;
});
};
/**
* Selects a newly-dropped object.
*
* @param classSelector the css class selector
* @param domainObject the domain object
*/
LayoutController.prototype.selectIfNew = function (selector, domainObject) {
if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) {
setTimeout(function () {
$('[data-layout-id="' + selector + '"]')[0].click();
delete this.droppedIdToSelectAfterRefresh;
}.bind(this), 0);
}
};
return LayoutController;
}
);

View File

@@ -1,82 +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/LayoutCompositionPolicy"],
function (LayoutCompositionPolicy) {
describe("Layout's composition policy", function () {
var mockChild,
mockCandidateObj,
mockCandidate,
mockContext,
candidateType,
contextType,
policy;
beforeEach(function () {
mockChild = jasmine.createSpyObj(
'childObject',
['getCapability']
);
mockCandidate =
jasmine.createSpyObj('candidateType', ['instanceOf']);
mockContext =
jasmine.createSpyObj('contextType', ['instanceOf']);
mockCandidateObj = jasmine.createSpyObj('domainObj', [
'getCapability'
]);
mockCandidateObj.getCapability.and.returnValue(mockCandidate);
mockChild.getCapability.and.returnValue(mockContext);
mockCandidate.instanceOf.and.callFake(function (t) {
return t === candidateType;
});
mockContext.instanceOf.and.callFake(function (t) {
return t === contextType;
});
policy = new LayoutCompositionPolicy();
});
it("disallows folders in layouts", function () {
candidateType = 'layout';
contextType = 'folder';
expect(policy.allow(mockCandidateObj, mockChild)).toBe(false);
});
it("does not disallow folders elsewhere", function () {
candidateType = 'nonlayout';
contextType = 'folder';
expect(policy.allow(mockCandidateObj, mockChild)).toBe(true);
});
it("allows things other than folders in layouts", function () {
candidateType = 'layout';
contextType = 'nonfolder';
expect(policy.allow(mockCandidateObj, mockChild)).toBe(true);
});
});
}
);

View File

@@ -1,479 +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/LayoutController",
"zepto"
],
function (
LayoutController,
$
) {
describe("The Layout controller", function () {
var mockScope,
mockEvent,
testModel,
testConfiguration,
controller,
mockCompositionCapability,
mockComposition,
mockCompositionObjects,
mockOpenMCT,
mockSelection,
mockDomainObjectCapability,
mockObjects,
unlistenFunc,
$element = [],
selectable = [];
function mockPromise(value) {
return {
then: function (thenFunc) {
return mockPromise(thenFunc(value));
}
};
}
function mockDomainObject(id) {
return {
getId: function () {
return id;
},
useCapability: function () {
return mockCompositionCapability;
},
getModel: function () {
if (id === 'b') {
return {
type : 'hyperlink'
};
} else {
return {};
}
},
getCapability: function () {
return mockDomainObjectCapability;
},
hasCapability: function (param) {
if (param === 'composition') {
return id !== 'b';
}
},
type: "testType"
};
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
["$watch", "$watchCollection", "$on"]
);
mockEvent = jasmine.createSpyObj(
'event',
['preventDefault', 'stopPropagation']
);
testModel = {};
mockComposition = ["a", "b", "c"];
mockCompositionObjects = mockComposition.map(mockDomainObject);
testConfiguration = {
panels: {
a: {
position: [20, 10],
dimensions: [5, 5]
}
}
};
unlistenFunc = jasmine.createSpy("unlisten");
mockDomainObjectCapability = jasmine.createSpyObj('capability',
['inEditContext', 'listen']
);
mockDomainObjectCapability.listen.and.returnValue(unlistenFunc);
mockCompositionCapability = mockPromise(mockCompositionObjects);
mockScope.domainObject = mockDomainObject("mockDomainObject");
mockScope.model = testModel;
mockScope.configuration = testConfiguration;
selectable[0] = {
context: {
oldItem: mockScope.domainObject
}
};
mockSelection = jasmine.createSpyObj("selection", [
'select',
'on',
'off',
'get'
]);
mockSelection.get.and.returnValue(selectable);
mockObjects = jasmine.createSpyObj('objects', [
'get'
]);
mockObjects.get.and.returnValue(mockPromise(mockDomainObject("mockObject")));
mockOpenMCT = {
selection: mockSelection,
objects: mockObjects
};
$element = $('<div></div>');
$(document).find('body').append($element);
spyOn($element[0], 'click');
spyOn(mockScope.domainObject, "useCapability").and.callThrough();
controller = new LayoutController(mockScope, $element, mockOpenMCT);
spyOn(controller, "layoutPanels").and.callThrough();
spyOn(controller, "commit");
jasmine.clock().install();
});
afterEach(function () {
$element.remove();
jasmine.clock().uninstall();
});
it("listens for selection change events", function () {
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
it("cleans up on scope destroy", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
'$destroy',
jasmine.any(Function)
);
mockScope.$on.calls.all()[0].args[1]();
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
// Model changes will indicate that panel positions
// may have changed, for instance.
it("watches for changes to composition", function () {
expect(mockScope.$watchCollection).toHaveBeenCalledWith(
"model.composition",
jasmine.any(Function)
);
});
it("Retrieves updated composition from composition capability", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
expect(mockScope.domainObject.useCapability).toHaveBeenCalledWith(
"composition"
);
expect(controller.layoutPanels).toHaveBeenCalledWith(
mockComposition
);
});
it("Is robust to concurrent changes to composition", function () {
var secondMockComposition = ["a", "b", "c", "d"],
secondMockCompositionObjects = secondMockComposition.map(mockDomainObject),
firstCompositionCB,
secondCompositionCB;
spyOn(mockCompositionCapability, "then");
mockScope.$watchCollection.calls.mostRecent().args[1]();
mockScope.$watchCollection.calls.mostRecent().args[1]();
firstCompositionCB = mockCompositionCapability.then.calls.all()[0].args[0];
secondCompositionCB = mockCompositionCapability.then.calls.all()[1].args[0];
//Resolve promises in reverse order
secondCompositionCB(secondMockCompositionObjects);
firstCompositionCB(mockCompositionObjects);
//Expect the promise call that was initiated most recently to
// be the one used to populate scope, irrespective of order that
// it was eventually resolved
expect(mockScope.composition).toBe(secondMockCompositionObjects);
});
it("provides styles for frames, from configuration", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
expect(controller.getFrameStyle("a")).toEqual({
top: "320px",
left: "640px",
width: "160px",
height: "160px",
minWidth : '160px',
minHeight : '160px'
});
});
it("provides default styles for frames", function () {
var styleB, styleC;
// b and c do not have configured positions
mockScope.$watchCollection.calls.mostRecent().args[1]();
styleB = controller.getFrameStyle("b");
styleC = controller.getFrameStyle("c");
// Should have a position, but we don't care what
expect(styleB.left).toBeDefined();
expect(styleB.top).toBeDefined();
expect(styleC.left).toBeDefined();
expect(styleC.top).toBeDefined();
// Should have ensured some difference in position
expect(styleB).not.toEqual(styleC);
});
it("allows panels to be dragged", function () {
// Populate scope
mockScope.$watchCollection.calls.mostRecent().args[1]();
// Verify precondition
expect(testConfiguration.panels.b).not.toBeDefined();
// Do a drag
controller.startDrag("b", [1, 1], [0, 0]);
controller.continueDrag([100, 100]);
controller.endDrag();
// We do not look closely at the details here;
// that is tested in LayoutDragSpec. Just make sure
// that a configuration for b has been defined.
expect(testConfiguration.panels.b).toBeDefined();
});
it("invokes commit after drag", function () {
// Populate scope
mockScope.$watchCollection.calls.mostRecent().args[1]();
// Do a drag
controller.startDrag("b", [1, 1], [0, 0]);
controller.continueDrag([100, 100]);
controller.endDrag();
expect(controller.commit).toHaveBeenCalled();
});
it("listens for drop events", function () {
// Layout should position panels according to
// where the user dropped them, so it needs to
// listen for drop events.
expect(mockScope.$on).toHaveBeenCalledWith(
'mctDrop',
jasmine.any(Function)
);
// Verify precondition
expect(testConfiguration.panels.d).not.toBeDefined();
// Notify that a drop occurred
mockScope.$on.calls.mostRecent().args[1](
mockEvent,
'd',
{ x: 300, y: 100 }
);
expect(testConfiguration.panels.d).toBeDefined();
expect(mockEvent.preventDefault).toHaveBeenCalled();
expect(controller.commit).toHaveBeenCalled();
});
it("ignores drops when default has been prevented", function () {
// Avoids redundant drop-handling, WTD-1233
mockEvent.defaultPrevented = true;
// Notify that a drop occurred
mockScope.$on.calls.mostRecent().args[1](
mockEvent,
'd',
{ x: 300, y: 100 }
);
expect(testConfiguration.panels.d).not.toBeDefined();
});
it("ensures a minimum frame size", function () {
var styleB;
// Start with a very small frame size
testModel.layoutGrid = [1, 1];
// White-boxy; we know which watch is which
mockScope.$watch.calls.all()[0].args[1](testModel.layoutGrid);
mockScope.$watchCollection.calls.all()[0].args[1](testModel.composition);
styleB = controller.getFrameStyle("b");
// Resulting size should still be reasonably large pixel-size
expect(parseInt(styleB.width, 10)).toBeGreaterThan(63);
expect(parseInt(styleB.width, 10)).toBeGreaterThan(31);
});
it("ensures a minimum frame size on drop", function () {
var style;
// Start with a very small frame size
testModel.layoutGrid = [1, 1];
mockScope.$watch.calls.all()[0].args[1](testModel.layoutGrid);
// Add a new object to the composition
mockComposition = ["a", "b", "c", "d"];
mockCompositionObjects = mockComposition.map(mockDomainObject);
mockCompositionCapability = mockPromise(mockCompositionObjects);
// Notify that a drop occurred
mockScope.$on.calls.mostRecent().args[1](
mockEvent,
'd',
{ x: 300, y: 100 }
);
style = controller.getFrameStyle("d");
// Resulting size should still be reasonably large pixel-size
expect(parseInt(style.width, 10)).toBeGreaterThan(63);
expect(parseInt(style.height, 10)).toBeGreaterThan(31);
});
it("updates positions of existing objects on a drop", function () {
var oldStyle;
mockScope.$watchCollection.calls.mostRecent().args[1]();
oldStyle = controller.getFrameStyle("b");
expect(oldStyle).toBeDefined();
// ...drop event...
mockScope.$on.calls.mostRecent()
.args[1](mockEvent, 'b', { x: 300, y: 100 });
expect(controller.getFrameStyle("b"))
.not.toEqual(oldStyle);
});
it("allows objects to be selected", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
var childObj = mockCompositionObjects[0];
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
expect(controller.selected(childObj)).toBe(true);
});
it("prevents event bubbling while drag is in progress", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
var childObj = mockCompositionObjects[0];
// Do a drag
controller.startDrag(childObj.getId(), [1, 1], [0, 0]);
controller.continueDrag([100, 100]);
controller.endDrag();
// Because mouse position could cause the parent object to be selected, this should be ignored.
controller.bypassSelection(mockEvent);
expect(mockEvent.stopPropagation).toHaveBeenCalled();
// Shoud be able to select another object when dragging is done.
jasmine.clock().tick(0);
mockEvent.stopPropagation.calls.reset();
controller.bypassSelection(mockEvent);
expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
});
it("shows frames by default", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
expect(controller.hasFrame(mockCompositionObjects[0])).toBe(true);
});
it("hyperlinks hide frame by default", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
expect(controller.hasFrame(mockCompositionObjects[1])).toBe(false);
});
it("selects the parent object when selected object is removed", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
var childObj = mockCompositionObjects[0];
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
var composition = ["b", "c"];
mockScope.$watchCollection.calls.mostRecent().args[1](composition);
expect($element[0].click).toHaveBeenCalled();
});
it("allows objects to be drilled-in only when editing", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
var childObj = mockCompositionObjects[0];
childObj.getCapability().inEditContext.and.returnValue(false);
controller.drill(mockEvent, childObj);
expect(controller.isDrilledIn(childObj)).toBe(false);
});
it("allows objects to be drilled-in only if it has sub objects", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
var childObj = mockCompositionObjects[1];
childObj.getCapability().inEditContext.and.returnValue(true);
controller.drill(mockEvent, childObj);
expect(controller.isDrilledIn(childObj)).toBe(false);
});
it("selects a newly-dropped object", function () {
mockScope.$on.calls.mostRecent().args[1](
mockEvent,
'd',
{ x: 300, y: 100 }
);
var childObj = mockDomainObject("d");
var testElement = $("<div data-layout-id='some-id'></div>");
$element.append(testElement);
spyOn(testElement[0], 'click');
controller.selectIfNew('some-id', childObj);
jasmine.clock().tick(0);
expect(testElement[0].click).toHaveBeenCalled();
});
});
}
);

View File

@@ -22,7 +22,6 @@
define([
"./src/MCTForm",
"./src/MCTToolbar",
"./src/MCTControl",
"./src/MCTFileInput",
"./src/FileInputService",
@@ -48,7 +47,6 @@ define([
'legacyRegistry'
], function (
MCTForm,
MCTToolbar,
MCTControl,
MCTFileInput,
FileInputService,
@@ -83,10 +81,6 @@ define([
"key": "mctForm",
"implementation": MCTForm
},
{
"key": "mctToolbar",
"implementation": MCTToolbar
},
{
"key": "mctControl",
"implementation": MCTControl,

View File

@@ -1,49 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2018, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<form novalidate>
<div class="tool-bar btn-bar contents abs">
<span ng-repeat="item in structure">
<span ng-if="item.control === 'divider'" class="l-control-group">
</span>
<ng-form ng-class="{ 'input-labeled': item.name }"
ng-hide="item.hidden"
ng-if="item.control !== 'divider'"
class="inline"
title="{{item.description}}"
name="mctFormInner">
<label ng-if="item.name">
{{item.name}}:
</label>
<mct-control key="item.control"
ng-class="{ disabled: item.disabled }"
ng-model="ngModel"
ng-required="item.required"
ng-pattern="getRegExp(item.pattern)"
options="item.options"
structure="item"
field="item.key">
</mct-control>
</ng-form>
</span>
</div>
</form>

View File

@@ -1,69 +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 MCTForm. Created by vwoeltje on 11/10/14.
*/
define(
[
"./MCTForm",
"../res/templates/toolbar.html",
"./controllers/ToolbarController"
],
function (
MCTForm,
toolbarTemplate,
ToolbarController
) {
/**
* The mct-toolbar directive allows generation of displayable
* forms based on a declarative description of the form's
* structure.
*
* This directive accepts three attributes:
*
* * `ng-model`: The model for the form; where user input
* will be stored.
* * `structure`: The declarative structure of the toolbar.
* Describes what controls should be shown and where
* their values should be read/written in the model.
* * `name`: The name under which to expose the form's
* dirty/valid state. This is similar to ng-form's use
* of name, except this will be made available in the
* parent scope.
*
* @memberof platform/forms
* @constructor
*/
function MCTToolbar() {
// Use Directive Definition Object from mct-form,
// but use the toolbar's template and controller instead.
var ddo = new MCTForm();
ddo.template = toolbarTemplate;
ddo.controller = ['$scope', 'openmct', ToolbarController];
return ddo;
}
return MCTToolbar;
}
);

View File

@@ -1,84 +0,0 @@
define(
[
'../../../commonUI/edit/src/representers/EditToolbar'
],
function (EditToolbar) {
// Default ng-pattern; any non whitespace
var NON_WHITESPACE = /\S/;
/**
* Controller for mct-toolbar directive.
*
* @memberof platform/forms
* @constructor
*/
function ToolbarController($scope, openmct) {
var regexps = [];
// ng-pattern seems to want a RegExp, and not a
// string (despite what documentation says) but
// we want toolbar structure to be JSON-expressible,
// so we make RegExp's from strings as-needed
function getRegExp(pattern) {
// If undefined, don't apply a pattern
if (!pattern) {
return NON_WHITESPACE;
}
// Just echo if it's already a regexp
if (pattern instanceof RegExp) {
return pattern;
}
// Otherwise, assume a string
// Cache for easy lookup later (so we don't
// creat a new RegExp every digest cycle)
if (!regexps[pattern]) {
regexps[pattern] = new RegExp(pattern);
}
return regexps[pattern];
}
this.openmct = openmct;
this.$scope = $scope;
$scope.editToolbar = {};
$scope.getRegExp = getRegExp;
$scope.$on("$destroy", this.destroy.bind(this));
openmct.selection.on('change', this.handleSelection.bind(this));
}
ToolbarController.prototype.handleSelection = function (selection) {
var domainObject = selection[0].context.oldItem;
var element = selection[0].context.elementProxy;
if ((domainObject && domainObject === this.selectedObject) || (element && element === this.selectedObject)) {
return;
}
this.selectedObject = domainObject || element;
if (this.editToolbar) {
this.editToolbar.destroy();
}
var structure = this.openmct.toolbars.get(selection) || [];
this.editToolbar = new EditToolbar(this.$scope, this.openmct, structure);
this.$scope.$parent.editToolbar = this.editToolbar;
this.$scope.$parent.editToolbar.structure = this.editToolbar.getStructure();
this.$scope.$parent.editToolbar.state = this.editToolbar.getState();
setTimeout(function () {
this.$scope.$apply();
}.bind(this));
};
ToolbarController.prototype.destroy = function () {
this.openmct.selection.off("change", this.handleSelection);
};
return ToolbarController;
}
);

View File

@@ -1,112 +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/MCTToolbar"],
function (MCTToolbar) {
describe("The mct-toolbar directive", function () {
var mockScope,
mockOpenMCT,
mockSelection,
mctToolbar;
function installController() {
var Controller = mctToolbar.controller[2];
return new Controller(mockScope, mockOpenMCT);
}
beforeEach(function () {
mockScope = jasmine.createSpyObj("$scope", [
"$watch",
"$on"
]);
mockScope.$parent = {};
mockSelection = jasmine.createSpyObj("selection", [
'on',
'off'
]);
mockOpenMCT = {
selection: mockSelection
};
mctToolbar = new MCTToolbar();
});
it("is restricted to elements", function () {
expect(mctToolbar.restrict).toEqual("E");
});
it("listens for selection change event", function () {
installController();
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
"change",
jasmine.any(Function)
);
});
it("allows strings to be converted to RegExps", function () {
// This is needed to support ng-pattern in the template
installController();
// Should have added getRegExp to the scope,
// to convert strings to regular expressions
expect(mockScope.getRegExp("^\\d+$")).toEqual(/^\d+$/);
});
it("returns the same regexp instance for the same string", function () {
// Don't want new instances each digest cycle, for performance
var strRegExp = "^[a-z]\\d+$",
regExp;
// Add getRegExp to scope
installController();
regExp = mockScope.getRegExp(strRegExp);
// Same object instance each time...
expect(mockScope.getRegExp(strRegExp)).toBe(regExp);
expect(mockScope.getRegExp(strRegExp)).toBe(regExp);
});
it("passes RegExp objects through untouched", function () {
// Permit using forms to simply provide their own RegExp object
var regExp = /^\d+[a-d]$/;
// Add getRegExp to scope
installController();
// Should have added getRegExp to the scope,
// to convert strings to regular expressions
expect(mockScope.getRegExp(regExp)).toBe(regExp);
});
it("passes a non-whitespace regexp when no pattern is defined", function () {
// If no pattern is supplied, ng-pattern should match anything
installController();
expect(mockScope.getRegExp()).toEqual(/\S/);
expect(mockScope.getRegExp(undefined)).toEqual(/\S/);
});
});
}
);

View File

@@ -26,6 +26,7 @@ define([
'uuid',
'./defaultRegistry',
'./api/api',
'./api/overlays/OverlayAPI',
'./selection/Selection',
'./api/objects/object-utils',
'./plugins/plugins',
@@ -40,6 +41,8 @@ define([
'./styles-new/core.scss',
'./styles-new/notebook.scss',
'./ui/components/layout/Layout.vue',
'../platform/core/src/objects/DomainObjectImpl',
'../platform/core/src/capabilities/ContextualDomainObject',
'vue'
], function (
EventEmitter,
@@ -47,6 +50,7 @@ define([
uuid,
defaultRegistry,
api,
OverlayAPI,
Selection,
objectUtils,
plugins,
@@ -61,6 +65,8 @@ define([
coreStyles,
NotebookStyles,
Layout,
DomainObjectImpl,
ContextualDomainObject,
Vue
) {
/**
@@ -183,15 +189,6 @@ define([
*/
this.types = new api.TypeRegistry();
/**
* Utilities for attaching common behaviors to views.
*
* @type {module:openmct.GestureAPI}
* @memberof module:openmct.MCT#
* @name gestures
*/
this.gestures = new api.GestureAPI();
/**
* An interface for interacting with domain objects and the domain
* object hierarchy.
@@ -221,11 +218,18 @@ define([
*/
this.indicators = new api.IndicatorAPI(this);
this.Dialog = api.Dialog;
this.notifications = new api.NotificationAPI();
this.editor = new api.EditorAPI.default(this);
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());
if (typeof BUILD_CONSTANTS !== 'undefined') {
this.install(buildInfoPlugin(BUILD_CONSTANTS));
@@ -243,6 +247,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#
@@ -310,13 +350,17 @@ define([
this.$injector.get('objectService');
var appLayout = new Vue({
mixins: [Layout.default],
components: {
'Layout': Layout.default
},
provide: {
openmct: this
}
},
template: '<Layout ref="layout"></Layout>'
});
domElement.appendChild(appLayout.$mount().$el);
this.layout = appLayout;
this.layout = appLayout.$refs.layout;
Browse(this);
this.router.start();
this.emit('start');

View File

@@ -20,32 +20,18 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
/**
* Defines composition policy for Display Layout objects.
* They cannot contain folders.
* @constructor
* @memberof platform/features/layout
* @implements {Policy.<View, DomainObject>}
*/
function LayoutCompositionPolicy() {
}
LayoutCompositionPolicy.prototype.allow = function (parent, child) {
var parentType = parent.getCapability('type');
if (parentType.instanceOf('layout') &&
child.getCapability('type').instanceOf('folder')) {
return false;
}
import LegacyContextMenuAction from './LegacyContextMenuAction';
export default function LegacyActionAdapter(openmct, legacyActions) {
function contextualCategoryOnly(action) {
if (action.category === 'contextual') {
return true;
};
return LayoutCompositionPolicy;
}
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,57 @@
import { timingSafeEqual } from "crypto";
/*****************************************************************************
* 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.
*****************************************************************************/
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;
}
appliesTo(objectPath) {
let legacyObject = this.openmct.legacyObject(objectPath);
return this.LegacyAction.appliesTo({
domainObject: legacyObject
});
}
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();
}
}

View File

@@ -28,7 +28,6 @@ define([
'./services/Instantiate',
'./services/MissingModelCompatibilityDecorator',
'./capabilities/APICapabilityDecorator',
'./policies/AdapterCompositionPolicy',
'./policies/AdaptedViewPolicy',
'./runs/AlternateCompositionInitializer',
'./runs/TimeSettingsURLHandler',
@@ -36,7 +35,9 @@ define([
'./runs/LegacyTelemetryProvider',
'./runs/RegisterLegacyTypes',
'./services/LegacyObjectAPIInterceptor',
'./views/installLegacyViews'
'./views/installLegacyViews',
'./policies/legacyCompositionPolicyAdapter',
'./actions/LegacyActionAdapter'
], function (
legacyRegistry,
ActionDialogDecorator,
@@ -45,7 +46,6 @@ define([
Instantiate,
MissingModelCompatibilityDecorator,
APICapabilityDecorator,
AdapterCompositionPolicy,
AdaptedViewPolicy,
AlternateCompositionInitializer,
TimeSettingsURLHandler,
@@ -53,7 +53,9 @@ define([
LegacyTelemetryProvider,
RegisterLegacyTypes,
LegacyObjectAPIInterceptor,
installLegacyViews
installLegacyViews,
legacyCompositionPolicyAdapter,
LegacyActionAdapter
) {
legacyRegistry.register('src/adapter', {
"extensions": {
@@ -117,11 +119,6 @@ define([
}
],
policies: [
{
category: "composition",
implementation: AdapterCompositionPolicy,
depends: ["openmct"]
},
{
category: "view",
implementation: AdaptedViewPolicy,
@@ -168,6 +165,19 @@ define([
"types[]",
"openmct"
]
},
{
implementation: legacyCompositionPolicyAdapter.default,
depends: [
"openmct"
]
},
{
implementation: LegacyActionAdapter.default,
depends: [
"openmct",
"actions[]"
]
}
],
licenses: [

View File

@@ -0,0 +1,42 @@
/*****************************************************************************
* 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.
*****************************************************************************/
export default function legacyCompositionPolicyAdapter(openmct) {
const instantiate = this.openmct.$injector.get('instantiate');
const policyService = this.openmct.$injector.get('policyService');
openmct.composition.addPolicy((parent, child) => {
let parentId = this.openmct.objects.makeKeyString(parent.identifier);
let childId = this.openmct.objects.makeKeyString(child.identifier);
let legacyParent = instantiate(parent, parentId);
let legacyChild = instantiate(child, childId);
let result = policyService.allow(
'composition',
legacyParent,
legacyChild
);
return result;
});
}

View File

@@ -0,0 +1,95 @@
define([
], function (
) {
const DEFAULT_VIEW_PRIORITY = 100;
const PRIORITY_LEVELS = {
"fallback": Number.NEGATIVE_INFINITY,
"default": -100,
"none": 0,
"optional": DEFAULT_VIEW_PRIORITY,
"preferred": 1000,
"mandatory": Number.POSITIVE_INFINITY
};
function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) {
console.warn(`DEPRECATION WARNING: Migrate ${typeDefinition.key} from ${typeDefinition.bundle.path} to use the new Inspector View APIs. Legacy Inspector view support will be removed soon.`);
let representation = openmct.$injector.get('representations[]')
.filter((r) => r.key === typeDefinition.inspector)[0];
return {
key: representation.key,
name: representation.name,
cssClass: representation.cssClass,
description: representation.description,
canView: function (selection) {
if (!selection[0] || !selection[0].context.item) {
return false;
}
let domainObject = selection[0].context.item;
return domainObject.type === typeDefinition.key;
},
view: function (selection) {
let domainObject = selection[0].context.item;
let $rootScope = openmct.$injector.get('$rootScope');
let templateLinker = openmct.$injector.get('templateLinker');
let scope = $rootScope.$new();
let legacyObject = convertToLegacyObject(domainObject);
let isDestroyed = false;
scope.domainObject = legacyObject;
scope.model = legacyObject.getModel();
return {
show: function (container) {
// TODO: implement "gestures" support ?
let uses = representation.uses || [];
let promises = [];
let results = uses.map(function (capabilityKey, i) {
let result = legacyObject.useCapability(capabilityKey);
if (result.then) {
promises.push(result.then(function (r) {
results[i] = r;
}));
}
return result;
});
function link() {
if (isDestroyed) {
return;
}
uses.forEach(function (key, i) {
scope[key] = results[i];
});
templateLinker.link(
scope,
openmct.$angular.element(container),
representation
);
container.style.height = '100%';
}
if (promises.length) {
Promise.all(promises)
.then(function () {
link();
scope.$digest();
});
} else {
link();
}
},
destroy: function () {
scope.$destroy();
}
}
}
};
};
return TypeInspectorViewProvider;
});

View File

@@ -1,8 +1,10 @@
define([
'./LegacyViewProvider',
'./TypeInspectorViewProvider',
'../../api/objects/object-utils'
], function (
LegacyViewProvider,
TypeInspectorViewProvider,
objectUtils
) {
function installLegacyViews(openmct, legacyViews, instantiate) {
@@ -16,6 +18,13 @@ define([
legacyViews.forEach(function (legacyView) {
openmct.objectViews.addProvider(new LegacyViewProvider(legacyView, openmct, convertToLegacyObject));
});
let inspectorTypes = openmct.$injector.get('types[]')
.filter((t) => t.hasOwnProperty('inspector'));
inspectorTypes.forEach(function (typeDefinition) {
openmct.inspectorViews.addProvider(new TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject));
});
}
return installLegacyViews;

86
src/api/Editor.js Normal file
View File

@@ -0,0 +1,86 @@
/*****************************************************************************
* 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 EventEmitter from 'EventEmitter';
export default class Editor extends EventEmitter {
constructor(openmct) {
super();
this.editing = false;
this.openmct = openmct;
}
/**
* Initiate an editing session. This will start a transaction during
* which any persist operations will be deferred until either save()
* or finish() are called.
*/
edit() {
if (this.editing === true) {
throw "Already editing";
}
this.editing = true;
this.getTransactionService().startTransaction();
this.emit('isEditing', true);
}
/**
* @returns true if the application is in edit mode, false otherwise.
*/
isEditing() {
return this.editing;
}
/**
* Save any unsaved changes from this editing session. This will
* end the current transaction.
*/
save() {
return this.getTransactionService().commit().then((result)=>{
this.editing = false;
this.emit('isEditing', false);
return result
}).catch((error)=>{
throw error;
});
}
/**
* End the currently active transaction and discard unsaved changes.
*/
cancel() {
this.getTransactionService().cancel();
this.editing = false;
this.emit('isEditing', false);
}
/**
* @private
*/
getTransactionService() {
if (!this.transactionService) {
this.transactionService = this.openmct.$injector.get('transactionService');
}
return this.transactionService;
}
}

View File

@@ -25,28 +25,32 @@ define([
'./objects/ObjectAPI',
'./composition/CompositionAPI',
'./types/TypeRegistry',
'./ui/Dialog',
'./ui/GestureAPI',
'./telemetry/TelemetryAPI',
'./indicators/IndicatorAPI'
'./indicators/IndicatorAPI',
'./notifications/NotificationAPI',
'./contextMenu/ContextMenuAPI',
'./Editor'
], function (
TimeAPI,
ObjectAPI,
CompositionAPI,
TypeRegistry,
Dialog,
GestureAPI,
TelemetryAPI,
IndicatorAPI
IndicatorAPI,
NotificationAPI,
ContextMenuAPI,
EditorAPI
) {
return {
TimeAPI: TimeAPI,
ObjectAPI: ObjectAPI,
CompositionAPI: CompositionAPI,
Dialog: Dialog,
TypeRegistry: TypeRegistry,
GestureAPI: GestureAPI,
TelemetryAPI: TelemetryAPI,
IndicatorAPI: IndicatorAPI
IndicatorAPI: IndicatorAPI,
NotificationAPI: NotificationAPI.default,
EditorAPI: EditorAPI,
ContextMenuRegistry: ContextMenuAPI.default
};
});

View File

@@ -44,7 +44,7 @@ define([
function CompositionAPI(publicAPI) {
this.registry = [];
this.policies = [];
this.addProvider(new DefaultCompositionProvider(publicAPI));
this.addProvider(new DefaultCompositionProvider(publicAPI, this));
this.publicAPI = publicAPI;
}

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