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
|
# TypeScript generated files
|
||||||
ts/**/*.js
|
ts/**/*.js
|
||||||
|
sticker-creator/**/*.js
|
||||||
|
|
||||||
**/*.d.ts
|
**/*.d.ts
|
||||||
webpack.config.ts
|
webpack.config.ts
|
||||||
|
|
||||||
# Temporarily ignored during TSLint transition
|
# Temporarily ignored during TSLint transition
|
||||||
# JIRA: DESKTOP-304
|
# JIRA: DESKTOP-304
|
||||||
sticker-creator/**/*.ts
|
|
||||||
sticker-creator/**/*.tsx
|
|
||||||
ts/*.ts
|
ts/*.ts
|
||||||
ts/components/*.ts
|
|
||||||
ts/components/*.tsx
|
|
||||||
ts/components/stickers/**
|
ts/components/stickers/**
|
||||||
ts/shims/**
|
ts/shims/**
|
||||||
ts/sql/**
|
ts/sql/**
|
||||||
ts/state/**
|
|
||||||
ts/storybook/**
|
ts/storybook/**
|
||||||
ts/styleguide/**
|
ts/styleguide/**
|
||||||
ts/test/**
|
ts/test/**
|
||||||
ts/textsecure/**
|
ts/textsecure/**
|
||||||
ts/types/**
|
|
||||||
ts/updater/**
|
ts/updater/**
|
||||||
ts/util/**
|
|
||||||
|
|
25
.eslintrc.js
25
.eslintrc.js
|
@ -77,6 +77,8 @@ const rules = {
|
||||||
|
|
||||||
// Prefer functional components with default params
|
// Prefer functional components with default params
|
||||||
'react/require-default-props': 'off',
|
'react/require-default-props': 'off',
|
||||||
|
|
||||||
|
'jsx-a11y/label-has-associated-control': ['error', { assert: 'either' }],
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -94,7 +96,7 @@ module.exports = {
|
||||||
|
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ['*.ts', '*.tsx'],
|
files: ['ts/**/*.ts', 'ts/**/*.tsx'],
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: 'tsconfig.json',
|
project: 'tsconfig.json',
|
||||||
|
@ -113,12 +115,31 @@ module.exports = {
|
||||||
],
|
],
|
||||||
rules,
|
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/**'],
|
files: ['**/*.stories.tsx', 'ts/build/**'],
|
||||||
rules: {
|
rules: {
|
||||||
...rules,
|
...rules,
|
||||||
'import/no-extraneous-dependencies': 'off',
|
'import/no-extraneous-dependencies': 'off',
|
||||||
'react/jsx-props-no-spreading': 'off',
|
|
||||||
'react/no-array-index-key': 'off',
|
'react/no-array-index-key': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -27,6 +27,7 @@ test/test.js
|
||||||
# React / TypeScript
|
# React / TypeScript
|
||||||
ts/**/*.js
|
ts/**/*.js
|
||||||
ts/protobuf/*.d.ts
|
ts/protobuf/*.d.ts
|
||||||
|
sticker-creator/**/*.js
|
||||||
|
|
||||||
# CSS Modules
|
# CSS Modules
|
||||||
**/*.scss.d.ts
|
**/*.scss.d.ts
|
||||||
|
|
|
@ -107,6 +107,7 @@
|
||||||
"p-props": "4.0.0",
|
"p-props": "4.0.0",
|
||||||
"p-queue": "6.2.1",
|
"p-queue": "6.2.1",
|
||||||
"pify": "3.0.0",
|
"pify": "3.0.0",
|
||||||
|
"popper.js": "1.15.0",
|
||||||
"protobufjs": "6.8.6",
|
"protobufjs": "6.8.6",
|
||||||
"proxy-agent": "3.1.1",
|
"proxy-agent": "3.1.1",
|
||||||
"react": "16.8.3",
|
"react": "16.8.3",
|
||||||
|
|
|
@ -9,7 +9,7 @@ import * as styles from './index.scss';
|
||||||
import { PageHeader } from '../elements/PageHeader';
|
import { PageHeader } from '../elements/PageHeader';
|
||||||
import { useI18n } from '../util/i18n';
|
import { useI18n } from '../util/i18n';
|
||||||
|
|
||||||
export const App = () => {
|
export const App: React.ComponentType = () => {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -32,7 +32,7 @@ const getClassName = ({ noMessage, empty }: Props) => {
|
||||||
return styles.main;
|
return styles.main;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AppStage = (props: Props) => {
|
export const AppStage: React.ComponentType<Props> = props => {
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
next,
|
next,
|
||||||
|
@ -67,7 +67,7 @@ export const AppStage = (props: Props) => {
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
{addMoreCount > 0 ? (
|
{addMoreCount > 0 ? (
|
||||||
<Text secondary={true}>
|
<Text secondary>
|
||||||
{i18n('StickerCreator--DropStage--addMore', [addMoreCount])}
|
{i18n('StickerCreator--DropStage--addMore', [addMoreCount])}
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -75,7 +75,7 @@ export const AppStage = (props: Props) => {
|
||||||
<Button
|
<Button
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
onClick={onNext || handleNext}
|
onClick={onNext || handleNext}
|
||||||
primary={true}
|
primary
|
||||||
disabled={!nextActive}
|
disabled={!nextActive}
|
||||||
>
|
>
|
||||||
{nextText || i18n('StickerCreator--AppStage--next')}
|
{nextText || i18n('StickerCreator--AppStage--next')}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { StickerGrid } from '../../components/StickerGrid';
|
||||||
import { stickersDuck } from '../../store';
|
import { stickersDuck } from '../../store';
|
||||||
import { useI18n } from '../../util/i18n';
|
import { useI18n } from '../../util/i18n';
|
||||||
|
|
||||||
export const DropStage = () => {
|
export const DropStage: React.ComponentType = () => {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const stickerPaths = stickersDuck.useStickerOrder();
|
const stickerPaths = stickersDuck.useStickerOrder();
|
||||||
const stickersReady = stickersDuck.useStickersReady();
|
const stickersReady = stickersDuck.useStickersReady();
|
||||||
|
@ -17,7 +17,7 @@ export const DropStage = () => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
resetStatus();
|
resetStatus();
|
||||||
}, []);
|
}, [resetStatus]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppStage next="/add-emojis" nextActive={stickersReady}>
|
<AppStage next="/add-emojis" nextActive={stickersReady}>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { StickerGrid } from '../../components/StickerGrid';
|
||||||
import { stickersDuck } from '../../store';
|
import { stickersDuck } from '../../store';
|
||||||
import { useI18n } from '../../util/i18n';
|
import { useI18n } from '../../util/i18n';
|
||||||
|
|
||||||
export const EmojiStage = () => {
|
export const EmojiStage: React.ComponentType = () => {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const emojisReady = stickersDuck.useEmojisReady();
|
const emojisReady = stickersDuck.useEmojisReady();
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { stickersDuck } from '../../store';
|
||||||
import { useI18n } from '../../util/i18n';
|
import { useI18n } from '../../util/i18n';
|
||||||
|
|
||||||
// tslint:disable-next-line max-func-body-length
|
// tslint:disable-next-line max-func-body-length
|
||||||
export const MetaStage = () => {
|
export const MetaStage: React.ComponentType = () => {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const actions = stickersDuck.useStickerActions();
|
const actions = stickersDuck.useStickerActions();
|
||||||
const valid = stickersDuck.useAllDataValid();
|
const valid = stickersDuck.useAllDataValid();
|
||||||
|
@ -47,19 +47,14 @@ export const MetaStage = () => {
|
||||||
|
|
||||||
const onConfirm = React.useCallback(() => {
|
const onConfirm = React.useCallback(() => {
|
||||||
history.push('/upload');
|
history.push('/upload');
|
||||||
}, [setConfirming]);
|
}, []);
|
||||||
|
|
||||||
const coverFrameClass = isDragActive
|
const coverFrameClass = isDragActive
|
||||||
? styles.coverFrameActive
|
? styles.coverFrameActive
|
||||||
: styles.coverFrame;
|
: styles.coverFrame;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppStage
|
<AppStage onNext={onNext} nextActive={valid} noMessage prev="/add-emojis">
|
||||||
onNext={onNext}
|
|
||||||
nextActive={valid}
|
|
||||||
noMessage={true}
|
|
||||||
prev="/add-emojis"
|
|
||||||
>
|
|
||||||
{confirming ? (
|
{confirming ? (
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
title={i18n('StickerCreator--MetaStage--ConfirmDialog--title')}
|
title={i18n('StickerCreator--MetaStage--ConfirmDialog--title')}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { stickersDuck } from '../../store';
|
||||||
import { useI18n } from '../../util/i18n';
|
import { useI18n } from '../../util/i18n';
|
||||||
import { Intl } from '../../../ts/components/Intl';
|
import { Intl } from '../../../ts/components/Intl';
|
||||||
|
|
||||||
export const ShareStage = () => {
|
export const ShareStage: React.ComponentType = () => {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const actions = stickersDuck.useStickerActions();
|
const actions = stickersDuck.useStickerActions();
|
||||||
const title = stickersDuck.useTitle();
|
const title = stickersDuck.useTitle();
|
||||||
|
@ -34,13 +34,13 @@ export const ShareStage = () => {
|
||||||
const handlePrev = React.useCallback(() => {
|
const handlePrev = React.useCallback(() => {
|
||||||
actions.reset();
|
actions.reset();
|
||||||
history.push('/');
|
history.push('/');
|
||||||
}, []);
|
}, [actions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppStage
|
<AppStage
|
||||||
nextText={i18n('StickerCreator--ShareStage--close')}
|
nextText={i18n('StickerCreator--ShareStage--close')}
|
||||||
onNext={handleNext}
|
onNext={handleNext}
|
||||||
nextActive={true}
|
nextActive
|
||||||
prevText={i18n('StickerCreator--ShareStage--createAnother')}
|
prevText={i18n('StickerCreator--ShareStage--createAnother')}
|
||||||
onPrev={handlePrev}
|
onPrev={handlePrev}
|
||||||
>
|
>
|
||||||
|
@ -66,7 +66,7 @@ export const ShareStage = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.row}>
|
<div className={styles.row}>
|
||||||
<Text className={styles.callToAction} center={true}>
|
<Text className={styles.callToAction} center>
|
||||||
<Intl
|
<Intl
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
id="StickerCreator--ShareStage--callToAction"
|
id="StickerCreator--ShareStage--callToAction"
|
||||||
|
|
|
@ -9,13 +9,12 @@ import { Button } from '../../elements/Button';
|
||||||
import { stickersDuck } from '../../store';
|
import { stickersDuck } from '../../store';
|
||||||
import { encryptAndUpload } from '../../util/preload';
|
import { encryptAndUpload } from '../../util/preload';
|
||||||
import { useI18n } from '../../util/i18n';
|
import { useI18n } from '../../util/i18n';
|
||||||
import { Toaster } from '../../components/Toaster';
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
history.push('/add-meta');
|
history.push('/add-meta');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UploadStage = () => {
|
export const UploadStage: React.ComponentType = () => {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const actions = stickersDuck.useStickerActions();
|
const actions = stickersDuck.useStickerActions();
|
||||||
const cover = stickersDuck.useCover();
|
const cover = stickersDuck.useCover();
|
||||||
|
@ -50,10 +49,10 @@ export const UploadStage = () => {
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return noop;
|
return noop;
|
||||||
}, [title, author, cover, orderedData]);
|
}, [actions, title, author, cover, orderedData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppStage empty={true}>
|
<AppStage empty>
|
||||||
<div className={styles.base}>
|
<div className={styles.base}>
|
||||||
<H2>{i18n('StickerCreator--UploadStage--title')}</H2>
|
<H2>{i18n('StickerCreator--UploadStage--title')}</H2>
|
||||||
<Text>
|
<Text>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StoryRow } from '../elements/StoryRow';
|
|
||||||
import { ShareButtons } from './ShareButtons';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import { text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { StoryRow } from '../elements/StoryRow';
|
||||||
|
import { ShareButtons } from './ShareButtons';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/components', module).add('ShareButtons', () => {
|
storiesOf('Sticker Creator/components', module).add('ShareButtons', () => {
|
||||||
const value = text('value', 'https://signal.org');
|
const value = text('value', 'https://signal.org');
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* eslint-disable max-len */
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as styles from './ShareButtons.scss';
|
import * as styles from './ShareButtons.scss';
|
||||||
import { useI18n } from '../util/i18n';
|
import { useI18n } from '../util/i18n';
|
||||||
|
@ -6,7 +8,8 @@ export type Props = {
|
||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ShareButtons = React.memo(({ value }: Props) => {
|
export const ShareButtons: React.ComponentType<Props> = React.memo(
|
||||||
|
({ value }) => {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
const buttonPaths = React.useMemo<
|
const buttonPaths = React.useMemo<
|
||||||
|
@ -53,6 +56,7 @@ export const ShareButtons = React.memo(({ value }: Props) => {
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
{buttonPaths.map(([title, fill, path, url]) => (
|
{buttonPaths.map(([title, fill, path, url]) => (
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
key={path}
|
key={path}
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
onClick={() => window.open(url)}
|
onClick={() => window.open(url)}
|
||||||
|
@ -66,4 +70,5 @@ export const ShareButtons = React.memo(({ value }: Props) => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StoryRow } from '../elements/StoryRow';
|
|
||||||
import { StickerFrame } from './StickerFrame';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { boolean, select, text } from '@storybook/addon-knobs';
|
import { boolean, select, text } from '@storybook/addon-knobs';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
import { StoryRow } from '../elements/StoryRow';
|
||||||
|
import { StickerFrame } from './StickerFrame';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/components', module).add('StickerFrame', () => {
|
storiesOf('Sticker Creator/components', module).add('StickerFrame', () => {
|
||||||
const image = text('image url', '/fixtures/512x515-thumbs-up-lincoln.webp');
|
const image = text('image url', '/fixtures/512x515-thumbs-up-lincoln.webp');
|
||||||
const showGuide = boolean('show guide', true);
|
const showGuide = boolean('show guide', true);
|
||||||
|
@ -16,7 +16,7 @@ storiesOf('Sticker Creator/components', module).add('StickerFrame', () => {
|
||||||
const [emoji, setEmoji] = React.useState(undefined);
|
const [emoji, setEmoji] = React.useState(undefined);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StoryRow top={true}>
|
<StoryRow top>
|
||||||
<StickerFrame
|
<StickerFrame
|
||||||
id="1337"
|
id="1337"
|
||||||
emojiData={emoji}
|
emojiData={emoji}
|
||||||
|
|
|
@ -31,7 +31,13 @@ export type Props = Partial<
|
||||||
readonly image?: string;
|
readonly image?: string;
|
||||||
readonly mode?: Mode;
|
readonly mode?: Mode;
|
||||||
readonly showGuide?: boolean;
|
readonly showGuide?: boolean;
|
||||||
onPickEmoji?({ id: string, emoji: EmojiPickData }): unknown;
|
onPickEmoji?({
|
||||||
|
id,
|
||||||
|
emoji,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
emoji: EmojiPickDataType;
|
||||||
|
}): unknown;
|
||||||
onRemove?(id: string): unknown;
|
onRemove?(id: string): unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -180,7 +186,8 @@ export const StickerFrame = React.memo(
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
ref={rootRef}
|
ref={rootRef}
|
||||||
>
|
>
|
||||||
{mode !== 'add' ? (
|
{// eslint-disable-next-line no-nested-ternary
|
||||||
|
mode !== 'add' ? (
|
||||||
image ? (
|
image ? (
|
||||||
<ImageHandle src={image} />
|
<ImageHandle src={image} />
|
||||||
) : (
|
) : (
|
||||||
|
@ -191,14 +198,11 @@ export const StickerFrame = React.memo(
|
||||||
<div className={styles.guide} />
|
<div className={styles.guide} />
|
||||||
) : null}
|
) : null}
|
||||||
{mode === 'add' && onDrop ? (
|
{mode === 'add' && onDrop ? (
|
||||||
<DropZone
|
<DropZone onDrop={onDrop} inner onDragActive={setDragActive} />
|
||||||
onDrop={onDrop}
|
|
||||||
inner={true}
|
|
||||||
onDragActive={setDragActive}
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
{mode === 'removable' ? (
|
{mode === 'removable' ? (
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className={styles.closeButton}
|
className={styles.closeButton}
|
||||||
onClick={handleRemove}
|
onClick={handleRemove}
|
||||||
// Reverse the mouseenter/leave logic for the remove button so
|
// Reverse the mouseenter/leave logic for the remove button so
|
||||||
|
@ -214,6 +218,7 @@ export const StickerFrame = React.memo(
|
||||||
<PopperReference>
|
<PopperReference>
|
||||||
{({ ref }) => (
|
{({ ref }) => (
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={styles.emojiButton}
|
className={styles.emojiButton}
|
||||||
onClick={handleToggleEmojiPicker}
|
onClick={handleToggleEmojiPicker}
|
||||||
|
|
|
@ -56,7 +56,6 @@ const InnerGrid = SortableContainer(
|
||||||
const webp = await convertToWebp(path);
|
const webp = await convertToWebp(path);
|
||||||
actions.addWebp(webp);
|
actions.addWebp(webp);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// @ts-ignore
|
|
||||||
window.log.error('Error processing image:', e);
|
window.log.error('Error processing image:', e);
|
||||||
actions.removeSticker(path);
|
actions.removeSticker(path);
|
||||||
actions.addToast({
|
actions.addToast({
|
||||||
|
@ -114,7 +113,7 @@ export const StickerGrid = SortableContainer((props: Props) => {
|
||||||
ids={ids}
|
ids={ids}
|
||||||
axis="xy"
|
axis="xy"
|
||||||
onSortEnd={handleSortEnd}
|
onSortEnd={handleSortEnd}
|
||||||
useDragHandle={true}
|
useDragHandle
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StoryRow } from '../elements/StoryRow';
|
|
||||||
import { StickerPackPreview } from './StickerPackPreview';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import { text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { StoryRow } from '../elements/StoryRow';
|
||||||
|
import { StickerPackPreview } from './StickerPackPreview';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/components', module).add(
|
storiesOf('Sticker Creator/components', module).add(
|
||||||
'StickerPackPreview',
|
'StickerPackPreview',
|
||||||
() => {
|
() => {
|
||||||
|
@ -14,7 +14,7 @@ storiesOf('Sticker Creator/components', module).add(
|
||||||
const images = React.useMemo(() => Array(39).fill(image), [image]);
|
const images = React.useMemo(() => Array(39).fill(image), [image]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StoryRow top={true}>
|
<StoryRow top>
|
||||||
<StickerPackPreview images={images} title={title} author={author} />
|
<StickerPackPreview images={images} title={title} author={author} />
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,8 +19,8 @@ export const StickerPackPreview = React.memo(
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.scroller}>
|
<div className={styles.scroller}>
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{images.map((src, id) => (
|
{images.map(src => (
|
||||||
<img key={id} className={styles.sticker} src={src} alt={src} />
|
<img key={src} className={styles.sticker} src={src} alt={src} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { debounce, dropRight } from 'lodash';
|
import { debounce, dropRight } from 'lodash';
|
||||||
import { StoryRow } from '../elements/StoryRow';
|
|
||||||
import { Toaster } from './Toaster';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { text as textKnob } from '@storybook/addon-knobs';
|
import { text as textKnob } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { StoryRow } from '../elements/StoryRow';
|
||||||
|
import { Toaster } from './Toaster';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/components', module).add('Toaster', () => {
|
storiesOf('Sticker Creator/components', module).add('Toaster', () => {
|
||||||
const inputText = textKnob('Slices', ['error 1', 'error 2'].join('|'));
|
const inputText = textKnob('Slices', ['error 1', 'error 2'].join('|'));
|
||||||
const initialState = React.useMemo(() => inputText.split('|'), [inputText]);
|
const initialState = React.useMemo(() => inputText.split('|'), [inputText]);
|
||||||
const [state, setState] = React.useState(initialState);
|
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(
|
const handleDismiss = React.useCallback(
|
||||||
// Debounce is required here since auto-dismiss is asynchronously called
|
// Debounce is required here since auto-dismiss is asynchronously called
|
||||||
// from multiple rendered instances (multiple themes)
|
// 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 * as React from 'react';
|
||||||
import { StoryRow } from './StoryRow';
|
|
||||||
import { Button } from './Button';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import { text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { StoryRow } from './StoryRow';
|
||||||
|
import { Button } from './Button';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/elements', module).add('Button', () => {
|
storiesOf('Sticker Creator/elements', module).add('Button', () => {
|
||||||
const onClick = action('onClick');
|
const onClick = action('onClick');
|
||||||
const child = text('text', 'foo bar');
|
const child = text('text', 'foo bar');
|
||||||
|
@ -13,12 +13,12 @@ storiesOf('Sticker Creator/elements', module).add('Button', () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StoryRow>
|
<StoryRow>
|
||||||
<Button onClick={onClick} primary={true}>
|
<Button onClick={onClick} primary>
|
||||||
{child}
|
{child}
|
||||||
</Button>
|
</Button>
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
<StoryRow>
|
<StoryRow>
|
||||||
<Button onClick={onClick} primary={true} disabled={true}>
|
<Button onClick={onClick} primary disabled>
|
||||||
{child}
|
{child}
|
||||||
</Button>
|
</Button>
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
|
@ -26,27 +26,27 @@ storiesOf('Sticker Creator/elements', module).add('Button', () => {
|
||||||
<Button onClick={onClick}>{child}</Button>
|
<Button onClick={onClick}>{child}</Button>
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
<StoryRow>
|
<StoryRow>
|
||||||
<Button onClick={onClick} disabled={true}>
|
<Button onClick={onClick} disabled>
|
||||||
{child}
|
{child}
|
||||||
</Button>
|
</Button>
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
<StoryRow>
|
<StoryRow>
|
||||||
<Button onClick={onClick} primary={true} pill={true}>
|
<Button onClick={onClick} primary pill>
|
||||||
{child}
|
{child}
|
||||||
</Button>
|
</Button>
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
<StoryRow>
|
<StoryRow>
|
||||||
<Button onClick={onClick} primary={true} pill={true} disabled={true}>
|
<Button onClick={onClick} primary pill disabled>
|
||||||
{child}
|
{child}
|
||||||
</Button>
|
</Button>
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
<StoryRow>
|
<StoryRow>
|
||||||
<Button onClick={onClick} pill={true}>
|
<Button onClick={onClick} pill>
|
||||||
{child}
|
{child}
|
||||||
</Button>
|
</Button>
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
<StoryRow>
|
<StoryRow>
|
||||||
<Button onClick={onClick} pill={true} disabled={true}>
|
<Button onClick={onClick} pill disabled>
|
||||||
{child}
|
{child}
|
||||||
</Button>
|
</Button>
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
|
|
|
@ -3,10 +3,8 @@ import * as classnames from 'classnames';
|
||||||
import * as styles from './Button.scss';
|
import * as styles from './Button.scss';
|
||||||
|
|
||||||
export type Props = React.HTMLProps<HTMLButtonElement> & {
|
export type Props = React.HTMLProps<HTMLButtonElement> & {
|
||||||
className?: string;
|
|
||||||
pill?: boolean;
|
pill?: boolean;
|
||||||
primary?: boolean;
|
primary?: boolean;
|
||||||
children: React.ReactNode;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getClassName = ({ primary, pill }: Props) => {
|
const getClassName = ({ primary, pill }: Props) => {
|
||||||
|
@ -25,12 +23,15 @@ const getClassName = ({ primary, pill }: Props) => {
|
||||||
return styles.base;
|
return styles.base;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Button = (props: Props) => {
|
export const Button: React.ComponentType<Props> = ({
|
||||||
const { className, pill, primary, children, ...otherProps } = props;
|
className,
|
||||||
|
children,
|
||||||
|
...otherProps
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={classnames(getClassName(props), className)}
|
type="button"
|
||||||
|
className={classnames(getClassName(otherProps), className)}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StoryRow } from './StoryRow';
|
|
||||||
import { ConfirmDialog } from './ConfirmDialog';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import { text } from '@storybook/addon-knobs';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
import { StoryRow } from './StoryRow';
|
||||||
|
import { ConfirmDialog } from './ConfirmDialog';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/elements', module).add('ConfirmDialog', () => {
|
storiesOf('Sticker Creator/elements', module).add('ConfirmDialog', () => {
|
||||||
const title = text('title', 'Foo bar banana baz?');
|
const title = text('title', 'Foo bar banana baz?');
|
||||||
const child = text(
|
const child = text(
|
||||||
|
|
|
@ -11,14 +11,14 @@ export type Props = {
|
||||||
readonly onCancel: () => unknown;
|
readonly onCancel: () => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ConfirmDialog = ({
|
export const ConfirmDialog: React.ComponentType<Props> = ({
|
||||||
title,
|
title,
|
||||||
children,
|
children,
|
||||||
confirm,
|
confirm,
|
||||||
cancel,
|
cancel,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onCancel,
|
onCancel,
|
||||||
}: Props) => {
|
}) => {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const cancelText = cancel || i18n('StickerCreator--ConfirmDialog--cancel');
|
const cancelText = cancel || i18n('StickerCreator--ConfirmDialog--cancel');
|
||||||
|
|
||||||
|
@ -27,10 +27,14 @@ export const ConfirmDialog = ({
|
||||||
<h1 className={styles.title}>{title}</h1>
|
<h1 className={styles.title}>{title}</h1>
|
||||||
<p className={styles.text}>{children}</p>
|
<p className={styles.text}>{children}</p>
|
||||||
<div className={styles.bottom}>
|
<div className={styles.bottom}>
|
||||||
<button className={styles.button} onClick={onCancel}>
|
<button type="button" className={styles.button} onClick={onCancel}>
|
||||||
{cancelText}
|
{cancelText}
|
||||||
</button>
|
</button>
|
||||||
<button className={styles.buttonPrimary} onClick={onConfirm}>
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.buttonPrimary}
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
{confirm}
|
{confirm}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StoryRow } from './StoryRow';
|
|
||||||
import { CopyText } from './CopyText';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import { text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { StoryRow } from './StoryRow';
|
||||||
|
import { CopyText } from './CopyText';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/elements', module).add('CopyText', () => {
|
storiesOf('Sticker Creator/elements', module).add('CopyText', () => {
|
||||||
const label = text('label', 'foo bar');
|
const label = text('label', 'foo bar');
|
||||||
const value = text('value', 'foo bar');
|
const value = text('value', 'foo bar');
|
||||||
|
|
|
@ -10,7 +10,8 @@ export type Props = {
|
||||||
onCopy?: () => unknown;
|
onCopy?: () => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CopyText = React.memo(({ label, onCopy, value }: Props) => {
|
export const CopyText: React.ComponentType<Props> = React.memo(
|
||||||
|
({ label, onCopy, value }) => {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const handleClick = React.useCallback(() => {
|
const handleClick = React.useCallback(() => {
|
||||||
copy(value);
|
copy(value);
|
||||||
|
@ -26,11 +27,12 @@ export const CopyText = React.memo(({ label, onCopy, value }: Props) => {
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
value={value}
|
value={value}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
readOnly={true}
|
readOnly
|
||||||
/>
|
/>
|
||||||
<Button onClick={handleClick}>
|
<Button onClick={handleClick}>
|
||||||
{i18n('StickerCreator--CopyText--button')}
|
{i18n('StickerCreator--CopyText--button')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { DropZone } from './DropZone';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
import { DropZone } from './DropZone';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/elements', module).add('DropZone', () => {
|
storiesOf('Sticker Creator/elements', module).add('DropZone', () => {
|
||||||
return <DropZone onDrop={action('onDrop')} />;
|
return <DropZone onDrop={action('onDrop')} />;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone, FileWithPath } from 'react-dropzone';
|
||||||
import * as styles from './DropZone.scss';
|
import * as styles from './DropZone.scss';
|
||||||
import { useI18n } from '../util/i18n';
|
import { useI18n } from '../util/i18n';
|
||||||
|
|
||||||
|
@ -21,12 +21,12 @@ const getClassName = ({ inner }: Props, isDragActive: boolean) => {
|
||||||
return styles.standalone;
|
return styles.standalone;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DropZone = (props: Props) => {
|
export const DropZone: React.ComponentType<Props> = props => {
|
||||||
const { inner, onDrop, onDragActive } = props;
|
const { inner, onDrop, onDragActive } = props;
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
const handleDrop = React.useCallback(
|
const handleDrop = React.useCallback(
|
||||||
files => {
|
(files: ReadonlyArray<FileWithPath>) => {
|
||||||
onDrop(files.map(({ path }) => path));
|
onDrop(files.map(({ path }) => path));
|
||||||
},
|
},
|
||||||
[onDrop]
|
[onDrop]
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StoryRow } from './StoryRow';
|
|
||||||
import { LabeledCheckbox } from './LabeledCheckbox';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import { text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { StoryRow } from './StoryRow';
|
||||||
|
import { LabeledCheckbox } from './LabeledCheckbox';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/elements', module).add('Labeled Checkbox', () => {
|
storiesOf('Sticker Creator/elements', module).add('Labeled Checkbox', () => {
|
||||||
const child = text('label', 'foo bar');
|
const child = text('label', 'foo bar');
|
||||||
const [checked, setChecked] = React.useState(false);
|
const [checked, setChecked] = React.useState(false);
|
||||||
|
|
|
@ -17,7 +17,9 @@ const checkSvg = (
|
||||||
export const LabeledCheckbox = React.memo(
|
export const LabeledCheckbox = React.memo(
|
||||||
({ children, value, onChange }: Props) => {
|
({ children, value, onChange }: Props) => {
|
||||||
const handleChange = React.useCallback(() => {
|
const handleChange = React.useCallback(() => {
|
||||||
|
if (onChange !== undefined) {
|
||||||
onChange(!value);
|
onChange(!value);
|
||||||
|
}
|
||||||
}, [onChange, value]);
|
}, [onChange, value]);
|
||||||
|
|
||||||
const className = value ? styles.checkboxChecked : styles.checkbox;
|
const className = value ? styles.checkboxChecked : styles.checkbox;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StoryRow } from './StoryRow';
|
|
||||||
import { LabeledInput } from './LabeledInput';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import { text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { StoryRow } from './StoryRow';
|
||||||
|
import { LabeledInput } from './LabeledInput';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/elements', module).add('LabeledInput', () => {
|
storiesOf('Sticker Creator/elements', module).add('LabeledInput', () => {
|
||||||
const child = text('label', 'foo bar');
|
const child = text('label', 'foo bar');
|
||||||
const placeholder = text('placeholder', 'foo bar');
|
const placeholder = text('placeholder', 'foo bar');
|
||||||
|
|
|
@ -13,7 +13,9 @@ export const LabeledInput = React.memo(
|
||||||
({ children, value, placeholder, onChange }: Props) => {
|
({ children, value, placeholder, onChange }: Props) => {
|
||||||
const handleChange = React.useCallback(
|
const handleChange = React.useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (onChange !== undefined) {
|
||||||
onChange(e.currentTarget.value);
|
onChange(e.currentTarget.value);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[onChange]
|
[onChange]
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StoryRow } from './StoryRow';
|
|
||||||
import { MessageBubble } from './MessageBubble';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { number, text } from '@storybook/addon-knobs';
|
import { number, text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { StoryRow } from './StoryRow';
|
||||||
|
import { MessageBubble } from './MessageBubble';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/elements', module).add('MessageBubble', () => {
|
storiesOf('Sticker Creator/elements', module).add('MessageBubble', () => {
|
||||||
const child = text('text', 'Foo bar banana baz');
|
const child = text('text', 'Foo bar banana baz');
|
||||||
const minutesAgo = number('minutesAgo', 3);
|
const minutesAgo = number('minutesAgo', 3);
|
||||||
|
|
|
@ -6,7 +6,10 @@ export type Props = Pick<MessageMetaProps, 'minutesAgo'> & {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MessageBubble = ({ children, minutesAgo }: Props) => {
|
export const MessageBubble: React.ComponentType<Props> = ({
|
||||||
|
children,
|
||||||
|
minutesAgo,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.base}>
|
<div className={styles.base}>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StoryRow } from './StoryRow';
|
|
||||||
import { MessageSticker } from './MessageSticker';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { number, text } from '@storybook/addon-knobs';
|
import { number, text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { StoryRow } from './StoryRow';
|
||||||
|
import { MessageSticker } from './MessageSticker';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/elements', module).add('MessageSticker', () => {
|
storiesOf('Sticker Creator/elements', module).add('MessageSticker', () => {
|
||||||
const image = text('image url', '/fixtures/512x515-thumbs-up-lincoln.webp');
|
const image = text('image url', '/fixtures/512x515-thumbs-up-lincoln.webp');
|
||||||
const minutesAgo = number('minutesAgo', 3);
|
const minutesAgo = number('minutesAgo', 3);
|
||||||
|
|
|
@ -6,7 +6,11 @@ export type Props = MessageMetaProps & {
|
||||||
image: string;
|
image: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MessageSticker = ({ image, kind, minutesAgo }: Props) => {
|
export const MessageSticker: React.ComponentType<Props> = ({
|
||||||
|
image,
|
||||||
|
kind,
|
||||||
|
minutesAgo,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.base}>
|
<div className={styles.base}>
|
||||||
<img src={image} alt="Sticker" className={styles.image} />
|
<img src={image} alt="Sticker" className={styles.image} />
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StoryRow } from './StoryRow';
|
|
||||||
import { PageHeader } from './PageHeader';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import { text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { StoryRow } from './StoryRow';
|
||||||
|
import { PageHeader } from './PageHeader';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/elements', module).add('PageHeader', () => {
|
storiesOf('Sticker Creator/elements', module).add('PageHeader', () => {
|
||||||
const child = text('text', 'foo bar');
|
const child = text('text', 'foo bar');
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StoryRow } from './StoryRow';
|
|
||||||
import { ProgressBar } from './ProgressBar';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { number } from '@storybook/addon-knobs';
|
import { number } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { StoryRow } from './StoryRow';
|
||||||
|
import { ProgressBar } from './ProgressBar';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/elements', module).add('ProgressBar', () => {
|
storiesOf('Sticker Creator/elements', module).add('ProgressBar', () => {
|
||||||
const count = number('count', 5);
|
const count = number('count', 5);
|
||||||
const total = number('total', 10);
|
const total = number('total', 10);
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StoryRow } from './StoryRow';
|
|
||||||
import { StickerPreview } from './StickerPreview';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import { text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { StoryRow } from './StoryRow';
|
||||||
|
import { StickerPreview } from './StickerPreview';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/elements', module).add('StickerPreview', () => {
|
storiesOf('Sticker Creator/elements', module).add('StickerPreview', () => {
|
||||||
const image = text('image url', '/fixtures/512x515-thumbs-up-lincoln.webp');
|
const image = text('image url', '/fixtures/512x515-thumbs-up-lincoln.webp');
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as styles from './StoryRow.scss';
|
import * as styles from './StoryRow.scss';
|
||||||
|
|
||||||
export type Props = {
|
type Props = {
|
||||||
children: React.ReactChild;
|
|
||||||
left?: boolean;
|
left?: boolean;
|
||||||
right?: boolean;
|
right?: boolean;
|
||||||
top?: boolean;
|
top?: boolean;
|
||||||
|
@ -29,6 +28,7 @@ const getClassName = ({ left, right, top, bottom }: Props) => {
|
||||||
return styles.base;
|
return styles.base;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StoryRow = (props: Props) => (
|
export const StoryRow: React.ComponentType<Props> = ({
|
||||||
<div className={getClassName(props)}>{props.children}</div>
|
children,
|
||||||
);
|
...props
|
||||||
|
}) => <div className={getClassName(props)}>{children}</div>;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StoryRow } from './StoryRow';
|
|
||||||
import { Toast } from './Toast';
|
|
||||||
|
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import { text } from '@storybook/addon-knobs';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
import { StoryRow } from './StoryRow';
|
||||||
|
import { Toast } from './Toast';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/elements', module).add('Toast', () => {
|
storiesOf('Sticker Creator/elements', module).add('Toast', () => {
|
||||||
const child = text('text', 'foo bar');
|
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) => (
|
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}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
));
|
));
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
import * as React from 'react';
|
/* eslint-disable no-script-url, jsx-a11y/anchor-is-valid */
|
||||||
import { StoryRow } from './StoryRow';
|
|
||||||
import { H1, H2, Text } from './Typography';
|
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import { text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { StoryRow } from './StoryRow';
|
||||||
|
import { H1, H2, Text } from './Typography';
|
||||||
|
|
||||||
storiesOf('Sticker Creator/elements', module).add('Typography', () => {
|
storiesOf('Sticker Creator/elements', module).add('Typography', () => {
|
||||||
const child = text('text', 'foo bar');
|
const child = text('text', 'foo bar');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StoryRow left={true}>
|
<StoryRow left>
|
||||||
<H1>{child}</H1>
|
<H1>{child}</H1>
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
<StoryRow left={true}>
|
<StoryRow left>
|
||||||
<H2>{child}</H2>
|
<H2>{child}</H2>
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
<StoryRow left={true}>
|
<StoryRow left>
|
||||||
<Text>
|
<Text>
|
||||||
{child} {child} {child} {child}
|
{child} {child} {child} {child}
|
||||||
</Text>
|
</Text>
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
<StoryRow left={true}>
|
<StoryRow left>
|
||||||
<Text>
|
<Text>
|
||||||
{child} {child} {child} {child}{' '}
|
{child} {child} {child} {child}{' '}
|
||||||
<a href="javascript: void 0;">
|
<a href="javascript: void 0;">
|
||||||
|
|
|
@ -35,7 +35,6 @@ export const Text = React.memo(
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
center,
|
center,
|
||||||
wide,
|
|
||||||
secondary,
|
secondary,
|
||||||
...rest
|
...rest
|
||||||
}: Props & ParagraphProps) => (
|
}: Props & ParagraphProps) => (
|
||||||
|
|
|
@ -4,5 +4,6 @@ import { Root } from './root';
|
||||||
|
|
||||||
const root = document.getElementById('root');
|
const root = document.getElementById('root');
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log('Sticker Creator: Starting root');
|
console.log('Sticker Creator: Starting root');
|
||||||
render(<Root />, root);
|
render(<Root />, root);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// tslint:disable-next-line no-submodule-imports
|
|
||||||
import { hot } from 'react-hot-loader/root';
|
import { hot } from 'react-hot-loader/root';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Provider as ReduxProvider } from 'react-redux';
|
import { Provider as ReduxProvider } from 'react-redux';
|
||||||
|
@ -8,7 +7,12 @@ import { history } from './util/history';
|
||||||
import { store } from './store';
|
import { store } from './store';
|
||||||
import { I18n } from './util/i18n';
|
import { I18n } from './util/i18n';
|
||||||
|
|
||||||
// @ts-ignore
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
localeMessages: { [key: string]: { message: string } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { localeMessages } = window;
|
const { localeMessages } = window;
|
||||||
|
|
||||||
const ColdRoot = () => (
|
const ColdRoot = () => (
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// tslint:disable no-dynamic-delete
|
/* eslint-disable no-param-reassign */
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
|
@ -13,8 +13,9 @@ import { clamp, find, isNumber, pull, remove, take, uniq } from 'lodash';
|
||||||
import { SortEnd } from 'react-sortable-hoc';
|
import { SortEnd } from 'react-sortable-hoc';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import arrayMove from 'array-move';
|
import arrayMove from 'array-move';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
import { AppState } from '../reducer';
|
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 { EmojiPickDataType } from '../../../ts/components/emoji/EmojiPicker';
|
||||||
import { convertShortName } from '../../../ts/components/emoji/lib';
|
import { convertShortName } from '../../../ts/components/emoji/lib';
|
||||||
|
|
||||||
|
@ -46,6 +47,16 @@ export const minStickers = 1;
|
||||||
export const maxStickers = 200;
|
export const maxStickers = 200;
|
||||||
export const maxByteSize = 100 * 1024;
|
export const maxByteSize = 100 * 1024;
|
||||||
|
|
||||||
|
interface StateStickerData {
|
||||||
|
readonly webp?: WebpData;
|
||||||
|
readonly emoji?: EmojiPickDataType;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateToastData {
|
||||||
|
key: string;
|
||||||
|
subs?: Array<number | string>;
|
||||||
|
}
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
readonly order: Array<string>;
|
readonly order: Array<string>;
|
||||||
readonly cover?: WebpData;
|
readonly cover?: WebpData;
|
||||||
|
@ -53,13 +64,26 @@ export type State = {
|
||||||
readonly author: string;
|
readonly author: string;
|
||||||
readonly packId: string;
|
readonly packId: string;
|
||||||
readonly packKey: string;
|
readonly packKey: string;
|
||||||
readonly toasts: Array<{ key: string; subs?: Array<number | string> }>;
|
readonly toasts: Array<StateToastData>;
|
||||||
readonly data: {
|
readonly data: {
|
||||||
readonly [src: string]: {
|
readonly [src: string]: StateStickerData;
|
||||||
readonly webp?: WebpData;
|
|
||||||
readonly emoji?: EmojiPickDataType;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 = {
|
const defaultState: State = {
|
||||||
|
@ -193,21 +217,22 @@ export const reducer = reduceReducers<State>(
|
||||||
defaultState
|
defaultState
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useTitle = () =>
|
export const useTitle = (): string =>
|
||||||
useSelector(({ stickers }: AppState) => stickers.title);
|
useSelector(({ stickers }: AppState) => stickers.title);
|
||||||
export const useAuthor = () =>
|
|
||||||
|
export const useAuthor = (): string =>
|
||||||
useSelector(({ stickers }: AppState) => stickers.author);
|
useSelector(({ stickers }: AppState) => stickers.author);
|
||||||
|
|
||||||
export const useCover = () =>
|
export const useCover = (): WebpData | undefined =>
|
||||||
useSelector(({ stickers }: AppState) => stickers.cover);
|
useSelector(({ stickers }: AppState) => stickers.cover);
|
||||||
|
|
||||||
export const useStickerOrder = () =>
|
export const useStickerOrder = (): Array<string> =>
|
||||||
useSelector(({ stickers }: AppState) => stickers.order);
|
useSelector(({ stickers }: AppState) => stickers.order);
|
||||||
|
|
||||||
export const useStickerData = (src: string) =>
|
export const useStickerData = (src: string): StateStickerData =>
|
||||||
useSelector(({ stickers }: AppState) => stickers.data[src]);
|
useSelector(({ stickers }: AppState) => stickers.data[src]);
|
||||||
|
|
||||||
export const useStickersReady = () =>
|
export const useStickersReady = (): boolean =>
|
||||||
useSelector(
|
useSelector(
|
||||||
({ stickers }: AppState) =>
|
({ stickers }: AppState) =>
|
||||||
stickers.order.length >= minStickers &&
|
stickers.order.length >= minStickers &&
|
||||||
|
@ -215,12 +240,12 @@ export const useStickersReady = () =>
|
||||||
Object.values(stickers.data).every(({ webp }) => !!webp)
|
Object.values(stickers.data).every(({ webp }) => !!webp)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useEmojisReady = () =>
|
export const useEmojisReady = (): boolean =>
|
||||||
useSelector(({ stickers }: AppState) =>
|
useSelector(({ stickers }: AppState) =>
|
||||||
Object.values(stickers.data).every(({ emoji }) => !!emoji)
|
Object.values(stickers.data).every(({ emoji }) => !!emoji)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useAllDataValid = () => {
|
export const useAllDataValid = (): boolean => {
|
||||||
const stickersReady = useStickersReady();
|
const stickersReady = useStickersReady();
|
||||||
const emojisReady = useEmojisReady();
|
const emojisReady = useEmojisReady();
|
||||||
const cover = useCover();
|
const cover = useCover();
|
||||||
|
@ -236,10 +261,12 @@ const selectUrl = createSelector(
|
||||||
(id, key) => `https://signal.art/addstickers/#pack_id=${id}&pack_key=${key}`
|
(id, key) => `https://signal.art/addstickers/#pack_id=${id}&pack_key=${key}`
|
||||||
);
|
);
|
||||||
|
|
||||||
export const usePackUrl = () => useSelector(selectUrl);
|
export const usePackUrl = (): string => useSelector(selectUrl);
|
||||||
export const useToasts = () =>
|
|
||||||
|
export const useToasts = (): Array<StateToastData> =>
|
||||||
useSelector(({ stickers }: AppState) => stickers.toasts);
|
useSelector(({ stickers }: AppState) => stickers.toasts);
|
||||||
export const useAddMoreCount = () =>
|
|
||||||
|
export const useAddMoreCount = (): number =>
|
||||||
useSelector(({ stickers }: AppState) =>
|
useSelector(({ stickers }: AppState) =>
|
||||||
clamp(minStickers - stickers.order.length, 0, minStickers)
|
clamp(minStickers - stickers.order.length, 0, minStickers)
|
||||||
);
|
);
|
||||||
|
@ -251,21 +278,23 @@ const selectOrderedData = createSelector(
|
||||||
order.map(id => ({
|
order.map(id => ({
|
||||||
...data[id],
|
...data[id],
|
||||||
emoji: convertShortName(
|
emoji: convertShortName(
|
||||||
data[id].emoji.shortName,
|
(data[id].emoji as EmojiPickDataType).shortName,
|
||||||
data[id].emoji.skinTone
|
(data[id].emoji as EmojiPickDataType).skinTone
|
||||||
),
|
),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useSelectOrderedData = () => useSelector(selectOrderedData);
|
export const useSelectOrderedData = (): Array<StickerData> =>
|
||||||
|
useSelector(selectOrderedData);
|
||||||
|
|
||||||
const selectOrderedImagePaths = createSelector(selectOrderedData, data =>
|
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();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { combineReducers, Reducer } from 'redux';
|
import { combineReducers, Reducer } from 'redux';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
import { reducer as stickers } from './ducks/stickers';
|
import { reducer as stickers } from './ducks/stickers';
|
||||||
|
|
||||||
export const reducer = combineReducers({
|
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 } };
|
messages: { [key: string]: { message: string } };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const I18n = ({ messages, children }: I18nProps) => {
|
export const I18n = ({ messages, children }: I18nProps): JSX.Element => {
|
||||||
const getMessage = React.useCallback<I18nFn>(
|
const getMessage = React.useCallback<I18nFn>(
|
||||||
(key, substitutions) => {
|
(key, substitutions) => {
|
||||||
if (Array.isArray(substitutions) && substitutions.length > 1) {
|
if (Array.isArray(substitutions) && substitutions.length > 1) {
|
||||||
|
@ -28,7 +28,8 @@ export const I18n = ({ messages, children }: I18nProps) => {
|
||||||
const { message } = messages[key];
|
const { message } = messages[key];
|
||||||
if (!substitutions) {
|
if (!substitutions) {
|
||||||
return message;
|
return message;
|
||||||
} else if (Array.isArray(substitutions)) {
|
}
|
||||||
|
if (Array.isArray(substitutions)) {
|
||||||
return substitutions.reduce(
|
return substitutions.reduce(
|
||||||
(result, substitution) =>
|
(result, substitution) =>
|
||||||
result.toString().replace(/\$.+?\$/, substitution.toString()),
|
result.toString().replace(/\$.+?\$/, substitution.toString()),
|
||||||
|
@ -50,7 +51,7 @@ export const I18n = ({ messages, children }: I18nProps) => {
|
||||||
const placeholderName = match[1];
|
const placeholderName = match[1];
|
||||||
const value = substitutions[placeholderName];
|
const value = substitutions[placeholderName];
|
||||||
if (!value) {
|
if (!value) {
|
||||||
// tslint:disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(
|
console.error(
|
||||||
`i18n: Value not provided for placeholder ${placeholderName} in key '${key}'`
|
`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';
|
import { Metadata } from 'sharp';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
convertToWebp: ConvertToWebpFn;
|
||||||
|
encryptAndUpload: EncryptAndUploadFn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type WebpData = {
|
export type WebpData = {
|
||||||
buffer: Buffer;
|
buffer: Buffer;
|
||||||
src: string;
|
src: string;
|
||||||
|
@ -13,9 +20,6 @@ export type ConvertToWebpFn = (
|
||||||
height?: number
|
height?: number
|
||||||
) => Promise<WebpData>;
|
) => Promise<WebpData>;
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
export const convertToWebp: ConvertToWebpFn = window.convertToWebp;
|
|
||||||
|
|
||||||
export type StickerData = { webp?: WebpData; emoji?: string };
|
export type StickerData = { webp?: WebpData; emoji?: string };
|
||||||
export type PackMetaData = { packId: string; key: string };
|
export type PackMetaData = { packId: string; key: string };
|
||||||
|
|
||||||
|
@ -26,5 +30,4 @@ export type EncryptAndUploadFn = (
|
||||||
onProgress?: () => unknown
|
onProgress?: () => unknown
|
||||||
) => Promise<PackMetaData>;
|
) => Promise<PackMetaData>;
|
||||||
|
|
||||||
// @ts-ignore
|
export const { encryptAndUpload, convertToWebp } = window;
|
||||||
export const encryptAndUpload: EncryptAndUploadFn = window.encryptAndUpload;
|
|
||||||
|
|
|
@ -182,7 +182,7 @@ export class MessageDetail extends React.Component<Props> {
|
||||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
||||||
<div className="module-message-detail" tabIndex={0} ref={this.focusRef}>
|
<div className="module-message-detail" tabIndex={0} ref={this.focusRef}>
|
||||||
<div className="module-message-detail__message-container">
|
<div className="module-message-detail__message-container">
|
||||||
<Message i18n={i18n} {...message} />
|
<Message {...message} i18n={i18n} />
|
||||||
</div>
|
</div>
|
||||||
<table className="module-message-detail__info">
|
<table className="module-message-detail__info">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
8
ts/model-types.d.ts
vendored
8
ts/model-types.d.ts
vendored
|
@ -18,7 +18,9 @@ type DeletesAttributesType = {
|
||||||
targetSentTimestamp: number;
|
targetSentTimestamp: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare class DeletesModelType extends Backbone.Model<DeletesAttributesType> {
|
export declare class DeletesModelType extends Backbone.Model<
|
||||||
|
DeletesAttributesType
|
||||||
|
> {
|
||||||
forMessage(message: MessageModelType): Array<DeletesModelType>;
|
forMessage(message: MessageModelType): Array<DeletesModelType>;
|
||||||
onDelete(doe: DeletesAttributesType): Promise<void>;
|
onDelete(doe: DeletesAttributesType): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -47,7 +49,9 @@ export type MessageAttributesType = {
|
||||||
sourceUuid?: string;
|
sourceUuid?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare class MessageModelType extends Backbone.Model<MessageAttributesType> {
|
export declare class MessageModelType extends Backbone.Model<
|
||||||
|
MessageAttributesType
|
||||||
|
> {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
static updateTimers(): void;
|
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 promise from 'redux-promise-middleware';
|
||||||
import { createLogger } from 'redux-logger';
|
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();
|
const env = window.getEnvironment();
|
||||||
|
|
||||||
// So Redux logging doesn't go to disk, and so we can get colors/styles
|
// So Redux logging doesn't go to disk, and so we can get colors/styles
|
||||||
const directConsole = {
|
const directConsole = {
|
||||||
// @ts-ignore
|
|
||||||
log: console._log,
|
log: console._log,
|
||||||
groupCollapsed: console.groupCollapsed,
|
groupCollapsed: console.groupCollapsed,
|
||||||
group: console.group,
|
group: console.group,
|
||||||
|
@ -27,7 +38,7 @@ const logger = createLogger({
|
||||||
// Exclude logger if we're in production mode
|
// Exclude logger if we're in production mode
|
||||||
const middlewareList = env === 'production' ? [promise] : [promise, logger];
|
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);
|
reduxCreateStore(reducer, initialState, enhancer);
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
export type CallId = any;
|
export type CallId = unknown;
|
||||||
|
|
||||||
export type CallDetailsType = {
|
export type CallDetailsType = {
|
||||||
callId: CallId;
|
callId: CallId;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable camelcase */
|
||||||
import {
|
import {
|
||||||
difference,
|
difference,
|
||||||
fromPairs,
|
fromPairs,
|
||||||
|
@ -113,7 +114,7 @@ export type MessageType = {
|
||||||
deletedForEveryone?: boolean;
|
deletedForEveryone?: boolean;
|
||||||
|
|
||||||
errors?: Array<Error>;
|
errors?: Array<Error>;
|
||||||
group_update?: any;
|
group_update?: unknown;
|
||||||
|
|
||||||
// No need to go beyond this; unused at this stage, since this goes into
|
// 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
|
// 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 {
|
return {
|
||||||
type: 'MESSAGE_SELECTED',
|
type: 'MESSAGE_SELECTED',
|
||||||
payload: {
|
payload: {
|
||||||
|
@ -567,13 +571,13 @@ function openConversationExternal(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function showInbox() {
|
function showInbox(): ShowInboxActionType {
|
||||||
return {
|
return {
|
||||||
type: 'SHOW_INBOX',
|
type: 'SHOW_INBOX',
|
||||||
payload: null,
|
payload: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function showArchivedConversations() {
|
function showArchivedConversations(): ShowArchivedConversationsActionType {
|
||||||
return {
|
return {
|
||||||
type: 'SHOW_ARCHIVED_CONVERSATIONS',
|
type: 'SHOW_ARCHIVED_CONVERSATIONS',
|
||||||
payload: null,
|
payload: null,
|
||||||
|
@ -596,7 +600,7 @@ function getEmptyState(): ConversationsStateType {
|
||||||
function hasMessageHeightChanged(
|
function hasMessageHeightChanged(
|
||||||
message: MessageType,
|
message: MessageType,
|
||||||
previous: MessageType
|
previous: MessageType
|
||||||
): Boolean {
|
): boolean {
|
||||||
const messageAttachments = message.attachments || [];
|
const messageAttachments = message.attachments || [];
|
||||||
const previousAttachments = previous.attachments || [];
|
const previousAttachments = previous.attachments || [];
|
||||||
|
|
||||||
|
@ -687,8 +691,7 @@ export function reducer(
|
||||||
const { id, data } = payload;
|
const { id, data } = payload;
|
||||||
const { conversationLookup } = state;
|
const { conversationLookup } = state;
|
||||||
|
|
||||||
let showArchived = state.showArchived;
|
let { showArchived, selectedConversation } = state;
|
||||||
let selectedConversation = state.selectedConversation;
|
|
||||||
|
|
||||||
const existing = conversationLookup[id];
|
const existing = conversationLookup[id];
|
||||||
// In the change case we only modify the lookup if we already had that conversation
|
// 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) {
|
if (first && oldest && first.received_at <= oldest.received_at) {
|
||||||
oldest = pick(first, ['id', 'received_at']);
|
oldest = pick(first, ['id', 'received_at']);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const actions = {
|
||||||
onUseEmoji,
|
onUseEmoji,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useActions = () => useBoundActions(actions);
|
export const useActions = (): typeof actions => useBoundActions(actions);
|
||||||
|
|
||||||
function onUseEmoji({ shortName }: EmojiPickDataType): OnUseEmojiAction {
|
function onUseEmoji({ shortName }: EmojiPickDataType): OnUseEmojiAction {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { useBoundActions } from '../../util/hooks';
|
||||||
// State
|
// State
|
||||||
|
|
||||||
export type ItemsStateType = {
|
export type ItemsStateType = {
|
||||||
readonly [key: string]: any;
|
readonly [key: string]: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
@ -23,7 +23,7 @@ type ItemPutExternalAction = {
|
||||||
type: 'items/PUT_EXTERNAL';
|
type: 'items/PUT_EXTERNAL';
|
||||||
payload: {
|
payload: {
|
||||||
key: string;
|
key: string;
|
||||||
value: any;
|
value: unknown;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,9 +58,9 @@ export const actions = {
|
||||||
resetItems,
|
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);
|
storageShim.put(key, value);
|
||||||
|
|
||||||
return {
|
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 {
|
return {
|
||||||
type: 'items/PUT_EXTERNAL',
|
type: 'items/PUT_EXTERNAL',
|
||||||
payload: {
|
payload: {
|
||||||
|
@ -139,4 +139,5 @@ const selectRecentEmojis = createSelector(
|
||||||
recents => recents.filter(isShortName)
|
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) {
|
for (let i = 0; i < max; i += 1) {
|
||||||
const conversation = searchResults[i];
|
const conversation = searchResults[i];
|
||||||
|
|
||||||
if (conversation.type === 'private' && !Boolean(conversation.lastMessage)) {
|
if (conversation.type === 'private' && !conversation.lastMessage) {
|
||||||
contacts.push(conversation.id);
|
contacts.push(conversation.id);
|
||||||
} else {
|
} else {
|
||||||
conversations.push(conversation.id);
|
conversations.push(conversation.id);
|
||||||
|
|
|
@ -376,7 +376,10 @@ export function reducer(
|
||||||
action: StickersActionType
|
action: StickersActionType
|
||||||
): StickersStateType {
|
): StickersStateType {
|
||||||
if (action.type === 'stickers/STICKER_PACK_ADDED') {
|
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 = {
|
const newPack = {
|
||||||
stickers: {},
|
stickers: {},
|
||||||
...payload,
|
...payload,
|
||||||
|
|
|
@ -92,5 +92,5 @@ export const reducers = {
|
||||||
user,
|
user,
|
||||||
};
|
};
|
||||||
|
|
||||||
// @ts-ignore: AnyAction breaks strong type checking inside reducers
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const reducer = combineReducers<StateType, ActionsType>(reducers);
|
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()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const FilteredCallManager = SmartCallManager as 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}>
|
<Provider store={store}>
|
||||||
<FilteredCallManager />
|
<FilteredCallManager />
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
|
@ -7,9 +7,14 @@ import { SmartCompositionArea } from '../smart/CompositionArea';
|
||||||
|
|
||||||
// Workaround: A react component's required properties are filtering up through connect()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const FilteredCompositionArea = SmartCompositionArea as 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}>
|
<Provider store={store}>
|
||||||
<FilteredCompositionArea {...props} />
|
<FilteredCompositionArea {...props} />
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
|
@ -7,9 +7,11 @@ import { SmartLeftPane } from '../smart/LeftPane';
|
||||||
|
|
||||||
// Workaround: A react component's required properties are filtering up through connect()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const FilteredLeftPane = SmartLeftPane as 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}>
|
<Provider store={store}>
|
||||||
<FilteredLeftPane />
|
<FilteredLeftPane />
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
|
@ -7,14 +7,19 @@ import { SmartSafetyNumberViewer } from '../smart/SafetyNumberViewer';
|
||||||
|
|
||||||
// Workaround: A react component's required properties are filtering up through connect()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const FilteredSafetyNumberViewer = SmartSafetyNumberViewer as any;
|
const FilteredSafetyNumberViewer = SmartSafetyNumberViewer as any;
|
||||||
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
contactID: string;
|
contactID: string;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createSafetyNumberViewer = (store: Store, props: Props) => (
|
export const createSafetyNumberViewer = (
|
||||||
|
store: Store,
|
||||||
|
props: Props
|
||||||
|
): React.ReactElement => (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<FilteredSafetyNumberViewer {...props} />
|
<FilteredSafetyNumberViewer {...props} />
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
|
@ -7,9 +7,14 @@ import { SmartShortcutGuideModal } from '../smart/ShortcutGuideModal';
|
||||||
|
|
||||||
// Workaround: A react component's required properties are filtering up through connect()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const FilteredShortcutGuideModal = SmartShortcutGuideModal as 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}>
|
<Provider store={store}>
|
||||||
<FilteredShortcutGuideModal {...props} />
|
<FilteredShortcutGuideModal {...props} />
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
|
@ -7,9 +7,11 @@ import { SmartStickerManager } from '../smart/StickerManager';
|
||||||
|
|
||||||
// Workaround: A react component's required properties are filtering up through connect()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const FilteredStickerManager = SmartStickerManager as 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}>
|
<Provider store={store}>
|
||||||
<FilteredStickerManager />
|
<FilteredStickerManager />
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
|
@ -7,9 +7,14 @@ import { SmartStickerPreviewModal } from '../smart/StickerPreviewModal';
|
||||||
|
|
||||||
// Workaround: A react component's required properties are filtering up through connect()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const FilteredStickerPreviewModal = SmartStickerPreviewModal as 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}>
|
<Provider store={store}>
|
||||||
<FilteredStickerPreviewModal {...props} />
|
<FilteredStickerPreviewModal {...props} />
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
|
@ -7,9 +7,14 @@ import { SmartTimeline } from '../smart/Timeline';
|
||||||
|
|
||||||
// Workaround: A react component's required properties are filtering up through connect()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const FilteredTimeline = SmartTimeline as 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}>
|
<Provider store={store}>
|
||||||
<FilteredTimeline {...props} />
|
<FilteredTimeline {...props} />
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
|
@ -49,7 +49,7 @@ export const getSelectedMessage = createSelector(
|
||||||
getConversations,
|
getConversations,
|
||||||
(state: ConversationsStateType): SelectedMessageType | undefined => {
|
(state: ConversationsStateType): SelectedMessageType | undefined => {
|
||||||
if (!state.selectedMessage) {
|
if (!state.selectedMessage) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -136,10 +136,7 @@ export const _getLeftPaneLists = (
|
||||||
const max = values.length;
|
const max = values.length;
|
||||||
for (let i = 0; i < max; i += 1) {
|
for (let i = 0; i < max; i += 1) {
|
||||||
let conversation = values[i];
|
let conversation = values[i];
|
||||||
if (!conversation.activeAt) {
|
if (conversation.activeAt) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedConversation === conversation.id) {
|
if (selectedConversation === conversation.id) {
|
||||||
conversation = {
|
conversation = {
|
||||||
...conversation,
|
...conversation,
|
||||||
|
@ -153,6 +150,7 @@ export const _getLeftPaneLists = (
|
||||||
conversations.push(conversation);
|
conversations.push(conversation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
conversations.sort(comparator);
|
conversations.sort(comparator);
|
||||||
archivedConversations.sort(comparator);
|
archivedConversations.sort(comparator);
|
||||||
|
@ -220,7 +218,7 @@ export const getConversationSelector = createSelector(
|
||||||
return (id: string) => {
|
return (id: string) => {
|
||||||
const conversation = lookup[id];
|
const conversation = lookup[id];
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return selector(conversation);
|
return selector(conversation);
|
||||||
|
@ -236,17 +234,12 @@ export const getConversationSelector = createSelector(
|
||||||
// - message details
|
// - message details
|
||||||
export function _messageSelector(
|
export function _messageSelector(
|
||||||
message: MessageType,
|
message: MessageType,
|
||||||
// @ts-ignore
|
_ourNumber: string,
|
||||||
ourNumber: string,
|
_regionCode: string,
|
||||||
// @ts-ignore
|
|
||||||
regionCode: string,
|
|
||||||
interactionMode: 'mouse' | 'keyboard',
|
interactionMode: 'mouse' | 'keyboard',
|
||||||
// @ts-ignore
|
_conversation?: ConversationType,
|
||||||
conversation?: ConversationType,
|
_author?: ConversationType,
|
||||||
// @ts-ignore
|
_quoted?: ConversationType,
|
||||||
author?: ConversationType,
|
|
||||||
// @ts-ignore
|
|
||||||
quoted?: ConversationType,
|
|
||||||
selectedMessageId?: string,
|
selectedMessageId?: string,
|
||||||
selectedMessageCounter?: number
|
selectedMessageCounter?: number
|
||||||
): TimelineItemType {
|
): TimelineItemType {
|
||||||
|
@ -319,7 +312,7 @@ export const getMessageSelector = createSelector(
|
||||||
return (id: string) => {
|
return (id: string) => {
|
||||||
const message = messageLookup[id];
|
const message = messageLookup[id];
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { conversationId, source, type, quote } = message;
|
const { conversationId, source, type, quote } = message;
|
||||||
|
@ -441,7 +434,7 @@ export const getConversationMessagesSelector = createSelector(
|
||||||
return (id: string): TimelinePropsType | undefined => {
|
return (id: string): TimelinePropsType | undefined => {
|
||||||
const conversation = messagesByConversation[id];
|
const conversation = messagesByConversation[id];
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return conversationMessagesSelector(conversation);
|
return conversationMessagesSelector(conversation);
|
||||||
|
|
|
@ -207,7 +207,7 @@ export const getSearchResults = createSelector(
|
||||||
items,
|
items,
|
||||||
messagesLoading,
|
messagesLoading,
|
||||||
noResults,
|
noResults,
|
||||||
regionCode: regionCode,
|
regionCode,
|
||||||
searchConversationName,
|
searchConversationName,
|
||||||
searchTerm: state.query,
|
searchTerm: state.query,
|
||||||
selectedConversationId,
|
selectedConversationId,
|
||||||
|
@ -218,14 +218,10 @@ export const getSearchResults = createSelector(
|
||||||
|
|
||||||
export function _messageSearchResultSelector(
|
export function _messageSearchResultSelector(
|
||||||
message: MessageSearchResultType,
|
message: MessageSearchResultType,
|
||||||
// @ts-ignore
|
_ourNumber: string,
|
||||||
ourNumber: string,
|
_regionCode: string,
|
||||||
// @ts-ignore
|
_sender?: ConversationType,
|
||||||
regionCode: string,
|
_recipient?: ConversationType,
|
||||||
// @ts-ignore
|
|
||||||
sender?: ConversationType,
|
|
||||||
// @ts-ignore
|
|
||||||
recipient?: ConversationType,
|
|
||||||
searchConversationId?: string,
|
searchConversationId?: string,
|
||||||
selectedMessageId?: string
|
selectedMessageId?: string
|
||||||
): MessageSearchResultPropsDataType {
|
): MessageSearchResultPropsDataType {
|
||||||
|
@ -282,7 +278,7 @@ export const getMessageSearchResultSelector = createSelector(
|
||||||
return (id: string) => {
|
return (id: string) => {
|
||||||
const message = messageSearchResultLookup[id];
|
const message = messageSearchResultLookup[id];
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { conversationId, source, type } = message;
|
const { conversationId, source, type } = message;
|
||||||
|
|
|
@ -31,12 +31,12 @@ const getSticker = (
|
||||||
): StickerType | undefined => {
|
): StickerType | undefined => {
|
||||||
const pack = packs[packId];
|
const pack = packs[packId];
|
||||||
if (!pack) {
|
if (!pack) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sticker = pack.stickers[stickerId];
|
const sticker = pack.stickers[stickerId];
|
||||||
if (!sticker) {
|
if (!sticker) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEphemeral = pack.status === 'ephemeral';
|
const isEphemeral = pack.status === 'ephemeral';
|
||||||
|
@ -67,7 +67,7 @@ export const translatePackFromDB = (
|
||||||
blessedPacks: Dictionary<boolean>,
|
blessedPacks: Dictionary<boolean>,
|
||||||
stickersPath: string,
|
stickersPath: string,
|
||||||
tempPath: string
|
tempPath: string
|
||||||
) => {
|
): StickerPackType => {
|
||||||
const { id, stickers, status, coverStickerId } = pack;
|
const { id, stickers, status, coverStickerId } = pack;
|
||||||
const isEphemeral = status === 'ephemeral';
|
const isEphemeral = status === 'ephemeral';
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ export const translatePackFromDB = (
|
||||||
const filterAndTransformPacks = (
|
const filterAndTransformPacks = (
|
||||||
packs: Dictionary<StickerPackDBType>,
|
packs: Dictionary<StickerPackDBType>,
|
||||||
packFilter: (sticker: StickerPackDBType) => boolean,
|
packFilter: (sticker: StickerPackDBType) => boolean,
|
||||||
packSort: (sticker: StickerPackDBType) => any,
|
packSort: (sticker: StickerPackDBType) => number | null,
|
||||||
blessedPacks: Dictionary<boolean>,
|
blessedPacks: Dictionary<boolean>,
|
||||||
stickersPath: string,
|
stickersPath: string,
|
||||||
tempPath: string
|
tempPath: string
|
||||||
|
|
|
@ -32,7 +32,7 @@ export const getUserUuid = createSelector(
|
||||||
|
|
||||||
export const getUserAgent = createSelector(
|
export const getUserAgent = createSelector(
|
||||||
getItems,
|
getItems,
|
||||||
(state: ItemsStateType): string => state.userAgent
|
(state: ItemsStateType): string => state.userAgent as string
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getIntl = createSelector(
|
export const getIntl = createSelector(
|
||||||
|
|
|
@ -10,7 +10,9 @@ import { SmartCallingDeviceSelection } from './CallingDeviceSelection';
|
||||||
|
|
||||||
// Workaround: A react component's required properties are filtering up through connect()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const FilteredCallingDeviceSelection = SmartCallingDeviceSelection as any;
|
const FilteredCallingDeviceSelection = SmartCallingDeviceSelection as any;
|
||||||
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
function renderDeviceSelection(): JSX.Element {
|
function renderDeviceSelection(): JSX.Element {
|
||||||
return <FilteredCallingDeviceSelection />;
|
return <FilteredCallingDeviceSelection />;
|
||||||
|
|
|
@ -89,4 +89,5 @@ const dispatchPropsMap = {
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, 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;
|
conversationId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SmartContactName = (props: ExternalProps) => {
|
export const SmartContactName: React.ComponentType<ExternalProps> = props => {
|
||||||
const { conversationId } = props;
|
const { conversationId } = props;
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||||
const getConversation = useSelector<StateType, GetConversationByIdType>(
|
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()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const FilteredSmartMainHeader = SmartMainHeader as any;
|
const FilteredSmartMainHeader = SmartMainHeader as any;
|
||||||
const FilteredSmartMessageSearchResult = SmartMessageSearchResult as any;
|
const FilteredSmartMessageSearchResult = SmartMessageSearchResult as any;
|
||||||
const FilteredSmartNetworkStatus = SmartNetworkStatus as any;
|
const FilteredSmartNetworkStatus = SmartNetworkStatus as any;
|
||||||
const FilteredSmartUpdateDialog = SmartUpdateDialog as any;
|
const FilteredSmartUpdateDialog = SmartUpdateDialog as any;
|
||||||
const FilteredSmartExpiredBuildDialog = SmartExpiredBuildDialog as any;
|
const FilteredSmartExpiredBuildDialog = SmartExpiredBuildDialog as any;
|
||||||
const FilteredSmartRelinkDialog = SmartRelinkDialog as any;
|
const FilteredSmartRelinkDialog = SmartRelinkDialog as any;
|
||||||
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
function renderExpiredBuildDialog(): JSX.Element {
|
function renderExpiredBuildDialog(): JSX.Element {
|
||||||
return <FilteredSmartExpiredBuildDialog />;
|
return <FilteredSmartExpiredBuildDialog />;
|
||||||
|
|
|
@ -22,11 +22,13 @@ import { SmartEmojiPicker } from './EmojiPicker';
|
||||||
|
|
||||||
// Workaround: A react component's required properties are filtering up through connect()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const FilteredSmartTimelineItem = SmartTimelineItem as any;
|
const FilteredSmartTimelineItem = SmartTimelineItem as any;
|
||||||
const FilteredSmartTypingBubble = SmartTypingBubble as any;
|
const FilteredSmartTypingBubble = SmartTypingBubble as any;
|
||||||
const FilteredSmartLastSeenIndicator = SmartLastSeenIndicator as any;
|
const FilteredSmartLastSeenIndicator = SmartLastSeenIndicator as any;
|
||||||
const FilteredSmartHeroRow = SmartHeroRow as any;
|
const FilteredSmartHeroRow = SmartHeroRow as any;
|
||||||
const FilteredSmartTimelineLoadingRow = SmartTimelineLoadingRow as any;
|
const FilteredSmartTimelineLoadingRow = SmartTimelineLoadingRow as any;
|
||||||
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
type ExternalProps = {
|
type ExternalProps = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -38,7 +40,7 @@ type ExternalProps = {
|
||||||
function renderItem(
|
function renderItem(
|
||||||
messageId: string,
|
messageId: string,
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
actionProps: Object
|
actionProps: Record<string, unknown>
|
||||||
): JSX.Element {
|
): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<FilteredSmartTimelineItem
|
<FilteredSmartTimelineItem
|
||||||
|
@ -61,7 +63,7 @@ function renderEmojiPicker({
|
||||||
onPickEmoji={onPickEmoji}
|
onPickEmoji={onPickEmoji}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
style={style}
|
style={style}
|
||||||
disableSkinTones={true}
|
disableSkinTones
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -112,4 +114,5 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
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()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const FilteredSmartContactName = SmartContactName as any;
|
const FilteredSmartContactName = SmartContactName as any;
|
||||||
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
function renderContact(conversationId: string): JSX.Element {
|
function renderContact(conversationId: string): JSX.Element {
|
||||||
return <FilteredSmartContactName conversationId={conversationId} />;
|
return <FilteredSmartContactName conversationId={conversationId} />;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { isNumber } from 'lodash';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
import { mapDispatchToProps } from '../actions';
|
||||||
import { isNumber } from 'lodash';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
STATE_ENUM,
|
STATE_ENUM,
|
||||||
|
@ -26,11 +26,16 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||||
|
|
||||||
const { isLoadingMessages, loadCountdownStart } = conversation;
|
const { isLoadingMessages, loadCountdownStart } = conversation;
|
||||||
|
|
||||||
const loadingState: STATE_ENUM = isLoadingMessages
|
let loadingState: STATE_ENUM;
|
||||||
? 'loading'
|
|
||||||
: isNumber(loadCountdownStart)
|
if (isLoadingMessages) {
|
||||||
? 'countdown'
|
loadingState = 'loading';
|
||||||
: 'idle';
|
} else if (isNumber(loadCountdownStart)) {
|
||||||
|
loadingState = 'countdown';
|
||||||
|
} else {
|
||||||
|
loadingState = 'idle';
|
||||||
|
}
|
||||||
|
|
||||||
const duration = loadingState === 'countdown' ? LOAD_COUNTDOWN : undefined;
|
const duration = loadingState === 'countdown' ? LOAD_COUNTDOWN : undefined;
|
||||||
const expiresAt =
|
const expiresAt =
|
||||||
loadingState === 'countdown' && loadCountdownStart
|
loadingState === 'countdown' && loadCountdownStart
|
||||||
|
|
|
@ -64,7 +64,7 @@ export function getExtensionForDisplay({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!contentType) {
|
if (!contentType) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const slash = contentType.indexOf('/');
|
const slash = contentType.indexOf('/');
|
||||||
|
@ -72,10 +72,12 @@ export function getExtensionForDisplay({
|
||||||
return contentType.slice(slash + 1);
|
return contentType.slice(slash + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAudio(attachments?: Array<AttachmentType>) {
|
export function isAudio(
|
||||||
|
attachments?: Array<AttachmentType>
|
||||||
|
): boolean | undefined {
|
||||||
return (
|
return (
|
||||||
attachments &&
|
attachments &&
|
||||||
attachments[0] &&
|
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 } =
|
const { height, width } =
|
||||||
attachments && attachments[0] ? attachments[0] : { height: 0, width: 0 };
|
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) {
|
if (attachment.thumbnail) {
|
||||||
return attachment.thumbnail.url;
|
return attachment.thumbnail.url;
|
||||||
}
|
}
|
||||||
|
@ -106,7 +110,7 @@ export function getThumbnailUrl(attachment: AttachmentType) {
|
||||||
return getUrl(attachment);
|
return getUrl(attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUrl(attachment: AttachmentType) {
|
export function getUrl(attachment: AttachmentType): string {
|
||||||
if (attachment.screenshot) {
|
if (attachment.screenshot) {
|
||||||
return attachment.screenshot.url;
|
return attachment.screenshot.url;
|
||||||
}
|
}
|
||||||
|
@ -114,7 +118,9 @@ export function getUrl(attachment: AttachmentType) {
|
||||||
return attachment.url;
|
return attachment.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isImage(attachments?: Array<AttachmentType>) {
|
export function isImage(
|
||||||
|
attachments?: Array<AttachmentType>
|
||||||
|
): boolean | undefined {
|
||||||
return (
|
return (
|
||||||
attachments &&
|
attachments &&
|
||||||
attachments[0] &&
|
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 (
|
return (
|
||||||
attachment &&
|
attachment &&
|
||||||
attachment.contentType &&
|
attachment.contentType &&
|
||||||
isImageTypeSupported(attachment.contentType)
|
isImageTypeSupported(attachment.contentType)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export function hasImage(attachments?: Array<AttachmentType>) {
|
export function hasImage(
|
||||||
|
attachments?: Array<AttachmentType>
|
||||||
|
): string | boolean | undefined {
|
||||||
return (
|
return (
|
||||||
attachments &&
|
attachments &&
|
||||||
attachments[0] &&
|
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]);
|
return attachments && isVideoAttachment(attachments[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVideoAttachment(attachment?: AttachmentType) {
|
export function isVideoAttachment(
|
||||||
|
attachment?: AttachmentType
|
||||||
|
): boolean | undefined {
|
||||||
return (
|
return (
|
||||||
attachment &&
|
attachment &&
|
||||||
attachment.contentType &&
|
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;
|
const firstAttachment = attachments ? attachments[0] : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -308,7 +324,7 @@ export const isFile = (attachment: Attachment): boolean => {
|
||||||
export const isVoiceMessage = (attachment: Attachment): boolean => {
|
export const isVoiceMessage = (attachment: Attachment): boolean => {
|
||||||
const flag = SignalService.AttachmentPointer.Flags.VOICE_MESSAGE;
|
const flag = SignalService.AttachmentPointer.Flags.VOICE_MESSAGE;
|
||||||
const hasFlag =
|
const hasFlag =
|
||||||
// tslint:disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
!is.undefined(attachment.flags) && (attachment.flags & flag) === flag;
|
!is.undefined(attachment.flags) && (attachment.flags & flag) === flag;
|
||||||
if (hasFlag) {
|
if (hasFlag) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -390,7 +406,7 @@ export const getFileExtension = (
|
||||||
attachment: Attachment
|
attachment: Attachment
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
if (!attachment.contentType) {
|
if (!attachment.contentType) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (attachment.contentType) {
|
switch (attachment.contentType) {
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
// @ts-ignore
|
import { format as formatPhoneNumber } from './PhoneNumber';
|
||||||
import Attachments from '../../app/attachments';
|
|
||||||
import { format as formatPhoneNumber } from '../types/PhoneNumber';
|
|
||||||
|
|
||||||
export interface ContactType {
|
export interface ContactType {
|
||||||
name?: Name;
|
name?: Name;
|
||||||
|
@ -76,7 +74,7 @@ export function contactSelector(
|
||||||
signalAccount?: string;
|
signalAccount?: string;
|
||||||
getAbsoluteAttachmentPath: (path: string) => string;
|
getAbsoluteAttachmentPath: (path: string) => string;
|
||||||
}
|
}
|
||||||
) {
|
): ContactType {
|
||||||
const { getAbsoluteAttachmentPath, signalAccount, regionCode } = options;
|
const { getAbsoluteAttachmentPath, signalAccount, regionCode } = options;
|
||||||
|
|
||||||
let { avatar } = contact;
|
let { avatar } = contact;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
export enum Dialogs {
|
export enum Dialogs {
|
||||||
None,
|
None,
|
||||||
Update,
|
Update,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
type LogFunction = (...args: Array<any>) => void;
|
type LogFunction = (...args: Array<unknown>) => void;
|
||||||
|
|
||||||
export type LoggerType = {
|
export type LoggerType = {
|
||||||
fatal: LogFunction;
|
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_OCTET_STREAM = 'application/octet-stream' as MIMEType;
|
||||||
export const APPLICATION_JSON = 'application/json' as MIMEType;
|
export const APPLICATION_JSON = 'application/json' as MIMEType;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
import { Attachment } from './Attachment';
|
import { Attachment } from './Attachment';
|
||||||
import { ContactType } from './Contact';
|
import { ContactType } from './Contact';
|
||||||
import { IndexableBoolean, IndexablePresence } from './IndexedDB';
|
import { IndexableBoolean, IndexablePresence } from './IndexedDB';
|
||||||
|
@ -21,7 +23,7 @@ export type IncomingMessage = Readonly<
|
||||||
// Optional
|
// Optional
|
||||||
body?: string;
|
body?: string;
|
||||||
decrypted_at?: number;
|
decrypted_at?: number;
|
||||||
errors?: Array<any>;
|
errors?: Array<Error>;
|
||||||
expireTimer?: number;
|
expireTimer?: number;
|
||||||
messageTimer?: number; // deprecated
|
messageTimer?: number; // deprecated
|
||||||
isViewOnce?: number;
|
isViewOnce?: number;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { instance, PhoneNumberFormat } from '../util/libphonenumberInstance';
|
|
||||||
import memoizee from 'memoizee';
|
import memoizee from 'memoizee';
|
||||||
|
import { instance, PhoneNumberFormat } from '../util/libphonenumberInstance';
|
||||||
|
|
||||||
function _format(
|
function _format(
|
||||||
phoneNumber: string,
|
phoneNumber: string,
|
||||||
|
@ -73,8 +73,8 @@ export function normalize(
|
||||||
return instance.format(parsedNumber, PhoneNumberFormat.E164);
|
return instance.format(parsedNumber, PhoneNumberFormat.E164);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@ export enum AudioNotificationSupport {
|
||||||
export function getAudioNotificationSupport(): AudioNotificationSupport {
|
export function getAudioNotificationSupport(): AudioNotificationSupport {
|
||||||
if (OS.isWindows(MIN_WINDOWS_VERSION) || OS.isMacOS()) {
|
if (OS.isWindows(MIN_WINDOWS_VERSION) || OS.isMacOS()) {
|
||||||
return AudioNotificationSupport.Native;
|
return AudioNotificationSupport.Native;
|
||||||
} else if (OS.isLinux()) {
|
}
|
||||||
|
if (OS.isLinux()) {
|
||||||
return AudioNotificationSupport.Custom;
|
return AudioNotificationSupport.Custom;
|
||||||
}
|
}
|
||||||
return AudioNotificationSupport.None;
|
return AudioNotificationSupport.None;
|
||||||
|
@ -22,11 +23,11 @@ export const isAudioNotificationSupported = (): boolean =>
|
||||||
|
|
||||||
// Using `Notification::tag` has a bug on Windows 7:
|
// Using `Notification::tag` has a bug on Windows 7:
|
||||||
// https://github.com/electron/electron/issues/11189
|
// https://github.com/electron/electron/issues/11189
|
||||||
export const isNotificationGroupingSupported = () =>
|
export const isNotificationGroupingSupported = (): boolean =>
|
||||||
!OS.isWindows() || OS.isWindows(MIN_WINDOWS_VERSION);
|
!OS.isWindows() || OS.isWindows(MIN_WINDOWS_VERSION);
|
||||||
|
|
||||||
// the "hide menu bar" option is specific to Windows and Linux
|
// 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
|
// 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();
|
static sounds = new Map();
|
||||||
|
|
||||||
private readonly context = new AudioContext();
|
private readonly context = new AudioContext();
|
||||||
|
|
||||||
private readonly loop: boolean;
|
private readonly loop: boolean;
|
||||||
|
|
||||||
private node?: AudioBufferSourceNode;
|
private node?: AudioBufferSourceNode;
|
||||||
|
|
||||||
private readonly src: string;
|
private readonly src: string;
|
||||||
|
|
||||||
constructor(options: SoundOpts) {
|
constructor(options: SoundOpts) {
|
||||||
|
@ -16,7 +19,7 @@ export class Sound {
|
||||||
this.src = options.src;
|
this.src = options.src;
|
||||||
}
|
}
|
||||||
|
|
||||||
async play() {
|
async play(): Promise<void> {
|
||||||
if (!Sound.sounds.has(this.src)) {
|
if (!Sound.sounds.has(this.src)) {
|
||||||
try {
|
try {
|
||||||
const buffer = await Sound.loadSoundFile(this.src);
|
const buffer = await Sound.loadSoundFile(this.src);
|
||||||
|
@ -44,7 +47,7 @@ export class Sound {
|
||||||
this.node = soundNode;
|
this.node = soundNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop(): void {
|
||||||
if (this.node) {
|
if (this.node) {
|
||||||
this.node.stop(0);
|
this.node.stop(0);
|
||||||
this.node = undefined;
|
this.node = undefined;
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import PQueue from 'p-queue';
|
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 = [];
|
window.batchers = [];
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
window.waitForAllBatchers = async () => {
|
window.waitForAllBatchers = async () => {
|
||||||
// @ts-ignore
|
|
||||||
await Promise.all(window.batchers.map(item => item.flushAndWait()));
|
await Promise.all(window.batchers.map(item => item.flushAndWait()));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,7 +37,7 @@ export function createBatcher<ItemType>(
|
||||||
options: BatcherOptionsType<ItemType>
|
options: BatcherOptionsType<ItemType>
|
||||||
): BatcherType<ItemType> {
|
): BatcherType<ItemType> {
|
||||||
let batcher: BatcherType<ItemType>;
|
let batcher: BatcherType<ItemType>;
|
||||||
let timeout: any;
|
let timeout: NodeJS.Timeout | null;
|
||||||
let items: Array<ItemType> = [];
|
let items: Array<ItemType> = [];
|
||||||
const queue = new PQueue({ concurrency: 1 });
|
const queue = new PQueue({ concurrency: 1 });
|
||||||
|
|
||||||
|
@ -70,18 +75,19 @@ export function createBatcher<ItemType>(
|
||||||
async function onIdle() {
|
async function onIdle() {
|
||||||
while (anyPending()) {
|
while (anyPending()) {
|
||||||
if (queue.size > 0 || queue.pending > 0) {
|
if (queue.size > 0 || queue.pending > 0) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await queue.onIdle();
|
await queue.onIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items.length > 0) {
|
if (items.length > 0) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await sleep(options.wait * 2);
|
await sleep(options.wait * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unregister() {
|
function unregister() {
|
||||||
// @ts-ignore
|
window.batchers = window.batchers.filter(item => item !== batcher);
|
||||||
window.batchers = window.batchers.filter((item: any) => item !== batcher);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function flushAndWait() {
|
async function flushAndWait() {
|
||||||
|
@ -104,7 +110,6 @@ export function createBatcher<ItemType>(
|
||||||
unregister,
|
unregister,
|
||||||
};
|
};
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
window.batchers.push(batcher);
|
window.batchers.push(batcher);
|
||||||
|
|
||||||
return batcher;
|
return batcher;
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { Sound } from './Sound';
|
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
|
import { Sound } from './Sound';
|
||||||
|
|
||||||
const ringtoneEventQueue = new PQueue({ concurrency: 1 });
|
const ringtoneEventQueue = new PQueue({ concurrency: 1 });
|
||||||
|
|
||||||
class CallingTones {
|
class CallingTones {
|
||||||
private ringtone?: Sound;
|
private ringtone?: Sound;
|
||||||
|
|
||||||
async playEndCall() {
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
async playEndCall(): Promise<void> {
|
||||||
const canPlayTone = await window.getCallRingtoneNotification();
|
const canPlayTone = await window.getCallRingtoneNotification();
|
||||||
if (!canPlayTone) {
|
if (!canPlayTone) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export function cleanSearchTerm(searchTerm: string) {
|
export function cleanSearchTerm(searchTerm: string): string {
|
||||||
const lowercase = searchTerm.toLowerCase();
|
const lowercase = searchTerm.toLowerCase();
|
||||||
const withoutSpecialCharacters = lowercase.replace(
|
const withoutSpecialCharacters = lowercase.replace(
|
||||||
/([!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~])/g,
|
/([!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~])/g,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
// We don't include unicode-12.1.0 because it's over 100MB in size
|
// 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
|
// 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 {
|
function isAllCKJV(name: string): boolean {
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const codePoint of name) {
|
for (const codePoint of name) {
|
||||||
if (!isCKJV(codePoint)) {
|
if (!isCKJV(codePoint)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -59,7 +62,6 @@ function isAllCKJV(name: string): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line cyclomatic-complexity
|
|
||||||
function isCKJV(codePoint: string) {
|
function isCKJV(codePoint: string) {
|
||||||
if (codePoint === ' ') {
|
if (codePoint === ' ') {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -5,7 +5,7 @@ const ONE_DAY = 24 * 60 * 60 * 1000;
|
||||||
export async function deleteForEveryone(
|
export async function deleteForEveryone(
|
||||||
message: MessageModelType,
|
message: MessageModelType,
|
||||||
doe: DeletesModelType,
|
doe: DeletesModelType,
|
||||||
shouldPersist: boolean = true
|
shouldPersist = true
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Make sure the server timestamps for the DOE and the matching message
|
// Make sure the server timestamps for the DOE and the matching message
|
||||||
// are less than one day apart
|
// are less than one day apart
|
||||||
|
|
|
@ -2,7 +2,7 @@ import moment from 'moment';
|
||||||
|
|
||||||
const HOUR = 1000 * 60 * 60;
|
const HOUR = 1000 * 60 * 60;
|
||||||
|
|
||||||
export function formatDuration(seconds: number) {
|
export function formatDuration(seconds: number): string {
|
||||||
const time = moment.utc(seconds * 1000);
|
const time = moment.utc(seconds * 1000);
|
||||||
|
|
||||||
if (seconds > HOUR) {
|
if (seconds > HOUR) {
|
||||||
|
|
|
@ -35,7 +35,7 @@ function isYear(timestamp: moment.Moment) {
|
||||||
export function formatRelativeTime(
|
export function formatRelativeTime(
|
||||||
rawTimestamp: number | Date,
|
rawTimestamp: number | Date,
|
||||||
options: { extended?: boolean; i18n: LocalizerType }
|
options: { extended?: boolean; i18n: LocalizerType }
|
||||||
) {
|
): string {
|
||||||
const { extended, i18n } = options;
|
const { extended, i18n } = options;
|
||||||
|
|
||||||
const formats = extended ? getExtendedFormats(i18n) : getShortFormats(i18n);
|
const formats = extended ? getExtendedFormats(i18n) : getShortFormats(i18n);
|
||||||
|
@ -45,13 +45,17 @@ export function formatRelativeTime(
|
||||||
|
|
||||||
if (diff.years() >= 1 || !isYear(timestamp)) {
|
if (diff.years() >= 1 || !isYear(timestamp)) {
|
||||||
return replaceSuffix(timestamp.format(formats.y));
|
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));
|
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));
|
return replaceSuffix(timestamp.format(formats.d));
|
||||||
} else if (diff.hours() >= 1) {
|
}
|
||||||
|
if (diff.hours() >= 1) {
|
||||||
return i18n('hoursAgo', [String(diff.hours())]);
|
return i18n('hoursAgo', [String(diff.hours())]);
|
||||||
} else if (diff.minutes() >= 1) {
|
}
|
||||||
|
if (diff.minutes() >= 1) {
|
||||||
return i18n('minutesAgo', [String(diff.minutes())]);
|
return i18n('minutesAgo', [String(diff.minutes())]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,14 +7,14 @@ function removeNonInitials(name: string) {
|
||||||
|
|
||||||
export function getInitials(name?: string): string | undefined {
|
export function getInitials(name?: string): string | undefined {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleaned = removeNonInitials(name);
|
const cleaned = removeNonInitials(name);
|
||||||
const parts = cleaned.split(' ');
|
const parts = cleaned.split(' ');
|
||||||
const initials = parts.map(part => part.trim()[0]);
|
const initials = parts.map(part => part.trim()[0]);
|
||||||
if (!initials.length) {
|
if (!initials.length) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return initials.slice(0, 2).join('');
|
return initials.slice(0, 2).join('');
|
||||||
|
|
|
@ -11,7 +11,7 @@ export function getStringForProfileChange(
|
||||||
change: ProfileNameChangeType,
|
change: ProfileNameChangeType,
|
||||||
changedContact: ConversationType,
|
changedContact: ConversationType,
|
||||||
i18n: LocalizerType
|
i18n: LocalizerType
|
||||||
) {
|
): string {
|
||||||
if (change.type === 'name') {
|
if (change.type === 'name') {
|
||||||
return changedContact.name
|
return changedContact.name
|
||||||
? i18n('contactChangedProfileName', {
|
? i18n('contactChangedProfileName', {
|
||||||
|
@ -23,7 +23,7 @@ export function getStringForProfileChange(
|
||||||
oldProfile: change.oldName,
|
oldProfile: change.oldName,
|
||||||
newProfile: change.newName,
|
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;
|
const NINETY_ONE_DAYS = 86400 * 91 * 1000;
|
||||||
|
|
||||||
export function hasExpired() {
|
export function hasExpired(): boolean {
|
||||||
const { getExpiration, log } = window;
|
const { getExpiration, log } = window;
|
||||||
|
|
||||||
let buildExpiration = 0;
|
let buildExpiration = 0;
|
||||||
|
@ -31,5 +31,5 @@ export function hasExpired() {
|
||||||
return Date.now() > buildExpiration && tooFarIntoFuture;
|
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
|
// Restore focus on teardown
|
||||||
export const useRestoreFocus = (
|
export const useRestoreFocus = (
|
||||||
// The ref for the element to receive initial focus
|
// 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
|
// Allow for an optional root element that must exist
|
||||||
root: boolean | HTMLElement | null = true
|
root: boolean | HTMLElement | null = true
|
||||||
) => {
|
): void => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!root) {
|
if (!root) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastFocused = document.activeElement as any;
|
const lastFocused = document.activeElement as HTMLElement;
|
||||||
|
|
||||||
if (focusRef.current) {
|
if (focusRef.current) {
|
||||||
focusRef.current.focus();
|
focusRef.current.focus();
|
||||||
}
|
}
|
||||||
|
@ -33,10 +34,10 @@ export const useRestoreFocus = (
|
||||||
|
|
||||||
export const useBoundActions = <T extends ActionCreatorsMapObject>(
|
export const useBoundActions = <T extends ActionCreatorsMapObject>(
|
||||||
actions: T
|
actions: T
|
||||||
) => {
|
): T => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
return React.useMemo(() => {
|
return React.useMemo(() => {
|
||||||
return bindActionCreators(actions, dispatch);
|
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