diff --git a/package.json b/package.json index eb7afd1f70..13bfe158dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openmct", - "version": "0.10.2-SNAPSHOT", + "version": "0.10.3-SNAPSHOT", "description": "The Open MCT core platform", "dependencies": { "express": "^4.13.1", diff --git a/platform/commonUI/edit/bundle.js b/platform/commonUI/edit/bundle.js index b8906107a0..3a9deb649e 100644 --- a/platform/commonUI/edit/bundle.js +++ b/platform/commonUI/edit/bundle.js @@ -170,7 +170,7 @@ define([ "navigationService", "$log" ], - "description": "Edit this object.", + "description": "Edit", "category": "view-control", "glyph": "p" }, diff --git a/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js b/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js index ffa6097125..9bcf19c002 100644 --- a/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js +++ b/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js @@ -67,10 +67,17 @@ define( } function onCancel() { - return self.persistenceCapability.refresh().then(function (result) { + if (self.domainObject.getModel().persisted !== undefined) { + //Fetch clean model from persistence + return self.persistenceCapability.refresh().then(function (result) { + self.persistPending = false; + return result; + }); + } else { self.persistPending = false; - return result; - }); + //Model is undefined in persistence, so return undefined. + return self.$q.when(undefined); + } } if (this.transactionService.isActive()) { diff --git a/platform/commonUI/edit/test/actions/CancelActionSpec.js b/platform/commonUI/edit/test/actions/CancelActionSpec.js index 411fdb1659..b83fbdab2b 100644 --- a/platform/commonUI/edit/test/actions/CancelActionSpec.js +++ b/platform/commonUI/edit/test/actions/CancelActionSpec.js @@ -24,12 +24,11 @@ define( ["../../src/actions/CancelAction"], function (CancelAction) { - //TODO: Disabled for NEM Beta - xdescribe("The Cancel action", function () { - var mockLocation, - mockDomainObject, - mockEditorCapability, - mockUrlService, + describe("The Cancel action", function () { + var mockDomainObject, + mockParentObject, + capabilities = {}, + parentCapabilities = {}, actionContext, action; @@ -42,61 +41,109 @@ define( } beforeEach(function () { - mockLocation = jasmine.createSpyObj( - "$location", - ["path"] - ); mockDomainObject = jasmine.createSpyObj( "domainObject", - ["getCapability", "hasCapability"] + [ + "getCapability", + "hasCapability", + "getModel" + ] ); - mockEditorCapability = jasmine.createSpyObj( + mockDomainObject.getModel.andReturn({}); + + mockParentObject = jasmine.createSpyObj( + "parentObject", + [ + "getCapability" + ] + ); + mockParentObject.getCapability.andCallFake(function (name) { + return parentCapabilities[name]; + }); + + capabilities.editor = jasmine.createSpyObj( "editor", - ["save", "cancel"] + ["save", "cancel", "isEditContextRoot"] ); - mockUrlService = jasmine.createSpyObj( - "urlService", - ["urlForLocation"] + capabilities.action = jasmine.createSpyObj( + "actionCapability", + [ + "perform" + ] + ); + capabilities.location = jasmine.createSpyObj( + "locationCapability", + [ + "getOriginal" + ] + ); + capabilities.location.getOriginal.andReturn(mockPromise(mockDomainObject)); + capabilities.context = jasmine.createSpyObj( + "contextCapability", + [ + "getParent" + ] + ); + capabilities.context.getParent.andReturn(mockParentObject); + + parentCapabilities.action = jasmine.createSpyObj( + "actionCapability", + [ + "perform" + ] ); actionContext = { domainObject: mockDomainObject }; - mockDomainObject.hasCapability.andReturn(true); - mockDomainObject.getCapability.andReturn(mockEditorCapability); - mockEditorCapability.cancel.andReturn(mockPromise(true)); + mockDomainObject.getCapability.andCallFake(function (name) { + return capabilities[name]; + }); - action = new CancelAction(mockLocation, mockUrlService, actionContext); + mockDomainObject.hasCapability.andCallFake(function (name) { + return !!capabilities[name]; + }); + + capabilities.editor.cancel.andReturn(mockPromise(true)); + + action = new CancelAction(actionContext); }); - it("only applies to domain object with an editor capability", function () { + it("only applies to domain object that is being edited", function () { + capabilities.editor.isEditContextRoot.andReturn(true); expect(CancelAction.appliesTo(actionContext)).toBeTruthy(); expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor"); + capabilities.editor.isEditContextRoot.andReturn(false); + expect(CancelAction.appliesTo(actionContext)).toBeFalsy(); + mockDomainObject.hasCapability.andReturn(false); - mockDomainObject.getCapability.andReturn(undefined); expect(CancelAction.appliesTo(actionContext)).toBeFalsy(); }); - it("invokes the editor capability's save functionality when performed", function () { - // Verify precondition - expect(mockEditorCapability.cancel).not.toHaveBeenCalled(); + it("invokes the editor capability's cancel functionality when" + + " performed", function () { action.perform(); // Should have called cancel - expect(mockEditorCapability.cancel).toHaveBeenCalled(); + expect(capabilities.editor.cancel).toHaveBeenCalled(); // Definitely shouldn't call save! - expect(mockEditorCapability.save).not.toHaveBeenCalled(); + expect(capabilities.editor.save).not.toHaveBeenCalled(); }); - it("returns to browse when performed", function () { + it("navigates to object if existing", function () { + mockDomainObject.getModel.andReturn({persisted: 1}); action.perform(); - expect(mockLocation.path).toHaveBeenCalledWith( - mockUrlService.urlForLocation("browse", mockDomainObject) - ); + expect(capabilities.action.perform).toHaveBeenCalledWith("navigate"); + }); + + it("navigates to parent if new", function () { + mockDomainObject.getModel.andReturn({persisted: undefined}); + action.perform(); + expect(parentCapabilities.action.perform).toHaveBeenCalledWith("navigate"); }); }); } diff --git a/platform/commonUI/edit/test/capabilities/TransactionalPersistenceCapabilitySpec.js b/platform/commonUI/edit/test/capabilities/TransactionalPersistenceCapabilitySpec.js index 63a9119826..70ad3d8a20 100644 --- a/platform/commonUI/edit/test/capabilities/TransactionalPersistenceCapabilitySpec.js +++ b/platform/commonUI/edit/test/capabilities/TransactionalPersistenceCapabilitySpec.js @@ -57,6 +57,15 @@ define( ); mockPersistence.persist.andReturn(fastPromise()); mockPersistence.refresh.andReturn(fastPromise()); + + mockDomainObject = jasmine.createSpyObj( + "domainObject", + [ + "getModel" + ] + ); + mockDomainObject.getModel.andReturn({persisted: 1}); + capability = new TransactionalPersistenceCapability(mockQ, mockTransactionService, mockPersistence, mockDomainObject); }); @@ -78,6 +87,20 @@ define( expect(mockPersistence.refresh).toHaveBeenCalled(); }); + it("if transaction is active, cancel call is queued that refreshes model when appropriate", function () { + mockTransactionService.isActive.andReturn(true); + capability.persist(); + expect(mockTransactionService.addToTransaction).toHaveBeenCalled(); + + mockDomainObject.getModel.andReturn({}); + mockTransactionService.addToTransaction.mostRecentCall.args[1](); + expect(mockPersistence.refresh).not.toHaveBeenCalled(); + + mockDomainObject.getModel.andReturn({persisted: 1}); + mockTransactionService.addToTransaction.mostRecentCall.args[1](); + expect(mockPersistence.refresh).toHaveBeenCalled(); + }); + it("persist call is only added to transaction once", function () { mockTransactionService.isActive.andReturn(true); capability.persist(); diff --git a/platform/commonUI/general/res/sass/_constants.scss b/platform/commonUI/general/res/sass/_constants.scss index c36a6cb00a..8c216f0151 100644 --- a/platform/commonUI/general/res/sass/_constants.scss +++ b/platform/commonUI/general/res/sass/_constants.scss @@ -124,6 +124,8 @@ $dirImgs: $dirCommonRes + 'images/'; /************************** TIMINGS */ $controlFadeMs: 100ms; +$browseToEditAnimMs: 400ms; +$editBorderPulseMs: 500ms; /************************** LIMITS */ $glyphLimit: '\e603'; diff --git a/platform/commonUI/general/res/sass/_effects.scss b/platform/commonUI/general/res/sass/_effects.scss index a048eb75d0..1a13b07d06 100644 --- a/platform/commonUI/general/res/sass/_effects.scss +++ b/platform/commonUI/general/res/sass/_effects.scss @@ -39,15 +39,20 @@ @include pulse($animName: pulse-subtle, $dur: 500ms, $opacity0: 0.7); } -@mixin pulseBorder($c: red, $dur: 500ms, $iteration: infinite, $delay: 0s, $opacity0: 0, $opacity100: 1) { - @include keyframes(pulseBorder) { - 0% { border-color: rgba($c, $opacity0); } - 100% { border-color: rgba($c, $opacity100); } +@mixin animTo($animName, $propName, $propValStart, $propValEnd, $dur: 500ms, $delay: 0) { + @include keyframes($animName) { + from { #{propName}: $propValStart; } + to { #{$propName}: $propValEnd; } } - @include animation-name(pulseBorder); - @include animation-duration($dur); - @include animation-direction(alternate); - @include animation-iteration-count($iteration); - @include animation-timing-function(ease); - @include animation-delay($delay); + @include animToParams($animName, $dur: 500ms, $delay: 0) } + +@mixin animToParams($animName, $dur: 500ms, $delay: 0) { + @include animation-name($animName); + @include animation-duration($dur); + @include animation-delay($delay); + @include animation-fill-mode(both); + @include animation-direction(normal); + @include animation-iteration-count(1); + @include animation-timing-function(ease-in-out); +} \ No newline at end of file diff --git a/platform/commonUI/general/res/sass/controls/_buttons.scss b/platform/commonUI/general/res/sass/controls/_buttons.scss index 6ff95634d5..5afad277b2 100644 --- a/platform/commonUI/general/res/sass/controls/_buttons.scss +++ b/platform/commonUI/general/res/sass/controls/_buttons.scss @@ -36,15 +36,7 @@ $pad: $interiorMargin * $baseRatio; padding: 0 $pad; font-size: 0.7rem; vertical-align: top; - - .icon { - font-size: 0.8rem; - color: $colorKey; - } - - .title-label { - vertical-align: top; - } + @include btnSubtle($colorBtnBg, $colorBtnBgHov, $colorBtnFg, $colorBtnIcon); &.lg { font-size: 1rem; @@ -58,19 +50,13 @@ $pad: $interiorMargin * $baseRatio; padding: 0 ($pad / $baseRatio) / 2; } - &.major { + &.major, + &.key-edit { $bg: $colorBtnMajorBg; $hc: lighten($bg, 10%); @include btnSubtle($bg, $hc, $colorBtnMajorFg, $colorBtnMajorFg); } - &:not(.major) { - // bg, bgHov, fg, ic - @include btnSubtle($colorBtnBg, $colorBtnBgHov, $colorBtnFg, $colorBtnIcon); - } - &.pause-play { - - } &.t-save:before { content:'\e612'; font-family: symbolsfont; @@ -114,9 +100,18 @@ $pad: $interiorMargin * $baseRatio; &:before { @extend .ui-symbol; @extend .icon; - content:'\e623'; + content: '\e623'; } } + + .icon { + font-size: 0.8rem; + color: $colorKey; + } + + .title-label { + vertical-align: top; + } } .s-icon-btn { @@ -283,4 +278,3 @@ body.desktop .mini-tab-icon { color: $colorPausedBg !important; } } - diff --git a/platform/commonUI/general/res/sass/user-environ/_layout.scss b/platform/commonUI/general/res/sass/user-environ/_layout.scss index 8537c85520..9ab8e0f65f 100644 --- a/platform/commonUI/general/res/sass/user-environ/_layout.scss +++ b/platform/commonUI/general/res/sass/user-environ/_layout.scss @@ -237,30 +237,10 @@ body.desktop .pane .mini-tab-icon.toggle-pane { top: $ueTopBarH + $interiorMarginLg; } -.l-object-wrapper { - @extend .abs; - - .object-holder-main { - @extend .abs; - } - .l-edit-controls { - //@include trans-prop-nice((opacity, height), 0.25s); - border-bottom: 1px solid $colorInteriorBorder; - line-height: $ueEditToolBarH; - height: 0px; - opacity: 0; - .tool-bar { - right: $interiorMargin; - } - } -} - .l-object-wrapper-inner { @include trans-prop-nice-resize(0.25s); } - - .object-browse-bar .s-btn, .top-bar .buttons-main .s-btn, .top-bar .s-menu-btn, @@ -377,19 +357,50 @@ body.desktop { .s-status-editing { .l-object-wrapper { - @include pulseBorder($colorEditAreaFg, $dur: 1s, $opacity0: 0.3); - border-radius: $controlCr; + $t2Dur: $browseToEditAnimMs; + $t1Dur: $t2Dur / 2; + $pulseDur: $editBorderPulseMs; + $bC0: rgba($colorEditAreaFg, 0.5); + $bC100: rgba($colorEditAreaFg, 1); + background-color: $colorEditAreaBg; - border-color: $colorEditAreaFg; - border-width: 2px; - border-style: dotted; - .l-object-wrapper-inner { - @include absPosDefault(3px, hidden); + border-radius: $controlCr; + border: 1px dotted $bC0; + + // Transition 1 + @include keyframes(wrapperIn) { + from { border: 0px dotted transparent; padding: 0; } + to { border: 1px dotted $bC0; padding: 5px; } } + + // Do last + @include keyframes(pulseNew) { + from { border-color: $bC0; } + to { border-color: $bC100; } + } + + @include animation-name(wrapperIn, pulseNew); + @include animation-duration($t1Dur, $pulseDur); + @include animation-delay(0s, $t1Dur + $t2Dur); + @include animation-direction(normal, alternate); + @include animation-fill-mode(both, none); + @include animation-iteration-count(1, infinite); + @include animation-timing-function(ease-in-out, linear); + + .l-edit-controls { - height: $ueEditToolBarH + $interiorMargin; - margin-bottom: $interiorMargin; - opacity: 1; + height: 0; + border-bottom: 1px solid $colorInteriorBorder; + overflow: hidden; + // Transition 2: reveal edit controls + @include keyframes(editIn) { + from { border-bottom: 0px solid transparent; height: 0; margin-bottom: 0; } + to { border-bottom: 1px solid $colorInteriorBorder; height: $ueEditToolBarH + $interiorMargin; margin-bottom: $interiorMargin; } + } + @include animToParams(editIn, $dur: $t2Dur, $delay: $t1Dur); + .tool-bar { + right: $interiorMargin; + } } } } diff --git a/platform/commonUI/general/res/templates/controls/datetime-field.html b/platform/commonUI/general/res/templates/controls/datetime-field.html index 47551fa25b..5f1705bc98 100644 --- a/platform/commonUI/general/res/templates/controls/datetime-field.html +++ b/platform/commonUI/general/res/templates/controls/datetime-field.html @@ -29,10 +29,12 @@ !structure.validate(ngModel[field])), 'picker-icon': structure.format === 'utc' || !structure.format }"> - - + + + +
xMax || y < yMin || y > yMax) { - scope.$eval(attrs.mctClickElsewhere); + scope.$apply(function () { + scope.$eval(attrs.mctClickElsewhere); + }); } } diff --git a/platform/commonUI/general/test/directives/MCTClickElsewhereSpec.js b/platform/commonUI/general/test/directives/MCTClickElsewhereSpec.js index 2e782a1a01..53924b0dd7 100644 --- a/platform/commonUI/general/test/directives/MCTClickElsewhereSpec.js +++ b/platform/commonUI/general/test/directives/MCTClickElsewhereSpec.js @@ -104,6 +104,8 @@ define( }); it("triggers an evaluation of its related Angular expression", function () { + expect(mockScope.$apply).toHaveBeenCalled(); + mockScope.$apply.mostRecentCall.args[0](); expect(mockScope.$eval) .toHaveBeenCalledWith(testAttrs.mctClickElsewhere); }); diff --git a/platform/commonUI/themes/espresso/res/sass/_constants.scss b/platform/commonUI/themes/espresso/res/sass/_constants.scss index bb774fb7a8..1ab6d734f3 100644 --- a/platform/commonUI/themes/espresso/res/sass/_constants.scss +++ b/platform/commonUI/themes/espresso/res/sass/_constants.scss @@ -14,7 +14,7 @@ $colorAHov: #fff; $contrastRatioPercent: 7%; $hoverRatioPercent: 10%; $basicCr: 3px; -$controlCr: 3px; +$controlCr: 2px; $smallCr: 2px; // Buttons and Controls diff --git a/platform/core/test/objects/DomainObjectProviderSpec.js b/platform/core/test/objects/DomainObjectProviderSpec.js index c8450551f4..4e60f19645 100644 --- a/platform/core/test/objects/DomainObjectProviderSpec.js +++ b/platform/core/test/objects/DomainObjectProviderSpec.js @@ -82,16 +82,6 @@ define( expect(result.a.getModel()).toEqual(model); }); - //TODO: Disabled for NEM Beta - xit("provides a new, fully constituted domain object for a" + - " provided model", function () { - var model = { someKey: "some value"}, - result; - result = provider.newObject("a", model); - expect(result.getId()).toEqual("a"); - expect(result.getModel()).toEqual(model); - }); - }); } ); diff --git a/platform/entanglement/src/actions/CopyAction.js b/platform/entanglement/src/actions/CopyAction.js index 2ed9725c41..0c5ab99755 100644 --- a/platform/entanglement/src/actions/CopyAction.js +++ b/platform/entanglement/src/actions/CopyAction.js @@ -81,6 +81,7 @@ define( if (phase.toLowerCase() === 'preparing' && !this.dialog) { this.dialog = this.dialogService.showBlockingMessage({ title: "Preparing to copy objects", + hint: "Do not navigate away from this page or close this browser tab while this message is displayed.", unknownProgress: true, severity: "info" }); diff --git a/platform/entanglement/src/policies/CrossSpacePolicy.js b/platform/entanglement/src/policies/CrossSpacePolicy.js index 29aab5a484..39d05bd532 100644 --- a/platform/entanglement/src/policies/CrossSpacePolicy.js +++ b/platform/entanglement/src/policies/CrossSpacePolicy.js @@ -24,10 +24,7 @@ define( [], function () { - var DISALLOWED_ACTIONS = [ - "move", - "copy" - ]; + var DISALLOWED_ACTIONS = ["move"]; /** * This policy prevents performing move/copy/link actions across diff --git a/platform/entanglement/test/policies/CrossSpacePolicySpec.js b/platform/entanglement/test/policies/CrossSpacePolicySpec.js index ef09a47619..38bb1f9606 100644 --- a/platform/entanglement/test/policies/CrossSpacePolicySpec.js +++ b/platform/entanglement/test/policies/CrossSpacePolicySpec.js @@ -70,27 +70,25 @@ define( policy = new CrossSpacePolicy(); }); - ['move', 'copy'].forEach(function (key) { - describe("for " + key + " actions", function () { - beforeEach(function () { - testActionMetadata.key = key; - }); + describe("for move actions", function () { + beforeEach(function () { + testActionMetadata.key = 'move'; + }); - it("allows same-space changes", function () { - expect(policy.allow(mockAction, sameSpaceContext)) - .toBe(true); - }); + it("allows same-space changes", function () { + expect(policy.allow(mockAction, sameSpaceContext)) + .toBe(true); + }); - it("disallows cross-space changes", function () { - expect(policy.allow(mockAction, crossSpaceContext)) - .toBe(false); - }); + it("disallows cross-space changes", function () { + expect(policy.allow(mockAction, crossSpaceContext)) + .toBe(false); + }); - it("allows actions with no selectedObject", function () { - expect(policy.allow(mockAction, { - domainObject: makeObject('a') - })).toBe(true); - }); + it("allows actions with no selectedObject", function () { + expect(policy.allow(mockAction, { + domainObject: makeObject('a') + })).toBe(true); }); }); diff --git a/platform/features/conductor/test/ConductorRepresenterSpec.js b/platform/features/conductor/test/ConductorRepresenterSpec.js index 83d37fb8a6..bd60b303e1 100644 --- a/platform/features/conductor/test/ConductorRepresenterSpec.js +++ b/platform/features/conductor/test/ConductorRepresenterSpec.js @@ -42,7 +42,7 @@ define( 'parent' ]; - xdescribe("ConductorRepresenter", function () { + describe("ConductorRepresenter", function () { var mockThrottle, mockConductorService, mockCompile, diff --git a/platform/features/table/res/templates/mct-table.html b/platform/features/table/res/templates/mct-table.html index d057ff5e1c..fc18775742 100644 --- a/platform/features/table/res/templates/mct-table.html +++ b/platform/features/table/res/templates/mct-table.html @@ -3,7 +3,7 @@ title="Export This View's Data"> Export -
+
diff --git a/platform/features/table/src/controllers/MCTTableController.js b/platform/features/table/src/controllers/MCTTableController.js index 0acb173274..bf5ea9218c 100644 --- a/platform/features/table/src/controllers/MCTTableController.js +++ b/platform/features/table/src/controllers/MCTTableController.js @@ -86,6 +86,12 @@ define( */ $scope.$on('add:row', this.addRow.bind(this)); $scope.$on('remove:row', this.removeRow.bind(this)); + + /* + * Listen for resize events to trigger recalculation of table width + */ + $scope.resize = this.setElementSizes.bind(this); + } /** diff --git a/platform/features/table/test/DomainColumnSpec.js b/platform/features/table/test/DomainColumnSpec.js index e707c8dd0b..516c32deae 100644 --- a/platform/features/table/test/DomainColumnSpec.js +++ b/platform/features/table/test/DomainColumnSpec.js @@ -30,23 +30,21 @@ define( var TEST_DOMAIN_VALUE = "some formatted domain value"; describe("A domain column", function () { - var mockDataSet, + var mockDatum, testMetadata, mockFormatter, column; beforeEach(function () { - mockDataSet = jasmine.createSpyObj( - "data", - ["getDomainValue"] - ); + mockFormatter = jasmine.createSpyObj( "formatter", ["formatDomainValue", "formatRangeValue"] ); testMetadata = { key: "testKey", - name: "Test Name" + name: "Test Name", + format: "Test Format" }; mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE); @@ -57,24 +55,24 @@ define( expect(column.getTitle()).toEqual("Test Name"); }); - xit("looks up data from a data set", function () { - column.getValue(undefined, mockDataSet, 42); - expect(mockDataSet.getDomainValue) - .toHaveBeenCalledWith(42, "testKey"); - }); + describe("when given a datum", function () { + beforeEach(function () { + mockDatum = { + testKey: "testKeyValue" + }; + }); - xit("formats domain values as time", function () { - mockDataSet.getDomainValue.andReturn(402513731000); + it("looks up data from the given datum", function () { + expect(column.getValue(undefined, mockDatum)) + .toEqual({ text: TEST_DOMAIN_VALUE }); + }); - // Should have just given the value the formatter gave - expect(column.getValue(undefined, mockDataSet, 42).text) - .toEqual(TEST_DOMAIN_VALUE); + it("uses formatter to format domain values as requested", function () { + column.getValue(undefined, mockDatum); + expect(mockFormatter.formatDomainValue) + .toHaveBeenCalledWith("testKeyValue", "Test Format"); + }); - // Make sure that service interactions were as expected - expect(mockFormatter.formatDomainValue) - .toHaveBeenCalledWith(402513731000); - expect(mockFormatter.formatRangeValue) - .not.toHaveBeenCalled(); }); }); diff --git a/platform/features/timeline/bundle.js b/platform/features/timeline/bundle.js index ae7a283fc5..d8f8729808 100644 --- a/platform/features/timeline/bundle.js +++ b/platform/features/timeline/bundle.js @@ -472,6 +472,7 @@ define([ "implementation": TimelineZoomController, "depends": [ "$scope", + "$window", "TIMELINE_ZOOM_CONFIGURATION" ] }, diff --git a/platform/features/timeline/res/templates/activity-gantt.html b/platform/features/timeline/res/templates/activity-gantt.html index 1615431e91..8515872352 100644 --- a/platform/features/timeline/res/templates/activity-gantt.html +++ b/platform/features/timeline/res/templates/activity-gantt.html @@ -20,7 +20,7 @@ at runtime from the About dialog for additional information. -->
+ ng-style="{ width: zoomController.width(timelineController.end()) + 'px' }">
+ ng-style="{ width: zoomController.width(timelineController.end()) + 'px' }">
diff --git a/platform/features/timeline/src/controllers/TimelineController.js b/platform/features/timeline/src/controllers/TimelineController.js index 64900b586c..68ca8c4bc7 100644 --- a/platform/features/timeline/src/controllers/TimelineController.js +++ b/platform/features/timeline/src/controllers/TimelineController.js @@ -79,15 +79,6 @@ define( graphPopulator.populate(swimlanePopulator.get()); } - // Get pixel width for right pane, using zoom controller - function width(zoomController) { - var start = swimlanePopulator.start(), - end = swimlanePopulator.end(); - return zoomController.toPixels(zoomController.duration( - Math.max(end - start, MINIMUM_DURATION) - )); - } - // Refresh resource graphs function refresh() { if (graphPopulator) { @@ -121,10 +112,10 @@ define( // Expose active set of swimlanes return { /** - * Get the width, in pixels, of the timeline area - * @returns {number} width, in pixels + * Get the end of the displayed timeline, in milliseconds. + * @returns {number} the end of the displayed timeline */ - width: width, + end: swimlanePopulator.end.bind(swimlanePopulator), /** * Get the swimlanes which should currently be displayed. * @returns {TimelineSwimlane[]} the swimlanes diff --git a/platform/features/timeline/src/controllers/TimelineZoomController.js b/platform/features/timeline/src/controllers/TimelineZoomController.js index 990a83a5b3..620e871d11 100644 --- a/platform/features/timeline/src/controllers/TimelineZoomController.js +++ b/platform/features/timeline/src/controllers/TimelineZoomController.js @@ -22,27 +22,17 @@ define( [], function () { + var PADDING = 0.25; /** * Controls the pan-zoom state of a timeline view. * @constructor */ - function TimelineZoomController($scope, ZOOM_CONFIGURATION) { + function TimelineZoomController($scope, $window, ZOOM_CONFIGURATION) { // Prefer to start with the middle index var zoomLevels = ZOOM_CONFIGURATION.levels || [1000], zoomIndex = Math.floor(zoomLevels.length / 2), - tickWidth = ZOOM_CONFIGURATION.width || 200, - bounds = { x: 0, width: tickWidth }, - duration = 86400000; // Default duration in view - - // Round a duration to a larger value, to ensure space for editing - function roundDuration(value) { - // Ensure there's always an extra day or so - var tickCount = bounds.width / tickWidth, - sz = zoomLevels[zoomLevels.length - 1] * tickCount; - value *= 1.25; // Add 25% padding to start - return Math.ceil(value / sz) * sz; - } + tickWidth = ZOOM_CONFIGURATION.width || 200; function toMillis(pixels) { return (pixels / tickWidth) * zoomLevels[zoomIndex]; @@ -63,14 +53,21 @@ define( } } + function setScroll(x) { + $window.requestAnimationFrame(function () { + $scope.scroll.x = x; + $scope.$apply(); + }); + } + function initializeZoomFromTimespan(timespan) { var timelineDuration = timespan.getDuration(); zoomIndex = 0; - while (toMillis(bounds.width) < timelineDuration && + while (toMillis($scope.scroll.width) < timelineDuration && zoomIndex < zoomLevels.length - 1) { zoomIndex += 1; } - bounds.x = toPixels(timespan.getStart()); + setScroll(toPixels(timespan.getStart())); } function initializeZoom() { @@ -80,9 +77,6 @@ define( } } - $scope.$watch("scroll", function (scroll) { - bounds = scroll; - }); $scope.$watch("domainObject", initializeZoom); return { @@ -100,9 +94,10 @@ define( zoom: function (amount) { // Update the zoom level if called with an argument if (arguments.length > 0 && !isNaN(amount)) { + var bounds = $scope.scroll; var center = this.toMillis(bounds.x + bounds.width / 2); setZoomLevel(zoomIndex + amount); - bounds.x = this.toPixels(center) - bounds.width / 2; + setScroll(this.toPixels(center) - bounds.width / 2); } return zoomLevels[zoomIndex]; }, @@ -124,16 +119,14 @@ define( */ toMillis: toMillis, /** - * Get or set the current displayed duration. If used as a - * setter, this will typically be rounded up to ensure extra - * space is available at the right. - * @returns {number} duration, in milliseconds + * Get the pixel width necessary to fit the specified + * timestamp, expressed as an offset in milliseconds from + * the start of the timeline. + * @param {number} timestamp the time to display */ - duration: function (value) { - if (arguments.length > 0) { - duration = roundDuration(value); - } - return duration; + width: function (timestamp) { + var pixels = Math.ceil(toPixels(timestamp * (1 + PADDING))); + return Math.max($scope.scroll.width, pixels); } }; } diff --git a/platform/features/timeline/test/controllers/TimelineControllerSpec.js b/platform/features/timeline/test/controllers/TimelineControllerSpec.js index aa88866ebc..7912dba149 100644 --- a/platform/features/timeline/test/controllers/TimelineControllerSpec.js +++ b/platform/features/timeline/test/controllers/TimelineControllerSpec.js @@ -214,23 +214,6 @@ define( }); - it("reports full scrollable width using zoom controller", function () { - var mockZoom = jasmine.createSpyObj('zoom', ['toPixels', 'duration']); - mockZoom.toPixels.andReturn(54321); - mockZoom.duration.andReturn(12345); - - // Initially populate - fireWatch('domainObject', mockDomainObject); - - expect(controller.width(mockZoom)).toEqual(54321); - // Verify interactions; we took zoom's duration for our start/end, - // and converted it to pixels. - // First, check that we used the start/end (from above) - expect(mockZoom.duration).toHaveBeenCalledWith(12321 - 42); - // Next, verify that the result was passed to toPixels - expect(mockZoom.toPixels).toHaveBeenCalledWith(12345); - }); - it("provides drag handles", function () { // TimelineDragPopulator et al are tested for these, // so just verify that handles are indeed exposed. diff --git a/platform/features/timeline/test/controllers/TimelineZoomControllerSpec.js b/platform/features/timeline/test/controllers/TimelineZoomControllerSpec.js index 47e79fefa8..18ed911671 100644 --- a/platform/features/timeline/test/controllers/TimelineZoomControllerSpec.js +++ b/platform/features/timeline/test/controllers/TimelineZoomControllerSpec.js @@ -28,6 +28,7 @@ define( describe("The timeline zoom state controller", function () { var testConfiguration, mockScope, + mockWindow, controller; beforeEach(function () { @@ -35,10 +36,16 @@ define( levels: [1000, 2000, 3500], width: 12321 }; - mockScope = jasmine.createSpyObj("$scope", ['$watch']); + mockScope = + jasmine.createSpyObj("$scope", ['$watch', '$apply']); mockScope.commit = jasmine.createSpy('commit'); + mockScope.scroll = { x: 0, width: 1000 }; + mockWindow = { + requestAnimationFrame: jasmine.createSpy('raf') + }; controller = new TimelineZoomController( mockScope, + mockWindow, testConfiguration ); }); @@ -47,12 +54,6 @@ define( expect(controller.zoom()).toEqual(2000); }); - it("allows duration to be changed", function () { - var initial = controller.duration(); - controller.duration(initial * 3.33); - expect(controller.duration() > initial).toBeTruthy(); - }); - it("handles time-to-pixel conversions", function () { var zoomLevel = controller.zoom(); expect(controller.toPixels(zoomLevel)).toEqual(12321); @@ -70,11 +71,6 @@ define( expect(controller.zoom()).toEqual(3500); }); - it("observes scroll bounds", function () { - expect(mockScope.$watch) - .toHaveBeenCalledWith("scroll", jasmine.any(Function)); - }); - describe("when watches have fired", function () { var mockDomainObject, mockPromise, @@ -115,6 +111,10 @@ define( mockScope.$watch.calls.forEach(function (call) { call.args[1](mockScope[call.args[0]]); }); + + mockWindow.requestAnimationFrame.calls.forEach(function (call) { + call.args[0](); + }); }); it("zooms to fit the timeline", function () { @@ -125,6 +125,27 @@ define( expect(Math.round(controller.toMillis(x2))) .toBeGreaterThan(testEnd); }); + + it("provides a width which is not less than scroll area width", function () { + var testPixel = mockScope.scroll.width / 4, + testMillis = controller.toMillis(testPixel); + expect(controller.width(testMillis)) + .not.toBeLessThan(mockScope.scroll.width); + }); + + it("provides a width with some margin past timestamp", function () { + var testPixel = mockScope.scroll.width * 4, + testMillis = controller.toMillis(testPixel); + expect(controller.width(testMillis)) + .toBeGreaterThan(controller.toPixels(testMillis)); + }); + + it("provides a width which does not greatly exceed timestamp", function () { + var testPixel = mockScope.scroll.width * 4, + testMillis = controller.toMillis(testPixel); + expect(controller.width(testMillis)) + .toBeLessThan(controller.toPixels(testMillis * 2)); + }); }); }); diff --git a/platform/representation/src/gestures/DropGesture.js b/platform/representation/src/gestures/DropGesture.js index 966618d88c..8da98b38a7 100644 --- a/platform/representation/src/gestures/DropGesture.js +++ b/platform/representation/src/gestures/DropGesture.js @@ -61,8 +61,7 @@ define( { x: event.pageX - rect.left, y: event.pageY - rect.top - }, - domainObject + } ); } } diff --git a/platform/representation/test/gestures/DropGestureSpec.js b/platform/representation/test/gestures/DropGestureSpec.js index 79ed9d1139..f8ed7c80da 100644 --- a/platform/representation/test/gestures/DropGestureSpec.js +++ b/platform/representation/test/gestures/DropGestureSpec.js @@ -34,8 +34,7 @@ define( TEST_ID = "test-id", DROP_ID = "drop-id"; - //TODO: Disabled for NEM Beta - xdescribe("The drop gesture", function () { + describe("The drop gesture", function () { var mockDndService, mockQ, mockElement, @@ -144,23 +143,6 @@ define( expect(mockCompose.perform).toHaveBeenCalled(); }); - - it("does not invoke compose on drop in browse mode for non-folders", function () { - // Set the mockDomainObject to not have the editor capability - mockDomainObject.hasCapability.andReturn(false); - // Set the mockDomainObject to not have a type of folder - mockDomainObject.getModel.andReturn({type: 'notAFolder'}); - - callbacks.dragover(mockEvent); - expect(mockAction.getActions).toHaveBeenCalledWith({ - key: 'compose', - selectedObject: mockDraggedObject - }); - callbacks.drop(mockEvent); - expect(mockCompose.perform).not.toHaveBeenCalled(); - }); - - it("invokes compose on drop in browse mode for folders", function () { // Set the mockDomainObject to not have the editor capability mockDomainObject.hasCapability.andReturn(false); diff --git a/protractor/common/EditItem.js b/protractor/common/EditItem.js index fffd8108a9..ceffcdb3ea 100644 --- a/protractor/common/EditItem.js +++ b/protractor/common/EditItem.js @@ -34,8 +34,8 @@ var EditItem = (function () { EditItem.prototype.EditButton = function () { return element.all(by.css('[ng-click="parameters.action.perform()"]')).filter(function (arg) { return arg.getAttribute("title").then(function (title){ - //expect(title).toEqual("Edit this object."); - return title == 'Edit this object.'; + //expect(title).toEqual("Edit"); + return title == 'Edit'; }) }); };