Merge pull request #619 from prymitive/refactor-resize

fix(ui): refactor browser window resize handler
This commit is contained in:
Łukasz Mierzwa
2019-04-13 11:24:47 +01:00
committed by GitHub
11 changed files with 168 additions and 153 deletions

View File

@@ -50,7 +50,6 @@
"react-tippy": "1.2.3",
"react-transition-group": "2.9.0",
"react-truncate": "2.4.0",
"screen-orientation": "1.0.3",
"whatwg-fetch": "3.0.0"
},
"scripts": {

View File

@@ -5,7 +5,7 @@
.components-animation-fade-appear-active,
.components-animation-fade-enter-active {
opacity: 1;
transition: opacity 0.15s ease-in;
transition: all 0.3s ease-in;
}
.components-animation-fade-exit {
@@ -13,5 +13,5 @@
}
.components-animation-fade-exit-active {
opacity: 0.01;
transition: opacity 0.15s ease-out;
transition: all 0.3s ease-out;
}

View File

@@ -8,7 +8,7 @@ import "./index.css";
const MountFade = ({ children, duration, ...props }) => (
<CSSTransition
classNames="components-animation-fade"
timeout={150}
timeout={300}
appear={true}
enter={true}
exit={true}

View File

@@ -55,7 +55,8 @@ const AlertGroup = observer(
group: APIGroup.isRequired,
showAlertmanagers: PropTypes.bool.isRequired,
settingsStore: PropTypes.instanceOf(Settings).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
style: PropTypes.object
};
constructor(props) {
@@ -154,7 +155,8 @@ const AlertGroup = observer(
group,
showAlertmanagers,
afterUpdate,
silenceFormStore
silenceFormStore,
style
} = this.props;
let footerAlertmanagers = [];
@@ -175,8 +177,8 @@ const AlertGroup = observer(
}
return (
<MountFade>
<div className="components-grid-alertgrid-alertgroup p-1">
<div className="components-grid-alertgrid-alertgroup p-1" style={style}>
<MountFade in={true}>
<div className="card">
<GroupHeader
collapseStore={this.collapse}
@@ -235,8 +237,8 @@ const AlertGroup = observer(
/>
) : null}
</div>
</div>
</MountFade>
</MountFade>
</div>
);
}
}

View File

@@ -12,4 +12,23 @@ const GridSizesConfig = [
{ mq: "5600px", columns: 8, gutter: 0 }
];
export { GridSizesConfig };
const GetGridElementWidth = canvasWidth =>
Math.floor(
canvasWidth < 800
? canvasWidth
: canvasWidth < 1400
? canvasWidth / 2
: canvasWidth < 2100
? canvasWidth / 3
: canvasWidth < 2800
? canvasWidth / 4
: canvasWidth < 3500
? canvasWidth / 5
: canvasWidth < 4200
? canvasWidth / 6
: canvasWidth < 5600
? canvasWidth / 7
: canvasWidth / 8
);
export { GridSizesConfig, GetGridElementWidth };

View File

@@ -1,51 +1,5 @@
.components-grid-alertgrid-alertgroup {
width: 100%;
}
@media screen and (min-width: 800px) and (max-width: 1399px) {
.components-grid-alertgrid-alertgroup {
width: 50%;
}
}
@media screen and (min-width: 1400px) and (max-width: 2099px) {
.components-grid-alertgrid-alertgroup {
width: 33.333%;
}
}
@media screen and (min-width: 2100px) and (max-width: 2799px) {
.components-grid-alertgrid-alertgroup {
width: 25%;
}
}
@media screen and (min-width: 2800px) and (max-width: 3499px) {
.components-grid-alertgrid-alertgroup {
width: 20%;
}
}
@media screen and (min-width: 3500px) and (max-width: 4199px) {
.components-grid-alertgrid-alertgroup {
width: 16.666%;
}
}
@media screen and (min-width: 4200px) and (max-width: 4899px) {
.components-grid-alertgrid-alertgroup {
width: 14.285%;
}
}
@media screen and (min-width: 4900px) and (max-width: 5599px) {
.components-grid-alertgrid-alertgroup {
width: 14.285%;
}
}
@media screen and (min-width: 5600px) {
.components-grid-alertgrid-alertgroup {
width: 12.5%;
}
transition-property: top, left;
transition-duration: 0.3s;
transition-timing-function: ease;
}

View File

