From cecd708dd1e8d84ef6fbffd767f4fb3ed825ac62 Mon Sep 17 00:00:00 2001 From: Pegah Sarram Date: Fri, 18 Aug 2017 16:28:41 -0700 Subject: [PATCH] Layout selection and show/hide frame MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added ability to show/hide object frames via a toggle button in the edit toolbar. All objects have frames by default except for ‘hyperlinks’. Also, implemented object selection in the layout and added tests for new features. Fixes #1658. --- .../res/templates/browse/object-header.html | 6 +- .../general/res/sass/_fixed-position.scss | 56 +----- .../general/res/sass/controls/_controls.scss | 5 - .../general/res/sass/edit/_editor.scss | 124 ++++++------ .../general/res/sass/overlay/_overlay.scss | 12 +- .../general/res/sass/user-environ/_frame.scss | 53 ++++-- .../res/sass/user-environ/_selecting.scss | 33 +++- platform/features/layout/bundle.js | 24 ++- .../features/layout/res/templates/fixed.html | 15 +- .../features/layout/res/templates/frame.html | 16 +- .../features/layout/res/templates/layout.html | 24 ++- .../features/layout/src/LayoutController.js | 176 ++++++++++++++++-- .../features/layout/src/MCTTriggerModal.js | 7 +- .../layout/test/LayoutControllerSpec.js | 137 +++++++++++++- 14 files changed, 493 insertions(+), 195 deletions(-) diff --git a/platform/commonUI/browse/res/templates/browse/object-header.html b/platform/commonUI/browse/res/templates/browse/object-header.html index 92e3dd43d6..9d59dc96e1 100644 --- a/platform/commonUI/browse/res/templates/browse/object-header.html +++ b/platform/commonUI/browse/res/templates/browse/object-header.html @@ -28,8 +28,4 @@ key="'menu-arrow'" mct-object='domainObject' class="flex-elem context-available-w"> - - - + \ No newline at end of file diff --git a/platform/commonUI/general/res/sass/_fixed-position.scss b/platform/commonUI/general/res/sass/_fixed-position.scss index 8e63047212..b2e10d7e72 100644 --- a/platform/commonUI/general/res/sass/_fixed-position.scss +++ b/platform/commonUI/general/res/sass/_fixed-position.scss @@ -21,32 +21,11 @@ *****************************************************************************/ .t-fixed-position { &.l-fixed-position { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: auto; - height: auto; - - .l-grid-holder { - position: relative; - height: 100%; - width: 100%; - .l-grid { - position: absolute; - height: 100%; - width: 100%; - pointer-events: none; - z-index: 0; - } - } + @extend .abs; } .l-fixed-position-item { position: absolute; - border: 1px solid transparent; - &.s-not-selected { opacity: 0.8; } @@ -105,37 +84,8 @@ } } } - - .l-fixed-position-item-handle { - $brd: 1px solid $colorKey; - background: rgba($colorKey, 0.5); - cursor: crosshair; - border: $brd; - position: absolute; - } } -.edit-mode .t-fixed-position { - &.l-fixed-position { - .l-grid-holder { - .l-grid { - &.l-grid-x { - @include bgTicks($colorGridLines, 'x'); - } - - &.l-grid-y { - @include bgTicks($colorGridLines, 'y'); - } - } - } - } - - .l-fixed-position-item { - &:not(.s-selected) { - border: 1px dotted rgba($colorKey, 0.75); - &:hover { - border: 1px dotted rgba($colorKey, 1.0); - } - } - } +.s-status-editing { + .l-fixed-position-item-handle.edit-corner { display: block; } } diff --git a/platform/commonUI/general/res/sass/controls/_controls.scss b/platform/commonUI/general/res/sass/controls/_controls.scss index 774fb28584..fcf8cbf642 100644 --- a/platform/commonUI/general/res/sass/controls/_controls.scss +++ b/platform/commonUI/general/res/sass/controls/_controls.scss @@ -704,11 +704,6 @@ textarea { } } -.view-switcher, -.t-btn-view-large { - @include trans-prop-nice-fade($controlFadeMs); -} - /******************************************************** BROWSER ELEMENTS */ body.desktop { ::-webkit-scrollbar { diff --git a/platform/commonUI/general/res/sass/edit/_editor.scss b/platform/commonUI/general/res/sass/edit/_editor.scss index bde666e768..b468d616b8 100644 --- a/platform/commonUI/general/res/sass/edit/_editor.scss +++ b/platform/commonUI/general/res/sass/edit/_editor.scss @@ -19,12 +19,24 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ -.s-status-editing .l-object-wrapper, -.edit-main { - // .s-status-editing .l-object-wrapper is relevant to New Edit Mode; - // .edit-main is legacy for old edit mode. - $handleD: 15px; - $cr: 5px; +.t-edit-handle-holder { display: none; } + +.l-grid-holder { + display: none; + position: relative; + height: 100%; + width: 100%; + .l-grid { + @extend .abs; + pointer-events: none; + z-index: 0; + &.l-grid-y { background-position: 0 1px; } + } +} + +.s-status-editing { + $handleD: 5px; + .t-edit-handle-holder { display: block; } .edit-corner, .edit-handle { position: absolute; @@ -32,81 +44,59 @@ } .edit-corner { + background: rgba($colorSelectableSelectedPrimary, 0.5); + cursor: crosshair; + display: none; // Hide by default + border: 1px solid $colorSelectableSelectedPrimary; width: $handleD; height: $handleD; + $o: (-1 * $handleD) + 1px; &:hover { z-index: 11; } - &.edit-resize-nw { - border-bottom-right-radius: $cr; - cursor: nw-resize; - top: 0; left: 0; - } - &.edit-resize-ne { - border-bottom-left-radius: $cr; - cursor: ne-resize; - top: 0; right: 0; - } - &.edit-resize-se { - border-top-left-radius: $cr; - cursor: se-resize; - bottom: 0; right: 0; - } - &.edit-resize-sw { - border-top-right-radius: $cr; - cursor: sw-resize; - bottom: 0; left: 0; - } - + &.edit-resize-nw { top: $o; left: $o; } + &.edit-resize-ne { top: $o; right: $o; } + &.edit-resize-se { bottom: $o; right: $o; } + &.edit-resize-sw { bottom: $o; left: $o; } } - .edit-handle { - top: $handleD; right: $handleD; bottom: $handleD; left: $handleD; - &.edit-move { - $m: 0; //$handleD; - cursor: move; - left: $m; - right: $m; - top: $m; - bottom: $m; - z-index: 1; - - } - &.edit-resize-n { - top: 0px; bottom: auto; - height: $handleD; - cursor: n-resize; - } - &.edit-resize-e { - right: 0px; left: auto; - width: $handleD; - cursor: e-resize; - } - &.edit-resize-s { - bottom: 0px; top: auto; - height: $handleD; - cursor: s-resize; - } - &.edit-resize-w { - left: 0px; right: auto; - width: $handleD; - cursor: w-resize; - } + .edit-handle.edit-move { + // main move box for the whole frame element + $m: 0; + left: $m; + right: $m; + top: $m; + bottom: $m; + z-index: 1; } .frame.child-frame.panel { &:hover { - @include boxShdwLarge(); - border-color: $colorSelectableSelectedPrimary; .view-switcher { opacity: 1; } - .edit-corner { - background-color: rgba($colorKey, 0.8); - &:hover { - background-color: rgba($colorKey, 1); - } - } } } + + // Editing Grids + .l-grid-holder { + display: block; + .l-grid { + &.l-grid-x { @include bgTicks($colorGridLines, 'x'); } + &.l-grid-y { @include bgTicks($colorGridLines, 'y'); } + } + } + + // Prevent nested frames from showing their grids + .t-frame-outer .l-grid-holder { display: none !important; } + + // Prevent nested elements from showing s-hover-border + .t-frame-outer .s-hover-border { + border: none !important; + } + + // Prevent nested frames from being selectable until we have proper sub-object editing + .t-frame-outer .t-frame-outer { + pointer-events: none; + } } diff --git a/platform/commonUI/general/res/sass/overlay/_overlay.scss b/platform/commonUI/general/res/sass/overlay/_overlay.scss index 20ddf8efb6..64a814538e 100644 --- a/platform/commonUI/general/res/sass/overlay/_overlay.scss +++ b/platform/commonUI/general/res/sass/overlay/_overlay.scss @@ -193,6 +193,7 @@ @include animToParams(overlayIn, $dur: $durLargeViewExpand, $delay: 0); background: $colorBodyBg; + z-index: 101; .abs.inner-holder { opacity: 0; @@ -203,10 +204,19 @@ @include animToParams(contentsIn, $dur: 50ms, $delay: $durLargeViewExpand * 1.25); } + // Hide View Large button .t-btn-view-large { display: none; } - z-index: 101; + + // But show View Large button when it's nested inside a Layout + .t-frame-inner .t-frame-inner .t-btn-view-large { display: block; } + } + } + // When multiple Large Views are visible, hide the blocker for all but the first + & + .l-large-view { + .blocker { + display: none; } } } diff --git a/platform/commonUI/general/res/sass/user-environ/_frame.scss b/platform/commonUI/general/res/sass/user-environ/_frame.scss index bbbbba8bb2..37dad0d21d 100644 --- a/platform/commonUI/general/res/sass/user-environ/_frame.scss +++ b/platform/commonUI/general/res/sass/user-environ/_frame.scss @@ -23,28 +23,43 @@ $ohH: $btnFrameH; $bc: $colorInteriorBorder; &.child-frame.panel { - background: $colorBodyBg; - border: 1px solid $bc; z-index: 0; // Needed to prevent child-frame controls from showing through when another child-frame is above - &:hover { - border-color: lighten($bc, 10%); + &:not(.no-frame) { + background: $colorBodyBg; + border: 1px solid $bc; + &:hover { + border-color: lighten($bc, 10%); + } } } .object-browse-bar { font-size: 0.75em; height: $ohH; line-height: $ohH; + .right { + @include trans-prop-nice-fade($controlFadeMs); + padding-left: $interiorMargin; + } + } + + &.t-object-type-timer, + &.t-object-type-clock, + &.t-object-type-hyperlink { + // Hide the right side buttons for objects where they don't make sense + // Note that this will hide the view Switcher button if applied + // to an object that it. + .object-browse-bar .right { display: none; } } > .object-holder.abs { top: $ohH + $interiorMargin; } .contents { - $myM: $interiorMargin; - top: $myM; - right: $myM; - bottom: $myM; - left: $myM; + $m: $interiorMargin; + top: $m; + right: $m; + bottom: $m; + left: $m; } &.frame-template { .s-button, @@ -67,15 +82,25 @@ } } .view-switcher { - margin-left: $interiorMargin; // Kick other top bar elements away when I'm present. + margin-right: $interiorMargin; // Kick other top bar elements away when I'm present. // Hide the name when the view switcher is in a frame context .title-label { display: none; } } + &.no-frame { background: transparent !important; border: none !important; + .object-browse-bar .right { + $m: 0; // $interiorMarginSm; + background: rgba(black, 0.3); + border-radius: $basicCr; + padding: $interiorMarginSm; + position: absolute; + top: $m; right: $m; + z-index: 2; + } &.t-frame-outer > .t-rep-frame { &.contents { $m: 2px; @@ -85,7 +110,7 @@ left: $m; } > .t-frame-inner { - > .object-browse-bar { + > .object-browse-bar .left { display: none; } > .object-holder.abs { @@ -117,8 +142,7 @@ body.desktop .frame { // Hide local controls initially and show it them on hover when they're in an element that's in a frame context // Frame template is used because we need to target the lowest nested frame - .view-switcher, - .t-btn-view-large { + .right { opacity: 0; pointer-events: none; } @@ -126,8 +150,7 @@ body.desktop .frame { // Target the first descendant so that we only show the elements in the outermost container. // Handles the case where we have layouts in layouts. &:hover > .object-browse-bar { - .view-switcher, - .t-btn-view-large { + .right { opacity: 1; pointer-events: inherit; } diff --git a/platform/commonUI/general/res/sass/user-environ/_selecting.scss b/platform/commonUI/general/res/sass/user-environ/_selecting.scss index 07b18e46ed..525cfea7b2 100644 --- a/platform/commonUI/general/res/sass/user-environ/_selecting.scss +++ b/platform/commonUI/general/res/sass/user-environ/_selecting.scss @@ -19,21 +19,36 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ -.s-selectable { - border: 1px solid transparent; +.s-hover-border { + border: 1px dotted transparent; +} - &.s-hover { - // Styles when hovering over a selectable object - border-color: $colorSelectableHov !important; +.s-status-editing { + // Limit to editing mode until we have sub-object selection + .s-hover-border { + // Show a border by default so user can see object bounds and empty objects + border: 1px dotted rgba($colorSelectableSelectedPrimary, 0.3) !important; + &:hover { + border-color: rgba($colorSelectableSelectedPrimary, 0.7) !important; + } } - &.s-selected { + .s-selected > .s-hover-border, + .s-selected.s-hover-border { // Styles for a selected object. Also used by legacy Fixed Position/Panel objects. border-color: $colorSelectableSelectedPrimary !important; + @include boxShdwLarge(); + // Show edit-corners if you got 'em + .edit-corner { + display: block; + &:hover { + background-color: rgba($colorKey, 1); + } + } } - &.s-moveable { - @include boxShdwLarge(); + .s-selected > .s-moveable, + .s-selected.s-moveable { cursor: move; } -} +} \ No newline at end of file diff --git a/platform/features/layout/bundle.js b/platform/features/layout/bundle.js index a38c447fb2..4801dff7ae 100644 --- a/platform/features/layout/bundle.js +++ b/platform/features/layout/bundle.js @@ -62,7 +62,29 @@ define([ "type": "layout", "template": layoutTemplate, "editable": true, - "uses": [] + "uses": [], + "toolbar": { + "sections": [ + { + "items": [ + { + "method": "showFrame", + "cssClass": "icon-frame-show", + "control": "button", + "title": "Show frame", + "description": "Show frame" + }, + { + "method": "hideFrame", + "cssClass": "icon-frame-hide", + "control": "button", + "title": "Hide frame", + "description": "Hide frame" + } + ] + } + ] + } }, { "key": "fixed", diff --git a/platform/features/layout/res/templates/fixed.html b/platform/features/layout/res/templates/fixed.html index 26c4502ccb..57f2c08fb3 100644 --- a/platform/features/layout/res/templates/fixed.html +++ b/platform/features/layout/res/templates/fixed.html @@ -25,15 +25,20 @@
- -
+
- + + +
- +
diff --git a/platform/features/layout/res/templates/layout.html b/platform/features/layout/res/templates/layout.html index 885c185b3f..75643cc938 100644 --- a/platform/features/layout/res/templates/layout.html +++ b/platform/features/layout/res/templates/layout.html @@ -19,19 +19,33 @@ this source code distribution or the Licensing information page available at runtime from the About dialog for additional information. --> -
-
+ + +
+
+
+
+ +
- + ' + '
' + '
' + ' ' + @@ -37,8 +36,7 @@ define([ ' Done' + '
' + '
' + -'
' + -'
'; +' '; /** * MCT Trigger Modal is intended for use in only one location: inside the @@ -81,7 +79,8 @@ define([ function openOverlay() { // Remove frame classes from being applied in a non-frame context $(frame).removeClass('frame frame-template'); - overlay = document.createElement('span'); + overlay = document.createElement('div'); + $(overlay).addClass('abs overlay l-large-view'); overlay.innerHTML = OVERLAY_TEMPLATE; overlayContainer = overlay.querySelector('.t-contents'); closeButton = overlay.querySelector('a.close'); diff --git a/platform/features/layout/test/LayoutControllerSpec.js b/platform/features/layout/test/LayoutControllerSpec.js index cc1d2459a5..e8fb943646 100644 --- a/platform/features/layout/test/LayoutControllerSpec.js +++ b/platform/features/layout/test/LayoutControllerSpec.js @@ -49,10 +49,30 @@ define( }, useCapability: function () { return mockCompositionCapability; + }, + getModel: function () { + if (id === 'b') { + return { + type : 'hyperlink' + }; + } else { + return {}; + } } }; } + // Utility function to find a watch for a given expression + function findWatch(expr) { + var watch; + mockScope.$watch.calls.forEach(function (call) { + if (call.args[0] === expr) { + watch = call.args[1]; + } + }); + return watch; + } + beforeEach(function () { mockScope = jasmine.createSpyObj( "$scope", @@ -60,7 +80,7 @@ define( ); mockEvent = jasmine.createSpyObj( 'event', - ['preventDefault'] + ['preventDefault', 'stopPropagation'] ); testModel = {}; @@ -68,6 +88,7 @@ define( mockComposition = ["a", "b", "c"]; mockCompositionObjects = mockComposition.map(mockDomainObject); + testConfiguration = { panels: { a: { @@ -82,10 +103,19 @@ define( mockScope.domainObject = mockDomainObject("mockDomainObject"); mockScope.model = testModel; mockScope.configuration = testConfiguration; + mockScope.selection = jasmine.createSpyObj( + 'selection', + ['select', 'get', 'selected', 'deselect'] + ); + spyOn(mockScope.domainObject, "useCapability").andCallThrough(); controller = new LayoutController(mockScope); spyOn(controller, "layoutPanels").andCallThrough(); + + findWatch("selection")(mockScope.selection); + + jasmine.Clock.useMock(); }); // Model changes will indicate that panel positions @@ -289,6 +319,111 @@ define( expect(controller.getFrameStyle("b")) .not.toEqual(oldStyle); }); + + it("allows panels to be selected", function () { + mockScope.$watchCollection.mostRecentCall.args[1](); + var childObj = mockCompositionObjects[0]; + + controller.select(mockEvent, childObj.getId()); + + expect(mockEvent.stopPropagation).toHaveBeenCalled(); + + expect(controller.selected(childObj)).toBe(true); + }); + + it("allows selection to be cleared", function () { + mockScope.$watchCollection.mostRecentCall.args[1](); + var childObj = mockCompositionObjects[0]; + + controller.select(null, childObj.getId()); + controller.clearSelection(); + + expect(controller.selected(childObj)).toBeFalsy(); + }); + + it("prevents clearing selection while drag is in progress", function () { + mockScope.$watchCollection.mostRecentCall.args[1](); + var childObj = mockCompositionObjects[0]; + var id = childObj.getId(); + + controller.select(mockEvent, id); + + // Do a drag + controller.startDrag(id, [1, 1], [0, 0]); + controller.continueDrag([100, 100]); + controller.endDrag(); + + // Because mouse position could cause clearSelection to be called, this should be ignored. + controller.clearSelection(); + + expect(controller.selected(childObj)).toBe(true); + + // Shoud be able to clear the selection after dragging is done. + jasmine.Clock.tick(0); + controller.clearSelection(); + + expect(controller.selected(childObj)).toBe(false); + }); + + it("clears selection after moving/resizing", function () { + mockScope.$watchCollection.mostRecentCall.args[1](); + var childObj = mockCompositionObjects[0]; + var id = childObj.getId(); + + controller.select(mockEvent, id); + + // Do a drag + controller.startDrag(id, [1, 1], [0, 0]); + controller.continueDrag([100, 100]); + controller.endDrag(); + + jasmine.Clock.tick(0); + controller.clearSelection(); + + expect(controller.selected(childObj)).toBe(false); + }); + + it("shows frames by default", function () { + mockScope.$watchCollection.mostRecentCall.args[1](); + + expect(controller.hasFrame(mockCompositionObjects[0])).toBe(true); + }); + + it("hyperlinks hide frame by default", function () { + mockScope.$watchCollection.mostRecentCall.args[1](); + + expect(controller.hasFrame(mockCompositionObjects[1])).toBe(false); + }); + + it("hides frame when selected object has frame ", function () { + mockScope.$watchCollection.mostRecentCall.args[1](); + var childObj = mockCompositionObjects[0]; + controller.select(mockEvent, childObj.getId()); + + expect(mockScope.selection.select).toHaveBeenCalled(); + + var selectedObj = mockScope.selection.select.mostRecentCall.args[0]; + + expect(controller.hasFrame(childObj)).toBe(true); + expect(selectedObj.hideFrame).toBeDefined(); + expect(selectedObj.hideFrame).toEqual(jasmine.any(Function)); + }); + + it("shows frame when selected object has no frame", function () { + mockScope.$watchCollection.mostRecentCall.args[1](); + + var childObj = mockCompositionObjects[1]; + controller.select(mockEvent, childObj.getId()); + + expect(mockScope.selection.select).toHaveBeenCalled(); + + var selectedObj = mockScope.selection.select.mostRecentCall.args[0]; + + expect(controller.hasFrame(childObj)).toBe(false); + expect(selectedObj.showFrame).toBeDefined(); + expect(selectedObj.showFrame).toEqual(jasmine.any(Function)); + }); + }); } );