Skip to content

Commit

Permalink
feat(ui): allow configuring grid size
Browse files Browse the repository at this point in the history
  • Loading branch information
prymitive committed Apr 14, 2019
1 parent c2d34cd commit b1c8985
Show file tree
Hide file tree
Showing 13 changed files with 300 additions and 78 deletions.
14 changes: 2 additions & 12 deletions ui/src/Components/Animations/MountFade/index.css
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
.components-animation-fade-appear,
.components-animation-fade-enter {
.components-animation-fade-appear {
opacity: 0.01;
}
.components-animation-fade-appear-active,
.components-animation-fade-enter-active {
.components-animation-fade-appear-active {
opacity: 1;
transition: all 0.3s ease-in;
}

.components-animation-fade-exit {
opacity: 1;
}
.components-animation-fade-exit-active {
opacity: 0.01;
transition: all 0.3s ease-out;
}
2 changes: 0 additions & 2 deletions ui/src/Components/Animations/MountFade/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ const MountFade = ({ children, duration, ...props }) => (
classNames="components-animation-fade"
timeout={300}
appear={true}
enter={true}
exit={true}
{...props}
>
{children}
Expand Down
32 changes: 12 additions & 20 deletions ui/src/Components/Grid/AlertGrid/GridSize.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,25 @@
const baseWidth = 400;

const MinWidth = canvasWidth =>
Math.floor(
baseWidth + (canvasWidth / Math.min(canvasWidth, baseWidth * 2)) * 10
);

// grid sizes, defines how many columns are used depending on the screen width
// this is config as expected by https://github.com/callmecavs/bricks.js#sizes
const GridSizesConfig = canvasWidth => {
const GridSizesConfig = (canvasWidth, baseWidth) => {
const generatedSizes = [];
for (let i = 2; i < 20; i++) {
generatedSizes.push({
mq: `${i * MinWidth(i * baseWidth)}px`,
mq: `${i * baseWidth}px`,
columns: i,
gutter: 0
});
}
//console.info(JSON.stringify(generatedSizes));
return [...[{ columns: 1, gutter: 0 }], ...generatedSizes];
};

const GetGridElementWidth = canvasWidth => {
const mw = MinWidth(canvasWidth);
return Math.floor(
Math.min(
mw + (canvasWidth % mw) / Math.floor(canvasWidth / mw),
canvasWidth
)
);
};
const GetColumnsCount = (canvasWidth, baseWidth) =>
[{ mq: "0px", columns: 1 }, ...GridSizesConfig(canvasWidth, baseWidth)]
.filter(gs => gs.mq !== undefined)
.filter(gs => canvasWidth >= Number.parseInt(gs.mq))
.map(gs => gs.columns)
.pop();

const GetGridElementWidth = (canvasWidth, baseWidth) =>
Math.floor(canvasWidth / GetColumnsCount(canvasWidth, baseWidth));

export { MinWidth, GridSizesConfig, GetGridElementWidth };
export { GridSizesConfig, GetColumnsCount, GetGridElementWidth };
23 changes: 12 additions & 11 deletions ui/src/Components/Grid/AlertGrid/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,20 @@ const AlertGrid = observer(
this.viewport = observable(
{
width: document.body.clientWidth,
update() {
this.width = document.body.clientWidth;
},
get gridSizesConfig() {
return GridSizesConfig(this.width);
return GridSizesConfig(
this.width,
props.settingsStore.gridConfig.config.groupWidth
);
},
get groupWidth() {
return GetGridElementWidth(this.width);
},
update() {
this.width = document.body.clientWidth;
return GetGridElementWidth(
this.width,
props.settingsStore.gridConfig.config.groupWidth
);
}
},
{
Expand Down Expand Up @@ -194,12 +200,6 @@ const AlertGrid = observer(
font700.load(null, 30000).then(this.masonryRepack, () => {});
}

componentDidUpdate() {
// whenever grid component re-renders we need to ensure that grid elements
// are packed correctly
this.masonryRepack();
}

render() {
const { alertStore, settingsStore, silenceFormStore } = this.props;

Expand All @@ -210,6 +210,7 @@ const AlertGrid = observer(
onResize={debounce(this.viewport.update, 100)}
/>
<MasonryInfiniteScroller
key={settingsStore.gridConfig.config.groupWidth}
ref={this.storeMasonryRef}
pack={true}
sizes={this.viewport.gridSizesConfig}
Expand Down
54 changes: 24 additions & 30 deletions ui/src/Components/Grid/AlertGrid/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { MockAlert, MockAlertGroup } from "__mocks__/Alerts.js";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { MinWidth, GetGridElementWidth } from "./GridSize";
import { GetGridElementWidth } from "./GridSize";
import { AlertGrid } from ".";

let alertStore;
Expand Down Expand Up @@ -110,44 +110,36 @@ describe("<AlertGrid />", () => {
expect(alertGroups).toHaveLength(80);
});

it("calls masonryRepack() after update`", () => {
it("calling masonryRepack() calls forcePack() on Masonry instance`", () => {
const tree = ShallowAlertGrid();
const instance = tree.instance();
const repackSpy = jest.spyOn(instance, "masonryRepack");
// it's a shallow render so we don't really have masonry mounted, fake it
instance.masonryComponentReference.ref = {
forcePack: jest.fn()
};
instance.componentDidUpdate();
expect(repackSpy).toHaveBeenCalled();
instance.masonryRepack();
expect(instance.masonryComponentReference.ref.forcePack).toHaveBeenCalled();
});

it("masonryRepack() doesn't crash when masonryComponentReference.ref=false`", () => {
const tree = ShallowAlertGrid();
const instance = tree.instance();
const repackSpy = jest.spyOn(instance, "masonryRepack");
instance.masonryComponentReference.ref = false;
instance.componentDidUpdate();
expect(repackSpy).toHaveBeenCalled();
instance.masonryRepack();
});

it("masonryRepack() doesn't crash when masonryComponentReference.ref=null`", () => {
const tree = ShallowAlertGrid();
const instance = tree.instance();
const repackSpy = jest.spyOn(instance, "masonryRepack");
instance.masonryComponentReference.ref = null;
instance.componentDidUpdate();
expect(repackSpy).toHaveBeenCalled();
instance.masonryRepack();
});

it("masonryRepack() doesn't crash when masonryComponentReference.ref=undefined`", () => {
const tree = ShallowAlertGrid();
const instance = tree.instance();
const repackSpy = jest.spyOn(instance, "masonryRepack");
instance.masonryComponentReference.ref = undefined;
instance.componentDidUpdate();
expect(repackSpy).toHaveBeenCalled();
instance.masonryRepack();
});

it("calling storeMasonryRef() saves the ref in local store", () => {
Expand Down Expand Up @@ -345,17 +337,18 @@ describe("<AlertGrid />", () => {
// known breakpoints calculated from GridSize logic
[
{ breakpoint: 400, columns: 1 },
{ breakpoint: 820, columns: 2 },
{ breakpoint: 1245, columns: 3 },
{ breakpoint: 1680, columns: 4 },
{ breakpoint: 2125, columns: 5 },
{ breakpoint: 2580, columns: 6 },
{ breakpoint: 3045, columns: 7 },
{ breakpoint: 3520, columns: 8 },
{ breakpoint: 4005, columns: 9 },
{ breakpoint: 4500, columns: 1 }
{ breakpoint: 800, columns: 2 },
{ breakpoint: 1200, columns: 3 },
{ breakpoint: 1600, columns: 4 },
{ breakpoint: 2000, columns: 5 },
{ breakpoint: 2400, columns: 6 },
{ breakpoint: 3000, columns: 7 },
{ breakpoint: 3400, columns: 8 },
{ breakpoint: 3800, columns: 9 },
{ breakpoint: 4200, columns: 10 }
].map(t =>
it(`renders ${t.columns} column(s) on ${t.breakpoint} breakpoint`, () => {
settingsStore.gridConfig.config.groupWidth = 400;
VerifyColumnCount(t.canvas - 1, Math.max(1, t.columns - 1));
VerifyColumnCount(t.canvas, t.columns);
VerifyColumnCount(t.canvas + 1, t.columns);
Expand All @@ -369,24 +362,25 @@ describe("<AlertGrid />", () => {
{ canvas: 1280, columns: 3 },
{ canvas: 1366, columns: 3 },
{ canvas: 1440, columns: 3 },
{ canvas: 1600, columns: 3 },
{ canvas: 1680, columns: 3 },
{ canvas: 1600, columns: 4 },
{ canvas: 1680, columns: 4 },
{ canvas: 1920, columns: 4 },
{ canvas: 2048, columns: 4 },
{ canvas: 2560, columns: 5 },
{ canvas: 3840, columns: 8 }
{ canvas: 2048, columns: 5 },
{ canvas: 2560, columns: 6 },
{ canvas: 3840, columns: 9 }
].map(t =>
it(`renders ${t.columns} column(s) with ${t.canvas} resolution`, () => {
settingsStore.gridConfig.config.groupWidth = 400;
VerifyColumnCount(t.canvas, t.columns);
})
);

it("renders expected number of columns for every resolution", () => {
const minWidth = 400;
let lastColumns = 1;
for (let i = 100; i <= 4096; i++) {
const minWidth = MinWidth(i);
const expectedColumns = Math.max(Math.floor(i / minWidth), 1);
const columns = Math.floor(i / GetGridElementWidth(i));
const columns = Math.floor(i / GetGridElementWidth(i, minWidth));

expect({
resolution: i,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { Component } from "react";
import PropTypes from "prop-types";

import { observable, action, toJS } from "mobx";
import { observer } from "mobx-react";

import { debounce } from "lodash";

import InputRange from "react-input-range";

import { Settings } from "Stores/Settings";

import "./InputRange.scss";

const AlertGroupWidthConfiguration = observer(
class AlertGroupWidthConfiguration extends Component {
static propTypes = {
settingsStore: PropTypes.instanceOf(Settings).isRequired
};

constructor(props) {
super(props);

this.config = observable({
groupWidth: toJS(props.settingsStore.gridConfig.config.groupWidth)
});
}

onChange = action(value => {
this.config.groupWidth = value;
});

onChangeComplete = debounce(
action(value => {
const { settingsStore } = this.props;

settingsStore.gridConfig.config.groupWidth = value;
}),
200
);

render() {
return (
<div className="form-group text-center">
<label className="mb-4 font-weight-bold">
Minimal alert group width
</label>
<InputRange
minValue={300}
maxValue={800}
step={20}
value={this.config.groupWidth}
id="formControlRange"
formatLabel={this.formatLabel}
onChange={this.onChange}
onChangeComplete={this.onChangeComplete}
/>
</div>
);
}
}
);

export { AlertGroupWidthConfiguration };
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from "react";

import { mount } from "enzyme";

import toDiffableHtml from "diffable-html";

import { Settings } from "Stores/Settings";
import { AlertGroupWidthConfiguration } from "./AlertGroupWidthConfiguration";

let settingsStore;
beforeEach(() => {
settingsStore = new Settings();
});

const FakeConfiguration = () => {
return mount(<AlertGroupWidthConfiguration settingsStore={settingsStore} />);
};

describe("<AlertGroupWidthConfiguration />", () => {
it("matches snapshot with default values", () => {
const tree = FakeConfiguration();
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});

it("call to onChange() updates internal state", () => {
const tree = FakeConfiguration();
tree.instance().onChange(500);
expect(tree.instance().config.groupWidth).toBe(500);
});

it("settings are updated on completed change", () => {
const tree = FakeConfiguration();
tree.instance().onChangeComplete(555);
expect(settingsStore.gridConfig.config.groupWidth).toBe(555);
});

it("custom interval value is rendered correctly", () => {
settingsStore.gridConfig.config.groupWidth = 455;
const component = FakeConfiguration();
expect(component.find("InputRange").props().value).toBe(455);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<AlertGroupWidthConfiguration /> matches snapshot with default values 1`] = `
"
<div class=\\"form-group text-center\\">
<label class=\\"mb-4 font-weight-bold\\">
Minimal alert group width
</label>
<div aria-disabled=\\"false\\"
class=\\"input-range\\"
>
<span class=\\"input-range__label input-range__label--min\\">
<span class=\\"input-range__label-container\\">
300
</span>
</span>
<div class=\\"input-range__track input-range__track--background\\">
<div style=\\"left: 0%; width: 24%;\\"
class=\\"input-range__track input-range__track--active\\"
>
</div>
<span class=\\"input-range__slider-container\\"
style=\\"position: absolute; left: 24%;\\"
>
<span class=\\"input-range__label input-range__label--value\\">
<span class=\\"input-range__label-container\\">
420
</span>
</span>
<div aria-valuemax=\\"800\\"
aria-valuemin=\\"300\\"
aria-valuenow=\\"420\\"
class=\\"input-range__slider\\"
draggable=\\"false\\"
role=\\"slider\\"
tabindex=\\"0\\"
>
</div>
</span>
</div>
<span class=\\"input-range__label input-range__label--max\\">
<span class=\\"input-range__label-container\\">
800
</span>
</span>
</div>
</div>
"
`;
Loading

0 comments on commit b1c8985

Please sign in to comment.