mirror of
https://github.com/prymitive/karma
synced 2026-05-07 03:26:52 +00:00
fix(ui): refactor browser window resize handler
bricks.js already handles resize but it needs fixed width on elements to work properly
This commit is contained in:
@@ -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": {
|
||||
|
||||
@@ -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 = [];
|
||||
@@ -176,7 +178,10 @@ const AlertGroup = observer(
|
||||
|
||||
return (
|
||||
<MountFade>
|
||||
<div className="components-grid-alertgrid-alertgroup p-1">
|
||||
<div
|
||||
className="components-grid-alertgrid-alertgroup p-1"
|
||||
style={style}
|
||||
>
|
||||
<div className="card">
|
||||
<GroupHeader
|
||||
collapseStore={this.collapse}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
.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%;
|
||||
}
|
||||
}
|
||||
@@ -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,9 +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 "./index.css";
|
||||
import { GridSizesConfig, GetGridElementWidth } from "./Constants";
|
||||
|
||||
const AlertGrid = observer(
|
||||
class AlertGrid extends Component {
|
||||
@@ -29,6 +31,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 +195,10 @@ const AlertGrid = observer(
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ReactResizeDetector
|
||||
handleWidth
|
||||
onResize={debounce(this.viewport.update, 100)}
|
||||
/>
|
||||
<MasonryInfiniteScroller
|
||||
ref={this.storeMasonryRef}
|
||||
pack={true}
|
||||
@@ -203,6 +228,9 @@ const AlertGrid = observer(
|
||||
afterUpdate={this.masonryRepack}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
style={{
|
||||
width: GetGridElementWidth(this.viewport.width)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</MasonryInfiniteScroller>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
|
||||
40
ui/yarn.lock
40
ui/yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user