Merge remote-tracking branch 'origin/master' into table-export-934

Conflicts:
	platform/commonUI/general/res/sass/controls/_buttons.scss
	platform/features/table/res/templates/mct-table.html
This commit is contained in:
Victor Woeltjen
2016-06-15 10:05:20 -07:00
32 changed files with 318 additions and 253 deletions

View File

@@ -42,7 +42,7 @@ define(
'parent'
];
xdescribe("ConductorRepresenter", function () {
describe("ConductorRepresenter", function () {
var mockThrottle,
mockConductorService,
mockCompile,

View File

@@ -3,7 +3,7 @@
title="Export This View's Data">
Export
</a>
<div class="l-view-section scrolling" style="overflow: auto;">
<div class="l-view-section scrolling" style="overflow: auto;" mct-resize="resize()">
<table class="sizing-table">
<tbody>
<tr>

View File

@@ -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);
}
/**

View File

@@ -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();
});
});

View File

@@ -472,6 +472,7 @@ define([
"implementation": TimelineZoomController,
"depends": [
"$scope",
"$window",
"TIMELINE_ZOOM_CONFIGURATION"
]
},

View File

@@ -20,7 +20,7 @@
at runtime from the About dialog for additional information.
-->
<div class="t-timeline-gantt l-timeline-gantt s-timeline-gantt"
ng-class="{ sm: gantt.width(timespan, parameters.scroll, parameters.toPixels) < 25 }"
ng-class="timespan ? { sm: gantt.width(timespan, parameters.scroll, parameters.toPixels) < 25 } : {}"
title="{{model.name}}"
ng-controller="TimelineGanttController as gantt"
ng-style="timespan ? {

View File

@@ -128,7 +128,7 @@
<div style="overflow: hidden; position: absolute; left: 0; top: 0; right: 0; height: 30px;" mct-scroll-x="scroll.x">
<mct-include key="'timeline-ticks'"
parameters="{
fullWidth: timelineController.width(zoomController),
fullWidth: zoomController.width(timelineController.end()),
start: scroll.x,
width: scroll.width,
step: zoomController.toPixels(zoomController.zoom()),
@@ -141,7 +141,7 @@
mct-scroll-x="scroll.x"
mct-scroll-y="scroll.y">
<div class="l-width-control"
ng-style="{ width: timelineController.width(zoomController) + 'px' }">
ng-style="{ width: zoomController.width(timelineController.end()) + 'px' }">
<div class="t-swimlane s-swimlane l-swimlane"
ng-repeat="swimlane in timelineController.swimlanes()"
ng-class="{
@@ -197,7 +197,7 @@
<div mct-scroll-x="scroll.x"
class="t-pane-r-scroll-h-control l-scroll-control s-scroll-control">
<div class="l-width-control"
ng-style="{ width: timelineController.width(zoomController) + 'px' }">
ng-style="{ width: zoomController.width(timelineController.end()) + 'px' }">
</div>
</div>
</div>

View File

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

View File

@@ -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);
}
};
}

View File

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

View File

@@ -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));
});
});
});