提交 a68e63f7 编写于 作者: P Piotr Bryk

Merge pull request #195 from bryk/extract-card-column-layout

Column layout for replica set list view
......@@ -38,6 +38,7 @@ gulp.task('styles', function() {
return gulp.src(path.join(conf.paths.frontendSrc, '**/*.scss'))
.pipe(gulpSourcemaps.init())
.pipe(gulpSass(sassOptions))
.pipe(gulpAutoprefixer())
.pipe(gulpSourcemaps.write("."))
.pipe(gulp.dest(conf.paths.serve))
// If BrowserSync is running, inform it that styles have changed.
......
......@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<md-card>
<md-card-content class="kd-replicaset-card">
<md-card class="kd-replicaset-card">
<md-card-content>
<div layout="row" layout-align="space-between center">
<div flex layout="column">
<a ng-href="{{::ctrl.getReplicaSetDetailHref()}}" flex>
......
......@@ -15,10 +15,11 @@
import ReplicaSetCardController from './replicasetcard_controller';
/**
* Returns directive definition object for logs menu.
* Returns directive definition object for replica set card directive.
*
* @return {!angular.Directive}
*/
export default function logsMenuDirective() {
export default function replicaSetCardDirective() {
return {
scope: {},
bindToController: {
......
......@@ -18,8 +18,7 @@ limitations under the License.
<md-icon class="material-icons">add</md-icon>
</md-button>
<div layout="row" layout-wrap layout-margin layout-align="center center">
<div ng-repeat="replicaSet in ::ctrl.replicaSets">
<kd-replica-set-card replica-set="::replicaSet"></kd-replica-set-card>
</div>
</div>
<kd-replica-set-list-container>
<kd-replica-set-card ng-repeat="replicaSet in ::ctrl.replicaSets" replica-set="::replicaSet">
</kd-replica-set-card>
</kd-replica-set-list-container>
......@@ -17,6 +17,7 @@ import logsMenuDirective from './logsmenu_directive';
import middleEllipsisFilter from 'common/filters/middleellipsis_filter';
import replicaSetCardDirective from './replicasetcard_directive';
import replicaSetDetailModule from 'replicasetdetail/replicasetdetail_module';
import replicaSetListContainer from './replicasetlistcontainer_directive';
/**
* Angular module for the Replica Set list view.
......@@ -34,4 +35,5 @@ export default angular.module(
.config(stateConfig)
.filter('middleEllipsis', middleEllipsisFilter)
.directive('logsMenu', logsMenuDirective)
.directive('kdReplicaSetListContainer', replicaSetListContainer)
.directive('kdReplicaSetCard', replicaSetCardDirective);
<!--
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="kd-replica-set-list-container" ng-transclude></div>
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Computes optimal height of the given container that will fit all its child elements into N
* column layout. N is based on current media query from the $mdMedia service.
*
* @param {!Element} container Container element that has child elements to be arranged in columns.
* It should be a flexbox container with columns wrapped.
* @param {function(string):boolean} mdMedia Angular Material $mdMedia service
* @return {number}
*/
export default function computeContainerHeight(container, mdMedia) {
/** @type {!Array<number>} */
let childHeights = Array.prototype.map.call(container.children, (child) => child.offsetHeight);
let columnCount = computeColumnCount(mdMedia);
return binarySearchOptimalHeight(childHeights, columnCount);
}
/**
* Returns optimal number of columns for current window size.
*
* @param {function(string):boolean} mdMedia Angular Material $mdMedia service
* @return {number}
*/
function computeColumnCount(mdMedia) {
if (mdMedia('gt-md')) {
return 3;
} else if (mdMedia('md')) {
return 2;
} else {
return 1;
}
}
/**
* Does binary search to find minimal integer I such that with height I elements fit into numColumns
* and with height I - 1 they do not.
*
* @param {!Array<number>} heights
* @param {number} numColumns
* @return {number}
*/
export function binarySearchOptimalHeight(heights, numColumns) {
let sum = Math.ceil(heights.reduce((a, b) => a + b, 0));
let height = 0;
let left = 0;
let right = sum;
for (;;) {
height = Math.floor((left + right) / 2);
let [leftChunks, rightChunks] = getActualColumnCount(heights, height - 1, height);
if ((leftChunks > numColumns && rightChunks <= numColumns) || (left === right)) {
break;
} else if (leftChunks > numColumns) {
left = height + 1;
} else {
right = height;
}
}
return height;
}
/**
* Returns actual column count for the given array of element heights and two actual heights.
* Two values are returned, first for leftHeight and the other for rightHeight. Infinity is
* returned if for any height (left of right) there is an item that does not fit it.
*
* @param {!Array<number>} heights
* @param {number} leftHeight
* @param {number} rightHeight
* @return {!Array<number>}
*/
function getActualColumnCount(heights, leftHeight, rightHeight) {
let sizeLeftChunks = 0;
let currentLeftHeight = 0;
let doesNotFitLeftHeight = false;
let doesNotFitRightHeight = false;
let sizeRightChunks = 0;
let currentRightHeight = 0;
for (let item of heights) {
if (item > leftHeight) {
doesNotFitLeftHeight = true;
}
if (item > rightHeight) {
doesNotFitRightHeight = true;
}
if (currentLeftHeight + item > leftHeight) {
currentLeftHeight = item;
sizeLeftChunks += 1;
} else {
currentLeftHeight += item;
}
if (currentRightHeight + item > rightHeight) {
currentRightHeight = item;
sizeRightChunks += 1;
} else {
currentRightHeight += item;
}
}
if (currentLeftHeight !== 0) {
sizeLeftChunks += 1;
}
if (currentRightHeight !== 0) {
sizeRightChunks += 1;
}
if (doesNotFitLeftHeight) {
sizeLeftChunks = Number.POSITIVE_INFINITY;
}
if (doesNotFitRightHeight) {
sizeRightChunks = Number.POSITIVE_INFINITY;
}
return [sizeLeftChunks, sizeRightChunks];
}
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
.kd-replica-set-list-container {
align-content: center;
align-items: center;
display: flex;
flex-flow: column wrap;
justify-content: top;
}
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import computeContainerHeight from './replicasetlistcontainer';
/**
* Returns directive definition object for the replica set list directive.
*
* @param {function(string):boolean} $mdMedia Angular Material $mdMedia service
* @return {!angular.Directive}
* @ngInject
*/
export default function replicaSetListContainerDirective($mdMedia) {
return {
scope: {},
transclude: true,
/**
* @param {!angular.Scope} scope
* @param {!angular.JQLite} jQliteElem
*/
link: function(scope, jQliteElem) {
/** @type {!Element} */
let element = jQliteElem[0];
let container = element.querySelector('.kd-replica-set-list-container');
if (!container) {
throw new Error('Required child element .kd-replica-set-list-container not found');
}
let nonNullContainer = container;
scope.$watch(() => computeContainerHeight(nonNullContainer, $mdMedia), (newHeight) => {
container.style.height = `${newHeight}px`;
});
},
templateUrl: 'replicasetlist/replicasetlistcontainer.html',
};
}
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {binarySearchOptimalHeight} from 'replicasetlist/replicasetlistcontainer';
describe('Replica set list container', () => {
it('should compute optimal height', () => {
let testData = [
// The format is: [heights array, num columns, expected result].
[[], 0, 0],
[[], 1, 0],
[[], 2321, 0],
[[1], 0, 1],
[[1], 1, 1],
[[1], 2321, 1],
[[2321], 1, 2321],
[[2321], 2321, 2321],
[[1, 1], 1, 2],
[[1, 1], 2, 1],
[[1, 2, 5, 8, 1, 3, 6, 2, 3], 1, 31],
[[1, 2, 5, 8, 1, 3, 6, 2, 3], 2, 16],
[[1, 2, 5, 8, 1, 3, 6, 2, 3], 3, 12],
[[1, 2, 5, 8, 1, 3, 6, 2, 3], 4, 9],
[[1, 2, 5, 8, 1, 3, 6, 2, 3], 5, 8],
[[1, 2, 5, 8, 1, 3, 6, 2, 3], 10, 8],
[[1, 2, 5, 8, 1, 3, 6, 2, 3], 2321, 8],
// Below test data is based on real examples.
[[237, 237, 197, 197, 197, 197, 197, 251, 204, 237, 246, 204, 197, 211, 232, 211], 8, 474],
[[237, 237, 197, 197, 197, 197, 197, 251, 204, 237, 246, 204, 197, 211, 232, 211], 7, 612],
[[237, 237, 197, 197, 197, 197, 197, 251, 204, 237, 246, 204, 197, 211, 232, 211], 6, 654],
[[237, 237, 197, 197, 197, 197, 197, 251, 204, 237, 246, 204, 197, 211, 232, 211], 5, 788],
[[237, 237, 197, 197, 197, 197, 197, 251, 204, 237, 246, 204, 197, 211, 232, 211], 4, 891],
[[237, 237, 197, 197, 197, 197, 197, 251, 204, 237, 246, 204, 197, 211, 232, 211], 3, 1262],
[[237, 237, 197, 197, 197, 197, 197, 251, 204, 237, 246, 204, 197, 211, 232, 211], 2, 1742],
[[237, 237, 197, 197, 197, 197, 197, 251, 204, 237, 246, 204, 197, 211, 232, 211], 1, 3452],
[[237, 237, 197, 197, 197, 197, 197, 251, 204, 237, 246, 204, 197, 211, 232, 211], 0, 3452],
[[237, 237, 197, 197, 197, 197, 10000, 204, 237, 246, 204, 197, 211, 232, 211], 10, 10000],
[[237, 237, 197, 197, 197, 197, 10000, 204, 237, 246, 204, 197, 211, 232, 211], 5, 10000],
[[237, 237, 197, 197, 197, 197, 10000, 204, 237, 246, 204, 197, 211, 232, 211], 3, 10000],
[[237, 237, 197, 197, 197, 197, 10000, 204, 237, 246, 204, 197, 211, 232, 211], 2, 11262],
[[237, 237, 197, 197, 197, 197, 10000, 204, 237, 246, 204, 197, 211, 232, 211], 1, 13004],
[[237, 237, 197, 197, 197, 197, 10000, 204, 237, 246, 204, 197, 211, 232, 211], 0, 13004],
];
for (let [heights, numColumns, expected] of testData) {
let actual = binarySearchOptimalHeight(heights, numColumns);
expect(actual).toBe(
expected, `Expected height to be ${expected} but was ${actual}. ` +
`Required number of columns: ${numColumns}, heights: [${heights}]`);
}
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册