@@ -8,6 +8,10 @@ import FontFaceObserver from "fontfaceobserver";
import moment from "moment";
import { debounce } from "lodash";
import ReactResizeDetector from "react-resize-detector";
import MasonryInfiniteScroller from "react-masonry-infinite";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -17,7 +21,7 @@ import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { AlertGroup } from "./AlertGroup";
import { GridSizesConfig } from "./Constants";
import { GridSizesConfig, GetGridElementWidth } from "./Constants";
import "./index.css";
@@ -29,6 +33,25 @@ const AlertGrid = observer(
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
constructor(props) {
super(props);
// this is used to track viewport width, when browser window is resized
// we need to recreate the entire grid object to apply new column count
// and group size
this.viewport = observable(
{
width: document.body.clientWidth,
update() {
this.width = document.body.clientWidth;
}
},
{
update: action.bound
}
);
}
// store reference to generated masonry component so we can call it
// to repack the grid after any component was re-rendered, which could
// alter its size breaking grid layout
@@ -174,6 +197,10 @@ const AlertGrid = observer(
return (
<React.Fragment>
<ReactResizeDetector
handleWidth
onResize={debounce(this.viewport.update, 100)}
/>
<MasonryInfiniteScroller
ref={this.storeMasonryRef}
pack={true}
@@ -203,6 +230,9 @@ const AlertGrid = observer(
afterUpdate={this.masonryRepack}
settingsStore={settingsStore}
silenceFormStore={silenceFormStore}
style={{
width: GetGridElementWidth(this.viewport.width)
}}
/>
))}
</MasonryInfiniteScroller>

View File

@@ -13,9 +13,15 @@ import { AlertGrid } from ".";
let alertStore;
let settingsStore;
let silenceFormStore;
let bodyWidth;
beforeAll(() => {
jest.useFakeTimers();
Object.defineProperty(document.body, "clientWidth", {
get: () => {
return bodyWidth;
}
});
});
beforeEach(() => {
@@ -74,6 +80,18 @@ const MockGroupList = (count, alertPerGroup) => {
alertStore.data.groups = groups;
};
const VerifyColumnCount = (innerWidth, columns) => {
bodyWidth = innerWidth;
MockGroupList(60, 5);
const tree = ShallowAlertGrid();
expect(
tree
.find("AlertGroup")
.at(0)
.props().style.width
).toBe(Math.floor(innerWidth / columns));
};
describe("<AlertGrid />", () => {
it("renders only first 50 alert groups", () => {
MockGroupList(60, 5);
@@ -322,4 +340,89 @@ describe("<AlertGrid />", () => {
advanceBy(60 * 1000);
jest.runOnlyPendingTimers();
});
it("renders 1 column with document.body.clientWidth=799", () => {
VerifyColumnCount(799, 1);
});
it("renders 2 columns with document.body.clientWidth=800", () => {
VerifyColumnCount(800, 2);
});
it("renders 2 columns with document.body.clientWidth=1399", () => {
VerifyColumnCount(1399, 2);
});
it("renders 3 columns with document.body.clientWidth=1400", () => {
VerifyColumnCount(1400, 3);
});
it("renders 3 columns with document.body.clientWidth=2099", () => {
VerifyColumnCount(2099, 3);
});
it("renders 4 columns with document.body.clientWidth=2100", () => {
VerifyColumnCount(2100, 4);
});
it("renders 4 columns with document.body.clientWidth=2799", () => {
VerifyColumnCount(2799, 4);
});
it("renders 5 columns with document.body.clientWidth=2800", () => {
VerifyColumnCount(2800, 5);
});
it("renders 5 columns with document.body.clientWidth=3499", () => {
VerifyColumnCount(3499, 5);
});
it("renders 6 columns with document.body.clientWidth=1399", () => {
VerifyColumnCount(3500, 6);
});
it("renders 6 columns with document.body.clientWidth=4199", () => {
VerifyColumnCount(4199, 6);
});
it("renders 7 columns with document.body.clientWidth=1399", () => {
VerifyColumnCount(4200, 7);
});
it("renders 7 columns with document.body.clientWidth=5599", () => {
VerifyColumnCount(5599, 7);
});
it("renders 8 columns with document.body.clientWidth=5600", () => {
VerifyColumnCount(5600, 8);
});
it("viewport resize also resizes alert groups", () => {
bodyWidth = 1980;
MockGroupList(60, 5);
const tree = ShallowAlertGrid();
expect(
tree
.find("AlertGroup")
.at(0)
.props().style.width
).toBe(1980 / 3);
bodyWidth = 1000;
// not sure how to force ReactResizeDetector to detect width change, so
// we directly call viewport update here
tree.instance().viewport.update();
expect(
tree
.find("AlertGroup")
.at(0)
.props().style.width
).toBe(1000 / 2);
});
it("doesn't crash on unmount", () => {
MockGroupList(60, 5);
const tree = ShallowAlertGrid();
tree.unmount();
});
});

View File

@@ -1,11 +1,8 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { observable, action } from "mobx";
import { observer } from "mobx-react";
import screenOrientation from "screen-orientation";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { SilenceFormStore } from "Stores/SilenceFormStore";
@@ -22,36 +19,6 @@ const Grid = observer(
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
constructor(props) {
super(props);
// this is used to track viewport width, when browser window is resized
// we need to recreate the entire grid object to apply new column count
// and group size
this.viewport = observable(
{
width: window.innerWidth,
orientation: screenOrientation().direction,
update() {
this.width = window.innerWidth;
this.orientation = screenOrientation().direction;
}
},
{
update: action.bound
}
);
}
componentDidMount() {
this.viewport.update();
window.addEventListener("resize", this.viewport.update);
}
componentWillUnmount() {
window.removeEventListener("resize", this.viewport.update);
}
render() {
const { alertStore, settingsStore, silenceFormStore } = this.props;
@@ -88,7 +55,6 @@ const Grid = observer(
))}
<AlertGrid
key={`${this.viewport.width}-${this.viewport.orientation}`}
alertStore={alertStore}
settingsStore={settingsStore}
silenceFormStore={silenceFormStore}

View File

@@ -94,24 +94,6 @@ describe("<Grid />", () => {
expect(tree.text()).toBe("<UpgradeNeeded />");
});
it("re-creates AlertGrid after viewport resize", () => {
// Different columns are positioned using css via fixed offsets, so
// it's hard to tell how many columns we have just by looking at the
// generated css
// This test only checks if we force re-render of the AlertGrid component
// by updating its key prop
global.innerWidth = 1980;
global.innerHeight = 1080;
const tree = ShallowGrid();
expect(tree.find("AlertGrid").key()).toBe("1980-landscape");
global.innerWidth = 500;
global.innerHeight = 1000;
global.dispatchEvent(new Event("resize"));
expect(tree.find("AlertGrid").key()).toBe("500-portrait");
});
it("unmounts without crashes", () => {
const tree = ShallowGrid();
tree.unmount();

View File

@@ -3829,11 +3829,6 @@ dom-serializer@0, dom-serializer@~0.1.1:
domelementtype "^1.3.0"
entities "^1.1.1"
dom-walk@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=
domain-browser@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
@@ -5077,14 +5072,6 @@ global-prefix@^3.0.0:
kind-of "^6.0.2"
which "^1.3.1"
global@~4.3.0:
version "4.3.2"
resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f"
integrity sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=
dependencies:
min-document "^2.19.0"
process "~0.5.1"
globals@^11.1.0, globals@^11.7.0:
version "11.11.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.11.0.tgz#dcf93757fa2de5486fbeed7118538adf789e9c2e"
@@ -7365,13 +7352,6 @@ mimic-fn@^2.0.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
min-document@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=
dependencies:
dom-walk "^0.1.0"
mini-css-extract-plugin@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz#ac0059b02b9692515a637115b0cc9fed3a35c7b0"
@@ -9101,11 +9081,6 @@ process@^0.11.10:
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
process@~0.5.1:
version "0.5.2"
resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf"
integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=
progress@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
@@ -10212,14 +10187,6 @@ schema-utils@^1.0.0:
ajv-errors "^1.0.0"
ajv-keywords "^3.1.0"
screen-orientation@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/screen-orientation/-/screen-orientation-1.0.3.tgz#a390951b13504417fa19293d5b7e1fb93e8766c4"
integrity sha1-o5CVGxNQRBf6GSk9W34fuT6HZsQ=
dependencies:
global "~4.3.0"
view-size "~1.0.1"
scss-tokenizer@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
@@ -11550,13 +11517,6 @@ vfile@^4.0.0:
unist-util-stringify-position "^2.0.0"
vfile-message "^2.0.0"
view-size@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/view-size/-/view-size-1.0.1.tgz#747d8285fab00896ba83e4c0dcf333e1e028ab11"
integrity sha1-dH2ChfqwCJa6g+TA3PMz4eAoqxE=
dependencies:
global "~4.3.0"
vm-browserify@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"