migrate components stickers to eslint

This commit is contained in:
Chris Svenningsen 2020-09-14 15:14:03 -07:00 committed by Josh Perez
parent 2ade4acd52
commit 5364de90a4
15 changed files with 225 additions and 203 deletions

View File

@ -31,8 +31,6 @@ webpack.config.ts
# Temporarily ignored during TSLint transition
# JIRA: DESKTOP-304
ts/*.ts
ts/components/stickers/**
ts/shims/**
ts/sql/**
ts/storybook/**

View File

@ -1892,6 +1892,26 @@
"message": "Why use words when you can use stickers?",
"description": "Shown as the body on a tooltip when the user upgrades to a version of Signal supporting stickers."
},
"stickers--StickerPicker--Open": {
"message": "Open the sticker picker",
"description": "Label for the open button for the sticker picker"
},
"stickers--StickerPicker--AddPack": {
"message": "Add a sticker pack",
"description": "Label for the add pack button in the sticker picker"
},
"stickers--StickerPicker--NextPage": {
"message": "Next Page",
"description": "Label for the next page button in the sticker picker"
},
"stickers--StickerPicker--PrevPage": {
"message": "Previous Page",
"description": "Label for the previous page button in the sticker picker"
},
"stickers--StickerPicker--Recents": {
"message": "Recent Sticker",
"description": "Label for the recent stickers button in the sticker picker"
},
"stickers--StickerPicker--DownloadError": {
"message": "Some stickers could not be downloaded.",
"description": "Shown in the sticker picker when one or more stickers could not be downloaded."

View File

@ -3,12 +3,8 @@ import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { boolean } from '@storybook/addon-knobs';
// @ts-ignore
import { setup as setupI18n } from '../../../js/modules/i18n';
// @ts-ignore
import enMessages from '../../../_locales/en/messages.json';
import { Props, StickerButton } from './StickerButton';
import {
createPack,

View File

@ -29,7 +29,6 @@ export type OwnProps = {
export type Props = OwnProps;
export const StickerButton = React.memo(
// tslint:disable-next-line max-func-body-length
({
i18n,
clearInstalledStickerPack,
@ -67,8 +66,9 @@ export const StickerButton = React.memo(
}
}, [
clearInstalledStickerPack,
onClickAddPack,
clearShowIntroduction,
installedPacks,
onClickAddPack,
popperRoot,
setOpen,
]);
@ -161,7 +161,6 @@ export const StickerButton = React.memo(
// Clear the installed pack after one minute
React.useEffect(() => {
if (installedPack) {
// tslint:disable-next-line:no-string-based-set-timeout
const timerId = setTimeout(clearInstalledStickerPack, 10 * 1000);
return () => {
@ -188,12 +187,14 @@ export const StickerButton = React.memo(
<Reference>
{({ ref }) => (
<button
type="button"
ref={ref}
onClick={handleClickButton}
className={classNames({
'module-sticker-button__button': true,
'module-sticker-button__button--active': open,
})}
aria-label={i18n('stickers--StickerPicker--Open')}
/>
)}
</Reference>
@ -201,6 +202,7 @@ export const StickerButton = React.memo(
<Popper placement={position} key={installedPack.id}>
{({ ref, style, placement, arrowProps }) => (
<button
type="button"
ref={ref}
style={style}
className="module-sticker-button__tooltip"
@ -237,6 +239,7 @@ export const StickerButton = React.memo(
<Popper placement={position}>
{({ ref, style, placement, arrowProps }) => (
<button
type="button"
ref={ref}
style={style}
className={classNames(
@ -260,8 +263,10 @@ export const StickerButton = React.memo(
</div>
<div className="module-sticker-button__tooltip--introduction__close">
<button
type="button"
className="module-sticker-button__tooltip--introduction__close__button"
onClick={handleClearIntroduction}
aria-label={i18n('close')}
/>
</div>
<div

View File

@ -2,12 +2,8 @@ import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
// @ts-ignore
import { setup as setupI18n } from '../../../js/modules/i18n';
// @ts-ignore
import enMessages from '../../../_locales/en/messages.json';
import { Props, StickerManager } from './StickerManager';
import { createPack, sticker1, sticker2 } from './StickerPicker.stories';

View File

@ -19,7 +19,6 @@ export type OwnProps = {
export type Props = OwnProps;
export const StickerManager = React.memo(
// tslint:disable-next-line max-func-body-length
({
installedPacks,
receivedPacks,
@ -51,18 +50,17 @@ export const StickerManager = React.memo(
focusRef.current.focus();
}
});
// We only want to attempt downloads on initial load
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const clearPackToPreview = React.useCallback(() => {
setPackToPreview(null);
}, [setPackToPreview]);
const previewPack = React.useCallback(
(pack: StickerPackType) => {
setPackToPreview(pack);
},
[clearPackToPreview]
);
const previewPack = React.useCallback((pack: StickerPackType) => {
setPackToPreview(pack);
}, []);
return (
<>
@ -98,7 +96,7 @@ export const StickerManager = React.memo(
},
].map(section => {
if (section.hideIfEmpty && section.packs.length === 0) {
return;
return null;
}
return (

View File

@ -15,7 +15,6 @@ export type OwnProps = {
export type Props = OwnProps;
export const StickerManagerPackRow = React.memo(
// tslint:disable-next-line max-func-body-length
({
installStickerPack,
uninstallStickerPack,
@ -37,7 +36,7 @@ export const StickerManagerPackRow = React.memo(
installStickerPack(id, key);
}
},
[installStickerPack, pack]
[id, installStickerPack, key]
);
const handleUninstall = React.useCallback(
@ -49,7 +48,7 @@ export const StickerManagerPackRow = React.memo(
setUninstalling(true);
}
},
[setUninstalling, id, key, isBlessed]
[id, isBlessed, key, setUninstalling, uninstallStickerPack]
);
const handleConfirmUninstall = React.useCallback(() => {
@ -57,7 +56,7 @@ export const StickerManagerPackRow = React.memo(
if (uninstallStickerPack) {
uninstallStickerPack(id, key);
}
}, [id, key, clearUninstalling]);
}, [id, key, clearUninstalling, uninstallStickerPack]);
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent) => {
@ -134,7 +133,7 @@ export const StickerManagerPackRow = React.memo(
<div className="module-sticker-manager__pack-row__controls">
{pack.status === 'installed' ? (
<StickerPackInstallButton
installed={true}
installed
i18n={i18n}
onClick={handleUninstall}
/>

View File

@ -15,11 +15,13 @@ export const StickerPackInstallButton = React.forwardRef<
Props
>(({ i18n, installed, blue, ...props }: Props, ref) => (
<button
type="button"
ref={ref}
className={classNames({
'module-sticker-manager__install-button': true,
'module-sticker-manager__install-button--blue': blue,
})}
aria-label={i18n('stickers--StickerManager--Install')}
{...props}
>
{installed

View File

@ -4,12 +4,8 @@ import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { boolean } from '@storybook/addon-knobs';
// @ts-ignore
import { setup as setupI18n } from '../../../js/modules/i18n';
// @ts-ignore
import enMessages from '../../../_locales/en/messages.json';
import { Props, StickerPicker } from './StickerPicker';
import { StickerPackType, StickerType } from '../../state/ducks/stickers';

View File

@ -1,5 +1,3 @@
/* tslint:disable:max-func-body-length */
/* tslint:disable:cyclomatic-complexity */
import * as React from 'react';
import classNames from 'classnames';
import { useRestoreFocus } from '../../util/hooks';
@ -25,7 +23,7 @@ function useTabs<T>(tabs: ReadonlyArray<T>, initialTab = tabs[0]) {
tabs.map(t => () => {
setTab(t);
}),
tabs
[tabs]
);
return [tab, handlers] as [T, ReadonlyArray<() => void>];
@ -72,11 +70,13 @@ export const StickerPicker = React.memo(
const focusRef = React.useRef<HTMLButtonElement>(null);
const tabIds = React.useMemo(
() => ['recents', ...packs.map(({ id }) => id)],
packs
[packs]
);
const [currentTab, [recentsHandler, ...packsHandlers]] = useTabs(
tabIds,
// If there are no recent stickers, default to the first sticker pack, unless there are no sticker packs.
// If there are no recent stickers,
// default to the first sticker pack,
// unless there are no sticker packs.
tabIds[recentStickers.length > 0 ? 0 : Math.min(1, tabIds.length)]
);
const selectedPack = packs.find(({ id }) => id === currentTab);
@ -110,8 +110,6 @@ export const StickerPicker = React.memo(
event.preventDefault();
onClose();
return;
}
};
@ -160,6 +158,7 @@ export const StickerPicker = React.memo(
>
{hasPacks ? (
<button
type="button"
onClick={recentsHandler}
className={classNames({
'module-sticker-picker__header__button': true,
@ -167,10 +166,12 @@ export const StickerPicker = React.memo(
'module-sticker-picker__header__button--selected':
currentTab === 'recents',
})}
aria-label={i18n('stickers--StickerPicker--Recents')}
/>
) : null}
{packs.map((pack, i) => (
<button
type="button"
key={pack.id}
onClick={packsHandlers[i]}
className={classNames(
@ -198,24 +199,29 @@ export const StickerPicker = React.memo(
</div>
{!isUsingKeyboard && packsPage > 0 ? (
<button
type="button"
className={classNames(
'module-sticker-picker__header__button',
'module-sticker-picker__header__button--prev-page'
)}
onClick={onClickPrevPackPage}
aria-label={i18n('stickers--StickerPicker--PrevPage')}
/>
) : null}
{!isUsingKeyboard && !isLastPacksPage(packsPage, packs.length) ? (
<button
type="button"
className={classNames(
'module-sticker-picker__header__button',
'module-sticker-picker__header__button--next-page'
)}
onClick={onClickNextPackPage}
aria-label={i18n('stickers--StickerPicker--NextPage')}
/>
) : null}
</div>
<button
type="button"
ref={addPackRef}
className={classNames(
'module-sticker-picker__header__button',
@ -225,6 +231,7 @@ export const StickerPicker = React.memo(
}
)}
onClick={onClickAddPack}
aria-label={i18n('stickers--StickerPicker--AddPack')}
/>
</div>
<div
@ -290,6 +297,7 @@ export const StickerPicker = React.memo(
return (
<button
type="button"
ref={maybeFocusRef}
key={`${packId}-${id}`}
className="module-sticker-picker__body__cell"
@ -307,6 +315,7 @@ export const StickerPicker = React.memo(
.fill(0)
.map((_, i) => (
<div
// eslint-disable-next-line react/no-array-index-key
key={i}
className="module-sticker-picker__body__cell__placeholder"
role="presentation"

View File

@ -1,8 +1,10 @@
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { text } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import { StickerPreviewModal } from './StickerPreviewModal';
// @ts-ignore
import { setup as setupI18n } from '../../../js/modules/i18n';
// @ts-ignore
import enMessages from '../../../_locales/en/messages.json';
import {
landscapeGreenUrl,
@ -10,10 +12,6 @@ import {
squareStickerUrl,
} from '../../storybook/Fixtures';
import { storiesOf } from '@storybook/react';
import { text } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
const i18n = setupI18n('en', enMessages);
storiesOf('Components/Stickers/StickerPreviewModal', module).add('Full', () => {
@ -46,7 +44,7 @@ storiesOf('Components/Stickers/StickerPreviewModal', module).add('Full', () => {
title,
isBlessed: true,
author,
status: 'downloaded' as 'downloaded',
status: 'downloaded' as const,
stickerCount: 101,
stickers: [
wideSticker,

View File

@ -64,171 +64,175 @@ function renderBody({ pack, i18n }: Props) {
);
}
export const StickerPreviewModal = React.memo(
// tslint:disable-next-line max-func-body-length
(props: Props) => {
const {
onClose,
pack,
i18n,
downloadStickerPack,
installStickerPack,
uninstallStickerPack,
} = props;
const focusRef = React.useRef<HTMLButtonElement>(null);
const [root, setRoot] = React.useState<HTMLElement | null>(null);
const [confirmingUninstall, setConfirmingUninstall] = React.useState(false);
export const StickerPreviewModal = React.memo((props: Props) => {
const {
onClose,
pack,
i18n,
downloadStickerPack,
installStickerPack,
uninstallStickerPack,
} = props;
const focusRef = React.useRef<HTMLButtonElement>(null);
const [root, setRoot] = React.useState<HTMLElement | null>(null);
const [confirmingUninstall, setConfirmingUninstall] = React.useState(false);
// Restore focus on teardown
useRestoreFocus(focusRef, root);
// Restore focus on teardown
useRestoreFocus(focusRef, root);
React.useEffect(() => {
const div = document.createElement('div');
document.body.appendChild(div);
setRoot(div);
React.useEffect(() => {
const div = document.createElement('div');
document.body.appendChild(div);
setRoot(div);
return () => {
document.body.removeChild(div);
};
}, []);
return () => {
document.body.removeChild(div);
};
}, []);
React.useEffect(() => {
if (pack && pack.status === 'known') {
downloadStickerPack(pack.id, pack.key);
}
if (
pack &&
pack.status === 'error' &&
(pack.attemptedStatus === 'downloaded' ||
pack.attemptedStatus === 'installed')
) {
downloadStickerPack(pack.id, pack.key, {
finalStatus: pack.attemptedStatus,
});
}
}, []);
React.useEffect(() => {
if (pack && pack.status === 'known') {
downloadStickerPack(pack.id, pack.key);
}
if (
pack &&
pack.status === 'error' &&
(pack.attemptedStatus === 'downloaded' ||
pack.attemptedStatus === 'installed')
) {
downloadStickerPack(pack.id, pack.key, {
finalStatus: pack.attemptedStatus,
});
}
// We only want to attempt downloads on initial load
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const isInstalled = Boolean(pack && pack.status === 'installed');
const handleToggleInstall = React.useCallback(() => {
if (!pack) {
return;
}
if (isInstalled) {
setConfirmingUninstall(true);
} else if (pack.status === 'ephemeral') {
downloadStickerPack(pack.id, pack.key, { finalStatus: 'installed' });
onClose();
} else {
installStickerPack(pack.id, pack.key);
const isInstalled = Boolean(pack && pack.status === 'installed');
const handleToggleInstall = React.useCallback(() => {
if (!pack) {
return;
}
if (isInstalled) {
setConfirmingUninstall(true);
} else if (pack.status === 'ephemeral') {
downloadStickerPack(pack.id, pack.key, { finalStatus: 'installed' });
onClose();
} else {
installStickerPack(pack.id, pack.key);
onClose();
}
}, [
downloadStickerPack,
installStickerPack,
isInstalled,
onClose,
pack,
setConfirmingUninstall,
]);
const handleUninstall = React.useCallback(() => {
if (!pack) {
return;
}
uninstallStickerPack(pack.id, pack.key);
setConfirmingUninstall(false);
// onClose is called by the confirmation modal
}, [uninstallStickerPack, setConfirmingUninstall, pack]);
React.useEffect(() => {
const handler = ({ key }: KeyboardEvent) => {
if (key === 'Escape') {
onClose();
}
}, [
isInstalled,
pack,
setConfirmingUninstall,
installStickerPack,
onClose,
]);
};
const handleUninstall = React.useCallback(() => {
if (!pack) {
return;
document.addEventListener('keydown', handler);
return () => {
document.removeEventListener('keydown', handler);
};
}, [onClose]);
const handleClickToClose = React.useCallback(
(e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
uninstallStickerPack(pack.id, pack.key);
setConfirmingUninstall(false);
// onClose is called by the confirmation modal
}, [uninstallStickerPack, setConfirmingUninstall, pack]);
},
[onClose]
);
React.useEffect(() => {
const handler = ({ key }: KeyboardEvent) => {
if (key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handler);
return () => {
document.removeEventListener('keydown', handler);
};
}, [onClose]);
const handleClickToClose = React.useCallback(
(e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
},
[onClose]
);
return root
? createPortal(
<div
// Not really a button. Just a background which can be clicked to close modal
role="button"
className="module-sticker-manager__preview-modal__overlay"
onClick={handleClickToClose}
>
{confirmingUninstall ? (
<ConfirmationDialog
i18n={i18n}
onClose={onClose}
actions={[
{
style: 'negative',
text: i18n('stickers--StickerManager--Uninstall'),
action: handleUninstall,
},
]}
>
{i18n('stickers--StickerManager--UninstallWarning')}
</ConfirmationDialog>
) : (
<div className="module-sticker-manager__preview-modal__container">
<header className="module-sticker-manager__preview-modal__container__header">
<h2 className="module-sticker-manager__preview-modal__container__header__text">
{i18n('stickers--StickerPreview--Title')}
</h2>
<button
onClick={onClose}
className="module-sticker-manager__preview-modal__container__header__close-button"
/>
</header>
{renderBody(props)}
{pack && pack.status !== 'error' ? (
<div className="module-sticker-manager__preview-modal__container__meta-overlay">
<div className="module-sticker-manager__preview-modal__container__meta-overlay__info">
<h3 className="module-sticker-manager__preview-modal__container__meta-overlay__info__title">
{pack.title}
{pack.isBlessed ? (
<span className="module-sticker-manager__preview-modal__container__meta-overlay__info__blessed-icon" />
) : null}
</h3>
<h4 className="module-sticker-manager__preview-modal__container__meta-overlay__info__author">
{pack.author}
</h4>
</div>
<div className="module-sticker-manager__preview-modal__container__meta-overlay__install">
{pack.status === 'pending' ? (
<Spinner svgSize="small" size="14px" />
) : (
<StickerPackInstallButton
ref={focusRef}
installed={isInstalled}
i18n={i18n}
onClick={handleToggleInstall}
blue={true}
/>
)}
</div>
return root
? createPortal(
// Not really a button. Just a background which can be clicked to close modal
// eslint-disable-next-line max-len
// eslint-disable-next-line jsx-a11y/interactive-supports-focus, jsx-a11y/click-events-have-key-events
<div
role="button"
className="module-sticker-manager__preview-modal__overlay"
onClick={handleClickToClose}
>
{confirmingUninstall ? (
<ConfirmationDialog
i18n={i18n}
onClose={onClose}
actions={[
{
style: 'negative',
text: i18n('stickers--StickerManager--Uninstall'),
action: handleUninstall,
},
]}
>
{i18n('stickers--StickerManager--UninstallWarning')}
</ConfirmationDialog>
) : (
<div className="module-sticker-manager__preview-modal__container">
<header className="module-sticker-manager__preview-modal__container__header">
<h2 className="module-sticker-manager__preview-modal__container__header__text">
{i18n('stickers--StickerPreview--Title')}
</h2>
<button
type="button"
onClick={onClose}
className="module-sticker-manager__preview-modal__container__header__close-button"
aria-label={i18n('close')}
/>
</header>
{renderBody(props)}
{pack && pack.status !== 'error' ? (
<div className="module-sticker-manager__preview-modal__container__meta-overlay">
<div className="module-sticker-manager__preview-modal__container__meta-overlay__info">
<h3 className="module-sticker-manager__preview-modal__container__meta-overlay__info__title">
{pack.title}
{pack.isBlessed ? (
<span className="module-sticker-manager__preview-modal__container__meta-overlay__info__blessed-icon" />
) : null}
</h3>
<h4 className="module-sticker-manager__preview-modal__container__meta-overlay__info__author">
{pack.author}
</h4>
</div>
) : null}
</div>
)}
</div>,
root
)
: null;
}
);
<div className="module-sticker-manager__preview-modal__container__meta-overlay__install">
{pack.status === 'pending' ? (
<Spinner svgSize="small" size="14px" />
) : (
<StickerPackInstallButton
ref={focusRef}
installed={isInstalled}
i18n={i18n}
onClick={handleToggleInstall}
blue
/>
)}
</div>
</div>
) : null}
</div>
)}
</div>,
root
)
: null;
});

View File

@ -7,7 +7,7 @@ export function countStickers(o: {
blessedPacks: ReadonlyArray<StickerPackType>;
installedPacks: ReadonlyArray<StickerPackType>;
receivedPacks: ReadonlyArray<StickerPackType>;
}) {
}): number {
return (
o.knownPacks.length +
o.blessedPacks.length +

View File

@ -13126,7 +13126,7 @@
"rule": "React-createRef",
"path": "ts/components/stickers/StickerManager.js",
"line": " const focusRef = React.createRef();",
"lineNumber": 20,
"lineNumber": 18,
"reasonCategory": "usageTrusted",
"updated": "2019-11-21T06:13:49.384Z",
"reasonDetail": "Used for setting focus only"

View File

@ -185,6 +185,7 @@
"ts/components/*.ts[x]",
"ts/components/conversation/**",
"ts/components/emoji/**",
"ts/components/stickers/**",
"ts/notifications/**",
"ts/protobuf/**",
"ts/scripts/**",