Migrate util, types, state, sticker-creator to ESLint
This commit is contained in:
parent
372aa44e49
commit
2ade4acd52
115 changed files with 647 additions and 448 deletions
|
@ -24,25 +24,19 @@ test/blanket_mocha.js
|
|||
|
||||
# TypeScript generated files
|
||||
ts/**/*.js
|
||||
sticker-creator/**/*.js
|
||||
|
||||
**/*.d.ts
|
||||
webpack.config.ts
|
||||
|
||||
# Temporarily ignored during TSLint transition
|
||||
# JIRA: DESKTOP-304
|
||||
sticker-creator/**/*.ts
|
||||
sticker-creator/**/*.tsx
|
||||
ts/*.ts
|
||||
ts/components/*.ts
|
||||
ts/components/*.tsx
|
||||
ts/components/stickers/**
|
||||
ts/shims/**
|
||||
ts/sql/**
|
||||
ts/state/**
|
||||
ts/storybook/**
|
||||
ts/styleguide/**
|
||||
ts/test/**
|
||||
ts/textsecure/**
|
||||
ts/types/**
|
||||
ts/updater/**
|
||||
ts/util/**
|
||||
|
|
25
.eslintrc.js
25
.eslintrc.js
|
@ -77,6 +77,8 @@ const rules = {
|
|||
|
||||
// Prefer functional components with default params
|
||||
'react/require-default-props': 'off',
|
||||
|
||||
'jsx-a11y/label-has-associated-control': ['error', { assert: 'either' }],
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
@ -94,7 +96,7 @@ module.exports = {
|
|||
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
files: ['ts/**/*.ts', 'ts/**/*.tsx'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
|
@ -113,12 +115,31 @@ module.exports = {
|
|||
],
|
||||
rules,
|
||||
},
|
||||
{
|
||||
files: ['sticker-creator/**/*.ts', 'sticker-creator/**/*.tsx'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: './sticker-creator/tsconfig.json',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react/recommended',
|
||||
'airbnb-typescript-prettier',
|
||||
],
|
||||
rules,
|
||||
},
|
||||
{
|
||||
files: ['**/*.stories.tsx', 'ts/build/**'],
|
||||
rules: {
|
||||
...rules,
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'react/jsx-props-no-spreading': 'off',
|
||||
'react/no-array-index-key': 'off',
|
||||
},
|
||||
},
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -27,6 +27,7 @@ test/test.js
|
|||
# React / TypeScript
|
||||
ts/**/*.js
|
||||
ts/protobuf/*.d.ts
|
||||
sticker-creator/**/*.js
|
||||
|
||||
# CSS Modules
|
||||
**/*.scss.d.ts
|
||||
|
|
|
@ -107,6 +107,7 @@
|
|||
"p-props": "4.0.0",
|
||||
"p-queue": "6.2.1",
|
||||
"pify": "3.0.0",
|
||||
"popper.js": "1.15.0",
|
||||
"protobufjs": "6.8.6",
|
||||
"proxy-agent": "3.1.1",
|
||||
"react": "16.8.3",
|
||||
|
|
|
@ -9,7 +9,7 @@ import * as styles from './index.scss';
|
|||
import { PageHeader } from '../elements/PageHeader';
|
||||
import { useI18n } from '../util/i18n';
|
||||
|
||||
export const App = () => {
|
||||
export const App: React.ComponentType = () => {
|
||||
const i18n = useI18n();
|
||||
|
||||
return (
|
||||
|
|
|
@ -32,7 +32,7 @@ const getClassName = ({ noMessage, empty }: Props) => {
|
|||
return styles.main;
|
||||
};
|
||||
|
||||
export const AppStage = (props: Props) => {
|
||||
export const AppStage: React.ComponentType<Props> = props => {
|
||||
const {
|
||||
children,
|
||||
next,
|
||||
|
@ -67,7 +67,7 @@ export const AppStage = (props: Props) => {
|
|||
</Button>
|
||||
) : null}
|
||||
{addMoreCount > 0 ? (
|
||||
<Text secondary={true}>
|
||||
<Text secondary>
|
||||
{i18n('StickerCreator--DropStage--addMore', [addMoreCount])}
|
||||
</Text>
|
||||
) : null}
|
||||
|
@ -75,7 +75,7 @@ export const AppStage = (props: Props) => {
|
|||
<Button
|
||||
className={styles.button}
|
||||
onClick={onNext || handleNext}
|
||||
primary={true}
|
||||
primary
|
||||
disabled={!nextActive}
|
||||
>
|
||||
{nextText || i18n('StickerCreator--AppStage--next')}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { StickerGrid } from '../../components/StickerGrid';
|
|||
import { stickersDuck } from '../../store';
|
||||
import { useI18n } from '../../util/i18n';
|
||||
|
||||
export const DropStage = () => {
|
||||
export const DropStage: React.ComponentType = () => {
|
||||
const i18n = useI18n();
|
||||
const stickerPaths = stickersDuck.useStickerOrder();
|
||||
const stickersReady = stickersDuck.useStickersReady();
|
||||
|
@ -17,7 +17,7 @@ export const DropStage = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
resetStatus();
|
||||
}, []);
|
||||
}, [resetStatus]);
|
||||
|
||||
return (
|
||||
<AppStage next="/add-emojis" nextActive={stickersReady}>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { StickerGrid } from '../../components/StickerGrid';
|
|||
import { stickersDuck } from '../../store';
|
||||
import { useI18n } from '../../util/i18n';
|
||||
|
||||
export const EmojiStage = () => {
|
||||
export const EmojiStage: React.ComponentType = () => {
|
||||
const i18n = useI18n();
|
||||
const emojisReady = stickersDuck.useEmojisReady();
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { stickersDuck } from '../../store';
|
|||
import { useI18n } from '../../util/i18n';
|
||||
|
||||
// tslint:disable-next-line max-func-body-length
|
||||
export const MetaStage = () => {
|
||||
export const MetaStage: React.ComponentType = () => {
|
||||
const i18n = useI18n();
|
||||
const actions = stickersDuck.useStickerActions();
|
||||
const valid = stickersDuck.useAllDataValid();
|
||||
|
@ -47,19 +47,14 @@ export const MetaStage = () => {
|
|||
|
||||
const onConfirm = React.useCallback(() => {
|
||||
history.push('/upload');
|
||||
}, [setConfirming]);
|
||||
}, []);
|
||||
|
||||
const coverFrameClass = isDragActive
|
||||
? styles.coverFrameActive
|
||||
: styles.coverFrame;
|
||||
|
||||
return (
|
||||
<AppStage
|
||||
onNext={onNext}
|
||||
nextActive={valid}
|
||||
noMessage={true}
|
||||
prev="/add-emojis"
|
||||
>
|
||||
<AppStage onNext={onNext} nextActive={valid} noMessage prev="/add-emojis">
|
||||
{confirming ? (
|
||||
<ConfirmModal
|
||||
title={i18n('StickerCreator--MetaStage--ConfirmDialog--title')}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { stickersDuck } from '../../store';
|
|||
import { useI18n } from '../../util/i18n';
|
||||
import { Intl } from '../../../ts/components/Intl';
|
||||
|
||||
export const ShareStage = () => {
|
||||
export const ShareStage: React.ComponentType = () => {
|
||||
const i18n = useI18n();
|
||||
const actions = stickersDuck.useStickerActions();
|
||||
const title = stickersDuck.useTitle();
|
||||
|
@ -34,13 +34,13 @@ export const ShareStage = () => {
|
|||
const handlePrev = React.useCallback(() => {
|
||||
actions.reset();
|
||||
history.push('/');
|
||||
}, []);
|
||||
}, [actions]);
|
||||
|
||||
return (
|
||||
<AppStage
|
||||
nextText={i18n('StickerCreator--ShareStage--close')}
|
||||
onNext={handleNext}
|
||||
nextActive={true}
|
||||
nextActive
|
||||
prevText={i18n('StickerCreator--ShareStage--createAnother')}
|
||||
onPrev={handlePrev}
|
||||
>
|
||||
|
@ -66,7 +66,7 @@ export const ShareStage = () => {
|
|||
/>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<Text className={styles.callToAction} center={true}>
|
||||
<Text className={styles.callToAction} center>
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="StickerCreator--ShareStage--callToAction"
|
||||
|
|
|
@ -9,13 +9,12 @@ import { Button } from '../../elements/Button';
|
|||
import { stickersDuck } from '../../store';
|
||||
import { encryptAndUpload } from '../../util/preload';
|
||||
import { useI18n } from '../../util/i18n';
|
||||
import { Toaster } from '../../components/Toaster';
|
||||
|
||||
const handleCancel = () => {
|
||||
history.push('/add-meta');
|
||||
};
|
||||
|
||||
export const UploadStage = () => {
|
||||
export const UploadStage: React.ComponentType = () => {
|
||||
const i18n = useI18n();
|
||||
const actions = stickersDuck.useStickerActions();
|
||||
const cover = stickersDuck.useCover();
|
||||
|
@ -50,10 +49,10 @@ export const UploadStage = () => {
|
|||
})();
|
||||
|
||||
return noop;
|
||||
}, [title, author, cover, orderedData]);
|
||||
}, [actions, title, author, cover, orderedData]);
|
||||
|
||||
return (
|
||||
<AppStage empty={true}>
|
||||
<AppStage empty>
|
||||
<div className={styles.base}>
|
||||
<H2>{i18n('StickerCreator--UploadStage--title')}</H2>
|
||||
<Text>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from '../elements/StoryRow';
|
||||
import { ShareButtons } from './ShareButtons';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
|
||||
import { StoryRow } from '../elements/StoryRow';
|
||||
import { ShareButtons } from './ShareButtons';
|
||||
|
||||
storiesOf('Sticker Creator/components', module).add('ShareButtons', () => {
|
||||
const value = text('value', 'https://signal.org');
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable max-len */
|
||||
|
||||
import * as React from 'react';
|
||||
import * as styles from './ShareButtons.scss';
|
||||
import { useI18n } from '../util/i18n';
|
||||
|
@ -6,64 +8,67 @@ export type Props = {
|
|||
value: string;
|
||||
};
|
||||
|
||||
export const ShareButtons = React.memo(({ value }: Props) => {
|
||||
const i18n = useI18n();
|
||||
export const ShareButtons: React.ComponentType<Props> = React.memo(
|
||||
({ value }) => {
|
||||
const i18n = useI18n();
|
||||
|
||||
const buttonPaths = React.useMemo<
|
||||
Array<[string, string, string, string]>
|
||||
>(() => {
|
||||
const packUrl = encodeURIComponent(value);
|
||||
const text = encodeURIComponent(
|
||||
`${i18n('StickerCreator--ShareStage--socialMessage')} ${value}`
|
||||
const buttonPaths = React.useMemo<
|
||||
Array<[string, string, string, string]>
|
||||
>(() => {
|
||||
const packUrl = encodeURIComponent(value);
|
||||
const text = encodeURIComponent(
|
||||
`${i18n('StickerCreator--ShareStage--socialMessage')} ${value}`
|
||||
);
|
||||
|
||||
return [
|
||||
// Facebook
|
||||
[
|
||||
i18n('StickerCreator--ShareButtons--facebook'),
|
||||
'#4267B2',
|
||||
'M20.155 10.656l-1.506.001c-1.181 0-1.41.561-1.41 1.384v1.816h2.817l-.367 2.845h-2.45V24h-2.937v-7.298h-2.456v-2.845h2.456V11.76c0-2.435 1.487-3.76 3.658-3.76 1.04 0 1.934.077 2.195.112v2.544z',
|
||||
`https://www.facebook.com/sharer/sharer.php?u=${packUrl}`,
|
||||
],
|
||||
// Twitter
|
||||
[
|
||||
i18n('StickerCreator--ShareButtons--twitter'),
|
||||
'#1CA1F2',
|
||||
'M22.362 12.737c.006.141.01.282.01.425 0 4.337-3.302 9.339-9.34 9.339A9.294 9.294 0 018 21.027c.257.03.518.045.783.045a6.584 6.584 0 004.077-1.405 3.285 3.285 0 01-3.067-2.279 3.312 3.312 0 001.483-.057 3.283 3.283 0 01-2.633-3.218v-.042c.442.246.949.394 1.487.411a3.282 3.282 0 01-1.016-4.383 9.32 9.32 0 006.766 3.43 3.283 3.283 0 015.593-2.994 6.568 6.568 0 002.085-.796 3.299 3.299 0 01-1.443 1.816A6.587 6.587 0 0024 11.038a6.682 6.682 0 01-1.638 1.699',
|
||||
`https://twitter.com/intent/tweet?text=${text}`,
|
||||
],
|
||||
// Pinterest
|
||||
// [
|
||||
// i18n('StickerCreator--ShareButtons--pinterest'),
|
||||
// '#BD081C',
|
||||
// 'M17.234 19.563c-.992 0-1.926-.536-2.245-1.146 0 0-.534 2.118-.646 2.527-.398 1.444-1.569 2.889-1.66 3.007-.063.083-.203.057-.218-.052-.025-.184-.324-2.007.028-3.493l1.182-5.008s-.293-.587-.293-1.454c0-1.362.789-2.379 1.772-2.379.836 0 1.239.628 1.239 1.38 0 .84-.535 2.097-.811 3.261-.231.975.489 1.77 1.451 1.77 1.74 0 2.913-2.236 2.913-4.886 0-2.014-1.356-3.522-3.824-3.522-2.787 0-4.525 2.079-4.525 4.402 0 .8.237 1.365.607 1.802.17.201.194.282.132.512-.045.17-.145.576-.188.738-.061.233-.249.316-.46.23-1.283-.524-1.882-1.931-1.882-3.511C9.806 11.13 12.008 8 16.374 8c3.51 0 5.819 2.538 5.819 5.265 0 3.605-2.005 6.298-4.959 6.298',
|
||||
// `https://pinterest.com/pin/create/button/?url=${packUrl}`,
|
||||
// ],
|
||||
// Whatsapp
|
||||
[
|
||||
i18n('StickerCreator--ShareButtons--whatsapp'),
|
||||
'#25D366',
|
||||
'M16.033 23.862h-.003a7.914 7.914 0 01-3.79-.965L8.035 24l1.126-4.109a7.907 7.907 0 01-1.059-3.964C8.104 11.556 11.661 8 16.033 8c2.121 0 4.113.826 5.61 2.325a7.878 7.878 0 012.321 5.609c-.002 4.371-3.56 7.928-7.931 7.928zm3.88-5.101c-.165.463-.957.885-1.338.942a2.727 2.727 0 01-1.248-.078 11.546 11.546 0 01-1.13-.418c-1.987-.858-3.286-2.859-3.385-2.991-.1-.132-.81-1.074-.81-2.049 0-.975.513-1.455.695-1.653a.728.728 0 01.528-.248c.132 0 .264.001.38.007.122.006.285-.046.446.34.165.397.56 1.372.61 1.471.05.099.083.215.017.347-.066.132-.1.215-.198.331-.1.115-.208.258-.297.347-.1.098-.203.206-.087.404.116.198.513.847 1.102 1.372.757.675 1.396.884 1.594.984.198.099.314.082.429-.05.116-.132.496-.578.628-.777.132-.198.264-.165.446-.099.18.066 1.156.545 1.354.645.198.099.33.148.38.231.049.083.049.479-.116.942zm-3.877-9.422c-3.636 0-6.594 2.956-6.595 6.589 0 1.245.348 2.458 1.008 3.507l.157.249-.666 2.432 2.495-.654.24.142a6.573 6.573 0 003.355.919h.003a6.6 6.6 0 006.592-6.59 6.55 6.55 0 00-1.93-4.662 6.549 6.549 0 00-4.66-1.932z',
|
||||
`https://wa.me?text=${text}`,
|
||||
],
|
||||
];
|
||||
}, [i18n, value]);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{buttonPaths.map(([title, fill, path, url]) => (
|
||||
<button
|
||||
type="button"
|
||||
key={path}
|
||||
className={styles.button}
|
||||
onClick={() => window.open(url)}
|
||||
title={title}
|
||||
>
|
||||
<svg width={32} height={32}>
|
||||
<circle cx="16" cy="16" r="16" fill={fill} />
|
||||
<path d={path} fill="#FFF" fillRule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
return [
|
||||
// Facebook
|
||||
[
|
||||
i18n('StickerCreator--ShareButtons--facebook'),
|
||||
'#4267B2',
|
||||
'M20.155 10.656l-1.506.001c-1.181 0-1.41.561-1.41 1.384v1.816h2.817l-.367 2.845h-2.45V24h-2.937v-7.298h-2.456v-2.845h2.456V11.76c0-2.435 1.487-3.76 3.658-3.76 1.04 0 1.934.077 2.195.112v2.544z',
|
||||
`https://www.facebook.com/sharer/sharer.php?u=${packUrl}`,
|
||||
],
|
||||
// Twitter
|
||||
[
|
||||
i18n('StickerCreator--ShareButtons--twitter'),
|
||||
'#1CA1F2',
|
||||
'M22.362 12.737c.006.141.01.282.01.425 0 4.337-3.302 9.339-9.34 9.339A9.294 9.294 0 018 21.027c.257.03.518.045.783.045a6.584 6.584 0 004.077-1.405 3.285 3.285 0 01-3.067-2.279 3.312 3.312 0 001.483-.057 3.283 3.283 0 01-2.633-3.218v-.042c.442.246.949.394 1.487.411a3.282 3.282 0 01-1.016-4.383 9.32 9.32 0 006.766 3.43 3.283 3.283 0 015.593-2.994 6.568 6.568 0 002.085-.796 3.299 3.299 0 01-1.443 1.816A6.587 6.587 0 0024 11.038a6.682 6.682 0 01-1.638 1.699',
|
||||
`https://twitter.com/intent/tweet?text=${text}`,
|
||||
],
|
||||
// Pinterest
|
||||
// [
|
||||
// i18n('StickerCreator--ShareButtons--pinterest'),
|
||||
// '#BD081C',
|
||||
// 'M17.234 19.563c-.992 0-1.926-.536-2.245-1.146 0 0-.534 2.118-.646 2.527-.398 1.444-1.569 2.889-1.66 3.007-.063.083-.203.057-.218-.052-.025-.184-.324-2.007.028-3.493l1.182-5.008s-.293-.587-.293-1.454c0-1.362.789-2.379 1.772-2.379.836 0 1.239.628 1.239 1.38 0 .84-.535 2.097-.811 3.261-.231.975.489 1.77 1.451 1.77 1.74 0 2.913-2.236 2.913-4.886 0-2.014-1.356-3.522-3.824-3.522-2.787 0-4.525 2.079-4.525 4.402 0 .8.237 1.365.607 1.802.17.201.194.282.132.512-.045.17-.145.576-.188.738-.061.233-.249.316-.46.23-1.283-.524-1.882-1.931-1.882-3.511C9.806 11.13 12.008 8 16.374 8c3.51 0 5.819 2.538 5.819 5.265 0 3.605-2.005 6.298-4.959 6.298',
|
||||
// `https://pinterest.com/pin/create/button/?url=${packUrl}`,
|
||||
// ],
|
||||
// Whatsapp
|
||||
[
|
||||
i18n('StickerCreator--ShareButtons--whatsapp'),
|
||||
'#25D366',
|
||||
'M16.033 23.862h-.003a7.914 7.914 0 01-3.79-.965L8.035 24l1.126-4.109a7.907 7.907 0 01-1.059-3.964C8.104 11.556 11.661 8 16.033 8c2.121 0 4.113.826 5.61 2.325a7.878 7.878 0 012.321 5.609c-.002 4.371-3.56 7.928-7.931 7.928zm3.88-5.101c-.165.463-.957.885-1.338.942a2.727 2.727 0 01-1.248-.078 11.546 11.546 0 01-1.13-.418c-1.987-.858-3.286-2.859-3.385-2.991-.1-.132-.81-1.074-.81-2.049 0-.975.513-1.455.695-1.653a.728.728 0 01.528-.248c.132 0 .264.001.38.007.122.006.285-.046.446.34.165.397.56 1.372.61 1.471.05.099.083.215.017.347-.066.132-.1.215-.198.331-.1.115-.208.258-.297.347-.1.098-.203.206-.087.404.116.198.513.847 1.102 1.372.757.675 1.396.884 1.594.984.198.099.314.082.429-.05.116-.132.496-.578.628-.777.132-.198.264-.165.446-.099.18.066 1.156.545 1.354.645.198.099.33.148.38.231.049.083.049.479-.116.942zm-3.877-9.422c-3.636 0-6.594 2.956-6.595 6.589 0 1.245.348 2.458 1.008 3.507l.157.249-.666 2.432 2.495-.654.24.142a6.573 6.573 0 003.355.919h.003a6.6 6.6 0 006.592-6.59 6.55 6.55 0 00-1.93-4.662 6.549 6.549 0 00-4.66-1.932z',
|
||||
`https://wa.me?text=${text}`,
|
||||
],
|
||||
];
|
||||
}, [i18n, value]);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{buttonPaths.map(([title, fill, path, url]) => (
|
||||
<button
|
||||
key={path}
|
||||
className={styles.button}
|
||||
onClick={() => window.open(url)}
|
||||
title={title}
|
||||
>
|
||||
<svg width={32} height={32}>
|
||||
<circle cx="16" cy="16" r="16" fill={fill} />
|
||||
<path d={path} fill="#FFF" fillRule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from '../elements/StoryRow';
|
||||
import { StickerFrame } from './StickerFrame';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { boolean, select, text } from '@storybook/addon-knobs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { StoryRow } from '../elements/StoryRow';
|
||||
import { StickerFrame } from './StickerFrame';
|
||||
|
||||
storiesOf('Sticker Creator/components', module).add('StickerFrame', () => {
|
||||
const image = text('image url', '/fixtures/512x515-thumbs-up-lincoln.webp');
|
||||
const showGuide = boolean('show guide', true);
|
||||
|
@ -16,7 +16,7 @@ storiesOf('Sticker Creator/components', module).add('StickerFrame', () => {
|
|||
const [emoji, setEmoji] = React.useState(undefined);
|
||||
|
||||
return (
|
||||
<StoryRow top={true}>
|
||||
<StoryRow top>
|
||||
<StickerFrame
|
||||
id="1337"
|
||||
emojiData={emoji}
|
||||
|
|
|
@ -31,7 +31,13 @@ export type Props = Partial<
|
|||
readonly image?: string;
|
||||
readonly mode?: Mode;
|
||||
readonly showGuide?: boolean;
|
||||
onPickEmoji?({ id: string, emoji: EmojiPickData }): unknown;
|
||||
onPickEmoji?({
|
||||
id,
|
||||
emoji,
|
||||
}: {
|
||||
id: string;
|
||||
emoji: EmojiPickDataType;
|
||||
}): unknown;
|
||||
onRemove?(id: string): unknown;
|
||||
};
|
||||
|
||||
|
@ -180,7 +186,8 @@ export const StickerFrame = React.memo(
|
|||
onMouseLeave={handleMouseLeave}
|
||||
ref={rootRef}
|
||||
>
|
||||
{mode !== 'add' ? (
|
||||
{// eslint-disable-next-line no-nested-ternary
|
||||
mode !== 'add' ? (
|
||||
image ? (
|
||||
<ImageHandle src={image} />
|
||||
) : (
|
||||
|
@ -191,14 +198,11 @@ export const StickerFrame = React.memo(
|
|||
<div className={styles.guide} />
|
||||
) : null}
|
||||
{mode === 'add' && onDrop ? (
|
||||
<DropZone
|
||||
onDrop={onDrop}
|
||||
inner={true}
|
||||
onDragActive={setDragActive}
|
||||
/>
|
||||
<DropZone onDrop={onDrop} inner onDragActive={setDragActive} />
|
||||
) : null}
|
||||
{mode === 'removable' ? (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.closeButton}
|
||||
onClick={handleRemove}
|
||||
// Reverse the mouseenter/leave logic for the remove button so
|
||||
|
@ -214,6 +218,7 @@ export const StickerFrame = React.memo(
|
|||
<PopperReference>
|
||||
{({ ref }) => (
|
||||
<button
|
||||
type="button"
|
||||
ref={ref}
|
||||
className={styles.emojiButton}
|
||||
onClick={handleToggleEmojiPicker}
|
||||
|
|
|
@ -56,7 +56,6 @@ const InnerGrid = SortableContainer(
|
|||
const webp = await convertToWebp(path);
|
||||
actions.addWebp(webp);
|
||||
} catch (e) {
|
||||
// @ts-ignore
|
||||
window.log.error('Error processing image:', e);
|
||||
actions.removeSticker(path);
|
||||
actions.addToast({
|
||||
|
@ -114,7 +113,7 @@ export const StickerGrid = SortableContainer((props: Props) => {
|
|||
ids={ids}
|
||||
axis="xy"
|
||||
onSortEnd={handleSortEnd}
|
||||
useDragHandle={true}
|
||||
useDragHandle
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from '../elements/StoryRow';
|
||||
import { StickerPackPreview } from './StickerPackPreview';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
|
||||
import { StoryRow } from '../elements/StoryRow';
|
||||
import { StickerPackPreview } from './StickerPackPreview';
|
||||
|
||||
storiesOf('Sticker Creator/components', module).add(
|
||||
'StickerPackPreview',
|
||||
() => {
|
||||
|
@ -14,7 +14,7 @@ storiesOf('Sticker Creator/components', module).add(
|
|||
const images = React.useMemo(() => Array(39).fill(image), [image]);
|
||||
|
||||
return (
|
||||
<StoryRow top={true}>
|
||||
<StoryRow top>
|
||||
<StickerPackPreview images={images} title={title} author={author} />
|
||||
</StoryRow>
|
||||
);
|
||||
|
|
|
@ -19,8 +19,8 @@ export const StickerPackPreview = React.memo(
|
|||
</div>
|
||||
<div className={styles.scroller}>
|
||||
<div className={styles.grid}>
|
||||
{images.map((src, id) => (
|
||||
<img key={id} className={styles.sticker} src={src} alt={src} />
|
||||
{images.map(src => (
|
||||
<img key={src} className={styles.sticker} src={src} alt={src} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import * as React from 'react';
|
||||
import { debounce, dropRight } from 'lodash';
|
||||
import { StoryRow } from '../elements/StoryRow';
|
||||
import { Toaster } from './Toaster';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { text as textKnob } from '@storybook/addon-knobs';
|
||||
|
||||
import { StoryRow } from '../elements/StoryRow';
|
||||
import { Toaster } from './Toaster';
|
||||
|
||||
storiesOf('Sticker Creator/components', module).add('Toaster', () => {
|
||||
const inputText = textKnob('Slices', ['error 1', 'error 2'].join('|'));
|
||||
const initialState = React.useMemo(() => inputText.split('|'), [inputText]);
|
||||
const [state, setState] = React.useState(initialState);
|
||||
|
||||
// TODO not sure how to fix this
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const handleDismiss = React.useCallback(
|
||||
// Debounce is required here since auto-dismiss is asynchronously called
|
||||
// from multiple rendered instances (multiple themes)
|
||||
|
|
1
sticker-creator/custom.d.ts
vendored
Normal file
1
sticker-creator/custom.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
declare module '*.scss';
|
|
@ -1,11 +1,11 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { Button } from './Button';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { Button } from './Button';
|
||||
|
||||
storiesOf('Sticker Creator/elements', module).add('Button', () => {
|
||||
const onClick = action('onClick');
|
||||
const child = text('text', 'foo bar');
|
||||
|
@ -13,12 +13,12 @@ storiesOf('Sticker Creator/elements', module).add('Button', () => {
|
|||
return (
|
||||
<>
|
||||
<StoryRow>
|
||||
<Button onClick={onClick} primary={true}>
|
||||
<Button onClick={onClick} primary>
|
||||
{child}
|
||||
</Button>
|
||||
</StoryRow>
|
||||
<StoryRow>
|
||||
<Button onClick={onClick} primary={true} disabled={true}>
|
||||
<Button onClick={onClick} primary disabled>
|
||||
{child}
|
||||
</Button>
|
||||
</StoryRow>
|
||||
|
@ -26,27 +26,27 @@ storiesOf('Sticker Creator/elements', module).add('Button', () => {
|
|||
<Button onClick={onClick}>{child}</Button>
|
||||
</StoryRow>
|
||||
<StoryRow>
|
||||
<Button onClick={onClick} disabled={true}>
|
||||
<Button onClick={onClick} disabled>
|
||||
{child}
|
||||
</Button>
|
||||
</StoryRow>
|
||||
<StoryRow>
|
||||
<Button onClick={onClick} primary={true} pill={true}>
|
||||
<Button onClick={onClick} primary pill>
|
||||
{child}
|
||||
</Button>
|
||||
</StoryRow>
|
||||
<StoryRow>
|
||||
<Button onClick={onClick} primary={true} pill={true} disabled={true}>
|
||||
<Button onClick={onClick} primary pill disabled>
|
||||
{child}
|
||||
</Button>
|
||||
</StoryRow>
|
||||
<StoryRow>
|
||||
<Button onClick={onClick} pill={true}>
|
||||
<Button onClick={onClick} pill>
|
||||
{child}
|
||||
</Button>
|
||||
</StoryRow>
|
||||
<StoryRow>
|
||||
<Button onClick={onClick} pill={true} disabled={true}>
|
||||
<Button onClick={onClick} pill disabled>
|
||||
{child}
|
||||
</Button>
|
||||
</StoryRow>
|
||||
|
|
|
@ -3,10 +3,8 @@ import * as classnames from 'classnames';
|
|||
import * as styles from './Button.scss';
|
||||
|
||||
export type Props = React.HTMLProps<HTMLButtonElement> & {
|
||||
className?: string;
|
||||
pill?: boolean;
|
||||
primary?: boolean;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const getClassName = ({ primary, pill }: Props) => {
|
||||
|
@ -25,12 +23,15 @@ const getClassName = ({ primary, pill }: Props) => {
|
|||
return styles.base;
|
||||
};
|
||||
|
||||
export const Button = (props: Props) => {
|
||||
const { className, pill, primary, children, ...otherProps } = props;
|
||||
|
||||
export const Button: React.ComponentType<Props> = ({
|
||||
className,
|
||||
children,
|
||||
...otherProps
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
className={classnames(getClassName(props), className)}
|
||||
type="button"
|
||||
className={classnames(getClassName(otherProps), className)}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { ConfirmDialog } from './ConfirmDialog';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { ConfirmDialog } from './ConfirmDialog';
|
||||
|
||||
storiesOf('Sticker Creator/elements', module).add('ConfirmDialog', () => {
|
||||
const title = text('title', 'Foo bar banana baz?');
|
||||
const child = text(
|
||||
|
|
|
@ -11,14 +11,14 @@ export type Props = {
|
|||
readonly onCancel: () => unknown;
|
||||
};
|
||||
|
||||
export const ConfirmDialog = ({
|
||||
export const ConfirmDialog: React.ComponentType<Props> = ({
|
||||
title,
|
||||
children,
|
||||
confirm,
|
||||
cancel,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}: Props) => {
|
||||
}) => {
|
||||
const i18n = useI18n();
|
||||
const cancelText = cancel || i18n('StickerCreator--ConfirmDialog--cancel');
|
||||
|
||||
|
@ -27,10 +27,14 @@ export const ConfirmDialog = ({
|
|||
<h1 className={styles.title}>{title}</h1>
|
||||
<p className={styles.text}>{children}</p>
|
||||
<div className={styles.bottom}>
|
||||
<button className={styles.button} onClick={onCancel}>
|
||||
<button type="button" className={styles.button} onClick={onCancel}>
|
||||
{cancelText}
|
||||
</button>
|
||||
<button className={styles.buttonPrimary} onClick={onConfirm}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.buttonPrimary}
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{confirm}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { CopyText } from './CopyText';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { CopyText } from './CopyText';
|
||||
|
||||
storiesOf('Sticker Creator/elements', module).add('CopyText', () => {
|
||||
const label = text('label', 'foo bar');
|
||||
const value = text('value', 'foo bar');
|
||||
|
|
|
@ -10,27 +10,29 @@ export type Props = {
|
|||
onCopy?: () => unknown;
|
||||
};
|
||||
|
||||
export const CopyText = React.memo(({ label, onCopy, value }: Props) => {
|
||||
const i18n = useI18n();
|
||||
const handleClick = React.useCallback(() => {
|
||||
copy(value);
|
||||
if (onCopy) {
|
||||
onCopy();
|
||||
}
|
||||
}, [onCopy, value]);
|
||||
export const CopyText: React.ComponentType<Props> = React.memo(
|
||||
({ label, onCopy, value }) => {
|
||||
const i18n = useI18n();
|
||||
const handleClick = React.useCallback(() => {
|
||||
copy(value);
|
||||
if (onCopy) {
|
||||
onCopy();
|
||||
}
|
||||
}, [onCopy, value]);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
value={value}
|
||||
aria-label={label}
|
||||
readOnly={true}
|
||||
/>
|
||||
<Button onClick={handleClick}>
|
||||
{i18n('StickerCreator--CopyText--button')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
value={value}
|
||||
aria-label={label}
|
||||
readOnly
|
||||
/>
|
||||
<Button onClick={handleClick}>
|
||||
{i18n('StickerCreator--CopyText--button')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as React from 'react';
|
||||
import { DropZone } from './DropZone';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { DropZone } from './DropZone';
|
||||
|
||||
storiesOf('Sticker Creator/elements', module).add('DropZone', () => {
|
||||
return <DropZone onDrop={action('onDrop')} />;
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { useDropzone, FileWithPath } from 'react-dropzone';
|
||||
import * as styles from './DropZone.scss';
|
||||
import { useI18n } from '../util/i18n';
|
||||
|
||||
|
@ -21,12 +21,12 @@ const getClassName = ({ inner }: Props, isDragActive: boolean) => {
|
|||
return styles.standalone;
|
||||
};
|
||||
|
||||
export const DropZone = (props: Props) => {
|
||||
export const DropZone: React.ComponentType<Props> = props => {
|
||||
const { inner, onDrop, onDragActive } = props;
|
||||
const i18n = useI18n();
|
||||
|
||||
const handleDrop = React.useCallback(
|
||||
files => {
|
||||
(files: ReadonlyArray<FileWithPath>) => {
|
||||
onDrop(files.map(({ path }) => path));
|
||||
},
|
||||
[onDrop]
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { LabeledCheckbox } from './LabeledCheckbox';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { LabeledCheckbox } from './LabeledCheckbox';
|
||||
|
||||
storiesOf('Sticker Creator/elements', module).add('Labeled Checkbox', () => {
|
||||
const child = text('label', 'foo bar');
|
||||
const [checked, setChecked] = React.useState(false);
|
||||
|
|
|
@ -17,7 +17,9 @@ const checkSvg = (
|
|||
export const LabeledCheckbox = React.memo(
|
||||
({ children, value, onChange }: Props) => {
|
||||
const handleChange = React.useCallback(() => {
|
||||
onChange(!value);
|
||||
if (onChange !== undefined) {
|
||||
onChange(!value);
|
||||
}
|
||||
}, [onChange, value]);
|
||||
|
||||
const className = value ? styles.checkboxChecked : styles.checkbox;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { LabeledInput } from './LabeledInput';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { LabeledInput } from './LabeledInput';
|
||||
|
||||
storiesOf('Sticker Creator/elements', module).add('LabeledInput', () => {
|
||||
const child = text('label', 'foo bar');
|
||||
const placeholder = text('placeholder', 'foo bar');
|
||||
|
|
|
@ -13,7 +13,9 @@ export const LabeledInput = React.memo(
|
|||
({ children, value, placeholder, onChange }: Props) => {
|
||||
const handleChange = React.useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(e.currentTarget.value);
|
||||
if (onChange !== undefined) {
|
||||
onChange(e.currentTarget.value);
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { MessageBubble } from './MessageBubble';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { number, text } from '@storybook/addon-knobs';
|
||||
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { MessageBubble } from './MessageBubble';
|
||||
|
||||
storiesOf('Sticker Creator/elements', module).add('MessageBubble', () => {
|
||||
const child = text('text', 'Foo bar banana baz');
|
||||
const minutesAgo = number('minutesAgo', 3);
|
||||
|
|
|
@ -6,7 +6,10 @@ export type Props = Pick<MessageMetaProps, 'minutesAgo'> & {
|
|||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const MessageBubble = ({ children, minutesAgo }: Props) => {
|
||||
export const MessageBubble: React.ComponentType<Props> = ({
|
||||
children,
|
||||
minutesAgo,
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.base}>
|
||||
{children}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { MessageSticker } from './MessageSticker';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { number, text } from '@storybook/addon-knobs';
|
||||
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { MessageSticker } from './MessageSticker';
|
||||
|
||||
storiesOf('Sticker Creator/elements', module).add('MessageSticker', () => {
|
||||
const image = text('image url', '/fixtures/512x515-thumbs-up-lincoln.webp');
|
||||
const minutesAgo = number('minutesAgo', 3);
|
||||
|
|
|
@ -6,7 +6,11 @@ export type Props = MessageMetaProps & {
|
|||
image: string;
|
||||
};
|
||||
|
||||
export const MessageSticker = ({ image, kind, minutesAgo }: Props) => {
|
||||
export const MessageSticker: React.ComponentType<Props> = ({
|
||||
image,
|
||||
kind,
|
||||
minutesAgo,
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.base}>
|
||||
<img src={image} alt="Sticker" className={styles.image} />
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { PageHeader } from './PageHeader';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { PageHeader } from './PageHeader';
|
||||
|
||||
storiesOf('Sticker Creator/elements', module).add('PageHeader', () => {
|
||||
const child = text('text', 'foo bar');
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { ProgressBar } from './ProgressBar';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { number } from '@storybook/addon-knobs';
|
||||
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { ProgressBar } from './ProgressBar';
|
||||
|
||||
storiesOf('Sticker Creator/elements', module).add('ProgressBar', () => {
|
||||
const count = number('count', 5);
|
||||
const total = number('total', 10);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { StickerPreview } from './StickerPreview';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { StickerPreview } from './StickerPreview';
|
||||
|
||||
storiesOf('Sticker Creator/elements', module).add('StickerPreview', () => {
|
||||
const image = text('image url', '/fixtures/512x515-thumbs-up-lincoln.webp');
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import * as styles from './StoryRow.scss';
|
||||
|
||||
export type Props = {
|
||||
children: React.ReactChild;
|
||||
type Props = {
|
||||
left?: boolean;
|
||||
right?: boolean;
|
||||
top?: boolean;
|
||||
|
@ -29,6 +28,7 @@ const getClassName = ({ left, right, top, bottom }: Props) => {
|
|||
return styles.base;
|
||||
};
|
||||
|
||||
export const StoryRow = (props: Props) => (
|
||||
<div className={getClassName(props)}>{props.children}</div>
|
||||
);
|
||||
export const StoryRow: React.ComponentType<Props> = ({
|
||||
children,
|
||||
...props
|
||||
}) => <div className={getClassName(props)}>{children}</div>;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { Toast } from './Toast';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { Toast } from './Toast';
|
||||
|
||||
storiesOf('Sticker Creator/elements', module).add('Toast', () => {
|
||||
const child = text('text', 'foo bar');
|
||||
|
||||
|
|
|
@ -7,7 +7,11 @@ export type Props = React.HTMLProps<HTMLButtonElement> & {
|
|||
};
|
||||
|
||||
export const Toast = React.memo(({ children, className, ...rest }: Props) => (
|
||||
<button className={classNames(styles.base, className)} {...rest}>
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(styles.base, className)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
));
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
import * as React from 'react';
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { H1, H2, Text } from './Typography';
|
||||
/* eslint-disable no-script-url, jsx-a11y/anchor-is-valid */
|
||||
|
||||
import * as React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
|
||||
import { StoryRow } from './StoryRow';
|
||||
import { H1, H2, Text } from './Typography';
|
||||
|
||||
storiesOf('Sticker Creator/elements', module).add('Typography', () => {
|
||||
const child = text('text', 'foo bar');
|
||||
|
||||
return (
|
||||
<>
|
||||
<StoryRow left={true}>
|
||||
<StoryRow left>
|
||||
<H1>{child}</H1>
|
||||
</StoryRow>
|
||||
<StoryRow left={true}>
|
||||
<StoryRow left>
|
||||
<H2>{child}</H2>
|
||||
</StoryRow>
|
||||
<StoryRow left={true}>
|
||||
<StoryRow left>
|
||||
<Text>
|
||||
{child} {child} {child} {child}
|
||||
</Text>
|
||||
</StoryRow>
|
||||
<StoryRow left={true}>
|
||||
<StoryRow left>
|
||||
<Text>
|
||||
{child} {child} {child} {child}{' '}
|
||||
<a href="javascript: void 0;">
|
||||
|
|
|
@ -35,7 +35,6 @@ export const Text = React.memo(
|
|||
children,
|
||||
className,
|
||||
center,
|
||||
wide,
|
||||
secondary,
|
||||
...rest
|
||||
}: Props & ParagraphProps) => (
|
||||
|
|
|
@ -4,5 +4,6 @@ import { Root } from './root';
|
|||
|
||||
const root = document.getElementById('root');
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Sticker Creator: Starting root');
|
||||
render(<Root />, root);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// tslint:disable-next-line no-submodule-imports
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import * as React from 'react';
|
||||
import { Provider as ReduxProvider } from 'react-redux';
|
||||
|
@ -8,7 +7,12 @@ import { history } from './util/history';
|
|||
import { store } from './store';
|
||||
import { I18n } from './util/i18n';
|
||||
|
||||
// @ts-ignore
|
||||
declare global {
|
||||
interface Window {
|
||||
localeMessages: { [key: string]: { message: string } };
|
||||
}
|
||||
}
|
||||
|
||||
const { localeMessages } = window;
|
||||
|
||||
const ColdRoot = () => (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// tslint:disable no-dynamic-delete
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
|
@ -13,8 +13,9 @@ import { clamp, find, isNumber, pull, remove, take, uniq } from 'lodash';
|
|||
import { SortEnd } from 'react-sortable-hoc';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import arrayMove from 'array-move';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { AppState } from '../reducer';
|
||||
import { PackMetaData, WebpData } from '../../util/preload';
|
||||
import { PackMetaData, WebpData, StickerData } from '../../util/preload';
|
||||
import { EmojiPickDataType } from '../../../ts/components/emoji/EmojiPicker';
|
||||
import { convertShortName } from '../../../ts/components/emoji/lib';
|
||||
|
||||
|
@ -46,6 +47,16 @@ export const minStickers = 1;
|
|||
export const maxStickers = 200;
|
||||
export const maxByteSize = 100 * 1024;
|
||||
|
||||
interface StateStickerData {
|
||||
readonly webp?: WebpData;
|
||||
readonly emoji?: EmojiPickDataType;
|
||||
}
|
||||
|
||||
interface StateToastData {
|
||||
key: string;
|
||||
subs?: Array<number | string>;
|
||||
}
|
||||
|
||||
export type State = {
|
||||
readonly order: Array<string>;
|
||||
readonly cover?: WebpData;
|
||||
|
@ -53,15 +64,28 @@ export type State = {
|
|||
readonly author: string;
|
||||
readonly packId: string;
|
||||
readonly packKey: string;
|
||||
readonly toasts: Array<{ key: string; subs?: Array<number | string> }>;
|
||||
readonly toasts: Array<StateToastData>;
|
||||
readonly data: {
|
||||
readonly [src: string]: {
|
||||
readonly webp?: WebpData;
|
||||
readonly emoji?: EmojiPickDataType;
|
||||
};
|
||||
readonly [src: string]: StateStickerData;
|
||||
};
|
||||
};
|
||||
|
||||
export type Actions = {
|
||||
addWebp: typeof addWebp;
|
||||
initializeStickers: typeof initializeStickers;
|
||||
removeSticker: typeof removeSticker;
|
||||
moveSticker: typeof moveSticker;
|
||||
setCover: typeof setCover;
|
||||
setEmoji: typeof setEmoji;
|
||||
setTitle: typeof setTitle;
|
||||
setAuthor: typeof setAuthor;
|
||||
setPackMeta: typeof setPackMeta;
|
||||
addToast: typeof addToast;
|
||||
dismissToast: typeof dismissToast;
|
||||
reset: typeof reset;
|
||||
resetStatus: typeof resetStatus;
|
||||
};
|
||||
|
||||
const defaultState: State = {
|
||||
order: [],
|
||||
data: {},
|
||||
|
@ -193,21 +217,22 @@ export const reducer = reduceReducers<State>(
|
|||
defaultState
|
||||
);
|
||||
|
||||
export const useTitle = () =>
|
||||
export const useTitle = (): string =>
|
||||
useSelector(({ stickers }: AppState) => stickers.title);
|
||||
export const useAuthor = () =>
|
||||
|
||||
export const useAuthor = (): string =>
|
||||
useSelector(({ stickers }: AppState) => stickers.author);
|
||||
|
||||
export const useCover = () =>
|
||||
export const useCover = (): WebpData | undefined =>
|
||||
useSelector(({ stickers }: AppState) => stickers.cover);
|
||||
|
||||
export const useStickerOrder = () =>
|
||||
export const useStickerOrder = (): Array<string> =>
|
||||
useSelector(({ stickers }: AppState) => stickers.order);
|
||||
|
||||
export const useStickerData = (src: string) =>
|
||||
export const useStickerData = (src: string): StateStickerData =>
|
||||
useSelector(({ stickers }: AppState) => stickers.data[src]);
|
||||
|
||||
export const useStickersReady = () =>
|
||||
export const useStickersReady = (): boolean =>
|
||||
useSelector(
|
||||
({ stickers }: AppState) =>
|
||||
stickers.order.length >= minStickers &&
|
||||
|
@ -215,12 +240,12 @@ export const useStickersReady = () =>
|
|||
Object.values(stickers.data).every(({ webp }) => !!webp)
|
||||
);
|
||||
|
||||
export const useEmojisReady = () =>
|
||||
export const useEmojisReady = (): boolean =>
|
||||
useSelector(({ stickers }: AppState) =>
|
||||
Object.values(stickers.data).every(({ emoji }) => !!emoji)
|
||||
);
|
||||
|
||||
export const useAllDataValid = () => {
|
||||
export const useAllDataValid = (): boolean => {
|
||||
const stickersReady = useStickersReady();
|
||||
const emojisReady = useEmojisReady();
|
||||
const cover = useCover();
|
||||
|
@ -236,10 +261,12 @@ const selectUrl = createSelector(
|
|||
(id, key) => `https://signal.art/addstickers/#pack_id=${id}&pack_key=${key}`
|
||||
);
|
||||
|
||||
export const usePackUrl = () => useSelector(selectUrl);
|
||||
export const useToasts = () =>
|
||||
export const usePackUrl = (): string => useSelector(selectUrl);
|
||||
|
||||
export const useToasts = (): Array<StateToastData> =>
|
||||
useSelector(({ stickers }: AppState) => stickers.toasts);
|
||||
export const useAddMoreCount = () =>
|
||||
|
||||
export const useAddMoreCount = (): number =>
|
||||
useSelector(({ stickers }: AppState) =>
|
||||
clamp(minStickers - stickers.order.length, 0, minStickers)
|
||||
);
|
||||
|
@ -251,21 +278,23 @@ const selectOrderedData = createSelector(
|
|||
order.map(id => ({
|
||||
...data[id],
|
||||
emoji: convertShortName(
|
||||
data[id].emoji.shortName,
|
||||
data[id].emoji.skinTone
|
||||
(data[id].emoji as EmojiPickDataType).shortName,
|
||||
(data[id].emoji as EmojiPickDataType).skinTone
|
||||
),
|
||||
}))
|
||||
);
|
||||
|
||||
export const useSelectOrderedData = () => useSelector(selectOrderedData);
|
||||
export const useSelectOrderedData = (): Array<StickerData> =>
|
||||
useSelector(selectOrderedData);
|
||||
|
||||
const selectOrderedImagePaths = createSelector(selectOrderedData, data =>
|
||||
data.map(({ webp }) => webp.src)
|
||||
data.map(({ webp }) => (webp as WebpData).src)
|
||||
);
|
||||
|
||||
export const useOrderedImagePaths = () => useSelector(selectOrderedImagePaths);
|
||||
export const useOrderedImagePaths = (): Array<string> =>
|
||||
useSelector(selectOrderedImagePaths);
|
||||
|
||||
export const useStickerActions = () => {
|
||||
export const useStickerActions = (): Actions => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return useMemo(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { combineReducers, Reducer } from 'redux';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { reducer as stickers } from './ducks/stickers';
|
||||
|
||||
export const reducer = combineReducers({
|
||||
|
|
9
sticker-creator/tsconfig.json
Normal file
9
sticker-creator/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"lib": ["dom", "es2017"],
|
||||
"jsx": "react",
|
||||
"rootDir": "."
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ export type I18nProps = {
|
|||
messages: { [key: string]: { message: string } };
|
||||
};
|
||||
|
||||
export const I18n = ({ messages, children }: I18nProps) => {
|
||||
export const I18n = ({ messages, children }: I18nProps): JSX.Element => {
|
||||
const getMessage = React.useCallback<I18nFn>(
|
||||
(key, substitutions) => {
|
||||
if (Array.isArray(substitutions) && substitutions.length > 1) {
|
||||
|
@ -28,7 +28,8 @@ export const I18n = ({ messages, children }: I18nProps) => {
|
|||
const { message } = messages[key];
|
||||
if (!substitutions) {
|
||||
return message;
|
||||
} else if (Array.isArray(substitutions)) {
|
||||
}
|
||||
if (Array.isArray(substitutions)) {
|
||||
return substitutions.reduce(
|
||||
(result, substitution) =>
|
||||
result.toString().replace(/\$.+?\$/, substitution.toString()),
|
||||
|
@ -50,7 +51,7 @@ export const I18n = ({ messages, children }: I18nProps) => {
|
|||
const placeholderName = match[1];
|
||||
const value = substitutions[placeholderName];
|
||||
if (!value) {
|
||||
// tslint:disable-next-line no-console
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`i18n: Value not provided for placeholder ${placeholderName} in key '${key}'`
|
||||
);
|
||||
|
@ -75,4 +76,4 @@ export const I18n = ({ messages, children }: I18nProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const useI18n = () => React.useContext(I18nContext);
|
||||
export const useI18n = (): I18nFn => React.useContext(I18nContext);
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
import { Metadata } from 'sharp';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
convertToWebp: ConvertToWebpFn;
|
||||
encryptAndUpload: EncryptAndUploadFn;
|
||||
}
|
||||
}
|
||||
|
||||
export type WebpData = {
|
||||
buffer: Buffer;
|
||||
src: string;
|
||||
|
@ -13,9 +20,6 @@ export type ConvertToWebpFn = (
|
|||
height?: number
|
||||
) => Promise<WebpData>;
|
||||
|
||||
// @ts-ignore
|
||||
export const convertToWebp: ConvertToWebpFn = window.convertToWebp;
|
||||
|
||||
export type StickerData = { webp?: WebpData; emoji?: string };
|
||||
export type PackMetaData = { packId: string; key: string };
|
||||
|
||||
|
@ -26,5 +30,4 @@ export type EncryptAndUploadFn = (
|
|||
onProgress?: () => unknown
|
||||
) => Promise<PackMetaData>;
|
||||
|
||||
// @ts-ignore
|
||||
export const encryptAndUpload: EncryptAndUploadFn = window.encryptAndUpload;
|
||||
export const { encryptAndUpload, convertToWebp } = window;
|
||||
|
|
|
@ -182,7 +182,7 @@ export class MessageDetail extends React.Component<Props> {
|
|||
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
||||
<div className="module-message-detail" tabIndex={0} ref={this.focusRef}>
|
||||
<div className="module-message-detail__message-container">
|
||||
<Message i18n={i18n} {...message} />
|
||||
<Message {...message} i18n={i18n} />
|
||||
</div>
|
||||
<table className="module-message-detail__info">
|
||||
<tbody>
|
||||
|
|
8
ts/model-types.d.ts
vendored
8
ts/model-types.d.ts
vendored
|
@ -18,7 +18,9 @@ type DeletesAttributesType = {
|
|||
targetSentTimestamp: number;
|
||||
};
|
||||
|
||||
declare class DeletesModelType extends Backbone.Model<DeletesAttributesType> {
|
||||
export declare class DeletesModelType extends Backbone.Model<
|
||||
DeletesAttributesType
|
||||
> {
|
||||
forMessage(message: MessageModelType): Array<DeletesModelType>;
|
||||
onDelete(doe: DeletesAttributesType): Promise<void>;
|
||||
}
|
||||
|
@ -47,7 +49,9 @@ export type MessageAttributesType = {
|
|||
sourceUuid?: string;
|
||||
};
|
||||
|
||||
declare class MessageModelType extends Backbone.Model<MessageAttributesType> {
|
||||
export declare class MessageModelType extends Backbone.Model<
|
||||
MessageAttributesType
|
||||
> {
|
||||
id: string;
|
||||
|
||||
static updateTimers(): void;
|
||||
|
|
|
@ -1,16 +1,27 @@
|
|||
import { applyMiddleware, createStore as reduxCreateStore } from 'redux';
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import {
|
||||
applyMiddleware,
|
||||
createStore as reduxCreateStore,
|
||||
DeepPartial,
|
||||
Store,
|
||||
} from 'redux';
|
||||
|
||||
import promise from 'redux-promise-middleware';
|
||||
import { createLogger } from 'redux-logger';
|
||||
|
||||
import { reducer } from './reducer';
|
||||
import { reducer, StateType } from './reducer';
|
||||
|
||||
declare global {
|
||||
interface Console {
|
||||
_log: Console['log'];
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const env = window.getEnvironment();
|
||||
|
||||
// So Redux logging doesn't go to disk, and so we can get colors/styles
|
||||
const directConsole = {
|
||||
// @ts-ignore
|
||||
log: console._log,
|
||||
groupCollapsed: console.groupCollapsed,
|
||||
group: console.group,
|
||||
|
@ -27,7 +38,7 @@ const logger = createLogger({
|
|||
// Exclude logger if we're in production mode
|
||||
const middlewareList = env === 'production' ? [promise] : [promise, logger];
|
||||
|
||||
const enhancer = applyMiddleware.apply(null, middlewareList);
|
||||
const enhancer = applyMiddleware(...middlewareList);
|
||||
|
||||
export const createStore = (initialState: any) =>
|
||||
export const createStore = (initialState: DeepPartial<StateType>): Store =>
|
||||
reduxCreateStore(reducer, initialState, enhancer);
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
|
||||
// State
|
||||
|
||||
export type CallId = any;
|
||||
export type CallId = unknown;
|
||||
|
||||
export type CallDetailsType = {
|
||||
callId: CallId;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable camelcase */
|
||||
import {
|
||||
difference,
|
||||
fromPairs,
|
||||
|
@ -113,7 +114,7 @@ export type MessageType = {
|
|||
deletedForEveryone?: boolean;
|
||||
|
||||
errors?: Array<Error>;
|
||||
group_update?: any;
|
||||
group_update?: unknown;
|
||||
|
||||
// No need to go beyond this; unused at this stage, since this goes into
|
||||
// a reducer still in plain JavaScript and comes out well-formed
|
||||
|
@ -393,7 +394,10 @@ function removeAllConversations(): RemoveAllConversationsActionType {
|
|||
};
|
||||
}
|
||||
|
||||
function selectMessage(messageId: string, conversationId: string) {
|
||||
function selectMessage(
|
||||
messageId: string,
|
||||
conversationId: string
|
||||
): MessageSelectedActionType {
|
||||
return {
|
||||
type: 'MESSAGE_SELECTED',
|
||||
payload: {
|
||||
|
@ -567,13 +571,13 @@ function openConversationExternal(
|
|||
};
|
||||
}
|
||||
|
||||
function showInbox() {
|
||||
function showInbox(): ShowInboxActionType {
|
||||
return {
|
||||
type: 'SHOW_INBOX',
|
||||
payload: null,
|
||||
};
|
||||
}
|
||||
function showArchivedConversations() {
|
||||
function showArchivedConversations(): ShowArchivedConversationsActionType {
|
||||
return {
|
||||
type: 'SHOW_ARCHIVED_CONVERSATIONS',
|
||||
payload: null,
|
||||
|
@ -596,7 +600,7 @@ function getEmptyState(): ConversationsStateType {
|
|||
function hasMessageHeightChanged(
|
||||
message: MessageType,
|
||||
previous: MessageType
|
||||
): Boolean {
|
||||
): boolean {
|
||||
const messageAttachments = message.attachments || [];
|
||||
const previousAttachments = previous.attachments || [];
|
||||
|
||||
|
@ -687,8 +691,7 @@ export function reducer(
|
|||
const { id, data } = payload;
|
||||
const { conversationLookup } = state;
|
||||
|
||||
let showArchived = state.showArchived;
|
||||
let selectedConversation = state.selectedConversation;
|
||||
let { showArchived, selectedConversation } = state;
|
||||
|
||||
const existing = conversationLookup[id];
|
||||
// In the change case we only modify the lookup if we already had that conversation
|
||||
|
@ -1087,7 +1090,8 @@ export function reducer(
|
|||
}
|
||||
}
|
||||
|
||||
// Update oldest and newest if we receive older/newer messages (or duplicated timestamps!)
|
||||
// Update oldest and newest if we receive older/newer
|
||||
// messages (or duplicated timestamps!)
|
||||
if (first && oldest && first.received_at <= oldest.received_at) {
|
||||
oldest = pick(first, ['id', 'received_at']);
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ export const actions = {
|
|||
onUseEmoji,
|
||||
};
|
||||
|
||||
export const useActions = () => useBoundActions(actions);
|
||||
export const useActions = (): typeof actions => useBoundActions(actions);
|
||||
|
||||
function onUseEmoji({ shortName }: EmojiPickDataType): OnUseEmojiAction {
|
||||
return {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { useBoundActions } from '../../util/hooks';
|
|||
// State
|
||||
|
||||
export type ItemsStateType = {
|
||||
readonly [key: string]: any;
|
||||
readonly [key: string]: unknown;
|
||||
};
|
||||
|
||||
// Actions
|
||||
|
@ -23,7 +23,7 @@ type ItemPutExternalAction = {
|
|||
type: 'items/PUT_EXTERNAL';
|
||||
payload: {
|
||||
key: string;
|
||||
value: any;
|
||||
value: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -58,9 +58,9 @@ export const actions = {
|
|||
resetItems,
|
||||
};
|
||||
|
||||
export const useActions = () => useBoundActions(actions);
|
||||
export const useActions = (): typeof actions => useBoundActions(actions);
|
||||
|
||||
function putItem(key: string, value: any): ItemPutAction {
|
||||
function putItem(key: string, value: unknown): ItemPutAction {
|
||||
storageShim.put(key, value);
|
||||
|
||||
return {
|
||||
|
@ -69,7 +69,7 @@ function putItem(key: string, value: any): ItemPutAction {
|
|||
};
|
||||
}
|
||||
|
||||
function putItemExternal(key: string, value: any): ItemPutExternalAction {
|
||||
function putItemExternal(key: string, value: unknown): ItemPutExternalAction {
|
||||
return {
|
||||
type: 'items/PUT_EXTERNAL',
|
||||
payload: {
|
||||
|
@ -139,4 +139,5 @@ const selectRecentEmojis = createSelector(
|
|||
recents => recents.filter(isShortName)
|
||||
);
|
||||
|
||||
export const useRecentEmojis = () => useSelector(selectRecentEmojis);
|
||||
export const useRecentEmojis = (): Array<string> =>
|
||||
useSelector(selectRecentEmojis);
|
||||
|
|
|
@ -292,7 +292,7 @@ async function queryConversationsAndContacts(
|
|||
for (let i = 0; i < max; i += 1) {
|
||||
const conversation = searchResults[i];
|
||||
|
||||
if (conversation.type === 'private' && !Boolean(conversation.lastMessage)) {
|
||||
if (conversation.type === 'private' && !conversation.lastMessage) {
|
||||
contacts.push(conversation.id);
|
||||
} else {
|
||||
conversations.push(conversation.id);
|
||||
|
|
|
@ -376,7 +376,10 @@ export function reducer(
|
|||
action: StickersActionType
|
||||
): StickersStateType {
|
||||
if (action.type === 'stickers/STICKER_PACK_ADDED') {
|
||||
const { payload } = action;
|
||||
// ts complains due to `stickers: {}` being overridden by the payload
|
||||
// but without full confidence that that's the case, `any` and ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const { payload } = action as any;
|
||||
const newPack = {
|
||||
stickers: {},
|
||||
...payload,
|
||||
|
|
|
@ -92,5 +92,5 @@ export const reducers = {
|
|||
user,
|
||||
};
|
||||
|
||||
// @ts-ignore: AnyAction breaks strong type checking inside reducers
|
||||
export const reducer = combineReducers<StateType, ActionsType>(reducers);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const reducer = combineReducers<StateType, ActionsType>(reducers as any);
|
||||
|
|
|
@ -7,9 +7,11 @@ import { SmartCallManager } from '../smart/CallManager';
|
|||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const FilteredCallManager = SmartCallManager as any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export const createCallManager = (store: Store) => (
|
||||
export const createCallManager = (store: Store): React.ReactElement => (
|
||||
<Provider store={store}>
|
||||
<FilteredCallManager />
|
||||
</Provider>
|
||||
|
|
|
@ -7,9 +7,14 @@ import { SmartCompositionArea } from '../smart/CompositionArea';
|
|||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const FilteredCompositionArea = SmartCompositionArea as any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export const createCompositionArea = (store: Store, props: Object) => (
|
||||
export const createCompositionArea = (
|
||||
store: Store,
|
||||
props: Record<string, unknown>
|
||||
): React.ReactElement => (
|
||||
<Provider store={store}>
|
||||
<FilteredCompositionArea {...props} />
|
||||
</Provider>
|
||||
|
|
|
@ -7,9 +7,11 @@ import { SmartLeftPane } from '../smart/LeftPane';
|
|||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const FilteredLeftPane = SmartLeftPane as any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export const createLeftPane = (store: Store) => (
|
||||
export const createLeftPane = (store: Store): React.ReactElement => (
|
||||
<Provider store={store}>
|
||||
<FilteredLeftPane />
|
||||
</Provider>
|
||||
|
|
|
@ -7,14 +7,19 @@ import { SmartSafetyNumberViewer } from '../smart/SafetyNumberViewer';
|
|||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const FilteredSafetyNumberViewer = SmartSafetyNumberViewer as any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
type Props = {
|
||||
contactID: string;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
export const createSafetyNumberViewer = (store: Store, props: Props) => (
|
||||
export const createSafetyNumberViewer = (
|
||||
store: Store,
|
||||
props: Props
|
||||
): React.ReactElement => (
|
||||
<Provider store={store}>
|
||||
<FilteredSafetyNumberViewer {...props} />
|
||||
</Provider>
|
||||
|
|
|
@ -7,9 +7,14 @@ import { SmartShortcutGuideModal } from '../smart/ShortcutGuideModal';
|
|||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const FilteredShortcutGuideModal = SmartShortcutGuideModal as any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export const createShortcutGuideModal = (store: Store, props: Object) => (
|
||||
export const createShortcutGuideModal = (
|
||||
store: Store,
|
||||
props: Record<string, unknown>
|
||||
): React.ReactElement => (
|
||||
<Provider store={store}>
|
||||
<FilteredShortcutGuideModal {...props} />
|
||||
</Provider>
|
||||
|
|
|
@ -7,9 +7,11 @@ import { SmartStickerManager } from '../smart/StickerManager';
|
|||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const FilteredStickerManager = SmartStickerManager as any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export const createStickerManager = (store: Store) => (
|
||||
export const createStickerManager = (store: Store): React.ReactElement => (
|
||||
<Provider store={store}>
|
||||
<FilteredStickerManager />
|
||||
</Provider>
|
||||
|
|
|
@ -7,9 +7,14 @@ import { SmartStickerPreviewModal } from '../smart/StickerPreviewModal';
|
|||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const FilteredStickerPreviewModal = SmartStickerPreviewModal as any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export const createStickerPreviewModal = (store: Store, props: Object) => (
|
||||
export const createStickerPreviewModal = (
|
||||
store: Store,
|
||||
props: Record<string, unknown>
|
||||
): React.ReactElement => (
|
||||
<Provider store={store}>
|
||||
<FilteredStickerPreviewModal {...props} />
|
||||
</Provider>
|
||||
|
|
|
@ -7,9 +7,14 @@ import { SmartTimeline } from '../smart/Timeline';
|
|||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const FilteredTimeline = SmartTimeline as any;
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export const createTimeline = (store: Store, props: Object) => (
|
||||
export const createTimeline = (
|
||||
store: Store,
|
||||
props: Record<string, unknown>
|
||||
): React.ReactElement => (
|
||||
<Provider store={store}>
|
||||
<FilteredTimeline {...props} />
|
||||
</Provider>
|
||||
|
|
|
@ -49,7 +49,7 @@ export const getSelectedMessage = createSelector(
|
|||
getConversations,
|
||||
(state: ConversationsStateType): SelectedMessageType | undefined => {
|
||||
if (!state.selectedMessage) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -136,21 +136,19 @@ export const _getLeftPaneLists = (
|
|||
const max = values.length;
|
||||
for (let i = 0; i < max; i += 1) {
|
||||
let conversation = values[i];
|
||||
if (!conversation.activeAt) {
|
||||
continue;
|
||||
}
|
||||
if (conversation.activeAt) {
|
||||
if (selectedConversation === conversation.id) {
|
||||
conversation = {
|
||||
...conversation,
|
||||
isSelected: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (selectedConversation === conversation.id) {
|
||||
conversation = {
|
||||
...conversation,
|
||||
isSelected: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (conversation.isArchived) {
|
||||
archivedConversations.push(conversation);
|
||||
} else {
|
||||
conversations.push(conversation);
|
||||
if (conversation.isArchived) {
|
||||
archivedConversations.push(conversation);
|
||||
} else {
|
||||
conversations.push(conversation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,7 +218,7 @@ export const getConversationSelector = createSelector(
|
|||
return (id: string) => {
|
||||
const conversation = lookup[id];
|
||||
if (!conversation) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return selector(conversation);
|
||||
|
@ -236,17 +234,12 @@ export const getConversationSelector = createSelector(
|
|||
// - message details
|
||||
export function _messageSelector(
|
||||
message: MessageType,
|
||||
// @ts-ignore
|
||||
ourNumber: string,
|
||||
// @ts-ignore
|
||||
regionCode: string,
|
||||
_ourNumber: string,
|
||||
_regionCode: string,
|
||||
interactionMode: 'mouse' | 'keyboard',
|
||||
// @ts-ignore
|
||||
conversation?: ConversationType,
|
||||
// @ts-ignore
|
||||
author?: ConversationType,
|
||||
// @ts-ignore
|
||||
quoted?: ConversationType,
|
||||
_conversation?: ConversationType,
|
||||
_author?: ConversationType,
|
||||
_quoted?: ConversationType,
|
||||
selectedMessageId?: string,
|
||||
selectedMessageCounter?: number
|
||||
): TimelineItemType {
|
||||
|
@ -319,7 +312,7 @@ export const getMessageSelector = createSelector(
|
|||
return (id: string) => {
|
||||
const message = messageLookup[id];
|
||||
if (!message) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { conversationId, source, type, quote } = message;
|
||||
|
@ -441,7 +434,7 @@ export const getConversationMessagesSelector = createSelector(
|
|||
return (id: string): TimelinePropsType | undefined => {
|
||||
const conversation = messagesByConversation[id];
|
||||
if (!conversation) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return conversationMessagesSelector(conversation);
|
||||
|
|
|
@ -207,7 +207,7 @@ export const getSearchResults = createSelector(
|
|||
items,
|
||||
messagesLoading,
|
||||
noResults,
|
||||
regionCode: regionCode,
|
||||
regionCode,
|
||||
searchConversationName,
|
||||
searchTerm: state.query,
|
||||
selectedConversationId,
|
||||
|
@ -218,14 +218,10 @@ export const getSearchResults = createSelector(
|
|||
|
||||
export function _messageSearchResultSelector(
|
||||
message: MessageSearchResultType,
|
||||
// @ts-ignore
|
||||
ourNumber: string,
|
||||
// @ts-ignore
|
||||
regionCode: string,
|
||||
// @ts-ignore
|
||||
sender?: ConversationType,
|
||||
// @ts-ignore
|
||||
recipient?: ConversationType,
|
||||
_ourNumber: string,
|
||||
_regionCode: string,
|
||||
_sender?: ConversationType,
|
||||
_recipient?: ConversationType,
|
||||
searchConversationId?: string,
|
||||
selectedMessageId?: string
|
||||
): MessageSearchResultPropsDataType {
|
||||
|
@ -282,7 +278,7 @@ export const getMessageSearchResultSelector = createSelector(
|
|||
return (id: string) => {
|
||||
const message = messageSearchResultLookup[id];
|
||||
if (!message) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { conversationId, source, type } = message;
|
||||
|
|
|
@ -31,12 +31,12 @@ const getSticker = (
|
|||
): StickerType | undefined => {
|
||||
const pack = packs[packId];
|
||||
if (!pack) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sticker = pack.stickers[stickerId];
|
||||
if (!sticker) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isEphemeral = pack.status === 'ephemeral';
|
||||
|
@ -67,7 +67,7 @@ export const translatePackFromDB = (
|
|||
blessedPacks: Dictionary<boolean>,
|
||||
stickersPath: string,
|
||||
tempPath: string
|
||||
) => {
|
||||
): StickerPackType => {
|
||||
const { id, stickers, status, coverStickerId } = pack;
|
||||
const isEphemeral = status === 'ephemeral';
|
||||
|
||||
|
@ -92,7 +92,7 @@ export const translatePackFromDB = (
|
|||
const filterAndTransformPacks = (
|
||||
packs: Dictionary<StickerPackDBType>,
|
||||
packFilter: (sticker: StickerPackDBType) => boolean,
|
||||
packSort: (sticker: StickerPackDBType) => any,
|
||||
packSort: (sticker: StickerPackDBType) => number | null,
|
||||
blessedPacks: Dictionary<boolean>,
|
||||
stickersPath: string,
|
||||
tempPath: string
|
||||
|
|
|
@ -32,7 +32,7 @@ export const getUserUuid = createSelector(
|
|||
|
||||
export const getUserAgent = createSelector(
|
||||
getItems,
|
||||
(state: ItemsStateType): string => state.userAgent
|
||||
(state: ItemsStateType): string => state.userAgent as string
|
||||
);
|
||||
|
||||
export const getIntl = createSelector(
|
||||
|
|
|
@ -10,7 +10,9 @@ import { SmartCallingDeviceSelection } from './CallingDeviceSelection';
|
|||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const FilteredCallingDeviceSelection = SmartCallingDeviceSelection as any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
function renderDeviceSelection(): JSX.Element {
|
||||
return <FilteredCallingDeviceSelection />;
|
||||
|
|
|
@ -89,4 +89,5 @@ const dispatchPropsMap = {
|
|||
|
||||
const smart = connect(mapStateToProps, dispatchPropsMap);
|
||||
|
||||
export const SmartCompositionArea = smart(CompositionArea);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const SmartCompositionArea = smart(CompositionArea as any);
|
||||
|
|
|
@ -16,7 +16,7 @@ type ExternalProps = {
|
|||
conversationId: string;
|
||||
};
|
||||
|
||||
export const SmartContactName = (props: ExternalProps) => {
|
||||
export const SmartContactName: React.ComponentType<ExternalProps> = props => {
|
||||
const { conversationId } = props;
|
||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||
const getConversation = useSelector<StateType, GetConversationByIdType>(
|
||||
|
|
|
@ -21,12 +21,14 @@ import { SmartUpdateDialog } from './UpdateDialog';
|
|||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const FilteredSmartMainHeader = SmartMainHeader as any;
|
||||
const FilteredSmartMessageSearchResult = SmartMessageSearchResult as any;
|
||||
const FilteredSmartNetworkStatus = SmartNetworkStatus as any;
|
||||
const FilteredSmartUpdateDialog = SmartUpdateDialog as any;
|
||||
const FilteredSmartExpiredBuildDialog = SmartExpiredBuildDialog as any;
|
||||
const FilteredSmartRelinkDialog = SmartRelinkDialog as any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
function renderExpiredBuildDialog(): JSX.Element {
|
||||
return <FilteredSmartExpiredBuildDialog />;
|
||||
|
|
|
@ -22,11 +22,13 @@ import { SmartEmojiPicker } from './EmojiPicker';
|
|||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const FilteredSmartTimelineItem = SmartTimelineItem as any;
|
||||
const FilteredSmartTypingBubble = SmartTypingBubble as any;
|
||||
const FilteredSmartLastSeenIndicator = SmartLastSeenIndicator as any;
|
||||
const FilteredSmartHeroRow = SmartHeroRow as any;
|
||||
const FilteredSmartTimelineLoadingRow = SmartTimelineLoadingRow as any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
type ExternalProps = {
|
||||
id: string;
|
||||
|
@ -38,7 +40,7 @@ type ExternalProps = {
|
|||
function renderItem(
|
||||
messageId: string,
|
||||
conversationId: string,
|
||||
actionProps: Object
|
||||
actionProps: Record<string, unknown>
|
||||
): JSX.Element {
|
||||
return (
|
||||
<FilteredSmartTimelineItem
|
||||
|
@ -61,7 +63,7 @@ function renderEmojiPicker({
|
|||
onPickEmoji={onPickEmoji}
|
||||
onClose={onClose}
|
||||
style={style}
|
||||
disableSkinTones={true}
|
||||
disableSkinTones
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -112,4 +114,5 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
|||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartTimeline = smart(Timeline);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const SmartTimeline = smart(Timeline as any);
|
||||
|
|
|
@ -20,7 +20,9 @@ type ExternalProps = {
|
|||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const FilteredSmartContactName = SmartContactName as any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
function renderContact(conversationId: string): JSX.Element {
|
||||
return <FilteredSmartContactName conversationId={conversationId} />;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { isNumber } from 'lodash';
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import { isNumber } from 'lodash';
|
||||
|
||||
import {
|
||||
STATE_ENUM,
|
||||
|
@ -26,11 +26,16 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
|||
|
||||
const { isLoadingMessages, loadCountdownStart } = conversation;
|
||||
|
||||
const loadingState: STATE_ENUM = isLoadingMessages
|
||||
? 'loading'
|
||||
: isNumber(loadCountdownStart)
|
||||
? 'countdown'
|
||||
: 'idle';
|
||||
let loadingState: STATE_ENUM;
|
||||
|
||||
if (isLoadingMessages) {
|
||||
loadingState = 'loading';
|
||||
} else if (isNumber(loadCountdownStart)) {
|
||||
loadingState = 'countdown';
|
||||
} else {
|
||||
loadingState = 'idle';
|
||||
}
|
||||
|
||||
const duration = loadingState === 'countdown' ? LOAD_COUNTDOWN : undefined;
|
||||
const expiresAt =
|
||||
loadingState === 'countdown' && loadCountdownStart
|
||||
|
|
|
@ -64,7 +64,7 @@ export function getExtensionForDisplay({
|
|||
}
|
||||
|
||||
if (!contentType) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const slash = contentType.indexOf('/');
|
||||
|
@ -72,10 +72,12 @@ export function getExtensionForDisplay({
|
|||
return contentType.slice(slash + 1);
|
||||
}
|
||||
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function isAudio(attachments?: Array<AttachmentType>) {
|
||||
export function isAudio(
|
||||
attachments?: Array<AttachmentType>
|
||||
): boolean | undefined {
|
||||
return (
|
||||
attachments &&
|
||||
attachments[0] &&
|
||||
|
@ -84,7 +86,9 @@ export function isAudio(attachments?: Array<AttachmentType>) {
|
|||
);
|
||||
}
|
||||
|
||||
export function canDisplayImage(attachments?: Array<AttachmentType>) {
|
||||
export function canDisplayImage(
|
||||
attachments?: Array<AttachmentType>
|
||||
): boolean | 0 | undefined {
|
||||
const { height, width } =
|
||||
attachments && attachments[0] ? attachments[0] : { height: 0, width: 0 };
|
||||
|
||||
|
@ -98,7 +102,7 @@ export function canDisplayImage(attachments?: Array<AttachmentType>) {
|
|||
);
|
||||
}
|
||||
|
||||
export function getThumbnailUrl(attachment: AttachmentType) {
|
||||
export function getThumbnailUrl(attachment: AttachmentType): string {
|
||||
if (attachment.thumbnail) {
|
||||
return attachment.thumbnail.url;
|
||||
}
|
||||
|
@ -106,7 +110,7 @@ export function getThumbnailUrl(attachment: AttachmentType) {
|
|||
return getUrl(attachment);
|
||||
}
|
||||
|
||||
export function getUrl(attachment: AttachmentType) {
|
||||
export function getUrl(attachment: AttachmentType): string {
|
||||
if (attachment.screenshot) {
|
||||
return attachment.screenshot.url;
|
||||
}
|
||||
|
@ -114,7 +118,9 @@ export function getUrl(attachment: AttachmentType) {
|
|||
return attachment.url;
|
||||
}
|
||||
|
||||
export function isImage(attachments?: Array<AttachmentType>) {
|
||||
export function isImage(
|
||||
attachments?: Array<AttachmentType>
|
||||
): boolean | undefined {
|
||||
return (
|
||||
attachments &&
|
||||
attachments[0] &&
|
||||
|
@ -123,14 +129,18 @@ export function isImage(attachments?: Array<AttachmentType>) {
|
|||
);
|
||||
}
|
||||
|
||||
export function isImageAttachment(attachment: AttachmentType) {
|
||||
export function isImageAttachment(
|
||||
attachment: AttachmentType
|
||||
): boolean | undefined {
|
||||
return (
|
||||
attachment &&
|
||||
attachment.contentType &&
|
||||
isImageTypeSupported(attachment.contentType)
|
||||
);
|
||||
}
|
||||
export function hasImage(attachments?: Array<AttachmentType>) {
|
||||
export function hasImage(
|
||||
attachments?: Array<AttachmentType>
|
||||
): string | boolean | undefined {
|
||||
return (
|
||||
attachments &&
|
||||
attachments[0] &&
|
||||
|
@ -138,11 +148,15 @@ export function hasImage(attachments?: Array<AttachmentType>) {
|
|||
);
|
||||
}
|
||||
|
||||
export function isVideo(attachments?: Array<AttachmentType>) {
|
||||
export function isVideo(
|
||||
attachments?: Array<AttachmentType>
|
||||
): boolean | undefined {
|
||||
return attachments && isVideoAttachment(attachments[0]);
|
||||
}
|
||||
|
||||
export function isVideoAttachment(attachment?: AttachmentType) {
|
||||
export function isVideoAttachment(
|
||||
attachment?: AttachmentType
|
||||
): boolean | undefined {
|
||||
return (
|
||||
attachment &&
|
||||
attachment.contentType &&
|
||||
|
@ -150,7 +164,9 @@ export function isVideoAttachment(attachment?: AttachmentType) {
|
|||
);
|
||||
}
|
||||
|
||||
export function hasVideoScreenshot(attachments?: Array<AttachmentType>) {
|
||||
export function hasVideoScreenshot(
|
||||
attachments?: Array<AttachmentType>
|
||||
): string | null | undefined {
|
||||
const firstAttachment = attachments ? attachments[0] : null;
|
||||
|
||||
return (
|
||||
|
@ -308,7 +324,7 @@ export const isFile = (attachment: Attachment): boolean => {
|
|||
export const isVoiceMessage = (attachment: Attachment): boolean => {
|
||||
const flag = SignalService.AttachmentPointer.Flags.VOICE_MESSAGE;
|
||||
const hasFlag =
|
||||
// tslint:disable-next-line no-bitwise
|
||||
// eslint-disable-next-line no-bitwise
|
||||
!is.undefined(attachment.flags) && (attachment.flags & flag) === flag;
|
||||
if (hasFlag) {
|
||||
return true;
|
||||
|
@ -390,7 +406,7 @@ export const getFileExtension = (
|
|||
attachment: Attachment
|
||||
): string | undefined => {
|
||||
if (!attachment.contentType) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
switch (attachment.contentType) {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
// @ts-ignore
|
||||
import Attachments from '../../app/attachments';
|
||||
import { format as formatPhoneNumber } from '../types/PhoneNumber';
|
||||
import { format as formatPhoneNumber } from './PhoneNumber';
|
||||
|
||||
export interface ContactType {
|
||||
name?: Name;
|
||||
|
@ -76,7 +74,7 @@ export function contactSelector(
|
|||
signalAccount?: string;
|
||||
getAbsoluteAttachmentPath: (path: string) => string;
|
||||
}
|
||||
) {
|
||||
): ContactType {
|
||||
const { getAbsoluteAttachmentPath, signalAccount, regionCode } = options;
|
||||
|
||||
let { avatar } = contact;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable camelcase */
|
||||
|
||||
export enum Dialogs {
|
||||
None,
|
||||
Update,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
type LogFunction = (...args: Array<any>) => void;
|
||||
type LogFunction = (...args: Array<unknown>) => void;
|
||||
|
||||
export type LoggerType = {
|
||||
fatal: LogFunction;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export type MIMEType = string & { _mimeTypeBrand: any };
|
||||
export type MIMEType = string & { _mimeTypeBrand: never };
|
||||
|
||||
export const APPLICATION_OCTET_STREAM = 'application/octet-stream' as MIMEType;
|
||||
export const APPLICATION_JSON = 'application/json' as MIMEType;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable camelcase */
|
||||
|
||||
import { Attachment } from './Attachment';
|
||||
import { ContactType } from './Contact';
|
||||
import { IndexableBoolean, IndexablePresence } from './IndexedDB';
|
||||
|
@ -21,7 +23,7 @@ export type IncomingMessage = Readonly<
|
|||
// Optional
|
||||
body?: string;
|
||||
decrypted_at?: number;
|
||||
errors?: Array<any>;
|
||||
errors?: Array<Error>;
|
||||
expireTimer?: number;
|
||||
messageTimer?: number; // deprecated
|
||||
isViewOnce?: number;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { instance, PhoneNumberFormat } from '../util/libphonenumberInstance';
|
||||
import memoizee from 'memoizee';
|
||||
import { instance, PhoneNumberFormat } from '../util/libphonenumberInstance';
|
||||
|
||||
function _format(
|
||||
phoneNumber: string,
|
||||
|
@ -73,8 +73,8 @@ export function normalize(
|
|||
return instance.format(parsedNumber, PhoneNumberFormat.E164);
|
||||
}
|
||||
|
||||
return;
|
||||
return undefined;
|
||||
} catch (error) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ export enum AudioNotificationSupport {
|
|||
export function getAudioNotificationSupport(): AudioNotificationSupport {
|
||||
if (OS.isWindows(MIN_WINDOWS_VERSION) || OS.isMacOS()) {
|
||||
return AudioNotificationSupport.Native;
|
||||
} else if (OS.isLinux()) {
|
||||
}
|
||||
if (OS.isLinux()) {
|
||||
return AudioNotificationSupport.Custom;
|
||||
}
|
||||
return AudioNotificationSupport.None;
|
||||
|
@ -22,11 +23,11 @@ export const isAudioNotificationSupported = (): boolean =>
|
|||
|
||||
// Using `Notification::tag` has a bug on Windows 7:
|
||||
// https://github.com/electron/electron/issues/11189
|
||||
export const isNotificationGroupingSupported = () =>
|
||||
export const isNotificationGroupingSupported = (): boolean =>
|
||||
!OS.isWindows() || OS.isWindows(MIN_WINDOWS_VERSION);
|
||||
|
||||
// the "hide menu bar" option is specific to Windows and Linux
|
||||
export const isHideMenuBarSupported = () => !OS.isMacOS();
|
||||
export const isHideMenuBarSupported = (): boolean => !OS.isMacOS();
|
||||
|
||||
// the "draw attention on notification" option is specific to Windows and Linux
|
||||
export const isDrawAttentionSupported = () => !OS.isMacOS();
|
||||
export const isDrawAttentionSupported = (): boolean => !OS.isMacOS();
|
||||
|
|
|
@ -7,8 +7,11 @@ export class Sound {
|
|||
static sounds = new Map();
|
||||
|
||||
private readonly context = new AudioContext();
|
||||
|
||||
private readonly loop: boolean;
|
||||
|
||||
private node?: AudioBufferSourceNode;
|
||||
|
||||
private readonly src: string;
|
||||
|
||||
constructor(options: SoundOpts) {
|
||||
|
@ -16,7 +19,7 @@ export class Sound {
|
|||
this.src = options.src;
|
||||
}
|
||||
|
||||
async play() {
|
||||
async play(): Promise<void> {
|
||||
if (!Sound.sounds.has(this.src)) {
|
||||
try {
|
||||
const buffer = await Sound.loadSoundFile(this.src);
|
||||
|
@ -44,7 +47,7 @@ export class Sound {
|
|||
this.node = soundNode;
|
||||
}
|
||||
|
||||
stop() {
|
||||
stop(): void {
|
||||
if (this.node) {
|
||||
this.node.stop(0);
|
||||
this.node = undefined;
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import PQueue from 'p-queue';
|
||||
|
||||
// @ts-ignore
|
||||
declare global {
|
||||
interface Window {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
batchers: Array<BatcherType<any>>;
|
||||
waitForAllBatchers: () => Promise<unknown>;
|
||||
}
|
||||
}
|
||||
|
||||
window.batchers = [];
|
||||
|
||||
// @ts-ignore
|
||||
window.waitForAllBatchers = async () => {
|
||||
// @ts-ignore
|
||||
await Promise.all(window.batchers.map(item => item.flushAndWait()));
|
||||
};
|
||||
|
||||
|
@ -32,7 +37,7 @@ export function createBatcher<ItemType>(
|
|||
options: BatcherOptionsType<ItemType>
|
||||
): BatcherType<ItemType> {
|
||||
let batcher: BatcherType<ItemType>;
|
||||
let timeout: any;
|
||||
let timeout: NodeJS.Timeout | null;
|
||||
let items: Array<ItemType> = [];
|
||||
const queue = new PQueue({ concurrency: 1 });
|
||||
|
||||
|
@ -70,18 +75,19 @@ export function createBatcher<ItemType>(
|
|||
async function onIdle() {
|
||||
while (anyPending()) {
|
||||
if (queue.size > 0 || queue.pending > 0) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await queue.onIdle();
|
||||
}
|
||||
|
||||
if (items.length > 0) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await sleep(options.wait * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unregister() {
|
||||
// @ts-ignore
|
||||
window.batchers = window.batchers.filter((item: any) => item !== batcher);
|
||||
window.batchers = window.batchers.filter(item => item !== batcher);
|
||||
}
|
||||
|
||||
async function flushAndWait() {
|
||||
|
@ -104,7 +110,6 @@ export function createBatcher<ItemType>(
|
|||
unregister,
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
window.batchers.push(batcher);
|
||||
|
||||
return batcher;
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { Sound } from './Sound';
|
||||
import PQueue from 'p-queue';
|
||||
import { Sound } from './Sound';
|
||||
|
||||
const ringtoneEventQueue = new PQueue({ concurrency: 1 });
|
||||
|
||||
class CallingTones {
|
||||
private ringtone?: Sound;
|
||||
|
||||
async playEndCall() {
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
async playEndCall(): Promise<void> {
|
||||
const canPlayTone = await window.getCallRingtoneNotification();
|
||||
if (!canPlayTone) {
|
||||
return;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export function cleanSearchTerm(searchTerm: string) {
|
||||
export function cleanSearchTerm(searchTerm: string): string {
|
||||
const lowercase = searchTerm.toLowerCase();
|
||||
const withoutSpecialCharacters = lowercase.replace(
|
||||
/([!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~])/g,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable camelcase */
|
||||
|
||||
// We don't include unicode-12.1.0 because it's over 100MB in size
|
||||
|
||||
// From https://github.com/mathiasbynens/unicode-12.1.0/tree/master/Block
|
||||
|
@ -50,6 +52,7 @@ export function combineNames(given: string, family?: string): null | string {
|
|||
}
|
||||
|
||||
function isAllCKJV(name: string): boolean {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const codePoint of name) {
|
||||
if (!isCKJV(codePoint)) {
|
||||
return false;
|
||||
|
@ -59,7 +62,6 @@ function isAllCKJV(name: string): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line cyclomatic-complexity
|
||||
function isCKJV(codePoint: string) {
|
||||
if (codePoint === ' ') {
|
||||
return true;
|
||||
|
|
|
@ -5,7 +5,7 @@ const ONE_DAY = 24 * 60 * 60 * 1000;
|
|||
export async function deleteForEveryone(
|
||||
message: MessageModelType,
|
||||
doe: DeletesModelType,
|
||||
shouldPersist: boolean = true
|
||||
shouldPersist = true
|
||||
): Promise<void> {
|
||||
// Make sure the server timestamps for the DOE and the matching message
|
||||
// are less than one day apart
|
||||
|
|
|
@ -2,7 +2,7 @@ import moment from 'moment';
|
|||
|
||||
const HOUR = 1000 * 60 * 60;
|
||||
|
||||
export function formatDuration(seconds: number) {
|
||||
export function formatDuration(seconds: number): string {
|
||||
const time = moment.utc(seconds * 1000);
|
||||
|
||||
if (seconds > HOUR) {
|
||||
|
|
|
@ -35,7 +35,7 @@ function isYear(timestamp: moment.Moment) {
|
|||
export function formatRelativeTime(
|
||||
rawTimestamp: number | Date,
|
||||
options: { extended?: boolean; i18n: LocalizerType }
|
||||
) {
|
||||
): string {
|
||||
const { extended, i18n } = options;
|
||||
|
||||
const formats = extended ? getExtendedFormats(i18n) : getShortFormats(i18n);
|
||||
|
@ -45,13 +45,17 @@ export function formatRelativeTime(
|
|||
|
||||
if (diff.years() >= 1 || !isYear(timestamp)) {
|
||||
return replaceSuffix(timestamp.format(formats.y));
|
||||
} else if (diff.months() >= 1 || diff.days() > 6) {
|
||||
}
|
||||
if (diff.months() >= 1 || diff.days() > 6) {
|
||||
return replaceSuffix(timestamp.format(formats.M));
|
||||
} else if (diff.days() >= 1 || !isToday(timestamp)) {
|
||||
}
|
||||
if (diff.days() >= 1 || !isToday(timestamp)) {
|
||||
return replaceSuffix(timestamp.format(formats.d));
|
||||
} else if (diff.hours() >= 1) {
|
||||
}
|
||||
if (diff.hours() >= 1) {
|
||||
return i18n('hoursAgo', [String(diff.hours())]);
|
||||
} else if (diff.minutes() >= 1) {
|
||||
}
|
||||
if (diff.minutes() >= 1) {
|
||||
return i18n('minutesAgo', [String(diff.minutes())]);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,14 +7,14 @@ function removeNonInitials(name: string) {
|
|||
|
||||
export function getInitials(name?: string): string | undefined {
|
||||
if (!name) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cleaned = removeNonInitials(name);
|
||||
const parts = cleaned.split(' ');
|
||||
const initials = parts.map(part => part.trim()[0]);
|
||||
if (!initials.length) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return initials.slice(0, 2).join('');
|
||||
|
|
|
@ -11,7 +11,7 @@ export function getStringForProfileChange(
|
|||
change: ProfileNameChangeType,
|
||||
changedContact: ConversationType,
|
||||
i18n: LocalizerType
|
||||
) {
|
||||
): string {
|
||||
if (change.type === 'name') {
|
||||
return changedContact.name
|
||||
? i18n('contactChangedProfileName', {
|
||||
|
@ -23,7 +23,7 @@ export function getStringForProfileChange(
|
|||
oldProfile: change.oldName,
|
||||
newProfile: change.newName,
|
||||
});
|
||||
} else {
|
||||
throw new Error('TimelineItem: Unknown type!');
|
||||
}
|
||||
|
||||
throw new Error('TimelineItem: Unknown type!');
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ const env = window.getEnvironment();
|
|||
|
||||
const NINETY_ONE_DAYS = 86400 * 91 * 1000;
|
||||
|
||||
export function hasExpired() {
|
||||
export function hasExpired(): boolean {
|
||||
const { getExpiration, log } = window;
|
||||
|
||||
let buildExpiration = 0;
|
||||
|
@ -31,5 +31,5 @@ export function hasExpired() {
|
|||
return Date.now() > buildExpiration && tooFarIntoFuture;
|
||||
}
|
||||
|
||||
return buildExpiration && Date.now() > buildExpiration;
|
||||
return buildExpiration !== 0 && Date.now() > buildExpiration;
|
||||
}
|
||||
|
|
|
@ -5,16 +5,17 @@ import { useDispatch } from 'react-redux';
|
|||
// Restore focus on teardown
|
||||
export const useRestoreFocus = (
|
||||
// The ref for the element to receive initial focus
|
||||
focusRef: React.RefObject<any>,
|
||||
focusRef: React.RefObject<HTMLElement>,
|
||||
// Allow for an optional root element that must exist
|
||||
root: boolean | HTMLElement | null = true
|
||||
) => {
|
||||
): void => {
|
||||
React.useEffect(() => {
|
||||
if (!root) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const lastFocused = document.activeElement as any;
|
||||
const lastFocused = document.activeElement as HTMLElement;
|
||||
|
||||
if (focusRef.current) {
|
||||
focusRef.current.focus();
|
||||
}
|
||||
|
@ -33,10 +34,10 @@ export const useRestoreFocus = (
|
|||
|
||||
export const useBoundActions = <T extends ActionCreatorsMapObject>(
|
||||
actions: T
|
||||
) => {
|
||||
): T => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return React.useMemo(() => {
|
||||
return bindActionCreators(actions, dispatch);
|
||||
}, [dispatch]);
|
||||
}, [actions, dispatch]);
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue