Merge pull request #1666 from nasa/layout-issue-1658

Added selection capability for the panels in layout. Also, added a to…
This commit is contained in:
Pete Richards 2017-08-22 17:31:52 -07:00 committed by GitHub
commit e205bf1fa4
14 changed files with 493 additions and 195 deletions

View File

@ -28,8 +28,4 @@
key="'menu-arrow'" key="'menu-arrow'"
mct-object='domainObject' mct-object='domainObject'
class="flex-elem context-available-w"></mct-representation> class="flex-elem context-available-w"></mct-representation>
</span> </span>
<a class="s-button icon-expand t-btn-view-large"
title="View large"
mct-trigger-modal>
</a>

View File

@ -21,32 +21,11 @@
*****************************************************************************/ *****************************************************************************/
.t-fixed-position { .t-fixed-position {
&.l-fixed-position { &.l-fixed-position {
position: absolute; @extend .abs;
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;
}
}
} }
.l-fixed-position-item { .l-fixed-position-item {
position: absolute; position: absolute;
border: 1px solid transparent;
&.s-not-selected { &.s-not-selected {
opacity: 0.8; 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 { .s-status-editing {
&.l-fixed-position { .l-fixed-position-item-handle.edit-corner { display: block; }
.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);
}
}
}
} }

View File

@ -704,11 +704,6 @@ textarea {
} }
} }
.view-switcher,
.t-btn-view-large {
@include trans-prop-nice-fade($controlFadeMs);
}
/******************************************************** BROWSER ELEMENTS */ /******************************************************** BROWSER ELEMENTS */
body.desktop { body.desktop {
::-webkit-scrollbar { ::-webkit-scrollbar {

View File

@ -19,12 +19,24 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
.s-status-editing .l-object-wrapper, .t-edit-handle-holder { display: none; }
.edit-main {
// .s-status-editing .l-object-wrapper is relevant to New Edit Mode; .l-grid-holder {
// .edit-main is legacy for old edit mode. display: none;
$handleD: 15px; position: relative;
$cr: 5px; 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-corner,
.edit-handle { .edit-handle {
position: absolute; position: absolute;
@ -32,81 +44,59 @@
} }
.edit-corner { .edit-corner {
background: rgba($colorSelectableSelectedPrimary, 0.5);
cursor: crosshair;
display: none; // Hide by default
border: 1px solid $colorSelectableSelectedPrimary;
width: $handleD; width: $handleD;
height: $handleD; height: $handleD;
$o: (-1 * $handleD) + 1px;
&:hover { &:hover {
z-index: 11; z-index: 11;
} }
&.edit-resize-nw { &.edit-resize-nw { top: $o; left: $o; }
border-bottom-right-radius: $cr; &.edit-resize-ne { top: $o; right: $o; }
cursor: nw-resize; &.edit-resize-se { bottom: $o; right: $o; }
top: 0; left: 0; &.edit-resize-sw { bottom: $o; left: $o; }
}
&.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-handle { .edit-handle.edit-move {
top: $handleD; right: $handleD; bottom: $handleD; left: $handleD; // main move box for the whole frame element
&.edit-move { $m: 0;
$m: 0; //$handleD; left: $m;
cursor: move; right: $m;
left: $m; top: $m;
right: $m; bottom: $m;
top: $m; z-index: 1;
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;
}
} }
.frame.child-frame.panel { .frame.child-frame.panel {
&:hover { &:hover {
@include boxShdwLarge();
border-color: $colorSelectableSelectedPrimary;
.view-switcher { .view-switcher {
opacity: 1; 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;
}
} }

View File

@ -193,6 +193,7 @@
@include animToParams(overlayIn, $dur: $durLargeViewExpand, $delay: 0); @include animToParams(overlayIn, $dur: $durLargeViewExpand, $delay: 0);
background: $colorBodyBg; background: $colorBodyBg;
z-index: 101;
.abs.inner-holder { .abs.inner-holder {
opacity: 0; opacity: 0;
@ -203,10 +204,19 @@
@include animToParams(contentsIn, $dur: 50ms, $delay: $durLargeViewExpand * 1.25); @include animToParams(contentsIn, $dur: 50ms, $delay: $durLargeViewExpand * 1.25);
} }
// Hide View Large button
.t-btn-view-large { .t-btn-view-large {
display: none; 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;
} }
} }
} }

View File

@ -23,28 +23,43 @@
$ohH: $btnFrameH; $ohH: $btnFrameH;
$bc: $colorInteriorBorder; $bc: $colorInteriorBorder;
&.child-frame.panel { &.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 z-index: 0; // Needed to prevent child-frame controls from showing through when another child-frame is above
&:hover { &:not(.no-frame) {
border-color: lighten($bc, 10%); background: $colorBodyBg;
border: 1px solid $bc;
&:hover {
border-color: lighten($bc, 10%);
}
} }
} }
.object-browse-bar { .object-browse-bar {
font-size: 0.75em; font-size: 0.75em;
height: $ohH; height: $ohH;
line-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 { > .object-holder.abs {
top: $ohH + $interiorMargin; top: $ohH + $interiorMargin;
} }
.contents { .contents {
$myM: $interiorMargin; $m: $interiorMargin;
top: $myM; top: $m;
right: $myM; right: $m;
bottom: $myM; bottom: $m;
left: $myM; left: $m;
} }
&.frame-template { &.frame-template {
.s-button, .s-button,
@ -67,15 +82,25 @@
} }
} }
.view-switcher { .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 // Hide the name when the view switcher is in a frame context
.title-label { .title-label {
display: none; display: none;
} }
} }
&.no-frame { &.no-frame {
background: transparent !important; background: transparent !important;
border: none !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 { &.t-frame-outer > .t-rep-frame {
&.contents { &.contents {
$m: 2px; $m: 2px;
@ -85,7 +110,7 @@
left: $m; left: $m;
} }
> .t-frame-inner { > .t-frame-inner {
> .object-browse-bar { > .object-browse-bar .left {
display: none; display: none;
} }
> .object-holder.abs { > .object-holder.abs {
@ -117,8 +142,7 @@
body.desktop .frame { 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 // 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 // Frame template is used because we need to target the lowest nested frame
.view-switcher, .right {
.t-btn-view-large {
opacity: 0; opacity: 0;
pointer-events: none; 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. // Target the first descendant so that we only show the elements in the outermost container.
// Handles the case where we have layouts in layouts. // Handles the case where we have layouts in layouts.
&:hover > .object-browse-bar { &:hover > .object-browse-bar {
.view-switcher, .right {
.t-btn-view-large {
opacity: 1; opacity: 1;
pointer-events: inherit; pointer-events: inherit;
} }

View File

@ -19,21 +19,36 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
.s-selectable { .s-hover-border {
border: 1px solid transparent; border: 1px dotted transparent;
}
&.s-hover { .s-status-editing {
// Styles when hovering over a selectable object // Limit to editing mode until we have sub-object selection
border-color: $colorSelectableHov !important; .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. // Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
border-color: $colorSelectableSelectedPrimary !important; 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 { .s-selected > .s-moveable,
@include boxShdwLarge(); .s-selected.s-moveable {
cursor: move; cursor: move;
} }
} }

View File

@ -62,7 +62,29 @@ define([
"type": "layout", "type": "layout",
"template": layoutTemplate, "template": layoutTemplate,
"editable": true, "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", "key": "fixed",

View File

@ -25,15 +25,20 @@
<!-- Background grid --> <!-- Background grid -->
<div class="l-grid-holder" ng-click="controller.clearSelection()"> <div class="l-grid-holder" ng-click="controller.clearSelection()">
<div class="l-grid l-grid-x" <div class="l-grid l-grid-x"
ng-if="!controller.getGridSize()[0] < 3"
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div> ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
<div class="l-grid l-grid-y" <div class="l-grid l-grid-y"
ng-if="!controller.getGridSize()[1] < 3"
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div> ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
</div> </div>
<!-- Fixed position elements --> <!-- Fixed position elements -->
<div ng-repeat="element in controller.getElements()" <div ng-repeat="element in controller.getElements()"
class="l-fixed-position-item" class="l-fixed-position-item s-selectable s-moveable s-hover-border"
ng-class="{ 's-not-selected': controller.selected() && !controller.selected(element) }" ng-class="{
's-not-selected': controller.selected() && !controller.selected(element),
's-selected': controller.selected(element)
}"
ng-style="element.style" ng-style="element.style"
ng-click="controller.select(element)"> ng-click="controller.select(element)">
<mct-include key="element.template" <mct-include key="element.template"
@ -43,15 +48,15 @@
</div> </div>
<!-- Selection highlight, handles --> <!-- Selection highlight, handles -->
<span ng-if="controller.selected()"> <span class="s-selected s-moveable" ng-if="controller.selected()">
<div class="l-fixed-position-item s-selectable s-selected s-moveable" <div class="l-fixed-position-item t-edit-handle-holder"
mct-drag-down="controller.moveHandle().startDrag(controller.selected())" mct-drag-down="controller.moveHandle().startDrag(controller.selected())"
mct-drag="controller.moveHandle().continueDrag(delta)" mct-drag="controller.moveHandle().continueDrag(delta)"
mct-drag-up="controller.moveHandle().endDrag()" mct-drag-up="controller.moveHandle().endDrag()"
ng-style="controller.selected().style"> ng-style="controller.selected().style">
</div> </div>
<div ng-repeat="handle in controller.handles()" <div ng-repeat="handle in controller.handles()"
class="l-fixed-position-item-handle" class="l-fixed-position-item-handle edit-corner"
ng-style="handle.style()" ng-style="handle.style()"
mct-drag-down="handle.startDrag()" mct-drag-down="handle.startDrag()"
mct-drag="handle.continueDrag(delta)" mct-drag="handle.continueDrag(delta)"

View File

@ -29,15 +29,21 @@
</mct-representation> </mct-representation>
</div> </div>
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed"> <div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
<mct-representation key="'switcher'" <mct-representation
ng-model="representation" key="'switcher'"
mct-object="domainObject"> ng-model="representation"
mct-object="domainObject">
</mct-representation> </mct-representation>
<a class="s-button icon-expand t-btn-view-large"
title="View large"
mct-trigger-modal>
</a>
</div> </div>
</div> </div>
<div class="abs object-holder"> <div class="abs object-holder">
<mct-representation key="representation.selected.key" <mct-representation
mct-object="representation.selected.key && domainObject"> key="representation.selected.key"
mct-object="representation.selected.key && domainObject">
</mct-representation> </mct-representation>
</div> </div>
</div> </div>

View File

@ -19,19 +19,33 @@
this source code distribution or the Licensing information page available this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<div class="l-layout"
ng-controller="LayoutController as controller">
<div class='frame child-frame panel abs' <div class="abs l-layout"
ng-controller="LayoutController as controller"
ng-click="controller.clearSelection()">
<!-- Background grid -->
<div class="l-grid-holder" ng-click="controller.clearSelection()">
<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='abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border'
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-selected':controller.selected(childObject) }"
ng-repeat="childObject in composition" ng-repeat="childObject in composition"
ng-click="controller.select($event, childObject.getId())"
ng-style="controller.getFrameStyle(childObject.getId())"> ng-style="controller.getFrameStyle(childObject.getId())">
<mct-representation key="'frame'" <mct-representation key="'frame'"
class="frame child-frame holder contents abs" class="t-rep-frame holder contents abs"
mct-object="childObject"> mct-object="childObject">
</mct-representation> </mct-representation>
<!-- Drag handles --> <!-- Drag handles -->
<span ng-show="domainObject.hasCapability('editor')"> <span class="abs t-edit-handle-holder s-hover-border" ng-if="controller.selected(childObject)">
<span class="edit-handle edit-move" <span class="edit-handle edit-move"
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])" mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
mct-drag="controller.continueDrag(delta)" mct-drag="controller.continueDrag(delta)"

View File

@ -33,6 +33,10 @@ define(
DEFAULT_GRID_SIZE = [32, 32], DEFAULT_GRID_SIZE = [32, 32],
MINIMUM_FRAME_SIZE = [320, 180]; MINIMUM_FRAME_SIZE = [320, 180];
var DEFAULT_HIDDEN_FRAME_TYPES = [
'hyperlink'
];
/** /**
* The LayoutController is responsible for supporting the * The LayoutController is responsible for supporting the
* Layout view. It arranges frames according to saved configuration * Layout view. It arranges frames according to saved configuration
@ -65,13 +69,10 @@ define(
return; return;
} }
// Ensure that configuration field is populated
$scope.configuration = $scope.configuration || {}; $scope.configuration = $scope.configuration || {};
// Make sure there is a "panels" field in the
// view configuration.
$scope.configuration.panels = $scope.configuration.panels =
$scope.configuration.panels || {}; $scope.configuration.panels || {};
// Store the position of this panel.
$scope.configuration.panels[id] = { $scope.configuration.panels[id] = {
position: [ position: [
Math.floor(position.x / self.gridSize[0]), Math.floor(position.x / self.gridSize[0]),
@ -79,6 +80,11 @@ define(
], ],
dimensions: self.defaultDimensions() dimensions: self.defaultDimensions()
}; };
// Store the id so that the newly-dropped object
// gets selected during refresh composition
self.droppedIdToSelectAfterRefresh = id;
// Mark change as persistable // Mark change as persistable
if ($scope.commit) { if ($scope.commit) {
$scope.commit("Dropped a frame."); $scope.commit("Dropped a frame.");
@ -111,6 +117,13 @@ define(
$scope.composition = composition; $scope.composition = composition;
self.layoutPanels(ids); self.layoutPanels(ids);
self.setFrames(ids);
// If there is a newly-dropped object, select it.
if (self.droppedIdToSelectAfterRefresh) {
self.select(null, self.droppedIdToSelectAfterRefresh);
delete self.droppedIdToSelectAfterRefresh;
}
} }
}); });
} }
@ -123,13 +136,18 @@ define(
// saved by the EditRepresenter. // saved by the EditRepresenter.
$scope.configuration = $scope.configuration =
$scope.configuration || {}; $scope.configuration || {};
// Make sure there is a "panels" field in the
// view configuration.
$scope.configuration.panels = $scope.configuration.panels =
$scope.configuration.panels || {}; $scope.configuration.panels || {};
// Store the position of this panel.
$scope.configuration.panels[self.activeDragId] = $scope.configuration.panels[self.activeDragId] =
self.rawPositions[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;
// Mark this object as dirty to encourage persistence // Mark this object as dirty to encourage persistence
if ($scope.commit) { if ($scope.commit) {
$scope.commit("Moved frame."); $scope.commit("Moved frame.");
@ -144,6 +162,10 @@ define(
// Watch for changes to the grid size in the model // Watch for changes to the grid size in the model
$scope.$watch("model.layoutGrid", updateGridSize); $scope.$watch("model.layoutGrid", updateGridSize);
$scope.$watch("selection", function (selection) {
this.selection = selection;
}.bind(this));
// Update composed objects on screen, and position panes // Update composed objects on screen, and position panes
$scope.$watchCollection("model.composition", refreshComposition); $scope.$watchCollection("model.composition", refreshComposition);
@ -151,6 +173,42 @@ define(
$scope.$on("mctDrop", handleDrop); $scope.$on("mctDrop", handleDrop);
} }
// 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] = DEFAULT_HIDDEN_FRAME_TYPES.indexOf(object.getModel().type) === -1;
}
}, this);
};
// Convert from { positions: ..., dimensions: ... } to an // Convert from { positions: ..., dimensions: ... } to an
// appropriate ng-style argument, to position frames. // appropriate ng-style argument, to position frames.
LayoutController.prototype.convertPosition = function (raw) { LayoutController.prototype.convertPosition = function (raw) {
@ -237,6 +295,7 @@ define(
this.gridSize this.gridSize
); );
}; };
/** /**
* Continue an active drag gesture. * Continue an active drag gesture.
* @param {number[]} delta the offset, in pixels, * @param {number[]} delta the offset, in pixels,
@ -251,17 +310,6 @@ define(
} }
}; };
// 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;
}
/** /**
* Compute panel positions based on the layout's object model. * Compute panel positions based on the layout's object model.
* Defined as member function to facilitate testing. * Defined as member function to facilitate testing.
@ -293,9 +341,99 @@ define(
* view configuration. * view configuration.
*/ */
LayoutController.prototype.endDrag = function () { LayoutController.prototype.endDrag = function () {
this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
this.endDragInScope(); this.endDragInScope();
}; };
/**
* Check 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) {
return !!this.selectedId && this.selectedId === obj.getId();
};
/**
* Set the active user selection in this view.
*
* @param event the mouse event
* @param {string} id the object id
*/
LayoutController.prototype.select = function (event, id) {
if (event) {
event.stopPropagation();
if (this.selection) {
event.preventDefault();
}
}
this.selectedId = id;
var selectedObj = {};
selectedObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id);
if (this.selection) {
this.selection.select(selectedObj);
}
};
/**
* Callback to show/hide the object frame.
*
* @param {string} id the object id
* @private
*/
LayoutController.prototype.toggleFrame = function (id) {
var configuration = this.$scope.configuration;
if (!configuration.panels[id]) {
configuration.panels[id] = {};
}
this.frames[id] = configuration.panels[id].hasFrame = !this.frames[id];
this.select(undefined, id); // reselect so toolbar updates
};
/**
* Clear the current user selection.
*/
LayoutController.prototype.clearSelection = function () {
if (this.dragInProgress) {
return;
}
if (this.selection) {
this.selection.deselect();
delete this.selectedId;
}
};
/**
* 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;
};
return LayoutController; return LayoutController;
} }
); );

View File

@ -27,7 +27,6 @@ define([
) { ) {
var OVERLAY_TEMPLATE = '' + var OVERLAY_TEMPLATE = '' +
'<div class="abs overlay l-large-view">' +
' <div class="abs blocker"></div>' + ' <div class="abs blocker"></div>' +
' <div class="abs outer-holder">' + ' <div class="abs outer-holder">' +
' <a class="close icon-x-in-circle"></a>' + ' <a class="close icon-x-in-circle"></a>' +
@ -37,8 +36,7 @@ define([
' <a class="t-done s-button major">Done</a>' + ' <a class="t-done s-button major">Done</a>' +
' </div>' + ' </div>' +
' </div>' + ' </div>' +
' </div>' + ' </div>';
'</div>';
/** /**
* MCT Trigger Modal is intended for use in only one location: inside the * MCT Trigger Modal is intended for use in only one location: inside the
@ -81,7 +79,8 @@ define([
function openOverlay() { function openOverlay() {
// Remove frame classes from being applied in a non-frame context // Remove frame classes from being applied in a non-frame context
$(frame).removeClass('frame frame-template'); $(frame).removeClass('frame frame-template');
overlay = document.createElement('span'); overlay = document.createElement('div');
$(overlay).addClass('abs overlay l-large-view');
overlay.innerHTML = OVERLAY_TEMPLATE; overlay.innerHTML = OVERLAY_TEMPLATE;
overlayContainer = overlay.querySelector('.t-contents'); overlayContainer = overlay.querySelector('.t-contents');
closeButton = overlay.querySelector('a.close'); closeButton = overlay.querySelector('a.close');

View File

@ -49,10 +49,30 @@ define(
}, },
useCapability: function () { useCapability: function () {
return mockCompositionCapability; 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 () { beforeEach(function () {
mockScope = jasmine.createSpyObj( mockScope = jasmine.createSpyObj(
"$scope", "$scope",
@ -60,7 +80,7 @@ define(
); );
mockEvent = jasmine.createSpyObj( mockEvent = jasmine.createSpyObj(
'event', 'event',
['preventDefault'] ['preventDefault', 'stopPropagation']
); );
testModel = {}; testModel = {};
@ -68,6 +88,7 @@ define(
mockComposition = ["a", "b", "c"]; mockComposition = ["a", "b", "c"];
mockCompositionObjects = mockComposition.map(mockDomainObject); mockCompositionObjects = mockComposition.map(mockDomainObject);
testConfiguration = { testConfiguration = {
panels: { panels: {
a: { a: {
@ -82,10 +103,19 @@ define(
mockScope.domainObject = mockDomainObject("mockDomainObject"); mockScope.domainObject = mockDomainObject("mockDomainObject");
mockScope.model = testModel; mockScope.model = testModel;
mockScope.configuration = testConfiguration; mockScope.configuration = testConfiguration;
mockScope.selection = jasmine.createSpyObj(
'selection',
['select', 'get', 'selected', 'deselect']
);
spyOn(mockScope.domainObject, "useCapability").andCallThrough(); spyOn(mockScope.domainObject, "useCapability").andCallThrough();
controller = new LayoutController(mockScope); controller = new LayoutController(mockScope);
spyOn(controller, "layoutPanels").andCallThrough(); spyOn(controller, "layoutPanels").andCallThrough();
findWatch("selection")(mockScope.selection);
jasmine.Clock.useMock();
}); });
// Model changes will indicate that panel positions // Model changes will indicate that panel positions
@ -289,6 +319,111 @@ define(
expect(controller.getFrameStyle("b")) expect(controller.getFrameStyle("b"))
.not.toEqual(oldStyle); .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));
});
}); });
} }
); );