docbrown/app/javascript/Snackbar/Snackbar.jsx
Nick Taylor a71d78df6e
[deploy] Add comment subscription component to article page (#7205)
* wip

* Right aligned the comment subscription panel.

* Cleaned up the comment area on the article page so it uses crayon margin classes.

* Updated tests

* Removed old UI for comment subscriptions.

* Made Discussion text bigger and bolder.

* Fixed some layout issues as per PR comments.

* Fixed some layout issues as per PR comments.

* Updated Storybook stories, tests and snapshots

* wip

* Everything works now, just need to integrate snackbar.

* Removed setTimeout I was debugging with.

* Using the new utilities/http/request utility now.

* Added the <SnackbarPoller />

* Now when unsubscribed, the subscription type resets to all comments.

* Removed overflow hidden from .home CSS class so comment subscription component displays properly.

* Now unsuccessful subscribes/unsubscribes error properly.

* Changed button text from Done to Subscribe

* Merged the <SnackbarPoller /> component into the <Snackbar /> component.

* Fixed a propType issue.

* Fixed a test.

* Removed snackbar tests for now. Need to figure out polling in tests.

* Now comment subscription component is only loaded for logged on users.

* Added a comment.

* Updated some storybook stories.

* Fixed a small formatting issue.

* Reduced snackbar item lifespans to 3 seconds.

* Extracted <CogIcon /> to it's own file because other features are going to need it.

* Added some Storybook stories and tests for the <CogIcon /> component.

* Revert "Extracted <CogIcon /> to it's own file because other features are going to need it."

This reverts commit b30406a50e491c53c3dce5be03bcdf8112b043df.

* Put back <CogIcon /> component.

* Added some error handling if the component doesn't load.

* Moving some things around.

* Rename the article pack file.

* Changed wording from "article" to "post".

* Fixed false negative CSS issue. It was a dangling div tag.

* Add the option to add an optional close button to snackbar items.

* Fixed z-index of subscription type options panel so it is always visible on mobile.

* Reworked the comment subsciption utilities a bit.

* Added test for comment subscription utilities.

* Fixed a broken test from a refactor.

* Added more tests for comment subscription utiltities.

* Fixed comments footer bottom padding.

* Fixed issue with stale find in comment subscription test.

* Update app/javascript/packs/articlePage.jsx

Co-authored-by: ludwiczakpawel <ludwiczakpawel@gmail.com>

* Update app/assets/stylesheets/article-show.scss

Co-authored-by: ludwiczakpawel <ludwiczakpawel@gmail.com>

* Changed close button wording to Dismiss.

* Fixed padding to use utility variable instead.

* Added missing import for SASS file.

Co-authored-by: ludwiczakpawel <ludwiczakpawel@gmail.com>
2020-05-05 12:50:30 -04:00

178 lines
4.2 KiB
JavaScript

import { h, Component } from 'preact';
import PropTypes from 'prop-types';
import { SnackbarItem } from './SnackbarItem';
let snackbarItems = [];
export function addSnackbarItem(snackbarItem) {
if (!Array.isArray(snackbarItem.actions)) {
snackbarItem.actions = []; // eslint-disable-line no-param-reassign
}
snackbarItems.push(snackbarItem);
}
export class Snackbar extends Component {
state = {
snacks: [],
};
pollingId;
paused = false;
pauseLifespan;
resumeLifespan;
componentDidMount() {
this.initializePolling();
}
componentDidUpdate() {
if (!this.pauseLifespan) {
this.pauseLifespan = (_event) => {
this.paused = true;
};
this.resumeLifespan = (event) => {
event.stopPropagation();
this.paused = false;
};
this.element.addEventListener('mouseover', this.pauseLifespan);
this.element.addEventListener('mouseout', this.resumeLifespan, true);
}
}
componentWillUnmount() {
if (this.element) {
this.element.removeEventListener('mouseover', this.pauseLifespan);
this.element.addEventListener('mouseout', this.resumeLifespan);
}
}
initializePolling() {
const { pollingTime, lifespan } = this.props;
this.pollingId = setInterval(() => {
if (snackbarItems.length > 0) {
// Need to add the lifespan to snackbar items because each second that goes by, we
// decrease the lifespan until it is no more.
const newSnacks = snackbarItems.map((snackbarItem) => ({
...snackbarItem,
lifespan,
}));
snackbarItems = [];
this.updateSnackbarItems(newSnacks);
// Start the lifespan countdowns for each new snackbar item.
newSnacks.forEach((snack) => {
// eslint-disable-next-line no-param-reassign
snack.lifespanTimeoutId = setTimeout(() => {
this.decreaseLifespan(snack);
}, 1000);
if (snack.addCloseButton) {
// Adds an optional close button if addDefaultACtion is true.
snack.actions.push({
text: 'Dismiss',
handler: () => {
this.setState((prevState) => {
return {
prevState,
snacks: prevState.snacks.filter(
(potentialSnackToFilterOut) =>
potentialSnackToFilterOut !== snack,
),
};
});
},
});
}
});
}
}, pollingTime);
}
updateSnackbarItems(newSnacks) {
this.setState((prevState) => {
let updatedSnacks = [...prevState.snacks, ...newSnacks];
if (updatedSnacks.length > 3) {
const snacksToBeDiscarded = updatedSnacks.slice(
0,
updatedSnacks.length - 3,
);
snacksToBeDiscarded.forEach(({ lifespanTimeoutId }) => {
clearTimeout(lifespanTimeoutId);
});
updatedSnacks = updatedSnacks.slice(updatedSnacks.length - 3);
}
return { ...prevState, snacks: updatedSnacks };
});
}
decreaseLifespan(snack) {
/* eslint-disable no-param-reassign */
if (!this.paused && snack.lifespan === 0) {
clearTimeout(snack.lifespanTimeoutId);
this.setState((prevState) => {
const snacks = prevState.snacks.filter(
(currentSnack) => currentSnack !== snack,
);
return {
...prevState,
snacks,
};
});
return;
}
if (!this.paused) {
snack.lifespan -= 1;
}
snack.lifespanTimeoutId = setTimeout(() => {
this.decreaseLifespan(snack);
}, 1000);
/* eslint-enable no-param-reassign */
}
render() {
const { snacks } = this.state;
return (
<div
className={snacks.length > 0 ? 'crayons-snackbar' : 'hidden'}
ref={(element) => {
this.element = element;
}}
>
{snacks.map(({ message, actions = [] }) => (
<SnackbarItem message={message} actions={actions} />
))}
</div>
);
}
}
Snackbar.defaultProps = {
lifespan: 5,
pollingTime: 300,
};
Snackbar.displayName = 'SnackbarPoller';
Snackbar.propTypes = {
lifespan: PropTypes.number,
pollingTime: PropTypes.number,
};